🐛 fix(voice): 增强 SparkBot 唤醒恢复与 Linx 诊断 - #349
Conversation
There was a problem hiding this comment.
Reviewed the new Bailian wrapper, wake-injection harness, and the documented evidence gates. The key parsing and wrapper syntax are sound, but the wake workflow has two correctness gaps that can either report a false pass or make the documented rerun/reconnect flow fail.
Validation performed: bash -n scripts/run_bailian_sparkbot_test.sh, Python AST parsing, invalid-key exit contract, and git diff --check.
Additional findings
scripts/voice_linx_wake_injection_test.py:?: [P1] Do not wait for a one-shot boot marker on every wake run:SERIAL_VOICE_TEST_READY=1is emitted once when the firmware serial task starts, before this process opens the port; it is not re-emitted on each connection or while the board is already running. With the defaultwakecommand (which does not pass--reset-before-run), a second invocation or a reconnect therefore waits for a marker that has already been consumed and times out before injecting any audio. The documented rerun/reconnect workflow needs a readiness check that works after attach, or the wrapper must make/reset-and-wait an explicit prerequisite rather than relying on this one-shot log line.
| cursor = wait_for(log, "SERIAL_VOICE_EVIDENCE event=tts_started ", cursor, args.timeout) | ||
| cursor = wait_for(log, "SERIAL_VOICE_EVIDENCE event=capture_started ", cursor, args.timeout) |
There was a problem hiding this comment.
[P1] Require first TTS audio before declaring wake success
This success gate skips tts_first_audio even though the template requires it. The runtime intentionally opens capture after ACK_FIRST_AUDIO_TIMEOUT when the acknowledgment has started but no first PCM arrived, and that path still emits capture_started; therefore a provider/audio-output failure can satisfy the two waits below and print wake_injection_success. Wait for SERIAL_VOICE_EVIDENCE event=tts_first_audio (and reject provider/abort errors) before accepting capture_started so this harness cannot report a false wake pass.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
百炼注入与唤醒重跑验证(2026-08-22)本阶段提交: 注入方式核验
SparkBot 实板结果命令:
结论与边界百炼注入格式和注入路径已证明不会制造 Linx 非法音频帧;本阶段此前多轮测试中的 Linx RST 仍需按 WebSocket 生命周期/TX 时序单独处理,不能把本次唤醒通过扩大为多轮链路通过。 |
阶段进度更新(2026-08-22,先暂停 CI,继续实板链路)本阶段已提交并推送: 已完成
当前实板普通对话结果测试日志:
下一步对照实验将使用官方
原始串口日志只保存在本机 |
官方 SparkBot 对照实验阶段进度(2026-08-22)本阶段目标是用 已完成
当前阻塞与证据官方固件启动后明确打印 因此目前还不能把“官方固件未连接 Linx”误判为服务端问题。下一步需要在官方配网页面录入同一 Wi-Fi 和 Linx 凭据,然后用相同百炼 preflight/wake/multiturn 模板完成正式对照。 风险控制
Refs #348 |
实板对照测试进度(2026-08-22)对照基线
与 VoiceLife 症状的对照VoiceLife 之前表现为首轮可以唤醒并开始回复,后续 TTS 阶段出现 真人测试用例(严格顺序)
判定标准检查唤醒事件、WebSocket 建连、ASR/TTS 明文、打断后状态回收;全程不应出现 本阶段只做官方固件对照测试,没有新增 VoiceLife 源码,因此没有额外 commit;测试完成后会在此评论补充逐步结果。 |
真人实板对照结果(2026-08-22)已通过
未完成项
日程边界官方 串口完整记录: |
阶段性进度:SparkBot 唤醒恢复与 Linx 传输诊断本阶段已提交并推送:
这次不是只加日志,核心是把“本地唤醒、Linx 会话、物理麦克风、TTS 播放、重连恢复”之间的边界重新固定下来,并把失败时能回答“哪一帧、哪一代、哪一个协议阶段出错”的证据补齐。 1. 语音会话和唤醒顺序修改了
2. Linx WebSocket 和 PCM 传输修改了 Linx Provider、ESP WebSocket Transport、JSON codec 和运行时装配:
3. 百炼和实板验证入口更新了百炼串口注入脚本、唤醒脚本、多轮脚本和固定测试模板:
4. 已取得的结果实板日志已经能稳定留下完整的唤醒阶段证据。例如:
其中可以看到:
唤醒确认 TTS 的首阶段延迟也有记录,示例约为 411ms/431ms;屏幕状态跟随这些事件切换,没有再把“收到”或“聆听中”提前显示。 主机和提交前检查结果:
5. 目前仍未闭合的问题这次没有把失败伪装成成功,剩余问题仍明确保留:
本评论记录的是当前阶段真实结果:本地语音状态和重连诊断已明显可观测、主机门禁已通过,但完整长对话和 IM 闭环仍需要后续实板验证。 |
CI 补充:修复严格编译器下的测试 fixture 初始化推送后的第一轮 CI 中,主机测试、Codecov 和 CodeQL C++ 同时在同一处失败:
原因是 已在新提交中修复:
该提交已推送到本 PR,等待新一轮 CI 结果。这个修复只补齐测试数据初始化,不改变固件运行逻辑。 |
同步主分支与 PR #342 展示代码已将当前开发分支同步到最新
本次实际带入的展示内容:
验证结果:
刷写注意:#342 不只是应用代码变化,还更新了 |
实板刷写验证:已使用最新 main + PR #342 展示资源在同步最新主分支后,已使用 ESP-IDF 6.0.2 重新构建并刷写 SparkBot:
刷写范围:
明确没有写入:
esptool 对三个镜像均报告 复位后的串口验证已经看到: 新资源实际为 68 帧循环,说明 #342 的新眼睛 GIF 已经被 assets 分区加载,不再是旧资源。当前工作树保持干净。 |
重要阶段结论:SparkBot 无 AEC,生产监听模式固定为
|
| # | 注入文本 | ASR | 设备回应 | 错误 |
|---|---|---|---|---|
| 1 | 你是谁? |
你是谁? |
😲哇! | 我是牛牛,是你身边的小伙伴! | 专门帮你记事情、安排日程,还会陪你聊天哦! | 你叫什么名字呀? |
无 |
| 2 | 你叫什么? |
你叫什么? |
😶我叫牛牛呀! | 就是你的小帮手,帮你记日程、陪你聊天的小伙伴! | 你刚才说你的名字是新鹏对吗? |
无 |
| 3 | 你能做什么? |
你能做什么? |
😶我可以帮你做三件事哦: | 1. 记事情——比如你说“明天下午三点有画画课”,我就能帮你记下来,还会提醒你。 | 2. 查日程——想知道今天或明天有什么安排,问我就可以。 | 3. 改或删日程——比如想取消某节课、改时间,我都能帮你处理。 | 不过我不会放音乐,也不会自己编故事,只做跟日程有关的事情! | 你现在有什么需要我帮忙的吗? |
无 |
| 4 | 你会记住前面的话吗? |
你会记住前面的话吗? |
😶我会记住一些和你有关系的事情, | 比如你的名字、你告诉我的日程安排。 | 但是其他不重要的信息,我聊完就会忘记啦,这样每次跟你聊天都更专注! | 你刚才说你是新鹏,这个名字我记住啦~ |
无 |
| 5 | 我喜欢散步。 |
我喜欢散步。 |
😌散步真棒! | 晚上吹吹风、走走逛逛,特别放松。 | 你平时喜欢在哪里散步呀? |
无 |
| 6 | 我刚才喜欢什么? |
我刚才喜欢什么? |
😶你刚才说喜欢晚上散步, | 我记得呢! | 是喜欢在小区里走走,还是去公园呀? |
无 |
| 7 | 请说说春天。 |
请说说春天 |
😌春天呀, | 天气暖暖的,花儿都开了,最适合出去散步啦! | 新鹏,春天的时候你是不是更喜欢在公园里散步呀? |
无 |
| 8 | 春天有什么特点? |
春天有什么特点? |
😶春天呀, | 天气变暖和了,小草变绿了,花儿也开了,还会有小蝴蝶飞来飞去。 | 不过这些我都是听说的,我平时主要帮你记日程,春天的事情我知道得不多哦~ |
无 |
| 9 | 我们聊了几个话题? |
我们聊了几个话题。 |
😶我们聊了四个话题哦: | 1. 我是谁 | 2. 我叫什么名字 | 3. 我能做什么 | 4. 你喜欢散步和春天 |
无 |
| 10 | 请总结一下。 |
请总结一下。 |
😌好呀! | 我帮你总结一下: | 你叫新鹏,喜欢晚上散步,还跟我聊了春天的特点。 | 我们聊了四个话题,都很开心! | 现在有什么需要我帮忙的吗? | 比如记个日程,或者查一下明天的安排? |
无 |
| 11 | 再短一点。 |
再短一点。 |
😶你是新鹏, | 喜欢晚上散步,我是帮你记日程的牛牛。 |
无 |
| 12 | 请用一句话结束这次聊天。 |
请用一句话结束这次聊天。 |
😶好的新鹏, | 今天和你聊天很开心,下次想记日程或聊天随时找我哦! |
无 |
串口和屏幕验收指标
- 每轮均观察到
listen.start mode=auto,并完成准备中 -> 聆听中 -> 处理中 -> 说话中 -> 下一轮聆听中。 in_drop=0、out_reject=0、short_write=0、in_i2s_err=0、out_i2s_err=0。SERIAL_VOICE_PCM=reject:0;INTERACTION_REJECTED:0;交互队列control_dropped/best_effort_dropped/board_dropped:0。Connection reset by peer:0;LINX_WS_ERROR:0;provider_error:0;transport_disconnected:0。- 音频统计:输入
test_in_frames=922,设备下行out_frames=7232,in_frames=9737,out_q_hi=36,无丢失或短写。 - 屏幕渲染快照 111 条、含正文 68 条;长文本观察到
overflow_width横向滚动,准备中/聆听中/处理中/说话中与语音事件同步。 - 最低空闲堆:
min_heap=4573856;未观察到异常复位、Guru Meditation、看门狗或 TLS/WSS 错误。
回归门禁
./scripts/run_host_tests.sh:83/83 通过。git diff --check:通过。- 实板补跑使用的日志和 JSON 均保留在上述临时目录,未提交 API Key、Authorization、Wi-Fi 密码或原始音频。
这条评论记录的是当前阶段的真实验收结果。后续仍需单独完成唤醒、日程 CRUD/持久化、提醒和 IM 闭环,但无 AEC SparkBot 的语音长对话模式已经有实板 12 轮通过证据,不应再被随意改回 realtime。
V0-V3 阶段索引更正(2026-08-23)上一条 V0-V3 汇总是过程草稿,包含尚未闭合的门禁,不再作为验收依据。最终通过的详细记录已拆成独立阶段评论: 后续阶段只引用对应的成功实验评论,不复用本条旧汇总。 |
旧计数勘误作废本条计数勘误属于旧过程草稿,已由 V1/V2 的最终实板成功记录取代。请以后续独立阶段评论为准:
|
串口测试任务在有限时间内等待 PCM 租约归还,避免 TLS 短暂背压制造输入丢帧;MCP tools/list 的工具项补齐 Linx 文档要求的 type=0。 Host 格式检查、mcp_server_test、mcp_json_writer_coverage_test、linx_mcp_bridge_test 已通过。 Refs #349
V1 实板唤醒验收(2026-08-23,详细记录更正)结论:当前固件在真实 SparkBot 上完成“你好牛牛”唤醒、确认音和自动监听闭环。本轮只验证唤醒阶段,没有发送普通聊天问题;因此“用户说了什么”和“设备回答什么”分别只有唤醒词与确认音,不能把这轮误写成普通对话验收。日志中的 固件、环境和修复
逐步输入、识别、播报和屏幕证据
这轮没有发生的事情
错误门禁
修复路径修复前,唤醒确认音、物理麦克风和旧 ASR 事件可能交错,表现为确认音后状态提前变化或测试误报。对应修复如下:
本轮日志中的协议顺序、字幕、GIF/状态和音频事件均按上述顺序出现,证明修复后的唤醒闭环没有提前切屏或把确认音误当用户输入。 验收状态:PASS。 |
V2 实板儿童视角多轮对话验收(2026-08-23)结论:修复串口背压和回合结束门禁后,SparkBot 在同一条 Linx WebSocket 会话内完成 8 轮儿童视角对话。8/8 回合完成、8/8 ASR 精确匹配、每句设备 TTS 原文均有串口证据,未发生 PCM reject、RST、provider、I2S 或交互队列错误。 固件、环境和修复
逐轮完整记录
上表不是模型摘要,而是串口明文逐句记录。结构化结果中的每轮边界为:R1 每轮状态与屏幕证据每轮均出现 错误门禁和 Linx 证据
修复路径旧 V2 的 67 条 验收状态:PASS。 |
v3 实板多轮语音阶段记录(2026-08-23)结论:在 SparkBot 实板、 固定环境
逐轮记录
第 3、4、7、8、12 轮仅有中文标点差异,归一化后与输入一致;没有语义丢失或端点截断。 每轮链路与屏幕证据12 轮均观察到:
音频与异常统计最终统计: 修复内容
仍未验收项本阶段只证明语音/屏幕/音频长对话链路。该日志没有 主机回归: |
v4 实板唤醒阶段记录(2026-08-23)结论:在 v3 多轮结束后重新打开串口,SparkBot 仍能稳定识别唤醒词“你好牛牛”,完成“收到!”确认音,并回到 环境与证据
实验步骤与结果
关键日志证据: 异常统计本阶段补充确认:唤醒确认音不是自我介绍,播报内容严格为“收到!”。初次 |
v5 服务器 IM 运行面核对(2026-08-23)结论:SSH 核对 服务器事实
日志证据网关历史路由计数: 已看到 对当前实验的影响
|
MCP 协议对照与回退结果(74c2a25)结论:PR338 与当前分支的 MCP 调用分发链没有结构性差异;本次确认的回归点是后续提交加入的 与 PR338 基线的逐项对照
本次修复
验证
本阶段没有修改 Wi-Fi、NVS、SQLite 分区,也没有带入工作树中的音频/日程/语音实验改动。 |
阶段记录:MCP 内部状态探测与屏幕终态修正提交: 已完成
已通过验证
实板日志: 当前边界本阶段没有把最后一次百炼音量语音夹具计为通过:串口夹具打开时将板子带入 USB 下载模式,已终止夹具并用 |
MCP 工具广告与 Linx 选择层验证(重要阶段记录)固件与证据绑定
设备侧实际广告内容设备收到 Linx 的一次 因此本次固件在线路上确实发送了包含 百炼真实语音单轮测试输入配置:
结论本阶段进一步排除了设备侧“工具没有注册/没有序列化/发送不完整”的可能性。调用链实际走到: 失败仍发生在 Linx 服务端工具目录重建、模型上下文工具暴露或模型下一步工具选择阶段;本地音量 handler 没有进入执行路径。 自动验证以下主机测试通过:
|
PR319 实板 MCP 验证(2026-08-24)结论:PR319 固件可以完成百炼语音交互,但本次没有完成任何业务 MCP 固件与环境
实验输入通过仓库固定脚本和 PR319 固件的
日志: 串口证据
交互结果
额外协议观察PR319 的串口协议只接受 最终判断PR319 实板复现了“工具已广告但没有业务 |
结论:本 PR 在固定百炼测试入口的基础上,提交 SparkBot 语音唤醒恢复、Linx 传输边界和重连诊断修复。当前主机侧和提交前门禁已通过;真实 SparkBot 的短链路证据已固定,但 Linx 在长播报期间的 RST 仍未闭合,因此本 PR 不宣称完整多轮验收通过。
Fixes #348
Lifecycle: Ready for review; merge after required checks and human approval.
Scope
listen.detect,再发送listen.start,问候 TTS 结束或有界超时后才打开物理麦克风。pthread私有依赖与实际 CMake 配置一致。/tmp/voicelife-bailian-tests。Verification
./scripts/check_format.sh:45 个文件通过。./scripts/run_checks.sh:公共 API、主机测试、架构边界、双端契约、Profile 校验通过;Python 188 项通过(跳过 1 项)。./scripts/run_host_tests.sh:CTest 83/83 通过。qwen-audio-3.0-tts-flash+longanlingxi,生成音频成功,未将 Key 写入证据。standby_ready -> wake_detected -> local_wake_ack_requested -> tts_started -> tts_first_audio -> capture_started,屏幕状态与语音阶段同步。sk-、Authorization或Bearer。Known limits
Connection reset by peer;这是当前剩余阻断项,需后续在实板继续定位,不能以本 PR 的 Host 通过替代。degraded,公众号通知闭环未在本 PR 中宣称完成。AI assistance
Codex 协助整理实现、测试脚本和证据记录;作者已核对代码差异、主机测试和真实 SparkBot/百炼日志。