编辑 | blame | 历史 | 原始文档

Xiaozhi Device Capability Specification v1.0

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:能力覆盖矩阵。


0. 冻结原则

新增 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。


1. Device 原子能力清单

1.1 必须冻结(Device Interaction 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 执行器

1.2 允许在 Device 执行、但参数来自配置的控制结构

ID 能力 范围
D15 loop(播放级) play_audio(loop=N),完成条件 = 播完 N 次。Phase 1 已有 repeat
D16 loop(步骤级) Server Task Runtime 重复下发 Step;设备不解释「再做一题」

1.3 禁止进入 Device 的能力(Server 原子能力)

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)

2. 每个能力的输入 / 输出

下列为 语义契约。MCP 名、MQTT action、未来 type=command 都只是运输。改运输不算引进新能力。

D1 play_audio

in:
  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 未作为独立事件。

D2 播放控制

audio.pause | audio.resume | audio.stop

当前:self.mp3.stop、TTS 时 PauseSdCardMp3。pause/resume 对 SD MP3 已有「TTS 暂停」语义,需提升为可被 Task 调用,而不仅是 Chat 副作用。

D3 speak

in:  text, optional interruptible: bool
out: speak.started, speak.completed, speak.interrupted

当前:Server TTS → type=tts + Opus。设备没有独立「本地 TTS Skill」。**冻结:设备负责出声与 barge_in,不负责合成。** App/Task 只写 speak("请回答")。

D4 barge_in

policy (由 InteractionMode 配置,不是 App 名):
  CHAT / BILINGUAL     允许打断 TTS
  PLAY_AUDIO           默认不允许把喇叭当用户插话(复读)
  FOLLOW_READ/SHADOWING 播放范读时忽略拾音;范读结束后才 listen
  TRANSLATION          播译文时忽略回声

当前:Server 已按 mode 限制 AEC 打断。这是 Mode 策略,必须冻结进 Runtime,禁止每个 App 自己 if。

D5 play_system_sound

in:  type = ding | beep | success | error | ...
out: sound.completed

当前:AudioService::PlaySound OGG。需编成稳定 type,而不是各板路径字符串。

D6 listen / D7 vad

listen.start(mode)
listen.stop
vad → user.speech.started | user.speech.stopped

当前:上行 Opus + Server VAD;设备端也有 AFE/VAD 用于唤醒。
冻结 v1: 跟读/口算继续用现有听流 + Server VAD 事件即可,不强制设备再做一套文件录音。
必须补的事件: Server(或设备)在 Mode≠CHAT 时也要能区分「用户说完一句」,否则封闭式评估无法切 Step。

D8 record_clip

分两档,避免把冻结卡死在「设备写 WAV」:

档 行为 覆盖
v1 listen 流 + Server report 归档(已有 chat audio BLOB) 跟读、口算、倒背、接龙
v1.1 save_local + upload + record.completed(audio_ref) 弱网、会议纪要、律师询问长录音

v1.1 不阻塞 用 5 个代表性 Task 冻结硬件。会议纪要类只增加「长录音上传」,不增加新传感器。

D9 button

short → toggle listen / 确认
long  → 退出当前 Task 回 chat(可配)
double → 预留 pause

当前:BOOT 键绑 ToggleChatState。冻结时要允许「退出 Task」而不拆掉 Chat 键。

D10 indicate

in: IDLE | LISTENING | PROCESSING | PLAYING | ERROR | TASK_RUNNING | TASK_COMPLETE

映射到 LED/屏是板级实现。App 只发逻辑态。当前 DeviceState 是硬件占用态,**不要**把 TASK_RUNNING 塞进 Listening/Speaking 迁移表。indicate 与 DeviceState 正交。

D11 wait

in:  ms, cancellable
out: wait.elapsed | wait.cancelled

当前:**无**一等 Skill。复读循环把等待藏在播放器内部。冻结后「播放 → 等待 800ms → listen」不得靠 Server 连发两次再自己 sleep 充数(可先 Server 睡,但协议要留 wait,避免每个 App 各写各的)。

D12 volume / D13 resource.fetch

当前:MCP 音量、snlp DOWNLOAD。冻结为资源到达 SD 后 play_audio 只认本地 resource_id/sd_path。

D14 task_control

TASK_START / PAUSE / RESUME / STOP / RESTART

当前:START_APP_TASK、STOP_MEDIA、优先级抢占。没有统一 PAUSE/RESUME Task(只有 MP3 被 TTS 暂停)。
PAUSE 语义:停播放与拾音,保留 pending 与 loop 剩余次数。STOP:清执行器,pending 回 chat.conversation。

D15 vs D16 Loop

同一音频听 10 遍     → Device play_audio.loop=10     (已验证,保持)
10 道不同口算        → Server 循环 Step,每步 speak+listen
跟读 10 句不同音频   → Server 循环 Step(或 Task JSON 列表)

禁止 Server 对同一文件连发 10 次 play(Phase 1 已否决)。


3. Client Runtime API(逻辑,不是立刻改函数名)

设备执行器只暴露:

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.*。


4. Server Command(逻辑)

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 追加命令种类。


5. Device Event(逻辑)

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 产生(设备未报也不算缺传感器)。


6. 状态机(两套,禁止合并)

6.1 DeviceState(已有,冻结迁移表)

Idle ↔ Connecting ↔ Listening ↔ Speaking
+ Upgrading / WifiConfiguring / ...

只回答:麦和喇叭现在归谁。Task 不得新增 kDeviceStateEnglish。

6.2 InteractionMode(Server 已有字符串,固件可不枚举)

CHAT
PLAY_AUDIO
LISTEN            # 只听,不自动 chat(跟读的听半拍;口算听答案)
FOLLOW_READ       # 先播后听;与今日 SHADOWING 对齐,建议最终统一命名
TRANSLATION
BILINGUAL         # 仍走 LLM,但是 Mode 策略(打断/提示词)不同

QUIZ/GAME 不是新 Mode。口算/倒背/接龙 = speak + listen + Server 评估,Mode 用 LISTEN 或 CHAT 策略开关 skip_llm=true。

6.3 Task Executor(设备缓存)

IDLE → EXECUTING → WAITING_USER → COMPLETED | FAILED | CANCELLED
PAUSE 可从 EXECUTING/WAITING_USER 进入,RESUME 返回

权威状态在 Server;设备缓存仅用于断线 UI 与离线复读。


7. 中断 / 暂停 / 恢复

来源 行为
用户 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)。设备只暂停播放/拾音。


8. 离线执行

分类冻结(Phase 1 已用事实证明):

类型 没网 例子
可离线 Skill 设备直接执行 复读 play_audio + loop
仅在线 Mode 禁止 WakeWordInvoke 干等 翻译、跟读评估、接龙、口算
入口口令 MultiNet 只保留总入口 + 可离线 Skill 系统词表按 device.locale 配置(Phase 6b);默认中文「切换模式」「复读机」。**禁止**把跟读/翻译/语言对口令塞进固件

禁止把所有 App 口令塞进 MultiNet。
禁止把 apps.json 当作离线权威业务引擎;SD 上的 JSON 若存在,只是缓存的声明式配置 + 资源,对错仍不能离线判(除非以后做本地 parser——默认不做)。


9. SD Card Resource

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/... 允许映射,不强迫一次搬家。


10. 音频录制与上传

v1  热路径:Mic → Opus 流 → Server ASR → 可选 WAV BLOB(现有 report)
v1  跟读:同一条听流;禁止为跟读再开第二套编解码
v1.1 文件:SD pending → 联网 upload → audio_ref

热路径禁止改:16k / 60ms Opus。record_clip 不得换一套 AAC 引擎来「优化会议」。


11. VAD

  • 唤醒 / 命令词:设备 AFE(已有)
  • 句边界(跟读、口算):Server VAD 可用;设备可后补 user.speech.stopped
  • Mode 策略:范读/复读播放中 不得 把喇叭当 VAD 命中

这是所有封闭式语音评估的共同前提。


12. TTS

Task JSON 只出现 speak(text) 或 play_audio(resource)。
合成落点(云 / 服务端 / 未来本地)由 Runtime 选。固件禁止写死「跟读必须云 TTS」。


13. Timer / Loop / Branch

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++。绝对禁止。


14. App Manifest(声明,不是固件)

目标形态(Phase 6 落地,现在只冻结字段方向):

app_id
name
default_task
tasks[]:
  task_id
  interaction_mode
  skills[] / steps[]     # steps 在 Server 执行
  resources[]            # 要下载的音频

固件 不解析业务规则。最多:离线时读 cached task 的 skills: [play_audio] 以便复读。


15. Task DSL(Server,设备不解释完整 DSL)

冻结方向:

Task
  Step: speak | play_audio | wait | listen | stop
  on listen.completed → Server ASR → (可选) Assessment → next Step

设备看到的永远是 单步 Capability,不是「如果孩子答错就降难度」。

成语接龙 / 口算 / 倒背 的差异只在 Server 的 Parser 与 Prompt,不在固件。


16. 哪些能力必须 Firmware 固化

一次(或少数几次)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(已有)

17. 哪些能力绝对不能进入 Firmware

ASR / LLM / 翻译算法 / 评分 / 口算解析 / 成语词库
情绪与临床判断
if (app_id == "english") { ... } 业务树
为单个 App 增加 GPIO 协议
把 QUIZ 做成新 DeviceState
把 apps.json 编译进固件当唯一词表

固件里出现 english_learning 字符串仅允许作为 Skill 查找表键(Phase 1 已有),不允许在旁边写音频逻辑。长期应用 task.skills[] 缓存取代硬编码表。


18. 用 5 个代表性 App/Task 做能力覆盖验证

不是因为这 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 时间戳,不是新硬件;**禁止**当临床诊断。


19. 能力覆盖矩阵(归纳结果)

图例: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。


20. 与 Phase 1 代码的差距(冻结清单,不是现在就写完)

必须在「宣称 Device 已冻结」之前补齐的固件/协议语义:

  1. audio.completed / audio.stopped(逻辑事件;运输可用 [skill_event] 或 MCP) ✅ Phase 2
  2. Mode≠CHAT 时可靠的「一句说完」(VAD/listen.completed) ✅ Phase 2 v1(Server VAD + ASR + 听窗上限)
  3. 跟读/评估路径的音频归档(现有 report 即可) ✅ Phase 2 文本 + report;Phase 3 可选 audio_id 挂 step
  4. wait 是否一等能力:可先 Server 侧延时,但不要再为每个 App 复制
  5. task pause/resume 语义(可后于 1–3)
  6. play_audio 资源 ID 统一(可与 Phase 6 资源管理一起;音频路径继续用 /{app}/{task}/lessons)

不需要为冻结而做: Assessment 算法、成语词库、口算出题器、type=task 全套、合并双 MQTT。
Java 表是 Task Runtime(Phase 3),不是 Device 冻结条件。


21. 对 Roadmap 的插入点

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。


22. 接受本 Spec 的标准

当且仅当团队同意下面四句,才允许进入后续编码(Phase 3 起同样适用):

  1. 新 App 不得要求新的传感器或新的 WS 消息种类;只许用本文件命令/事件表。
  2. 对错、出题、翻译、接龙词库只在 Server。
  3. 离线只保证「可离线 Skill」(播放),不保证智能 Task。
  4. SD 放资源;声明式 Task JSON 若缓存到 SD,也不等于固件懂英语。