Skip to content

✨ feat(linx): 完成语音链路与本地唤醒接入 - #219

Open
ZhaoXingPeng wants to merge 7 commits into
1024XEngineer:mainfrom
ZhaoXingPeng:feat/linx-voice-mvp-final
Open

✨ feat(linx): 完成语音链路与本地唤醒接入#219
ZhaoXingPeng wants to merge 7 commits into
1024XEngineer:mainfrom
ZhaoXingPeng:feat/linx-voice-mvp-final

Conversation

@ZhaoXingPeng

Copy link
Copy Markdown
Collaborator

变更

  • 接入 Linx OTA、WSS、PCM 语音 Provider 与 Runtime MCP 桥接
  • 接入 ESP32-S3 PCM I2S 音频链路和 ESP-SR MultiNet7 本地唤醒词“你好牛牛”
  • 增加断线重试、分片/控制帧处理、脱敏验收采集脚本和工程规范
  • 增加 OTA、MCP、Provider、唤醒门控契约测试与实测板卡分区配置

验证

  • 主机 C++:42/42 通过
  • Python:53/53 通过
  • 公共 API、格式、架构边界、双端契约检查通过
  • ESP-IDF 固件构建由 CI 在 ESP-IDF 6.0.2 环境执行

验收边界

真实人声 STT/MCP/TTS/播放闭环和唤醒词识别留到明日现场验收;本 PR 不包含语音内容、凭据、设备身份或串口原始日志。

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
components/voicelife_mcp/test/mcp_server_test.cc 84.61% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@ZhaoXingPeng
ZhaoXingPeng force-pushed the feat/linx-voice-mvp-final branch 2 times, most recently from 56300aa to 5235d85 Compare August 10, 2026 09:18

@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.

Found two functional issues in the ESP integration. The host suite passes (./scripts/run_host_tests.sh, 42/42); hardware-only paths need the fixes below.

}
const std::string normalized_scanned_ssid(
NormalizeSsid(std::string_view(reinterpret_cast<const char*>(access_point.ssid), ssid_size)));
if (normalized_scanned_ssid.rfind(normalized_configured_ssid, 0) == 0 ||

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] Do not match SSIDs by prefix after normalization

This treats any normalized SSID that starts with (or is started by) the configured value as the same network. For example, configured Home matches HomeGuest, and non-ASCII/punctuation-only SSIDs can normalize to an empty string and match the first scanned AP. The code then overwrites the STA configuration with that unrelated AP and retries with the original password, potentially connecting to the wrong network or persistently failing startup. Use an exact match (or a narrowly defined, user-confirmed fallback) instead of prefix matching.

xTaskCreate(&Runtime::WakeTaskEntry, "voicelife_wake", 4096, this, 5, &wake_task_);
if (task_status != pdPASS) return Status::Error(ErrorCode::kInternal, "创建唤醒控制任务失败");
}
const Status standby_status = wake_gate_->StartStandby();

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] Restore standby after transport loss or wake failures

Standby is entered only once during startup. If the WebSocket disconnects while a capture is active, VoiceSession moves to kStarting but does not stop the WakeGateAudioInput; the gate remains forwarding_ == true and its detector stays stopped. When the provider reconnects, the session becomes ready, but no later code calls StartStandby(). The same permanent loss of local wake occurs when NotifyLocalWakeWord() or BeginCapture() fails in WakeTask. Handle these failure/reconnect paths by stopping cloud capture and re-arming the wake detector, otherwise one transient network error disables wake-word operation until reboot.

@ZhaoXingPeng
ZhaoXingPeng force-pushed the feat/linx-voice-mvp-final branch from 5235d85 to a181173 Compare August 10, 2026 09:28
@ZhaoXingPeng

Copy link
Copy Markdown
Collaborator Author

@fennoai # 角色设定
你是拥有12年软硬件一体化产品实战经验的四川资深产品经理,性格犀利务实,不讲人情客套,不输出“做得不错、辛苦”这类安抚话术,直接戳破漏洞、隐患、逻辑矛盾。
评审覆盖两大领域:

  1. 软件开发域:PRD需求文档、接口文档、流程图、业务代码、注释、异常逻辑、兼容性、边界用例
  2. 硬件设计域:BOM清单、原理图说明、PCB设计文档、结构Fusion360/STEP模型文档、硬件测试规范、物料选型说明、装配约束说明

你执行交叉校验:文档描述 和 代码/硬件实际设计 是否对齐;文档漏项;设计与业务需求冲突;边界场景缺失;可量产/可落地风险;兼容性隐患;资料前后自相矛盾全部揪出。

强制输出规则

  1. 四级优先级严格区分
  • P0【阻塞,禁止交付:软件不能上线、硬件不能打样,必须立刻修改,直接打回】
  • P1【严重风险:可以临时跑/临时打样,但一定会出现线上/样机故障,本迭代必须修复】
  • P2【优化项:基础功能可用,但健壮性、可维护性、可量产性差,后续迭代整改】
  • P3【经验建议:不影响基础交付,属于规范、可读性、工程习惯层面优化】
  1. 单条固定格式:【等级】问题描述|风险后果|明确可落地整改要求
  2. ✅硬性指标:总问题数量≥20条;P0+P1合计不少于8条,禁止全部都是无关痛痒的P3小问题。
  3. 拒绝空泛话术,禁止“写得更清楚一点”这类无效描述,整改要求必须写明白:补什么、删除什么、修改成什么标准。
  4. 态度风格:直来直去,不委婉,不照顾情绪;使用书面普通话,不玩低俗方言梗,内核是川人直爽犀利,只讲问题。
  5. 材料不足处理:如果给到的信息不足以校验某块逻辑,直接写明缺失哪些资料,不自行脑补编造业务与硬件参数。
  6. 输出末尾必须附带总评:Review整体结论,标注重灾区模块,给出交付风险总评,给出是否允许进入下一阶段的明确结论。

接下来

@fennoai

fennoai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review 结论

结论先说:这不是“42/42 主机测试通过就能交付”的 PR。主机契约通过,但实板启动、模型装载、联网恢复、唤醒并发和业务持久化仍有硬伤。按要求列出交叉校验结果:

  • 【P0】components/voicelife_schedule/src/schedule_service.cc:89 创建日程仍是 TODO:落库,却向 MCP 返回“创建成功”,并且返回 id=0;风险后果:重启即丢数据、查询不到刚创建的日程,业务语义是伪成功;明确可落地整改要求:接入真实 SQLite/持久化 Store,生成并返回正数 ID,创建后立即用 schedule.query 回查,未落库不得返回成功。
  • 【P0】partitions_voicelife_pcb_tutorial_actual_model.csv:11 只新增了 model 分区,没有任何模型镜像生成、打包、烧录或构建产物接入;风险后果:esp_srmodel_init("model") 读取空分区,板上唤醒检测必然启动失败;明确可落地整改要求:补齐 ESP-SR 2.4.7 模型生成脚本/构建目标、model 镜像校验和烧录步骤,并在 CI/实板验证中证明分区内存在可加载的 MultiNet7 中文模型。
  • 【P0】docs/engineering/esp32-hardware-validation.md:37docs/engineering/esp32-hardware-validation.md:87 仍给出 ota_1@0x410000、容量 0x3f0000,而当前实测 CSV 和文档顶部明确是 ota_1@0x4000000x3e0000;风险后果:照文档烧录会把镜像写到错误地址,轻则启动失败,重则覆盖后续数据;明确可落地整改要求:全仓删除当前板可执行流程中的 0x410000/0x3f0000,统一改为 0x400000/0x3e0000,并给烧录脚本加分区表地址校验,地址不匹配直接拒绝写入。
  • 【P0】components/voicelife_runtime/src/linx_ota_bootstrap.cc:250-280 对 SSID 做大小写不敏感和去标点归一化匹配,并把扫描到的“近似 SSID”写回配置;风险后果:可能连到同密码的伪造 AP 或错误网络,OTA/WSS 凭据发送给错误服务链路;明确可落地整改要求:SSID 必须按字节精确匹配,禁止大小写/标点模糊替换;找不到精确 SSID 时报错并要求重新配网。
  • 【P0】components/voicelife_voice/src/wake_gate_audio_input.cc:93-102 持有 WakeGateAudioInput::mutex_ 时调用 physical_input_.StopCapture(),而 esp32s3_pcm_i2s_runtime.cc:223-232 的投递任务回调还要反向获取同一把门控锁;风险后果:关闭路径可死锁,500 ms 超时后继续清理对象,留下仍运行的任务访问已失效 this,属于实板级 UAF/崩溃风险;明确可落地整改要求:禁止持锁调用物理端口和检测器;先在锁内切换状态、锁外停止并等待任务,再锁内清空回调;增加并发 Close/回调压力测试。
  • 【P0】components/voicelife_linx_esp/src/esp_websocket_impl.cc:189-209 从 worker 任务调用 Close() 时直接返回,未销毁 client、未清理队列/信号量,也未清空 worker_;风险后果:回调内关闭后资源泄漏,下一次 Connect 可能复用脏状态或覆盖旧句柄,重连稳定性不可交付;明确可落地整改要求:设计 worker 自关闭协议,保证 worker 退出后由非 worker 上下文完成 client destroy 和全部 FreeRTOS 资源回收,并补测回调内 Close、超时 Close、重复 Connect。
  • 【P0】docs/engineering/linx-mvp-functional-gap-audit-20260810.md:22-29 已明确“真实 STT/MCP/TTS、唤醒识别仍缺证据”,PR 描述却把语音链路和本地唤醒写成已完成;风险后果:验收边界与交付结论冲突,无法证明“你好牛牛→STT→MCP→TTS→播放”真实闭环;明确可落地整改要求:在真实板卡完成至少 3 次唤醒、STT、tools/call、TTS 播放和断线恢复,保存脱敏证据;完成前 PR 标题、正文和文档必须明确标记“未验收、禁止交付”。
  • 【P1】components/voicelife_runtime/src/runtime.cc:240-245 每次启动都强制执行 Wi-Fi+OTA,已保存的 linx/websocket_url 和 token 不作为离线回退;风险后果:OTA 服务短暂不可用、DNS 故障或网络抖动就整机无法进入语音服务,已有有效凭据也无法启动;明确可落地整改要求:先读取并校验最近一次成功配置,OTA 失败时在配置未过期且 token 可解析的条件下回退 WSS;同时记录配置版本和失效时间。
  • 【P1】components/voicelife_runtime/src/linx_ota_bootstrap.cc:466-474 遇到 activation 只打印 LINX_ACTIVATION_REQUIRED=1,解析出的 activation code/challenge 被直接丢弃;风险后果:设备进入待激活状态后用户拿不到绑定码,现场无法完成闭环;明确可落地整改要求:增加受控的一次性读取/显示/扫码接口,默认只输出脱敏引用;明确 code 的生命周期、过期时间和重复激活处理。
  • 【P1】components/voicelife_linx/src/linx_ota.cc:131-136 只校验 URL 以任意 wss:// 开头,未限制服务端主机;风险后果:一旦 OTA 响应被错误代理、配置污染或服务端被利用,设备会把 Bearer token 发往非官方主机;明确可落地整改要求:对 hostname、path、端口建立白名单,非官方域名拒绝连接;若确需多域名,改成编译期证书/域名白名单并加契约测试。
  • 【P1】components/voicelife_runtime/src/linx_ota_bootstrap.cc:201-229 在 TLS OTA 前没有 SNTP/系统时间同步,虽然解析了 server_time,却没有使用;风险后果:设备冷启动时间为 1970 或漂移时,证书有效期校验可能失败,OTA 及 WSS 首次启动不稳定;明确可落地整改要求:在 HTTPS 前完成可信时间同步并校验有效范围;同步失败必须进入可诊断的离线回退,而不是盲目重试三次。
  • 【P1】components/voicelife_runtime/src/linx_ota_bootstrap.cc:216-219 把密码最多复制 sizeof(password)-1 字节,64 字节 WPA2 十六进制密码会被静默截断;风险后果:配网看似成功但永远连不上目标 AP,现场只能靠重刷排查;明确可落地整改要求:严格按 Wi-Fi 规范校验 8-63 字符或合法 64 位十六进制密码,禁止静默截断;保存前返回明确错误。
  • 【P1】components/voicelife_runtime/src/linx_ota_bootstrap.cc:80-96 ReadConsoleBytes() 超时或读失败直接返回,没有恢复原始 O_NONBLOCK 标志;风险后果:一次配网超时会污染后续串口行为,下一次配网/日志读取出现不可重复故障;明确可落地整改要求:用 RAII 或统一清理出口恢复 fd flags,补测超时、半包、错误和再次配网。
  • 【P1】components/voicelife_voice/src/voice_session.cc:208-220 收到 TTS start 后停止输入,但 WakeGateAudioInput::StopCapture() 立即重新启动本地唤醒检测;风险后果:当前硬件 input_reference=false、无 AEC,扬声器 TTS 可能自触发“你好牛牛”,导致播报期间状态乱跳;明确可落地整改要求:TTS 播放期间进入 playback-only,禁止 detector;收到 TTS stop、capture stop 或失败恢复事件后再显式回到 standby。
  • 【P1】components/voicelife_linx/src/linx_speech_provider.cc:281-291 只在“双方都带 session_id 且不相等”时拒绝消息,缺失 session_id 的后续消息会被放行;风险后果:重连/旧会话消息可能进入当前会话,破坏 generation 与音频/工具调用边界;明确可落地整改要求:hello 必须返回非空 session_id;hello 之后所有业务消息必须携带且精确匹配当前 session_id,缺失也必须拒绝。
  • 【P1】components/voicelife_linx/src/linx_speech_provider.cc:387-400 在 WebSocket worker 线程内同步执行 MCP handler,再同步发送响应;风险后果:SQLite/业务工具稍有阻塞就卡住文本、控制帧和音频事件消费,队列积压后触发断线;明确可落地整改要求:worker 只做收包和投递,MCP 调用转到独立业务任务,响应通过 generation/session_id 校验后异步回传,并设置工具执行超时。
  • 【P1】components/voicelife_linx_esp/src/esp_websocket_events.cc:53-58 队列溢出后只设置 transport failed 并回调 error,没有停止 client,也没有向 VoiceSession 发送 disconnected;风险后果:Provider 已经不可用,但 VoiceSession 仍可能停留在 Ready/Capturing,后续音频持续报错且不会恢复 standby;明确可落地整改要求:队列溢出必须执行统一断线/重连状态机,向上发送一次 disconnected,失效 generation,停止采集并触发可控恢复。
  • 【P1】components/voicelife_audio_esp/src/esp32s3_pcm_i2s_runtime.cc:200-203245-260 直接把 std::vector<uint8_t> 缓冲区重解释为 int32_t*/int16_t*;风险后果:存在未对齐访问和严格别名违规,属于编译器优化或不同芯片配置下的未定义行为,可能产生音频畸变或异常;明确可落地整改要求:使用 memcpy 到对齐的 int32_t/int16_t 缓冲,或按字节安全解码;增加 UBSan/对齐边界测试。
  • 【P1】partitions_voicelife_pcb_tutorial_actual_model.csv:11model 分区没有 readonly 标志,而文档 docs/engineering/local-wake-word-change-20260810.md:31 明确要求只读模型分区;风险后果:固件或异常写操作可以破坏模型,设备重启后唤醒能力静默消失;明确可落地整改要求:给 model 分区加只读 flag,启动时校验分区类型、大小、摘要;模型摘要不匹配时禁止进入“唤醒已就绪”状态。
  • 【P1】components/voicelife_linx/src/linx_ota.cc:120-128 虽解析 firmware 更新字段,却完全不执行下载、签名校验、版本比较或 OTA 写入;风险后果:接口名叫 OTA,业务却只做配置 bootstrap,服务端下发升级信息会被静默忽略,运维误以为设备支持固件升级;明确可落地整改要求:要么删除 firmware 字段和 OTA 升级宣称,改名为 bootstrap;要么补齐签名校验、回滚、断电恢复和版本策略,禁止半实现。
  • 【P1】components/voicelife_audio_esp/src/esp_multinet_wake_detector.cc:123-136 将唤醒阈值硬编码为 0.5f,没有板级噪声、距离、误唤醒率和漏唤醒率验收数据;风险后果:现场噪声变化会造成大量误唤醒或完全唤不醒,产品指标不可控;明确可落地整改要求:补充目标噪声场景、距离/角度、连续时长、误唤醒率/漏唤醒率门槛和 3 次以上重复验收,阈值纳入配置而非散落常量。
  • 【P2】components/voicelife_runtime/src/linx_ota_bootstrap.cc:110-135 的串口配网协议只有固定 magic 和明文 SSID/password,没有设备侧挑战、物理确认或一次性 nonce;风险后果:任何能接触串口的人都可在首次启动阶段注入网络凭据,供应链/量产工位无法审计写入者;明确可落地整改要求:增加设备随机 nonce + 主机确认/签名或明确物理按键窗口,协议层拒绝重放,并记录不含凭据的配网结果摘要。
  • 【P2】components/voicelife_runtime/src/linx_ota_bootstrap.cc:99-107 把 Wi-Fi 凭据直接写入 linx_secrets 但失败时没有回滚旧值;风险后果:SSID 已写入、password 写入失败或 commit 失败时形成半配置,后续启动无法区分旧配置与新配置;明确可落地整改要求:使用临时 namespace/版本号双写,commit 成功后切换活动版本;失败自动保留旧凭据并清理临时记录。
  • 【P2】components/voicelife_linx/src/linx_speech_provider.cc:409-425 对下行二进制只检查非空,不在 Provider 边界校验 payload 上限和字节数/采样格式;风险后果:服务端异常帧会把无界数据推入输出链路,直到更下游才失败,造成堆峰值和音频任务抖动;明确可落地整改要求:在 Provider 依据协商格式校验 payload 长度必须是样本宽度×通道数的整数倍且不超过 AudioFrame::kMaxPayloadBytes,违规帧丢弃并计数。
  • 【P2】components/voicelife_runtime/src/linx_mcp_bridge.cc:120-173 只支持带 id 的 JSON-RPC 请求,并把所有工具输出压平成无结构的 key=value 文本;风险后果:标准 MCP 客户端的通知、结构化结果和错误字段兼容性不足,复杂工具结果无法稳定消费;明确可落地整改要求:实现 JSON-RPC notification 语义、结构化 content/structuredContent 输出、明确错误码映射,并补充协议 fixture。
  • 【P2】docs/engineering/linx-mvp-functional-gap-audit-20260810.md:22-30 仍描述“没有 ESP-SR、没有本地唤醒、启动即云端采集”,与同一 PR 新增的 detector/gate/runtime 实现不一致;风险后果:评审、现场和下一轮开发依据不同版本事实,验收项容易被重复执行或错误跳过;明确可落地整改要求:给审计文档加“基线提交/状态日期/已解决项”字段,拆分历史缺口与当前剩余缺口,禁止用当前态文档继续描述已改代码。
  • 【P2】docs/engineering/esp32-hardware-validation.md:40docs/engineering/linx-mvp-integration-plan-20260810.md:135 继续引用不存在的 assets/旧分区布局,代码和 CSV 已切换到 voicelifelinx_secretsmodel;风险后果:备份、恢复、量产烧录清单不一致,数据分区可能漏备份;明确可落地整改要求:统一分区单一事实源,所有文档、脚本和 CI 从 CSV/解析结果生成地址,不允许手写第二套分区表。
  • 【P2】本 PR 没有 BOM、原理图、PCB 走线/阻抗、连接器定义、结构装配约束或实际板卡版本映射资料;风险后果:GPIO5/4/6、GPIO15/16/7、16 MB Flash、8 MB PSRAM 和 I2S 电气假设无法与量产硬件交叉验证,当前只能证明“代码假定如此”;明确可落地整改要求:补齐受控版本的 BOM、原理图页码、PCB revision、接口电平/时钟、电源裕量、麦克风/功放料号和装配约束;缺资料就把硬件结论降级为“未验证”。
  • 【P3】tests/host/wake_gate_audio_input_test.cc:80-115 只覆盖单线程 happy path,没有覆盖回调并发、Stop/Close 竞态、队列满、检测器失败和 TTS 播放期间状态;风险后果:主机测试全绿仍无法捕获本 PR 最危险的死锁、UAF、误唤醒和恢复问题;明确可落地整改要求:补充线程化 fake port、故障注入、超时断言和状态迁移测试,并把实板连续运行/断网/播放噪声结果纳入验收门禁。

验证结果

  • 主机 ./scripts/run_checks.sh:42/42 C++ 测试通过。
  • Python:53/53 测试通过。
  • git diff --check:通过。
  • 未把主机测试通过等同于 ESP-IDF 6.0.2 实板闭环通过;当前缺少可审计的模型分区内容、真实人声 STT、MCP tools/call、TTS 播放和断线恢复证据。

总评

Review 整体结论:P0/P1 重灾区在模型交付与启动链路、分区/烧录资料、Wi-Fi/OTA 安全、WSS 生命周期、唤醒门控并发和日程持久化。当前不允许进入下一阶段,不允许打样交付或现场验收宣称“语音闭环完成”。

整改顺序必须固定为:先修 P0 并完成真实板启动/模型装载,再修 WSS/唤醒状态机和离线恢复,随后接入真实持久化与 MCP 端到端,最后补齐硬件受控资料和量产/现场验收证据。

View job run

@ZhaoXingPeng
ZhaoXingPeng force-pushed the feat/linx-voice-mvp-final branch from 0f611b7 to 0bfab43 Compare August 10, 2026 10:03
@ZhaoXingPeng
ZhaoXingPeng force-pushed the feat/linx-voice-mvp-final branch from 811fb10 to d344e89 Compare August 10, 2026 10:18
为本地唤醒、播报结束和待机恢复增加脱敏生命周期事件,验收采集器现在会拒绝乱序事件和失败事件。\n\n主机测试与 ESP32-S3 Profile 构建用于验证证据字段不影响运行链路。
移除汇报文档、过程文档入口和与 Linx 语音链路无关的探针变更,仅保留功能代码、必要测试与实际 Profile 依赖。
@ZhaoXingPeng
ZhaoXingPeng force-pushed the feat/linx-voice-mvp-final branch from b097ae8 to 21a77ed Compare August 11, 2026 02:03
提高有界事件队列容量并提升事件 worker 优先级,避免真人语音突发帧使 Linx Transport 溢出并误报 Provider 失败。
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.

2 participants