本文档描述 pyqtUIDesigner 支持的所有数据收发协议格式,包括 上行解析(串口接收→控件显示)和 下行发送(控件操作→串口发送)。
┌──────────┐ ┌──────────┐
│ 下位机 │ ── 串口 ──→ 原始字节(raw) ──→ 解析器 │ 上位机 │
│ │ │ │
│ │ ┌─ serialRawReceived ──→ HEX字节提取模式 │
│ │ 解析器输出 ──┤ │
│ │ └─ serialDataReceived ─→ 键值/JSON/MB │
│ │ │
│ │ ←── 串口 ←── 文本/HEX/Modbus帧 ←── 控件操作 │
└──────────┘ └──────────┘
| 信号名 | 数据类型 | 说明 |
|---|---|---|
serialRawReceived |
bytes |
原始串口字节,供 HEX字节提取 使用 |
serialDataReceived |
dict |
解析后的字典,供 键值匹配/JSON路径/Modbus 使用 |
serialDataSent |
bytes |
已发送的原始字节(用于调试回显) |
从解析后的字典中,用指定键名直接取值。
| 参数 | 类型 | 说明 |
|---|---|---|
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
从解析后的嵌套 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
直接从原始串口字节中,按固定偏移和数据类型提取数值。可选帧头帧尾定位有效载荷。
| 参数 | 类型 | 说明 |
|---|---|---|
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 或末尾(仅绑定) |
从 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 (可选) | 转换表达式 |
# 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)
将文本命令通过串口发送,可追加换行符。
| 参数 | 类型 | 说明 |
|---|---|---|
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
以十六进制字节直接发送,可附加帧头帧尾。
| 参数 | 类型 | 说明 |
|---|---|---|
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
自动构建标准 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),否则保留字符串。
读请求 (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 │
└──────┴──────────┴──────────┴────────┘
采用 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数据类型(仅数值类型)。键值匹配模式下若未配置匹配键,会尝试用曲线名称作为键。