修复串口助手6处采集缺陷:wait-idle no_data与首字节门、log先过滤后截尾、HTTP独占绑定修正、超时不再误拉daemon、配置BOM容错
This commit is contained in:
+16
-6
@@ -151,12 +151,15 @@
|
||||
... serial_tool.py log # 末 50 行
|
||||
... serial_tool.py log --tail 200 # 末 200 行
|
||||
... serial_tool.py log --tail 0 # 全部行
|
||||
... serial_tool.py log --grep "App init OK" # 只留含子串的行
|
||||
... serial_tool.py log --grep "App init OK" # 只留含子串的行(全文件搜,再截尾)
|
||||
... serial_tool.py log --grep "init" --tail 100
|
||||
... serial_tool.py log --grep "app init" -i # 忽略大小写
|
||||
... serial_tool.py log --regex "\[STACK\]|Network Init" --tail 5 # 正则
|
||||
```
|
||||
|
||||
- **直接读日志文件**实现——daemon 死机/被杀也不受影响;`gen` 取自 `meta.json`。
|
||||
- 返回 `{"ok":true,"gen":N,"lines":["[RX] …", …]}`。
|
||||
- **先过滤、后截尾**(2026-10-10 修):旧实现先 `--tail`(默认 50)后 grep,`log --grep "App init OK"` 只要开机后又刷了 50 行就必然查无此 marker——这就是旧文档"log --grep 实测不可信"的根因,现已修复,可直接作判定依据。
|
||||
- 返回 `{"ok":true,"gen":N,"lines":[…],"scanned":总行数,"matched":命中行数}`;`--tail` 截掉了部分命中时另带 `note` 说明。`--regex` 写错 → `ok:false` + 错误文本(exit 3)。
|
||||
- 等价的手动读法(Agent 用 Read/Grep 工具、人用 PowerShell 均可):
|
||||
|
||||
```powershell
|
||||
@@ -178,6 +181,7 @@ Select-String -Path 'tools\serial_tool\state\serial.log' -Pattern 'App init OK'
|
||||
```powershell
|
||||
... serial_tool.py wait-idle # 静默 8s 或超时 30s
|
||||
... serial_tool.py wait-idle --settle-ms 5000 --max-ms 60000 # 自定义
|
||||
... serial_tool.py wait-idle --first-timeout-ms 12000 # 首字节 12s 未到即判 no_data
|
||||
```
|
||||
|
||||
```json
|
||||
@@ -187,11 +191,14 @@ Select-String -Path 'tools\serial_tool\state\serial.log' -Pattern 'App init OK'
|
||||
| `reason` | 含义 | `idle` |
|
||||
|---|---|---|
|
||||
| `idle` | 收到过数据,且已连续静默 ≥ `--settle-ms`(**正常收尾**) | true |
|
||||
| `no_data` | 全程一个字节都没收到(板子没输出/口不对/没烧进去) | false |
|
||||
| `no_data` | **本次等待期间**一个字节都没收到(板子没输出/口不对/没烧进去) | false |
|
||||
| `timeout` | `--max-ms` 到了仍在持续吐数据 | false |
|
||||
|
||||
- **`--settle-ms` 默认 8000 不是随便定的**:本工程 boot 任务间有 3~4s 自然静默间隙,判据小于它会把开机日志截断。
|
||||
- @test 流程(`run_boot_test.py`)用它作为"烧录后日志收齐"的判据。
|
||||
- **首字节门(2026-10-10 加)**:首字节到达**之前**不启动静默计时——旧实现从进入即计时,板子第一行比 `--settle-ms` 来得晚就提前收尾("开机日志缺尾巴"的另一半成因)。`--first-timeout-ms`(0 = 同 `--max-ms`)到点仍零字节 → `no_data` 快速失败。
|
||||
- **`rx_bytes` 是本次增量**(旧实现回的是 daemon 累计值,常驻 daemon 下动辄百万,无判读价值)。
|
||||
- **CLI 超时自动推导**:未显式带 `--timeout` 时按 `--max-ms/1000 + 5` 取值,不再需要手工加(旧版固定 5s 会在 daemon 还在等时掐断连接 → CLI exit 2 + daemon.log 刷 `ConnectionAbortedError`)。
|
||||
- @test 流程(`run_boot_test.py`)用它作为"烧录后日志收齐"的判据(`--first-wait-ms` 默认 12000)。
|
||||
|
||||
### 3.9 `start` / `stop` / `restart` — daemon 生命周期
|
||||
|
||||
@@ -324,11 +331,13 @@ Get-Content 'tools\serial_tool\state\daemon.log' -Tail 30 # ② daemon 自身
|
||||
| `port not found: COMx (available: …)` | 板子没接 / CH340 驱动没装 / 口号变了 | 对照 `available` 列表;装 CH340 驱动;改 `.opencode/serial_config.json` 的 `portName` |
|
||||
| `port busy: COMx (held by another process)` | 别的程序占着口 | `Get-Process` 找占用者关掉;或 `… serial_tool.py close` 释放本工具占的口 |
|
||||
| `daemon start timeout` / `daemon unreachable` | daemon 起不来(端口冲突/解释器问题) | 看 `state/daemon.log`;`… serial_tool.py restart`;确认 18765 未被占(改 `httpPort`) |
|
||||
| `daemon did not answer within Ns (busy? raise --timeout)` | 命令耗时超过 `--timeout`——daemon 活着、只是还在等(典型:显式给 `wait-idle` 带了小 `--timeout`) | 调大 `--timeout`,或不显式给(`wait-idle` 按 `--max-ms` 自动推导)。2026-10-10 起这种情况**不再**顺手拉起第二个 daemon |
|
||||
| `wait-idle` 返回 `no_data` | 全程无 RX | 核对 COM 号与波特率;板子供电;烧录是否成功;`status` 看 `port.ready` |
|
||||
| 日志里出现 `HEX:…` 行 | 该段字节不是 UTF-8/GBK 文本(二进制流/乱码) | 正常回退行为,按 hex 对照协议解析 |
|
||||
| `state: error` + `error: read: …` | 读串口出错/拔线 | daemon 仍活着;重插后 `open` 即恢复(自动 1.5s 退避) |
|
||||
| 改了 `serial_tool.py` 行为没变 | 运行中的 daemon 还是旧代码 | `… serial_tool.py restart` |
|
||||
| 开机日志"缺尾巴"(如缺 `Network Init`) | `--settle-ms` 太小,在 boot 自然静默间隙就收尾了 | 用默认 8000(`run_boot_test.py` 已内置) |
|
||||
| 开机日志"缺尾巴"(如缺 `Network Init`) | ① `--settle-ms` 太小,在 boot 自然静默间隙就收尾;② 首字节晚于 `--settle-ms` 到达,静默计时提前触发 | 用默认 8000(`run_boot_test.py` 已内置);2026-10-10 起首字节到达前不启动静默计时,必要时调 `--first-timeout-ms` |
|
||||
| `open` 报 `no port specified … [config: …]` | `serial_config.json` 缺失 / JSON 语法错 / `baudRate`·`httpPort` 非整数(现按 `utf-8-sig` 读并**显式回显**错误,不再静默回退成"没配端口") | 按 `config_error` 文本修文件;`status` 与 `ports` 也回显 `config_error` |
|
||||
| 旧日志"丢了" | 20MB 截头丢的是**文件头** | 查旧内容要看尾部;重要轮次开始前先 `clear` 分代 |
|
||||
|
||||
## 8. 常见问题
|
||||
@@ -354,7 +363,7 @@ daemon 在 `127.0.0.1:<httpPort>`(默认 18765)提供 JSON API,与 CLI 一
|
||||
| `POST /close` | — | 同 `close` |
|
||||
| `POST /send` | `{"text":"hi","nl":true}` 或 `{"hex":"0D0A"}` | 同 `send` |
|
||||
| `POST /clear` | — | 同 `clear` |
|
||||
| `POST /wait-idle` | `{"settle_ms":8000,"max_ms":30000}` | **同步阻塞**至静默/超时才返回 |
|
||||
| `POST /wait-idle` | `{"settle_ms":8000,"max_ms":30000,"first_timeout_ms":12000}`(均可省) | **同步阻塞**至静默/首字节超时/整体超时才返回 |
|
||||
| `POST /stop` | — | 关口后 daemon 退出(响应先于退出发出) |
|
||||
|
||||
响应码:`200` 成功 / `400` 领域错误(`ok:false`+`error`)/ `504` daemon 忙(命令 20s 未完成)/ `404` 未知端点。
|
||||
@@ -371,3 +380,4 @@ Invoke-RestMethod -Method Post http://127.0.0.1:18765/send -Body '{"text":"ping"
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-09-15 | 初版:命令全参考(含实测输出)、日志规范、典型工作流、故障排查、FAQ、HTTP 附录;与 v1.1 实现(含 4 项 Windows 缺陷修复)对齐 |
|
||||
| 2026-10-10 | 第一批可靠性修复同步:① `wait-idle` `no_data` 改按**本次增量**判(旧实现常驻 daemon 下永不触发)、加**首字节门** `--first-timeout-ms`、`rx_bytes` 改回本次增量;② `log` 改**先过滤后截尾**并加 `-i`/`--regex` + `scanned`/`matched` 计数(旧顺序即"log --grep 不可信"根因);③ `wait-idle` CLI 超时按 `--max-ms` 自动推导(不再需手工 `--timeout`),且超时不再被误判成"daemon 不可达"——旧实现会顺手拉起一个绑不上 HTTP 口的 daemon(`daemon.log` 里成串的 `listening`/`HTTP port busy` 即由此而来),现改为快速失败并提示调大 `--timeout`;④ 配置读取改 `utf-8-sig` 并显式回显 `config_error`;⑤ `status.daemon.exclusive_bind` 可验 HTTP 口独占绑定是否真生效 |
|
||||
|
||||
@@ -83,7 +83,8 @@ flowchart LR
|
||||
| `close` | 关端口(flush 后释放) | — | `{"ok":true}` |
|
||||
| `send` | 发 TX 字节(进日志);默认**精确字节**不加换行 | 位置参数文本(UTF-8)或 `--hex 0D0A`;`--nl` 追加 CRLF | `{"ok":true,"bytes":12}` |
|
||||
| `status` | 全状态(见 §4.2) | — | 见 §4.2 |
|
||||
| `log` | 读日志(文件尾) | `--tail N`(默认 50;`0` = 全部)`--grep P` | `{"ok":true,"gen":8,"lines":["…"]}` |
|
||||
| `log` | 读日志(**先过滤后截尾**,2026-10-10) | `--tail N`(默认 50;`0` = 全部)`--grep P` `-i` `--regex` | `{"ok":true,"gen":8,"lines":["…"],"scanned":N,"matched":M}` |
|
||||
| `wait-idle` | 等 RX 静默(@test 收尾判据) | `--settle-ms`(默认 8000)`--max-ms`(默认 30000)`--first-timeout-ms`(默认同 max) | `{"ok":true,"rx_bytes":本次增量,"idle":bool,"waited_ms":N,"reason":"idle\|no_data\|timeout"}` |
|
||||
| `clear` | 清空日志:原地截断 + 写 `[SYS] log cleared (gen=N+1)` | — | `{"ok":true,"gen":8}` |
|
||||
| `start` / `stop` / `restart` | daemon 生命周期 | — | `{"ok":true,"pid":…}` / stop 后 `{"ok":true,"stopped":true}` |
|
||||
| `selftest` | 无头自检(行拆分器/解码回退/截头算法 单测回环,无需硬件) | — | 0=PASS |
|
||||
@@ -166,7 +167,7 @@ flowchart LR
|
||||
|
||||
| 项 | 规定 |
|
||||
|---|---|
|
||||
| 单实例 | HTTP 端口(默认 18765,`serial_config.json` 的 `httpPort` 可改)绑定即锁;端口被占 = 已有实例 |
|
||||
| 单实例 | HTTP 端口(默认 18765,`serial_config.json` 的 `httpPort` 可改)绑定即锁;端口被占 = 已有实例。Windows 上真独占靠 `SO_EXCLUSIVEADDRUSE`(**真名,值 -5**;2026-10-10 修:旧代码写成 `SO_EXCLUSIVEADDRPLACE` + 数值 `0x0040`,`setsockopt` 抛 `WinError 10042` 被静默吞掉,"独占绑定"从未生效),失败现打 WARN,`status.daemon.exclusive_bind` 可验。⚠️ 进程列表里出现**两个** `--serve` 是正常的:`.venv\Scripts\python.exe` 是 venv stub,会派生基解释器(本机 anaconda)子进程跑真 daemon(`status.daemon.pid` = 子进程),不是多实例 |
|
||||
| 拉起方式 | CLI detached 子进程(`CREATE_NO_WINDOW`),解释器 = `sys.executable`(保证同一 .venv) |
|
||||
| `open` 语义 | 幂等(同端口已开 = 成功返回,**不重开**);端口不存在 → ≤1s 报错并附可用端口列表;被占用 → ≤1s 报错(Windows 共享冲突即时返回)并提示持有者 |
|
||||
| DTR/RTS | 默认**不翻转**(`dtr=False, rts=False`,防 STM32 自动复位电路误复位目标板);`--dtr/--rts` 可显式置位 |
|
||||
@@ -174,6 +175,9 @@ flowchart LR
|
||||
| 重开退避 | 错误恢复路径重开端口前 ≥1.5s 退避(CH340 特性);正常流程不重开 |
|
||||
| `stop` | 关端口(flush 尾行)→ 写 `[SYS] port closed` → 退出 |
|
||||
| 进程被强杀 | Windows 进程退出自动释放 COM 端口;下次命令自动拉起新实例(`open` 幂等路径) |
|
||||
| `wait-idle` 判据(2026-10-10 修) | `no_data` = **本次等待期间零字节**(旧实现比的是 daemon 累计 `rx_bytes` 是否为 0 → 常驻 daemon 下永不触发,板子全程静默也误报 `idle:true`,@test 的 no_data 拦截形同虚设);**首字节到达前不启动 settle 计时**(`first_timeout_ms` 兜底,默认 = `max_ms`);返回 `rx_bytes` = 本次增量 |
|
||||
| CLI 超时与断连噪声(2026-10-10) | `wait-idle` 未显式带 `--timeout` 时按 `max_ms/1000 + 5` 自动推导(旧版固定 5s 会在 daemon 还在等时掐断连接);handler 侧 `ConnectionError`(客户端已断开)不再往 daemon.log 打 traceback(本机曾累积 16 次);`daemon.log` 超 1MB 在拉起新实例前原地保尾 256KB |
|
||||
| 配置读取(2026-10-10) | 按 `utf-8-sig` 读(容忍 BOM,与 `run_boot_test.py` 同口径);文件缺失 / JSON 语法错 / `baudRate`·`httpPort` 非整数不再静默回退成"没配端口",错误文本进 `configError`,由 `status` / `ports` / `open` 显式回显 |
|
||||
|
||||
## 7. 集成与迁移(@test 切换)
|
||||
|
||||
@@ -266,3 +270,4 @@ flowchart LR
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-09-15 | v1.1 实施完成(S1–S6)。实施中实测发现并修复 4 个设计文档未覆盖的 Windows 实现级缺陷:① `SO_REUSEADDR` 在 Windows 允许多个活跃 socket 绑同端口 → HTTP 服务改**独占绑定**(`SO_EXCLUSIVEADDRPLACE`,发行版未导出时用数值 0x40 兜底)+ restart 强制确认旧实例死亡,防"already_running"假成功留下旧代码;② 写句柄 `"ab"` 追加模式在 Windows **禁读** → 溢出截头的"读尾部"必崩,改 `"a+b"`(自测用独立句柄故未暴露,真实 daemon 溢出路径已 E2E 验证);③ Windows 对**不存在**的 COM 口常报 Access Denied → 误报"port busy",改以 `list_ports` 枚举结果为"不存在/占用"判定准绳;④ 静默判据默认 2s 会截断 boot(任务间 3~4s 自然静默间隙),`-SettleMs` 默认改 8000ms。验收状态:A1–A5/A8 PASS(本机无 CH340,用 COM1 替代口验证;A5 溢出用进程内驱动真实 writer_loop 验证);A6/A7 正路径待硬件接入补验 |
|
||||
| 2026-10-10 | **第一批可靠性修复**(5 项,均实测复现后修,`selftest` 加 T5/T6 共 6 个回归用例,16/16 PASS):① `wait_idle` `no_data` 改按**本次增量**判——旧实现比 daemon 累计 `rx_bytes` 是否为 0,常驻 daemon(本机 rx_bytes=1.2MB)下板子全程静默也返回 `idle:true`,@test 的 no_data 拦截形同虚设;同时加**首字节门**(首字节到达前不启动 settle 计时,`--first-timeout-ms` 兜底)并把返回的 `rx_bytes` 改为本次增量;② `log` 改**先过滤后截尾**(旧顺序 + 默认 `--tail 50` = 开机 marker 必然查不到,即 AGENTS.md"log --grep 实测不可信"的真根因),并加 `-i` / `--regex` 与 `scanned`/`matched` 计数;③ `wait-idle` 的 CLI 超时按 `--max-ms` 自动推导(旧版固定 5s 掐断长等待 → daemon.log 累积 16 次 `ConnectionAbortedError` traceback,现 handler 侧静音 `ConnectionError`);同时 CLI 不再把**超时**误判成"daemon 不可达"——旧实现超时与拒连走同一分支,一次 1s 超时就会额外拉起一个 daemon(新实例绑不上 HTTP 口,打 `HTTP port busy` 后退出;daemon.log 里 36 条 `listening` 大半是这种抖动攒出来的),现超时快速失败并提示调大 `--timeout`,只有真·拒连才自动拉起(`selftest` T7 锁);④ **§13 09-15 那条"独占绑定"其实是坏的**:`SO_EXCLUSIVEADDRPLACE` 是拼错的名字(真名 `SO_EXCLUSIVEADDRUSE`,值 **-5**),数值兜底 `0x0040` 是无效选项 → `setsockopt` 抛 `WinError 10042` 被 `except` 吞掉,独占从未生效;现改真名 + 失败打 WARN + `status.daemon.exclusive_bind` 可验(实测 true);⑤ 配置读取改 `utf-8-sig`(旧版遇 BOM 静默回退成"没配端口")并把解析错误显式回显;另加 `daemon.log` 超 1MB 原地保尾 256KB。顺带纠正一条误诊:进程列表里两个 `--serve` 是 **venv stub + 基解释器父子对**,不是"多 daemon 并存" |
|
||||
|
||||
@@ -67,8 +67,8 @@
|
||||
|---|---|
|
||||
| `selftest` FAIL / `ModuleNotFoundError: pyserial` | venv 未建或依赖缺失:按 AGENTS.md「Python 环境」重建 `.venv` + `pip install -r requirements.txt` |
|
||||
| daemon 无响应 / 自动拉起失败 | 查 `tools/serial_tool/state/daemon.log`;`serial_tool.py restart` 重建 daemon;检查 COM 是否被其他程序占用 |
|
||||
| 抓到日志缺 `Network Init` | `-SettleMs` 太小(boot 任务间有 ~3-4s 自然静默间隙),用默认 8000ms |
|
||||
| 烧录后无数据(`no_data`) | 开串口 + clear 必须在烧录**前**完成(`run_boot_test.py` 已保证);核对 COM 号、板子供电,看 `state/serial.log` 有无 SYS 异常行 |
|
||||
| 抓到日志缺 `Network Init` | `-SettleMs` 太小(boot 任务间有 ~3-4s 自然静默间隙),用默认 8000ms;2026-10-10 起首字节到达前不启动静默计时(`--first-wait-ms` 默认 12000),首行晚到不再被提前收尾 |
|
||||
| 烧录后无数据(`no_data`) | 2026-10-10 起 `no_data` = **本次等待期间零字节**(旧实现比 daemon 累计字节,常驻 daemon 下永不触发,只能靠"日志里没有 [RX] 行"兜底);开串口 + clear 必须在烧录**前**完成(`run_boot_test.py` 已保证);核对 COM 号、板子供电,看 `state/serial.log` 有无 SYS 异常行 |
|
||||
| 端口被占用(`port busy`) | 先 `serial_tool.py close` 或 `restart`;检查其他程序占用;Keil ST-Link 不占 COM |
|
||||
| 日志中无 UI 相关输出 | 见下节「UI 自动化」 |
|
||||
| 日志出现 `[ERR]` 行 | 固件错误级日志(`DBG_ERROR`),默认 Forbidden 会判 FAIL:按 TAG 查对应模块的 `docs/问题记录/*_Trap_Records.md`;若属测试套件的**预期**错误(守卫负路径、非法名校验等),用 `--forbidden` 放宽 |
|
||||
@@ -93,7 +93,7 @@
|
||||
负路径与 FTL 运行期坏块、FatFs 非法名校验等),与默认 Forbidden 冲突,须显式放宽:
|
||||
`--forbidden "FAIL;HardFault"`(`[FAIL]` 仍会拦截真实断言失败);
|
||||
例:GD5F 只读 `--required "=== GD5F2GQ5UE Tests: 16/16 PASSED"`,破坏性 `68/68`。
|
||||
- **长时间采集**:daemon 全量持续写入 `state/serial.log`(20MB 上限自动截头保尾 10MB),直接读文件即可;或用 `log --tail 0 --grep <标记>` 过滤。
|
||||
- **长时间采集**:daemon 全量持续写入 `state/serial.log`(20MB 上限自动截头保尾 10MB),直接读文件即可;或用 `log --grep <标记>` 过滤(2026-10-10 起先过滤后截尾,不必再配 `--tail 0`)。
|
||||
|
||||
## 变更记录
|
||||
|
||||
@@ -102,3 +102,4 @@
|
||||
| 2026-09-13 | 「UI 屏幕自动化(当前缺口)」更新:UI 初始化 DBG 打印已实现(`ui_task.c`),屏幕内容已纳入模拟器像素回归;测试分层与门禁见 `测试开发标准流程.md` |
|
||||
| 2026-09-15 | 采集机制整体切换:serial-mcp-server(采集窗口/first-byte/EOF 重试)→ 仓库内置 Python 串口助手(daemon 常驻全量日志 + `wait-idle` 静默判据 `-SettleMs`/`-MaxWaitMs`);`serial_capture.ps1` 删除,流程/脚本清单/失败模式表同步 |
|
||||
| 2026-09-21 | 默认 Forbidden 增补 `[ERR]`:固件 `DBG_ERROR` 前缀是 `[ERR]`(≠ `ERROR` 子串),此前驱动/模块错误日志不会被自动闭环拦截;`verify_log.py` / `run_boot_test.py` 默认值同步,测试套件运行需显式放宽 |
|
||||
| 2026-10-10 | 采集链可靠性修复(serial_tool 第一批):`wait-idle` 的 `no_data` 改按本次增量判(旧实现常驻 daemon 下永不触发,"烧录后无数据"这一失败模式实际测不出来)+ 首字节门 `--first-timeout-ms`(`run_boot_test.py` 新增 `--first-wait-ms`,默认 12000);`log --grep` 改先过滤后截尾(旧顺序导致 marker 必然查不到)并加 `-i`/`--regex`;`wait-idle` CLI 超时自动推导,不再需手工 `--timeout`;HTTP 口独占绑定修正(旧选项名/数值均无效,实际未生效) |
|
||||
|
||||
Reference in New Issue
Block a user