Files
HW-Debug-Tools/SerialMate/AGENTS.md
T

333 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md — SerialMate 工程档案(开发约束与进度)
> 人类文档 [README.md](README.md) 回答"这是什么、怎么用";本文记录"代码怎么组织、
> 怎么构建发布、改动时哪些约束不能破坏",兼作本工具的进度记录。
> 修改本目录代码前先读完本文。仓库整体的开发需求与进度管理见根目录 `agent.md`。
## 1. 项目定位
仓库 `HW-Debug-Tools` 是 LT8619C→DLPC3421 投影转接板的调试上位机工具集。
SerialMate 是其中的**通用串口/RS485 调试助手**:收发 + 协议解析 + 曲线。
默认帧格式与 DLPilot 光机 RS485 协议完全一致(`D5 01 LEN <payload> CRC`),
可直接调试板载 GD32E230 网关;帧格式(包头/板ID/CRC 算法与覆盖范围)可定制,
适配任意"包头+板ID+LEN(=payload长度)+payload+CRC"五段式协议。
协议语义权威同 DLPilot:上游固件仓 `protocols.md`,本仓不维护副本。
## 2. 目录契约
```
SerialMate/
├── src/ 源代码区 —— 只有这里可以手改
├── release/ 产物区 —— SerialMate.exe + README.md(exe 不入库本地保留,README 入库),
│ *-win64.zip(gitignore,只上传 Release)
├── assets/ 图标等生成资产(暂无;build_exe.py 已预留 assets/serial-mate.ico)
├── README.md 人类文档
└── AGENTS.md 本文
```
### 文件角色速查
| 文件 | 角色 |
|---|---|
| `src/main.py` | Fluent GUI 入口(含 `--seconds N` 冒烟参数),argparse 仅此一处 |
| `src/protocol.py` | 协议模块(纯标准库):FrameLayout 帧构成令牌、ProtoConfig 组帧/校验、FrameParser 增量解析、CRC 算法表、extract_value 数值提取 |
| `src/link.py` | 串口工作线程 SerialLink(pyserial 只准在这里 import),支持 `loop://` 虚拟回环 |
| `src/plot.py` | 滚动曲线控件(纯 QPainter,刻意不引 matplotlib/pyqtgraph) |
| `src/config.py` | BASE_DIR 定位与 serial_mate.json / board_profiles.json 读写(缺键补默认,读容错) |
| `src/theme.py` | 主题三件套同步(Fluent setTheme + QPalette/QSS + DWM 标题栏),与 EDID-Viewer 同源 |
| `src/selftest.py` | 协议自检(无依赖、无硬件,改 protocol.py 后必跑) |
| `src/selftest_loop.py` | 链路自检(loop:// 回环验证 组帧→发送→接收→解析→提取,缺 pyserial 跳过) |
| `src/selftest_gui.py` | GUI 功能自检(offscreen 驱动事件处理函数:预览/回显/提取/配置往返,改 main.py 后建议重跑) |
| `src/build_exe.py` | PyInstaller onefile 打包 → `release/SerialMate.exe` |
| `src/package_release.py` | exe+README → `release/SerialMate-v<版本>-win64.zip` |
| `src/publish_gitea.py` | 建/复用 Gitea Release 并上传 zip(`GITEA_TOKEN`,需 `--insecure`) |
## 3. 代码不变量(改代码必须维持)
1. **依赖分层**:protocol.py / config.py 纯标准库;pyserial 只准在 link.py
import(main.py 经 `SerialLink` 间接使用);plot.py 只依赖 PySide6;
GUI 硬依赖 PySide6-Fluent-Widgets + pyserial(串口工具缺 pyserial 即无意义,
与 DLPilot 的"缺失降级"策略不同,只保留 try-import 提示)。
1a. **卡片布局约定**:`_card(side=True)` 把短副标题放标题右侧省一行(串口/
协议/发送卡);右列是纵向 QSplitter(接收/发送高度用户可拖拽,拉伸余量
默认给接收、发送卡保持自然高度防空腔);发送卡末尾 `lay.addStretch(1)`
保证内容贴顶(卡片被拖高后富余高度收在卡底,勿删——否则拉伸被各行均分,
标题上方出现空隙);发送卡行序:板型管理行(下拉+新建+删除+配置+重载) /
预设行(下拉+填入+直接发送) / 模式行(下拉+存为板型+存入预设) / 输入行 /
周期行;帧日志 payload 高亮走 `_append_rx_frame`(`_esc_log` 转义后拼 span
背景,toPlainText 内容不变,selftest 的日志断言因此不受影响);预设下拉
显示"名称 · 命令",存入命令走 `_add_command_named`(弹窗核 `_add_command`
包装,测试用核函数),预设直发走 `_send_preset_direct`(经输入框复用
组帧/历史链路)。
1b. **窗口最小高度**:首帧布局后 `_apply_minimum_size` 按内容实际最小高度
抬高窗口下限(高度拉过矮时布局约束无法满足,曲线卡绘图区会被压塌并与
通道行互相叠压——用户实测);勿删, 调整曲线卡行数/高度后该值自动跟随。
2. **回调线程边界**:SerialLink 的 on_status/on_frame/on_raw/on_tx 全在后台线程
触发,一律 `events.put(...)` 投递,主线程 30ms QTimer 轮询 `_drain_events`
分发(与 DLPilot `_poll_dlpc` 同模式),禁止回调里直接碰 GUI。
**时间戳由 link 后台线程打**(字节到达/写入串口时刻):四个回调签名均带
尾参 ts 并随事件入队(事件元组尾部即 ts,如 `('frame', fr, ts)`)——GUI
消费侧只经 `_timestamp(t)` 格式化、不现取(消费时刻打戳会随 UI 卡顿成批
挤压失真,v0.1.0 教训,见 `ISSUE_rx_timestamp_and_lag.md`)。
2b. **接收日志治理**:日志行只入 `_rx_buf`(`_append_rx`/`_append_rx_frame`
负责转义),`_drain_events` 末尾 `_flush_rx` 一次 appendHtml——每行必须包
`<p style="margin:0">`(**`<br>` 连接只算 1 个 block,会让
maximumBlockCount 裁旧行失效**,offscreen 实验验证过);转义用 `_esc_log`
(空格转 `&nbsp;`,防富文本折叠连续空格破坏 TX 行对齐);`_update_counters`
由 drain 末尾统一调一次,勿回到每帧调用。事件积压超 `FOLD_QSIZE`(200) 时
该批帧走 `_absorb_frame`(曲线照常入库、计数器照常,日志折叠为带首尾到达
时刻的计数摘要行,消化到阈值内自动恢复逐帧)。
2c. **通道配置缓存**:`_on_frame` 帧路径只读 `self._ch_cfg`(`_on_channel`
用户改动配置时经 `_collect_channels` 重建),禁止每帧遍历 UI 控件读配置
(高频时放大 UI 线程占用,v0.1.0 延迟累积的帮凶之一)。
3. **半双工节奏**:link 工作线程每个循环周期最多出队写一帧(20ms 间隔),
RS485 总线多帧连发留应答间隙,勿改成批量 drain 一次写。
4. **帧构成语义**:帧由 `FrameLayout` 令牌序列定义(`HDR ID LEN PAYLOAD CRC`
令牌 + 十六进制字面量,收发共用布局、仅包头值分方向);约束:LEN/PAYLOAD/CRC
恰好各一次、CRC 必须最后、LEN 前至少一个 HDR 或字面量(同步定界)、ID 最多一次。
LEN 口径(`LEN_SEMS`):payload 长度 / 到帧尾(payload+CRC) / 整帧;
CRC 算法/覆盖范围见 protocol.CRC_ALGOS/CRC_SCOPES,覆盖按**帧内位置**定义
(with_header=第1字节起 / no_header=跳过第1字节 / payload_only=仅载荷),
任意布局通用;2 字节 CRC 线上字节序 MODBUS 低前、其余高前,按算法约定单序
校验(改动需同步 selftest 向量)。
5. **解析重同步策略**:前缀令牌逐位对齐(HDR/字面量须匹配、ID 通配),LEN 值经
口径换算为 payload 长度,负值视为伪帧头逐出;CRC 失配丢 1 字节重同步——
通用工具不预设合法 LEN 区间(与 DLPilot 的 LEN 范围预判不同)。
5a. **解析不吞数据(v0.1.2 起的产品语义)**:串口助手是本体、协议解析是附加层。
FrameParser 重同步丢弃的字节累积进 `garbage`,`link.SerialLink._dispatch_rx`
每批 `drain_garbage()` 无条件取走(未接回调也不许在解析器里无限累积),
经 `on_garbage(data, ts)` 上抛;GUI 把它并入 `('raw', ...)` 事件,与透传行
同格式显示。纯文本设备/波特率不符的乱码在解析模式下必须全可见。
例外:只匹配上前缀、还在等后续字节的"半帧"缓冲不算垃圾(可能补全成真帧),
字节流恰好在帧头后截断时会扣留至多一个前缀长度的字节——可接受,勿改成
超时强制吐出(会把慢速真帧撕碎)。改此语义需同步 selftest 8b / loop 3b。
6. **ID 位不校验只透出**:`Frame.code` 原样上抛(布局无 ID 时为 None,GUI 日志
与 selftest 均需兼容 None),TYPE_NAMES 仅用于显示译码(DLPilot 语义)。
7. **BASE_DIR 语义**(config.py):frozen 时 =exe 所在目录,源码运行时 =src/;
设置平铺存 `serial_mate.json`,不建子目录。`tx.periodic` 不随配置
自动启动(防误连发),其余配置启动恢复。
8. **曲线数据流**:每帧对每个启用通道调 `extract_value`(失败静默跳过),
`PlotWidget.add` 内部 deque(maxlen=3000) 滚动丢弃;数值格式统一
`fmt_val`(4 位有效数字)。
9. **主题**:theme.py 三件套同步(见 EDID-Viewer 教训:只 setTheme 会黑块);
plot 曲线颜色固定 4 色,文字/网格色取自 QPalette 自动跟随明暗。
9a. **曲线时间窗**:PlotWidget 持有 `_span`(窗宽秒, None=跟随全部) 与
`_offset`(右端距最新秒数, 0=实时);横轴域换算在 `visible_domain`(纯计算,
selftest 有断言)。**窗口不向数据收敛**(用户实测反馈):选 5min 而数据只有
23s 时显示完整 5min 轴、曲线贴右端随采集填满(示波器式),右下角标注
"已采集 Xs / 窗口 Ns";滞后超出全部历史 = 空窗保宽。弹窗内 时间窗下拉+
滑条 只改这两个状态;滑条 0..1000 线性映射 (1000-v)/1000×maxpan,实时
跟随=拉到最右(1000);poll 定时器 `_sync_time_slider` 回写位置(用户拖动中
跳过);嵌回卡片时强制重置为跟随全部(卡内无滑条, 防困在滞后窗里)。纵轴
数值刻度画在每条横向参考线上。
悬停读值:mouseTracking 开启, mouseMove 只记录 `_hover_pos` 并请求重绘;
读值内容由 paintEvent **每帧**经 `_hover_lines_at(_hover_pos)` 重算(光标
时间 T 经 `_plot_geometry` 换算, `_nearest` 二分取 ±10px 容差内最近采样),
画在顶部居中反色小框(`_hover_box`)——鼠标停住不动时数值也随实时数据持续
刷新(勿改回 mouseMove 里一次性算好/或 QToolTip, 那样不动鼠标就不更新——
用户实测反馈)。`_nearest`/`_hover_lines_at` 有单测。绘图区内边距统一用
`M_LEFT/TOP/RIGHT/BOTTOM` 类常量(绘制与鼠标换算共用, 勿在 paint 里另写)。
9b. **暂停(两级语义)**:主界面"暂停"= `_paused`, add() 丢弃暂停期间的数据;
弹窗"暂停渲染"= `_frozen`, 数据照常入库但跳过重绘——冻结是**绝对状态**:
PlotWidget 覆写 `update()` 直接吞掉一切重绘请求(任何代码路径都无法在
冻结期打破), 只有 `set_frozen(False)` 恢复并一次性渲染积累;冻结时画面
中央显示"已冻结渲染(采集继续)"角标让状态可见。两者互不干扰, 均有
selftest 断言。
9c. **弹窗曲线交互**:自动Y(勾选按钮, 与主界面 chk_auto_y 双向同步, 关闭时
以 `auto_yrange()` 初始化手动量程);图上左键拖动 = 平移手动 Y 量程
(`on_yrange_changed` 回调同步主界面 Ymin/Ymax 输入框, 阻断信号防回环);
滚轮 = 缩放时间窗 2~900s(滞后按比例缩放, 经 `on_span_changed` 回调让
`_sync_time_slider` 把时间窗下拉同步为匹配档位/自定义(-1));"实时"按钮
= offset 归零跳回最新。自动 Y 量程抽取为 `auto_yrange()`(纯计算, 有单测)。
11a. **曲线弹出窗口**:绘图区在曲线卡留有固定槽位布局 `_plot_slot`,弹出 =
`setParent(None)` 后装入顶层窗口(通道/暂停/Y 轴控件留在主界面控制同一
PlotWidget 实例);关掉弹出窗口(eventFilter 捕 Close)或主窗 closeEvent
都会走 `_dock_plot()` 嵌回 —— `_dock_plot` 必须先摘 `self._plot_win` 引用
再 `win.close()`(防 Close 递归),且 `setParent(None)` 后必须补 `show()`
(重挂布局不会自动恢复可见)。
10. **多实例**:有意不加单实例锁(双口双开的合法场景),串口冲突靠
pyserial 报错提示("串口被占用"文案在 link.py,勿删)。
11. **板型档案**:`board_profiles.json`(BASE_DIR 平铺,用户本机数据不入库,
首次运行自动生成默认"DLPilot光机")。档案 = name + proto(7 个帧协议字段,
**不含 parse** —— 解析开关是查看偏好) + commands[[名,命令]..];切换板型
应用协议参数并装载命令表,"存入"命令以命令文本为名去重;启动恢复选中
板型时**只装载命令表不重复应用协议参数**(协议以上次会话 store 为准);
GUI 增删后经 config.save_profiles 落盘,"配置/重载"支持手改 JSON。
## 4. 构建与发布(全部在 src/ 下执行)
```powershell
python -m compileall -q . # 全模块可编译
python selftest.py # 协议回归(67 项, 改 protocol.py 后必跑)
python selftest_loop.py # 链路回环(21 项, 需 pyserial, 改 link.py 后必跑)
python selftest_gui.py # GUI 功能回归(68 项, offscreen, 改 main.py 后建议重跑)
python main.py --seconds 5 # GUI 冒烟(源码); offscreen: set QT_QPA_PLATFORM=offscreen
python build_exe.py # → ../release/SerialMate.exe(缺 PyInstaller 自动装)
python package_release.py <版本> # → ../release/SerialMate-v<版本>-win64.zip
python publish_gitea.py ../release/SerialMate-v<版本>-win64.zip --replace --insecure
```
发布约定与 Gitea 实例怪癖(TLS 抽风加 `--insecure`、assets 两种形状兼容、
token 读 `GITEA_TOKEN` 用户级环境变量)同 DLPilot,详见 `DLPilot/AGENTS.md` §5;
tag/release 命名 `SerialMate-vX.Y.Z`,zip 命名
`SerialMate-vX.Y.Z-win64.zip`(publish_gitea.py 从文件名推 tag)。
## 5. 已知坑(本工具踩过/继承的)
- `qfluentwidgets.CaptionLabel` 无 `setDim` 方法(本机版本),灰字用
`CaptionLabel` 本身即可;`ComboBox` 不要用 `currentData`(qfluentwidgets
自绘下拉 userData 支持不稳),一律 currentIndex + 平行元组映射。
- **qfluentwidgets 控件默认水平 Expanding**:紧凑行不设 stretch 项时空白会被
CheckBox/Label 等瓜分出大空隙 —— 一律走 `Window._row()`(行尾默认补
stretch;Qt 规则:有 stretch 因子时额外空间只按因子分配),需要自管拉伸的
行传 `trailing=False`。
- **qfluentwidgets SpinBox/DoubleSpinBox 设固定宽后内部行编辑器会塌缩**
(minimumSizeHint 142/193 只是参考,强设小于它的宽度时行编辑器只剩几像素,
数值被裁成残字——整数 0 在 96px 时渲染成 "]"):整数 SpinBox ≥110px、
DoubleSpinBox ≥130px 才能完整显示数值;曲线卡偏移/序号/倍率/Y 量程框的
宽度有 selftest 断言把守, 调布局时勿低于阈值。
- 组帧/解析回路自测时必须让 `tx_hdr == rx_hdr`(收发方向包头本就允许不同,
D5 组的帧 B5 解析器看不见——selftest 的 fuzz/回路用例已按此约定)。
- offscreen 冒烟报 `QFontDatabase: Cannot find font directory` 属 Qt offscreen
平台噪音,桌面运行无此问题,勿当 bug 修。
- onefile 运行时定位 exe 用 `sys.executable`(`__file__` 在 `_MEIPASS`),
config.py 已按此实现。
## 6. 验证清单(改完代码后)
1. `python -m compileall -q .` —— 全模块可编译;
2. `python selftest.py` —— 协议回归(改 protocol.py 后必跑);
3. `python selftest_loop.py` —— 链路回环(改 link.py 后必跑);
4. `python main.py --seconds 5` —— GUI 冒烟自动开关;
5. `python build_exe.py` → 双击 release/SerialMate.exe 确认界面渲染;
6. (有硬件时)USB-RS485 接光机发 M1/M201/M731 验证真机收发与曲线。
## 7. 进度记录
### v0.1.2(2026-10-16,接收语义修复:解析模式不再吞数据)
背景:用户接 COM25(非 DLPilot 协议设备)开解析模式,接收区一片空白——
`_dispatch_rx` 在解析模式只上抛完整合法帧,不成帧的字节被 FrameParser 静默
丢弃;用户明确产品定位:**首先是基础串口调试工具,自定义协议解析是附加项**,
附加能力不得影响本体功能。
已完成:
- protocol.py:FrameParser 丢弃字节不再销毁——重同步逐出的字节累积进
`garbage`,新增 `drain_garbage()` 取走清空;`clear()` 一并清垃圾;
`feed()` 返回值不变(旧用法零改动)。
- link.py:新增 `on_garbage(data, ts)` 回调(区别于透传模式的 `on_raw`,
语义可分但 GUI 同样并入 raw 事件显示);`_dispatch_rx` 每批 drain 无条件
执行(未接回调也不累积),有字节且接了回调才上抛,ts=同一批到达时刻。
- main.py:接线 `on_garbage → ('raw', ...)`;"解析协议帧(收)"补 tooltip
说明两模式语义;APP_VERSION 0.1.2。
- 例外语义(有意为之):只匹配前缀、等待后续字节的半帧缓冲不算垃圾
(可能补全成真帧);字节流恰在帧头后截断时扣留至多一个前缀长度。
- 自检扩充:selftest 8b(纯文本全透出/噪声+帧+尾字节/坏CRC帧字节全透出/
半帧头扣留后补全成帧)+10 项;selftest_loop 3b(解析模式非帧字节经
on_garbage 透出、回环 TX 帧只算非帧字节)+3 项;selftest_gui 5b
(on_garbage 接线+非帧字节日志行)+2 项;全部通过。
- README 协议解析段同步"解析不吞数据"语义;exe 重建(v0.1.2);
已发布:tag `SerialMate-v0.1.2` 指向 `8e91e66`,Release 附件
`SerialMate-v0.1.2-win64.zip`(63.6 MB)已上传核对。发布当日本机(联想
笔记本)到 1234 端口 TCP 能连但 banner 静默(22/443 正常,异机 N100
SSH 认证正常——服务端无恙,是本机网络路径对 1234 的选择性干扰),
push 改走 HTTPS+token 成功;TLS 抽风加 `--insecure` 老毛病不变。
待办 / 计划:无新增,沿用 v0.1.0 待办。
### v0.1.1(2026-10-10,接收路径修复:时间戳失真 + 显示延迟累积)
背景:FilmForge 固件联调(GD32F405 RS485 115200,M2 连续温度上报 2~10 帧/s)
发现日志时间戳成批挤压、显示延迟越跑越大;固件侧二分验证 100% 出帧,问题
独立存在于 SerialMate 显示层,见 `ISSUE_rx_timestamp_and_lag.md`。
已完成:
- **根因 1**(时间戳在 GUI 消费时刻打):时间戳改在 link 后台线程打
(字节到达/写入串口时刻),`on_status/on_frame/on_raw/on_tx` 签名均追加
尾参 ts 并随事件入队;`_timestamp(t)` 变为纯格式化函数。日志时间恢复为
真实到达时间。
- **根因 2**(接收日志无限增长 → UI 递减性卡顿 → 延迟累积):
- 接收日志批量插入:`_drain_events` 把本轮日志行攒入 `_rx_buf`,末尾
`_flush_rx` 一次 appendHtml(每行一个 `<p style="margin:0">`,
maximumBlockCount 裁旧行照常生效;`_esc_log` 空格转 `&nbsp;` 防富文本
折叠连续空格);`_update_counters` 改为 drain 末尾统一一次;
- 通道配置缓存 `_ch_cfg`:`_on_channel` 时重建,帧路径不再每帧遍历控件
(提取逻辑收敛进 `_extract_channels`);
- 积压折叠:事件队列超 `FOLD_QSIZE`(200) 时该批帧走 `_absorb_frame`——
曲线照常入库、计数器照常,日志折叠为一行计数摘要(含首尾到达时刻,
如 `← 12:00:00.100~12:00:02.300 ⏳ 显示滞后, 折叠 250 帧: B=0xF0 温度
上报 ×250`),消化到阈值内自动恢复逐帧;
- 行数上限 5000(v0.1.0 末期已加的止血,本次提为常量 `RX_MAX_BLOCKS`)。
- 自检扩充:selftest_loop 增回调 ts 断言共 18 项;selftest_gui 增时间戳
格式化/帧日志用到达时刻/通道缓存同步/积压折叠 8 项共 69 项;另做端到端
模拟(600 帧积压一次性消费):日志压到 201 行、曲线 600 点全收、时间戳
单调且逐帧等于真实到达时刻。
- README 接收显示段同步时间戳/日志治理语义;exe 重建入库(v0.1.1);
zip/Release 未发布(待用户验收)。
- **真机 M2S100"卡住"排查(2026-10-10,用户复测反馈)**:用户 v0.1.1 exe
发 M2S100 后 ack 与周期帧均不显示。本机分层复现均正常——裸 pyserial+
解析器(帧 100% 到达、ack ok、100ms 间隔稳)、offscreen GUI 全链路
(60 帧/6s 一帧不丢)、桌面真渲染完整用户序列(连接→M1/M2→M2S100→帧流,
138 帧全显示)。exe 经归档解包确认含 v0.1.1 代码。未能复现,定性为
用户端环境/运行态问题,待用户按状态栏"积压"指示再复现时定位。
排查副产品:
- link.py 串口参数加 `inter_byte_timeout=0.02`(Win32
ReadIntervalTimeout)——注意 **pyserial 的 timeout 是总超时**(150ms 内
必返回,不无限攒批),此参数把周期帧的 read 粒度从 ~150ms 一批改善为
单帧粒度(帧时间戳更准);
- 状态栏计数器追加 **积压 N**(events.qsize())——常驻分层观测:数字
持续增长=事件产出快于消费(GUI 侧问题),恒 0=RX 链路断(串口/驱动/
固件侧问题);"卡住"时 UI 整体无响应则是主线程被阻塞,另查。
- 勘误:修复初期曾把 M2S100 卡住归因"read(4096) 攒批 41 秒",该论述
错误(win32 timeout 为总超时),已修正注释;loop:// 虚拟口的 read
立即返回缓冲,**测不出真串口等待行为**,链路自检覆盖不到此处。
- 探测善后:COM5 FilmForge 固件曾残留 100ms 上报态(用户上次 M2S100
生效的证据),已发 M2S0 关闭(M2 查询=OFF)。
待办 / 计划:无新增,沿用 v0.1.0 待办。
### v0.1.0(2026-09-27,首版)
已完成:
- 协议模块:**帧构成令牌序列可定制**(`HDR ID LEN PAYLOAD CRC` 令牌 + 十六进制
字面量同步字节,收发共用布局,支持字段换序/无板ID/多字节同步头;LEN 口径三种:
payload 长度 / 到帧尾 / 整帧)、5 种 CRC 算法 × 3 种覆盖范围(按帧内位置定义,
任意布局通用)、增量解析(前缀逐位对齐 + CRC 失败逐字节重同步)、
二进制/文本/正则三种曲线数值提取;自测 64 项全 PASS(含 DLPilot 实测帧向量、
CRC 标准校验值 check("123456789")、布局换序/字面量/LEN 口径向量与布局模糊往返)。
- 串口链路:参数热切换、发送队列(半双工友好 + 未连接丢弃)、解析/透传双模式
热切、`loop://` 虚拟回环自检 13 项全 PASS(组帧→发送→接收→解析→曲线提取)。
- GUI(PySide6-Fluent-Widgets):串口/协议/曲线/接收/发送五卡,帧构成与 LEN 口径
可视化编辑(非法输入即时提示),payload 字符串/HEX 切换(帧日志的 payload
段垫半透明底色高亮——主题无关 rgba, 明暗切换不残留, 经 appendHtml 实现)、
原始帧 hex、时间戳、日志保存;发送:自动包装(`/` 分隔多条)、HEX 直发、
周期发送、组帧实时预览、历史回溯(↑↓)、回车发送;主题跟随系统。
- 曲线(plot.py + 弹窗,2026-09-27 实机反馈多轮迭代):4 通道三种提取方式 +
倍率,自动/手动 Y 量程;绘图区可**弹出独立窗口**(嵌回自动重置视图);
纵轴每条参考线标数值刻度;时间窗(跟随/10s~15min, 示波器式不向数据收敛,
右下角"已采集/窗口"标注)+ 横向滑条回看历史;悬停读值框固定顶部、鼠标
不动也随数据持续刷新;**暂停渲染**(冻结显示继续收数, 与主界面"暂停"
两级语义, 冻结有中央角标);自动Y 开关、图上拖动自定义 Y 轴(同步主界面
输入框)、滚轮缩放时间窗(2~900s)、实时按钮;窗口最小高度按内容自适应
(防高度拉小时通道行叠压绘图区)。
- 板型档案:发送卡"板型"下拉一键切换协议参数 + 命令预设;**新建板型**(协议
沿用当前、命令预设空白,防遗留其他板子的命令)/存为板型(同名覆盖)/
**删除板型**(确认弹窗,至少保留一个,删除后切第一个板型且协议参数不动)/
存入预设命令(弹窗自定义显示名称)/配置=内嵌表格编辑器(**CommandTable**:
第 0 列 ≡ 手柄按住上下拖动整行排序——内部 QDrag,move_row 整行搬移;
网格线/隔行底色按 palette 明暗自适配,空行清晰可见;+加行 −删行;空命令与
重复命令保存时忽略/去重,写回 board_profiles.json)/重载;预设下拉
"名称 · 命令"同显。
- 发布流水线:build_exe/package_release/publish_gitea 与全仓一致;exe 已重建
入库,zip/Release 未发布(待用户验收)。
待办 / 计划:
- 【无图标】assets/serial-mate.ico 未生成(build_exe.py 已预留路径,
参照 DLPilot logos/make_ico.py 流程补)。
- 【真机已过】用户已实机联调(COM15 真机收发/曲线提取/多板型档案实际使用,
2026-09-27 多轮界面反馈即来自真机数据);温度曲线量纲(倍率取值)待用户
按传感器手册确认。
- 【计划】发送区多条 payload 的"批处理脚本"化(延时/循环/条件等待应答)。
- 【计划】曲线数据导出 CSV;接收日志按日期自动分文件。
- 【考虑】CRC 解析侧双字节序兼容开关(当前按算法约定单序)。