1224 lines
38 KiB
Markdown
1224 lines
38 KiB
Markdown
# 数据采集终端—云端系统设备接口文档
|
||||
|
|
|
|||
|
|
> 文档版本:1.0
|
|||
|
|
> 设备接口版本:`/device/v2`
|
|||
|
|
> OpenAPI 契约版本:`0.68.0`
|
|||
|
|
> 本地联调地址:`http://localhost:58080`
|
|||
|
|
> 契约来源:`openapi/openapi.yaml`、实际 HTTP 路由及设备处理代码
|
|||
|
|
> 编制日期:2026-08-27
|
|||
|
|
|
|||
|
|
## 1. 文档范围
|
|||
|
|
|
|||
|
|
本文档面向终端固件、Windows 模拟设备和设备联调人员,说明终端直接调用云端的全部接口,包括:
|
|||
|
|
|
|||
|
|
- 设备启动、身份校验和配置同步;
|
|||
|
|
- 双向语音通话创建、入房确认、智能体启动状态、心跳和停止;
|
|||
|
|
- 设备遥测、错误和命令结果上报;
|
|||
|
|
- 云端命令拉取与执行回执;
|
|||
|
|
- 固件私有下载;
|
|||
|
|
- 通话终稿补传;
|
|||
|
|
- 双轨录音分片声明、对象存储直传、校验查询和本地删除回执。
|
|||
|
|
|
|||
|
|
本文档详细描述的是设备使用 HMAC 身份直接调用的 `/device/v2` 接口。家庭或运营人员使用的 `/api/v1/devices`、设备绑定、固件发布和灰度管理接口在第 15 章列出边界,但它们不由设备固件直接调用。火山 RTC/豆包回调也不属于设备接口。
|
|||
|
|
|
|||
|
|
## 2. 总体通信关系
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart LR
|
|||
|
|
D[终端设备] -->|HTTPS 控制请求 + HMAC| API[云端 API]
|
|||
|
|
API --> PG[(PostgreSQL)]
|
|||
|
|
API --> REDIS[(Redis)]
|
|||
|
|
API -->|可靠任务| W[Worker]
|
|||
|
|
W -->|StartVoiceChat / StopVoiceChat| V[火山语音服务]
|
|||
|
|
D <-->|RTC 实时音频| RTC[火山 RTC 房间]
|
|||
|
|
V -->|豆包智能体加入| RTC
|
|||
|
|
D -->|录音文件 PUT| OSS[对象存储]
|
|||
|
|
API -->|只签发短期地址| D
|
|||
|
|
W -->|流式校验录音| OSS
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
重要边界:
|
|||
|
|
|
|||
|
|
1. 设备的控制请求经过本项目 API;实时麦克风音频不经过本项目 API 和数据库。
|
|||
|
|
2. 设备和豆包智能体分别直接加入同一个火山 RTC 房间。
|
|||
|
|
3. 录音文件使用短期单对象地址直接上传对象存储,不经过业务 API 传输文件字节。
|
|||
|
|
4. RTC Token、上传地址和固件下载地址都是短期凭证,只允许保存在设备运行内存中。
|
|||
|
|
|
|||
|
|
## 3. 接口总览
|
|||
|
|
|
|||
|
|
| 序号 | 方法 | 路径 | 用途 | 幂等键 |
|
|||
|
|
|---:|---|---|---|---|
|
|||
|
|
| 1 | GET | `/device/v2/bootstrap` | 启动登记、版本与时钟检查 | 否 |
|
|||
|
|
| 2 | GET | `/device/v2/config` | 拉取期望配置和绑定信息 | 否,支持 ETag |
|
|||
|
|
| 3 | GET | `/device/v2/commands` | 拉取本设备待执行命令 | 否 |
|
|||
|
|
| 4 | POST | `/device/v2/commands/{id}/ack` | 回报命令接收、成功或失败 | 否,业务状态幂等 |
|
|||
|
|
| 5 | GET | `/device/v2/firmware-releases/{id}/download` | 获取固件私有短期下载地址 | 否 |
|
|||
|
|
| 6 | POST | `/device/v2/conversations` | 创建通话并取得 RTC 房间资料 | 是 |
|
|||
|
|
| 7 | POST | `/device/v2/conversations/{id}/activate` | 确认设备入房并启动豆包智能体 | 是 |
|
|||
|
|
| 8 | GET | `/device/v2/conversations/{id}` | 查询权威通话和智能体状态 | 否 |
|
|||
|
|
| 9 | POST | `/device/v2/conversations/{id}/heartbeat` | 通话心跳和租约续期 | 否,按序号去重 |
|
|||
|
|
| 10 | POST | `/device/v2/conversations/{id}/stop` | 停止通话和豆包任务 | 是 |
|
|||
|
|
| 11 | POST | `/device/v2/conversations/{id}/turns:batch` | 补传设备侧最终字幕 | 是 |
|
|||
|
|
| 12 | POST | `/device/v2/telemetry:batch` | 批量上报状态、错误和命令结果 | 是 |
|
|||
|
|
| 13 | POST | `/device/v2/conversations/{id}/upload-batches` | 声明录音分片并取得上传地址 | 是 |
|
|||
|
|
| 14 | POST | `/device/v2/upload-batches/{id}/complete` | 声明上传完成并触发校验 | 否,业务状态幂等 |
|
|||
|
|
| 15 | GET | `/device/v2/upload-batches/{id}` | 查询录音校验结果 | 否 |
|
|||
|
|
| 16 | POST | `/device/v2/media-segments/{id}/local-delete-ack` | 回报本地录音密文删除结果 | 否,成功回执幂等 |
|
|||
|
|
|
|||
|
|
## 4. 通用协议约定
|
|||
|
|
|
|||
|
|
### 4.1 传输与数据格式
|
|||
|
|
|
|||
|
|
- 生产环境必须使用 HTTPS;本机开发环境可使用 HTTP。
|
|||
|
|
- JSON 请求使用 `Content-Type: application/json; charset=utf-8`。
|
|||
|
|
- 时间统一使用 RFC3339 UTC,例如 `2026-08-27T06:30:00Z`。
|
|||
|
|
- UUID 使用带连字符的标准格式。
|
|||
|
|
- 未声明可为空的字段不要发送 `null`,直接省略。
|
|||
|
|
- 设备必须忽略响应中的未知非必填字段,以保持 `/device/v2` 向后兼容。
|
|||
|
|
- 请求对象声明 `additionalProperties: false` 时,不得发送未定义字段。
|
|||
|
|
|
|||
|
|
### 4.2 统一成功响应
|
|||
|
|
|
|||
|
|
除 304、固件下载重定向等特殊响应外,成功响应统一为:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6...",
|
|||
|
|
"data": {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`request_id` 是本次云端请求关联编号,设备日志应保存该值,但不得把密钥、签名、Token 或临时对象地址写入日志。
|
|||
|
|
|
|||
|
|
### 4.3 统一错误响应
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6...",
|
|||
|
|
"error": {
|
|||
|
|
"code": "VALIDATION_FAILED",
|
|||
|
|
"message": "请求字段不符合契约",
|
|||
|
|
"retryable": false,
|
|||
|
|
"field_errors": [
|
|||
|
|
{
|
|||
|
|
"field": "body.device_time",
|
|||
|
|
"message": "must be a valid RFC3339 timestamp"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"details": {}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
设备应优先使用 `error.code` 和 `retryable` 决定动作,不解析或匹配 `message` 文本。
|
|||
|
|
|
|||
|
|
## 5. 设备 HMAC 身份认证
|
|||
|
|
|
|||
|
|
### 5.1 必填请求头
|
|||
|
|
|
|||
|
|
所有 `/device/v2` 请求都必须包含:
|
|||
|
|
|
|||
|
|
| 请求头 | 格式 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `X-Device-Id` | UUID | 已登记的设备公开编号 |
|
|||
|
|
| `X-Device-Timestamp` | RFC3339 | 本次请求 UTC 时间;默认允许前后 5 分钟误差 |
|
|||
|
|
| `X-Device-Nonce` | 16~128 字符 | 每次 HTTP 尝试都必须重新生成的随机数 |
|
|||
|
|
| `X-Device-Content-SHA256` | 64 位小写十六进制 | 原始 HTTP 正文字节的 SHA-256 |
|
|||
|
|
| `X-Device-Signature` | Base64URL,无 `=` 填充 | HMAC-SHA256 签名结果 |
|
|||
|
|
|
|||
|
|
POST JSON 的摘要必须针对真正发送的原始 UTF-8 字节计算。GET 或空正文请求也必须计算空字节摘要:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 签名原文
|
|||
|
|
|
|||
|
|
使用单个换行符按以下顺序拼接,最后一行后不增加换行:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
UPPERCASE_HTTP_METHOD
|
|||
|
|
REQUEST_URI_WITH_EXACT_QUERY_STRING
|
|||
|
|
X-Device-Timestamp原值
|
|||
|
|
X-Device-Nonce原值
|
|||
|
|
X-Device-Content-SHA256原值
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
例如:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
GET
|
|||
|
|
/device/v2/bootstrap?firmware_version=1.2.0&device_time=2026-08-27T06%3A30%3A00Z
|
|||
|
|
2026-08-27T06:30:01Z
|
|||
|
|
62d4b688f77b4baaa87a38d315c2f3b1
|
|||
|
|
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
计算方法:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
signature = Base64URL_NoPadding(
|
|||
|
|
HMAC_SHA256(device_secret, canonical_string)
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
注意事项:
|
|||
|
|
|
|||
|
|
- 查询参数的顺序、百分号编码和大小写必须与实际请求 URI 完全一致。
|
|||
|
|
- `Nonce` 不是业务幂等键。业务请求重试时保留原 `Idempotency-Key` 和正文,但必须生成新 `Nonce`、时间戳和签名。
|
|||
|
|
- 不允许在设备镜像、日志或前端页面中明文输出设备密钥。
|
|||
|
|
- 云端 Redis 或设备身份数据库不可用时鉴权关闭失败并返回 503,不会绕过签名。
|
|||
|
|
|
|||
|
|
### 5.3 幂等键
|
|||
|
|
|
|||
|
|
需要幂等保护的接口必须包含:
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
Idempotency-Key: 3ca4e514-cbde-41fc-aa32-6f1ed460cb01
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
长度为 16~128 字符。规则如下:
|
|||
|
|
|
|||
|
|
1. 同一设备、同一接口、同一幂等键和相同正文:返回第一次的业务结果。
|
|||
|
|
2. 同一幂等键对应不同正文:返回 409 `IDEMPOTENCY_KEY_REUSED`。
|
|||
|
|
3. 网络超时且无法确认结果时:使用原幂等键、原正文重试,但重新生成 HMAC 请求头。
|
|||
|
|
4. 返回已有结果时,响应可能包含 `Idempotency-Replayed: true`。
|
|||
|
|
|
|||
|
|
## 6. 启动与配置接口
|
|||
|
|
|
|||
|
|
### 6.1 设备启动登记
|
|||
|
|
|
|||
|
|
`GET /device/v2/bootstrap`
|
|||
|
|
|
|||
|
|
用于设备开机、重连或固件升级后向云端上报运行版本,并取得服务器时间、设备状态、绑定、最低版本策略和期望配置版本。
|
|||
|
|
|
|||
|
|
查询参数:
|
|||
|
|
|
|||
|
|
| 参数 | 必填 | 类型/限制 | 说明 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `firmware_version` | 是 | string,1~80 | 当前固件版本 |
|
|||
|
|
| `device_time` | 是 | RFC3339 | 设备当前时间 |
|
|||
|
|
| `board_rev` | 否 | string,≤80 | 硬件板卡版本 |
|
|||
|
|
| `rtc_sdk_version` | 否 | string,≤80 | RTC SDK 版本 |
|
|||
|
|
| `codec` | 否 | string,≤40 | 当前首选音频编码 |
|
|||
|
|
| `sample_rate` | 否 | integer,≥1 | 当前采样率 |
|
|||
|
|
| `network` | 否 | string,≤80 | 网络类型或描述 |
|
|||
|
|
|
|||
|
|
成功响应示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6BOOTSTRAP",
|
|||
|
|
"data": {
|
|||
|
|
"server_time": "2026-08-27T06:30:01Z",
|
|||
|
|
"clock_skew_ms": 850,
|
|||
|
|
"device": {
|
|||
|
|
"device_id": "10000000-0000-0000-0000-000000000511",
|
|||
|
|
"status": "registered",
|
|||
|
|
"model": "simulated-terminal",
|
|||
|
|
"hardware_revision": "sim-rev-a",
|
|||
|
|
"minimum_security_version": "security-v1",
|
|||
|
|
"minimum_firmware_version": "0.1.0",
|
|||
|
|
"firmware_compatible": true,
|
|||
|
|
"config_revision": 3,
|
|||
|
|
"binding": {
|
|||
|
|
"bound": true,
|
|||
|
|
"household_id": "20000000-0000-0000-0000-000000000001",
|
|||
|
|
"subject_id": "30000000-0000-0000-0000-000000000001",
|
|||
|
|
"subject_name": "演示长者"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
设备处理:
|
|||
|
|
|
|||
|
|
- `status=registered` 且 `binding.bound=true` 才能进入正常业务。
|
|||
|
|
- `quarantined`:停止通话能力,只保留受控诊断或升级。
|
|||
|
|
- `revoked`:立即停止业务并进入重新登记/售后流程。
|
|||
|
|
- `firmware_compatible=false`:进入升级提示,不要创建新通话;云端仍会在创建通话时二次阻断。
|
|||
|
|
- 使用 `server_time` 和 `clock_skew_ms` 校正后续签名时间。
|
|||
|
|
|
|||
|
|
主要状态码:200、400、401、503。
|
|||
|
|
|
|||
|
|
### 6.2 获取设备配置
|
|||
|
|
|
|||
|
|
`GET /device/v2/config`
|
|||
|
|
|
|||
|
|
请求可携带上次响应的强 ETag:
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
If-None-Match: "v3"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
成功响应头:
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
ETag: "v4"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
成功响应示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6CONFIG",
|
|||
|
|
"data": {
|
|||
|
|
"device_id": "10000000-0000-0000-0000-000000000511",
|
|||
|
|
"revision": 4,
|
|||
|
|
"desired_config": {
|
|||
|
|
"volume": { "speaker": 60 },
|
|||
|
|
"audio": { "sample_rate": 16000 },
|
|||
|
|
"interaction": {},
|
|||
|
|
"upload": {},
|
|||
|
|
"rtc": {},
|
|||
|
|
"policy_refs": {}
|
|||
|
|
},
|
|||
|
|
"reported_revision": 3,
|
|||
|
|
"binding": {
|
|||
|
|
"bound": true,
|
|||
|
|
"household_id": "20000000-0000-0000-0000-000000000001",
|
|||
|
|
"subject_id": "30000000-0000-0000-0000-000000000001",
|
|||
|
|
"subject_name": "演示长者"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 配置未变化返回 304,无响应正文。
|
|||
|
|
- 设备只在完整校验新配置成功后切换本地生效版本。
|
|||
|
|
- 应保留上一次有效配置;503 时不能清空本地配置。
|
|||
|
|
- 应通过心跳或遥测上报实际应用的 `reported_config_revision`。
|
|||
|
|
|
|||
|
|
主要状态码:200、304、401、503。
|
|||
|
|
|
|||
|
|
## 7. 双向语音通话接口
|
|||
|
|
|
|||
|
|
### 7.1 创建通话并分配 RTC 房间
|
|||
|
|
|
|||
|
|
`POST /device/v2/conversations`
|
|||
|
|
|
|||
|
|
必须携带 `Idempotency-Key`。本接口只分配房间和短期凭证,不调用豆包 `StartVoiceChat`。
|
|||
|
|
|
|||
|
|
请求字段:
|
|||
|
|
|
|||
|
|
| 字段 | 必填 | 类型/限制 | 说明 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `device_time` | 是 | RFC3339 | 设备时间 |
|
|||
|
|
| `monotonic_ms` | 否 | int64,≥0 | 开机后单调时钟毫秒数 |
|
|||
|
|
| `capabilities.rtc` | 是 | `true` | 支持 RTC |
|
|||
|
|
| `capabilities.audio_tracks` | 是 | `near`/`far` 数组 | 支持的音频轨道,至少一项且唯一 |
|
|||
|
|
| `capabilities.upload_resume` | 是 | boolean | 是否支持断点/恢复上传 |
|
|||
|
|
| `capabilities.rtc_credential_renewal` | 否 | boolean | 明确为 true 才允许心跳续签 RTC 凭证 |
|
|||
|
|
| `network.type` | 是 | string,1~40 | 网络类型,如 wifi/ethernet/4g |
|
|||
|
|
| `network.rssi` | 否 | integer | 信号强度 |
|
|||
|
|
| `requested_goal` | 是 | 固定 `open_conversation` | 设备技术目标,不作为豆包提示词 |
|
|||
|
|
|
|||
|
|
请求示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"device_time": "2026-08-27T06:31:00Z",
|
|||
|
|
"monotonic_ms": 15820,
|
|||
|
|
"capabilities": {
|
|||
|
|
"rtc": true,
|
|||
|
|
"audio_tracks": ["near", "far"],
|
|||
|
|
"upload_resume": true,
|
|||
|
|
"rtc_credential_renewal": true
|
|||
|
|
},
|
|||
|
|
"network": {
|
|||
|
|
"type": "wifi",
|
|||
|
|
"rssi": -48
|
|||
|
|
},
|
|||
|
|
"requested_goal": "open_conversation"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
201 响应示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6CREATE",
|
|||
|
|
"data": {
|
|||
|
|
"conversation_id": "0829059e-4485-46b9-87ea-5a2fcefc6c35",
|
|||
|
|
"status": "ROOM_ISSUED",
|
|||
|
|
"rtc": {
|
|||
|
|
"app_id": "<rtc-app-id>",
|
|||
|
|
"room_id": "r_25a5f0e5825645d099540604e17ee15c",
|
|||
|
|
"user_id": "d_10000000000000000000000000000511",
|
|||
|
|
"agent_user_id": "a_0829059e448546b987ea5a2fcefc6c35",
|
|||
|
|
"token": "<short-lived-rtc-token>",
|
|||
|
|
"expires_at": "2026-08-27T06:41:00Z"
|
|||
|
|
},
|
|||
|
|
"privacy": {
|
|||
|
|
"retention_mode": "transcript_only",
|
|||
|
|
"record_near": false,
|
|||
|
|
"record_far": false,
|
|||
|
|
"upload_allowed": false
|
|||
|
|
},
|
|||
|
|
"session": {
|
|||
|
|
"engine_mode": "s2s_hybrid",
|
|||
|
|
"heartbeat_interval_sec": 10,
|
|||
|
|
"allocation_expires_at": "2026-08-27T06:33:00Z"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
设备处理:
|
|||
|
|
|
|||
|
|
1. Token 只保存在内存中,不写日志和磁盘。
|
|||
|
|
2. 在 `allocation_expires_at` 前使用 `rtc.room_id + rtc.user_id + rtc.token` 加入 RTC。
|
|||
|
|
3. 发布麦克风近端音频,并订阅 `agent_user_id` 的智能体音频。
|
|||
|
|
4. 成功入房后调用激活接口。
|
|||
|
|
5. `session.engine_mode` 仅供云端诊断,终端不得据此切换采集、编码、发布或播放逻辑。
|
|||
|
|
|
|||
|
|
主要状态码:
|
|||
|
|
|
|||
|
|
| HTTP | 错误码/情况 | 设备动作 |
|
|||
|
|
|---:|---|---|
|
|||
|
|
| 201 | 创建成功 | 加入 RTC 后激活 |
|
|||
|
|
| 403 | `CONSENT_REQUIRED` | 停止并等待授权 |
|
|||
|
|
| 409 | `DEVICE_NOT_BOUND` | 提示先绑定设备 |
|
|||
|
|
| 409 | `CONVERSATION_ALREADY_ACTIVE` | 查询或恢复已有通话 |
|
|||
|
|
| 409 | `IDEMPOTENCY_KEY_REUSED` | 更正调用逻辑,不能换正文重用同键 |
|
|||
|
|
| 422 | `AGENT_CONTEXT_PREPARATION_FAILED` | 不入房,提示背景资料准备失败 |
|
|||
|
|
| 426 | `FIRMWARE_UPDATE_REQUIRED` | 进入固件升级流程 |
|
|||
|
|
| 429 | `USAGE_QUOTA_EXCEEDED` | 停止自动重试并提示额度/并发限制 |
|
|||
|
|
| 503 | `RUNTIME_CONFIG_UNAVAILABLE` 等 | 按 `retryable` 退避重试 |
|
|||
|
|
|
|||
|
|
### 7.2 确认入房并启动智能体
|
|||
|
|
|
|||
|
|
`POST /device/v2/conversations/{id}/activate`
|
|||
|
|
|
|||
|
|
必须携带 `Idempotency-Key`。只有 RTC 加入成功后调用。
|
|||
|
|
|
|||
|
|
请求字段:
|
|||
|
|
|
|||
|
|
| 字段 | 必填 | 限制 | 说明 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `joined_at` | 是 | RFC3339 | 实际入房时间 |
|
|||
|
|
| `room_id` | 是 | 1~200 | 必须等于创建响应的房间号 |
|
|||
|
|
| `rtc_user_id` | 是 | 1~200 | 必须等于创建响应的设备 RTC 用户号 |
|
|||
|
|
| `rtc_join_result` | 是 | 固定 `success` | 只接受成功入房确认 |
|
|||
|
|
| `config_revision` | 是 | int64,≥1 | 本通话使用的配置版本 |
|
|||
|
|
| `connection_reference` | 否 | ≤255 | 设备侧连接关联号,不能包含凭证 |
|
|||
|
|
|
|||
|
|
请求示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"joined_at": "2026-08-27T06:31:03Z",
|
|||
|
|
"room_id": "r_25a5f0e5825645d099540604e17ee15c",
|
|||
|
|
"rtc_user_id": "d_10000000000000000000000000000511",
|
|||
|
|
"rtc_join_result": "success",
|
|||
|
|
"config_revision": 4,
|
|||
|
|
"connection_reference": "rtc-session-00001"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
202 响应示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6ACTIVATE",
|
|||
|
|
"data": {
|
|||
|
|
"conversation_id": "0829059e-4485-46b9-87ea-5a2fcefc6c35",
|
|||
|
|
"status": "STARTING_AGENT",
|
|||
|
|
"accepted_at": "2026-08-27T06:31:04Z"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
响应表示云端已经受理,不表示豆包一定已经进房。随后必须调用通话状态查询接口,不能不断更换幂等键重复激活。
|
|||
|
|
|
|||
|
|
主要状态码:202、400、401、404、409、503。
|
|||
|
|
|
|||
|
|
### 7.3 查询权威通话状态
|
|||
|
|
|
|||
|
|
`GET /device/v2/conversations/{id}`
|
|||
|
|
|
|||
|
|
200 响应示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6STATE",
|
|||
|
|
"data": {
|
|||
|
|
"conversation_id": "0829059e-4485-46b9-87ea-5a2fcefc6c35",
|
|||
|
|
"status": "ACTIVE",
|
|||
|
|
"action": "continue"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
字段说明:
|
|||
|
|
|
|||
|
|
| 字段 | 取值 | 设备动作 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `status` | `PREPARING`、`ROOM_ISSUED`、`STARTING_AGENT`、`ACTIVE`、`STOPPING`、`COMPLETED`、`FAILED`、`EXPIRED` | 云端权威状态 |
|
|||
|
|
| `action` | `wait` | 保持 RTC 房间并有限次数继续查询 |
|
|||
|
|
| `action` | `continue` | 豆包已激活,进入正常双向语音和心跳 |
|
|||
|
|
| `action` | `leave_room` | 立即退出 RTC 房间并结束本地通话流程 |
|
|||
|
|
| `failure_code` | 可选稳定错误码 | 仅用于展示和诊断,不显示供应商原始错误 |
|
|||
|
|
|
|||
|
|
失败示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6STATEFAILED",
|
|||
|
|
"data": {
|
|||
|
|
"conversation_id": "0829059e-4485-46b9-87ea-5a2fcefc6c35",
|
|||
|
|
"status": "FAILED",
|
|||
|
|
"action": "leave_room",
|
|||
|
|
"failure_code": "provider_start_rejected"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
推荐轮询:首次等待 500 ms,之后每 1 秒查询,累计不超过房间预留有效期;达到上限仍未收敛时按本地超时处理并调用停止接口。
|
|||
|
|
|
|||
|
|
主要状态码:200、400、401、404、503。
|
|||
|
|
|
|||
|
|
### 7.4 通话心跳
|
|||
|
|
|
|||
|
|
`POST /device/v2/conversations/{id}/heartbeat`
|
|||
|
|
|
|||
|
|
进入 `STARTING_AGENT` 或 `ACTIVE` 后,按创建响应的 `heartbeat_interval_sec` 调用。
|
|||
|
|
|
|||
|
|
请求主要字段:
|
|||
|
|
|
|||
|
|
| 字段 | 必填 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `sequence_no` | 是 | 从 1 开始严格递增;进程重启后应从耐久状态恢复 |
|
|||
|
|
| `device_time` | 是 | RFC3339 UTC |
|
|||
|
|
| `monotonic_ms` | 是 | 单调时钟毫秒数 |
|
|||
|
|
| `conversation_status` | 是 | 设备当前观察的通话状态 |
|
|||
|
|
| `rtc.joined/uplink/downlink` | 是 | RTC 入房、上行和下行状态 |
|
|||
|
|
| `network.type` | 是 | 当前网络类型 |
|
|||
|
|
| `network.rssi` | 否 | 信号强度 |
|
|||
|
|
| `firmware_version` | 是 | 当前固件版本 |
|
|||
|
|
| `reported_config_revision` | 是 | 已实际应用的配置版本 |
|
|||
|
|
| `recording_queue.pending_segments` | 是 | 待上传录音数 |
|
|||
|
|
| `recording_queue.failed_segments` | 是 | 上传失败录音数 |
|
|||
|
|
| `recording_queue.last_failure_code/at` | 否 | 最近稳定失败码和时间 |
|
|||
|
|
| `rtc_credential_expires_at` | 否 | 当前 RTC Token 到期时间;声明续签能力时使用 |
|
|||
|
|
| `last_error` | 否 | ≤80 的脱敏稳定错误码,不得传自由文本堆栈 |
|
|||
|
|
| `command_results` | 是 | 最多 20 项;当前可为空数组 |
|
|||
|
|
|
|||
|
|
请求示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"sequence_no": 1,
|
|||
|
|
"device_time": "2026-08-27T06:31:15Z",
|
|||
|
|
"monotonic_ms": 30820,
|
|||
|
|
"conversation_status": "ACTIVE",
|
|||
|
|
"rtc": {
|
|||
|
|
"joined": true,
|
|||
|
|
"uplink": true,
|
|||
|
|
"downlink": true
|
|||
|
|
},
|
|||
|
|
"network": {
|
|||
|
|
"type": "wifi",
|
|||
|
|
"rssi": -50
|
|||
|
|
},
|
|||
|
|
"firmware_version": "1.2.0",
|
|||
|
|
"reported_config_revision": 4,
|
|||
|
|
"recording_queue": {
|
|||
|
|
"pending_segments": 0,
|
|||
|
|
"failed_segments": 0
|
|||
|
|
},
|
|||
|
|
"rtc_credential_expires_at": "2026-08-27T06:41:00Z",
|
|||
|
|
"command_results": []
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
200 响应示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6HEARTBEAT",
|
|||
|
|
"data": {
|
|||
|
|
"lease_expires_at": "2026-08-27T06:32:05Z",
|
|||
|
|
"actions": [
|
|||
|
|
{ "type": "continue" }
|
|||
|
|
],
|
|||
|
|
"desired_config_revision": 4,
|
|||
|
|
"commands": []
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`actions[].type`:
|
|||
|
|
|
|||
|
|
- `continue`:继续通话;
|
|||
|
|
- `stop`:尽快停止并离房;
|
|||
|
|
- `stop_after_turn`:当前话轮完成后停止;
|
|||
|
|
- `renew_rtc_credential`:响应同一动作的 `rtc` 字段包含新 RTCGrant,设备先替换内存凭证,再在下一次心跳报告新到期时间。
|
|||
|
|
|
|||
|
|
同序号或更小序号不会续租,也不会覆盖较新的设备状态。报告配置版本高于云端期望版本会返回 409。
|
|||
|
|
|
|||
|
|
主要状态码:200、400、401、404、409、503。
|
|||
|
|
|
|||
|
|
### 7.5 停止通话
|
|||
|
|
|
|||
|
|
`POST /device/v2/conversations/{id}/stop`
|
|||
|
|
|
|||
|
|
必须携带 `Idempotency-Key`。
|
|||
|
|
|
|||
|
|
请求字段:
|
|||
|
|
|
|||
|
|
| 字段 | 必填 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `reason` | 是 | 1~64 字符稳定原因码,字符集 `[A-Za-z0-9_.:-]` |
|
|||
|
|
| `device_time` | 是 | RFC3339 |
|
|||
|
|
| `last_heartbeat_sequence` | 是 | 最后成功心跳序号,可为 0 |
|
|||
|
|
| `rtc_left` | 是 | 请求时是否已经退出 RTC |
|
|||
|
|
| `recording.near_segments` | 是 | 本地近端录音分片数量 |
|
|||
|
|
| `recording.far_segments` | 是 | 本地远端录音分片数量 |
|
|||
|
|
|
|||
|
|
请求示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"reason": "user_hangup",
|
|||
|
|
"device_time": "2026-08-27T06:35:00Z",
|
|||
|
|
"last_heartbeat_sequence": 23,
|
|||
|
|
"rtc_left": true,
|
|||
|
|
"recording": {
|
|||
|
|
"near_segments": 0,
|
|||
|
|
"far_segments": 0
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
202 响应示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6STOP",
|
|||
|
|
"data": {
|
|||
|
|
"conversation_id": "0829059e-4485-46b9-87ea-5a2fcefc6c35",
|
|||
|
|
"status": "STOPPING",
|
|||
|
|
"accepted_at": "2026-08-27T06:35:01Z"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
允许的响应状态为 `STOPPING`、`COMPLETED`、`FAILED` 或 `EXPIRED`。相同停止请求不会重复调用供应商 Stop。
|
|||
|
|
|
|||
|
|
主要状态码:202、400、401、404、409、503。
|
|||
|
|
|
|||
|
|
## 8. 设备遥测接口
|
|||
|
|
|
|||
|
|
### 8.1 批量上报遥测
|
|||
|
|
|
|||
|
|
`POST /device/v2/telemetry:batch`
|
|||
|
|
|
|||
|
|
必须携带 `Idempotency-Key`。每批 1~500 条,同一批内 `event_key` 和 `sequence_no` 分别唯一。
|
|||
|
|
|
|||
|
|
事件公共字段:
|
|||
|
|
|
|||
|
|
| 字段 | 必填 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `event_key` | 是 | 设备内稳定唯一键,1~255 字符 |
|
|||
|
|
| `sequence_no` | 是 | 设备内严格正整数单调序号 |
|
|||
|
|
| `occurred_at` | 是 | 事件发生时间 |
|
|||
|
|
| `event_type` | 是 | `status`、`error`、`command_result` |
|
|||
|
|
| `conversation_id` | 否 | 一旦填写,必须属于当前签名设备 |
|
|||
|
|
| `status/error/command_result` | 条件必填 | 只允许携带与 `event_type` 同名的一个对象 |
|
|||
|
|
|
|||
|
|
状态事件:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"event_key": "status.boot.000001",
|
|||
|
|
"sequence_no": 1,
|
|||
|
|
"occurred_at": "2026-08-27T06:30:00Z",
|
|||
|
|
"event_type": "status",
|
|||
|
|
"status": {
|
|||
|
|
"firmware_version": "1.2.0",
|
|||
|
|
"reported_config_revision": 4,
|
|||
|
|
"network": { "type": "wifi", "rssi": -48 },
|
|||
|
|
"rtc": { "joined": false, "uplink": false, "downlink": false },
|
|||
|
|
"recording_queue": { "pending_segments": 0, "failed_segments": 0 }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
错误事件:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"event_key": "error.audio.000002",
|
|||
|
|
"sequence_no": 2,
|
|||
|
|
"occurred_at": "2026-08-27T06:30:10Z",
|
|||
|
|
"event_type": "error",
|
|||
|
|
"error": {
|
|||
|
|
"code": "AUDIO_DEVICE_UNAVAILABLE",
|
|||
|
|
"severity": "error"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
命令结果事件:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"event_key": "command.9e70.result",
|
|||
|
|
"sequence_no": 3,
|
|||
|
|
"occurred_at": "2026-08-27T06:30:20Z",
|
|||
|
|
"event_type": "command_result",
|
|||
|
|
"command_result": {
|
|||
|
|
"command_id": "9e7006eb-6cd2-4e60-82ba-e24ba45158bd",
|
|||
|
|
"status": "SUCCEEDED",
|
|||
|
|
"result_summary": {
|
|||
|
|
"result_code": "OK"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
完整请求:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"events": [
|
|||
|
|
{
|
|||
|
|
"event_key": "status.boot.000001",
|
|||
|
|
"sequence_no": 1,
|
|||
|
|
"occurred_at": "2026-08-27T06:30:00Z",
|
|||
|
|
"event_type": "status",
|
|||
|
|
"status": {
|
|||
|
|
"firmware_version": "1.2.0",
|
|||
|
|
"reported_config_revision": 4,
|
|||
|
|
"network": { "type": "wifi", "rssi": -48 },
|
|||
|
|
"rtc": { "joined": false, "uplink": false, "downlink": false },
|
|||
|
|
"recording_queue": { "pending_segments": 0, "failed_segments": 0 }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
200 响应:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6TELEMETRY",
|
|||
|
|
"data": {
|
|||
|
|
"accepted_count": 1,
|
|||
|
|
"duplicate_count": 0,
|
|||
|
|
"desired_config_revision": 4,
|
|||
|
|
"actions": [
|
|||
|
|
{ "type": "pull_commands" }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
响应动作包括 `refresh_config`、`pull_commands` 和 `stop_conversation`。结果对象递归禁止出现 `secret`、`token`、`password`、`credential`、`private_key`、`api_key` 等敏感字段。
|
|||
|
|
|
|||
|
|
主要状态码:200、400、401、404、409、503。
|
|||
|
|
|
|||
|
|
## 9. 云端设备命令接口
|
|||
|
|
|
|||
|
|
### 9.1 拉取命令
|
|||
|
|
|
|||
|
|
`GET /device/v2/commands?limit=10`
|
|||
|
|
|
|||
|
|
`limit` 可选,范围 1~20,默认 10。只返回当前设备未过期、未终结的命令。
|
|||
|
|
|
|||
|
|
响应示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6COMMANDS",
|
|||
|
|
"data": {
|
|||
|
|
"items": [
|
|||
|
|
{
|
|||
|
|
"id": "9e7006eb-6cd2-4e60-82ba-e24ba45158bd",
|
|||
|
|
"type": "diagnostics",
|
|||
|
|
"payload": {
|
|||
|
|
"scope": ["audio", "network", "storage"]
|
|||
|
|
},
|
|||
|
|
"status": "DELIVERED",
|
|||
|
|
"expires_at": "2026-08-27T06:40:00Z",
|
|||
|
|
"delivery_attempts": 1,
|
|||
|
|
"created_at": "2026-08-27T06:35:00Z"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
命令类型:
|
|||
|
|
|
|||
|
|
| 类型 | 用途 |
|
|||
|
|
|---|---|
|
|||
|
|
| `apply_config` | 拉取并应用指定配置 |
|
|||
|
|
| `reboot` | 受控重启设备 |
|
|||
|
|
| `diagnostics` | 执行音频、网络、存储等有限诊断 |
|
|||
|
|
| `firmware_update` | 下载、验签、安装并确认固件 |
|
|||
|
|
|
|||
|
|
命令主状态机:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
QUEUED → DELIVERED → ACKNOWLEDGED → SUCCEEDED / FAILED
|
|||
|
|
└────────→ EXPIRED / CANCELLED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
设备进程重启后可以重新拉取 `ACKNOWLEDGED` 但未终结的命令,用于恢复执行。
|
|||
|
|
|
|||
|
|
主要状态码:200、401、503。
|
|||
|
|
|
|||
|
|
### 9.2 命令执行回执
|
|||
|
|
|
|||
|
|
`POST /device/v2/commands/{id}/ack`
|
|||
|
|
|
|||
|
|
请求:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"status": "ACKNOWLEDGED",
|
|||
|
|
"result_summary": {
|
|||
|
|
"phase": "running",
|
|||
|
|
"progress_percent": 10
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`status` 只允许 `ACKNOWLEDGED`、`SUCCEEDED`、`FAILED`。同阶段或相同终态重复提交保持幂等,禁止状态回退。
|
|||
|
|
|
|||
|
|
固件命令可用的阶段包括:
|
|||
|
|
|
|||
|
|
- `downloading`
|
|||
|
|
- `installing`
|
|||
|
|
- `staging`
|
|||
|
|
- `reboot_pending`
|
|||
|
|
- `boot_verifying`
|
|||
|
|
- `confirmed`
|
|||
|
|
- `rolled_back`
|
|||
|
|
|
|||
|
|
成功结果应包含 `installed_version`;A/B 启动健康失败并自动回退时应提交:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"status": "FAILED",
|
|||
|
|
"result_summary": {
|
|||
|
|
"phase": "rolled_back",
|
|||
|
|
"progress_percent": 95,
|
|||
|
|
"result_code": "BOOT_HEALTH_FAILED_ROLLED_BACK",
|
|||
|
|
"rollback_applied": true,
|
|||
|
|
"active_slot": "A",
|
|||
|
|
"failed_slot": "B",
|
|||
|
|
"rollback_version": "1.1.0"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
主要状态码:200、400、401、409。
|
|||
|
|
|
|||
|
|
## 10. 固件下载接口
|
|||
|
|
|
|||
|
|
### 10.1 获取私有固件地址
|
|||
|
|
|
|||
|
|
`GET /device/v2/firmware-releases/{id}/download`
|
|||
|
|
|
|||
|
|
只有属于当前灰度批次且版本仍可用的设备能够取得地址。响应签发约 5 分钟有效的私有下载地址,设备必须:
|
|||
|
|
|
|||
|
|
1. 只接受云端固定固件下载接口返回的地址;
|
|||
|
|
2. 限制下载对象大小为 1 字节~256 MiB;
|
|||
|
|
3. 下载后校验命令清单中的小写 SHA-256;
|
|||
|
|
4. 使用安全区预置的 Ed25519 公钥验证 `firmware-manifest-v1`;
|
|||
|
|
5. 校验型号、硬件版本、制品大小、版本和发布编号;
|
|||
|
|
6. 全部校验通过后才允许写入非活动分区或执行安装。
|
|||
|
|
|
|||
|
|
不要信任命令 `payload` 临时携带的公钥,不要把下载地址写入持久日志。
|
|||
|
|
|
|||
|
|
主要状态码:200、401、403。
|
|||
|
|
|
|||
|
|
## 11. 通话终稿补传接口
|
|||
|
|
|
|||
|
|
### 11.1 批量补传最终话轮
|
|||
|
|
|
|||
|
|
`POST /device/v2/conversations/{id}/turns:batch`
|
|||
|
|
|
|||
|
|
必须携带 `Idempotency-Key`。用于供应商最终转写缺失或不完整时补交设备侧终稿,每批 1~500 条。
|
|||
|
|
|
|||
|
|
话轮字段:
|
|||
|
|
|
|||
|
|
| 字段 | 必填 | 限制 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `turn_key` | 是 | 1~255,通话内稳定唯一 |
|
|||
|
|
| `sequence_no` | 是 | 正整数,批内唯一 |
|
|||
|
|
| `speaker` | 是 | `subject` 或 `agent` |
|
|||
|
|
| `start_at` | 是 | RFC3339 |
|
|||
|
|
| `end_at` | 是 | RFC3339,不早于 `start_at` |
|
|||
|
|
| `final_text` | 是 | 1~20000 字符 |
|
|||
|
|
| `interrupted` | 是 | 是否被打断 |
|
|||
|
|
|
|||
|
|
请求示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"turns": [
|
|||
|
|
{
|
|||
|
|
"turn_key": "device-turn-0001",
|
|||
|
|
"sequence_no": 1,
|
|||
|
|
"speaker": "subject",
|
|||
|
|
"start_at": "2026-08-27T06:32:00Z",
|
|||
|
|
"end_at": "2026-08-27T06:32:03Z",
|
|||
|
|
"final_text": "今天天气怎么样?",
|
|||
|
|
"interrupted": false
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
响应:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"request_id": "01J6TURNS",
|
|||
|
|
"data": {
|
|||
|
|
"conversation_id": "0829059e-4485-46b9-87ea-5a2fcefc6c35",
|
|||
|
|
"accepted_count": 1,
|
|||
|
|
"duplicate_count": 0
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
相同终稿计入重复数;相同 `turn_key` 或 `sequence_no` 对应不同内容时整批返回 409 `TRANSCRIPT_TURN_CONFLICT`,设备不得覆盖云端内容。
|
|||
|
|
|
|||
|
|
主要状态码:200、400、401、403、404、409、503。
|
|||
|
|
|
|||
|
|
## 12. 双轨录音上传接口
|
|||
|
|
|
|||
|
|
录音上传只在创建响应满足以下条件时允许:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
privacy.retention_mode = transcript_and_audio
|
|||
|
|
privacy.upload_allowed = true
|
|||
|
|
对应 track 的 record_near / record_far = true
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
当前编码基线只接受可解析的 WAV PCM 16 位:
|
|||
|
|
|
|||
|
|
- `codec=wav_pcm_s16le`
|
|||
|
|
- `content_type=audio/wav`
|
|||
|
|
- 单片时长 1~600000 ms
|
|||
|
|
- 单片大小 1~67108864 字节
|
|||
|
|
- 每批最多 1000 片
|
|||
|
|
|
|||
|
|
### 12.1 声明上传批次
|
|||
|
|
|
|||
|
|
`POST /device/v2/conversations/{id}/upload-batches`
|
|||
|
|
|
|||
|
|
必须携带 `Idempotency-Key`。
|
|||
|
|
|
|||
|
|
请求:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"segments": [
|
|||
|
|
{
|
|||
|
|
"track": "near",
|
|||
|
|
"sequence_no": 0,
|
|||
|
|
"codec": "wav_pcm_s16le",
|
|||
|
|
"content_type": "audio/wav",
|
|||
|
|
"duration_ms": 10000,
|
|||
|
|
"expected_size": 320044,
|
|||
|
|
"expected_digest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
201 响应的 `data` 为上传批次:
|
|||
|
|
|
|||
|
|
| 字段 | 说明 |
|
|||
|
|
|---|---|
|
|||
|
|
| `id` | 批次 UUID |
|
|||
|
|
| `conversation_id` | 所属通话 |
|
|||
|
|
| `status` | `PENDING/UPLOADING/VERIFYING/COMPLETED/FAILED/EXPIRED` |
|
|||
|
|
| `segment_count` | 分片数 |
|
|||
|
|
| `expected_total_size` | 声明总字节数 |
|
|||
|
|
| `verified_count/rejected_count` | 校验成功/拒绝数量 |
|
|||
|
|
| `verification_attempts` | 校验尝试次数 |
|
|||
|
|
| `last_error_code` | 批次稳定失败码 |
|
|||
|
|
| `expires_at` | 批次到期时间 |
|
|||
|
|
| `segments` | 分片结果数组 |
|
|||
|
|
|
|||
|
|
新分片包含短期 `upload_url` 和 `upload_until`。设备使用 HTTP PUT 将该分片原始字节直接上传,不得把多个分片或其他内容写入同一 URL。
|
|||
|
|
|
|||
|
|
主要状态码:201、400、401、403、409。
|
|||
|
|
|
|||
|
|
### 12.2 声明批次上传完成
|
|||
|
|
|
|||
|
|
`POST /device/v2/upload-batches/{id}/complete`
|
|||
|
|
|
|||
|
|
请求无 JSON 正文,但仍需发送空正文 SHA-256。平台返回 202,并把批次置为 `VERIFYING` 或返回已经完成的结果。重复提交不会重复创建校验任务。
|
|||
|
|
|
|||
|
|
主要状态码:202、401、404、409、410。
|
|||
|
|
|
|||
|
|
### 12.3 查询批次校验结果
|
|||
|
|
|
|||
|
|
`GET /device/v2/upload-batches/{id}`
|
|||
|
|
|
|||
|
|
分片状态:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
DECLARED → AUTHORIZED → UPLOADED → VERIFYING → VERIFIED / REJECTED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
关键字段:
|
|||
|
|
|
|||
|
|
| 字段 | 说明 |
|
|||
|
|
|---|---|
|
|||
|
|
| `id` | 分片 UUID |
|
|||
|
|
| `track` | `near` 或 `far` |
|
|||
|
|
| `sequence_no` | 轨道内序号 |
|
|||
|
|
| `expected_size/digest` | 声明大小和 SHA-256 |
|
|||
|
|
| `status` | 当前分片状态 |
|
|||
|
|
| `last_error_code` | 校验失败稳定码 |
|
|||
|
|
| `verified_at` | 校验成功时间 |
|
|||
|
|
| `local_delete_after` | 只有 VERIFIED 才返回;此时间之后才能删除本地密文 |
|
|||
|
|
| `local_delete_status` | 最近一次本地删除回执 `DELETED/FAILED` |
|
|||
|
|
|
|||
|
|
建议每 1~2 秒轮询,达到批次 `expires_at` 或本地等待上限后停止。对象暂时不可见时平台可能保持 `VERIFYING` 并后台重试。
|
|||
|
|
|
|||
|
|
主要状态码:200、401、404。
|
|||
|
|
|
|||
|
|
### 12.4 回报本地密文删除结果
|
|||
|
|
|
|||
|
|
`POST /device/v2/media-segments/{id}/local-delete-ack`
|
|||
|
|
|
|||
|
|
只能在 `local_delete_after` 之后删除设备本地加密录音副本并回报。
|
|||
|
|
|
|||
|
|
成功:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"status": "DELETED"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
失败:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"status": "FAILED",
|
|||
|
|
"error_code": "LOCAL_STORAGE_DELETE_FAILED"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `FAILED` 时 `error_code` 必填;`DELETED` 时禁止携带。
|
|||
|
|
- 过早提交成功回执返回 409。
|
|||
|
|
- `DELETED` 重复回报幂等。
|
|||
|
|
- 此回执只代表设备副本已删除,不删除云端已验证对象。
|
|||
|
|
|
|||
|
|
主要状态码:200、400、401、404、409。
|
|||
|
|
|
|||
|
|
## 13. 业务流程
|
|||
|
|
|
|||
|
|
### 13.1 设备开机与配置同步
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant D as 设备
|
|||
|
|
participant A as 云端API
|
|||
|
|
D->>A: GET /bootstrap + HMAC
|
|||
|
|
A-->>D: server_time、状态、绑定、版本门禁
|
|||
|
|
alt 固件不兼容
|
|||
|
|
D->>D: 进入升级流程,禁止新通话
|
|||
|
|
else 固件兼容
|
|||
|
|
D->>A: GET /config + If-None-Match
|
|||
|
|
A-->>D: 200 新配置或 304 未变化
|
|||
|
|
D->>D: 校验并原子应用配置
|
|||
|
|
D->>A: POST /telemetry:batch 上报实际版本
|
|||
|
|
end
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 13.2 双向语音通话
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant D as 设备
|
|||
|
|
participant A as 云端API
|
|||
|
|
participant R as 火山RTC
|
|||
|
|
participant W as Worker
|
|||
|
|
participant B as 豆包智能体
|
|||
|
|
D->>A: POST /conversations
|
|||
|
|
A-->>D: conversation_id + room/token
|
|||
|
|
D->>R: 加入房间并发布麦克风
|
|||
|
|
D->>A: POST /conversations/{id}/activate
|
|||
|
|
A-->>D: 202 STARTING_AGENT
|
|||
|
|
A->>W: agent_start_requested
|
|||
|
|
W->>B: StartVoiceChat
|
|||
|
|
B->>R: 智能体加入同一房间
|
|||
|
|
loop 有限轮询
|
|||
|
|
D->>A: GET /conversations/{id}
|
|||
|
|
A-->>D: wait / continue / leave_room
|
|||
|
|
end
|
|||
|
|
loop 通话中
|
|||
|
|
D->>A: POST /heartbeat
|
|||
|
|
A-->>D: continue/stop/renew + config/commands
|
|||
|
|
D->>R: 上行麦克风音频
|
|||
|
|
R-->>D: 下行豆包语音
|
|||
|
|
end
|
|||
|
|
D->>A: POST /stop
|
|||
|
|
A->>W: agent_stop_requested
|
|||
|
|
W->>B: StopVoiceChat
|
|||
|
|
D->>R: 退出房间
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 13.3 设备命令
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant D as 设备
|
|||
|
|
participant A as 云端API
|
|||
|
|
D->>A: GET /commands
|
|||
|
|
A-->>D: 待执行命令
|
|||
|
|
D->>A: POST /commands/{id}/ack ACKNOWLEDGED
|
|||
|
|
D->>D: 执行命令并保存本地状态
|
|||
|
|
D->>A: POST /commands/{id}/ack SUCCEEDED/FAILED
|
|||
|
|
A-->>D: 最终命令状态
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 13.4 录音上传
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant D as 设备
|
|||
|
|
participant A as 云端API
|
|||
|
|
participant O as 对象存储
|
|||
|
|
participant W as Worker
|
|||
|
|
D->>A: POST /conversations/{id}/upload-batches
|
|||
|
|
A-->>D: 分片ID + 短期PUT地址
|
|||
|
|
D->>O: PUT 分片字节
|
|||
|
|
D->>A: POST /upload-batches/{id}/complete
|
|||
|
|
A->>W: media.verify_requested
|
|||
|
|
W->>O: 校验大小、摘要、WAV与时长
|
|||
|
|
loop 查询结果
|
|||
|
|
D->>A: GET /upload-batches/{id}
|
|||
|
|
A-->>D: VERIFYING/VERIFIED/REJECTED
|
|||
|
|
end
|
|||
|
|
D->>D: 到 local_delete_after 后删除本地密文
|
|||
|
|
D->>A: POST /media-segments/{id}/local-delete-ack
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 14. 错误码与重试策略
|
|||
|
|
|
|||
|
|
| HTTP | 错误码 | 是否自动重试 | 处理建议 |
|
|||
|
|
|---:|---|---|---|
|
|||
|
|
| 400 | `VALIDATION_FAILED` | 否 | 修正字段,重新生成随机数和签名 |
|
|||
|
|
| 401 | `DEVICE_AUTHENTICATION_FAILED` | 否 | 检查设备登记、时钟、摘要、签名或重放 |
|
|||
|
|
| 403 | `CONSENT_REQUIRED` | 否 | 等待用户有效授权 |
|
|||
|
|
| 403 | `TRANSCRIPT_STORAGE_NOT_ALLOWED` | 否 | 停止补传,不能绕过留存策略 |
|
|||
|
|
| 404 | `TELEMETRY_CONVERSATION_NOT_FOUND` | 否 | 删除错误通话引用 |
|
|||
|
|
| 409 | `DEVICE_NOT_BOUND` | 否 | 完成设备绑定 |
|
|||
|
|
| 409 | `CONVERSATION_ALREADY_ACTIVE` | 否 | 恢复或查询已有通话 |
|
|||
|
|
| 409 | `IDEMPOTENCY_KEY_REUSED` | 否 | 不同正文必须使用新业务键 |
|
|||
|
|
| 409 | `ROOM_ALLOCATION_EXPIRED` | 否 | 重新创建通话和房间 |
|
|||
|
|
| 409 | `RTC_ALLOCATION_MISMATCH` | 否 | 丢弃本地房间资料并重新创建 |
|
|||
|
|
| 409 | `CONFIG_VERSION_CONFLICT` | 否 | 重新获取设备配置 |
|
|||
|
|
| 409 | `CONVERSATION_STATE_CONFLICT` | 否 | 查询权威状态,禁止强制覆盖 |
|
|||
|
|
| 409 | `TRANSCRIPT_TURN_CONFLICT` | 否 | 保存冲突明细,转人工对账 |
|
|||
|
|
| 409 | `TELEMETRY_EVENT_CONFLICT` | 否 | 停止推进确认点,检查事件键和序号 |
|
|||
|
|
| 409 | `DEVICE_COMMAND_STATE_CONFLICT` | 否 | 重新拉取命令,禁止状态回退 |
|
|||
|
|
| 422 | `AGENT_CONTEXT_PREPARATION_FAILED` | 否 | 等待修正长者背景资料后新建通话 |
|
|||
|
|
| 426 | `FIRMWARE_UPDATE_REQUIRED` | 否 | 执行固件升级 |
|
|||
|
|
| 429 | `USAGE_QUOTA_EXCEEDED` | 延迟/人工 | 提示额度或并发限制 |
|
|||
|
|
| 503 | `DEVICE_AUTHENTICATION_UNAVAILABLE` | 是 | 指数退避,使用新随机数重试 |
|
|||
|
|
| 503 | `DEVICE_CONFIG_UNAVAILABLE` | 是 | 保留旧配置并退避重试 |
|
|||
|
|
| 503 | `RUNTIME_CONFIG_UNAVAILABLE` | 有条件 | 停止创建通话,等待平台恢复路由 |
|
|||
|
|
| 503 | `TRANSCRIPT_BATCH_UNAVAILABLE` | 是 | 原正文、原幂等键,新 HMAC 头重试 |
|
|||
|
|
| 503 | `DEVICE_TELEMETRY_UNAVAILABLE` | 是 | 保留事件队列,原正文和幂等键重试 |
|
|||
|
|
| 503 | `CONVERSATION_HEARTBEAT_UNAVAILABLE` | 是 | 同序号、新 HMAC 头退避重试 |
|
|||
|
|
| 503 | `CONVERSATION_STOP_UNAVAILABLE` | 是 | 原正文和幂等键、新 HMAC 头重试 |
|
|||
|
|
|
|||
|
|
建议重试参数:
|
|||
|
|
|
|||
|
|
- 初始等待 500 ms;
|
|||
|
|
- 指数退避 0.5、1、2、4、8、15、30 秒;
|
|||
|
|
- 加入 0~20% 随机抖动;
|
|||
|
|
- 单次业务的最大重试时间受 RTC 房间、上传批次或命令过期时间约束;
|
|||
|
|
- `retryable=false` 时不得自动循环重试。
|
|||
|
|
|
|||
|
|
## 15. 与设备相关但不由终端直接调用的管理接口
|
|||
|
|
|
|||
|
|
下列接口由 SaaS 管理后台或运营后台调用,使用 OIDC 用户身份和权限控制,不使用设备 HMAC:
|
|||
|
|
|
|||
|
|
| 方法 | 路径 | 用途 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| POST | `/api/v1/device-claims` | 家庭成员使用设备屏幕显示的绑定码绑定设备 |
|
|||
|
|
| GET | `/api/v1/devices` | 查询当前家庭可管理设备 |
|
|||
|
|
| GET | `/api/v1/devices/{id}` | 查询设备详情 |
|
|||
|
|
| GET | `/api/v1/devices/{id}/presence` | 查询设备在线和当前通话状态 |
|
|||
|
|
| GET | `/api/v1/devices/{id}/twin` | 查询设备期望/实际配置影子 |
|
|||
|
|
| PATCH | `/api/v1/devices/{id}/twin` | 修改期望配置,需 `If-Match` |
|
|||
|
|
| GET | `/api/v1/devices/{id}/commands` | 查询设备命令记录 |
|
|||
|
|
| POST | `/api/v1/devices/{id}/commands` | 创建配置、重启、诊断或固件命令 |
|
|||
|
|
| GET/POST | `/api/v1/households/{id}/firmware-signing-keys` | 管理固件 Ed25519 信任根 |
|
|||
|
|
| POST | `/api/v1/households/{id}/firmware-signing-keys/revoke` | 吊销签名公钥 |
|
|||
|
|
| GET/POST | `/api/v1/households/{id}/firmware-releases` | 查询或创建固件版本 |
|
|||
|
|
| POST | `/api/v1/firmware-releases/{id}/upload-session` | 创建固件上传会话 |
|
|||
|
|
| POST | `/api/v1/firmware-releases/{id}/finalize` | 完成固件制品校验与发布 |
|
|||
|
|
| GET | `/api/v1/firmware-releases/{id}/impact` | 查询版本、设备和命令影响 |
|
|||
|
|
| POST | `/api/v1/firmware-releases/{id}/pause` | 暂停版本 |
|
|||
|
|
| POST | `/api/v1/firmware-releases/{id}/resume` | 恢复版本 |
|
|||
|
|
| POST | `/api/v1/firmware-releases/{id}/retire` | 退役版本 |
|
|||
|
|
| GET/POST | `/api/v1/households/{id}/firmware-rollouts` | 查询或创建灰度批次 |
|
|||
|
|
| GET | `/api/v1/firmware-rollouts/{id}` | 查询灰度批次状态 |
|
|||
|
|
| POST | `/api/v1/firmware-rollouts/{id}/pause` | 暂停灰度 |
|
|||
|
|
| POST | `/api/v1/firmware-rollouts/{id}/rollback` | 回滚到已验证版本 |
|
|||
|
|
|
|||
|
|
## 16. 设备实现检查清单
|
|||
|
|
|
|||
|
|
### 16.1 身份与安全
|
|||
|
|
|
|||
|
|
- [ ] 每台设备使用独立密钥,密钥位于安全存储区。
|
|||
|
|
- [ ] 每次请求生成新 Nonce,网络重试也不复用。
|
|||
|
|
- [ ] 对实际发送的原始正文字节计算 SHA-256。
|
|||
|
|
- [ ] 签名 URI 与实际请求路径、查询串完全一致。
|
|||
|
|
- [ ] 不记录设备密钥、HMAC 签名、RTC Token、上传地址和下载地址。
|
|||
|
|
- [ ] 生产环境强制 HTTPS 并校验证书。
|
|||
|
|
|
|||
|
|
### 16.2 离线与幂等
|
|||
|
|
|
|||
|
|
- [ ] 关键请求正文和幂等键在成功确认前持久化。
|
|||
|
|
- [ ] 重试保留原正文与 Idempotency-Key,但更新 HMAC 请求头。
|
|||
|
|
- [ ] 遥测事件键和序号跨进程重启保持单调或稳定唯一。
|
|||
|
|
- [ ] 命令 `ACKNOWLEDGED` 后保存本地执行进度,重启可恢复。
|
|||
|
|
- [ ] 保留最近一次有效配置,配置拉取失败不清空。
|
|||
|
|
|
|||
|
|
### 16.3 实时语音
|
|||
|
|
|
|||
|
|
- [ ] 创建通话后先加入 RTC,再调用 activate。
|
|||
|
|
- [ ] 识别 `wait/continue/leave_room`,失败时及时离房。
|
|||
|
|
- [ ] 按服务器心跳间隔发送递增序号。
|
|||
|
|
- [ ] 支持续签时只在内存替换 RTC Token。
|
|||
|
|
- [ ] 收到 `stop` 或终态后停止采集、退出 RTC 并调用停止接口。
|
|||
|
|
- [ ] 不根据 `engine_mode` 改变设备媒体行为。
|
|||
|
|
|
|||
|
|
### 16.4 录音与隐私
|
|||
|
|
|
|||
|
|
- [ ] 只有云端明确允许时才录制和上传对应轨道。
|
|||
|
|
- [ ] 上传前计算真实字节大小和 SHA-256。
|
|||
|
|
- [ ] 每个短期 URL 只 PUT 对应单一对象。
|
|||
|
|
- [ ] 只有校验为 VERIFIED 且到达 `local_delete_after` 后才删除本地密文。
|
|||
|
|
- [ ] 删除失败上报稳定错误码,不上传包含个人信息的自由文本堆栈。
|
|||
|
|
|
|||
|
|
## 17. 契约与联调依据
|
|||
|
|
|
|||
|
|
- 权威机器可读契约:`openapi/openapi.yaml`
|
|||
|
|
- 设备接入实现说明:`docs/device-integration.md`
|
|||
|
|
- 设备命令可靠下发:`docs/device-commands.md`
|
|||
|
|
- 录音上传和访问:`docs/media-upload-and-access.md`
|
|||
|
|
- 设备 API 兼容策略:`docs/device-api-compatibility.md`
|
|||
|
|
- A/B 固件协议:`docs/device-firmware-ab-protocol.md`
|
|||
|
|
|
|||
|
|
若本文档与当前 OpenAPI 发生字段差异,应以同一构建版本中的 `openapi/openapi.yaml` 为准,并同步修订本文档;不得根据固件版本、设备型号或 User-Agent 猜测接口字段。
|