Files
STM32F4-Base/docs/测试说明/串口助手使用指南.md
T
wandering1 d1e6bb4ba7 W5波+N-19收口:FTP鉴权落地、Trap25三连定案修复(N-30)、N-20/N-23/N-26
N-19 FTP 鉴权:
- lftpd_auth(USER/PASS 状态机 + 连续5次失败锁60s)+ SHA-256
  (user||pwd) 口令哈希;config.ini [ftp] 段 user/pwd_hash/write,
  出厂 admin/admin 在用有启动告警;lftpd_log.c 删除并入 lftpd_auth
- ftp_cfg 配置读写;台架 ftp_auth_bench.py 13/13

N-30 / Trap 25(CH395F_Trap_Records.md 四次定案全记录):
- 修复1 closed-with-data accept:客户端发完正常 FIN 后芯片按
  FUN_PARA(0x09) §5.52② 保留 Socket+RX 缓冲,旧代码把 CLOSED 一律
  relisten 自毁缓冲 → STOR 必挂 425;改为接受"已关但可读"连接排空
- 修复2 丢 CONNECT 中断探针 net_lost_connect_probe + last_peer_port
  幽灵甄别(拆链窗口握手完成但中断丢失,双信源去抖补 accept)
- 修复3 send_list/send_nlst 批量单发:背靠背 ~60B 小段毒化芯片 TX
  (芯片报 SEND_OK 但对端全丢),整份目录一次性写入
- 证伪存档:固定沉降/Socket 轮换冷却/listen 门控(台架 v7 矩阵)
- 台架工具 test/board/ch395f_reuse_bench.{c,h} + host 脚本存档
  (不注册编译;实验分支 n30-chip-bench 保留)

N-20:Error_Handler 死循环降级(记录+复位路径)
N-23:scripts/check_registration.py 双端注册反向校验(pre-commit)
N-26:GD5F NAND 直 DMA 调用方缓冲窗口断言

验证:BUILD_OK 0/0;ftp_regression 连跑 4 轮 11/11(修复前
LIST#4/#7 100% 挂、STOR#2 曾 100% 425);ftp_auth_bench 13/13;
@test 启动标记 PASS;ci_local L0+L0.5 全过;doc-links 通过。
登记簿 N-30 已销项。
2026-10-11 18:50:24 +08:00

385 lines
22 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.
# Python 串口助手(serial_tool)使用指南
> **定位**:面向使用者的操作手册——命令怎么敲、输出怎么看、常见场景怎么组合、出问题怎么排查。
> **关联文档**:设计决策与实现细节见 `串口助手设计说明.md`;@test 自动化闭环见 `自动化测试流程.md`;`serial-assistant` skill 有一页速查(用到自动加载,AGENTS.md 不再重复收录)。
> **版本**:v1.1(与 `.dsh/skills/serial-assistant/scripts/serial_tool.py` 当前实现一致,2026-09-15)
## 1. 它是什么(60 秒版)
仓库内置的串口调试工具,三个组成部分:
| 组成 | 是什么 | 你要知道的 |
|---|---|---|
| **常驻 daemon** | 后台 Python 进程,**串口唯一持有者**,把 RX/TX 持续写进日志文件 | 不用管它——任何命令发现它没在跑会**自动拉起** |
| **薄 CLI** | `serial_tool.py <命令>`,每条命令都是带 5s 硬超时的短操作 | 你唯一直接打交道的界面 |
| **日志文件** | `.dsh/skills/serial-assistant/scripts/state/serial.log`,**全量历史就在这里** | 想回看任何时刻的数据 → 直接读这个文件 |
**统一入口**(仓库根目录执行,任意 cwd 均可):
```powershell
& '.venv\Scripts\python.exe' '.dsh/skills/serial-assistant/scripts/serial_tool.py' <命令> [参数]
```
两条心法:
1. **所有输出都是 JSON**(stdout),退出码 0=成功 / 1=领域错误 / 2=daemon 不可达 / 3=用法错误;
2. **历史不用"采集"**——daemon 一直在写日志文件,随时 Read/Grep 就是完整历史。
## 2. 快速上手
```powershell
# ① 看本机有哪些 COM 口(不依赖 daemon;configured = 配置目标口)
& '.venv\Scripts\python.exe' '.dsh/skills/serial-assistant/scripts/serial_tool.py' ports
# ② 开串口(自动拉起 daemon;端口取 .serialtest/config.json,也可显式指定)
& '.venv\Scripts\python.exe' '.dsh/skills/serial-assistant/scripts/serial_tool.py' open
# ③ 发一条数据(文本;--hex 发字节;--nl 末尾加 CRLF)
& '.venv\Scripts\python.exe' '.dsh/skills/serial-assistant/scripts/serial_tool.py' send "hello" --nl
# ④ 看最近 20 行日志(含刚发的 TX 和收到的 RX)
& '.venv\Scripts\python.exe' '.dsh/skills/serial-assistant/scripts/serial_tool.py' log --tail 20
# ⑤ 不用了:关串口(daemon 继续常驻;要彻底退出用 stop)
& '.venv\Scripts\python.exe' '.dsh/skills/serial-assistant/scripts/serial_tool.py' close
```
## 3. 命令速查
| 命令 | 作用 | 依赖 daemon |
|---|---|---|
| `ports` | 枚举 COM 口,标注配置目标口(`configured_present`;系统里只有**一个**候选时给 `hint`) | 否(本地执行) |
| `open` | 开串口(**幂等**:同口已开 = 直接成功;不带 `--dtr/--rts` = **保持现状**,显式给则现场应用并在 `note` 里回显) | 是(未运行自动拉起) |
| `close` | 关串口 | 是 |
| `send` | 发 TX 字节(进日志) | 是 |
| `status` | 全状态一条 JSON(含 `state`、`rx_age_ms` = 距最后一个 RX 字节的毫秒数,`null` = 这个 daemon 还没收过字节) | 是 |
| `log` | 读日志文件(`--tail N` / `--grep P`) | **否**(直接读文件,daemon 死了也能用) |
| `clear` | 清空日志 + 代际 +1 | 是 |
| `wait-idle` | 等 RX 静默(@test 的收尾判据) | 是 |
| `start` / `stop` / `restart` | daemon 生命周期 | — |
| `selftest` | 无头自检(行拆分/解码/截头算法,无需硬件) | 否 |
所有命令都支持 `--timeout <秒>`(默认 5)覆盖 CLI→daemon 硬超时。
### 3.1 `ports` — 枚举串口
```powershell
... serial_tool.py ports
```
```json
{"ok": true,
"ports": [{"name": "COM1", "description": "通信端口 (COM1)", "hwid": "ACPI\\PNP0501\\0"}, …],
"configured": "COM6"}
```
- `configured` 来自 `.serialtest/config.json` 的 `portName`;找不到它 = 板子没接/驱动没装/口号变了。
- CH340 的 `description` 通常含"USB 串行设备"字样。
### 3.2 `open` — 开串口
```powershell
... serial_tool.py open # 端口/波特率取配置文件
... serial_tool.py open --port COM6 --baud 115200
... serial_tool.py open --dtr 1 --rts 0 # 显式置位(默认都不翻转,见下)
```
- **幂等**:同端口同波特率已开 → 直接返回成功(`note: already open`),不会重开端口。
- **DTR/RTS 默认不翻转**——防 STM32 自动复位电路把目标板误复位;确需翻转才用 `--dtr 1` / `--rts 1`。
- 成功返回:`{"ok":true,"port":"COM6","baud":115200,"dtr":false,"rts":false,"gen":7,"since":"…ISO…"}`。
- 失败(**≤1s 快速返回**,exit 1):
| 错误 | 含义 |
|---|---|
| `port not found: COM6 (available: COM1, …)` | 该口不存在(Windows 对此常报 Access Denied,工具已按端口枚举结果纠正判定) |
| `port busy: COM1 (held by another process)` | 口被其他进程占用 |
- **重开退避**:距上次关口/daemon 启动 <1.5s 时,open 会先等待再开(CH340"释放后立即重开 → 读 0 字节"陷阱的规避)。
### 3.3 `close` — 关串口
```powershell
... serial_tool.py close # → {"ok":true}
```
未开时返回 `{"ok":true,"note":"port not open"}`(不算错误)。daemon 继续运行。
### 3.4 `send` — 发数据
```powershell
... serial_tool.py send "hello" # 文本(UTF-8 编码发出,精确字节,不加换行)
... serial_tool.py send "hello" --nl # 末尾追加 CRLF
... serial_tool.py send --hex 0D0A # 发字节 0x0D 0x0A(支持空格分隔:0D 0A 亦可)
... serial_tool.py send --hex 48454C4C4F # = "HELLO"
```
- 文本与 `--hex` **二选一**;都不给 = 用法错误(exit 3)。
- 成功返回:`{"ok":true,"bytes":7}`(实际发出字节数)。
- 端口未开 → `port not open (run 'open' first)`。
- 日志中的 TX 行会把控制字符转义成字面文本(`…hello\r\n`),保持日志行式完整。
### 3.5 `status` — 全状态
```powershell
... serial_tool.py status
```
```json
{
"ok": true,
"daemon": {"pid": 16816, "version": "1.0.0", "uptime_s": 31},
"port": {"ready": true, "name": "COM1", "baud": 115200, "dtr": false, "rts": false,
"since": "2026-09-15T02:30:57.739+08:00"},
"counters": {"rx_bytes": 1834, "tx_bytes": 22, "rx_lines": 57, "tx_lines": 3, "errors": 0},
"last_rx": {"ts": "…", "line": "UI menu init: OK", "age_ms": 320},
"log": {"path": "…\\tools\\serial_tool\\state\\serial.log", "size": 4523, "gen": 7},
"state": "active",
"error": null
}
```
| 字段 | 说明 |
|---|---|
| `state` | `active`(正常收数据)/ `stopped`(口未开)/ `error`(读错/拔线,`error` 字段有详情;重开 `open` 即恢复) |
| `port.ready` | 已开 && 非 error |
| `counters` | 本 daemon 生命周期累计(daemon 重启清零) |
| `last_rx.age_ms` | 距最后一行 RX 的毫秒数——判断"还在刷/已静默" |
| `log.gen` | 当前日志代际(`clear` 会 +1) |
### 3.6 `log` — 读日志
```powershell
... 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 "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`。
- **先过滤、后截尾**(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
Get-Content '.dsh\skills\serial-assistant\scripts\state\serial.log' -Tail 20
Select-String -Path '.dsh\skills\serial-assistant\scripts\state\serial.log' -Pattern 'App init OK'
```
### 3.7 `clear` — 清空日志(代际 +1)
```powershell
... serial_tool.py clear # → {"ok":true,"gen":8}
```
- 原地截断 + 写 `[SYS] … log cleared (gen=N+1)` 标记行;**不重开文件、不动串口**。
- 用途:每次烧录/实验前 clear 一下,之后日志里就只属于本轮,新旧内容按 `gen`/标记行区分。
### 3.8 `wait-idle` — 等 RX 静默
```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
{"ok": true, "rx_bytes": 1834, "idle": true, "waited_ms": 8041, "reason": "idle"}
```
| `reason` | 含义 | `idle` |
|---|---|---|
| `idle` | 收到过数据,且已连续静默 ≥ `--settle-ms`(**正常收尾**) | true |
| `no_data` | **本次等待期间**一个字节都没收到(板子没输出/口不对/没烧进去) | false |
| `timeout` | `--max-ms` 到了仍在持续吐数据 | false |
- **`--settle-ms` 默认 8000 不是随便定的**:本工程 boot 任务间有 3~4s 自然静默间隙,判据小于它会把开机日志截断。
- **首字节门(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 生命周期
```powershell
... serial_tool.py start # 拉起(已在跑 → already_running:true,不做任何事)
... serial_tool.py stop # 优雅退出:关串口 → 冲刷尾行 → 退出
... serial_tool.py restart # 停旧 + 拉新,保证新进程加载的是当前代码
```
- 日常**不需要** start(任何命令都会自动拉起)。
- **改了 `serial_tool.py` 之后**要 `restart`,否则运行中的还是旧代码。
- `stop` 后 COM 端口立即释放,可交给其他程序;daemon 死掉/被杀同理(下次命令自动拉起,但**串口不会自动重开**——需要时再 `open`)。
### 3.10 `selftest` — 无头自检
```powershell
... serial_tool.py selftest # 10 项进程内回环测试,无需硬件
# [PASS] T1a CRLF 行 … SELFTEST PASS(exit 0)
```
新机器搭完环境先跑它:过了 = 解释器/pyserial/核心算法都正常。
## 4. 日志文件(全量历史)
### 4.1 三个文件
以下文件均在 `state/` 下,属**运行时产物,未创建于仓库**(首次运行工具后才生成):
| 文件 | 内容 | 谁读 |
|---|---|---|
| `.dsh/skills/serial-assistant/scripts/state/serial.log` | RX/TX/SYS 全量日志 | 你(Read/Grep/`log` 命令) |
| `.dsh/skills/serial-assistant/scripts/state/meta.json`(运行时未创建) | daemon pid / 版本 / 当前 gen / 口配置快照 | 工具内部 |
| `.dsh/skills/serial-assistant/scripts/state/daemon.log` | daemon 自身 stderr(启动行、异常) | 排障时看 |
整个 `state/` 目录已 gitignore——纯本机运行时产物。
### 4.2 行格式
```
[SYS] 2026-09-15T02:30:57.739+08:00 port opened COM1 115200 (gen=0)
[RX] 2026-09-15T02:31:23.101+08:00 App init OK
[TX] 2026-09-15T02:31:23.986+08:00 hello_from_tool\r\n
[SYS] 2026-09-15T02:31:24.245+08:00 log cleared (gen=1)
[SYS] 2026-09-15T02:39:54.598+08:00 log truncated to 10MB (cap=20MB)
```
| 规则 | 说明 |
|---|---|
| 方向标记 | `[RX]` 串口收到 / `[TX]` 你发出 / `[SYS]` 工具事件(open/close/error/cleared/truncated) |
| 时间戳 | 本地 ISO8601 含毫秒与偏移;= **该行首字节到达时刻** |
| **只含完整行** | 残行留 daemon 内存,200ms 无后续数据才冲刷成行 → 你**永远读不到半行** |
| 解码回退 | UTF-8 → GBK → 都不行则该文本域写 `HEX:ab12…`(纯二进制流时整行转 hex) |
| TX 转义 | 发出的控制字符写成字面 `\r` `\n`(日志按行存储,真换行会把一行劈成两行) |
| flush | 每行即 flush——文件随时可读、内容即时落盘 |
### 4.3 体积上限与截头
- 单文件上限 **20MB**;超过后 daemon **原地截头**保留**尾部 10MB** 并写 `[SYS] … log truncated` 标记行。
- 截头不重开文件、不重开端口、不影响正在收的数据——但**文件头部的老内容会消失**。
- 因此:查旧数据要看**文件尾部**(`log --tail N` / `Get-Content -Tail`),别从文件头读。
- 嫌它涨得快/想立刻清空 → `clear`(代际 +1,比截头干净)。
## 5. 典型工作流
### 5.1 交互式调试(发命令看响应)
```powershell
... serial_tool.py open
... serial_tool.py send "AT+STATUS" --nl
... serial_tool.py log --tail 10 # 看响应(或直接 Read 日志文件)
# … 反复 send / log …
... serial_tool.py close
```
### 5.2 抓一次开机日志(手动版 @test)
```powershell
... serial_tool.py open --port COM6
... serial_tool.py clear # 代际 +1:此后内容只属于本轮
# ……此刻去烧录/复位板子(ST-Link 烧录不占 COM,串口全程保持打开)……
... serial_tool.py wait-idle --settle-ms 8000 --max-ms 30000
... serial_tool.py log --tail 0 # 取本轮全量 → 人眼/脚本检查标记
```
自动化版本 = `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`(同一序列 + 烧录 + 标记校验,见 `自动化测试流程.md`)。
### 5.3 长时运行监控
```powershell
... serial_tool.py log --grep "ERROR" --tail 50 # 随时捞特定内容
... serial_tool.py status # 看 counters.errors / last_rx.age_ms
```
daemon 常驻,日志持续累积(20MB 自动截头),几天不关也没压力。
### 5.4 排障三件套
```powershell
... serial_tool.py status # ① state/error/counters 一眼看全
Get-Content '.dsh\skills\serial-assistant\scripts\state\daemon.log' -Tail 30 # ② daemon 自身输出
... serial_tool.py restart # ③ 重建 daemon(顺带加载新代码)
```
## 6. 配置
唯一配置源:`.serialtest/config.json`
```json
{
"portName": "COM6",
"portHint": "USB 串行设备",
"baudRate": 115200,
"httpPort": 18765,
"note": "……"
}
```
| 字段 | 用途 | 缺省 |
|---|---|---|
| `portName` | `open` 的默认端口;`ports` 的 `configured` 标注 | 无(必须给) |
| `baudRate` | 默认波特率 | 115200 |
| `httpPort` | daemon HTTP 端口(一般不动) | 18765 |
**换端口只改这个文件**(`@test` 与手动调试都读它)。`httpPort` 冲突时才需要改。
## 7. 故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
| `port not found: COMx (available: …)` | 板子没接 / CH340 驱动没装 / 口号变了 | 对照 `available` 列表;装 CH340 驱动;改 `.serialtest/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 自然静默间隙就收尾;② 首字节晚于 `--settle-ms` 到达,静默计时提前触发 | 用默认 8000(`run_boot_test.py` 已内置);2026-10-10 起首字节到达前不启动静默计时,必要时调 `--first-timeout-ms` |
| `open` 报 `no port specified … [config: …]` | `.serialtest/config.json` 缺失 / JSON 语法错 / `baudRate`·`httpPort` 非整数(现按 `utf-8-sig` 读并**显式回显**错误,不再静默回退成"没配端口") | 按 `config_error` 文本修文件;`status` 与 `ports` 也回显 `config_error` |
| 旧日志"丢了" | 20MB 截头丢的是**文件头** | 查旧内容要看尾部;重要轮次开始前先 `clear` 分代 |
## 8. 常见问题
- **daemon 会一直占着内存吗?** 一个 Python 进程约 20MB;不需要时 `stop` 即释放。常驻与否都不影响正确性。
- **daemon 被强杀(taskkill)会怎样?** 端口由系统自动释放;下次任何命令自动拉起新 daemon。**串口不会自动重开**,需要时再 `open`(这是设计:避免在设备异常时盲目重连)。
- **能同时开两个串口吗?** v1 单口(一个 daemon 管一个口)。多口需求见 `docs/测试说明/串口助手设计说明.md` §8 v2 候选。
- **能直接删 `serial.log` 吗?** 可以,但代际 `gen` 不会变、写句柄不变——建议用 `clear`(原子 + 代际 +1 + 标记行)。
- **为什么 TX 日志里是字面 `\r\n` 而不是真换行?** 日志按行存储;真换行会把一条 TX 劈成两行,转义后一条 TX 恒一行。
- **ST-Link 烧录会抢串口吗?** 不会(烧录走 SWD,不走 COM)——这正是"串口全程保持打开、烧录前后不重开"的设计基础,也彻底绕开了 CH340 释放/重开陷阱。
- **别的程序/Agent 能绕过 CLI 直接用吗?** 能——daemon 暴露本机 HTTP JSON 接口(§9)。
## 9. 附录:HTTP 协议(绕过 CLI)
daemon 在 `127.0.0.1:<httpPort>`(默认 18765)提供 JSON API,与 CLI 一一对应:
| 方法 路径 | 请求体(JSON,可空) | 说明 |
|---|---|---|
| `GET /ping` | — | `{"ok":true,"version":"1.0.0","pid":…}` |
| `GET /status` | — | 同 `status` 命令 |
| `GET /ports` | — | 同 `ports` 命令 |
| `POST /open` | `{"port":"COM6","baud":115200,"dtr":0,"rts":0}`(均可省) | 同 `open` |
| `POST /close` | — | 同 `close` |
| `POST /send` | `{"text":"hi","nl":true}` 或 `{"hex":"0D0A"}` | 同 `send` |
| `POST /clear` | — | 同 `clear` |
| `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` 未知端点。
PowerShell 示例:
```powershell
Invoke-RestMethod http://127.0.0.1:18765/status
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 口独占绑定是否真生效 |
| 2026-10-10 | 第三批同步:① `ports` 加 `configured_present` + `hint`(换 USB 口/换机后 COM 号变了,系统里只有一个候选时直接给出口名;`open` 的 `port not found` 报错里也带 `[hint: ...]`);② `status` 加 `rx_age_ms`(判"板子是不是根本没在说话"不必自己减时间戳);③ `open` 幂等分支不再吞 `--dtr/--rts`——不带这俩参数时**保持当前 DTR/RTS**(旧实现把"没给"当 False,重开即静默拉低),显式给则现场应用并在 `note` 回显 `applied dtr=1` |