From 4932c9c6c7d0a3f121504f31fd9110f991e1c589 Mon Sep 17 00:00:00 2001 From: Josh Gao Date: Mon, 16 Jun 2025 16:08:05 +0800 Subject: [PATCH] =?UTF-8?q?doc=20&=20fix:=20=E5=A2=9E=E5=8A=A0comtool=20pl?= =?UTF-8?q?ugin,=20=E4=BF=AE=E6=94=B9=E4=B8=8A=E4=BC=A0=E9=81=A5=E6=B5=8B?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/HSJL 上位机串口通信协议.md | 33 +-- doc/comtool-plugin-hsjl.py | 433 +++++++++++++++++++++++++++++++++ src/systembus/systembus.c | 8 +- 3 files changed, 455 insertions(+), 19 deletions(-) create mode 100644 doc/comtool-plugin-hsjl.py diff --git a/doc/HSJL 上位机串口通信协议.md b/doc/HSJL 上位机串口通信协议.md index af8034a..eb21700 100644 --- a/doc/HSJL 上位机串口通信协议.md +++ b/doc/HSJL 上位机串口通信协议.md @@ -1,7 +1,7 @@ # HSJL 上位机串口通信协议 > 版本:v1.2 -> 日期:2025 年 6 月 12 日 +> 日期:2025 年 6 月 16 日 --- @@ -46,19 +46,19 @@ block-beta | 字段名 | 字节数 (Byte) | 说明 | |--|--|--| -| 帧头 | 2 | 固定值:`0x54 0x5A` | +| 帧头 | 2 | 固定值:`0x5A 0x54` | | 数据段 | ≤950 | 业务数据内容 | | Dummy Byte | 1 | 固定填充:`0x00` | | 校验和 | 1 | 所有**数据段**字节累加和(无符号累加) | | 数据段长度 | 2 | 数据段字节数(不含 Dummy/校验/帧尾) | -| 帧尾 | 2 | 固定值:`0xFE 0x5A` | +| 帧尾 | 2 | 固定值:`0x5A 0xFE` | ### 帧内冲突规避规则 -为防止数据段中出现帧尾 `0xFE 0x5A`: +为防止数据段中出现帧尾 `0x5A 0xFE`: -- 若数据段中出现 `0xFE 0x5A`,需插入 `0x00` 变成 `0x00 0xFE 0x5A` -- 接收端收到 `0x00 0xFE 0x5A` 还原为 `0xFE 0x5A` +- 若数据段中出现 `0x5A 0xFE`,需插入 `0x00` 变成 `0x00 0x5A 0xFE` +- 接收端收到 `0x00 0x5A 0xFE` 还原为 `0x5A 0xFE` ### 帧长说明 @@ -192,13 +192,13 @@ block-beta ### (可选)遥测指令 -遥测指令上传的 Payload 由 5 部分组成,顺序存储,共 188 字节: +遥测指令上传的 Payload 由 5 部分组成,顺序存储,共 200 字节: | 字段名 | 字节数 (Byte) | | ------------ | ------------- | | 时间戳 | 8 | | 总线日志 | 16 | -| 链路层日志 | 88 | +| 链路层日志 | 100 | | 物理层日志 | 56 | | 硬件状态日志 | 20 | @@ -219,7 +219,7 @@ block-beta #### 链路层日志 -链路层日志顺序存储,共 88字节: +链路层日志顺序存储,共 100 字节: | 字段名 | 字节数 (Byte) | 说明 | | -------------------- | ------------- | ---------------------------------------- | @@ -239,12 +239,15 @@ block-beta | 分发帧发送计数器 | 4 | 每发送一次分发帧,计数器+1(仅簇首) | | 分发帧接收计数器 | 4 | 每配置接收一次分发帧,计数器+1(仅成员) | | 分发帧接收正确计数器 | 4 | 每正确接收一次分发帧,计数器+1(仅成员) | -| 网关帧发送计数器 | 4 | 每发送一次网关帧,计数器+1 | -| 网关帧接收计数器 | 4 | 每配置接收一次网关帧,计数器+1 | -| 网关帧接收正确计数器 | 4 | 每正确接收一次网关帧,计数器+1 | -| RTT帧发送计数器 | 4 | 每发送一次RTT帧,计数器+1 | -| RTT帧接收计数器 | 4 | 每配置接收一次RTT帧,计数器+1 | -| RTT帧接收正确计数器 | 4 | 每正确接收一次RTT帧,计数器+1 | +| 网管帧发送计数器 | 4 | 每发送一次网管帧,计数器+1 | +| 网管帧接收计数器 | 4 | 每配置接收一次网管帧,计数器+1 | +| 网管帧接收正确计数器 | 4 | 每正确接收一次网管帧,计数器+1 | +| RTT帧发送计数器 | 4 | 每发送一次 RTT 帧,计数器+1 | +| RTT帧接收计数器 | 4 | 每配置接收一次 RTT 帧,计数器+1 | +| RTT帧接收正确计数器 | 4 | 每正确接收一次 RTT 帧,计数器+1 | +| 业务帧发送计数器 | 4 | 每发送一次业务帧,计数器+1 | +| 业务帧接收计数器 | 4 | 每配置接收一次业务帧,计数器+1 | +| 业务帧接收正确计数器 | 4 | 每正确接收一次业务帧,计数器+1 | | 时间偏移量记录 | 4, int32 | 记录上一次节点时间和帧同步时间的偏差 | | 发送数据ID记录 | 4 | 记录上一次发送数据的ID | | 接收数据ID记录 | 4 | 记录上一次接收数据的ID | diff --git a/doc/comtool-plugin-hsjl.py b/doc/comtool-plugin-hsjl.py new file mode 100644 index 0000000..a43f960 --- /dev/null +++ b/doc/comtool-plugin-hsjl.py @@ -0,0 +1,433 @@ +''' + @brief This is an example plugin, can receive and send data + @author + @date + @license LGPL-3.0 +''' +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QPushButton, QLabel, QScrollArea, QSplitter +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QFont, QTextCursor +from struct import Struct +try: + import utils_ui + from plugins.base import Plugin_Base + from conn import ConnectionStatus + from i18n import _ + from widgets import statusBar +except ImportError: + from COMTool import utils_ui + from COMTool.plugins.base import Plugin_Base + from COMTool.i18n import _ + from COMTool.conn import ConnectionStatus + from COMTool.widgets import statusBar + +class Plugin(Plugin_Base): + id = "HJ DATALINK" + name = _("HJ DATALINK") + showReceiveDataSignal = pyqtSignal(str) + + def onInit(self, config): + ''' + init params + ''' + default = { + "version": 1, + "customSendItems": [ + { + "caption": "自检", + "icon": "fa5.question-circle", + "data": "5A 54 E1 EE 00 CF 02 5A FE" + }, + # { + # "caption": "数据发送测试 - 1", + # "icon": "fa5.play-circle", + # "data": "" + # }, + # { + # "caption": "数据发送测试 - 2", + # "icon": "fa5.play-circle", + # "data": "" + # }, + { + "caption": "装订 - 【主 - 0x01】128 kbps,时隙表 0", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE AA 01 22 00 00 7B 06 5A FE" + }, + { + "caption": "装订 - 【主 - 0x01】2 Mbps,时隙表 1", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE AA 01 44 01 00 C1 06 5A FE" + }, + { + "caption": "装订 - 【主 - 0x01】静默", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE 55 01 22 00 00 49 06 5A FE" + }, + { + "caption": "装订 - 【从 - 0x02】128 kbps,时隙表 0", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE AA 02 22 00 00 9F 06 5A FE" + }, + { + "caption": "装订 - 【从 - 0x02】2 Mbps,时隙表 1", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE AA 02 44 01 00 C2 06 5A FE" + }, + { + "caption": "装订 - 【从 - 0x02】静默", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE 55 02 22 00 00 0C 06 5A FE" + }, + { + "caption": "装订 - 【从 - 0x03】128 kbps,时隙表 0", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE AA 03 22 00 00 A0 06 5A FE" + }, + { + "caption": "装订 - 【从 - 0x03】2 Mbps,时隙表 1", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE AA 03 44 01 00 C3 06 5A FE" + }, + { + "caption": "装订 - 【从 - 0x03】静默", + "icon": "fa5.play-circle", + "data": "5A 54 E3 EE 55 03 22 00 00 0D 06 5A FE" + }, + { + "caption": "遥测", + "icon": "fa5.play-circle", + "data": "5A 54 E6 EE 00 D4 02 5A FE" + }, + ] + } + self.config = config + for k in default: + if not k in self.config: + self.config[k] = default[k] + + def onConnChanged(self, status:ConnectionStatus, msg:str): + print("-- connection changed: {}, msg: {}".format(status, msg)) + + def onWidgetMain(self, parent): + ''' + main widget, just return a QWidget object + ''' + self.mainWidget = QSplitter(Qt.Vertical) + # receive widget + self.receiveWidget = QTextEdit("") + font = QFont('Consolas, DejaVu Sans Mono, Courier New, monospace, Microsoft YaHei', 10) + self.receiveWidget.setFont(font) + self.receiveWidget.setLineWrapMode(QTextEdit.NoWrap) + # clear button + self.clearBtn = QPushButton("") + utils_ui.setButtonIcon(self.clearBtn, "mdi6.broom") + # send scroll + self.customSendScroll = QScrollArea() + self.customSendScroll.setMinimumHeight(20) + self.customSendScroll.setWidgetResizable(True) + self.customSendScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.customSendScroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + cutomSendItemsWraper = QWidget() + self.customSendScroll.setWidget(cutomSendItemsWraper) + + # -- wrapper widget + cutomSendItemsWraper0 = QWidget() + cutomSendItemsWraper0.setProperty("class", "scrollbar2") + layout0 = QVBoxLayout() + layout0.setContentsMargins(0,8,0,0) + cutomSendItemsWraper0.setLayout(layout0) + layout0.addWidget(self.customSendScroll) + customSendItemsLayoutWrapper = QVBoxLayout() + customSendItemsLayoutWrapper.setContentsMargins(0,0,0,0) + cutomSendItemsWraper.setLayout(customSendItemsLayoutWrapper) + + # -- items container + self.customItems = QWidget() + self.customSendItemsLayout = QVBoxLayout() + self.customSendItemsLayout.setContentsMargins(0,0,0,0) + self.customItems.setLayout(self.customSendItemsLayout) + customSendItemsLayoutWrapper.addWidget(self.customItems) + customSendItemsLayoutWrapper.addStretch(0) + + # add widgets + self.mainWidget.addWidget(self.receiveWidget) + self.mainWidget.addWidget(self.clearBtn) + self.mainWidget.addWidget(cutomSendItemsWraper0) + self.mainWidget.setStretchFactor(0, 2) + self.mainWidget.setStretchFactor(1, 1) + self.mainWidget.setStretchFactor(2, 11) + + # event + def clearReceived(): + self.receiveWidget.clear();self.statusBar.clear() + self.clearBtn.clicked.connect(clearReceived) + return self.mainWidget + + def onWidgetStatusBar(self, parent): + self.statusBar = statusBar(rxTxCount=True) + return self.statusBar + + def onUiInitDone(self): + ''' + UI init done, you can update your widget here + this method runs in UI thread, do not block too long + ''' + newItems = [] + print(self.config["customSendItems"]) + for item in self.config["customSendItems"]: + print(item) + item = self.insertSendItem(item, load=True) + newItems.append(item) + self.config["customSendItems"] = newItems + self.showReceiveDataSignal.connect(self.showReceivedData) + # frame struct + self.structSelfTestRsp = Struct("4d}\n" + ret += f"AD 温度 2: {payload[hwOffset+4]:>4d}\n" + ret += f"FPGA 温度: {payload[hwOffset+5]:>4d}\n" + ret += f"天线-1 工作 PLL: {(payload[hwOffset+6] >> 15) & 0x01:>1d}\n" + ret += f"天线-1 工作频率: {(payload[hwOffset+6] & 0x7FFF)+3700:>4d}\n" + ret += f"天线-2 工作 PLL: {(payload[hwOffset+7] >> 15) & 0x01:>1d}\n" + ret += f"天线-2 工作频率: {(payload[hwOffset+7] & 0x7FFF)+3700:>4d}\n" + ret += f"天线-3 工作 PLL: {(payload[hwOffset+8] >> 15) & 0x01:>1d}\n" + ret += f"天线-3 工作频率: {(payload[hwOffset+8] & 0x7FFF)+3700:>4d}\n" + ret += f"天线 PLL 锁定状态: {bin(payload[hwOffset+9])[2:].zfill(8)}\n" + ret += f"天线工作模式: {bin(payload[hwOffset+10])[2:].zfill(8)}\n" + ret += f"天线温度: {payload[hwOffset+11]:>4d}\n" + ret += "--------------------------------\n" + + return ret + '\n' + self.printFrame(data) + + def printFrame(self, data: bytes): + ret = '' + for i in range(len(data)): + ret += f'{data[i]:02X}' + ret += '\n' if i%16 == 15 else ' ' + ret += '\n' + return ret + + def evalStatusCheckRsp(self, data: bytes): + try: + payload = self.structStatusCheckRsp.unpack(data) + except : + return f"\n### evalStatusCheckRsp:: Failed to unpack data (data length {len(data)} byte).\n" + + ret = "----------- 遥测回令 -----------\n" + + ret += "节点类型: " + if payload[1] == 0xEE: + ret += f"C频段 (0x{payload[1]:02X})\n" + else: + ret += f"【未知错误】(0x{payload[1]:02X})\n" + + ret += f"时间戳: {payload[2]/1e5:.2f} ms\n" + + sysbusOffset = 3 + linkOffset = 7 + phyOffset = 36 + hwOffset = 47 + + ret += "\n -- 总线状态\n" + ret += f"配置装订: {payload[sysbusOffset + 0]:08X}\n" + ret += f"接收数据计数: {payload[sysbusOffset + 1]:>6d}\n" + ret += f"发送数据计数: {payload[sysbusOffset + 2]:>6d}\n" + ret += f"遥测请求计数: {payload[sysbusOffset + 3]:>6d}\n" + + ret += "\n -- 链路层状态\n" + ret += f"时元号: {payload[linkOffset + 0]:>6d}\n" + ret += f"时隙号: {payload[linkOffset + 1]:>3d}\n" + + ret += "当前角色: " + if payload[linkOffset + 2] == 0xAA: + ret += f"簇首 (0x{payload[linkOffset + 2]:02X})\n" + elif payload[linkOffset + 2] == 0xCC: + ret += f"成员 (0x{payload[linkOffset + 2]:02X})\n" + else: + ret += f"【未知错误】(0x{payload[linkOffset + 2]:02X})\n" + + ret += "网络连接状态: " + if payload[linkOffset + 3] == 0x11: + ret += f"已连接 (0x{payload[linkOffset + 3]:02X})\n" + elif payload[linkOffset + 3] == 0x00: + ret += f"未连接 (0x{payload[linkOffset + 3]:02X})\n" + else: + ret += f"【未知错误】(0x{payload[linkOffset + 3]:02X})\n" + + ret += "网络运行阶段: " + if payload[linkOffset + 4] == 0x11: + ret += f"建网阶段 (0x{payload[linkOffset + 4]:02X})\n" + elif payload[linkOffset + 4] == 0x22: + ret += f"业务通信 (0x{payload[linkOffset + 4]:02X})\n" + else: + ret += f"【未知错误】(0x{payload[linkOffset + 4]:02X})\n" + + ret += f"重建/入网计数: {payload[linkOffset + 5]:>6d}\n" + ret += f"通信天线位号: {payload[linkOffset + 6]:>1d}\n" + + ret += f"建网帧发送计数: {payload[linkOffset + 7]:>6d}\n" + ret += f"建网帧接收计数: {payload[linkOffset + 8]:>6d}\n" + ret += f"建网帧正确计数: {payload[linkOffset + 9]:>6d}\n" + ret += f"应答帧发送计数: {payload[linkOffset + 10]:>6d}\n" + ret += f"应答帧接收计数: {payload[linkOffset + 11]:>6d}\n" + ret += f"应答帧正确计数: {payload[linkOffset + 12]:>6d}\n" + ret += f"分发帧发送计数: {payload[linkOffset + 13]:>6d}\n" + ret += f"分发帧接收计数: {payload[linkOffset + 14]:>6d}\n" + ret += f"分发帧正确计数: {payload[linkOffset + 15]:>6d}\n" + ret += f"网管帧发送计数: {payload[linkOffset + 16]:>6d}\n" + ret += f"网管帧接收计数: {payload[linkOffset + 17]:>6d}\n" + ret += f"网管帧正确计数: {payload[linkOffset + 18]:>6d}\n" + ret += f"RTT 帧发送计数: {payload[linkOffset + 19]:>6d}\n" + ret += f"RTT 帧接收计数: {payload[linkOffset + 20]:>6d}\n" + ret += f"RTT 帧正确计数: {payload[linkOffset + 21]:>6d}\n" + ret += f"业务帧发送计数: {payload[linkOffset + 22]:>6d}\n" + ret += f"业务帧接收计数: {payload[linkOffset + 23]:>6d}\n" + ret += f"业务帧正确计数: {payload[linkOffset + 24]:>6d}\n" + ret += f"时间偏差记录: {payload[linkOffset + 25]/1e2:.2f} us\n" + ret += f"发送数据 ID: {payload[linkOffset + 26]:>6d}\n" + ret += f"接收数据 ID: {payload[linkOffset + 27]:>6d}\n" + + ret += "\n -- 物理层状态\n" + ret += f"捕获计数: {payload[phyOffset + 0]:>6d}\n" + ret += f"帧同步计数: {payload[phyOffset + 1]:>6d}\n" + ret += f"译码计数: {payload[phyOffset + 2]:>6d}\n" + ret += f"CRC 正确计数:{payload[phyOffset + 3]:>6d}\n" + ret += f"多普勒估计: {payload[phyOffset + 4]:.2f} Hz\n" + ret += f"正确接收时刻: {payload[phyOffset + 5]/1e2:.2f} us\n" + ret += f"捕获峰均比: {payload[phyOffset + 6]:.2f}\n" + ret += f"捕获相位值: {payload[phyOffset + 7]:>3d}\n" + ret += f"捕获频偏通道: {payload[phyOffset + 8]:>3d}\n" + ret += f"帧同步峰均比: {payload[phyOffset + 9]:.2f}\n" + ret += f"帧同步频偏通道: {payload[phyOffset + 10]:>3d}\n" + ret += f"交织输入数据幅度统计:\t0x{payload[phyOffset + 11]:04X}\n" + ret += f"天线控制模式: {bin(payload[phyOffset + 10])[2:].zfill(8)}\n" + + ret += "\n -- 硬件状态\n" + ret += f"FPGA 电压监测: {bin(payload[hwOffset+0])[2:].zfill(8)}\n" + ret += f"时钟锁定状态: {bin(payload[hwOffset+1])[2:].zfill(8)}\n" + ret += f"AD 锁定状态: {bin(payload[hwOffset+2])[2:].zfill(8)}\n" + ret += f"AD 温度 1: {payload[hwOffset+3]:>4d}\n" + ret += f"AD 温度 2: {payload[hwOffset+4]:>4d}\n" + ret += f"FPGA 温度: {payload[hwOffset+5]:>4d}\n" + ret += f"天线-1 工作 PLL: {(payload[hwOffset+6] >> 15) & 0x01:>1d}\n" + ret += f"天线-1 工作频率: {(payload[hwOffset+6] & 0x7FFF)+3700:>4d}\n" + ret += f"天线-2 工作 PLL: {(payload[hwOffset+7] >> 15) & 0x01:>1d}\n" + ret += f"天线-2 工作频率: {(payload[hwOffset+7] & 0x7FFF)+3700:>4d}\n" + ret += f"天线-3 工作 PLL: {(payload[hwOffset+8] >> 15) & 0x01:>1d}\n" + ret += f"天线-3 工作频率: {(payload[hwOffset+8] & 0x7FFF)+3700:>4d}\n" + ret += f"天线 PLL 锁定状态: {bin(payload[hwOffset+9])[2:].zfill(8)}\n" + ret += f"天线工作模式: {bin(payload[hwOffset+10])[2:].zfill(8)}\n" + ret += f"天线温度: {payload[hwOffset+11]:>4d}\n" + + ret += "--------------------------------\n" + return ret + '\n' + self.printFrame(data) + + def onSent(self, ok, msg, length, path): + if ok: + self.statusBar.addTx(length) + else: + self.hintSignal.emit("error", _("Error"), _("Send data failed!") + " " + msg) + + def sendCustomItem(self, item): + data = item["data"] + dateBytes = self.parseSendData(data, encoding = self.configGlobal["encoding"], isHexStr = True) + if dateBytes: + self.send(dateBytes, callback=self.onSent) diff --git a/src/systembus/systembus.c b/src/systembus/systembus.c index 92aaeae..57a86b5 100644 --- a/src/systembus/systembus.c +++ b/src/systembus/systembus.c @@ -128,7 +128,7 @@ // -- Request // -- -- -- // -- Response -#define RM_TEST_RSP_LEN 190 +#define RM_TEST_RSP_LEN 202 // -- -- 8 byte time stamp #define RT_TIME_STAMP_OFFSET 0x02 #define RT_TIME_STAMP_LEN 8 @@ -137,12 +137,12 @@ #define RT_SYSBUS_LOG_LEN 16 // -- -- 88 byte link layer log #define RT_LINK_LOG_OFFSET 0x1A -#define RT_LINK_LOG_LEN 88 +#define RT_LINK_LOG_LEN 100 // -- -- 56 byte physical layer log -#define RT_PHY_LOG_OFFSET 0x72 +#define RT_PHY_LOG_OFFSET 0x7E #define RT_PHY_LOG_LEN 56 // -- -- 20 byte hardware log -#define RT_HARDWARE_ST_OFFSET 0xAA +#define RT_HARDWARE_ST_OFFSET 0xB6 #define RT_HARDWARE_ST_LEN 20 // My Production Type