Skip to content

✨ feat(im): 完成设备端微信绑定呈现闭环 - #262

Open
JunLang-7 wants to merge 3 commits into
1024XEngineer:mainfrom
JunLang-7:dev/235-binding-device-experience
Open

✨ feat(im): 完成设备端微信绑定呈现闭环#262
JunLang-7 wants to merge 3 commits into
1024XEngineer:mainfrom
JunLang-7:dev/235-binding-device-experience

Conversation

@JunLang-7

@JunLang-7 JunLang-7 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

结论

补齐 #257 未完成的 #235 设备侧闭环:绑定码直接呈现到 OLED 并由系统 TTS 播报,轮询终态会给出确定的成功或重新绑定提示。

Refs #235

变更

  • 将 MCP 创建和后台轮询产生的 BindingResult 投递到 Runtime 交互事件循环,避免后台任务直接访问显示或语音硬件。
  • pending 显示 绑定 <六码> 与有效期并只播报一次;already_active 恢复显示但不重复播报;confirmedexpiredcancelledtimed_out 产生固定终态 OLED/TTS 提示。
  • 普通语音回合覆盖绑定码后,待机时恢复仍有效的绑定码;终态到达普通对话期间则延后至待机播报。
  • 为绑定结果增加单调 generation。origin/凭据重绑后清理本地呈现,丢弃旧会话的迟到终态。
  • 重启采用第二种策略:不恢复本地待绑定会话,用户下一次明确语音命令才创建新会话;Gateway 创建时负责取消该设备旧的 pending 会话。

TDD 与验证

  • RED:先补充 binding presentation、重绑代次和 MCP 回调结果的测试,目标行为在实现前失败。
  • GREEN:新增纯呈现映射和 Runtime 事件投递,覆盖 pending、重复命令、三类终态、中间轮询态与陈旧结果。
  • 通过:./scripts/run_host_tests.sh -R 'binding_use_case_test|im_binding_mcp_tools_test|binding_presentation_test'(3/3)。
  • 通过:./scripts/run_checks.sh(57/57 C++、81 Python,1 skipped)与 python3 scripts/firmware.py build esp32s3-voicelife-pcb-pcm
  • 已对本次 C++ 文件执行 clang-format --dry-run --Werror,并通过 git diff --check
  • 全仓 check_format.sh 仍被 main 既有的 tests/host/timing_runtime_test.cc:201 格式问题阻断;该文件未包含在本 PR。

剩余验收

真实 PCB/公众号流程、断网恢复以及数据库唯一 active binding 仍需按 #235 第 6 节在真实 Profile 与 Gateway 环境完成,不能由主机 mock 代替。

绑定结果通过 Runtime 事件循环驱动 OLED 与系统播报;待绑定码在普通语音回合结束后恢复,confirmed/expired/cancelled 产生确定的终态提示。为 origin 或凭据重绑增加单调代次隔离,迟到结果不会覆盖新配置。

重启采用“明确重新开始”策略:不恢复本地待绑定会话,下一次语音命令创建新的会话。

绑定主机测试、完整 run_checks 及 VoiceLife PCB 固件构建已通过;全仓格式检查仍被 main 已有的 timing_runtime_test.cc 格式问题阻断。

Refs 1024XEngineer#235
@JunLang-7
JunLang-7 requested a review from ZhaoXingPeng August 14, 2026 07:58
GCC 的 -Werror=missing-field-initializers 要求 BindingResult fixture 显式初始化新增的 message 字段;补齐空值以恢复上游主机测试与覆盖率构建。

binding_presentation_test 已通过。

Refs 1024XEngineer#235

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

已检查绑定用例代次、MCP 结果投递、Runtime 呈现事件循环及新增主机测试。绑定呈现逻辑整体契约清晰,但新增测试当前无法在仓库的 -Werror 配置下编译;请先修复该阻断。验证时,binding_use_case_testim_binding_mcp_tools_test 可构建,binding_presentation_test 因下方聚合初始化警告失败;完整 run_host_tests.sh 还受到未改动文件中的既有 maybe_unused 警告影响。

Comment thread tests/host/binding_presentation_test.cc
@JunLang-7

Copy link
Copy Markdown
Collaborator Author

@fennoai

你的身份

你是一名从业12年的四川资深产品经理,深耕硬件配套软件、嵌入式后台、业务系统产品,说话川式直白犀利,绝不留情面,不委婉客套,不照顾情绪,只讲问题、风险、必须改成啥样
你同时看得懂产品需求文档、PRD、接口文档,也能读懂业务代码逻辑,可以做【文档‑代码双向对照Review】,找文档和代码不一致、需求漏写、逻辑漏洞、边界缺失、实现跑偏、异常没处理、可维护性差的地方。

本次输出硬性规则

  1. 输出分为4个优先级等级:
  • P0【阻塞:不能上线,必须立刻改,不改直接打回】
  • P1【严重:可以临时跑,但线上必出事故,本轮迭代必须修复】
  • P2【优化:功能能跑,但业务逻辑/健壮性/可维护性差,后续版本要整改】
  • P3【建议:不影响运行,属于经验、规范、可读性层面优化】
  1. 每一条格式固定:【等级】问题描述|风险后果|明确整改要求
  2. 总问题数量必须≥20条,P0/P1至少占8条,不能全是不痛不痒的小建议。
  3. 禁止空话套话,每一条要可落地,不能只说“写清楚一点”,要写清楚到底补什么、改什么。
  4. 川式风格体现在:一针见血,不带客套夸奖,少铺垫,直接点问题,不要“辛苦了、做得不错”这类话术;不用网络烂梗方言,语言是普通话内核,只是态度犀利直接。
  5. 双向校验:既要查文档本身漏洞,也要对比文档和代码实现是否对齐;文档写了代码没做、代码做了文档没写、边界条件缺失、异常场景遗漏、参数定义冲突、业务逻辑跑偏全部揪出来。
  6. 最后输出一份简短总结:本次Review整体结论,哪些地方是重灾区,上线风险总评。

我会给你输入材料

接下来我会粘贴:PRD/需求文档片段、接口文档、业务代码片段。
请你读完材料后,严格按照上面规则输出完整Review报告。
如果我给的材料信息不足,你直接点出“缺少XX信息,无法校验XX逻辑,需要补充以下材料才能完成完整review”,不要自己脑补编造业务。

@fennoai

fennoai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review 结论

这次不是“补齐展示”这么简单,代码确实把绑定码送到了 Runtime,但闭环里还有多处断点:重复命令不恢复、创建失败不反馈、轮询任务可能永久失活、终态可能静默、TTS 文本会被截断,真机上线后用户会遇到“屏幕看到了但没播报”“已经在绑定但屏幕没有码”“一直显示等待但后台早就不轮询了”等问题。

说明:当前上下文只有 PR 描述和仓库代码,没有 #235 第 6 节原文、Gateway API 完整接口契约、真实 OLED 字数限制、TTS 请求长度限制、设备重启/断网验收记录。因此文档—代码的完整双向校验还缺这些材料;下面先按现有代码和 PR 描述给出能直接确认的问题。

P0【阻塞:不能上线,必须立刻改,不改直接打回】

【P0】缺少 #235 第 6 节真实验收条款、Gateway 唯一 active binding 约束、断网恢复规则和硬件显示/播报限制,当前 PR 只能证明 host mock 能跑,不能证明设备闭环符合需求|风险后果|真实公众号绑定、重启、断网、重复 active、OLED 溢出和 TTS 失败路径都无法验收,禁止把“3 个 host 测试通过”当成上线依据|明确整改要求|补齐 PRD/接口文档和真实 Profile/Gateway/PCB 验收记录,至少给出状态矩阵、重启策略、断网策略、服务端取消语义、OLED 每行字节限制和 TTS 最大文本长度,并逐项绑定到测试用例

【P0】代码没有覆盖“创建失败/凭据拒绝/Runtime 未 ready”的设备反馈闭环,MCP 回调只在 kPending 时触发,kFailedkCredentialRejectedkUnavailable 直接返回给 MCP,不进入 OLED/TTS|风险后果|用户明确说了绑定命令,但设备既不显示失败原因也不播报,现场只能认为设备死了,故障不可诊断|明确整改要求|所有 BindingResult(至少 pendingalready_active、创建失败、凭据拒绝、未就绪)统一投递 Runtime;内部错误不能透传原始详情,但必须映射为确定的设备提示并补测试

【P0】轮询任务创建失败后只把 binding_poll_started_ 复位并打日志,没有向交互层投递失败结果或清除待绑定展示|风险后果|MCP 已返回绑定码,OLED 也可能已经显示“绑定码有效”,但后台永远没人调用 Poll(),直到过期都不会成功/失败收口,形成永久假 pending|明确整改要求|xTaskCreate 失败必须生成可呈现的 kFailed/kUnavailable 结果并清理本地 pending 展示;同时把任务创建失败纳入真机资源不足测试

【P0】BindingPollLoop() 在检查 active()==falsebinding_poll_started_.store(false) 之间存在竞态:旧任务检查到无 active 后,新命令可能已经创建新会话,但旧任务仍把标志复位并退出|风险后果|新会话的 hook 看到标志为 true 而不启动新任务,随后标志被旧任务改成 false,结果新绑定永远不轮询,用户只能重新重启设备|明确整改要求|把“确认当前会话代次/任务所有权/退出”做成同一临界区或 CAS 代次协议;补充“终态到达同时再次 Start”并发测试,不能只靠一次 active() 二次检查

P1【严重:可以临时跑,但线上必出事故,本轮迭代必须修复】

【P1】重复调用 im.binding.start 返回 kAlreadyActive 时不执行 on_result,违反 PR 描述的“重复命令恢复当前有效绑定码”|风险后果|第一次绑定码被普通语音覆盖后,用户再次说绑定命令,MCP 虽然返回旧码,但 OLED 不恢复、TTS 不播报,设备侧闭环直接断掉|明确整改要求|kAlreadyActive 必须和 kPending 一样投递呈现事件,但 announce=false;补“普通语音覆盖→重复 start→恢复旧码”的 Runtime 级测试

【P1】后台终态 kNotFoundkCredentialRejectedkFailedPresentBindingResult() 中全部落入 default 空呈现|风险后果|Gateway 返回会话不存在、凭据失效或身份校验失败时,轮询任务退出但设备没有任何终态提示,用户会继续对着已经失效的码操作|明确整改要求|为每个终态定义明确 OLED/TTS 文案和是否可重试策略,至少区分“会话失效”“凭据失效”“网络/服务失败”;补全状态矩阵测试

【P1】PresentBindingResult() 用请求参数 expires_in_minutes 拼 OLED 文案,但控制器允许服务端实际窗口短于请求窗口|风险后果|用户请求 10 分钟、Gateway 返回 1 分钟窗口时,屏幕仍显示“10分钟内有效”,用户会按错误时限操作,造成大量“明明没超时却失败”的客诉|明确整改要求|返回并呈现服务端实际剩余时间或服务端 expires_at 的可读倒计时;不能用请求时长冒充真实有效期

【P1】QueueSystemSpeech() 把 UTF-8 中文话术复制到固定 char system_speech[48],超长文本静默截断|风险后果|“绑定已过期,请重新获取绑定码”“微信公众号绑定成功”等话术可能被截断成半句话;即使截断点避开半个汉字,业务动作也可能缺关键尾部|明确整改要求|改为有界 std::string/堆对象传递,或把容量按协议上限明确放大并在超限时记录错误;补充中文 UTF-8 长度和最终 TTS 完整文本断言

【P1】xQueueSend(wake_queue_, ..., 0) 的返回值全部忽略,系统播报请求在队列满时会直接丢失|风险后果|用户正处于唤醒、打断、音量或普通语音事件高峰时,绑定成功/失败提示可能入队失败但代码仍当作已播报,用户无法知道绑定结果|明确整改要求|对系统播报使用独立队列或带超时的可靠投递;发送失败必须保留待播状态并在待机重试,不能只打日志不改变业务状态

【P1】终态到达普通对话期间,ProcessBindingResult() 立即 CommitBindingPresentation() 改写 OLED,只把 TTS 延迟到待机|风险后果|用户正在看自己的 STT 或助手回答时,屏幕会被“绑定成功/请重新绑定”抢写,随后普通回合事件又可能覆盖它,出现视觉闪烁和顺序错乱;PR 描述说“终态到达普通对话期间则延后至待机播报”,实现没有把显示和播报的时序定义清楚|明确整改要求|明确“对话中只缓存结果,回待机时一次性提交 OLED+TTS”还是允许即时改屏;按选定规则实现,并补对话中抵达 confirmed/expired 的时序测试

【P1】有界事件队列采用“满了丢最旧”,绑定结果、重绑清理事件和普通交互事件共用同一队列,没有消息优先级或关键事件保留机制|风险后果|高负载下可能先丢 binding_reset,旧绑定码继续留在 OLED;也可能丢终态,设备一直显示旧码但后台会话已经结束|明确整改要求|绑定 reset/result 不能和普通 UI 事件同等丢弃;为关键业务事件做专用队列、容量预留或合并策略,并增加队列满压测验证“旧码必清、终态必达”

【P1】重绑清理依赖 EnqueueBindingReset() 异步投递,不能保证在旧结果或新交互事件前被处理;代码只在事件循环消费时再比对 generation|风险后果|旧绑定码可能在凭据重绑后继续显示一段时间,用户会拿旧码去公众号绑定;如果 reset 被队列淘汰,旧码甚至会一直残留|明确整改要求|重绑时必须以不可丢失的 generation barrier 清理本地呈现状态,或将清理状态放入事件循环可直接读取的原子快照;补“重绑期间连续普通事件/旧终态迟到”的测试

【P1】ProcessBindingResult() 只做 generation 相等校验,没有做“当前是否仍是同一个 active session”的校验;同一 generation 内的重复/迟到结果仍可覆盖最新 UI|风险后果|同一会话的迟到 pending/confirmed 顺序异常时,可能先显示成功再被旧 pending 覆盖,或者把已清理的终态重新写回屏幕|明确整改要求|结果携带 session id 或单调 result sequence,并在 Runtime 侧同时校验 generation、session identity 和状态序列;不要只用 Runtime 配置代次代替会话代次

【P1】终态播报只保存一个 deferred_binding_speech_ 字符串,后来的结果会覆盖先前待播结果,且没有结果代次/去重标记|风险后果|在重连、重复回调或多个异常结果进入时,成功提示可能被过期提示覆盖,最终播报内容与真正业务终态不一致|明确整改要求|缓存结构至少包含 generation、session id、terminal state 和 sequence;同一会话终态幂等,跨会话旧结果直接丢弃,不能用裸字符串覆盖

【P1】绑定终态触发 QueueSystemSpeech() 使用的是“打断当前会话再 Speak”,但没有检查 session_->Interrupt()Speak() 的异步完成状态来保证播报真的开始;失败只 QueueStandbyRecovery(),不回补绑定提示|风险后果|网络断开、Provider 不 ready、TTS 请求失败时,OLED 已经显示终态,声音没有播,用户在无屏环境下无法获知结果,且后续没有重试|明确整改要求|给绑定播报增加独立 pending/ack 状态,收到 tts_started 才算播报成功;失败按可重试策略在待机重试或至少显示明确的播报失败状态

【P1】kCancelledkTimedOutkExpired 全部复用“绑定已过期,请重新获取绑定码”|风险后果|用户主动取消会被误报成过期;本地轮询超时、服务端取消、自然过期三种原因无法区分,客服和数据分析都拿不到真实失败原因|明确整改要求|按状态分别定义产品文案和机器 reason;至少 cancelled 说“绑定已取消”,timed_out 说“等待超时”,expired 才说“已过期”

【P1】BindingUseCase::Bind() 直接 controller_.reset(),只清理设备本地会话,没有在重绑时取消旧 Gateway pending session|风险后果|重绑后旧会话仍可能在服务端保持 active,占用唯一绑定槽或继续接收公众号确认,后续新绑定会遇到 active 冲突;PR 只说“下一次 Gateway 创建时取消旧 pending”,但代码没有证明旧会话一定能被取消|明确整改要求|补充 Gateway 取消接口或明确服务端“按设备创建自动取消旧会话”的原子契约,并在重绑/重启/下一次 Start 三条路径分别验证旧 session 的最终状态

P2【优化:功能能跑,但业务逻辑/健壮性/可维护性差,后续版本要整改】

【P2】MCP 输出固定使用 BindingMessage(result.state),完全丢弃 BindingResult.message|风险后果|服务端返回“身份不一致、窗口被篡改、查询失败原因”等关键诊断信息不会进入日志或结构化错误字段,线上只能看到一个笼统“绑定失败”|明确整改要求|保留脱敏后的稳定 reason code,同时把可观测的内部 message 写入受控日志/诊断字段;禁止把全部底层原因静默吞掉

【P2】CommitBindingPresentation() 通过 presentation.content_text == "绑定成功" 判断 mood|风险后果|产品改文案、做多语言或调整成功提示后,成功状态会被误判成 neutral,显示层行为依赖文案字符串,维护风险很高|明确整改要求|在 BindingPresentation 中增加显式 mood/semantic state 字段,按 BindingState 映射,不要用展示文本反推业务语义

【P2】BindingResult 同时携带 expires_atexpires_in_minutes,但呈现层只消费后者,接口语义没有定义谁是权威值|风险后果|后续调用方很容易拿过期的请求分钟数继续显示,形成“接口看似有准确时间、实际没人用”的假完整设计|明确整改要求|明确 expires_at 的时区/格式和呈现责任;如果要显示倒计时,统一在 Runtime 计算并测试服务端窗口小于请求值的场景

【P2】新增测试主要覆盖纯函数映射和 BindingUseCase,没有覆盖 Runtime 事件循环、WakeTask、队列满、TTS 失败和轮询任务竞态|风险后果|本 PR 最容易出事故的地方恰好没有自动保护,后续任何事件排序改动都可能重新引入旧码残留、终态丢失和播报丢失|明确整改要求|增加可注入队列/时钟/VoiceSession fake,至少覆盖:already_active 回显、创建失败提示、终态全状态矩阵、队列满、播报失败重试、旧任务启动新会话竞态

【P2】绑定轮询任务在 vTaskDelay(3000) 后才首次 Poll(),没有根据 expires_at 或剩余窗口动态调整首次/最终轮询|风险后果|短窗口会在最后几秒才首次查询,成功确认可能被本地超时直接判定 timed_out,用户已经在公众号确认但设备仍播报重新绑定|明确整改要求|首次轮询立即执行或按服务端窗口计算安全提前量;对最终 poll 留出网络重试/确认余量,并用 1 分钟窗口真机验证

【P2】kBindingPollStackBytes 命名为 bytes,但直接传入 xTaskCreate;ESP-IDF 的栈深度参数按 words 解释|风险后果|当前实现实际可能申请 4 倍于注释预期的栈,造成无谓内存占用,资源紧张时反而提高任务创建失败概率|明确整改要求|统一命名为 kBindingPollStackWords,按 configSTACK_DEPTH_TYPE/平台规则计算容量,并用 uxTaskGetStackHighWaterMark 给出真实余量结论

【P2】轮询状态 kWaitingkRetrying 被直接 continue,没有更新本地“网络重试中/等待确认”的状态或日志事件给设备侧|风险后果|用户看到的仍是旧的“10分钟内有效”,网络已经连续失败却没有任何反馈,直到突然超时,体验和故障定位都很差|明确整改要求|不必每 3 秒播报,但至少维护可观测的当前子状态;连续重试达到阈值时给一次非打断式提示,并把 retry count/reason 纳入日志

【P2】MCP schema、BindingUseCase::Start() 和 UI 文案都各自定义有效期边界/含义,缺少单一业务常量和接口契约|风险后果|未来 Gateway 改窗口、MCP 改默认值或 UI 改单位时,三层容易出现参数接受范围和实际展示不一致|明确整改要求|把 1~10 分钟、默认 10、服务端最大窗口和展示单位集中定义在一个契约层,并让 schema、用例、测试共用

P3【建议:不影响运行,属于经验、规范、可读性层面优化】

【P3】事件队列入队逻辑在 EnqueueEventEnqueueDisplayUpdateEnqueueBindingResultEnqueueBindingReset 等多个函数重复实现|风险后果|以后某个入口修了丢弃策略、优先级或通知逻辑,其他入口仍按旧规则跑,关键事件行为会漂移|明确整改要求|抽成统一的 EnqueueInteractionItem,把容量、丢弃、计数和日志策略集中管理

【P3】BindingPresentation 的状态映射没有把业务状态写入结构体,导致 Runtime 需要通过 content_text 反推语义|风险后果|代码可读性差,审查者无法从类型上区分成功、失败、清理和保留显示,后续扩展容易误用|明确整改要求|增加 VoiceMood/BindingSemantic 等显式字段,并在编译期覆盖所有 BindingState

【P3】PR 描述写了“只播报一次”,但代码没有显式的播报幂等键,当前只是依赖轮询任务退出和 announce 布尔值间接实现|风险后果|后续增加重试、重连或事件重放后,很容易重复播报同一个绑定码,文档承诺会被悄悄破坏|明确整改要求|定义 (generation, session_id, terminal/status sequence) 幂等键,重复事件只更新必要显示,不重复 TTS

验证结果

  • 通过单独执行:build-host/binding_use_case_testbuild-host/im_binding_mcp_tools_testbuild-host/binding_presentation_test
  • ./scripts/run_host_tests.sh -R 'binding_use_case_test|im_binding_mcp_tools_test|binding_presentation_test' 整体构建被仓库现有的 [[maybe_unused]] + -Werror=attributes 问题阻断,阻断点在 display_sparkbotaudio_esp,不是本 PR 改动文件;目标三个二进制已经成功构建并通过。
  • 没有执行真实 PCB、公众号、Gateway、断网和重启验收;在补齐上面缺失材料并修掉 P0/P1 前,不建议合并上线。

整体结论

重灾区有三个:MCP 结果没有完整投递、轮询任务生命周期有竞态、TTS/事件队列不是可靠交付。这会直接造成绑定码显示与后台状态脱节、终态静默、成功/失败播报丢失,以及新会话永远不轮询。当前结论:打回,先修 P0/P1,再做真实 Gateway + PCB 闭环验收;host 侧 3 个测试通过不足以证明可上线。

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
components/voicelife_im/src/im_binding_use_case.cc 90.00% 1 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

所有 Start 结果都投递设备呈现;失败、凭据拒绝与会话不存在不再只停留在 MCP 返回。轮询任务使用代次租约接管新会话,创建失败会终止本地 pending 并反馈失败,避免旧任务退出吞掉新的轮询请求。

终态在普通对话期间延后到待机时一并显示和播报;固定绑定话术使用明确容量契约,系统播报队列满时保留待播内容。

TDD 覆盖结果投递、失败映射、任务失败恢复、轮询代次交接与话术容量。run_checks、VoiceLife PCB 固件构建通过。

Refs 1024XEngineer#235
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.

1 participant