Skip to content

✅ test(voice): 固化百炼 SparkBot 实板测试模板 - #349

Open
ZhaoXingPeng wants to merge 4 commits into
mainfrom
fix/sparkbot-wake-recovery-20260822
Open

✅ test(voice): 固化百炼 SparkBot 实板测试模板#349
ZhaoXingPeng wants to merge 4 commits into
mainfrom
fix/sparkbot-wake-recovery-20260822

Conversation

@ZhaoXingPeng

Copy link
Copy Markdown
Collaborator

结论:本 PR 将百炼 Key 读取、真实 TTS 预检、SparkBot 唤醒注入和多轮串口证据固定为一个可重复入口,后续测试统一从该入口开始。

请审核测试模板的配置读取和失败门禁;合并后使用 scripts/run_bailian_sparkbot_test.sh preflightwakemultiturn,不要直接把 key.txt 整段赋给 DASHSCOPE_API_KEY。

Fixes #348

Lifecycle: Ready for review; merge after required checks and human approval.

Scope

  • 新增 scripts/run_bailian_sparkbot_test.sh,固定 qwen-audio-3.0-tts-flash + longanlingxi、SparkBot 串口和日志目录。
  • Key 只取配置文件第一条完整 sk-... 行,支持行首尾空白和 CR;JSON、串口日志和元数据不写入密钥。
  • 新增百炼 TTS 唤醒注入夹具,统一等待 standby_ready、唤醒、TTS 和采集 marker。
  • 新增固定测试模板文档,包含 preflight、wake、multiturn、重启/重连、日程 CRUD 记录表和证据边界。

Non-goals

  • 不修改 Linx WebSocket 服务端,也不把 Linx RST 伪装成语音链路通过。
  • 不修改当前工作树中尚未提交的 Runtime/Linx 修复。

Verification

  • bash -n scripts/run_bailian_sparkbot_test.sh
  • Python 编译检查通过。
  • Host CTest 83/83 通过。
  • 百炼 preflight 实测通过:模型 qwen-audio-3.0-tts-flash、音色 longanlingxi、音频 58187 bytes、failed=0
  • SparkBot 实板 wake 实测通过:standby_ready -> wake_detected -> local_wake_ack_requested -> tts_started -> tts_first_audio -> capture_started;屏幕同步经过“收到/说话中/准备中/聆听中”。
  • 短 multiturn 实测生成完整 JSON 和串口日志;板端 ASR/TTS 已到达,但 Linx 在播报中 Connection reset by peer,结果被正确判定为失败。
  • 负例 Key 文件被拒绝,证据目录未发现 sk-AuthorizationBearer

Risks and closeout

  • 当前 Linx RST 仍是独立链路缺陷;本 PR 只提供稳定、可复核的证据入口。
  • PR 合并后,后续固件回归必须保留 /tmp/voicelife-bailian-tests 下的 JSON、.meta.txt 和串口 .log,公开评论只粘贴脱敏汇总。

AI assistance

  • Codex 协助整理脚本、文档和测试命令;作者已在真实 SparkBot、百炼服务和主机 83 项测试上核对结果。

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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=1 is 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 default wake command (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.

Comment on lines +111 to +112
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ZhaoXingPeng

Copy link
Copy Markdown
Collaborator Author

百炼注入与唤醒重跑验证(2026-08-22)

本阶段提交:7ae4f5a 🐛 fix(test): 提升百炼 SparkBot 唤醒注入重跑稳定性

注入方式核验

  • 百炼预检:qwen-audio-3.0-tts-flash + longanlingxi,真实 SDK 调用成功,successful=1failed=0、音频 58187 bytes。
  • 独立格式核验:SDK 返回单声道 MP3;ffmpeg 强制转换为 16 kHz / mono / S16LE,2.586 秒音频得到 130 个严格 640-byte PCM 帧。
  • 串口协议核验:每帧为 VLVT-v1kind=PCM、little-endian length=640;固件再次校验格式后才进入现有有界输入队列。注入路径不直接伪造或调用 Linx WebSocket 帧。
  • 主机检查:四项 C++ 契约测试、Python 编译、Shell 语法、git diff --check 均通过。

SparkBot 实板结果

命令:scripts/run_bailian_sparkbot_test.sh wake --timeout 45

  • 日志:/tmp/voicelife-bailian-tests/wake-20260822-163708.log
  • SERIAL_VOICE_TEST_READY=1 后只出现一次待机拒绝 code=4,等待 standby_ready 后一次握手成功 SERIAL_VOICE_WAKE_BEGIN=ok;不再在启动期堆积重复握手帧。
  • test_in_frames=58test_in_bytes=37120SERIAL_VOICE_PCM=reject 无,in_drop=0out_reject=0short_write=0,I2S 错误为 0。
  • WAKE_DETECTED word=你好牛牛tts_startedtts_first_audiocapture_started 全部出现;日志中没有 Connection reset by peerprovider_error

结论与边界

百炼注入格式和注入路径已证明不会制造 Linx 非法音频帧;本阶段此前多轮测试中的 Linx RST 仍需按 WebSocket 生命周期/TX 时序单独处理,不能把本次唤醒通过扩大为多轮链路通过。

@ZhaoXingPeng

Copy link
Copy Markdown
Collaborator Author

阶段进度更新(2026-08-22,先暂停 CI,继续实板链路)

本阶段已提交并推送:156d4e7 🐛 fix(linx): 完善 SparkBot 传输边界与重连诊断

已完成

  • 百炼固定入口预检通过:qwen-audio-3.0-tts-flash + longanlingxi,真实 TTS 成功,successful=1failed=0、音频 58187 bytes。
  • SparkBot 实板唤醒通过:standby_ready -> WAKE_DETECTED(你好牛牛) -> local_wake_ack_requested -> tts_started -> tts_first_audio -> capture_started
  • 唤醒注入严格转换为 16 kHz、单声道、S16LE、20 ms、640-byte PCM;SERIAL_VOICE_PCM=rejectin_dropout_rejectshort_write、I2S 错误均为 0。
  • 固件补充了 Linx hello 参数、TX 文本/音频顺序、MCP method/id、WebSocket error 分类和重连日志;listen.start/stop 与 PCM 共用有序媒体队列,断线后清理旧代际队列。
  • 主机验证通过:4 项 C++ 契约测试、Python 编译、Shell 语法、git diff --check

当前实板普通对话结果

测试日志:/tmp/voicelife-bailian-tests/multiturn-20260822-165203.log
结果:/tmp/voicelife-bailian-tests/multiturn-20260822-165203.json

  • ASR 精确匹配输入:请简单介绍一下你自己。
  • 已收到两段 TTS 文本并开始播放,屏幕状态依次覆盖“聆听中/处理中/说话中”,字幕滚动指标正常。
  • 播放第二段 TTS 约 2 秒时,Linx 对端 TCP RST:esp_tls_conn_read ... Connection reset by peer;随后设备自动重连并再次完成 hello。
  • 该轮被测试门禁正确判定为失败(缺少 tts_stopped),没有把“播出部分内容”误报为完整对话通过。
  • 这次失败不符合百炼 PCM 注入错误特征,当前证据仍指向 Linx WebSocket 会话生命周期/服务端策略或协议时序,需要独立对照实验确认。

下一步对照实验

将使用官方 78/xiaozhi-esp32 的 SparkBot 实现,按 Linx 的 xiaozhi-firmware/xiaozhi-hardware 文档配置同一网络和凭据,再用同一套百炼 TTS 自动注入与串口证据检查:

  1. 官方固件唤醒与一句普通对话;
  2. 官方固件多段 TTS/多轮上下文;
  3. 对比 WebSocket hello、listen.start/PCM/listen.stop、RST 时间点;
  4. 若官方固件同样 RST,优先归因 Linx 服务/服务端会话策略;若官方固件稳定,再回到 VoiceLife TX 队列和生命周期实现;
  5. 对照结果、完整日志路径和结论继续追加到本 PR。

原始串口日志只保存在本机 /tmp/voicelife-bailian-tests,不提交密钥、Authorization 或私密音频。

@ZhaoXingPeng

Copy link
Copy Markdown
Collaborator Author

官方 SparkBot 对照实验阶段进度(2026-08-22)

本阶段目标是用 78/xiaozhi-esp32 官方 SparkBot 固件连接同一 Linx OTA/WebSocket 服务,再用固定百炼 TTS 注入模板对比 VoiceLife 的链路行为。

已完成

  • 克隆官方仓库并固定 commit:bb9122ab08c3083eeb4f67b3974b7afe771723b8
  • 确认官方 SparkBot 硬件配置与实板一致:ESP32-S3、ES8311、16 kHz 单声道音频、同一 GPIO 配置。
  • 临时构建配置使用 Linx OTA:https://xrobo.qiniuapi.com/v1/ota/;官方固件构建成功,生成 xiaozhi.bin(2.8 MiB)和资源镜像。
  • 刷写前完整读取实板 16 MiB Flash,备份:/tmp/voicelife-sparkbot-backup-20260822/flash-16mb.bin
  • 跳过 0x9000 NVS,仅刷入官方启动程序、分区表、OTA 数据、应用和资源;官方固件已在 SparkBot 实板启动。
  • 官方启动日志:/tmp/voicelife-official-boot-20260822.log。硬件初始化、LCD、ES8311、I2S 和本地唤醒模型均正常。

当前阻塞与证据

官方固件启动后明确打印 SsidManager: NVS namespace wifi doesn't exist,随后进入 Xiaozhi-81B1 配网模式。原因是 VoiceLife 使用加密的独立 linx_secrets 分区保存 Wi-Fi/Linx 凭据,而官方固件使用未加密默认 NVS;保留原 NVS 不能跨固件复用凭据,不是 Linx WebSocket 失败。

因此目前还不能把“官方固件未连接 Linx”误判为服务端问题。下一步需要在官方配网页面录入同一 Wi-Fi 和 Linx 凭据,然后用相同百炼 preflight/wake/multiturn 模板完成正式对照。

风险控制

  • 原 VoiceLife 整片 Flash 已备份,可恢复;官方临时固件和构建目录均位于 /tmp,没有混入仓库。
  • 未提交 Wi-Fi 密码、Linx token、Authorization、原始语音或完整敏感日志。
  • 本阶段没有新增仓库代码或 commit;现有分支仍保持干净,CI 按当前要求暂缓。

Refs #348

@ZhaoXingPeng

Copy link
Copy Markdown
Collaborator Author

实板对照测试进度(2026-08-22)

对照基线

  • 固件:78/xiaozhi-esp32,commit bb9122ab08c3083eeb4f67b3974b7afe771723b8
  • 板卡:SparkBot(esp-sparkbot);当前唤醒词为“你好小智”。
  • Linx:已成功连接 xrobo.qiniuapi.com:443,OTA 检查通过,设备进入 activating -> idle
  • Wi-Fi:已连接 zxp,地址 10.42.159.73
  • 串口明文日志:持续记录至 /tmp/voicelife-human-serial-monitor-20260822.log

与 VoiceLife 症状的对照

VoiceLife 之前表现为首轮可以唤醒并开始回复,后续 TTS 阶段出现 esp_tls_conn_read ... Connection reset by peer。官方干净固件目前已稳定待机,尚未出现连接重置、看门狗或异常重启;真人语音对照仍待执行。

真人测试用例(严格顺序)

  1. 你好小智
  2. 你能听见我吗?
  3. 我叫新鹏,请记住这个名字。
  4. 我刚才叫什么名字?
  5. 请用两句话介绍一下你能做什么。
  6. 在第 5 步播报期间说:你好小智,停一下。
  7. 回到聆听后说:今天天气怎么样?
  8. 再见。

判定标准

检查唤醒事件、WebSocket 建连、ASR/TTS 明文、打断后状态回收;全程不应出现 Connection reset by peeresp_tls_conn_readGuru Meditationtask_wdt 或异常复位。

本阶段只做官方固件对照测试,没有新增 VoiceLife 源码,因此没有额外 commit;测试完成后会在此评论补充逐步结果。

@ZhaoXingPeng

Copy link
Copy Markdown
Collaborator Author

真人实板对照结果(2026-08-22)

已通过

  • 唤醒词“你好小智”识别成功,Linx WebSocket 握手成功并建立 Session。
  • 普通对话、连续上下文记忆、长句 TTS、结束语均完成;设备在每轮正确回到 listening
  • ASR/TTS 明文持续可见,Wi-Fi 与连接稳定。
  • 全程未出现 Connection reset by peeresp_tls_conn_readGuru Meditationtask_wdt 或异常复位。

未完成项

  • 播报中打断尚未形成有效证据:两次“你好小智,停一下”均发生在设备已回到 listening 后;其中一次完整 ASR 已识别,但不是 barge-in。
  • 需要在设备仍处于 speaking 时立即插话,确认是否提前停止播放并回到 listening

日程边界

官方 78/xiaozhi-esp32 对照固件注册的是底盘、屏幕、相机等 MCP 工具,没有 VoiceLife 日程持久化工具;本轮“新增日程”仅验证了服务端自然语言引导,不能替代 VoiceLife 日程增删改查与持久化验收。

串口完整记录:/tmp/voicelife-human-serial-monitor-20260822.log。本轮没有新增 VoiceLife 源码或 commit。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🧪 test(voice): 固化百炼 SparkBot 实板测试模板

1 participant