# Phase 4 之后:评估报告与更新后的 Roadmap > Date: 2026-09-18 > 对照:当前仓库代码 · `roadmap.md`(2026-09-02)· `phase-1-review.md` · `phase-2-review.md` · `phase-3-review.md` · `phase-4-plan.md` > 产品决策:家长端(小程序)提前;小智硬件代际 / 多版本(原 Phase V)放到最后。 > 粒度:架构与项目管理。不是 Coding Plan。 > 下一编码阶段:`phase-7a-plan.md`——家长任务下发(场景+立即/计划;RTC 只写最近一次)+ 执行记录(后,无计量 UI)。 原则不变:每完成一个 Phase → Completion Review → Architecture 更新 → Roadmap Review → 下一份 Phase Plan。 **本文取代**旧路线图里「下一编码 = Phase 4 / Phase V 可与 4 并行」的排序。 --- ## 0. 一页结论 跟读/复读已经能在**这一款**色宁设备上完整跑完:清单一次下发、设备本地播→听→下一句、后评估定量、本地 wav + 同名 txt、Instance 可入库。缺的不是再做一个英语 App,而是**家长能设一天的计划,设备自主开/关/切模式**;历史记录第二刀,分数先不给家长看。 | 判断 | 说明 | |------|------| | Phase 1–4 产品骨架 | **可用。** 对话 / 翻译 / 中英 / 复读 / 跟读已在设备上验收通过(锁定路径见 `.cursor/rules`) | | Phase 4 相对原施工单 | **目标达成,执行器换了地方。** 步进权威从「Server 时钟 + 每句 MCP」改成「设备播放列表循环」;后评估仍在 Server,不指挥喇叭 | | 演示闭环缺口 | 无云端计划表;闹钟 pending 只有一份;已开机到点不切模式;课文写死;`manager-mobile` 无计划页;Instance GET 仅 server secret | | 下一刀 | **Phase 7a 家长任务下发**。全表同步、RTC 只写下一时刻;立即优先于计划;History 后做;metrics UI 不做 | | 原 Phase V | **最后做。** 现网只有一款硬件;hello 代际字段不增加演示能力 | 不要在 7a 之前开倒背/口算/接龙、不要做摄像头兼容矩阵、不要把课文下载平台化当成选课前提(SD 手工拷卡 + catalog 与卡上路径一致即可)。不要先做分数页。 --- ## 1. 总进度(对照旧 Roadmap) | 编号 | 旧 Roadmap 一句话 | 代码事实(2026-09-18) | 状态 | |------|-------------------|------------------------|------| | Phase 1 | 薄 App Runtime | `apps.json` + `start_app_task`;CHAT / PLAY_AUDIO / TRANSLATION / BILINGUAL / SHADOWING;MQTT `START_APP_TASK`;离线口令挂钩 | **完成**(有债,见 §2.1) | | Freeze | Capability Spec v1.0 | 文档冻结 | **完成** | | Phase 2 | 事件 + 采集 | `[skill_event]` + `duration_ms`;TaskSession JSON;SD `/{app}/{task}/lessons` | **完成**(跟读已不是 TTS 范读) | | Phase 3 | TaskInstance 持久化 | 三表 + PUT/GET;report 可回写 `audio_id` | **完成**(GET 鉴权不对家长;见 §2.3) | | Phase 4 | 跟读时钟 + 后评估 | 设备播放列表 + `listen_ratio` 听窗;`expected_text` / `metrics`;本地 `data/recordings` + 同名 `.txt` | **完成**(与原文「固件零 diff」不符,见 §3) | | Phase V | 产品代际 + 端云矩阵 | 无 generation/revision/cap_set | **未做 → 调到最后** | | Phase 4b | 倒背 / 口算 / 接龙 inline | 仅 `apps.json` 意图槽预留 | **未做 → 演示闭环之后** | | Phase 5 | Server Scheduler | 设备 RTC + NVS pending;**无**云端日历表 | **未做 → 权威表并入 7a-B;5 剩到点补推** | | Phase 6 | 资源 DOWNLOAD + 在线 Locale | 只有 mkdir;词表仍硬编码中文 | **未做** | | Phase 6b | 离线 Locale OTA | 仅现网中文 MultiNet | **未做** | | Phase 7 | 家长任务管理 | `manager-mobile` 无计划/Instance UI | **未做 → 7a 下发优先、历史后置** | | Phase 8 / 9 | 开发者 / 订阅 | — | **后置** | ```text 已落地(设备可用) chat.conversation · 翻译 · 中英对话 · 复读清单 · 跟读清单 TaskSession JSON · Instance 三表 · 后评估 metrics · 本地 wav/txt 未落地(挡住「真实使用」) 家长:按场景创建任务(泛听/跟读/双语…);立即执行或周期计划 固件:本地计划全表;RTC 只编程最近一次;已开机到点执行 云端 ai_parent_task + SYNC_SCHEDULE(不要 10 槽 SET_ALARM 一对一) 历史:oauth2 读 Instance / 聊天(7a-D,无 metrics) ``` --- ## 2. 已完成 Phase 的缺失(不要假装没有) 只列**仍影响产品或后续施工**的缺口。已在对应 `*-review.md` 写过、且不再挡演示的过程债(例如「Phase 1 无 pytest」)从略——Phase 2 之后已有 pytest。 ### 2.1 Phase 1 仍缺 | 缺口 | 现状 | 是否挡 7a / 演示 | |------|------|------------------| | 家长小程序 UI | `manager-mobile` = 智能体列表 + 配网 + 聊天记录,不是练习控制台 | **挡。** 7a 本体 | | Server Schedule 权威表 | 只有设备 `SET_ALARM`(软件最多 10 条)+ **唯一** NVS pending | **挡。** 7a:云端任务表 + 设备全表;DS3231 只写下一时刻。不要把 `ALARM_MAX_NUM` 当家长上限 | | 冷场多次主动开口 | 进入对话有欢迎词;可配循环催促未做 | 不挡 | | 非 Sening 板复读 | Task Gateway 仅 `sening-xiaozhi-s3` | 不挡(就这一款) | | 双技能表 | 固件 `k_task_skills[]` + Server `apps.json` | 不挡;禁止再为新 App 扩固件表 | | 离线/在线状态分叉 | 没网复读结束后 Server 仍可能以为在 CHAT | 演示保持在线 | ### 2.2 Phase 2 仍缺 | 缺口 | 现状 | 是否挡 7a / 演示 | |------|------|------------------| | D8 v1.1 本地文件上传 | 跟读 wav 在 xiaozhi-server 磁盘;chat report 另走 BLOB | 家长播录音依赖现有 chat_audio **或** 只展示文本。完整听原声可后做 | | D11 `wait` Skill | 听窗在设备墙上时钟 | 不挡跟读;4b 间隔出题再用 | | D9 长按退出 Task | 口令退出已有 | 不挡 | | D10 `indicate` | 无 | 不挡 | | 跟读句子表曾在 Server | **已改:** 整份 playlist JSON 一次下发,设备排序执行 | 已是现网;文档须承认 | ### 2.3 Phase 3 仍缺 | 缺口 | 现状 | 是否挡 7a / 演示 | |------|------|------------------| | GET 鉴权 | `ShiroConfig`:`/task/instance/**` **全部** `server`。计划写 oauth2,实施因「不能按方法拆过滤器」改成 server | **挡 7a-D。** Dispatch 不依赖读 Instance。History 再拆 oauth2 新路径;PUT 仍 server | | `prompt_text` 仍是 SD 路径 | 原文在 `expected_text` | UI 必须显示 `expected_text`,不要把路径当课文 | | upsert 失败后 `_skip_upsert` | 本会话不再上报。`audio_id` 已改为 VARCHAR(255) 消掉截断 | 部署 Liquibase 后新会话正常;旧 skip 会话需重新跟读 | | 查询副本非权威 | 权威 = 连接内存 + `data/task_sessions/*.json` | 家长读库即可演示;不要把库当步进引擎 | ### 2.4 Phase 4 仍缺 / 与计划漂移 见下一节。对家长 **History**:**asr_text + expected_text + 可选 chat_audio 已够**。metrics 已入库,7a **不展示**。不要等音素、及格线、流利度。 --- ## 3. Phase 4:原计划 vs 代码 ### 3.1 原施工单(`phase-4-plan.md`)钉死的模型 ```text 设备(当时要求零 diff) 播一句 → completed(duration_ms) → Server 开听窗 → 时钟到了 MCP 播下一句 分析全部后置、定量、允许失败;无 passed;无 JUDGE。 ``` ### 3.2 实际实现(必须当成 V5 架构,不要回退) SRAM / 58 句清单证明:每句再 MCP 一次会把设备卡死或截断列表。现网是: ```text Server 一次 JSON playlist(url|name, repeat, speed;speed 默认 1.0) Device PSRAM 清单,本地循环: 播一句 → 关解码器 → 听窗 = 墙钟播放时长 × listen_ratio(1.2) → 录音上行 → 下一句 清单结束 → audio.playlist_completed → Server 播「跟读结束」回对话 Server ASR 只暂存/评估,不进 chat/LLM 后评估 text_similarity_v0 → step.metrics data/recordings/{mac}/{mode}/{date}/{time}_{asr|tts}_*.wav 同 stem .txt(UTF-8) 复读 同样一次下发清单;播完「复读结束」;播放中忽略闲聊 离线口令:退出复读 / 我们来聊天吧 ``` 锁定(已验收,后续禁止为修跟读去改):开机欢迎词时序、CHAT / TRANSLATION / BILINGUAL 的 ASR→LLM 路径。 | 原计划条目 | 代码 | 结论 | |------------|------|------| | 固件零 diff | `app_task.cc` 播放列表、听窗、`listen_ratio`;解码器/唤醒词在听窗前关掉 | **有意偏离。** 设备成为步骤循环执行器,Server 不再逐句 MCP | | 下一句只由 Server 时钟触发 | 设备墙上时钟;Server 旁路评估 | 语义仍是「分析不挡喇叭」,触发器换端 | | `expected_text` 来自 txt 契约 | `resolve_expected_text`;禁止用路径做相似度 | **符合** | | `metrics[]` 无 passed | `text_similarity_v0` 0–1 | **符合** | | 说错/不说仍下一句 | 听窗到点就进下一句;静音也落 wav | **符合** | | 退出立刻回 Chat | 口令 `is_exit_to_chat` | **符合** | | Liquibase 新列 | `expected_text` / `metrics` / `assessed_at`;后又 `audio_id` VARCHAR(255) | **符合**(255 是 9-18 为路径加的) | 宣称 Phase 4 **Done** 的产品标准(原 §2.3):热路径流畅 + 后评估定量 —— **达到**。 宣称「固件冻结面未移动」—— **不要再写。** 冻结的是 Skill 种类,不是「跟读循环必须在云端」。 ### 3.3 Phase 4 未做(原 Out of Scope,保持) 当场纠错再播、及格线、音素、租户、4b 三个 Task、热路径读卡上 txt、manager-web 打卡页、Locale。 --- ## 4. Current Architecture V5(基于代码) ### 4.1 设备(仅 sening-xiaozhi-s3) ```text Application 热路径(hello/listen/tts/stt/Opus 语义锁定) Task Gateway START_APP_TASK / pending / 离线口令 SET_ALARM 软件表最多 10 时刻;DS3231 只编程下一次 Alarm1 **7a-A:家长计划改走本地全表,不要 10 槽一对一;pending = 下一笔计划** playlist:repeat + speed + listen_ratio skill_event:audio.completed|stopped|playlist_completed + duration_ms SD mkdir /{app}/{task}/lessons ``` ### 4.2 Server ```text xiaozhi-server apps.json 权威定义 start_app_task / mode 分流 跟读/复读:一次下发 playlist,不逐句 MCP TaskSession → JSON + PUT Instance assessment.py 后评估(不 await 再 play) audio_archive:recordings + sidecar txt manager-api PUT/GET /task/instance 目前仅 server secret ai_task_instance / step / event 不执行 Skill、不判对错结论 ``` ### 4.3 家长端现状 ```text manager-mobile(uni-app,可出微信小程序) 登录 / 智能体 / 配网 / 聊天记录 / 声纹 无:练习会话、metrics、START_APP_TASK、Schedule manager-web 智控台(人设、模型、OTA) 无跟读页 ``` 色宁 MQTT `START_APP_TASK` **设备侧已通**。缺的是小程序发出去,以及用用户身份读 Instance。 ### 4.4 数据(跟读一句) ```text 设备录音 Opus → Server ASR(不进 LLM) → recordings/..._asr_....wav + .txt → turn.asr_text / expected_text / metrics → PUT Instance(audio_id = 相对路径,≤255) → 可选 chat-history report → 32 位 blob,回写同一列则覆盖路径 ``` 家长 7a-D:**只展示文本**(跟读 expected/asr;对话类走 chat_history)。相似度不渲染。播放原声若要用现成 API,走 chat_audio id,不要假设 manager-api 能读 xiaozhi-server 磁盘路径。 --- ## 5. 更新后的 Roadmap ### 5.1 新依赖图 ```text Phase 1–4 ✅ 设备侧英语学习可演示 ↓ Phase 7a 家长任务下发 ← 下一编码 │ A 本地计划全表;RTC 只写最近一次;开机到点也执行 │ B ai_parent_task + 场景 catalog + SYNC_SCHEDULE / 立即 START │ C 小程序:场景参数 + 立即 / 周期 │ D 历史(文本+录音;无分数) │ ├───────────────┐ ↓ ↓ Phase 5 到点补推 Phase 6 课文 DOWNLOAD + 在线 Locale (7a 日历表已建) (换课不靠拷卡时再做;不挡选课) │ ↓ Phase 4b 倒背 / 口算 / 接龙(inline 评估) │ 固件仍禁止 if correct;用已有 speak/listen ↓ Phase 6b 离线 Locale(仅 zh/en 有模型才做) Phase 7b 家长完整:语言对、PAUSE、长按退出、听原声完善 Phase 8 开发者 App 平台 Phase 9 订阅 Phase V 产品代际 / 端云兼容 ← 最后(就这一款硬件) ``` **必须串行:** 7a-A 固件(全表+下一闹钟)→ 7a-B 云端 → 7a-C 小程序。未完成 A 不要宣称周期任务可用。7a-D 不挡 Dispatch Done。 **7a 不依赖:** 4b、6 下载、6b、V。SD 已有 unit01 即可选课。语言对可存 params。 **5 不挡 Dispatch:** 权威表在 7a-B;Phase 5 只补「已开机漏响」的云端再推。 ### 5.2 Phase 7a — 家长任务下发(Roadmap 级) 1. **Objective** 家长先选场景再填参数:泛听/跟读(课文、遍数、结束后关机或回对话);双语(中英/中法/中日…,可选时长)。可 **立即执行**(临时,最高优先)或 **存为周期计划**。设备保存计划全表;DS3231 **只写入下一次最近时刻**。History 第二刀看文本/录音,**不展示分数**。 2. **Why Now** 10 槽闹钟一对一撑不住「每小时整点聊 10 分钟」。芯片本来就只编程下一次。现网 pending 会被覆盖;已开机到点不执行;课文写死;家长文案「复读/复读机」和跟读分不清。 3. **Dependencies** `START_APP_TASK` / 软关机 / Alarm1;Phase 3 Instance(7a-D);登录与绑设备。不把 `SET_ALARM` 当任务总线。 4. **Scope** - **7a-A** 本地 `schedule.json` 全表;`recompute_next_alarm()` 写唯一 pending + Alarm1;已开机也执行;软关机前重算。 - **7a-B** `ai_parent_task` + 场景/课文/语言对 catalog;计划 `SYNC_SCHEDULE`;立即 `START_APP_TASK`。 - **7a-C** 小程序:场景动态表单 + 立即 / 每天 / 每小时。对外写 **泛听**,不写复读机。 - **7a-D**(后)oauth2 读 Instance + 聊天;不渲染 metrics。 5. **Out of Scope** 分数、课文 DOWNLOAD、倒背/口算、OTA 代际、改锁定热路径、用 `ALARM_MAX_NUM` 限制条数、删旧口令「复读」。 6. **Client(设备)** 允许补计划文件、只写下一闹钟、duration 墙钟、on_complete。禁止新 DeviceState、业务 if 树、把 58 句清单塞进 RTC。可新增一条 MQTT `SYNC_SCHEDULE`(不是新 WS type)。 7. **Acceptance(Dispatch)** 立即跟读可打断计划;≥15 条计划含 hourly 全量在设备、芯片只有最近一次;软关机唤醒进正确场景;结束后重算下一闹钟;计划不打断临时。Chat/翻译/中英口令回归不坏。History 不挡 Dispatch Done。 施工单:`phase-7a-plan.md`。未完成 7a-A 不要宣称周期任务可用。 ### 5.3 后续 Phase(目标级,不锁类名) | 编号 | 一句话 | 相对旧图的变化 | |------|--------|----------------| | Phase 5 | 设备已开机漏响时云端再推 START | 任务表已并入 7a-B;本阶段是补救扫描 | | Phase 6 | 清单 / DOWNLOAD / `resource_id`;在线 phrase pack | mkdir 已有。7a 已能存 language_pair;pack 不挡选课 | | Phase 4b | 倒背/口算/接龙 = `assessment.timing=inline` + 及时 speak | **挪到家长能看见跟读之后** | | Phase 6b | 离线 zh/en 词表 OTA | 不挡 7a/7b | | Phase 7b | PAUSE/RESUME、长按退出、录音播放完善 | 语言对选择已在 7a 双语场景;7b 补控制台与离线词 | | Phase 8 / 9 | 开发者沙箱 / 订阅 | 不变,更后 | | Phase V | `product.generation/revision` + hello/OTA 矩阵 | **最后。** 摄像头 SKU 或第二款硬件立项时再打开 | 评估时机表(deferred vs inline)仍有效,见旧 `roadmap.md`;4b 不得把跟读改回流内纠错。 ### 5.4 明确后置 | 事项 | 最早 | |------|------| | 小智 AI 1 / 1-1 / 2 代际字段 | Phase V(最后) | | 摄像头 / 新传感器 Skill | Phase V 之后 | | D8 v1.1 本地文件当家长主播放通道 | 7b 或独立;7a 用文本 + 可选 chat_audio | | D11 wait 一等 Skill | 4b 需要「播完停 800ms」且不想云端睡时 | | D14 TASK_PAUSE/RESUME | 7b | | 新 WS `type=command` | 仅当 detect 前缀不够;不加新语义 | | 德语离线唤醒 | 可能永久做不到(无官方 MultiNet) | --- ## 6. 场景(7a Dispatch Done 时) ```text 家长创建: 立即 跟读 unit01 ×1,结束后回对话 → 马上执行(可打断当前计划) 计划 A 每天 07:30 跟读 unit01 计划 B 每天 10:00 泛听 unit01,结束后软关机 计划 C 每小时整点 双语(中英) 10 分钟,结束后软关机 (计划可以很多条,不是 10) 设备: 本地保存全部计划 软关机前只把「下一次最近」写入 Alarm1 10:00 泛听关机后,闹钟变成 11:00 双语,而不是再占 10 个槽 History(7a-D,不挡 Dispatch): 跟读:每句原文 / ASR / 可选录音 泛听:完成态 双语/翻译/对话:该时段聊天 不展示相似度 ``` --- ## 7. 刻意不做进 7a 的事项 - 为倒背/口算改 C++ 或加 MultiNet 业务口令 - 把 `branch(if correct)` 放进固件 - 在 7a 做兼容矩阵 / hello 代际字段 - 把 `audio_id` 路径当成 manager-api 可直接 fopen 的文件 - 把及格、等级写入 `ai_task_step`;家长页渲染 metrics - 改 CHAT / 翻译 / 中英 / 开机欢迎词去「顺便修」家长页 - 重做 SD 目录或逐句 MCP 跟读 - 用 10 个闹钟 id 一对一绑定家长任务 - 先做历史页、后做「全表 + 下一闹钟」(顺序必须 A→B→C,再 D) **下一编码阶段:** 只打开 `phase-7a-plan.md`。先补本地计划表与只写下一时刻的 RTC,再云端与小程序。对外文案用 **泛听 / 跟读**。