331 lines
25 KiB
Markdown
331 lines
25 KiB
Markdown
# 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`
|
||
(空格转 ` `,防富文本折叠连续空格破坏 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)已上传核对(发布当日实例 SSH
|
||
1234 端口不通,git push 改走 HTTPS+token 一次重试成功——TLS 抽风老毛病)。
|
||
|
||
待办 / 计划:无新增,沿用 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` 空格转 ` ` 防富文本
|
||
折叠连续空格);`_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 解析侧双字节序兼容开关(当前按算法约定单序)。
|