b45f50569b
依据 Issue #52:CH340 一直都在、/serial-boot-test 已跑通(boot_log_data 有 2026-10 大批日志), 「等 CH340 接入」是 2026-09 建档时的过时状态;字母+数字编号按 docs/编号规范.md §0 已废。 - 串口助手设计说明.md:§5 状态改 CH340 已接入(COM3)+ 正路径已具备验收条件; §9.2 验收表 A1–A8 全部换语义短语;§10 步骤 4/5 同步。 - 登记簿.md §7:销掉「串口助手正路径补验」跟踪行(CH340 一直在、流程已跑通,无需再等)。
267 lines
20 KiB
Markdown
267 lines
20 KiB
Markdown
# Python 串口助手(serial_tool)设计说明
|
||
|
||
> **版本**:v1.1(需求 + 设计 + 实施记录)
|
||
> **日期**:2026-09-15
|
||
> **状态**:**已实施**(步骤 1–6 完成;本机无硬件自检项全部通过;CH340 已接入 = COM3,「拔线容错」与「烧录+抓开机日志全流程」两条正路径的硬件前提已具备,后者已由 `/serial-boot-test` 台架持续跑通)
|
||
> **关联文档**:`串口助手使用指南.md`(命令用法/工作流/排障) · `自动化测试流程.md` · `serial-assistant` skill(命令速查/陷阱) · `.serialtest/config.json`(端口单一配置源,2026-10-10 由 `.opencode/serial_config.json` 迁入)
|
||
> **取代**:`serial-mcp-server_串口调试.md`(实施时删除,工具历史并入本文 §12)
|
||
|
||
## 1. 背景与目标
|
||
|
||
### 1.1 为什么重写
|
||
|
||
现串口调试 = Adancurusul/serial-mcp-server(Rust 二进制,手编译置于用户档案)+ MCP/CLI 双形态。两个致命缺陷(2026-09-15 本机实测):
|
||
|
||
| # | 缺陷 | 影响 |
|
||
|---|---|---|
|
||
| F1 | 二进制绑定**用户档案**:`opencode.json` 的 `mcp.serial` 指向 `C:\Users\16241\AppData\Local\serial-mcp-server\*.exe`,本机实际用户为 **Administrator**(该档案不存在,二进制缺失) | serial MCP 工具不可用、/serial-boot-test 采集步骤断裂;换机器/换用户无法复现 |
|
||
| F2 | **无历史缓冲**:`read` 只能读调用**之后**到达的字节 | 抓开机日志被迫走"采集窗口 + first-byte 触发 + EOF 重试"仪式(6 个参数 + JSON 解析 + CH340 释放后重开陷阱特判) |
|
||
|
||
Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机器/用户可复现);常驻 daemon 把**全量** RX/TX 持续写入日志文件,历史 = 日志文件本身,采集窗口仪式整体消失。
|
||
|
||
### 1.2 目标
|
||
|
||
| # | 目标 |
|
||
|---|---|
|
||
| G1 | Agent 可枚举 COM 口、连接;失败 ≤1s 快速返回,绝不阻塞 |
|
||
| G2 | RX/TX 全量写入纯文本日志文件,Agent 直接读文件即得完整历史 |
|
||
| G3 | Agent 一切操作 = 带硬超时(5s)的短命令;串口所有权归常驻 daemon |
|
||
| G4 | 一条 `status` 命令看全状态(daemon/端口/计数/最后 RX/日志文件/错误) |
|
||
| G5 | 随时清空日志(原子 + 代际号,新旧内容可区分) |
|
||
| G6 | /serial-boot-test 一键流程切换新工具:open → clear → 烧录 → 读文件 → verify,无采集窗口 |
|
||
| G7 | 全流程**不关端口**(ST-Link 烧录不占 COM),从根上规避 CH340 释放/重开陷阱 |
|
||
|
||
## 2. 需求清单
|
||
|
||
| # | 需求 | 优先级 | 来源 |
|
||
|---|---|---|---|
|
||
| R1 | `ports`:枚举 COM 口(名称/友好名/厂商,标注配置目标口) | P0 | 用户 #1 |
|
||
| R2 | `open`:连接端口;不存在/被占用 ≤1s 返回明确错误;参数 port/baud/DTR/RTS | P0 | 用户 #1 |
|
||
| R3 | RX/TX 全量时间戳日志(单文件,纯文本,行格式见 §5) | P0 | 用户 #2 |
|
||
| R4 | 全部命令硬超时 5s + 失败返回 JSON(exit code 区分),CLI 永不挂起 | P0 | 用户 #3 |
|
||
| R5 | `status`:单条 JSON 全状态 | P0 | 用户 #4 |
|
||
| R6 | `clear`:原子清空 + 代际递增 + 文件内标记行 | P0 | 用户 #5 |
|
||
| R7 | 常驻 daemon + 单实例 + CLI 自动拉起(Agent 无需记生命周期) | P0 | 补充 |
|
||
| R8 | 日志 20MB 上限,超限**原地截头**保留尾部 10MB(不重开端口/不 rename) | P0 | 补充(用户确认) |
|
||
| R9 | 开端口不翻转 DTR/RTS(防 STM32 自动复位电路误复位),可配置 | P0 | 补充 |
|
||
| R10 | 串口读错/拔线 → daemon 存活,state=error,可重开;daemon 被杀可自恢复 | P0 | 补充 |
|
||
| R11 | /serial-boot-test 集成:`run_boot_test.py` 采集步改写 + `verify_log.py` 兼容 + 旧文档/配置迁移 | P0 | 用户场景 |
|
||
| R12 | `send`(TX 进日志)、`log --tail/--grep`、`start/stop/restart`、`selftest` 无头自检 | P1 | 补充 |
|
||
|
||
## 3. 总体方案
|
||
|
||
### 3.1 形态:常驻 daemon + 薄 CLI + "日志文件即接口"
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A["Agent(opencode bash 工具)"] -->|"短命令(硬超时 5s)"| C["serial_tool.py CLI"]
|
||
C -->|"HTTP 127.0.0.1:18765(JSON)"| D["daemon 进程(detached 单实例)"]
|
||
D --> S["串口读线程(pyserial,后台)"]
|
||
S --> W["行拆分/解码/flush"]
|
||
W --> F[("state/serial.log<br/>纯文本,Agent 直接读")]
|
||
D --> M[("state/meta.json<br/>pid/gen/版本")]
|
||
D --> L[("state/daemon.log<br/>daemon 自身 stderr")]
|
||
```
|
||
|
||
- **单文件实现**:`.dsh/skills/serial-assistant/scripts/serial_tool.py`(stdlib + pyserial,约 1000 行);解释器统一 `.venv\Scripts\python.exe`
|
||
- **Agent 调用式**:`& '.venv\Scripts\python.exe' '.dsh/skills/serial-assistant/scripts/serial_tool.py' <命令> [参数]`(任意 cwd;state 目录按脚本位置解析,与 cwd 无关)
|
||
- **历史 = 日志文件**:Agent 用 Read/Grep 直接看历史,无需专门 API(`log` 命令只是便捷封装)
|
||
- **配置沿用** `.serialtest/config.json`(`portName`/`baudRate` 为默认值;新增可选字段 `httpPort`,默认 18765)
|
||
|
||
### 3.2 "不卡死"的归属(对用户 #3 的重新表述)
|
||
|
||
串口读是长时操作,归 daemon 后台线程;**Agent 侧只存在短命令**。"不卡死" = 命令层契约:每条 CLI 命令有 5s 硬截止(`--timeout` 可覆盖),到期即返回超时 JSON 与 exit 2,不做任何无限等待。
|
||
|
||
## 4. 命令契约
|
||
|
||
### 4.1 子命令一览
|
||
|
||
| 命令 | 用途 | 关键参数 | 返回(stdout,恒 JSON) |
|
||
|---|---|---|---|
|
||
| `ports` | 枚举 COM 口 | — | `{"ok":true,"ports":[{"name":"COM6","description":"USB 串行设备 (CH340)"},…],"configured":"COM6"}` |
|
||
| `open` | 开端口(**幂等**:同端口已开 = 直接成功,不重开) | `--port` `--baud` `--dtr 0/1` `--rts 0/1`(默认取配置文件;DTR/RTS 默认 0) | `{"ok":true,"port":"COM6","baud":115200,"gen":7,"since":"…ISO…"}` |
|
||
| `close` | 关端口(flush 后释放) | — | `{"ok":true}` |
|
||
| `send` | 发 TX 字节(进日志);默认**精确字节**不加换行 | 位置参数文本(UTF-8)或 `--hex 0D0A`;`--nl` 追加 CRLF | `{"ok":true,"bytes":12}` |
|
||
| `status` | 全状态(见 §4.2) | — | 见 §4.2 |
|
||
| `log` | 读日志(**先过滤后截尾**,2026-10-10) | `--tail N`(默认 50;`0` = 全部)`--grep P` `-i` `--regex` | `{"ok":true,"gen":8,"lines":["…"],"scanned":N,"matched":M}` |
|
||
| `wait-idle` | 等 RX 静默(/serial-boot-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 |
|
||
|
||
### 4.2 `status` 返回示例
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"daemon": {"pid": 1234, "version": "1.0.0", "uptime_s": 61},
|
||
"port": {"ready": true, "name": "COM6", "baud": 115200, "dtr": false, "rts": false,
|
||
"since": "2026-09-15T10:29:58.112+08:00"},
|
||
"counters": {"rx_bytes": 1834, "tx_bytes": 12, "rx_lines": 57, "errors": 0},
|
||
"last_rx": {"ts": "2026-09-15T10:31:02.441+08:00", "line": "UI menu init: OK", "age_ms": 320},
|
||
"log": {"path": "D:\\…\\tools\\serial_tool\\state\\serial.log", "size": 4523, "gen": 7},
|
||
"state": "active",
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
`state ∈ {stopped, active, error}`;`port.ready = 已开 && state≠error`。Agent 轮询等待只用本命令。
|
||
|
||
### 4.3 CLI 退出码契约
|
||
|
||
| exit | 含义 |
|
||
|---|---|
|
||
| 0 | 命令成功(`ok:true`) |
|
||
| 1 | 领域错误(`ok:false` + `error`:端口不存在/被占用/参数错…)——**快速失败,≤1s** |
|
||
| 2 | daemon 不可达或命令超时(5s 硬截止) |
|
||
| 3 | 参数/用法错误 |
|
||
|
||
### 4.4 自动拉起
|
||
|
||
任何命令(除 `start` 本身)发现 daemon 未运行 → CLI 以 `sys.executable`(同一 .venv 解释器)detached 拉起 daemon(`CREATE_NO_WINDOW`,stderr → `state/daemon.log`),轮询 HTTP 就绪(≤5s)后**重试原命令一次**;仍失败按 exit 2 返回。Agent 无需任何显式 start 步骤。
|
||
|
||
## 5. 日志文件规范
|
||
|
||
### 5.1 位置与配套文件(目录整体 gitignore)
|
||
|
||
以下均为**运行时产物,未创建于仓库**(首次运行工具后自动生成):
|
||
|
||
| 文件 | 用途 |
|
||
|---|---|
|
||
| `.dsh/skills/serial-assistant/scripts/state/serial.log` | RX/TX/SYS 全量日志(Agent 直接读) |
|
||
| `.dsh/skills/serial-assistant/scripts/state/meta.json`(运行时未创建) | daemon 元数据(pid / 版本 / 当前 gen) |
|
||
| `.dsh/skills/serial-assistant/scripts/state/daemon.log` | daemon 自身 stderr(排障用) |
|
||
|
||
### 5.2 行格式
|
||
|
||
```
|
||
[SYS] 2026-09-15T10:29:58.112+08:00 port opened COM6 115200 (gen=7)
|
||
[SYS] 2026-09-15T10:29:58.301+08:00 log cleared (gen=8)
|
||
[RX] 2026-09-15T10:30:03.445+08:00 App init OK
|
||
[RX] 2026-09-15T10:30:07.912+08:00 UI menu init: OK
|
||
[TX] 2026-09-15T10:30:09.872+08:00 AT+PING
|
||
[SYS] 2026-09-15T10:31:00.000+08:00 log truncated to 10MB (cap=20MB)
|
||
```
|
||
|
||
| 规则 | 规定 |
|
||
|---|---|
|
||
| 方向标记 | `[RX]` 串口入 / `[TX]` Agent 发出 / `[SYS]` 工具事件(open/close/error/cleared/truncated) |
|
||
| 时间戳 | 本地 ISO8601 含毫秒与偏移(`2026-09-15T10:30:03.445+08:00`);**行时间戳 = 该行首字节到达时刻** |
|
||
| **完整行不变量** | 文件**只含完整行**:残行留 daemon 内存,`200ms` 无后续数据即冲刷成行(或 clear/close 时强制冲刷)→ Agent 读文件永不见半行。**两条补充(2026-10-10 第三批)**:① 无换行残行 **64KB 封顶**(`PARTIAL_MAX_BYTES`)——板子跑飞/烧录期吐二进制垃圾时旧实现会无限攒在内存里(长会话可到几十 MB),且后续每条正常行都被拼在这坨垃圾尾巴上变成不可读长行;超限即当一行落地(解码回退成 `HEX:`)并自动重新对齐换行。② `open` / `close` / `clear` 是**会话边界**,残行一并丢弃(`splitter_gen` 计数,串口线程下一轮循环生效)——旧实现残行跨会话留存,上一段末尾被复位截断的半行会粘进新会话第一行,开机日志首行因此不可读 |
|
||
| 分行 | 按 `\n` 分(`\r` 剥除);纯二进制流(无换行)按 200ms 窗口成行 |
|
||
| 解码回退 | UTF-8 → GBK → 仍失败则该行文本域写 `HEX:<原始字节 hex>`(MCU 流 = ASCII 为主 + UTF-8 中文,前两级足够) |
|
||
| flush | 每行即 flush(串口速率低,无缓冲压力)→ 文件随时可读、内容即时可见 |
|
||
|
||
### 5.3 清空与代际
|
||
|
||
- `clear` = 写句柄上**原地截断**(`seek(0)+truncate`)+ 写 `[SYS] log cleared (gen=N+1)` 标记行;对写线程原子(单写者,截断与写入同一线程串行)
|
||
- 代际 `gen` 持久化于 `meta.json`(daemon 重启不丢);Agent 以"最后一个 cleared/opened 标记行"或 `status.log.gen` 区分新旧内容
|
||
|
||
### 5.4 体积上限(20MB,用户确认)
|
||
|
||
- 每次写后检查:`size > 20MB` → **原地截头**:在同一写句柄上**读尾部 10MB → 截空 → 写回**(Windows 无"丢弃头部"单系统调用,`ftruncate` 只能截尾;单写者线程内执行,无并发风险)。**按行边界对齐(2026-10-10)**:取到的尾部会丢掉首个换行之前的残片——旧实现按字节偏移切,截出来的**第一行是半行**,直接破坏"文件只含完整行"这条不变量(Grep / verify_log 读到的首行是垃圾)
|
||
- **禁止** rename 轮转(Windows 下写句柄跟随被 rename 的文件)与重开文件/端口(CH340 释放后立即重开 → 读 0 字节 EOF 陷阱)
|
||
- 截头后写 `[SYS] … log truncated to 10MB (cap=20MB)`;文件头内容可能随时消失 → **Agent 读日志应看尾部**(`log --tail` 或直接读文件末段)
|
||
|
||
## 6. daemon 行为规范
|
||
|
||
| 项 | 规定 |
|
||
|---|---|
|
||
| 单实例 | HTTP 端口(默认 18765,`.serialtest/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` 可显式置位 |
|
||
| 读线程容错 | 串口读异常/设备拔出 → 记 `[SYS] … serial error: …`,`state=error`(`status.error` 非空),**daemon 不退出**;重插后 `open` 重开恢复 |
|
||
| 重开退避 | 错误恢复路径重开端口前 ≥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`,/serial-boot-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. 集成与迁移(/serial-boot-test 切换)
|
||
|
||
### 7.1 新 /serial-boot-test 流程(`run_boot_test.py` 采集步改写,退出码约定不变:0/1/2/3)
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A["open(自动拉起 daemon;幂等)"] --> B["clear → gen=N"]
|
||
B --> C["stlink-flash skill(stlink_flash.py flash:ST-Link 烧录+复位,端口全程打开)"]
|
||
C --> D["wait-idle:rx 静默 8s(-SettleMs)或超时 30s(-MaxWaitMs)"]
|
||
D --> E["提取本代际 [RX] 行,剥 [RX] 时间戳前缀 → boot txt"]
|
||
E --> F["verify_log.py(标记匹配)"]
|
||
```
|
||
|
||
- 对比旧流程:6 个采集窗口参数 + JSON 解析 + EOF 重试 **全部删除**;采集 = 端口常开 + 读文件
|
||
- `verify_log.py` 与纯文本日志契约一致(2026-09-20 随 /serial-boot-test 迁移 Python,判定语义不变)
|
||
- 参数保留:`-Port` `-BaudRate` `-FlashCmd` `-Required` `-Forbidden` `-OutDir`;删除 `-DurationMs` 等窗口参数,新增 `-SettleMs`(静默判据,**默认 8000**——boot 任务间有 3~4s 自然静默间隙,2000 会截断)`-MaxWaitMs`(默认 30000)
|
||
|
||
### 7.2 配套变更清单
|
||
|
||
| 对象 | 变更 |
|
||
|---|---|
|
||
| `requirements.txt` | 增 `pyserial`(**保持纯 ASCII** 注释) |
|
||
| `.gitignore` | 增 `.dsh/skills/serial-assistant/scripts/state/` |
|
||
| `opencode.json`(仓库根) | **删除 `mcp.serial` 段**(指向缺失二进制,已失效) |
|
||
| `测试说明/serial-mcp-server_串口调试.md` | **已删除**(内容精华并入本文;工具历史见 §12) |
|
||
| `AGENTS.md` | "串口调试"节改写为 serial_tool(配置源仍 `serial_config.json`);"自动化测试"节同步新流程 |
|
||
| `.opencode/command/test.md` | Step 0/2/Notes 改写(前置检查 = serial_tool 可用性 + `ports`;流程 = §7.1) |
|
||
| `docs/测试说明/流程与规范/自动化测试流程.md` | 采集机制描述同步(去 serial-mcp-server 表述) |
|
||
| `.opencode/serial_config.json` | 结构不变(可选增 `httpPort`);`note` 更新 |
|
||
|
||
## 8. 非目标与 v2 候选
|
||
|
||
**v1 明确不做**:多端口并发、MCP 封装、协议级 HEX 分析、硬件流控(RTS/CTS)、波特率自适应、GUI。
|
||
**v2 候选**:多端口(每口一 daemon 实例或子会话)、MCP wrapper(如需工具面板呈现)、按 tag 过滤的日志订阅(WebSocket/SSE)。
|
||
|
||
## 9. 测试与验收
|
||
|
||
### 9.1 无头自检(`selftest`,0=PASS)
|
||
|
||
行拆分器(CRLF/残行冲刷/纯二进制窗口)、解码回退链(UTF-8/GBK/HEX)、截头算法(合成大文件 → ftruncate 语义)——纯进程内,无需硬件。
|
||
|
||
### 9.2 端到端验收(逐项过)
|
||
|
||
| 用例名 | 用例 | 判据 |
|
||
|---|---|---|
|
||
| 端口枚举 | `ports` | 列表含 CH340 端口,`configured` 标注正确 |
|
||
| 端口被占用快速失败 | 端口被占用(另一进程先开 COM6)→ `open` | ≤1s 返回 `ok:false`(占用),无挂起 |
|
||
| 全流程走查 | `open→status→send→log→clear→close` 全走一遍 | 全 `ok:true`;日志含 TX 行与 cleared 标记;gen 递增 |
|
||
| 强杀 daemon 自恢复 | `stop` 后强杀 daemon(taskkill)→ 再发任意命令 | 自动拉起成功,`open` 幂等路径无 CH340 EOF 错误 |
|
||
| 日志截头 | 日志灌至 >20MB | 自动截头至 ~10MB,写入连续、无重开端口,`[SYS] truncated` 行存在 |
|
||
| 拔线容错 | 运行中拔 CH340 → `status` | daemon 存活,`state=error`;重插 + `open` 恢复 |
|
||
| 烧录+抓开机日志全流程 | **/serial-boot-test E2E ×3**(含一轮"上轮端口未关"场景) | 三轮全 PASS(Required 标记齐、无 Forbidden),boot 日志完整 |
|
||
| 无头自检 | `selftest` | 0=PASS |
|
||
|
||
## 10. 实施步骤(按序执行)
|
||
|
||
| # | 步骤 | 产物 | 验证 |
|
||
|---|---|---|---|
|
||
| 步骤 1 | `requirements.txt` 增 pyserial + `.venv` 安装 | requirements.txt | `pip show pyserial` 有版本 ✅ |
|
||
| 步骤 2 | daemon 核心:HTTP 服务 + 串口读线程 + 行日志写(含 clear/截头/DTR 策略) | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(daemon 部分) | `start→status→send→stop` 手动走通 ✅ |
|
||
| 步骤 3 | CLI 全子命令 + 自动拉起 + 超时契约 + `selftest` | serial_tool.py(CLI 部分) | `selftest` 0=PASS + 命令走查 ✅ |
|
||
| 步骤 4 | 验收各用例 | 测试通过记录 | 本机可验项全部 PASS ✅;「拔线容错」项硬件前提已由 CH340(COM3)具备 |
|
||
| 步骤 5 | /serial-boot-test 集成:`run_boot_test.py` 采集步改写 + 参数收敛 | run_boot_test.py | 负路径(端口缺失→exit 2)+ 假烧录全链路 + verify 正/负判定 ✅;「烧录+抓开机日志全流程」正路径硬件前提已由 CH340(COM3)具备 |
|
||
| 步骤 6 | 文档/配置迁移(§7.2 全清单) | docs + 配置 | 全库 grep 无 serial-mcp-server 残留引用(本文 §12 工具历史除外) ✅ |
|
||
|
||
提交:全部完成后单提交(pre-commit 门禁;纯宿主侧 Python,不涉及 Keil 构建)。
|
||
|
||
## 11. 风险与开放问题
|
||
|
||
| # | 项 | 策略 |
|
||
|---|---|---|
|
||
| O1 | 换机器缺 CH340 驱动 → `ports` 无目标口 | `ports` 返回带提示("未找到 CH340,请装驱动");文档列驱动要求 |
|
||
| O2 | 截头瞬间 Agent 恰在读文件 → 视图错位 | 已知可接受:Agent 读**尾部**;`log` 命令经 daemon 读更稳 |
|
||
| O3 | daemon 强杀瞬间丢 ≤1 行(内存残行) | 可接受(debug 日志场景);`close` 路径保证冲刷 |
|
||
| O4 | HTTP 端口 18765 与其他服务冲突 | `.serialtest/config.json` `httpPort` 可改 |
|
||
| O5 | TX 用途(本工程 USART1 单向输出) | 保留能力,面向未来交互/RS485 调试;v1 仅保证进日志 |
|
||
| O6 | daemon 常驻内存(Python 进程 ~20MB) | 可接受;`stop` 即释放 |
|
||
|
||
## 12. 工具历史
|
||
|
||
| 代 | 方案 | 结局 |
|
||
|---|---|---|
|
||
| 1 | Serial Studio MCP(GUI 依赖、导出文件定位问题) | 已清理(2026-09 前) |
|
||
| 2 | Adancurusul/serial-mcp-server(Rust,MCP+CLI) | **被本工具取代**:二进制绑定用户档案(本机缺失)+ 无历史缓冲(采集窗口仪式) |
|
||
| 3 | **serial_tool(本文,Python 仓库内)** | 当前方案 |
|