dfd1528409
按 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 见工单汇报。
4.6 KiB
4.6 KiB
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 封顶。