Files
STM32F4-Base/.dsh/skills/serial-assistant/SKILL.md
T
super_agent_bot dfd1528409
CI / 静态门禁 (pre-commit) (pull_request) Failing after 4m19s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 44s
CI / 静态门禁 (pre-commit) (push) Failing after 4m51s
CI / 主机侧单测 (MSVC) (push) Failing after 43s
fix(serial-assistant): daemon 重启自动重开口 + 自动重连 + 日志缺口标记(#75)
按 Gitea issue #75 R1–R5 修采集层「daemon 死了采集静默停摆」族问题:
旧行为下 daemon 崩溃/被杀/读错丢口后,新实例起得来但口不开
(run() 先 _save_meta 把现场覆成 null,且无任何重开路径),
serial.log 缺口无边界,wait-idle 计满还良性返回 no_data,
把「采集器死」伪装成「板子静默」。

- R1 启动自动重开:meta.json 的 port 字段升格为「开口意图」现场
  (缺口期不清空,显式 close/stop 清除);run() 先读旧 meta 再覆写;
  重开在串口线程内执行(串口对象唯一持有者不变量),首次 = 出生 +1.5s。
  restart 特判:cmd_start(force) 在 stop 前抓 /status 现场、新 daemon
  就绪后 POST /open 开回来(stop 语义不变,结果进返回 JSON 的 reopen 键)。
- R2 失败自动重连:_port_error 后退避重试(REOPEN_SETTLE_S=1.5 起步、
  倍增、REOPEN_BACKOFF_MAX_S=30 封顶)直到成功或显式 close;
  逐次失败原文进 daemon.log(dlog=stderr),不刷 serial.log。
- R3 缺口标记:serial.log 落 [SYS] 行——daemon started(新旧 pid +
  重开现场)/ GAP-START(丢口与崩溃恢复两种,窗口起点下限声明)/
  GAP-END(窗口秒数 + 尝试次数 + 字节不可找回)/ GAP-RETRY(退避期
  仅首次 + 之后每分钟 ≤1 条,口径定死并写入设计说明 §6.1)。
- R4 状态可见:status 新增 auto_reopen{enabled,pending,attempts,
  last_error,next_retry_in_ms};port 新增 desired(意图)与 ready
  (实际)并列,缺口期 name/baud 显示待重开参数;wait-idle 入口加
  无口门:口未开秒回 ok:false + 现场,不再计满返回良性 no_data。
- R5:VERSION=1.2.0-skill;selftest 新增 T10–T15 共 22 项无头用例
  (重开意图判定 / 退避倍增封顶 / 缺口行格式 / 无口门 / auto_reopen
  三态 / restart 捕获),合计 45 项全绿;SKILL.md 与使用指南、
  设计说明 §6.1+§13 同步(机理只在设计说明详述一处)。

兼容性:JSON 只增字段、退出码形状不变;run_boot_test.py 正常路径
不受影响,其 wait-idle 撞无口时按采集错误 exit 2 归因(正是工单
要的区分)。

验证:selftest 45/45 PASS;COM3 台架三场景全过(崩溃恢复自动重开
+ GAP 标记、显式 close 后 restart 不重开、wait-idle 快速失败),
transcript 见工单汇报。
2026-11-04 11:10:04 +08:00

4.6 KiB
Raw Blame History

name, description, whenToUse
name description whenToUse
serial-assistant 常驻串口助手(采集层):daemon + 薄 CLI + 日志文件即接口。ports/open/send/log/clear/wait-idle/status 全套命令;RX/TX 全量历史落盘可直接 Read/Grep;一台机单 daemon 跨进程共享,自动拉起。serial-boot-test 等串口工作流的底座。 需要打开/关闭串口、发送命令或字节、查询串口历史日志、等 RX 静默(抓开机日志)、排查串口占用或端口参数问题时。

serial-assistant — 串口助手(项目级 skill,采集层)

单脚本 .dsh/skills/serial-assistant/scripts/serial_tool.py(Python + pyserial)。 架构:CLI(短命进程,硬超时)→ HTTP JSON → daemon(detached 单实例,127.0.0.1:18765); daemon = 串口读线程(唯一 I/O 者)+ 日志写线程(唯一文件写者)。 历史 = scripts/state/serial.log(纯文本、只含完整行;绝对路径以 status 的 log.path 为准,直接 Read/Grep 即是历史,无采集窗口仪式)。

前置条件

解释器需装有 pyserial:优先项目自己的 venv;否则一次性建 skill 专用 venv:

python -m venv ".dsh\skills\serial-assistant\venv"
& ".dsh\skills\serial-assistant\venv\Scripts\python.exe" -m pip install pyserial

下文 $PY = 选定解释器,$TOOL = .dsh\skills\serial-assistant\scripts\serial_tool.py。 所有命令在项目根目录下运行(cwd 决定配置源解析)。

命令(全部 JSON 输出;退出码 0=成功 1=领域错误 2=daemon 不可达/超时 3=用法)

& $PY $TOOL ports                 # 枚举 COM 口 + configured 标注 + 唯一候选 hint
& $PY $TOOL open                  # 开串口(幂等;daemon 未跑自动拉起);--port/--baud 可覆盖
& $PY $TOOL send "help"           # 发文本;send --hex 0D0A --nl 发字节并追加 CRLF
& $PY $TOOL log --tail 40         # 读日志;--grep P [-i] [--regex](先过滤后截尾)
& $PY $TOOL clear                 # 清空日志(代际 +1,会话边界)
& $PY $TOOL wait-idle             # 等 RX 静默:--settle-ms 8000 --max-ms 30000 --first-timeout-ms 12000
& $PY $TOOL status                # 全状态 JSON(daemon 版本/端口 desired+ready/计数/rx_age_ms/log.path/auto_reopen)
& $PY $TOOL start|stop|restart    # daemon 生命周期
& $PY $TOOL selftest              # 无头自检(行拆分/解码/截头/wait 判据,无需硬件)

抓开机日志铁律:open → clear → 烧录/复位 → wait-idle → 读日志校验标记 (采集必须先于烧录,否则错过开机头几行)。

配置源(串口参数单一配置源)

解析顺序:$DSH_SERIAL_CONFIG > <cwd>/.serialtest/config.json > <cwd>/.opencode/serial_config.json(旧位置)。用到的键:portName、baudRate、 httpPort(缺省 18765)。端口变化只改这个文件,改完必须 restart (daemon 启动时缓存配置)。

陷阱(实测教训)

  • 一台机只有一个 daemon(HTTP 口 SO_EXCLUSIVEADDRUSE 独占绑定)。多个项目的 serial_tool 副本协议兼容、可互连:status 的 daemon.version 辨归属 (本 skill 版 = 1.2.0-skill),log.path 辨日志落点。
  • open 报"拒绝访问/端口被占用":先 status 查是不是残留旧 daemon 占着口 → stop(或 taskkill /F /PID)→ 重新 open。
  • wait-idle 判据:no_data = 本次等待期间零字节且口开着;首字节到达前不启动静默计时; 返回的 rx_bytes 是本次增量。settle-ms 必须大于固件的自然静默间隙(常见 3~4s)。 v1.2:口未开 → 秒回 ok:false(exit 1,带 auto-reopen 现场),不再计满返回 no_data。
  • 自动重开(v1.2,issue #75):daemon 崩溃/被杀/读错丢口 → 新实例按 meta.json 现场自动重开 (串口线程内执行;退避 1.5s 倍增封顶 30s;status 的 auto_reopen={enabled,pending,attempts, last_error,next_retry_in_ms} 可观测,port.desired=意图、ready=实际)。显式 close/stop =清意图(重启后不再自动开);restart 免操心(stop 前抓现场、就绪后自动开回,结果在返回 JSON 的 reopen 键)。serial.log 缺口边界有 [SYS] … GAP-START … / GAP-END … 行:窗口内字节不可 找回;GAP-START 无 GAP-END = 采集器未恢复(查 auto_reopen/daemon.log),别当成板子静默。
  • open 不带 --dtr/--rts = 保持当前线路状态;显式给才翻转。
  • 日志 20MB 上限自动按行边界截头保尾 10MB;无换行残行 64KB 封顶。