Files
story/docs/device-api-complete-reference.md
Codex Local 0c2151ace9
ci / verify (push) Canceled after 0s
chore: checkpoint 第一版
2026-09-06 17:04:11 +08:00

848 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设备相关接口完整说明
> 版本:2026-09-05
> 契约来源:`internal/httpapi/router.go`、实际处理器、`openapi/openapi.yaml`
> 生产 Base URL:`https://121.41.46.238`
> 适用对象:EchoEar/ESP32-S3 固件、Windows 模拟设备、用户 App/Web、平台运维端。
## 1. 文档范围
本文覆盖当前代码中全部直接或间接管理设备的接口:
| 接口面 | 方法数 | 鉴权 | 用途 |
|---|---:|---|---|
| 设备直连接口 `/device/v2`、`/device/v3` | 20 | 设备 HMAC | 启动、配置、命令、遥测、固件下载、会话、字幕、媒体、火山凭据 |
| 用户设备管理 `/api/v1/devices*` | 10 | OIDC Bearer | 绑定、设备列表、在线状态、影子、命令 |
| 运维设备管理 `/api/v1/ops/devices*` | 9 | OIDC Bearer + `X-Tenant-Id` | 登记、密钥轮换、绑定码、激活状态机 |
| 家庭端与运维端固件接口 | 31 | OIDC Bearer | 签名密钥、版本、上传、灰度、暂停、回滚、下载 |
| 设备诊断处置 | 2 | OIDC Bearer + `X-Tenant-Id` | 强制刷新配置、吊销设备 |
火山硬件智能体 WebSocket 不是平台 HTTP API,但设备聊天必须使用,故在第 10 节单独说明。
## 2. 通用约定
### 2.1 平台响应包络
成功:
```json
{
"request_id": "req-01J...",
"data": {}
}
```
失败:
```json
{
"request_id": "req-01J...",
"error": {
"code": "VALIDATION_FAILED",
"message": "请求参数不正确",
"retryable": false,
"field_errors": [{ "field": "hardware_id", "message": "不能为空" }],
"details": {}
}
}
```
### 2.2 ID、时间和分页
- `{id}`、`device_id`、`conversation_id`、`household_id`、`subject_id` 均为 UUID。
- 时间均为 RFC3339 UTC,例如 `2026-09-05T06:30:00Z`。
- `Idempotency-Key` 长度为 16—128 字符;业务重试保留原正文和原幂等键。
- `If-Match` 使用接口返回的强 ETag,例如 `"7"`。
- 密钥、Token、绑定码和下载地址不得写入普通日志。
### 2.3 设备 HMAC 请求头
所有 `/device/v2/*` 和 `/device/v3/*` 请求必须携带:
| Header | 输入说明 |
|---|---|
| `X-Device-Id` | 平台设备公开 UUID |
| `X-Device-Timestamp` | RFC3339 时间,允许时钟偏差默认 ±5 分钟 |
| `X-Device-Nonce` | 16—128 字符,一次请求只能使用一次 |
| `X-Device-Content-SHA256` | 原始请求正文 SHA-256 小写十六进制;空正文也计算 |
| `X-Device-Signature` | HMAC-SHA256 的 Base64URL 无填充编码 |
签名原文:
```text
HTTP_METHOD大写\n
完整REQUEST_URI(含查询字符串)\n
X-Device-Timestamp\n
X-Device-Nonce\n
X-Device-Content-SHA256
```
## 3. 设备启动、配置、命令和遥测
### 3.1 GET `/device/v2/bootstrap`
介绍:设备上电后报告固件和硬件信息,取得平台权威设备状态、绑定信息、配置版本和服务器时间。
输入:
| 位置 | 字段 | 必填 | 说明 |
|---|---|---:|---|
| Query | `firmware_version` | 是 | 当前固件版本,最长 80 |
| Query | `device_time` | 是 | RFC3339 设备时间 |
| Query | `board_rev` | 否 | 硬件版本 |
| Query | `rtc_sdk_version` | 否 | SDK 版本 |
| Query | `codec` | 否 | 编码,例如 `pcm_s16le` |
| Query | `sample_rate` | 否 | 采样率,例如 `16000` |
| Query | `network` | 否 | 网络类型,例如 `wifi` |
输出:`server_time`、`clock_skew_ms`、设备状态、最低版本、兼容性、`config_revision` 和绑定对象。
示例:
```http
GET /device/v2/bootstrap?firmware_version=1.0.0&board_rev=echoear-v1&codec=pcm_s16le&sample_rate=16000&device_time=2026-09-05T06%3A30%3A00Z&network=wifi
```
```json
{
"request_id": "req-1",
"data": {
"server_time": "2026-09-05T06:30:01Z",
"clock_skew_ms": 1000,
"device": {
"device_id": "10000000-0000-0000-0000-000000000512",
"status": "registered",
"model": "echoear",
"firmware_compatible": true,
"config_revision": 7,
"binding": { "bound": true, "household_id": "11111111-1111-1111-1111-111111111111", "subject_id": "22222222-2222-2222-2222-222222222222", "subject_name": "张大妈" }
}
}
}
```
### 3.2 GET `/device/v2/config`
介绍:读取云端期望配置和当前绑定。设备保存响应 `ETag`,后续用 `If-None-Match` 增量检查。
输入:无正文;可选 Header `If-None-Match: "7"`。
输出:HTTP `200` 返回 `device_id`、`revision`、`desired_config`、`reported_revision`、`binding`;配置未变化返回 `304` 且无正文。
```json
{
"request_id": "req-2",
"data": {
"device_id": "10000000-0000-0000-0000-000000000512",
"revision": 8,
"desired_config": { "voice_runtime": "volc_hardware_agent", "volume": { "speaker": 70 } },
"reported_revision": 7,
"binding": { "bound": true, "subject_id": "22222222-2222-2222-2222-222222222222" }
}
}
```
### 3.3 GET `/device/v2/commands`
介绍:拉取未过期的待执行命令,并增加投递次数。
输入:Query `limit`,范围 1—20,默认 10。
输出:`items[]`;每项包含 `id`、`type`、`payload`、`status`、`expires_at`、`delivery_attempts`、`created_at`。
```json
{
"request_id": "req-3",
"data": {
"items": [{
"id": "33333333-3333-3333-3333-333333333333",
"type": "apply_config",
"payload": { "revision": 8 },
"status": "DELIVERED",
"expires_at": "2026-09-05T07:30:00Z",
"delivery_attempts": 1,
"created_at": "2026-09-05T06:30:00Z"
}]
}
}
```
### 3.4 POST `/device/v2/commands/{id}/ack`
介绍:设备回报命令已接收、成功或失败。
输入:`status` 为 `ACKNOWLEDGED`、`SUCCEEDED`、`FAILED`;`result_summary` 只能包含有限诊断字段,不得含 Secret/Token。
```json
{
"status": "SUCCEEDED",
"result_summary": { "applied_revision": 8 }
}
```
输出:更新后的命令投递对象。
```json
{
"request_id": "req-4",
"data": { "id": "33333333-3333-3333-3333-333333333333", "type": "apply_config", "status": "SUCCEEDED", "result_summary": { "applied_revision": 8 } }
}
```
### 3.5 POST `/device/v2/telemetry:batch`
介绍:批量上报状态、脱敏错误或命令结果;按 `event_key` 和 `sequence_no` 去重,并返回平台动作。
输入:`events` 为 1—500 项;`event_type` 为 `status`、`error` 或 `command_result`,且只携带对应同名对象。
```json
{
"events": [{
"event_key": "boot.000008",
"sequence_no": 8,
"occurred_at": "2026-09-05T06:31:00Z",
"event_type": "status",
"status": {
"firmware_version": "1.0.0",
"reported_config_revision": 8,
"network": { "type": "wifi", "rssi": -51 },
"rtc": { "joined": false, "uplink": false, "downlink": false },
"recording_queue": { "pending_segments": 0, "failed_segments": 0 }
}
}]
}
```
输出:接收/重复数、最新期望配置版本和动作列表。
```json
{
"request_id": "req-5",
"data": { "accepted_count": 1, "duplicate_count": 0, "desired_config_revision": 8, "actions": [{ "type": "pull_commands" }] }
}
```
## 4. 火山硬件智能体平台控制面
### 4.1 POST `/device/v3/hardware-agent-credentials:provision`
介绍:平台使用 Product Secret 代设备调用火山动态注册,只向当前设备返回它自己的一机一密。允许状态:`BOUND`、`PROVISIONING`、`ACTIVE`。
输入:Header `Idempotency-Key`;正文只包含稳定 `hardware_id`。
```json
{ "hardware_id": "echoear-b43a451634a8" }
```
输出:`instance_id`、`product_key`、`device_name`、`hardware_id`、一次性敏感的 `device_secret`、版本和来源。响应必须 `Cache-Control: no-store`。
```json
{
"request_id": "req-6",
"data": {
"instance_id": "6a92945cc0c4689234aef656",
"product_key": "6a9568047dd4b23eacab9b1a",
"device_name": "echoear-100000000512",
"hardware_id": "echoear-b43a451634a8",
"device_secret": "<仅保存到设备安全区>",
"credential_ref": "cred-01J...",
"credential_version": 1,
"credential_source": "platform_dynamic_registration",
"issued_at": "2026-09-05T06:32:00Z"
}
}
```
### 4.2 POST `/device/v3/agent-sessions`
介绍:每次聊天前创建一次平台会话,冻结本次 Bot、授权、人设、记忆、隐私策略和会话证明。仅 `ACTIVE` 设备可调用。
输入:
```json
{
"device_time": "2026-09-05T06:35:00Z",
"monotonic_ms": 123456,
"capabilities": {
"hardware_agent": true,
"sdk_version": "1.1.0-echoear",
"transports": ["low_load"],
"input_audio_format": "pcm_s16le_16000_mono",
"output_audio_format": "pcm_s16le_16000_mono",
"server_event_callback": false,
"transcript_upload": true,
"dynamic_session_config": true,
"credential_rotation": true,
"near_pcm_recording": false,
"far_pcm_recording": false
},
"network": { "type": "wifi", "rssi": -52 },
"requested_goal": "open_conversation",
"config_revision": 8
}
```
输出:
```json
{
"request_id": "req-7",
"data": {
"conversation_id": "44444444-4444-4444-4444-444444444444",
"status": "SESSION_READY",
"runtime_type": "volc_hardware_agent",
"runtime": {
"provider": "volcengine",
"bot_id": "botM4gWQ9A9y",
"transport": "low_load",
"provider_session_ref": "provider-ref",
"session_nonce": "<仅内存保存>",
"config_digest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"wait_for_session_update": true
},
"session_config": {
"dialog_id": "55555555-5555-5555-5555-555555555555",
"system_prompt": "<本次人设>",
"persona_tone": "亲切、自然",
"authorized_context": "<本次授权背景与记忆>",
"prompt_bundle_version": "minimal-v2"
},
"privacy": { "retention_mode": "transcript_only", "record_near": false, "record_far": false, "upload_allowed": false },
"session": { "engine_mode": "s2s_hybrid", "heartbeat_interval_sec": 15, "allocation_expires_at": "2026-09-05T06:40:00Z" }
}
}
```
### 4.3 POST `/device/v3/agent-sessions/{id}/events`
介绍:设备回流 `session_started`、`session_updated`、`session_ended`、`session_failed`。`sequence_no` 严格递增。
开始事件输入:
```json
{
"event_id": "evt-start-1",
"sequence_no": 1,
"event_type": "session_started",
"occurred_at": "2026-09-05T06:35:03Z",
"provider_session_ref": "provider-ref",
"session_nonce": "<创建会话返回值>",
"provider_connection_ref": "event_LUVjX_wvR",
"bot_id": "botM4gWQ9A9y",
"transport": "low_load",
"config_digest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"dynamic_config_applied": true
}
```
输出:`status`、平台动作、是否重复和接收时间。
```json
{
"request_id": "req-8",
"data": { "conversation_id": "44444444-4444-4444-4444-444444444444", "status": "ACTIVE", "action": "continue", "duplicate": false, "accepted_at": "2026-09-05T06:35:03Z" }
}
```
`action=stop_session` 时必须立即停麦、断开火山,再上报 `session_ended` 或 `session_failed`。
### 4.4 POST `/device/v3/agent-sessions/{id}/turns:batch`
介绍:可靠上传最终字幕;禁止上传增量字幕。单批 1—500 条。
输入:
```json
{
"turns": [{
"turn_key": "user-final-1",
"sequence_no": 1,
"speaker": "subject",
"start_at": "2026-09-05T06:35:10Z",
"end_at": "2026-09-05T06:35:12Z",
"final_text": "你好。",
"interrupted": false
}]
}
```
输出:
```json
{ "request_id": "req-9", "data": { "conversation_id": "44444444-4444-4444-4444-444444444444", "accepted_count": 1, "duplicate_count": 0 } }
```
## 5. 旧 RTC 会话兼容接口
新 EchoEar 使用硬件智能体 v3,不应调用本节的创建/激活接口。本节仅供存量 RTC 设备。
### 5.1 POST `/device/v2/conversations`
介绍:分配 RTC 房间和短期 Token,不启动智能体。
输入:
```json
{
"device_time": "2026-09-05T06:40:00Z",
"monotonic_ms": 130000,
"capabilities": { "rtc": true, "audio_tracks": ["near", "far"], "upload_resume": true, "rtc_credential_renewal": true },
"network": { "type": "wifi", "rssi": -50 },
"requested_goal": "open_conversation"
}
```
输出:`conversation_id`、`ROOM_ISSUED`、RTC 房间/用户/Token、隐私策略和心跳配置。
```json
{
"request_id": "req-10",
"data": {
"conversation_id": "66666666-6666-6666-6666-666666666666",
"status": "ROOM_ISSUED",
"rtc": { "app_id": "app", "room_id": "room", "user_id": "device-user", "agent_user_id": "agent-user", "token": "<仅内存>", "expires_at": "2026-09-05T06:45:00Z" },
"privacy": { "retention_mode": "none", "record_near": false, "record_far": false, "upload_allowed": false },
"session": { "engine_mode": "cascade", "heartbeat_interval_sec": 10, "allocation_expires_at": "2026-09-05T06:45:00Z" }
}
}
```
### 5.2 POST `/device/v2/conversations/{id}/activate`
介绍:设备确认已成功进入指定 RTC 房间后,触发平台启动旧语音任务。
输入:
```json
{ "joined_at": "2026-09-05T06:40:02Z", "room_id": "room", "rtc_user_id": "device-user", "rtc_join_result": "success", "config_revision": 8, "connection_reference": "rtc-conn-1" }
```
输出:
```json
{ "request_id": "req-11", "data": { "conversation_id": "66666666-6666-6666-6666-666666666666", "status": "STARTING_AGENT", "accepted_at": "2026-09-05T06:40:02Z" } }
```
### 5.3 GET `/device/v2/conversations/{id}`
介绍:读取平台权威会话状态和设备下一步动作。
输入:Path `id`。
输出示例:
```json
{ "request_id": "req-12", "data": { "conversation_id": "66666666-6666-6666-6666-666666666666", "status": "ACTIVE", "action": "continue" } }
```
失败时可能返回 `status=FAILED`、`action=leave_room`、稳定脱敏的 `failure_code`。
### 5.4 POST `/device/v2/conversations/{id}/heartbeat`
介绍:刷新会话租约、设备在线状态和配置状态;返回停止、续签或配置动作。
输入:
```json
{
"sequence_no": 2,
"device_time": "2026-09-05T06:40:12Z",
"monotonic_ms": 142000,
"conversation_status": "ACTIVE",
"rtc": { "joined": true, "uplink": true, "downlink": true },
"network": { "type": "wifi", "rssi": -49 },
"firmware_version": "1.0.0",
"reported_config_revision": 8,
"recording_queue": { "pending_segments": 0, "failed_segments": 0 },
"command_results": []
}
```
输出:
```json
{ "request_id": "req-13", "data": { "lease_expires_at": "2026-09-05T06:40:42Z", "actions": [{ "type": "continue" }], "desired_config_revision": 8, "commands": [] } }
```
### 5.5 POST `/device/v2/conversations/{id}/stop`
介绍:设备挂断并可靠停止旧供应商任务;支持幂等重放。
输入:
```json
{ "reason": "user_hangup", "device_time": "2026-09-05T06:42:00Z", "last_heartbeat_sequence": 5, "rtc_left": true, "recording": { "near_segments": 0, "far_segments": 0 } }
```
输出:
```json
{ "request_id": "req-14", "data": { "conversation_id": "66666666-6666-6666-6666-666666666666", "status": "STOPPING", "accepted_at": "2026-09-05T06:42:00Z" } }
```
### 5.6 POST `/device/v2/conversations/{id}/turns:batch`
介绍:旧 RTC 回调缺少终稿时,由设备补传最终字幕。输入输出与 4.4 完全相同。
```json
{ "turns": [{ "turn_key": "agent-final-2", "sequence_no": 2, "speaker": "agent", "start_at": "2026-09-05T06:41:00Z", "end_at": "2026-09-05T06:41:02Z", "final_text": "您好。", "interrupted": false }] }
```
## 6. 设备录音上传接口
仅当本次 `privacy.upload_allowed=true` 且对应轨道获授权时使用。
### 6.1 POST `/device/v2/conversations/{id}/upload-batches`
介绍:声明待上传录音分片,平台返回每个对象的短期 PUT 地址;业务 API 不接收音频字节。
输入:
```json
{
"segments": [{
"track": "near",
"sequence_no": 0,
"codec": "wav_pcm_s16le",
"content_type": "audio/wav",
"duration_ms": 10000,
"expected_size": 320044,
"expected_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}]
}
```
输出:批次状态和分片;创建响应中的分片包含 `upload_url`、`upload_until`。
```json
{
"request_id": "req-15",
"data": {
"id": "77777777-7777-7777-7777-777777777777",
"conversation_id": "66666666-6666-6666-6666-666666666666",
"status": "UPLOADING",
"segment_count": 1,
"expected_total_size": 320044,
"verified_count": 0,
"rejected_count": 0,
"verification_attempts": 0,
"expires_at": "2026-09-05T07:02:00Z",
"segments": [{ "id": "88888888-8888-8888-8888-888888888888", "track": "near", "sequence_no": 0, "status": "AUTHORIZED", "upload_url": "https://object.example/put?...", "upload_until": "2026-09-05T07:02:00Z" }]
}
}
```
### 6.2 POST `/device/v2/upload-batches/{id}/complete`
介绍:声明分片已上传,平台开始异步对象大小、摘要和容器校验。
输入:无正文;Path `id` 为上传批次 UUID。
输出:HTTP `202`,返回批次,`status` 通常为 `VERIFYING`。
```json
{ "request_id": "req-16", "data": { "id": "77777777-7777-7777-7777-777777777777", "status": "VERIFYING", "segment_count": 1, "verified_count": 0, "rejected_count": 0, "segments": [] } }
```
### 6.3 GET `/device/v2/upload-batches/{id}`
介绍:轮询后台校验结果。只有 `VERIFIED` 分片返回 `local_delete_after`。
输入:Path `id`。
输出示例:
```json
{ "request_id": "req-17", "data": { "id": "77777777-7777-7777-7777-777777777777", "status": "COMPLETED", "segment_count": 1, "verified_count": 1, "rejected_count": 0, "segments": [{ "id": "88888888-8888-8888-8888-888888888888", "status": "VERIFIED", "local_delete_after": "2026-09-05T07:10:00Z" }] } }
```
### 6.4 POST `/device/v2/media-segments/{id}/local-delete-ack`
介绍:到达平台允许删除时间后,设备删除本地密文并回报;不会删除云端对象。
输入:
```json
{ "status": "DELETED" }
```
失败示例:`{ "status": "FAILED", "error_code": "FLASH_DELETE_FAILED" }`。
输出:更新后的分片对象,包含 `local_delete_status` 和 `local_delete_reported_at`。
## 7. 设备固件下载接口
### GET `/device/v2/firmware-releases/{id}/download`
介绍:仅向当前灰度批次中的目标设备签发 5 分钟私有下载地址。
输入:Path `id` 为固件版本 UUID,无正文。
输出:
```json
{ "request_id": "req-18", "data": { "download_url": "https://object.example/firmware.bin?...", "download_until": "2026-09-05T07:05:00Z" } }
```
非目标设备或暂停/退役版本返回 `403 FIRMWARE_DOWNLOAD_DENIED`。
## 8. 用户 App/Web 设备管理接口
统一输入 Header:`Authorization: Bearer <OIDC access token>`。以下表中“示例输入/输出”均省略通用包络字段。
| 方法与路径 | 接口介绍 | 输入 | 输出 | 示例 |
|---|---|---|---|---|
| `POST /api/v1/device-claims` | 消费一次性绑定码 | Body:`claim_code`、`household_id`、`subject_id` | `binding_id`、`device_id`、归属、`bound_at` | 入:`{"claim_code":"CLAIM-...","household_id":"<uuid>","subject_id":"<uuid>"}`;出:`{"binding_id":"<uuid>","device_id":"<uuid>","bound_at":"2026-09-05T07:00:00Z"}` |
| `GET /api/v1/devices` | 列出当前账号可管理设备 | 无 | `items: ManagedDevice[]` | `{"items":[{"id":"<uuid>","model":"echoear","status":"registered","desired_revision":8}]}` |
| `GET /api/v1/devices/{id}` | 查询设备与绑定摘要 | Path:设备 UUID | `ManagedDevice` | `{"id":"<uuid>","binding_id":"<uuid>","subject_name":"张大妈","firmware_version":"1.0.0","version":3}` |
| `GET /api/v1/devices/{id}/binding-impact` | 解绑/转移前检查阻断项 | Query:`action=unbind\|transfer`;转移时 `target_subject_id` | 影响、`allowed`、`blockers`、当前 `binding_id` | `{"action":"transfer","allowed":true,"pending_command_count":0,"active_firmware_task_count":0}` |
| `POST /api/v1/devices/{id}/binding-change` | 执行解绑或转移 | Body:`action`、`expected_binding_id`、`reason`、`acknowledged=true`;转移时 `target_subject_id` | 旧/新绑定和变更时间 | 入:`{"action":"unbind","expected_binding_id":"<uuid>","reason":"用户解除绑定","acknowledged":true}`;出:`{"device_id":"<uuid>","action":"unbind","changed_at":"..."}` |
| `GET /api/v1/devices/{id}/presence` | 查询实时在线状态 | Path:设备 UUID | `online`、`status`、`last_seen_at`、`source` | `{"device_id":"<uuid>","online":true,"status":"ACTIVE","last_seen_at":"...","source":"redis"}` |
| `GET /api/v1/devices/{id}/twin` | 查询设备影子 | Path:设备 UUID | 期望/上报配置、版本、能力、同步状态;Header `ETag` | `{"device_id":"<uuid>","desired_revision":8,"desired_config":{"volume":{"speaker":70}},"sync_state":"applied"}` |
| `PATCH /api/v1/devices/{id}/twin` | 修改非敏感期望配置 | Header:`If-Match`;Body:`desired_config`,顶层仅 `volume/interaction/upload/rtc/policy_refs` | 更新后的 `DeviceTwin` 和新 `ETag` | `{"desired_config":{"volume":{"speaker":60}}}` |
| `GET /api/v1/devices/{id}/commands` | 查询最近 100 条命令 | Path:设备 UUID | `items: DeviceCommand[]` | `{"items":[{"id":"<uuid>","type":"reboot","status":"SUCCEEDED"}]}` |
| `POST /api/v1/devices/{id}/commands` | 下发可靠命令 | Header:`Idempotency-Key`;Body:`type`、`payload`、`expires_in_sec` | `DeviceCommand`,首次 `202`,重放 `200` | 入:`{"type":"reboot","payload":{"reason":"maintenance"},"expires_in_sec":300}`;出:`{"id":"<uuid>","status":"QUEUED","delivery_attempts":0}` |
`ManagedDevice` 主要输出字段:`id`、`binding_id`、`household_id`、`subject_id`、`subject_name`、`model`、`hardware_revision`、`status`、固件/配置版本、`last_reported_at`、`bound_at`、`registered_at`、`version`。
## 9. 平台运维设备管理接口
统一输入 Header:`Authorization: Bearer <operator token>`、`X-Tenant-Id: <tenant UUID>`。只有独立运维账号可调用。
| 方法与路径 | 接口介绍 | 输入 | 输出 | 示例 |
|---|---|---|---|---|
| `GET /api/v1/ops/devices` | 查询租户全部设备 | 无 | `items: OpsDevice[]`,不返回密钥 | `{"items":[{"id":"<uuid>","model":"echoear","activation_state":"ACTIVE","credential_status":"active","voice_runtime":"volc_hardware_agent"}]}` |
| `POST /api/v1/ops/devices` | 登记新设备并生成平台 HMAC 密钥 | Body:`model` 必填;可选硬件/最低版本/归属/运行时/初始激活状态 | `device`、只显示一次的 `credential`;可能同时返回 `claim` | 入:`{"model":"echoear","hardware_revision":"esp32-s3","voice_runtime":"volc_hardware_agent","activation_state":"FACTORY"}`;出:`{"device":{"id":"<uuid>"},"credential":{"device_id":"<uuid>","secret":"<64位hex>","key_version":1}}` |
| `GET /api/v1/ops/device-assignment-options` | 查询可分配家庭和长者 | 无 | `items[]` 含家庭/长者 ID 和名称 | `{"items":[{"household_id":"<uuid>","household_name":"张家","subject_id":"<uuid>","subject_name":"张大妈"}]}` |
| `PATCH /api/v1/ops/devices/{id}` | 更新设备型号、版本策略、状态、归属或运行时 | Header:`If-Match`;Body 至少一个可修改字段 | 更新后的 `OpsDevice` | `{"minimum_firmware_version":"1.1.0","status":"registered"}` |
| `DELETE /api/v1/ops/devices/{id}` | 软删除并吊销设备/凭据/绑定 | Header:`If-Match` | 吊销后的 `OpsDevice` | `{"id":"<uuid>","status":"revoked","activation_state":"REVOKED","credential_status":"revoked"}` |
| `POST /api/v1/ops/devices/{id}/credentials/rotate` | 轮换平台 HMAC 密钥,旧密钥立即失效 | 无正文 | 只显示一次的新 `secret`、`key_version`、`issued_at` | `{"device_id":"<uuid>","secret":"<64位hex>","key_version":2,"issued_at":"..."}` |
| `POST /api/v1/ops/devices/{id}/claims` | 为未绑定设备生成 24 小时绑定码 | 无正文 | 只显示一次的 `claim_code` 和过期时间 | `{"device_id":"<uuid>","claim_code":"CLAIM-...","issued_at":"...","expires_at":"..."}` |
| `POST /api/v1/ops/devices/{id}/activation-transitions` | 执行受控激活状态迁移 | Body:`target_state`、`reason`、`expected_revision` | 更新后的 `OpsDevice` | `{"target_state":"CLAIMABLE","reason":"工厂质检通过","expected_revision":1}` |
| `GET /api/v1/ops/devices/{id}/activation-transitions` | 查询完整激活审计历史 | Path:设备 UUID | `items: DeviceActivationTransition[]` | `{"items":[{"from_state":"FACTORY","to_state":"CLAIMABLE","reason":"工厂质检通过","activation_revision":2,"created_at":"..."}]}` |
激活状态:`FACTORY → CLAIMABLE → BOUND → PROVISIONING → ACTIVE → LOST/REVOKED`。设备不能自行修改该状态。
## 10. 火山硬件智能体 WebSocket
### 10.1 建连
```text
wss://ai-gateway.vei.volces.com/v1/realtime?bot=botM4gWQ9A9y&wait_for_session_update=true
```
输入 Header:
| Header | 说明 |
|---|---|
| `X-Auth-Type` | 固定 `1` |
| `X-Product-Key` | 平台预配返回的 Product Key |
| `X-Device-Name` | 平台预配返回的 Device Name |
| `X-Random-Num` | 本次随机整数 |
| `X-Timestamp` | Unix 秒 |
| `X-Instance-Id` | 平台预配返回的 Instance ID |
| `X-Signature` | 使用火山 Device Secret 计算的 HMAC-SHA256 Base64 |
| `X-Hardware-Id` | 稳定 Hardware ID |
签名正文顺序固定:
```text
auth_type=1&device_name={name}&random_num={random}&product_key={key}&timestamp={unix}&instance_id={instance}
```
### 10.2 动态配置输入
连接打开后立即发送:
```json
{
"event_id": "windows-session-update-<uuid>",
"type": "session.update",
"session": {
"object": "realtime.session",
"model": "",
"input_audio_format": "pcm16",
"output_audio_format": "pcm16",
"config": {
"ASRConfig": { "TurnDetectionMode": 0 },
"LLMConfig": { "SystemMessages": ["<平台冻结的人设与授权上下文>"] }
},
"agent_config": { "EnableConversationStateCallback": false }
}
}
```
输出:`session.created` 或 `session.updated`;在收到确认前不得开启麦克风。
### 10.3 音频和文本输入
上行音频:PCM16 little-endian、16kHz、单声道,建议每 20ms 320 个采样。
```json
{ "event_id": "audio-<uuid>", "type": "input_audio_buffer.append", "audio": "<PCM Base64>" }
```
文本测试:
```json
{
"event_id": "text-<uuid>",
"type": "conversation.item.create",
"item": { "type": "message", "role": "user", "content": [{ "type": "input_text", "text": "你好" }], "interrupt_mode": 1 }
}
```
### 10.4 设备消费的输出事件
| 事件 | 输出内容 | 设备动作 |
|---|---|---|
| `input_audio_buffer.speech_started/stopped` | 服务端 VAD 状态 | 更新交互状态 |
| `conversation.item.input_audio_transcription.completed` | 用户最终字幕 | 上传平台 `turns:batch` |
| `response.audio.delta` | Base64 PCM16、16kHz、单声道 | 连续缓冲并按声卡采样率重采样播放 |
| `response.audio.done` | 本轮音频结束 | 排空播放缓冲 |
| `response.audio_transcript.delta/done` | 智能体增量/最终字幕 | 只把最终字幕上传平台 |
| `response.function_call_arguments.done` | 工具名、参数和 `call_id` | 调用平台受控工具并回传结果 |
| `response.done` | 本轮完成 | 继续监听 |
| `error` | 供应商错误 | 脱敏后上报 `session_failed` |
注意:low-load WebSocket 下行是 16kHz。把它按 24kHz 播放会出现约 1.5 倍速、音调升高和明显电子感。
## 11. 固件管理接口
### 11.1 家庭/租户授权接口
| 方法与路径 | 介绍 | 输入 | 输出 |
|---|---|---|---|
| `GET /api/v1/households/{id}/firmware-signing-keys` | 查询可信 Ed25519 公钥 | Path `household id` | `FirmwareSigningKey[]` |
| `POST /api/v1/households/{id}/firmware-signing-keys` | 登记签名公钥 | `key_id`、`public_key_base64` | `201`,创建结果 |
| `POST /api/v1/households/{id}/firmware-signing-keys/revoke` | 吊销公钥 | `key_id` | `FirmwareSigningKey` |
| `GET /api/v1/households/{id}/firmware-releases` | 查询固件版本 | Path `household id` | `FirmwareRelease[]` |
| `POST /api/v1/households/{id}/firmware-releases` | 创建 `UPLOADING` 版本 | `PrepareFirmwareReleaseRequest` | 版本和短期上传地址 |
| `GET /api/v1/firmware-releases/{id}/impact` | 发布/暂停/退役前影响评估 | Path `release id` | `FirmwareReleaseImpact` |
| `POST /api/v1/firmware-releases/{id}/upload-session` | 重新签发上传地址 | 无正文 | `FirmwareRelease` |
| `POST /api/v1/firmware-releases/{id}/finalize` | 校验大小、SHA-256、Ed25519 后发布 | 无正文 | `FirmwareRelease` |
| `POST /api/v1/firmware-releases/{id}/pause` | 暂停版本和未投递命令 | 无正文 | `FirmwareRelease` |
| `POST /api/v1/firmware-releases/{id}/resume` | 恢复版本分发 | 无正文 | `FirmwareRelease` |
| `POST /api/v1/firmware-releases/{id}/retire` | 退役版本并保留审计 | 无正文 | `FirmwareRelease` |
| `GET /api/v1/households/{id}/firmware-rollouts` | 查询 OTA 批次 | Path `household id` | `FirmwareRollout[]` |
| `POST /api/v1/households/{id}/firmware-rollouts` | 启动灰度升级 | `StartFirmwareRolloutRequest` | `202 FirmwareRollout` |
| `GET /api/v1/firmware-rollouts/{id}` | 查询逐设备升级进度 | Path `rollout id` | `FirmwareRollout` |
| `POST /api/v1/firmware-rollouts/{id}/pause` | 暂停批次 | 无正文 | `FirmwareRollout` |
| `POST /api/v1/firmware-rollouts/{id}/rollback` | 创建回滚批次 | `rollback_release_id` | 新的 `FirmwareRollout` |
### 11.2 运维固件接口
以下接口全部额外要求 `X-Tenant-Id`:
| 方法与路径 | 介绍 | 输入 | 输出 |
|---|---|---|---|
| `GET /api/v1/ops/firmware-signing-keys` | 查询租户可信公钥 | 无 | `FirmwareSigningKey[]` |
| `POST /api/v1/ops/firmware-signing-keys` | 登记可信公钥 | `key_id`、`public_key_base64` | `201` |
| `POST /api/v1/ops/firmware-signing-keys/revoke` | 吊销公钥 | `key_id` | `FirmwareSigningKey` |
| `GET /api/v1/ops/firmware-releases` | 按 `model/status` 查询版本 | Query `model`、`status` 可选 | `FirmwareRelease[]` |
| `POST /api/v1/ops/firmware-releases` | 创建租户级版本 | `PrepareOpsFirmwareReleaseRequest` | `201 FirmwareRelease` |
| `POST /api/v1/ops/firmware-releases/{id}/upload-session` | 续签上传地址 | 无正文 | `FirmwareRelease` |
| `POST /api/v1/ops/firmware-releases/{id}/finalize` | 正式发布 | 无正文 | `FirmwareRelease` |
| `POST /api/v1/ops/firmware-releases/{id}/pause` | 暂停 | 无正文 | `FirmwareRelease` |
| `POST /api/v1/ops/firmware-releases/{id}/resume` | 恢复 | 无正文 | `FirmwareRelease` |
| `POST /api/v1/ops/firmware-releases/{id}/retire` | 退役 | 无正文 | `FirmwareRelease` |
| `GET /api/v1/ops/firmware-releases/{id}/download` | 运维下载制品 | Path `release id` | 下载地址、摘要、签名元数据 |
| `GET /api/v1/ops/firmware-rollouts` | 查询 OTA 批次 | 无 | `FirmwareRollout[]` |
| `POST /api/v1/ops/firmware-rollouts` | 启动 OTA | `StartFirmwareRolloutRequest` | `202 FirmwareRollout` |
| `GET /api/v1/ops/firmware-rollouts/{id}` | 查询逐设备进度 | Path `rollout id` | `FirmwareRollout` |
| `POST /api/v1/ops/firmware-rollouts/{id}/pause` | 暂停 OTA | 无正文 | `FirmwareRollout` |
固件请求示例:
```json
{ "key_id": "release-key-2026-01", "public_key_base64": "<32字节Ed25519公钥的Base64>" }
```
```json
{
"model": "echoear",
"version": "1.1.0",
"expected_size": 2097152,
"expected_digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"signing_key_id": "release-key-2026-01",
"signature": "<firmware-manifest-v1签名Base64>"
}
```
```json
{ "release_id": "<uuid>", "name": "第一批灰度", "target_percentage": 10, "device_ids": ["<device-uuid>"] }
```
典型版本输出:
```json
{ "request_id": "req-fw", "data": { "id": "<uuid>", "model": "echoear", "version": "1.1.0", "status": "READY", "sha256": "bbbb...", "created_at": "2026-09-05T07:00:00Z" } }
```
典型批次输出:
```json
{ "request_id": "req-rollout", "data": { "id": "<uuid>", "release_id": "<uuid>", "name": "第一批灰度", "status": "RUNNING", "target_percentage": 10, "targeted": 1, "succeeded": 0, "failed": 0 } }
```
## 12. 运维诊断设备动作
| 方法与路径 | 介绍 | 输入 | 输出 | 示例 |
|---|---|---|---|---|
| `POST /api/v1/ops/diagnostics/devices/{id}/config-refresh` | 强制设备重新拉取配置 | `X-Tenant-Id`、`Idempotency-Key`、设备 UUID;Body:`reason` | `action=refresh_config`、命令 ID/状态、`queued` | 入:`{"reason":"远端配置与影子不一致"}`;出:`{"action":"refresh_config","device_id":"<uuid>","command_id":"<uuid>","command_status":"QUEUED","queued":true}` |
| `POST /api/v1/ops/diagnostics/devices/{id}/revoke` | 紧急吊销异常设备 | `X-Tenant-Id`、`Idempotency-Key`、设备 UUID;Body:`reason` | `action=revoke_device`、设备 ID、`queued` | 入:`{"reason":"设备密钥疑似泄露"}`;出:`{"action":"revoke_device","device_id":"<uuid>","queued":false}` |
## 13. 主要错误码
| HTTP | 错误码 | 含义与处理 |
|---:|---|---|
| 400 | `VALIDATION_FAILED` | 字段、Header、时间或幂等键不符合契约 |
| 401 | `DEVICE_AUTHENTICATION_FAILED` | 检查平台 Device Secret、时钟、URI、正文摘要和 Base64URL |
| 403 | `DEVICE_ACTIVATION_REQUIRED` | 当前激活状态不允许该操作 |
| 403 | `TRANSCRIPT_STORAGE_NOT_ALLOWED` | 当前授权不允许保存字幕 |
| 403 | `FIRMWARE_DOWNLOAD_DENIED` | 设备不是灰度目标或版本不可下载 |
| 404 | `RESOURCE_NOT_FOUND` | 资源不存在或不属于当前主体/设备 |
| 409 | `IDEMPOTENCY_KEY_REUSED` | 同一幂等键对应不同正文,客户端必须换业务请求 |
| 409 | `DEVICE_ACTIVATION_CONFLICT` | 激活状态或修订号冲突,刷新后再处理 |
| 409 | `HARDWARE_AGENT_HARDWARE_MISMATCH` | Hardware ID 与已注册记录不一致,禁止覆盖 |
| 409 | `HARDWARE_SESSION_PROOF_INVALID` | `session_nonce`/配置摘要/Bot/传输不匹配,丢弃本次会话重新创建 |
| 412 | `VERSION_CONFLICT` | `If-Match` 过期,先重新 GET |
| 426 | `FIRMWARE_UPDATE_REQUIRED` | 当前固件低于最低版本 |
| 429 | `USAGE_QUOTA_EXCEEDED` | 额度或并发限制达到上限 |
| 503 | `HARDWARE_AGENT_PROVISIONING_UNAVAILABLE` | 平台 Product Secret/产品配置不可用 |
| 503 | `HARDWARE_AGENT_PROVISIONING_FAILED` | 火山动态注册失败;按 `request_id` 排障,可退避重试 |
## 14. 设备端推荐调用顺序
```text
上电
→ GET /device/v2/bootstrap
→ GET /device/v2/config
→ GET /device/v2/commands
→ POST /device/v2/telemetry:batch
首次绑定后
→ POST /device/v3/hardware-agent-credentials:provision
→ 安全保存火山 Device Secret
每次聊天
→ POST /device/v3/agent-sessions
→ 连接火山 WebSocket
→ session.update
→ 收到 session.created/session.updated
→ POST /device/v3/agent-sessions/{id}/events (session_started)
→ 16k PCM 双向音频
→ POST /device/v3/agent-sessions/{id}/turns:batch
→ 周期 events (session_updated)
→ events (session_ended/session_failed)
```
## 15. 机器可读契约
- 完整 OpenAPI:[`../openapi/openapi.yaml`](../openapi/openapi.yaml)
- 设备实现与安全说明:[`device-api-reference.md`](device-api-reference.md)
- 设备接入说明:[`device-integration.md`](device-integration.md)
- 固件灰度说明:[`firmware-rollout.md`](firmware-rollout.md)
若本文与 OpenAPI 或运行时代码不一致,以同一发布版本的 OpenAPI 和处理器为准,并应同步修正文档。