小智后端接口实现指南(含设备管理+网关)(仅可用,非完整版)
本文介绍了小智设备网关与设备管理的核心接口规范,旨在帮助开发者快速实现二次开发。主要内容包括:1)需实现的REST接口(设备检查、激活、绑定等)和WebSocket网关通道;2)设备管理流程,区分pending和active两种状态处理;3)WebSocket通信协议细节,包括连接鉴权、音频传输、状态管理等;4)关键实现要点,如协议转换、音频编解码、事件映射等。通过规范接口定义和交互流程,开发者无
这篇文章基于自己实现时整理的可用的版本,非完整版小智接口,仅包含设备网关与设备管理的核心接口。目标是让做二次开发的人不必去读硬件代码,只要按本文实现接口即可完成设备联调。
1) 你需要实现的接口清单
- REST:
POST /device/check、POST /device/activate、POST /device/check/activate、POST /device/bindings/bind、POST /device/bindings/unbind - WebSocket:
/ws/device/(设备直连的网关通道,文本 + 二进制)
说明:设备侧只会调用这些接口;网关内部是否桥接 /ws/app 取决于你的架构,但协议与字段需保持一致。
2) 设备管理(REST)
2.1 核心状态与字段
status:pending|activedevice_id:设备唯一标识(设备端实际通过请求头Device-Id传递)client_id:设备/网关客户端标识(设备端实际通过请求头Client-Id传递)activation_code:激活码,pending时稳定不变challenge:挑战码,每次check重新生成
2.2 推荐流程(两条路径)
路径 A:首次未绑定(pending)
- 设备启动 →
POST /device/check - 若
status=pending,设备展示activation_code与challenge - 用户在控制台输入激活码并选择
bot→POST /device/bindings/bind - 设备再次
POST /device/check→ 获取websocket信息 - 设备使用
websocket.url+websocket.token连接/ws/device/
说明:小智设备固件不会携带 bot_id,自助激活仅适用于自定义客户端。设备固件建议走绑定流程。
路径 B:已绑定/已激活(active)
- 设备启动 →
POST /device/check - 若
status=active且返回websocket,直接连接/ws/device/ - 后续按 WebSocket 协议交互
2.3 POST /device/check
设备开机/心跳接口(无需登录)。设备端会把 ota_url 指向这个接口(建议以 /device/check 作为基地址)。
设备端请求头(固定携带):
Device-IdClient-IdSerial-Number(有则携带)Activation-Version(有序列号为2,无序列号为1)User-Agent、Accept-Language(用于日志或展示,可选)
设备端请求体为“系统信息 JSON”(board.GetSystemInfoJson()),后端可选择忽略:
{
"version": 2,
"mac_address": "00:11:22:33:44:55",
"uuid": "client-uuid",
"application": {
"name": "xiaozhi",
"version": "1.0.0"
}
}
说明:设备端实际不会在请求体内传 device_id/client_id,后端必须以请求头为准。
响应(pending 状态会包含 activation,active 状态会包含 websocket):
{
"binding": {
"device_id": "dev-001",
"client_id": "client-001",
"user_id": 1,
"bot_id": null,
"status": "pending",
"activation_code": "123456",
"challenge": "a1b2c3d4e5f6...",
"last_seen_at": "2025-12-08T10:00:00Z"
},
"activation": {
"message": "请在设备上完成激活",
"code": "123456",
"challenge": "a1b2c3d4e5f6...",
"timeout_ms": 300000
},
"websocket": null
}
active 时:
{
"binding": {
"device_id": "dev-001",
"client_id": "client-001",
"user_id": 1,
"bot_id": 1001,
"status": "active",
"activation_code": "123456",
"challenge": "a1b2c3d4e5f6...",
"last_seen_at": "2025-12-08T10:00:00Z"
},
"activation": null,
"websocket": {
"url": "wss://host/ws/device/",
"token": "device-jwt-token",
"version": 1
}
}
说明:
activation_code:pending状态下稳定不变;默认 6 位数字(若已有bot_id,会用bot_id作为激活码)。challenge:每次check都会刷新。serial_number当前仅透传保留,后端未做业务校验。
2.4 POST /device/activate 与 POST /device/check/activate
设备侧激活接口(无需登录),用于 pending → active。设备端请求头与 check 一致(Device-Id/Client-Id/Serial-Number)。
设备端实际请求体(小智固件):
{
"algorithm": "hmac-sha256",
"serial_number": "SN-001",
"challenge": "a1b2c3d4e5f6...",
"hmac": "hex-string"
}
若设备无序列号(Activation-Version=1),请求体可能是 {}。后端需要容忍空 payload。
响应:
{
"bot_id": 1001,
"status": "active",
"device_id": "dev-001"
}
设备端只根据 HTTP 状态码判断结果:200 视为成功,202 视为等待/重试,其它状态视为失败。
校验要点:
challenge必须与最近一次check的challenge匹配,否则返回激活失败。- 设备端不会传
bot_id,若需要绑定到具体机器人,请通过/device/bindings/bind完成。
2.5 POST /device/bindings/bind
用户侧绑定接口(需登录 Bearer Token),把激活码绑定到用户与 bot。
请求体:
{
"activation_code": "123456",
"bot_id": 1001,
"challenge": "a1b2c3d4e5f6..."
}
响应:
{
"binding": {
"device_id": "dev-001",
"client_id": "client-001",
"user_id": 1,
"bot_id": 1001,
"status": "active",
"activation_code": "123456",
"challenge": "a1b2c3d4e5f6...",
"last_seen_at": "2025-12-08T10:00:00Z"
},
"activation": null,
"websocket": {
"url": "wss://host/ws/device/",
"token": "device-jwt-token",
"version": 1
}
}
失败场景(示例):
- 激活码无效:
NOT_FOUND+DEVICE.NOT_FOUND - challenge 不匹配:
BAD_REQUEST+DEVICE.ACTIVATION_FAILED - 绑定冲突:
CONFLICT+DEVICE.BINDING_CONFLICT
2.6 POST /device/bindings/unbind
用户主动解绑(需登录 Bearer Token)。
请求体:
{
"bot_id": 1001
}
响应:
{
"bot_id": 1001,
"device_id": "dev-001",
"status": "pending"
}
2.7 错误响应格式
/device/* 成功时返回的是业务 JSON;失败时统一为:
{
"code": "BAD_REQUEST",
"message": "设备激活失败",
"data": null,
"requestId": "req-xxx",
"details": {
"error": "DEVICE.ACTIVATION_FAILED"
}
}
3) 设备网关 WebSocket(/ws/device/)
3.1 连接与鉴权
设备连接路径:/ws/device/(使用 wss 或 ws,以 websocket.url 为准)。
握手请求头(必须):
Device-IdClient-IdAuthorization: Bearer <device_token>
可选:
Protocol-Version(建议填websocket.version,当前不做强校验)
校验逻辑:
device_token为 JWT,需包含device_id/client_id/user_id/bot_id- 绑定必须是
active,且user_id/bot_id必须存在 Device-Id、Client-Id与 token 需一致
鉴权失败时,网关先发 alert 再断开:
{"type":"alert","status":"DEVICE_INACTIVE","message":"Device not active"}
3.2 hello 交互
设备发送:
{"type":"hello"}
网关返回:
{
"type": "hello",
"transport": "websocket",
"audio_params": {
"sample_rate": 16000,
"frame_duration": 60
},
"session_id": "ws-session-id"
}
session_id 为网关会话号(便于日志追踪),不是 /ws/app 的会话号。
3.3 上行音频(设备 → 网关 → 语音后端)
设备直接发送 二进制 Opus 帧(raw Opus,无自定义头)。
网关将其解码为 PCM16,并转发给语音后端。
网关发给内部语音后端的格式(如 /ws/app):
{
"type": "audio_chunk",
"ts": "2025-12-08T10:00:00+08:00",
"payload": {
"seq": 1,
"last": false,
"audio": {
"codec": "pcm16",
"encoding": "base64",
"data": "<base64 pcm>",
"sampleRate": 16000
}
}
}
3.4 listen(开始/结束)
设备发送:
{"type":"listen","state":"start"}
{"type":"listen","state":"stop"}
网关转发 listen 给语音后端,并在 stop 时额外发送一条 last=true 的空音频块,表示一句话结束:
{
"type": "audio_chunk",
"ts": "2025-12-08T10:00:00+08:00",
"payload": {
"seq": 2,
"last": true,
"audio": {
"codec": "pcm16",
"encoding": "base64",
"data": "",
"sampleRate": 16000
}
}
}
3.5 abort(打断)
设备发送:
{"type":"abort"}
网关会:
- 向语音后端发送
interrupt_reply(不带reply_token) - 向设备发送
tts stop
3.6 下行事件(语音后端 → 网关 → 设备)
语音后端事件(/ws/app → 网关)示例:
{"type":"transcript","scope":"partial","text":"你好"}
{"type":"reply_text","text":"你好","sentence_index":0,"reply_token":"r-1"}
{"type":"reply_audio","codec":"opus","data":"base64...","seq":1,"last":false,"reply_token":"r-1"}
{"type":"reply_state","state":"finished","reply_token":"r-1"}
{"type":"error","scope":"auth","code":"INVALID_TOKEN","message":"invalid"}
网关对设备的输出:
transcript→stt
{"type":"stt","scope":"partial","text":"你好"}
reply_text→tts
{"type":"tts","state":"start"}
{"type":"tts","state":"sentence_start","text":"你好"}
reply_audio→ 二进制 Opus 帧(同时触发tts start,最后一帧触发tts stop)reply_state(finished/canceled)→tts stop
{"type":"tts","state":"stop"}
error→alert
{"type":"alert","status":"INVALID_TOKEN","message":"invalid"}
注意点:
reply_token是触发tts start/stop的关键字段,务必确保reply_text/reply_audio/reply_state都携带。reply_text只有在同时具备sentence_index + text时才会发sentence_start。
3.7 编解码与协议版本
- 当前实现仅支持 raw Opus(v1),不解析自定义头。
- 设备侧应按
frame_duration(默认 60ms)切帧发送。 - 若需 v2/v3 自定义头,需自行改造网关解析逻辑。
4) (可选)网关对接 /ws/app 的内部格式
如果你沿用“网关 → /ws/app”的分层,网关发送的是 AppInboundEnvelope:
{
"type": "hello",
"ts": "2025-12-08T10:00:00+08:00",
"payload": {
"token": "device-jwt-token",
"botId": 1001,
"clientId": "client-001",
"audio": {
"codec": "pcm16",
"sampleRate": 16000,
"channels": 1,
"frameMs": 60
}
}
}
audio_chunk/listen/interrupt_reply 同理放在 payload。/ws/app → 网关的事件为 snake_case(如 reply_text、reply_audio、reply_state),与上文一致。
5) 实现清单
- 实现设备管理接口与激活/绑定流程
/ws/device/鉴权:Device-Id + Client-Id + Bearer tokenhello/listen/abort文本消息解析- 二进制 Opus 上行、PCM16 下行转换
reply_text/reply_audio/reply_state/transcript/error事件映射reply_token维度的 TTS 状态管理- 关键日志记录(device_id、client_id、session_id)
6) 总结
设备网关本质上是“协议桥 + 音频编解码器”。只要严格实现本文列出的 REST 接口与 WebSocket 事件,就能在不读硬件代码的前提下完成设备联调。
7)口述版websocket 流程
设备携带Device-Id、Client-Id、Protocol-Version、Authorization: Bearer向网关发起连接,连接通过之后,设备立刻发送一条 hello 事件,然后设备网关携带sample_rate/frame_duration/session_id回复hello 时间,接着设备自动进入监听状态,并在确实收到音频时直接发送Opus 音频帧,后端识别出文本后,设备网关将识别结果实时发送stt事件传输到设备,等用户这轮说话停止时设备会发送{"type":"listen","state":"stop"}事件,此时 实时ASR 也会进行清理 stream,接着后端处理生成文字回复时同时进行两个操作: 1.将文本消息转语音,2.设备网关携带文本发送tts事件到设备(文本传输分为三个阶段:第一阶段是刚开始时携带上start状态,只告诉设备“开始播报/进入说话状态”,不带文本;第二阶段是过程中携带sentence_start状态,带 text,用于屏幕显示;结束时携带stop状态,告诉设备“播报结束”,不带文本),同时有TTS 实时生成并在设备网关Opus TTS 音频流,按帧定时下发到设备,如果在设备播放音频时用户又对着设备说话了,设备会主动发送abort事件到设备网关
更多推荐



所有评论(0)