Files
story/docs/设备终端接口文档.md
T

38 KiB
Raw Permalink Blame History

数据采集终端—云端系统设备接口文档

文档版本: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. 总体通信关系

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、固件下载重定向等特殊响应外,成功响应统一为:

{
  "request_id": "01J6...",
  "data": {}
}

request_id 是本次云端请求关联编号,设备日志应保存该值,但不得把密钥、签名、Token 或临时对象地址写入日志。

4.3 统一错误响应

{
  "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 或空正文请求也必须计算空字节摘要:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

5.2 签名原文

使用单个换行符按以下顺序拼接,最后一行后不增加换行:

UPPERCASE_HTTP_METHOD
REQUEST_URI_WITH_EXACT_QUERY_STRING
X-Device-Timestamp原值
X-Device-Nonce原值
X-Device-Content-SHA256原值

例如:

GET
/device/v2/bootstrap?firmware_version=1.2.0&device_time=2026-08-27T06%3A30%3A00Z
2026-08-27T06:30:01Z
62d4b688f77b4baaa87a38d315c2f3b1
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

计算方法:

signature = Base64URL_NoPadding(
    HMAC_SHA256(device_secret, canonical_string)
)

注意事项:

  • 查询参数的顺序、百分号编码和大小写必须与实际请求 URI 完全一致。
  • Nonce 不是业务幂等键。业务请求重试时保留原 Idempotency-Key 和正文,但必须生成新 Nonce、时间戳和签名。
  • 不允许在设备镜像、日志或前端页面中明文输出设备密钥。
  • 云端 Redis 或设备身份数据库不可用时鉴权关闭失败并返回 503,不会绕过签名。

5.3 幂等键

需要幂等保护的接口必须包含:

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 网络类型或描述

成功响应示例:

{
  "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:

If-None-Match: "v3"

成功响应头:

ETag: "v4"

成功响应示例:

{
  "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 设备技术目标,不作为豆包提示词

请求示例:

{
  "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 响应示例:

{
  "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 设备侧连接关联号,不能包含凭证

请求示例:

{
  "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 响应示例:

{
  "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 响应示例:

{
  "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 可选稳定错误码 仅用于展示和诊断,不显示供应商原始错误

失败示例:

{
  "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 项;当前可为空数组

请求示例:

{
  "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 响应示例:

{
  "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 是 本地远端录音分片数量

请求示例:

{
  "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 响应示例:

{
  "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 同名的一个对象

状态事件:

{
  "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 }
  }
}

错误事件:

{
  "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"
  }
}

命令结果事件:

{
  "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"
    }
  }
}

完整请求:

{
  "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 响应:

{
  "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。只返回当前设备未过期、未终结的命令。

响应示例:

{
  "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 下载、验签、安装并确认固件

命令主状态机:

QUEUED → DELIVERED → ACKNOWLEDGED → SUCCEEDED / FAILED
                           └────────→ EXPIRED / CANCELLED

设备进程重启后可以重新拉取 ACKNOWLEDGED 但未终结的命令,用于恢复执行。

主要状态码:200、401、503。

9.2 命令执行回执

POST /device/v2/commands/{id}/ack

请求:

{
  "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 启动健康失败并自动回退时应提交:

{
  "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 是 是否被打断

请求示例:

{
  "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
    }
  ]
}

响应:

{
  "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. 双轨录音上传接口

录音上传只在创建响应满足以下条件时允许:

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。

请求:

{
  "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}

分片状态:

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 之后删除设备本地加密录音副本并回报。

成功:

{
  "status": "DELETED"
}

失败:

{
  "status": "FAILED",
  "error_code": "LOCAL_STORAGE_DELETE_FAILED"
}
  • FAILED 时 error_code 必填;DELETED 时禁止携带。
  • 过早提交成功回执返回 409。
  • DELETED 重复回报幂等。
  • 此回执只代表设备副本已删除,不删除云端已验证对象。

主要状态码:200、400、401、404、409。

13. 业务流程

13.1 设备开机与配置同步

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 双向语音通话

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 设备命令

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 录音上传

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 猜测接口字段。