第二次汇报学习笔记:把“发送”从按钮点击,做成可配置能力——同一个发送区既能发文本,也能发二进制(HEX/转义),还能按需追加 CRC,并支持定时自动发送。
串口工具里,“发送”看似简单,其实最容易变成一团 if-else:
-
勾了 HEX 就按 HEX 发;
-
勾了转义就解析
\n; -
-
勾了自动发送还要挂定时器; 最后写出来的函数通常又长又绕。
我后来调整思路:把发送区抽象成结构体 + 统一的 build 流程,做到每个能力都“插拔式”。
1. 把发送区抽象成结构体:控件不再散落各处
一个发送区(例如发送区 1)通常包含:
-
文本输入框(QPlainTextEdit)
-
一组复选框(HEX、转义、CRC、自动发送、参加轮发)
-
一个周期输入框(ms)
-
一个定时器(自动发送用)
把这些“天然属于一个发送区”的东西收拢成结构体,代码阅读性会立刻提升:
struct SendAreaEx {
QPlainTextEdit *edit;
QCheckBox *cbHex;
QCheckBox *cbEscape;
QCheckBox *cbCrc;
QCheckBox *cbRound;
QCheckBox *cbAuto;
QLineEdit *lePeriod;
QTimer *timer;
};
然后做一个 m_sendArea[3],把 3 个发送区统一管理。
2. 统一发送入口:sendFromArea(i)
我给每个发送区一个索引(0/1/2),所有发送都走同一个入口:
-
检查串口是否已打开;
-
调用
buildAreaBytes()把“用户输入 + 复选框选项”转换成最终字节流; -
调用
sendRawBytes(bytes)发出去。
这样按钮点击(手动发送)和定时器触发(自动发送)都可以复用同一条链路。
3. buildAreaBytes:把“字符串”变成“要发的字节”
核心逻辑就是:输入是 QString,输出是 QByteArray,中间根据用户选项走不同分支:
-
HEX 勾选 → 解析 HEX 字符串;
-
非 HEX:
-
勾选转义 → 解析
\n \r \t \0 \\ \xNN等; -
否则 → 按本地编码
toLocal8Bit();
-
-
最后如果 CRC 勾选 → 追加 Modbus CRC16(两个字节)。
我喜欢把它写成“从上到下的流水线”,减少嵌套。
4. HEX 解析:容错比“能跑”更重要
HEX 发送经常遇到用户输入:
-
01 03 00 00 00 02 -
0x01 0x03 0x00 0x00 0x00 0x02 -
010300000002
所以解析前先做规范化:
-
trim;
-
去所有空白;
-
去
0x前缀; -
大写;
-
长度必须为偶数;
-
每两位转一个字节,遇到非法字符给出明确错误。
当解析失败时,不要悄悄返回空,最好给出 “HEX 包含非法字符:XX” 这种定位明确的信息,用户体验会好很多。
5. 转义解析:让“文本输入框”也能发二进制
转义解析的价值在于:不用切换到 HEX 也能构造二进制 payload,比如:
-
hello\n -
\x01\x03\x00\x00\x00\x02
实现上本质是一个扫描器:遇到反斜杠就看后续字符,根据规则写入对应字节;否则写入原字符的字节。
这里的关键点是:转义解析输出是 QByteArray,而不是 QString。因为 \0、\xNN 都可能产生不可打印字符。
6. 自动发送:用 QTimer 把“能力”挂上去
自动发送的实现不应该写进 on_xxx_clicked() 里,否则会导致逻辑分叉。
更合理的做法是:
-
给每个发送区都配一个 timer;
-
复选框
toggled(true)时启动 timer,interval 从周期输入框读取; -
timeout时调用sendFromArea(i, quiet=true); -
周期文本变化时,如果 timer 正在跑就动态更新 interval;
-
周期输入用
QIntValidator限制为整数(ms)。
这样自动发送就是一个“组件能力”,不会污染手动发送逻辑。
7. 小结
这一阶段最大的收获是:把“发送”当成可组合能力,而不是按钮事件。
-
结构体把同类控件聚拢;
-
sendFromArea → buildAreaBytes → sendRawBytes的流水线让所有发送路径统一; -
HEX/转义/CRC 的差异只存在于 build 阶段;
-
自动发送只是 timer 调度同一个入口。








