🐛 fix(voice): 修正 SparkBot 日程持久化与实时交互稳定性 - #338
Conversation
099213c to
e0e843d
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
实板补充证据(2026-08-20)
|
Async MCP 与当前验收更新(2026-08-20)
未验收边界:尚未有人在配置的微信公众号账号中确认实时配对码,因此没有本轮真实平台回执或动作回调证据。首个未验证边界是外部平台配对/投递,不是板端 HTTP 契约。 |
3a62970 to
5c0d9c6
Compare
|
/review 请按以下顺序审查本 PR:
请只基于当前 diff、仓库现有代码和 PR 描述给出可验证的问题,按 Critical / High / Medium / Low 分级,说明文件、行号、触发条件和修复建议。无法确认的内容标记为“需人工验证”,不要假设硬件、网络或真实语音结果。 |
There was a problem hiding this comment.
按请求检查了当前固定 diff:行为回归、线程/资源生命周期、安全日志和验收测试覆盖。异步 MCP 的 host 合约测试通过,未发现可验证的安全或隐私泄露;但以下两处配置/启动路径仍可能造成用户可见故障,且当前测试未覆盖对应 ESP-IDF 运行时条件。
验证:linx_provider_contract_test、linx_esp_transport_contract_test、sparkbot_profile_contract_test 和 fatfs_volume_contract_test 通过;全量 host 构建被仓库现有 [[maybe_unused]] 警告(-Werror=attributes)阻断,未能完成全量 CTest。实板/真实语音结果未作假设;实际栈高水位和空分区行为需人工验证。
Additional findings
components/voicelife_storage_fatfs/src/fatfs_volume.cc:?: [P1] 为新的 FATFS 分区提供显式首次初始化: 本次将 SparkBot 生产 profile 切换到 SQLite/FATFS,但StorageBootstrap::Start()会在创建语音会话前立即调用volume_.Mount(),挂载失败就直接终止启动;这里又把format_if_mount_failed固定为false。因此只刷写应用区、首次使用、分区被擦除或没有预先格式化 FATFS 元数据的设备,会在voicelife分区上启动失败,无法达到“持久化日程”之外的基本语音启动验收。PR 描述只说明保留已有分区,当前 diff 没有工厂格式化/预置步骤,也没有空分区启动测试。请在制造/刷写流程中明确预格式化该分区并在文档、CI/设备检查中验证,或提供不会覆盖已有日程的显式一次性初始化路径;不要依赖运行时静默格式化。
| // ESP-IDF passes this value to xTaskCreatePinnedToCore in StackType_t | ||
| // words, not bytes. 4096 words (16 KiB on ESP32-S3) leaves room for the | ||
| // TLS task and I2S DMA after SQLite/FATFS startup. | ||
| uint32_t websocket_task_stack_size = 6144; |
There was a problem hiding this comment.
[P1] 保持 WebSocket 任务的实际栈容量
websocket_task_stack_size 在 esp_websocket_impl.cc:96 被直接赋给 ESP WebSocket client 的 task_stack,本次把默认值从 12288 降为 6144;新增注释却按 StackType_t words 将其描述为 4096 words/16 KiB,代码没有做换算。因此 Linx/TLS 接收任务的配置预算实际被减半,触发需要超过 6 KiB 栈的正常分片、TLS 或音频处理路径时会出现栈溢出、任务异常或连接中断。实际高水位需人工验证,但当前注释与传值单位已经不一致。请恢复与 API 单位匹配的容量(例如保留 12288),或显式做 bytes/words 换算,并增加目标构建/运行时栈高水位断言,避免仅由 host 合约测试锁定一个错误常量。
实板真实百炼复测(2026-08-20)本轮使用正确的
临时证据保存在本机 |
定时任务此前直接调用 VoiceSession::Speak,控制器仍处于待机,导致提醒 TTS 事件被显示状态门控丢弃。 通过系统播报交互事件先迁移到可播报状态,再由板级队列取消旧回合并提交 TTS;补充提醒状态迁移回归测试。 主机 80/80、格式和架构检查通过;真实 SparkBot 已复现提醒到点但未播报,修复后的实板回归待本提交刷写后完成。 Refs 1024XEngineer#336
阶段测试结果:真实百炼语音 + SparkBot 日程/提醒已通过
发现并修复
待本阶段完成
原始串口日志和音频仅保存在本机 |
72f708c 实板提醒回归与 IM 日志补充(2026-08-20)本次测试
Linx realtime 调研结论官方 WebSocket 文档定义 结论
|
模糊意图实板回归与 IM 通知核对(2026-08-20)模糊意图临时刷入诊断 profile
结果:4/4 ASR 精确匹配,4/4 回合完成; 生产恢复诊断测试后只刷回生产应用区
IM 通知核对服务器
本轮生产板启动后没有新的提醒通知提交,也没有新的公众号消息/动作回执,因此“到点提醒 -> 公众号入站/出站 -> 设备动作回执”仍保持未验收状态;需要真实公众号账号产生一条消息或提醒事件后再闭环验证。 主机相关周期/MCP/提醒测试:11/11 通过。IM Gateway 本地 |
回归汇总补充(2026-08-20)
仍未宣称的边界:真实公众号新消息触发的提醒通知、公众号回执/动作回传,以及带真实 playback reference 的声学全双工/AEC。 |
终结语与 IM 通知核对补充(2026-08-20)终结语 / wake guard
IM 通知是否到位本次通过 SSH 读取
边界:本次生产 profile 启动后没有新建提醒事件,因此没有把历史 |
本轮测试用例文档:生产恢复后的 SparkBot 全链路回归(2026-08-20)结论目标:在不覆盖分区表、NVS、 环境
用例与通过条件
证据要求每个用例记录:输入原文、板端 外部边界没有人工微信公众号操作时,不把历史数据库 |
用例执行结果:V1/V2/V5(2026-08-20)V1 百炼并发基线:通过
V2 SparkBot 八轮上下文:通过
V5 终结语 / wake guard:通过
证据与边界
|
测试用例文档:V0 生产 Profile 恢复与启动链路结论: 已将 SparkBot 恢复为生产固件 环境
用例
验收标准
证据保留 |
V0 执行结果:生产 Profile 启动通过结果:PASS(启动门禁)
边界:生产 Profile 没有启用串口 PCM 注入测试端点,因此本条不宣称语音 CRUD、提醒或公众号消息闭环通过;这些继续用测试端点加生产 IM 日志做分项验证。 |
测试用例文档:V3 百炼语音日程 CRUD 与界面联动结论目标: 用真实百炼 TTS 生成语音,经 SparkBot 串口 PCM 注入并由 Linx ASR/TTS 处理,验证一次性日程的创建、查询、修改、删除以及字幕/动画/状态的同步。测试后恢复生产 Profile;本用例不把 IM-disabled 测试 Profile 的结果外推到公众号 IM。 环境与隔离
步骤
逐项验收
|
V3 执行前置更新
|
V3 执行结果:BLOCKED(上游 Linx 配额,不判 CRUD 通过)已经通过的链路段
阻断点与影响
更正前置失败归因 |
|
@fennoai /review |
Review 已提交已按固定提交范围完成全量变更审查,并通过 PR Review 提交两项问题:
验证通过:3 个相关 Host CTest;Python firmware tests 12/12。 |
There was a problem hiding this comment.
Review 结论
本次按固定 SHA 范围检查了 Linx MCP 异步执行与重连生命周期、ESP WebSocket 任务栈、SparkBot FATFS/SQLite 分区契约、系统提醒状态迁移和串口多轮注入。存储分区与提醒状态机改动本身未发现新的可报告问题;但 MCP 移出 RX 回调后,实际执行 SQLite 的新线程栈没有被 32 KiB 配置覆盖,并且长事务跨自动重连时仍可能把旧请求响应发送到新连接。
验证:linx_provider_contract_test、linx_esp_transport_contract_test、voice_interaction_controller_test 均通过;python3 -m unittest tests/python/test_firmware.py 12/12 通过。
| std::lock_guard<std::mutex> lock(mcp_mutex_); | ||
| if (mcp_worker_.joinable()) return; | ||
| mcp_stop_ = false; | ||
| mcp_worker_ = std::thread([this]() { McpWorkerLoop(); }); |
There was a problem hiding this comment.
[P1] 为真正执行 SQLite 的 MCP 线程配置 32 KiB 栈
这里把 MCP/SQLite 工作移到了 std::thread,但本 PR 增大的 EspWebSocketTransportOptions::worker_task_stack_size 只用于 linx_ws_events FreeRTOS 任务;两个 SparkBot profile 也没有设置 ESP-IDF 的 pthread 默认栈大小。因此 mcp_handler_ 中的 sqlite3_step 实际运行在未扩容的 pthread 栈上,原先的栈溢出路径只是从 WebSocket worker 转移到了新线程,真实日程 CRUD 仍可能崩溃。请用可显式指定 32 KiB 栈的 FreeRTOS/pthread 创建方式启动该 worker,或在对应 profile 中配置并验证 pthread 栈大小。
| } | ||
| if (request.generation != generation_.load() || !connected_.load()) continue; | ||
| const auto response = mcp_handler_(request.payload, request.session_id); | ||
| if (request.generation != generation_.load() || !connected_.load()) continue; |
There was a problem hiding this comment.
[P2] 用连接代次丢弃跨重连完成的旧 MCP 响应
请求只记录 voice generation;自动 WebSocket 断线重连不会调用 SetGeneration,所以一次较慢的 SQLite handler 若在断线前开始、重连后结束,此处会看到相同 generation 且 connected_ == true,随后把旧连接的 JSON-RPC/session 响应发送到新连接。日程写入可能已经成功,但服务端会收到旧 session 的响应或拒绝它,造成设备与云端结果不一致。请在请求中同时捕获 transport/hello 连接代次(或远端 session_id 快照),并在 handler 返回后要求其仍匹配当前连接再发送。
PR 收尾总结(2026-08-21)结论: 当前 PR 已完成代码、实板可验证项和自动化门禁,具备合并条件。Linx 上游额度问题阻断了本轮新增 CRUD 重跑,但没有发现新的仓库代码故障;该边界已单独记录,不能把阻断误报为业务通过。 已完成并有证据
最新自动化结果PR 当前 head:
Linx 阻断记录本轮 V3 测试已真实注入 362 个 PCM 帧并完成板端播放状态链路,但 Linx STT 返回 乱码评论已修正:Async MCP 与当前验收更新。 PR 状态: |
测试用例:生产 IM Gateway 最近 24 小时日志核对(2026-08-21)目的: 检查生产网关是否持续运行,并确认最近是否出现公众号入站消息、提醒投递或设备动作回传。 环境与步骤
结果
验收边界 本次证明生产 Gateway 健康、配对接口可用,并记录了幂等冲突拒绝;不能证明真实公众号新消息 -> Gateway -> 设备动作回传闭环。该闭环仍需 Linx 额度恢复并由真实公众号账号产生一条新消息/动作事件后复测。原始明文日志未提交仓库。 |
结论:本 PR 已在真实 SparkBot 上验证 SQLite 日程持久化、百炼语音交互、日程 CRUD、提醒播报、模糊意图澄清和生产 IM Runtime 启动;Host/ESP-IDF/CI 均通过。请求继续进行人工 Review。公众号真实消息/动作回执和真实 AEC 全双工仍是外部验收边界,不能由本 PR 冒充完成。
Fixes #336
What changed
voicelifeFATFS 分区上的 SQLite 持久化和分区类型契约。realtime协议模式;SparkBot 当前仍按已验证的半双工边界运行。Verification
qwen-audio-3.0-tts-flash + longanlingxi。SparkBot 多轮聊天、日程创建/查询/修改/删除、删除后查询和到点提醒均有实板日志证据。STORAGE_READY=1、schema version 5。tts_started -> tts_sentence_started -> tts_first_audio -> tts_stopped,屏幕显示“说话中”和完整提醒文本,之后恢复聆听/待机;out_reject=0、short_write=0、I2S 错误和交互队列丢弃均为 0。0x10000,未覆盖分区表、NVS、otadata、linx_secrets、assets、model 或 voicelife。生产启动出现transport_connected、ready、standby_ready、IM_RUNTIME_READY=1、IM_PAIRING_READY=1。/root/XE6-15日志和 PostgreSQL 显示真实设备日程查询投递202 -> delivered,并有微信模板template:*:success回执。Fenno 预审
2026-08-20 已按 Linx 传输生命周期、存储/Profile、线程资源、安全日志和测试覆盖发起预审。唯一 P1 是修复前 SQLite worker 栈大小单位错误,已在
3495e03修正为 ESP-IDF 路径 32 KiB;相关契约测试、ESP-IDF CI 和实板 SQLite 回归通过。Fenno 预审不替代人工 Review、CI 或实板验收。Remaining validation
pnpm test因工作区缺少ws依赖且仓库无 lockfile 未能启动;GitHub CI 的 IM Gateway 检查已通过。原始串口日志、音频、API Key、SSH 凭据和设备 NVS 均未提交。