# Roadmap(Current Architecture V5 + Capability Spec v1.0) > Date: 2026-09-18 > **评估与排序修订:** `phase-4-eval-and-roadmap.md`(对照 Phase 1–4 代码缺口 + 家长端提前)。 > 输入(历史):`phase-1-review.md` · `phase-2-review.md` · `phase-3-review.md` · Capability Spec v1.0 > 粒度:架构与项目管理。不是 Coding Plan。 > Phase 4 已实施(设备播放列表 + 后评估)。**下一编码阶段:Phase 7a 家长任务下发**(施工单 `phase-7a-plan.md`)。先 Dispatch,再 History;**不做计量 UI**。 > 语言包 / 离线词表仍见 **Phase 6 / 6b / 7b**。7a 双语场景只存并传递 `language_pair`,不换 MultiNet。 > **产品代际 / 多硬件版本(原 Phase V)改到最后**:现网只有一款色宁设备,不挡演示。 原则:每完成一个 Phase → Completion Review → Architecture 更新 → Roadmap Review → 下一份 Phase Plan。 **禁止**把本文当成锁死到 Phase 9 的施工图。后面 Phase 只保证目标、边界与 **对应的 Spec 能力 ID**。 --- --- ## 0. 冻结之后怎么切 Phase Capability Spec 把系统分成三层: ```text Application 以后才无限扩展(教育 / 益智 / 陪伴 / 助手) Task Runtime 尽快稳定,权威在 Server Device Capability 语义已冻结:播 / 听 / VAD / speak 出声 / wait / loop / 指示 / 抢占 ``` 因此路线图不再按「再做 10 个 App」排列,而按: ```text 1. 把已声明、固件尚未具备的原子能力补齐(只允许因此 OTA) 2. 把 Task Runtime 做成可归档、可查询 3. 把评估 / 调度 / 资源放到 Server 4. 用倒背 / 口算 / 接龙反证:新 App 零固件增量 ``` 原 V1.1「Phase 2 = type=task + Java 表 + 跟读 + 评分」仍然拆开。Spec 额外要求: - 运输可变(MCP / MQTT / detect 前缀),**语义集合不再为新 App 加命令种类** - `loop`(同一音频 N 次)在设备;`branch(答对了吗)` 只在 Server,且 **仅 Task 声明为流内评估时**才走进业务(倒背/接龙);跟读默认不走 - 离线只保证可离线 Skill(播放);翻译/评估/接龙必须在线 - 评估时机是 Task 属性(后评估 vs 流内),不是新传感器 - 离线只保证可离线 Skill(播放);翻译/评估/接龙必须在线 - SD 放资源;`apps.json` 不是离线业务引擎 - `record_clip` v1 = 听流 + Server 归档;本地 WAV 上传是 v1.1 | 编号 | 一句话 | Spec 主要验证 / 交付 | |------|--------|----------------------| | Phase 1 | 薄 App Runtime | 已完成:D15 设备 loop、D4 Mode 策略雏形、翻译走 Server | | Freeze | Capability Spec v1.0 | 文档冻结;不编码 | | Phase 2 | 用复读+跟读钉死事件与采集 | **已完成(见 phase-2-review.md)**:D1、D6、D7 v1、D8 v1 文本;另提前 D13 目录 `/{app}/{task}/lessons` | | Phase 3 | TaskInstance 持久化 | **已完成(见 phase-3-review.md)**:三表 + PUT/GET;GET 现为 server secret | | Phase 4 | 跟读:清单循环 + **后评估** | **已完成(见 phase-4-eval-and-roadmap.md)**:设备本地播→听→下一句;metrics 不指挥喇叭 | | Phase 7a | 家长任务下发 + 执行记录 | **下一编码**:场景+参数;立即 vs 计划;本地全表、RTC 只写下一次。泛听≠跟读。History 后做、无 metrics | | Phase 5 | Server Scheduler 补救扫描 | 权威表已并入 7a-B;本阶段剩「设备已开机、云端到点再推一把」 | | Phase 6 | App / 资源 + **在线 Locale** | D13 fetch;phrase pack;翻译/双语的 language_pair | | Phase 4b | 倒背 / 口算 / 接龙 | **流内评估 + 及时 speak**;排在家长能看见跟读之后 | | Phase 6b | 离线 Locale(可选 OTA) | MultiNet/唤醒词表按 locale;**仅 ESP-SR 已有模型的语种(先 zh/en)** | | Phase 7b | 家长完整能力 | 语言对、PAUSE、长按退出、听原声完善 | | Phase 8 | 开发者 App 平台 | Manifest 只能调已冻结 Skill | | Phase 9 | Subscription / Marketplace | 账号,不改音频 | | Phase V | 产品代际 + 端云兼容 | **最后做。** 小智 AI 1 / 1-1 / 2;就这一款硬件时不开工 | --- ## 1. Phase 依赖图 ```text Phase 1 薄 App Runtime ✅ ↓ Capability Spec v1.0 ✅ 文档 ↓ Phase 2 钉死 D1 事件 + D6/D7/D8v1 采集 ✅ ↓ Phase 3 TaskInstance 持久化 ✅ ↓ Phase 4 跟读清单循环 + 后评估 ✅ 设备执行列表;Server 只评估 ↓ Phase 7a 家长任务下发 ← 下一编码 │ 7a-A 固件:计划全表 + RTC 只写最近一次;开机到点也执行 │ 7a-B 云端任务表 + 场景 catalog + SYNC_SCHEDULE / 立即 START │ 7a-C 小程序:场景参数 + 立即 / 周期 │ 7a-D 历史(文本+可选录音;无 metrics) ├─────────────┐ ↓ ↓ Phase 5 到点补推 Phase 6 资源 + 在线 Locale │ ↓ Phase 4b 倒背/口算/接龙(inline;禁止改跟读后评估模型) ↓ Phase 6b 离线 Locale(zh/en) Phase 7b 家长完整(语言对 / PAUSE / 听原声) ↓ Phase 8 开发者平台 ↓ Phase 9 订阅 ↓ Phase V 代际/兼容 最后;第二款硬件或摄像头 SKU 前才必须 ``` **必须串行:** 1 → Freeze → 2 → 3 → 4 → **7a-A 固件(全表+下一次闹钟)** → 7a-B 云端 → 7a-C 小程序。7a-D 历史不挡宣称 Dispatch Done。 **7a 不依赖** 4b / 6 / V。SD 已有 unit01 即可选课。家长任务数量 **不是** 10;`ALARM_MAX_NUM` 不准当上限。语言对可存 params,phrase pack 仍 Phase 6。 **Phase 4b** 依赖 4 的听窗/Instance,也依赖家长能看见结果(否则新 App 同样「只有喇叭」)。禁止为此改 C++。评估时机见下文「评估时机」表。 **Phase V 不挡 7a / 4b / 5。** 现网一款硬件;摄像头立项前再做握手字段。 **Phase 6:** 目录树与 `ensure_app` 已在 Phase 2 提前落地。本阶段做清单 / DOWNLOAD / `resource_id`,并做 **在线 Locale**。不要重做 mkdir。 **Phase 6b:** 仅当需要「没网也用德语/英语喊切换」时才 OTA 离线词表。德语等 **没有** 官方 MultiNet 的语种,离线入口保持 zh 或 en。 **明确后置、不挡冻结宣称:** | Spec 能力 | 最早 Phase | |-----------|------------| | D11 `wait` 一等 Skill | 4b 前可仍用 Server 延时;需要「播完停 800ms 再听」且不想云端睡时再下固件 | | D14 TASK_PAUSE/RESUME | 5 或 7b 家长控制需要时 | | D8 v1.1 本地文件上传 | 会议纪要 / 律师询问产品化时;7a 不依赖 | | D9 长按退出 Task | 可与口令退出并存;正式绑定放 7b | | D10 `indicate` 逻辑态 | 有空再做;禁止塞进 DeviceState 迁移表 | | 新 WS `type=command` | 仅当 detect 前缀不够表达步进;**不加新语义** | | 在线 Locale / 语言对 | Phase 6(phrase pack + pair);控制台 Phase 7b | | 摄像头 / 新传感器 Skill | **Phase V 之后**;新 SKU = 小智 AI 2,禁止混进 1 的固件包 | | 产品代际 hello 字段 | **Phase V,最后做**;现网一款硬件不挡 7a | --- ## 2. 各 Phase(Roadmap 级) ### Phase 2 — 冻结面验证:采集 + 完成事件 ✅ 1. **Objective** 不是做一个新 App。用 **已有** 复读机 + 跟读 Demo,把 Spec 里尚未落地的三块钉进 Runtime: `audio.completed/stopped`(D1)、非 Chat 的 listen/句边界(D6/D7)、跟读归档 v1(D8)。 **完成事实:** 见 `phase-2-review.md`。跟读改为 SD 一句一 MP3;订阅 App 可 mkdir 不烧录。 2. **Why Now** Spec §20:这三项不补齐就不能宣称 Device 已冻结。跟读 Mode 已在线,继续堆倒背/口算只会在没有事件的底座上复制 Demo。 3. **Dependencies** Phase 1 执行器、`SHADOWING`/`PLAY_AUDIO` 分流、Opus 听流、`report`、设备 `repeat` loop(D15,本阶段 **不改**)。 4. **Scope** 逻辑事件(运输:`[skill_event]`,对应 Spec `AUDIO_COMPLETED/STOPPED`);TaskSession 文件;跟读 turn = prompt + asr + 可选 WAV;pytest。Mode 名代码里可仍叫 `SHADOWING`,文档对齐 Spec `FOLLOW_READ`。 5. **Out of Scope** 评分(S4)、Java 表、`type=task`、倒背/口算/接龙产品、SD 范读必修、`wait` Skill、`TASK_PAUSE`、`record_clip` 本地文件、apps.json 下发、新 DeviceState、MultiNet 加业务口令。 6. **Client** 复读结束上报完成/停止事件;不改 Opus;不把跟读句子表写进固件。 7. **Server** Session + 跟读记轮次 + 吃事件后回 chat;ASR/LLM/翻译仍在 Server。 8. **Protocol** **不增加命令种类。** 只把已声明的 Event 用现有 `listen/detect` 文本运过去。语义见 Spec §4–5。 9. **Database** 无新表。归档走现有 `ai_agent_chat_audio`(D8 v1)。 10. **API** 无新 REST。 11. **Data Flow** 见 `phase-2-plan.md`。跟读下一步由 **Server 步进**(D16),不是设备 loop。 12. **State Machine** DeviceState 不动。InteractionMode:`PLAY_AUDIO` / `SHADOWING(FOLLOW_READ)` / 回 `CHAT`。 13. **Acceptance Criteria** Spec §22 四条纪律 + 本文 Phase 2 验收:完成事件单次回聊、跟读 JSON 轮次完整、Chat/离线复读/翻译不回归。 14. **Risks** 回声当跟读;事件与 `[app_task]chat` 双招呼。 15. **Regression Risks** 复读双播、离线「切换模式」连云、Chat barge_in。 详细施工:`phase-2-plan.md`。 --- ### Phase 3 — TaskInstance Persistence ✅ 1. **Objective** 中层 Task Runtime 可查询:Session JSON → `ai_task_instance` / `ai_task_step` / `ai_task_event`。不新增 Device Capability。 **完成事实:** 见 `phase-3-review.md`。GET 走 server secret(非计划中的 oauth2)。未建空 score 列。跟读 `prompt_text` 仍是 SD 路径。 2. **Why Now** Phase 2 字段已稳定,建表不会空。评估和调度需要主键。 3. **Dependencies** Phase 2 `TaskSession` schema;D8 v1 report 管道。固件保持 Phase 2 bin。 4. **Scope** manager-api 最小 DDL;`TaskSession.save()` upsert;只读 GET。Agent ≠ App。JSON 文件双写保留。 5. **Out of Scope** 评分算法、对象存储、家长 UI、新 WS type、设备缓存 instance_id、改 SD 布局。 6–10. Client **零改动**。Server Python 上报 + Java CRUD。协议不加新命令种类。音频挂 step 尽力而为。 13. **Acceptance** 一次跟读可在 API 查到 steps + 文本 + 可选 audio_id;manage_api 挂了语音仍可用。 14–15. 与 `chat_history` 双写但表分开;旧固件无 instance_id 必须兼容(id 只在云端)。 详细施工:`phase-3-plan.md`。验收:`phase-3-test.md`。 --- ### Phase 4 — 跟读:清单循环 + 后评估(定量) ✅ 1. **Objective** **仅跟读这一类**:预定 MP3 序列 + 定时录音。分析全部后置、定量、允许失败。设备不宣读对错。 **完成事实:** 见 `phase-4-eval-and-roadmap.md`。步进在**设备播放列表**(一次 JSON 下发),不是逐句 MCP;后评估仍在 Server。 2. **Why Now** 跟读/打卡录音的价值在归档与事后看指标。若把云端比对绑在句间,喇叭会卡。 3. **Dependencies** Phase 3 Instance;Phase 2 `duration_ms` 听窗。原文来自 txt 契约,**不能**用 `prompt_text` 路径。 4. **Scope(已落地)** 设备:`listen_ratio` × 墙钟播放时长;清单结束 `playlist_completed`。 Server:ASR 不进 LLM;`metrics[{method,value}]`;本地 wav + 同名 txt。无 `passed`、无当场 TTS 分数。 5. **Out of Scope** 倒背/接龙的及时反馈(4b);家长 UI(**7a**);S8 再播同一句;音素;租户。 6. **Client** 有 diff(播放列表 / 听窗)。禁止再改回「每句 MCP」。 13. **Acceptance** 说错/不说/API 挂了仍播下一句。语音回归已按锁定路径验收。 详细施工(历史):`phase-4-plan.md`(其中「固件零 diff / Server 时钟」已被代码修正,以评估报告为准)。 --- ### Phase 7a — 家长任务下发 + 执行记录 ← 下一编码 > 产品:选场景填参数;可立即执行(临时,最高优先)或存为周期计划。设备存全表,RTC 只写最近一次。先下发,后看记录;无计量 UI。对外 **泛听**(旧称复读机)/ **跟读**。 > 详细见 `phase-4-eval-and-roadmap.md` §5.2。施工单:`phase-7a-plan.md`。 1. **Objective** 家长按场景创建任务(泛听/跟读:课文、遍数、结束后关机或回对话;双语:中英/中法/中日…;对话可带时长)。立即下发马上跑;计划任务到点跑。软关机后从本地表挑下一最近时刻写入唯一硬件闹钟。History:跟读句文本+录音;对话类聊天。不展示相似度。 2. **Why Now** 把计划 1:1 塞进 `ALARM_MAX_NUM=10` 撑不住「每小时整点聊 10 分钟」这类周期。DS3231 本来就只编程下一次。现网唯一 pending 会被覆盖;已开机到点不执行;课文写死。 3. **Dependencies** Phase 1 `START_APP_TASK` / 软关机 / Alarm1;Phase 3 Instance;Phase 4 播放列表。不依赖 4b/6/V。 4. **Scope** 7a-A 本地 schedule 全表 + `recompute_next_alarm`;7a-B `ai_parent_task` + `SYNC_SCHEDULE` + 立即 START;7a-C 场景表单;7a-D 历史。 5. **Out of Scope** metrics;课文 DOWNLOAD;倒背口算;新 WS type;改锁定热路径;用 10 限制条数。 6. **Device** 允许 OTA:计划文件、只写 Alarm1、duration 墙钟、on_complete。禁止新 DeviceState。课文不进固件。 13. **Acceptance(Dispatch)** 立即跟读可打断计划;≥15 条计划含 hourly 全量在设备、芯片只有最近一次;软关机唤醒进正确场景;结束后重算下一闹钟。History 不挡 Dispatch Done。 --- ### Phase V — 产品代际 + 端云版本匹配 (最后做) > **2026-09-18:** 现网只有一款硬件。本阶段 **不挡** 7a / 5 / 4b。摄像头 SKU 或第二款板子立项前再开工。 > 现状结论(2026-09-02 核对代码,仍成立):**没有「小智 AI 1 / 1-1 / 2」代际,也没有兼容矩阵。** > 施工单以后写 `phase-v-plan.md`。 1. **Objective** 把三套独立的编号钉死,并在连接时对上: **产品代际**(小智 AI 1 / 1-1 / 2)≠ **固件 semver**(OTA 包)≠ **协议/能力集**(Server 决定开哪些业务)。 Server 对过旧设备降级或引导 OTA;对过新设备拒绝未知能力,而不是 silently 当聊天。 2. **Why Now** 不加摄像头也可以出 1-1(跟读/Task Runtime 已是大功能面)。不加代际字段,OTA 只能按板型比 `2.2.6` vs `1.0.0` 两个对不上的数字,Server 无法判断「这台有没有相机 MCP、能不能跑 cap.v1.1 的跟读听窗」。 3. **Dependencies** 现网已有:OTA 上报 `application.version` + `board.type`;WS hello `version`(帧格式 1/2/3)+ `features.mcp|aec`;MQTT `snlp/v1` + header `"1.0"`;manager-api Liquibase 日期 changeset。 **不依赖** Phase 4 完成。摄像头硬件不在本阶段。 4. **编号模型(冻结语义,实现可后写)** ```text product.generation 1 / 2 / 3 硬件族。1=现网音箱;2=加摄像头(或其它新传感器) product.revision 0 / 1 / 2 同代硬件上的软件大改。对外:小智 AI 1、小智 AI 1-1、小智 AI 2 firmware.semver 1.4.2 每一次可 OTA 的 bin;只升这个数字去升包 capability.set cap.v1 / v1.1 与 Spec 修订对齐(播/听/跟读听窗/…);新 App 不加 cap 号 protocol.ws 1 已有 hello.version,只为二进制帧;禁止拿它当产品代际 protocol.mqtt snlp/v1 topic 大改才升 v2;header.version 只升破坏性字段 server.release 2026.09.x 或 semver xiaozhi-server + manager-api 同发 api.rest /xiaozhi/v1/ 破坏性 REST 才 /v2/;旧路径保留到兼容窗结束 ``` 对外称呼: | 对外 | generation | revision | 含义 | |------|------------|----------|------| | 小智 AI 1 | 1 | 0 | 现网:无摄像头,当前能力面 | | 小智 AI 1-1 | 1 | 1 | **同一块硬件**,能力集或业务面大改(例如 Task Runtime 成为默认) | | 小智 AI 2 | 2 | 0 | **新硬件**(摄像头等);固件包与 1.x **分轨**,禁止烧到对方板上 | 5. **现状缺口(对照代码,不要假装已有)** | 层 | 现在有什么 | 缺什么 | |----|------------|--------| | 终端产品号 | `system.h` 里 `APP_FIRMWARE_VERSION "1.0.0"` 几乎不 bump;CMake `PROJECT_VER "2.2.6"` 是上游仓库号;User-Agent 是 `BOARD_NAME/esp_app.version` | 没有 `product.generation/revision`;1 和 1-1 无法区分 | | WS 握手 | hello:`version=1` + `features.{mcp,aec}` + 音频参数 | 不报固件、不报代际、不报 cap_set;Server 不拒不匹配 | | OTA | 按 **板型** 比 semver,新则给下载 URL | 不按产品代际分轨;不检查 Server 最低 cap;1.0.0 vs 2.2.6 两套数字并存 | | MQTT snlp | topic `v1`、header `"1.0"` 写死 | 无协商;无能力位 | | Server | `config.yaml` `xiaozhi.version: 1`;manager-api 靠日期 SQL | 无 `server.release`;REST 无兼容窗;无「此 Server 支持的 generation 范围」 | | 业务匹配 | MCP 工具列表运行时发现 | 没有「这台 gen1 永远不要 vision」的矩阵;新 API 默认所有旧设备都能打 | 6. **Scope** - 设备 OTA JSON + WS hello **增补**(不改旧字段含义):`product_generation`、`product_revision`、`firmware`、`capability_set`。 - Server 维护一张 **compat 矩阵**(哪一档 Server 接受哪一档设备;过旧 → 只开旧业务或下发 OTA;过新/未知 cap → 拒绝或忽略未知 MCP)。 - manager-api:固件包绑定 `(board_type, generation)`,禁止 gen2 包推给 gen1。 - REST:现有 `/task/instance` 视为 v1;破坏性变更走新路径或带 `Accept-Version`,旧客户端有兼容窗。 - 文档:一张表写清「小智 AI 1 最低 Server」「Server x 最低固件」。 7. **Out of Scope** 摄像头驱动、vision MCP 实现、把 cap_set 当 App 商店、为每个 App 升 generation、把 `hello.version` 改成 2 来表示 1-1。 8. **Protocol** **只加字段,不加新 WS type。** 旧固件不带这些字段时:Server 视为 **小智 AI 1 / revision 0 / cap.v1**,保持现网能连。 匹配发生在 hello 与 OTA check,不在每句 ASR。 9. **Client / Server 匹配规则(产品语义)** ```text 连接时 Device → generation + revision + firmware + cap_set Server → 查矩阵: 未知 generation → 拒绝或只开 CHAT cap_set < server.min → 引导 OTA;OTA 前只跑 cap 交集(例如没有跟读听窗就当 PLAY_AUDIO) cap_set > server.max → Server 忽略未知能力,不崩溃 generation=1 无 camera → 永不下发 vision 工具 generation=2 → 允许 vision;固件包与 1 分轨 ``` 同一硬件出 1-1:只升 `product.revision` + `capability_set` + `firmware.semver`,**不**升 generation。 新传感器:升 generation,新 board_type,新 OTA 目录。 13. **Acceptance** - 现网未改字段的固件仍能连当前 Server(默认 1.0 / cap.v1)。 - 控制台能看到设备是 1 还是 1-1,OTA 不会把 gen2 bin 推给 gen1。 - hello 日志能打印两端的 generation / cap_set / 采用的交集。 - 文档有兼容矩阵;破坏性 REST 有旧路径或明确截止日期。 14. **Risks** 把业务版本和协议版本混成一个数;1-1 误当成必须换机;Server 静默吞掉未知字段导致「以为支持摄像头」。 15. **Regression Risks** hello 加字段导致旧 Server 解析失败(必须忽略未知键);OTA 板型字符串与 generation 不一致导致升错包。 --- ### 评估时机(Task 声明,后续 4b 施工单再细化) 不是新 Device 能力。Manifest / Task JSON 标明 `assessment.timing`: | timing | 听窗结束后 | 代表 | 实现阶段 | |--------|------------|------|----------| | **`deferred`** | 立刻下一步;metrics 后台可晚到 | 跟读、故事复述、打卡录音 | **Phase 4** | | **`inline`** | **先** Server 评估,**再** `speak` 反馈,然后下一题 | 倒背数字、成语接龙、口算 | **Phase 4b** | 共同点:判题只在 Server;设备只收到已有 `speak` / `play_audio`;固件无 `if correct`、无 `JUDGE`。 差别:跟读可以评估失败仍往下播;倒背若评估失败应 speak「没听清」之类再进入下一题,**不能**把反馈拖到家长事后打开 App 才算完成这一回合。 倒背示例(Server 编排,可后补进 4b-plan): ```text speak 数字,间隔 ~1s(Server 睡或以后 D11 wait) speak「开始」 listen 固定时长 ASR + parser → metrics(对了几个、准确率) speak 反馈(及时、可含定量,如「倒对了 4 个」) 下一题或结束 ``` 成语接龙:听完用户一句 → Server 判是否合法接龙 → 马上 speak 下一成语或纠错提示。同样是 `inline`。 口算:默认同 `inline`(对/错或报出正确答案属于业务流)。若某练习模式只要交卷后看报表,再在 Task JSON 改成 `deferred`。 --- ### Phase 4b — 冻结验收集(倒背 / 口算 / 接龙)· 流内评估 1. **Objective** 三个代表 Task **只加 Server Task / Parser / Prompt**。其中倒背、接龙(及默认口算)必须是 **`inline`**:听窗结束就要评估并出声反馈,**禁止**做成跟读那样的后评估。 2. **Why Now** 跟读只覆盖「录完继续」。规则型/游戏型回合若不及时反馈,产品就不是那个 App。 3. **Dependencies** Phase 4 的听窗、Instance、metrics 字段。另增:听窗结束 → await 短评估 → speak(仍不改固件)。 4. **Scope** 倒背:间隔出题 → 「开始」→ 录音窗 → 立刻报对了几个/准确率。 接龙:用户说完 → 立刻合法性与下一提示。 口算:默认同 inline。 出题 TTS 语言 = `device.locale`(无则 zh)。Parser 仍产出定量 `metrics[]`;反馈文案由 Server 生成后 `speak`,不是固件模板。 5. **Out of Scope** 新 Mode、新 DeviceState、MultiNet 业务词、新 WS、`JUDGE` 命令、难度引擎产品化、小程序、phrase pack、把跟读改回流内纠错。 6. **Client** **禁止改 C++。** 13. **Acceptance** 倒背一轮:录音结束后 **数秒内** 设备说出评估结果,再出下一题或结束。断评估服务时有超时兜底 speak,不得死等。固件 git diff 为空。 施工单:以后写 `phase-4b-plan.md`(本文件只钉时机差异)。 ### Phase 5 — Server Scheduler(补救扫描) 1. **Objective** 权威任务表已在 **7a-B `ai_parent_task`**。本阶段补:设备已开机但本地闹钟漏触发时,云端按分钟再推 `START_APP_TASK`。RTC 仍只负责「下一次」唤醒。 2. **Why Now** 7a 完成后「每天准时」仍可能两边漏一刀;云扫描是对账,不是日历真相的第二份拷贝。 3. **Dependencies** 7a-B plan 表;Phase 1 `START_APP_TASK`。 4. **Scope** 到期扫描;同一 plan+同一分钟幂等一次;跳过正在跑的 `mqtt_immediate`。 5. **Out of Scope** 再做一张平行日历;Device 上的复杂 Plan DSL。若 7a J5 已落地,本阶段可标部分完成。 8. **Protocol** 复用已有 MQTT 命令,不新增种类。PAUSE/RESUME 若做,必须是 Spec D14,不是第五套协议。 --- ### Phase 6 — App / Resource Management(D13)+ 在线 Locale 1. **Objective** 资源权威在 Server;设备只缓存文件。同时落地 **设备默认语言(在线半)** 与 **翻译/双语的可替换语言对**。 2. **Why Now** 跟读换标准音、多 App 音频,不能靠手工拷 SD。词表若继续写死中文,德语机联网后仍只能喊「开始跟读」。 3. **Dependencies** Phase 3。不依赖 4b、不依赖 6b 固件。现网 `ai_agent.language` / `lang_code` 可先当 Chat 语种来源。 4. **Scope — 资源** 清单 / DOWNLOAD / `resource_id`。可选缓存 manifest。设备仍只执行 skills[]。 5. **Scope — Locale(在线,本阶段必须做)** 见下文 **「语言模型」**。phrase pack、`device.locale`、翻译/双语 `language_pair`。热路径:ASR 文本用该 locale 的 enter_phrases 匹配;TTS 进场语用该 locale;Chat 系统提示与音色跟 locale。 6. **Out of Scope** App Store;把判题 JSON 当固件逻辑;德语离线唤醒(6b / 可能永久做不到);Phase 4 时钟重做。 --- ### Phase 6b — 离线 Locale(可选 OTA) 1. **Objective** 没网时,唤醒词与系统级口令(切换入口 / 复读机 / 回 Chat)与 `device.locale` 一致。**不是新 Device 能力种类**(Spec:MultiNet 词表可配)。 2. **Why Now / 为何单独** ESP-SR 的 WakeNet / MultiNet **按语种出模型**。中文口令是拼音音素;英语是另一套 MN;官方 **没有** 德语 MultiNet 可预期。把「烧一套德语句表」塞进 Phase 6 会假装离线德语已经存在。 3. **Scope** NVS `locale`;从 assets 加载该语种的 wake + 系统命令表。v1 **只承诺 zh 与 en**(有模型才做)。切换仍只允许系统级词,禁止把「开始跟读」写进固件。 4. **Out of Scope** 为泰/法/德训练唤醒模型(产品/数据项目,不是编码 Phase);运行时从云下载声学模型进 PSRAM(除非以后单独立项)。 5. **接受现实** `locale=de-DE` 的设备:在线菜单/Chat 可以是德语;离线唤醒可以仍是英文或中文 SKU,直到有模型。文档必须写清,不要当 bug。 --- ### Phase 7b — Parent Task Management(完整) 1. **Objective** 小程序在 7a 之上:改 Schedule;**设置设备默认语言与翻译/双语语言对**;PAUSE;听原声完善。 2. **Dependencies** Phase 7a + Phase 5 + Phase 6。6b 不是前提:控制台改 locale 后,在线立即生效。 4. **Scope** UI。D9 长按退出若未做可一并。语言对只能选 App Manifest 声明的 supported_pairs。 5. **Out of Scope** 开发者平台、支付、在小程序里训练唤醒词、硬件代际管理。 --- ### Phase 8 — Developer App Platform 1. **Objective** 第三方 Task JSON **只能引用 Spec 已冻结 Skill**。校验失败即拒。 2. **Dependencies** Phase 6;InteractionMode 集合稳定。 5. **Out of Scope** GPIO、设备脚本引擎、新传感器。 --- ### Phase 9 — Subscription / Marketplace 账号与权益。不改 Opus,不上情绪诊断。 --- ### 语言模型(评估结论;实现落在 6 / 6b / 7) 分三层,不要混成「一个 language 字段搞定唤醒和翻译」。 | 层 | 是什么 | 例子 | 难度 | |----|--------|------|------| | **A 设备默认语言 `device.locale`** | 开机 Chat、进场 TTS、在线场景口令、无语言对的 App(倒背、口算、故事复述) | `zh-CN` / `en-US` / `de-DE` | 在线:**容易**(Server phrase pack + 已有 `ai_agent.language`)。离线唤醒/口令:**不容易**,见 6b | | **B App 语言对 `language_pair`** | 翻译、双语对话的可替换选项 | 翻译 `th↔fr`;对话 `en↔de` | **容易**,纯 Server;一个 App + 参数,不要每种语言对一个 `app_id` | | **C 资源内容语言** | 音频 MP3/txt 本身 | 英语跟读课永远是英文音频 | 跟 lesson 包走,不跟 locale。德语机练英语跟读:菜单德语、喇叭音频英语 | **功能 1 好不好做:** - 联网后的中文改德语 Chat / 「切换模式」菜单 / 「开始跟读」的德语同义句:把 `runtime.py` 硬编码词表换成 `locales/{locale}/phrases.json`,hello 时带上 `device.locale`。**工作量中等、风险低,Phase 6。** - 开机离线就用德语喊唤醒、喊「复读机」:必须换 WakeNet + MultiNet 模型与音素格式。中文现网是拼音编译进 `system.h`。英语官方 MN 可做第二套 assets。**德语没有现成 ESP-SR 模型时,离线入口做不到**,只能在线。不要承诺「改一个配置全世界离线德语」。 - 工厂 SKU(出厂烧中文机 / 英文机)比运行时热切换离线模型容易得多。运行时切换 locale:在线立刻生效;离线要 6b 且仅 zh/en。 **功能 2 怎么配:** 不要再复制 `english_translation` / `english_conversation` 两个 App 名。Manifest: ```text app_id: translation default_pair: ["zh","en"] # 设备 locale 为 zh 时的缺省 supported_pairs: [["zh","en"],["th","fr"],["en","de"], ...] instance.options.language_pair # 家长/控制台覆盖 app_id: bilingual_chat default_pair: ["zh","en"] supported_pairs: [["zh","en"],["en","de"], ...] 语义:用户说 lang_a → 只答 lang_b,反之 ``` 翻译机:未指定时仍可 auto 检测,目标语取 pair 的另一侧或 `target_lang`。 缺省 pair 的推导:`[device.locale 的主语言, 机构配置的 L2]`,没有 L2 则 `zh-en`。 倒背 / 故事复述 / 口算:只有 A,TTS 与听窗提示用 `device.locale`。不要给它们配 language_pair。 **权威:** `device.locale` 在 manager-api(绑定/出厂);Agent.language 与其对齐,避免两套打架。xiaozhi-server 连接时读取,固件 NVS 只服务 6b 离线。设备 **仍然没有 tenant_id**。 **禁止:** 为每种语言对改 C++;把「开始跟读」的德语写进固件;Phase 4 做 Locale。 --- ## 3. 跨端依赖(Command / Event / State) 对齐 Spec §4–5。每个 Phase 必须同时有 Server→Device 与 Device→Server,禁止单端表。 ```text Phase 2 Device AUDIO_COMPLETED|STOPPED ──detect──► Server Device listen 流 + VAD ────────► Server ASR / D8 v1 report Server SPEAK / PLAY_AUDIO / 回 CHAT ──tts/mcp──► Device Server ensure_app dirs ──MCP/MQTT──► Device mkdir(运输,非新能力) Phase 3 ✅ Server TaskSession JSON ──HTTP upsert──► manager-api Instance 无新 Device 能力;设备不持有 instance_id Phase 4 ✅ Server 一次 JSON playlist ──MCP──► Device Device 本地播→听窗→下一句 Device 听流(旁路 ASR/report) ──► Server 后评估 metrics Device playlist_completed ──detect──► Server 回 CHAT 跟读:分析失败不挡下一句;无 JUDGE Phase 7a 小程序 场景任务(立即/计划) ──HTTP──► manager-api 立即 START_APP_TASK mqtt_immediate ──MQTT──► Device 计划 SYNC_SCHEDULE 全表 ──MQTT──► Device schedule.json Device 挑最近一次 ──Alarm1──► 唤醒/到点 StartAppTask 7a-D GET instance / chat ──oauth2──► 文本+可选录音 无新 WS type;无 metrics UI Phase 4b Device 听流 ──► Server ASR + parser(inline,短超时) Server SPEAK 反馈 / 下一题 ──► Device 仍无 JUDGE、无固件 if correct;评估在业务流内,不能拖到后评估 Phase 5 Server TASK_START ──MQTT──► Device Device TASK_STARTED/COMPLETED ──ack/event──► Server Phase 6 Server FETCH ──DOWNLOAD──► Device SD Device PLAY_AUDIO(resource) ──event──► Server Phase V(最后) Device hello/OTA 带 generation+revision+cap_set ──► Server 矩阵 Server 交集业务 / 引导 OTA / 拒未知 SKU ──► Device 旧固件缺字段 = 当前这一款 SKU;不加新 WS type ``` --- ## 4. Master Plan / Spec 对照 | 长期目标 | Roadmap | |----------|---------| | Device 能力冻结 | Freeze 文档 + Phase 2 补事件/采集 | | Task Runtime 在 Server | P2 Session → P3 Instance ✅ → P4 后评估 ✅ → P4b 流内评估 | | 新 App 不改 C++ | P4b 三个代表 Task 验收 | | 跟读语音对象 | P2 D8 v1 → P3 挂 step → P4 本地 wav/txt → P6 标准音下载 | | Scheduler | **P7a 任务全表 + 设备只写下一闹钟**;临时优先于计划;P5 到点补推可选 | | App ≠ Firmware | 贯穿;P6 资源、P8 沙箱 | | 设备默认语言 / 语言对 | P6 在线 pack + pair;P6b 离线 zh/en;P7b 控制台;P4b 继承 locale | | 产品代际 / 端云匹配 | **Phase V,最后**;现网一款硬件视为当前 SKU | | 家长可见练习 | **P7a 场景任务下发 + 7a-D 历史(无分数)**;语言对已作为双语参数;PAUSE P7b | | 心理陪伴 / 主持人 | 无新硬件;复用 listen/speak + Server Memory | | Subscription | P9 | --- ## 5. 刻意不做进近期的事项 来自 Spec §17 与 §21: - 为成语/口算/接龙改 C++ 或加 MultiNet 业务口令 - 把 `branch(if correct)` 放进固件 - 合并 Xiaozhi MQTT 与 snlp(改运输 ≠ 加能力,但不作为近期项目) - 把 apps.json 当离线权威引擎 - Phase 2 做 `wait` Skill、`TASK_PAUSE`、本地 WAV 上传、`indicate` 大改 - Phase 2 拆微服务 / 对象存储 / 情绪分析 / 临床诊断 - 一次性写死 Phase 5–9 的类名与 REST 路径 - 把业务 App 切换词写入固件(系统级仅 Chat + 离线复读 + 「切换模式」总入口) - 在 Phase 4 引入租户 / `tenant_id`(最早缝在 Phase 6,产品化 Phase 7b) - 在 Phase 4 做多语言 / 把德语唤醒写进固件 - 为每种翻译语言对新增一个 `app_id` 或新 WS type - 用一个数字同时表示产品代际、固件 semver 和协议版本 - 把摄像头固件包按 semver 推给无摄像头的小智 AI 1 - 在 Phase 4 / 7a 做兼容矩阵 / 改 hello 字段(放到 **Phase V,最后**) - 在 7a 之前做倒背/口算/接龙或课文 DOWNLOAD 平台 **下一编码阶段:** 只打开 `phase-7a-plan.md`。跟读/泛听热路径与欢迎词/翻译/中英锁定。先 7a-A「全表 + 只写下一次闹钟」,不要再做 10 槽一对一。History 与 metrics 后置。