Skip to content

Latest commit

 

History

History
639 lines (479 loc) · 17.2 KB

File metadata and controls

639 lines (479 loc) · 17.2 KB

通信协议文档

本文档描述 pyqtUIDesigner 支持的所有数据收发协议格式,包括 上行解析(串口接收→控件显示)和 下行发送(控件操作→串口发送)。


目录


一、数据流总览

┌──────────┐                                          ┌──────────┐
│  下位机   │  ── 串口 ──→  原始字节(raw)  ──→  解析器  │  上位机   │
│          │                                          │          │
│          │              ┌─ serialRawReceived ──→ HEX字节提取模式 │
│          │  解析器输出 ──┤                                      │
│          │              └─ serialDataReceived ─→ 键值/JSON/MB  │
│          │                                                     │
│          │  ←── 串口 ←──  文本/HEX/Modbus帧  ←── 控件操作      │
└──────────┘                                          └──────────┘
信号名 数据类型 说明
serialRawReceived bytes 原始串口字节,供 HEX字节提取 使用
serialDataReceived dict 解析后的字典,供 键值匹配/JSON路径/Modbus 使用
serialDataSent bytes 已发送的原始字节(用于调试回显)

二、上行解析协议(接收)

1. 键值匹配

从解析后的字典中,用指定键名直接取值。

参数

参数 类型 说明
key string 字典键名
expression string (可选) 转换表达式,如 value * 0.1

下位机发送格式(任选一种)

格式A:KEY=VALUE(推荐简单场景)

temperature=25.3,humidity=68.5\n

解析结果:{"temperature": 25.3, "humidity": 68.5}

格式B:JSON 行

{"temperature": 25.3, "humidity": 68.5}

解析结果:同上

格式C:CSV(自动命名 ch0, ch1, ...)

25.3,68.5,1013.2\n

解析结果:{"ch0": 25.3, "ch1": 68.5, "ch2": 1013.2}

完整示例

下位机(Arduino):

void loop() {
    float temp = readTemp();
    float humi = readHumi();
    // 格式A:KEY=VALUE
    Serial.print("temperature=");
    Serial.print(temp, 2);
    Serial.print(",humidity=");
    Serial.println(humi, 2);
    // 输出: temperature=25.30,humidity=68.50\n
    delay(1000);
}

上位机绑定设置:

  • 解析模式:键值匹配
  • 绑定字段:temperature

2. JSON路径

从解析后的嵌套 JSON 结构中,用点号分隔路径取值,支持数组下标。

参数

参数 类型 说明
jsonPath string 点号分隔路径,如 data.sensors.0.temp
expression string (可选) 转换表达式

下位机发送格式

{"data":{"sensors":[{"temp":25.3,"humi":68},{"temp":22.1,"humi":72}]}}\n

路径取值示例

JSON路径 取得值
data.sensors.0.temp 25.3
data.sensors.1.humi 72
data.sensors.0 {"temp": 25.3, "humi": 68}

完整示例

下位机(ESP32):

void loop() {
    StaticJsonDocument<256> doc;
    doc["device"] = "ESP32";
    JsonObject data = doc.createNestedObject("data");
    JsonArray sensors = data.createNestedArray("sensors");
    JsonObject s0 = sensors.createNestedObject();
    s0["temp"] = readTemp(0);
    s0["humi"] = readHumi(0);
    JsonObject s1 = sensors.createNestedObject();
    s1["temp"] = readTemp(1);
    s1["humi"] = readHumi(1);
    serializeJson(doc, Serial);
    Serial.println();  // 行结尾 \n
    delay(1000);
}

上位机绑定设置:

  • 解析模式:JSON路径
  • JSON路径:data.sensors.0.temp

3. HEX字节提取

直接从原始串口字节中,按固定偏移和数据类型提取数值。可选帧头帧尾定位有效载荷。

参数

参数 类型 说明
byteOffset int 有效载荷内的字节偏移
dataType enum uint8 / int8 / uint16 / int16 / uint32 / int32 / float32 / ASCII*
byteOrder enum 大端(BE) / 小端(LE)
frameHeader string 帧头,十六进制字符串,如 AA 55(可选)
frameTail string 帧尾,十六进制字符串,如 0D 0A(可选)
expression string (可选) 转换表达式

* ASCII 类型仅数据绑定支持,曲线图不支持。

帧结构

[帧头(可选)] [有效载荷] [帧尾(可选)]

解析时先找帧头位置,从帧头后开始;再找帧尾位置,帧尾前结束。得到的即为有效载荷,再按 byteOffset + dataType 提取。

完整示例

场景:传感器以固定帧格式发送温湿度

帧格式定义:

帧头: AA 55
数据: [温度 float32 大端] [湿度 float32 大端]
帧尾: 0D 0A

下位机发送(C/STM32):

uint8_t frame[14];
frame[0] = 0xAA;           // 帧头
frame[1] = 0x55;
float temp = 25.3f;
float humi = 68.5f;
memcpy(&frame[2], &temp, 4);  // 偏移0: 温度 float32
memcpy(&frame[6], &humi, 4);  // 偏移4: 湿度 float32
frame[10] = 0x0D;          // 帧尾
frame[11] = 0x0A;
HAL_UART_Transmit(&huart1, frame, 12, 100);

实际发送的十六进制:

AA 55 41 CA 66 66 42 89 00 00 0D 0A

上位机绑定设置(温度):

  • 解析模式:HEX字节提取
  • 帧头(HEX):AA 55
  • 帧尾(HEX):0D 0A
  • 字节偏移:0
  • 数据类型:float32
  • 字节序:大端(BE)

上位机绑定设置(湿度):

  • 同上,字节偏移改为 4

各数据类型字节长度

数据类型 字节数 范围
uint8 1 0 ~ 255
int8 1 -128 ~ 127
uint16 2 0 ~ 65535
int16 2 -32768 ~ 32767
uint32 4 0 ~ 4294967295
int32 4 -2147483648 ~ 2147483647
float32 4 IEEE 754 单精度浮点
ASCII 变长 到 0x00 或末尾(仅绑定)

4. Modbus RTU

从 Modbus RTU 响应帧解析出的字典中取寄存器/线圈值。

前提:串口解析器需设置为 modbus 类型,否则 RTU 帧不会被正确解析为字典。

参数

参数 类型 说明
regIndex int 寄存器/线圈索引(从0开始)
mbDataType enum uint16 / int16 / uint32 / int32 / float32
mbRegOrder enum 大端(AB CD) / 小端(CD AB)(双寄存器类型时有效)
mbFcFilter int 功能码过滤,-1 不过滤
mbSlaveFilter int 从站地址过滤,-1 不过滤
expression string (可选) 转换表达式

Modbus 解析器输出的字典格式

# FC 0x03 读保持寄存器 - 响应
{
    "slave": 1,
    "fc": 3,
    "reg0": 1000,    # 第1个寄存器值 (uint16)
    "reg1": 2500,    # 第2个寄存器值
    "reg2": 100,     # 第3个寄存器值
}

# FC 0x01 读线圈 - 响应
{
    "slave": 1,
    "fc": 1,
    "coil0": 1,      # 第1个线圈 (0或1)
    "coil1": 0,
    "coil2": 1,
}

数据类型解析规则

数据类型 寄存器数 说明
uint16 1 直接取 reg{i} 无符号值
int16 1 reg{i} > 32767 时减 65536
uint32 2 reg{i}reg{i+1},按寄存器序组合
int32 2 同上,有符号
float32 2 同上,IEEE 754

寄存器序说明:

  • 大端(AB CD)reg{i} 为高16位,reg{i+1} 为低16位
  • 小端(CD AB)reg{i+1} 为高16位,reg{i} 为低16位

完整示例

场景:读从站1的保持寄存器,地址0开始3个寄存器

从站响应帧(十六进制):

01 03 06 03E8 09C4 0064 CRC_LO CRC_HI
│  │  │  │    │    │
│  │  │  │    │    └─ reg2 = 0x0064 = 100
│  │  │  │    └────── reg1 = 0x09C4 = 2500
│  │  │  └─────────── reg0 = 0x03E8 = 1000
│  │  └────────────── 字节数 = 6
│  └───────────────── FC = 03 (读保持寄存器)
└──────────────────── 从站地址 = 1

解析结果字典:

{"slave": 1, "fc": 3, "reg0": 1000, "reg1": 2500, "reg2": 100}

上位机绑定设置(读 reg0 作为温度,0.1倍):

  • 解析模式:Modbus
  • 寄存器索引:0
  • 数据类型:uint16
  • 从站过滤:1
  • 功能码过滤:3(或 -1 不过滤)
  • 转换表达式:value * 0.1

场景:读 float32(跨两个寄存器)

从站寄存器 reg0=0x41CA, reg1=0x6666 → 组合 41 CA 66 66 → float32 = 25.3

上位机绑定设置:

  • 寄存器索引:0
  • 数据类型:float32
  • 寄存器序:大端(AB CD)

三、下行发送协议(发送)

1. 文本模式

将文本命令通过串口发送,可追加换行符。

参数

参数 类型 说明
command string 发送内容
commandNewline enum / \\r\\n / \\n / \\r
commandTemplate string 模板,默认 {value}{value} 被替换为控件值

示例

按钮发送固定命令

控件: PushButton
command: LED_ON
commandNewline: \r\n

点击按钮 → 串口发送:LED_ON\r\n

滑块发送变量值

控件: Slider (当前值 75)
commandTemplate: SET_SPEED={value}
commandNewline: \n
sendOnChange: true

拖动到75 → 串口发送:SET_SPEED=75\n

输入框发送用户输入

控件: LineEdit (用户输入 "Hello")
commandTemplate: MSG:{value}
commandNewline: \r\n
sendOnEnter: true

回车 → 串口发送:MSG:Hello\r\n


2. HEX模式

以十六进制字节直接发送,可附加帧头帧尾。

参数

参数 类型 说明
command string 十六进制字符串,如 01 06 00 01
frameHeader string 帧头十六进制,如 AA 55
frameTail string 帧尾十六进制,如 0D 0A
dataType enum HEX字符串 / uint8 / int16 / float32
byteOrder enum 大端(BE) / 小端(LE)
commandTemplate string 模板

示例

按钮发送固定HEX帧

控件: PushButton
commandMode: HEX
command: 01 02 03 04
frameHeader: AA 55
frameTail: 0D 0A

点击 → 串口发送:AA 55 01 02 03 04 0D 0A

滑块发送结构化数值

控件: Slider (值 = 1000)
commandMode: HEX
commandTemplate: 01 06 00 01 {value}
dataType: uint16
byteOrder: 大端(BE)

拖动到1000 → {value} 被替换为 03E8(1000的大端uint16)→ 串口发送:01 06 00 01 03 E8


3. Modbus模式

自动构建标准 Modbus RTU 请求帧(含 CRC16 校验)。

参数

参数 类型 说明
mbSlave int (1-247) 从站地址
mbFunc enum 功能码(见下表)
mbAddress int 起始地址
mbValue int 读取时=数量,写入时=值

支持的功能码

选项 FC码 用途
01 读线圈 0x01 读离散输出
02 读离散输入 0x02 读离散输入
03 读保持寄存器 0x03 读保持寄存器
04 读输入寄存器 0x04 读输入寄存器
05 写单线圈 0x05 写单个线圈(ON=0xFF00, OFF=0x0000)
06 写单寄存器 0x06 写单个保持寄存器

帧格式

[从站地址 1B] [功能码 1B] [起始地址 2B 大端] [数量/值 2B 大端] [CRC16 2B 小端]

示例

按钮轮询读取3个保持寄存器

控件: PushButton
commandMode: Modbus
mbSlave: 1
mbFunc: 03 读保持寄存器
mbAddress: 0
mbValue: 3

点击 → 自动组帧:01 03 00 00 00 03 05 CB

分解:

01       从站地址 = 1
03       功能码 = 读保持寄存器
00 00    起始地址 = 0
00 03    数量 = 3
05 CB    CRC16

开关控件写线圈

控件: ToggleSwitch
commandMode: Modbus
mbSlave: 1
mbFunc: 05 写单线圈
mbAddress: 0

开关ON → 01 05 00 00 FF 00 8C 3A 开关OFF → 01 05 00 00 00 00 CD CA

滑块写寄存器值

控件: Slider (值 = 500)
commandMode: Modbus
mbSlave: 1
mbFunc: 06 写单寄存器
mbAddress: 10
sendOnChange: true

拖动到500 → 01 06 00 0A 01 F4 A8 1B

分解:

01       从站地址 = 1
06       功能码 = 写单寄存器
00 0A    地址 = 10
01 F4    值 = 500
A8 1B    CRC16

四、行协议自动识别规则

默认串口解析器为 line 模式(按 \n 分行),每行按以下优先级自动识别:

收到一行文本
  │
  ├─ 以 '{' 开头? ──是──→ JSON解析 → dict
  │
  ├─ 按分隔符(默认逗号)切分
  │    │
  │    ├─ 有 '=' ? ──是──→ KEY=VALUE模式
  │    │                    "a=1,b=2" → {"a": 1, "b": 2}
  │    │
  │    └─ 无 '=' ──────→ CSV模式
  │                       "1.0,2.0,3.0" → {"ch0": 1.0, "ch1": 2.0, "ch2": 3.0}
  │
  └─ 空行 → 跳过

数值自动转换:整数优先(int),否则浮点(float),否则保留字符串。


五、Modbus RTU 帧格式参考

请求帧(主站→从站)

读请求 (FC 01-04)

┌──────┬──────┬──────────┬──────────┬────────┐
│ 地址 │  FC  │ 起始地址 │   数量   │  CRC   │
│ 1B   │ 1B   │  2B BE   │  2B BE   │ 2B LE  │
└──────┴──────┴──────────┴──────────┴────────┘

写单个 (FC 05/06)

┌──────┬──────┬──────────┬──────────┬────────┐
│ 地址 │  FC  │ 寄存器/  │   值     │  CRC   │
│ 1B   │ 1B   │ 线圈地址 │  2B BE   │ 2B LE  │
│      │      │  2B BE   │          │        │
└──────┴──────┴──────────┴──────────┴────────┘

响应帧(从站→主站)

读响应 (FC 03/04)

┌──────┬──────┬──────────┬──────────────────────┬────────┐
│ 地址 │  FC  │ 字节数   │    寄存器数据         │  CRC   │
│ 1B   │ 1B   │  1B      │  N×2B (每2B为大端值)  │ 2B LE  │
└──────┴──────┴──────────┴──────────────────────┴────────┘

读线圈响应 (FC 01/02)

┌──────┬──────┬──────────┬──────────────────────┬────────┐
│ 地址 │  FC  │ 字节数   │    线圈位数据         │  CRC   │
│ 1B   │ 1B   │  1B      │  N字节(每字节低位先)  │ 2B LE  │
└──────┴──────┴──────────┴──────────────────────┴────────┘

异常响应

┌──────┬──────────┬──────────┬────────┐
│ 地址 │ FC|0x80  │ 异常码   │  CRC   │
│ 1B   │  1B      │  1B      │ 2B LE  │
└──────┴──────────┴──────────┴────────┘

CRC16 校验

采用 Modbus 标准 CRC16(多项式 0xA001),结果以小端序附加在帧末。

def modbus_crc(data: bytes) -> int:
    crc = 0xFFFF
    for b in data:
        crc ^= b
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc  # 低字节在前

附录:实时曲线解析配置

实时曲线支持每条曲线独立配置解析方式,与数据绑定的四种模式相同。

参数 键值匹配 JSON路径 HEX字节提取 Modbus
曲线名称
匹配键
JSON路径
字节偏移
数据类型(HEX)
字节序(HEX)
帧头/帧尾(HEX)
寄存器索引
数据类型(MB)
寄存器序(MB)
功能码过滤
从站过滤

注意:曲线 HEX 模式不支持 ASCII 数据类型(仅数值类型)。键值匹配模式下若未配置匹配键,会尝试用曲线名称作为键。