Status: Freeze Analysis(钉死语义)
Date: 2026-08-31
Based on: Phase 1–2 真实代码 · Master Plan · 教育 / 益智 / 陪伴 / 助手四类 App 反向归纳
本文不是又一份 Master Plan,也不是某一 Phase 的 Coding Plan。
本文回答:**以后新增 App,固件原则上不再改什么。**
Phase 2 已完成(见 phase-2-review.md): D1 完成事件、D6/D7 v1 跟读采集、D8 v1 文本归档;SD 按 App 唯一名建树。
下一编码阶段是 phase-3-plan.md: TaskInstance 落 manager-api,**无新 Device 能力、不改固件。**
覆盖矩阵的可视化见 Canvas:能力覆盖矩阵。
新增 App
→ 只改 Server:App / Task / Step / Assessment / Resource
→ Device:读取配置或执行已有 Capability
→ 禁止:为单个 App 改 C++、改协议字段含义、强制 OTA
允许 OTA 的唯一理由:补齐 本 Spec 已声明、当前固件尚未具备 的原子能力(例如统一 play_audio 完成事件、listen 完成事件)。不允许为「成语接龙」单独 OTA。
三层模型:
Application 教育 / 益智 / 陪伴 / 助手 ← 无限扩展,全在 Server + 资源
↓
Task Runtime Task / Step / Flow / 评估 ← 尽快稳定,权威在 Server
↓
Device Capability 播 / 听 / VAD / 等待 / 指示 ← 现在冻结语义
再拆一层,避免把「麦克风」直接当 API:
Device Primitive I2S / 按键 GPIO / LED / SD / 网络
↓
Interaction Capability play_audio / listen / vad / speak / wait / indicate
↓
Task Capability 跟读一轮、口算一题、接龙一回合 ← 禁止写进固件
判定规则: 若完成条件依赖「答案是否正确 / 语义是否合格 / 该不该下一题」,这是 Server Task Capability,不是 Device Capability。
| ID | 能力 | 一句话 |
|---|---|---|
| D1 | play_audio |
播放一项音频资源,可循环,必有完成/停止事件 |
| D2 | audio.pause / resume / stop |
打断或挂起当前播放源(与 TTS 互斥) |
| D3 | speak |
朗读文本;实现可以是云 TTS,设备只负责出声与打断 |
| D4 | barge_in |
播放中检测到用户说话 → 停播 → Listening |
| D5 | play_system_sound |
统一提示音,App 不得自带 ding 实现 |
| D6 | listen |
上行语音流(现有 Opus),可按 Mode 不进 Chat |
| D7 | vad |
上报用户开始说 / 停止说(硬件可观测) |
| D8 | record_clip |
v1 可用 listen 流 + Server 归档;v1.1 本地文件 + 上传 |
| D9 | button |
短按 / 长按 / 双击 → 开始、停止、退出 Task |
| D10 | indicate |
把逻辑态映射到 LED/屏,App 不写 GPIO |
| D11 | wait |
可取消的本地延时 |
| D12 | volume |
播放音量 |
| D13 | resource.fetch |
把资源落到 SD/缓存(现有 DOWNLOAD 的语义升级) |
| D14 | task_control |
start / pause / resume / stop / restart 当前 Task 执行器 |
| ID | 能力 | 范围 |
|---|---|---|
| D15 | loop(播放级) |
play_audio(loop=N),完成条件 = 播完 N 次。Phase 1 已有 repeat |
| D16 | loop(步骤级) |
Server Task Runtime 重复下发 Step;设备不解释「再做一题」 |
| ID | 能力 | 原因 |
|---|---|---|
| S1 | ASR | 设备只有 PCM/Opus |
| S2 | LLM | — |
| S3 | Translation | Phase 1 已证明走 Server |
| S4 | Assessment / Parser / 判题 | 口算、倒背、接龙对错都不在固件 |
| S5 | Memory / Knowledge / RAG | — |
| S6 | Task Generation / 动态出题 | — |
| S7 | Speech feature → 临床诊断 | 特征可后抽,诊断永不上设备 |
| S8 | branch(if correct) |
设备不知道 correct;若将来做精练模式,只执行「再播 / 下一题」命令。**默认教育 App 热路径不用 S8**:定时结束即下一步(见 phase-4-plan) |
下列为 语义契约。MCP 名、MQTT action、未来 type=command 都只是运输。改运输不算引进新能力。
play_audioin:
resource: { kind: sd_path | http | asset_id, uri }
volume?: 0-100
loop?: int >= 1 # 默认 1;在设备上循环
speed?: 0.5-2.0 float # 默认 1.0;超出夹紧。重采样实现,任意浮点,非离散档;非 1.0 会变调
power_off_after?: bool # 已有软关机挂钩,可保留为播放策略而非 App 逻辑
out (events):
audio.started
audio.completed # 自然结束(含 loop 全部完成)
audio.stopped # 被 stop / 抢占 / 用户退出
audio.paused
audio.resumed
audio.failed # 缺文件、解码失败
当前代码: Sening self.mp3.play(name, speed, repeat, power_off_after);Phase 2 已上报 audio.completed / audio.stopped(运输:[skill_event]...|duration_ms)。无统一 WAV/HTTP play。
冻结缺口(仍开放): 资源 ID 而不是业务路径散落;非 Sening 板无此 Skill;audio.paused/resumed/failed 未作为独立事件。
audio.pause | audio.resume | audio.stop
当前:self.mp3.stop、TTS 时 PauseSdCardMp3。pause/resume 对 SD MP3 已有「TTS 暂停」语义,需提升为可被 Task 调用,而不仅是 Chat 副作用。
speakin: text, optional interruptible: bool
out: speak.started, speak.completed, speak.interrupted
当前:Server TTS → type=tts + Opus。设备没有独立「本地 TTS Skill」。**冻结:设备负责出声与 barge_in,不负责合成。** App/Task 只写 speak("请回答")。
barge_inpolicy (由 InteractionMode 配置,不是 App 名):
CHAT / BILINGUAL 允许打断 TTS
PLAY_AUDIO 默认不允许把喇叭当用户插话(复读)
FOLLOW_READ/SHADOWING 播放范读时忽略拾音;范读结束后才 listen
TRANSLATION 播译文时忽略回声
当前:Server 已按 mode 限制 AEC 打断。这是 Mode 策略,必须冻结进 Runtime,禁止每个 App 自己 if。
play_system_soundin: type = ding | beep | success | error | ...
out: sound.completed
当前:AudioService::PlaySound OGG。需编成稳定 type,而不是各板路径字符串。
listen / D7 vadlisten.start(mode)
listen.stop
vad → user.speech.started | user.speech.stopped
当前:上行 Opus + Server VAD;设备端也有 AFE/VAD 用于唤醒。
冻结 v1: 跟读/口算继续用现有听流 + Server VAD 事件即可,不强制设备再做一套文件录音。
必须补的事件: Server(或设备)在 Mode≠CHAT 时也要能区分「用户说完一句」,否则封闭式评估无法切 Step。
record_clip分两档,避免把冻结卡死在「设备写 WAV」:
| 档 | 行为 | 覆盖 |
|---|---|---|
| v1 | listen 流 + Server report 归档(已有 chat audio BLOB) |
跟读、口算、倒背、接龙 |
| v1.1 | save_local + upload + record.completed(audio_ref) |
弱网、会议纪要、律师询问长录音 |
v1.1 不阻塞 用 5 个代表性 Task 冻结硬件。会议纪要类只增加「长录音上传」,不增加新传感器。
buttonshort → toggle listen / 确认
long → 退出当前 Task 回 chat(可配)
double → 预留 pause
当前:BOOT 键绑 ToggleChatState。冻结时要允许「退出 Task」而不拆掉 Chat 键。
indicatein: IDLE | LISTENING | PROCESSING | PLAYING | ERROR | TASK_RUNNING | TASK_COMPLETE
映射到 LED/屏是板级实现。App 只发逻辑态。当前 DeviceState 是硬件占用态,**不要**把 TASK_RUNNING 塞进 Listening/Speaking 迁移表。indicate 与 DeviceState 正交。
waitin: ms, cancellable
out: wait.elapsed | wait.cancelled
当前:**无**一等 Skill。复读循环把等待藏在播放器内部。冻结后「播放 → 等待 800ms → listen」不得靠 Server 连发两次再自己 sleep 充数(可先 Server 睡,但协议要留 wait,避免每个 App 各写各的)。
volume / D13 resource.fetch当前:MCP 音量、snlp DOWNLOAD。冻结为资源到达 SD 后 play_audio 只认本地 resource_id/sd_path。
task_controlTASK_START / PAUSE / RESUME / STOP / RESTART
当前:START_APP_TASK、STOP_MEDIA、优先级抢占。没有统一 PAUSE/RESUME Task(只有 MP3 被 TTS 暂停)。
PAUSE 语义:停播放与拾音,保留 pending 与 loop 剩余次数。STOP:清执行器,pending 回 chat.conversation。
同一音频听 10 遍 → Device play_audio.loop=10 (已验证,保持)
10 道不同口算 → Server 循环 Step,每步 speak+listen
跟读 10 句不同音频 → Server 循环 Step(或 Task JSON 列表)
禁止 Server 对同一文件连发 10 次 play(Phase 1 已否决)。
设备执行器只暴露:
Capability.execute(name, args) -> accepted
Capability.cancel(name)
on Event(name, payload)
对照现状:
| 逻辑 API | 今日落点 |
|---|---|
play_audio |
self.mp3.play / PLAY_MEDIA |
audio.stop |
self.mp3.stop / STOP_MEDIA |
speak |
被动收 type=tts |
listen |
SendStartListening / 现有拾音 |
wait |
无 |
indicate |
Display/LED 散落 |
task_control |
Board_StartAppTask + preempt |
resource.fetch |
MQTT DOWNLOAD |
冻结策略: 新 App 只允许调用上表。板级 self.mp3.* 作为 play_audio 的 Sening 实现,逐步改名为逻辑名或保留别名。禁止再新增 self.english.*。
Server → Device,最终至少这些 命令语义(运输可变):
TASK_START {app_id, task_id, params, source, instance_id?}
TASK_PAUSE
TASK_RESUME
TASK_STOP
TASK_RESTART
PLAY_AUDIO {resource, loop, volume, speed}
AUDIO_PAUSE | AUDIO_RESUME | AUDIO_STOP
SPEAK {text} # 也可继续走现有 tts 帧,算同一能力
LISTEN_START | LISTEN_STOP
WAIT {ms}
INDICATE {state}
SOUND {type}
FETCH {url, dest}
今日等价物:type=mcp tools/call、type=tts、MQTT START_APP_TASK。
v1 运输冻结:可以继续 MCP + MQTT,不强制新 WS type。
v1 语义冻结:上表集合不再为新 App 追加命令种类。
Device → Server:
TASK_STARTED | TASK_COMPLETED | TASK_FAILED | TASK_CANCELLED
STEP_STARTED | STEP_COMPLETED | STEP_FAILED # Step 权威仍在 Server;设备可只报 skill 完成
AUDIO_STARTED | AUDIO_COMPLETED | AUDIO_STOPPED | AUDIO_FAILED
SPEAK_COMPLETED | SPEAK_INTERRUPTED
LISTEN_STARTED
USER_SPEECH_STARTED | USER_SPEECH_STOPPED
RECORD_COMPLETED {audio_ref?} # v1.1
WAIT_ELAPSED
BUTTON {kind: short|long|double}
FETCH_PROGRESS | FETCH_COMPLETED | FETCH_FAILED
今日等价物:MCP result、[app_task] / 规划中的 [skill_event]、MQTT rpt/ack。
跟读 v1 允许 USER_SPEECH_* 由 Server VAD 产生(设备未报也不算缺传感器)。
Idle ↔ Connecting ↔ Listening ↔ Speaking
+ Upgrading / WifiConfiguring / ...
只回答:麦和喇叭现在归谁。Task 不得新增 kDeviceStateEnglish。
CHAT
PLAY_AUDIO
LISTEN # 只听,不自动 chat(跟读的听半拍;口算听答案)
FOLLOW_READ # 先播后听;与今日 SHADOWING 对齐,建议最终统一命名
TRANSLATION
BILINGUAL # 仍走 LLM,但是 Mode 策略(打断/提示词)不同
QUIZ/GAME 不是新 Mode。口算/倒背/接龙 = speak + listen + Server 评估,Mode 用 LISTEN 或 CHAT 策略开关 skip_llm=true。
IDLE → EXECUTING → WAITING_USER → COMPLETED | FAILED | CANCELLED
PAUSE 可从 EXECUTING/WAITING_USER 进入,RESUME 返回
权威状态在 Server;设备缓存仅用于断线 UI 与离线复读。
| 来源 | 行为 |
|---|---|
| 用户 barge_in(Mode 允许) | 停 TTS/音频 → listen |
| 用户按钮长按 / 「退出」 | TASK_STOP → chat.conversation |
| 家长 MQTT 即时 | 优先级 3 抢占,STOP 当前 Skill 再 START |
| 计划任务 | 优先级 2,不可抢 MQTT 即时 |
| 语音切 App | 优先级 1,不可抢 2/3 |
| Chat TTS vs 课程 MP3 | 已有 PauseSdCardMp3,保持为音频互斥,不是 Task Pause |
TASK_PAUSE:保留 loop 剩余、当前 step 索引(索引在 Server)。设备只暂停播放/拾音。
分类冻结(Phase 1 已用事实证明):
| 类型 | 没网 | 例子 |
|---|---|---|
| 可离线 Skill | 设备直接执行 | 复读 play_audio + loop |
| 仅在线 Mode | 禁止 WakeWordInvoke 干等 | 翻译、跟读评估、接龙、口算 |
| 入口口令 | MultiNet 只保留总入口 + 可离线 Skill | 系统词表按 device.locale 配置(Phase 6b);默认中文「切换模式」「复读机」。**禁止**把跟读/翻译/语言对口令塞进固件 |
禁止把所有 App 口令塞进 MultiNet。
禁止把 apps.json 当作离线权威业务引擎;SD 上的 JSON 若存在,只是缓存的声明式配置 + 资源,对错仍不能离线判(除非以后做本地 parser——默认不做)。
Phase 1 决议仍然有效:**定义权威在 Server,不下发 App 商店。**
资源层与定义层分开。Phase 2 已落地的业务路径(经 SDCard_Map_Business_Path 接到 MOUNT_POINT):
/{app_id}/{task_id}/lessons/{lesson_id}/file.mp3
例:
/english_learning/repeater/lessons/unit01/full.mp3
/english_learning/shadowing/lessons/unit01/01.mp3
app_id 是 App 唯一名。用户订阅新 App 后由程序 mkdir(MCP self.sd.ensure_app / MQTT ENSURE_APP_DIRS),**不重烧固件**。这是运输暴露已有 mkdir,不是新传感器。
长期仍可增加缓存目录(Phase 6),不必再搬家上面这棵树:
/sdcard/
{app_id}/{task_id}/lessons/ # 音频(Phase 2 事实)
cache/
recordings/pending/ # 仅 v1.1
apps/{app_id}/ # 可选:manifest 缓存(Phase 6);设备仍只执行 skills[]
设备 不知道「这是英语 App」。它只知道:
resource_id → 文件
play_audio(resource_id)
Sening 现有 /data/courses/... 允许映射,不强迫一次搬家。
v1 热路径:Mic → Opus 流 → Server ASR → 可选 WAV BLOB(现有 report)
v1 跟读:同一条听流;禁止为跟读再开第二套编解码
v1.1 文件:SD pending → 联网 upload → audio_ref
热路径禁止改:16k / 60ms Opus。record_clip 不得换一套 AAC 引擎来「优化会议」。
user.speech.stopped这是所有封闭式语音评估的共同前提。
Task JSON 只出现 speak(text) 或 play_audio(resource)。
合成落点(云 / 服务端 / 未来本地)由 Runtime 选。固件禁止写死「跟读必须云 TTS」。
Timer Device wait(ms) 播放与听之间的间隙
Loop Device:同一 play_audio N 次
Server:不同 Step 重复
Branch 仅 Server:if assessment.pass then next else retry
Device 只收到 PLAY 或 SPEAK 或 STOP
Branch 进入固件 = 口算判题进 C++。绝对禁止。
目标形态(Phase 6 落地,现在只冻结字段方向):
app_id
name
default_task
tasks[]:
task_id
interaction_mode
skills[] / steps[] # steps 在 Server 执行
resources[] # 要下载的音频
固件 不解析业务规则。最多:离线时读 cached task 的 skills: [play_audio] 以便复读。
冻结方向:
Task
Step: speak | play_audio | wait | listen | stop
on listen.completed → Server ASR → (可选) Assessment → next Step
设备看到的永远是 单步 Capability,不是「如果孩子答错就降难度」。
成语接龙 / 口算 / 倒背 的差异只在 Server 的 Parser 与 Prompt,不在固件。
一次(或少数几次)OTA 补齐后应长期稳定:
Codec / AEC / 唤醒 / MultiNet 宿主(词表可配,词义不写业务)
WS + 色宁 MQTT 栈
MCP / Skill 宿主
SD 文件系统 + 下载
音频互斥(TTS vs play_audio)
play_audio + loop + stop + 完成事件
listen 流
barge_in 与 Mode 策略表
button 事件
indicate 映射
wait
task_control 抢占
软关机 / RTC 闹钟执行 pending(已有)
ASR / LLM / 翻译算法 / 评分 / 口算解析 / 成语词库
情绪与临床判断
if (app_id == "english") { ... } 业务树
为单个 App 增加 GPIO 协议
把 QUIZ 做成新 DeviceState
把 apps.json 编译进固件当唯一词表
固件里出现 english_learning 字符串仅允许作为 Skill 查找表键(Phase 1 已有),不允许在旁边写音频逻辑。长期应用 task.skills[] 缓存取代硬编码表。
不是因为这 5 个最赚钱,而是 覆盖率最高。产品化顺序仍可先复读/跟读;倒背/口算/接龙用于 验证 Runtime 是否真冻结,不必先做漂亮 UI。
| 代表 Task | 验证的 Device 能力 | 验证的 Server 能力 | 现状 |
|---|---|---|---|
| 英语复读机 | play_audio, loop, stop, 完成事件, resource, 离线 | Task 启动、优先级 | Phase 2:播放+loop+完成事件已有 |
| 英语跟读 | play/speak, wait, listen, VAD, barge_in 策略, 音频归档 | ASR(旁路), 后评估定量, Session | Phase 2 采集已有;Phase 4:时钟步进 + txt 契约,比对不挡播放 |
| 数字倒背 | speak, listen, VAD, wait | ASR, Sequence parser, 流内 score + 及时 speak | 未做(口令槽已预留;Phase 4b inline) |
| 10 以内口算 | speak, listen, wait, timer | 出题, parser, 默认流内反馈 | 未做 |
| 成语接龙 | speak, listen, barge_in(CHAT 策略) | ASR, LLM/词库, 流内 合法性与下一提示 | 未做 |
这 5 个做完(哪怕后三个只做 Server 编排 + 现有 listen/speak),Device 侧不应再出现新传感器类能力。
其后:
故事复述 / AI 主持人 / 回忆录 / 心理陪伴 / 会议纪要 / 律师询问
只增加 Task Definition、Prompt、Memory、Assessment、(v1.1)长录音上传。
心理陪伴:**长期行为信号**(响应时延、完成率)来自 Event 时间戳,不是新硬件;**禁止**当临床诊断。
图例:D = 需要已冻结的 Device 能力;S = Server;空 = 不需要。
| App/Task | 播 | TTS | VAD | 听/录 | ASR | LLM | 评估 | wait/loop | 动态下一步 |
|---|---|---|---|---|---|---|---|---|---|
| 纯读 / 复读 | D | D | D | ||||||
| 英语跟读 | D | D | D | D | S | S | D | S | |
| 中英翻译 | D | D | D | S | S* | ||||
| 中英对话 | D | D | D | S | S | S | |||
| 成语接龙 | D | D | D | S | S | S | S | ||
| 口算 | D | D | D | S | 可选 | S | D | S | |
| 数字倒背 | D | D | D | S | S | D | S | ||
| 快问快答 | D | D | D | S | S | S | D | S | |
| 故事复述 | D | D | D | D | S | S | S | S | |
| AI 主持人 | D | D | D | S | S | S | S | ||
| 回忆录 | D | D | D | S | S | S | S | ||
| 会议纪要 | D† | S | S | S | |||||
| 心理陪伴 | D | D | D | S | S | S‡ | S |
* 翻译可用专用模型,仍是 Server。
† 长录音偏 v1.1 record_clip。
‡ 只做参与度/行为信号,不做诊断。
硬件列只有:播、TTS 出声、VAD、听/录、wait/loop、按钮/指示。 其余全是 Server。
必须在「宣称 Device 已冻结」之前补齐的固件/协议语义:
audio.completed / audio.stopped(逻辑事件;运输可用 [skill_event] 或 MCP) ✅ Phase 2audio_id 挂 stepwait 是否一等能力:可先 Server 侧延时,但不要再为每个 App 复制task pause/resume 语义(可后于 1–3)play_audio 资源 ID 统一(可与 Phase 6 资源管理一起;音频路径继续用 /{app}/{task}/lessons)不需要为冻结而做: Assessment 算法、成语词库、口算出题器、type=task 全套、合并双 MQTT。
Java 表是 Task Runtime(Phase 3),不是 Device 冻结条件。
Phase 1 薄 App Runtime ✅
↓
本 Spec Device Capability Freeze ✅ 文档
↓
Phase 2 用跟读/复读验证 listen/VAD/事件 ✅ D1 / D6 / D7 v1 / D8 v1;见 phase-2-review.md
↓
Phase 3 TaskInstance 落库 ✅ 无新 Device 能力;见 phase-3-review.md
↓
Phase 4 时钟步进 + 后评估 ← 下一编码;见 phase-4-plan.md
↓
倒背 / 口算 / 接龙 (Phase 4b:流内评估 + 及时 speak;反证固件冻结)
Phase 2 的工程价值是:**把采集 + 完成事件钉进 Runtime。** 已完成。
Phase 3 的工程价值是:**Instance 可查询。** 已完成。
跟读后评估是 Phase 4;倒背/接龙的及时反馈是 Phase 4b,不能混成后评估。施工单:phase-4-plan.md、phase-4-test.md、roadmap.md。
当且仅当团队同意下面四句,才允许进入后续编码(Phase 3 起同样适用):