这篇文章基于自己实现时整理的可用的版本,非完整版小智接口,仅包含设备网关与设备管理的核心接口。目标是让做二次开发的人不必去读硬件代码,只要按本文实现接口即可完成设备联调。

1) 你需要实现的接口清单

  • REST:POST /device/checkPOST /device/activatePOST /device/check/activatePOST /device/bindings/bindPOST /device/bindings/unbind
  • WebSocket:/ws/device/(设备直连的网关通道,文本 + 二进制)

说明:设备侧只会调用这些接口;网关内部是否桥接 /ws/app 取决于你的架构,但协议与字段需保持一致。

2) 设备管理(REST)

2.1 核心状态与字段

  • statuspending | active
  • device_id:设备唯一标识(设备端实际通过请求头 Device-Id 传递)
  • client_id:设备/网关客户端标识(设备端实际通过请求头 Client-Id 传递)
  • activation_code:激活码,pending 时稳定不变
  • challenge:挑战码,每次 check 重新生成

2.2 推荐流程(两条路径)

路径 A:首次未绑定(pending)

  1. 设备启动 → POST /device/check
  2. status=pending,设备展示 activation_codechallenge
  3. 用户在控制台输入激活码并选择 botPOST /device/bindings/bind
  4. 设备再次 POST /device/check → 获取 websocket 信息
  5. 设备使用 websocket.url + websocket.token 连接 /ws/device/

说明:小智设备固件不会携带 bot_id,自助激活仅适用于自定义客户端。设备固件建议走绑定流程。

路径 B:已绑定/已激活(active)

  1. 设备启动 → POST /device/check
  2. status=active 且返回 websocket,直接连接 /ws/device/
  3. 后续按 WebSocket 协议交互

2.3 POST /device/check

设备开机/心跳接口(无需登录)。设备端会把 ota_url 指向这个接口(建议以 /device/check 作为基地址)。

设备端请求头(固定携带):

  • Device-Id
  • Client-Id
  • Serial-Number(有则携带)
  • Activation-Version(有序列号为 2,无序列号为 1
  • User-AgentAccept-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 状态会包含 activationactive 状态会包含 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_codepending 状态下稳定不变;默认 6 位数字(若已有 bot_id,会用 bot_id 作为激活码)。
  • challenge:每次 check 都会刷新。
  • serial_number 当前仅透传保留,后端未做业务校验。

2.4 POST /device/activatePOST /device/check/activate

设备侧激活接口(无需登录),用于 pendingactive。设备端请求头与 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 必须与最近一次 checkchallenge 匹配,否则返回激活失败。
  • 设备端不会传 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/(使用 wssws,以 websocket.url 为准)。

握手请求头(必须):

  • Device-Id
  • Client-Id
  • Authorization: Bearer <device_token>

可选:

  • Protocol-Version(建议填 websocket.version,当前不做强校验)

校验逻辑:

  • device_token 为 JWT,需包含 device_id/client_id/user_id/bot_id
  • 绑定必须是 active,且 user_id/bot_id 必须存在
  • Device-IdClient-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"}

网关对设备的输出:

  • transcriptstt
{"type":"stt","scope":"partial","text":"你好"}
  • reply_texttts
{"type":"tts","state":"start"}
{"type":"tts","state":"sentence_start","text":"你好"}
  • reply_audio → 二进制 Opus 帧(同时触发 tts start,最后一帧触发 tts stop
  • reply_statefinished / canceled)→ tts stop
{"type":"tts","state":"stop"}
  • erroralert
{"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_textreply_audioreply_state),与上文一致。

5) 实现清单

  • 实现设备管理接口与激活/绑定流程
  • /ws/device/ 鉴权:Device-Id + Client-Id + Bearer token
  • hello/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事件到设备网关

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐