23 KiB
设备接口文档
适用范围:平台设备管理、EchoEar/ESP32-S3 设备接入、火山引擎硬件对话智能体。
契约基线:当前工作区实际路由、处理器与openapi/openapi.yaml,更新日期 2026-09-05。
生产 API:https://121.41.46.238(正式域名启用后只替换 Base URL,不改变路径)。
1. 接入模型
设备同时使用两套彼此独立的身份:
| 身份/密钥 | 用途 | 生成与保存位置 | 是否下发设备 |
|---|---|---|---|
平台 device_id |
标识平台登记设备 | 平台生成;设备保存公开编号 | 是 |
| 平台 Device Secret | 调用 /device/* 时做 HMAC 鉴权 |
平台逐设备生成;平台加密保存,设备安全区保存 | 是,只在登记/轮换时显示一次 |
火山 instance_id / product_key |
标识火山实例和产品 | 平台配置 | 可随预配响应返回,非秘密 |
| 火山 Product Secret | 调用 DynamicRegister 的产品级秘密 | 只保存在平台 KMS/Secret | 否 |
火山 device_name / device_secret |
设备直连火山时使用的一机一密 | 平台代设备动态注册;平台加密保存,设备安全区保存 | 是,只返回给已认证的对应设备 |
| Bot ID | 选择硬件对话智能体 | 平台按设备绑定 | 会话响应返回,非秘密 |
核心原则:设备固件不内置 Product Secret。设备先用平台 Device Secret 认证,由平台代设备调用火山动态注册,再把该设备专属的火山 Device Secret 返回给设备。
1.1 火山 License 的准确含义
火山硬件对话智能体的 License 是产品侧可用额度。平台调用 DynamicRegister 时,火山自动消耗当前产品的一份有效额度并返回一机一密;设备不接收一段可安装的 License 明文,也不需要向平台提交 License 安装回执。
因此,当前有效契约中:
- 没有
/device/v3/hardware-agent-license:ack路由; - 没有设备端
license字段; - 没有
AVAILABLE → ALLOCATED → ACTIVE的设备 License 安装状态机; - 平台可以维护额度监控或影子账本,但不能用它替代火山的真实额度,也不能阻断已正确取得一机一密的设备。
火山返回额度耗尽、过期等错误时,平台统一转换为 HARDWARE_AGENT_PROVISIONING_FAILED,同时通过内部日志和请求编号保留供应商错误供运维定位。
2. 通用响应格式
成功响应:
{
"request_id": "req-...",
"data": {}
}
失败响应:
{
"request_id": "req-...",
"error": {
"code": "VALIDATION_FAILED",
"message": "请求参数不正确",
"retryable": false,
"field_errors": [],
"details": {}
}
}
排障时应记录 request_id、HTTP 状态码和稳定错误码,不记录任何 Secret、Token、完整人设或完整对话正文。
3. 设备 HMAC 鉴权
所有 /device/v2/* 和 /device/v3/* 请求必须携带:
| 请求头 | 说明 |
|---|---|
X-Device-Id |
平台设备公开 UUID |
X-Device-Timestamp |
RFC3339 UTC 时间,默认允许前后 5 分钟 |
X-Device-Nonce |
本次请求唯一的 16—128 字符随机值 |
X-Device-Content-SHA256 |
原始 HTTP 正文的 SHA-256 小写十六进制;空正文也要计算 |
X-Device-Signature |
HMAC-SHA256 结果的无填充 Base64URL |
签名原文必须逐字使用实际请求 URI(含查询字符串):
UPPERCASE_HTTP_METHOD
REQUEST_URI_WITH_EXACT_QUERY_STRING
X-Device-Timestamp原值
X-Device-Nonce原值
X-Device-Content-SHA256原值
伪代码:
bodyHash = hexLower(SHA256(rawBody))
canonical = methodUpper + "\n" + requestURI + "\n" + timestamp + "\n" + nonce + "\n" + bodyHash
signature = base64UrlNoPadding(HMAC_SHA256(platformDeviceSecret, canonical))
注意事项:
- JSON 签名后不得重新格式化或改变字段顺序;服务端校验的是收到的原始字节。
- 每次网络重试都生成新的
timestamp、nonce和签名。 - 需要幂等的业务请求保留原
Idempotency-Key和原正文。 - 时间未同步时先通过安全时间源校时;不能关闭时间窗或重放保护。
4. 设备激活状态机
主流程:
FACTORY → CLAIMABLE → BOUND → PROVISIONING → ACTIVE
│
├─→ LOST
└─→ REVOKED
| 状态 | 含义 | 允许的设备操作 |
|---|---|---|
FACTORY |
只有出厂身份,等待质检放行 | 不允许设备 API 鉴权 |
CLAIMABLE |
可生成/消费绑定码 | 启动、配置、命令、遥测;不能通话 |
BOUND |
已绑定家庭和使用人 | 可申请火山一机一密;不能创建会话 |
PROVISIONING |
正在注册或恢复火山运行凭据 | 可重复预配;不能创建会话 |
ACTIVE |
平台身份、绑定和运行凭据就绪 | 全部设备接口 |
LOST |
设备挂失 | 设备鉴权和新会话被拒绝 |
REVOKED |
永久吊销 | 终态,不可恢复 |
代码允许的恢复/解绑边包括:BOUND → CLAIMABLE、PROVISIONING → BOUND、ACTIVE → CLAIMABLE/PROVISIONING/LOST、LOST → CLAIMABLE/ACTIVE;任意非 REVOKED 状态均可受控转为 REVOKED。状态变更必须由平台业务或运维接口执行,设备不能自行声明已激活。
每次变更增加 activation_revision 并保存审计历史。进入 ACTIVE 时,硬件智能体设备必须已经绑定且存在有效火山一机一密。
5. 首次激活完整流程
- 运维调用
POST /api/v1/ops/devices登记设备,取得一次性平台 Device Secret。 - 工厂烧录/安全注入
device_id、平台 Device Secret、API Base URL 和稳定hardware_id;不得烧录 Product Secret。 - 运维将设备从
FACTORY放行到CLAIMABLE,并生成绑定码。 - 用户 App/Web 调用
POST /api/v1/device-claims消费绑定码,平台把设备变为BOUND。 - 设备使用平台 HMAC 调用
POST /device/v3/hardware-agent-credentials:provision。 - 平台把状态推进到
PROVISIONING,自动建立产品默认 Bot/传输/最低 SDK 绑定,再使用服务端 Product Secret 调用火山 DynamicRegister。 - 火山消耗产品 License 额度并返回该
device_name对应的device_secret。 - 平台加密保存火山 Device Secret,并通过
no-store响应返回给同一设备,然后自动完成到ACTIVE的迁移。 - 设备把火山一机一密写入 NVS 加密分区/安全芯片;以后重复预配会返回已保存的同一凭据,不重复注册。
- 每次用户发起聊天时,设备先创建平台硬件智能体会话,注入本次人设和上下文后,再直连火山。
6. 设备端接口总览
6.1 启动、配置、命令和固件(v2 公共能力)
| 方法 | 路径 | 状态要求 | 幂等键 | 作用 |
|---|---|---|---|---|
GET |
/device/v2/bootstrap |
CLAIMABLE/BOUND/PROVISIONING/ACTIVE |
否 | 上报启动版本并取得绑定和兼容性 |
GET |
/device/v2/config |
同上 | 否 | 拉取期望配置;支持 If-None-Match |
GET |
/device/v2/commands |
同上 | 否 | 拉取待执行命令 |
POST |
/device/v2/commands/{id}/ack |
同上 | 否 | 上报命令阶段或终态 |
POST |
/device/v2/telemetry:batch |
同上 | 是 | 批量上报设备遥测 |
GET |
/device/v2/firmware-releases/{id}/download |
同上 | 否 | 下载受控固件制品 |
bootstrap 示例:
GET /device/v2/bootstrap?firmware_version=1.0.0&board_rev=echoear-v1&rtc_sdk_version=1.0.0&codec=pcm_s16le&sample_rate=16000&device_time=2026-09-03T08%3A00%3A00Z&network=wifi
配置拉取建议:首次不带 If-None-Match;保存响应 ETag,后续携带该值。HTTP 304 表示继续使用本地有效配置。
6.2 旧 RTC 通话(v2,仅兼容旧设备)
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/device/v2/conversations |
创建旧 RTC 通话 |
POST |
/device/v2/conversations/{id}/activate |
入房成功后激活供应商任务 |
GET |
/device/v2/conversations/{id} |
查询通话权威状态 |
POST |
/device/v2/conversations/{id}/heartbeat |
通话心跳 |
POST |
/device/v2/conversations/{id}/stop |
停止通话 |
POST |
/device/v2/conversations/{id}/turns:batch |
补传最终字幕 |
新 EchoEar 固件的 voice_runtime=volc_hardware_agent 时不得调用这组 RTC 创建/激活接口。
6.3 录音上传(v2 公共媒体能力)
| 方法 | 路径 | 作用 |
|---|---|---|
POST |
/device/v2/conversations/{id}/upload-batches |
创建分片上传批次 |
GET |
/device/v2/upload-batches/{id} |
查询上传校验状态 |
POST |
/device/v2/upload-batches/{id}/complete |
通知平台分片已上传 |
POST |
/device/v2/media-segments/{id}/local-delete-ack |
回报本地密文已删除 |
只有 ACTIVE 设备且本次隐私策略允许时才能使用;没有录音授权不得采集、缓存或上传 PCM。
7. 火山硬件智能体接口(v3)
7.1 申请火山一机一密
POST /device/v3/hardware-agent-credentials:provision
Idempotency-Key: 2ee3b675-59c0-4d93-a189-6db7f8db3750
Cache-Control: no-store
Content-Type: application/json
状态要求:BOUND、PROVISIONING 或 ACTIVE。
请求:
{
"hardware_id": "echoear-b43a451634a8"
}
hardware_id 必须来自稳定硬件身份(如 eFuse/Chip ID 的规范化值),设备重启、升级和恢复出厂后保持不变。
成功响应:
{
"request_id": "req-...",
"data": {
"instance_id": "<instance-id>",
"product_key": "<product-key>",
"device_name": "echoear-...",
"hardware_id": "echoear-b43a451634a8",
"device_secret": "<volc-device-secret>",
"credential_ref": "<non-secret-ref>",
"credential_version": 1,
"credential_source": "platform_dynamic_registration",
"issued_at": "2026-09-03T08:00:00Z"
}
}
安全要求:
- 响应带
Cache-Control: no-store和Pragma: no-cache。 device_secret只写入设备安全存储,不进入普通配置文件、串口日志、崩溃转储或平台遥测。credential_source=platform_stored表示幂等返回平台已保存的凭据,不代表重新消耗 License。hardware_id与已注册记录不一致时返回冲突,不允许覆盖原凭据。
常见错误:
| HTTP | 错误码 | 处理 |
|---|---|---|
400 |
VALIDATION_FAILED |
修正 hardware_id 或幂等键 |
409 |
HARDWARE_AGENT_BINDING_REQUIRED |
先在平台绑定 Bot/产品配置 |
409 |
HARDWARE_AGENT_PRODUCT_MISMATCH |
运维核对实例、产品和设备绑定 |
409 |
HARDWARE_AGENT_HARDWARE_MISMATCH |
停止自动重试,核对硬件身份 |
409 |
DEVICE_ACTIVATION_REQUIRED |
先完成用户绑定,进入 BOUND |
503 |
HARDWARE_AGENT_PROVISIONING_UNAVAILABLE |
平台未配置动态注册;联系运维 |
503 |
HARDWARE_AGENT_PROVISIONING_FAILED |
使用原幂等键、原正文和新签名退避重试;由 request_id 查火山原始错误 |
7.2 创建本次硬件智能体会话
POST /device/v3/agent-sessions
Idempotency-Key: d7f08e7a-9e1d-455f-b8ec-a745a34d8790
Content-Type: application/json
状态要求:仅 ACTIVE。每次聊天创建一个新会话,不复用上一通响应。
请求示例:
{
"device_time": "2026-09-03T08:05:00Z",
"monotonic_ms": 123456,
"capabilities": {
"hardware_agent": true,
"sdk_version": "1.0.0-echoear-platform.1",
"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": 1
}
约束:
transports只能包含low_load、high_quality,至少一个。transcript_upload、dynamic_session_config、credential_rotation必须为true。- EchoEar 当前使用
low_load;server_event_callback=false时设备事件和最终字幕上传是权威回流。 config_revision必须使用最近一次/device/v2/config返回的版本。
成功响应核心字段:
{
"request_id": "req-...",
"data": {
"conversation_id": "<uuid>",
"status": "SESSION_READY",
"runtime_type": "volc_hardware_agent",
"runtime": {
"provider": "volcengine",
"bot_id": "<bot-id>",
"transport": "low_load",
"provider_session_ref": "<provider-session-ref>",
"session_nonce": "<memory-only-secret>",
"config_digest": "<sha256-hex>",
"wait_for_session_update": true
},
"session_config": {
"dialog_id": "<uuid>",
"system_prompt": "<本次冻结的人设提示>",
"persona_tone": "<语气>",
"authorized_context": "<已授权背景/近期摘要/长期记忆>",
"prompt_bundle_version": "<version>"
},
"privacy": {
"retention_mode": "transcript_only",
"record_near": false,
"record_far": false,
"upload_allowed": false
},
"session": {
"engine_mode": "volc_hardware_agent",
"heartbeat_interval_sec": 10,
"allocation_expires_at": "2026-09-03T08:10:00Z"
}
}
}
设备处理顺序:
- 只在运行内存保存
session_nonce。 - 使用预配取得的一机一密连接响应指定的 Bot 和传输模式。
- 以
dialog_id作为本次业务对话标识。 - 在开启麦克风前通过 SDK 动态配置或 WebSocket
session.update注入system_prompt、persona_tone和authorized_context。 - 收到火山配置已应用确认后,再上报
session_started。
tool_scope_token 如存在也只能保存在运行内存,并且仅用于响应指定的 tool_url 和本次会话。
7.3 上报会话事件
POST /device/v3/agent-sessions/{conversation_id}/events
Content-Type: application/json
开始事件:
{
"event_id": "evt-start-0001",
"sequence_no": 1,
"event_type": "session_started",
"occurred_at": "2026-09-03T08:05:03Z",
"provider_session_ref": "<response-runtime-provider-session-ref>",
"session_nonce": "<response-runtime-session-nonce>",
"provider_connection_ref": "<actual-volc-connection-ref>",
"bot_id": "<response-runtime-bot-id>",
"transport": "low_load",
"config_digest": "<response-runtime-config-digest>",
"dynamic_config_applied": true
}
后续事件类型:
session_updated:按heartbeat_interval_sec上报存活;sequence_no严格递增。session_ended:正常结束。session_failed:异常结束,可附加稳定、脱敏的error_code。
响应:
{
"request_id": "req-...",
"data": {
"conversation_id": "<uuid>",
"status": "ACTIVE",
"action": "continue",
"duplicate": false,
"accepted_at": "2026-09-03T08:05:03Z"
}
}
action=stop_session 时,设备必须立即关闭麦克风和火山连接,然后补报 session_ended 或 session_failed。相同 event_id 重放时必须保持原正文一致。
7.4 批量上传最终话轮
POST /device/v3/agent-sessions/{conversation_id}/turns:batch
Idempotency-Key: 1710b8cc-9bf2-45e8-a16d-f32c3b80cbbe
Content-Type: application/json
请求:
{
"turns": [
{
"turn_key": "user-final-0001",
"sequence_no": 1,
"speaker": "subject",
"start_at": "2026-09-03T08:05:10Z",
"end_at": "2026-09-03T08:05:12Z",
"final_text": "今天天气怎么样?",
"interrupted": false
},
{
"turn_key": "agent-final-0002",
"sequence_no": 2,
"speaker": "agent",
"start_at": "2026-09-03T08:05:12Z",
"end_at": "2026-09-03T08:05:15Z",
"final_text": "我来帮您看看。",
"interrupted": false
}
]
}
只上传最终字幕;不得上传增量识别结果。单批 1—500 条,speaker 只能为 subject 或 agent。同一 turn_key/sequence_no 的重试必须保持正文完全一致。
成功响应:
{
"request_id": "req-...",
"data": {
"conversation_id": "<uuid>",
"accepted_count": 2,
"duplicate_count": 0
}
}
8. App/Web 的设备接口
以下接口使用用户 OIDC Bearer Token,不使用设备 HMAC:
| 方法 | 路径 | 作用 |
|---|---|---|
POST |
/api/v1/device-claims |
消费绑定码并绑定设备 |
GET |
/api/v1/devices |
列出当前用户可管理设备 |
GET |
/api/v1/devices/{id} |
查看设备详情 |
GET |
/api/v1/devices/{id}/binding-impact |
变更绑定前评估影响 |
POST |
/api/v1/devices/{id}/binding-change |
解绑或改绑 |
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 |
下发设备命令 |
绑定码由平台/运维为 CLAIMABLE 设备生成,App 只消费,不自行生成。解绑会影响设备激活状态和火山运行凭据,必须先调用 binding-impact 展示影响并要求用户确认。
9. 平台运维设备接口
所有接口要求 OIDC Bearer Token 和有效的 X-Tenant-Id: <tenant-uuid>,且仅独立运维账号可调用。
| 方法 | 路径 | 作用 |
|---|---|---|
GET |
/api/v1/ops/devices |
设备列表 |
POST |
/api/v1/ops/devices |
登记设备并一次性返回平台 Device Secret |
GET |
/api/v1/ops/device-assignment-options |
可选家庭/长者列表 |
PATCH |
/api/v1/ops/devices/{id} |
修改设备;必须携带 If-Match |
DELETE |
/api/v1/ops/devices/{id} |
吊销设备;必须携带 If-Match |
POST |
/api/v1/ops/devices/{id}/credentials/rotate |
轮换平台 Device Secret,只返回一次明文 |
POST |
/api/v1/ops/devices/{id}/claims |
生成一次性绑定码 |
POST |
/api/v1/ops/devices/{id}/activation-transitions |
人工迁移激活状态 |
GET |
/api/v1/ops/devices/{id}/activation-transitions |
查询激活迁移历史 |
9.1 登记硬件智能体设备
{
"model": "echoear",
"hardware_revision": "esp32-s3",
"minimum_security_version": "1",
"minimum_firmware_version": "1.0.0",
"household_id": "",
"subject_id": "",
"voice_runtime": "volc_hardware_agent",
"activation_state": "FACTORY"
}
Instance ID、Product Key、默认 Bot、传输模式和最低 SDK 由平台产品配置统一持有。设备第一次预配时自动生成绑定,运维创建设备或设备自身都不能覆盖这些字段。
响应中的 data.credential.secret 只显示一次。运维系统应立即通过受控注入工具写入设备安全区,不得复制到工单、聊天记录或普通数据库字段。
9.2 人工迁移激活状态
{
"target_state": "CLAIMABLE",
"reason": "工厂质检通过",
"expected_revision": 1
}
并发控制:列表/详情返回的设备版本对应 ETag;修改和删除使用 If-Match。激活迁移单独使用 expected_revision。版本不一致返回 412 VERSION_CONFLICT 或 409 DEVICE_ACTIVATION_CONFLICT,刷新后再决定,不能强制覆盖。
10. 幂等、重试和本地持久化
| 数据 | 本地策略 |
|---|---|
| 平台 Device Secret | 安全区长期保存;轮换时原子替换 |
| 火山 Device Secret | 安全区长期保存;不要每次启动都预配 |
session_nonce |
仅本次通话运行内存 |
tool_scope_token |
仅本次通话运行内存 |
| 最终字幕待上传队列 | 授权允许时加密持久化,成功确认后删除 |
| 增量字幕/原始音频 | 默认不长期保存 |
重试规则:
Idempotency-Key长度 16—128 字符。- HTTP 超时或
retryable=true:原业务正文 + 原幂等键 + 新 HMAC nonce/signature,指数退避。 401:先检查平台 Device Secret、时钟、请求 URI、正文摘要和 Base64URL 编码,不要盲目轮换密钥。409 IDEMPOTENCY_KEY_REUSED:同一幂等键配了不同正文;这是客户端错误。HARDWARE_SESSION_PROOF_INVALID:丢弃当前会话内存数据,重新创建新会话,不尝试其他设备的 nonce。action=stop_session:优先停止音频和供应商连接,再做结束确认及本地队列收尾。
11. EchoEar 固件接入清单
出厂只写入:
- 平台
device_id; - 平台 Device Secret;
hardware_id;- API Base URL 和服务器信任锚;
- 固件签名信任锚。
首次绑定后设备取得并安全保存:
- 火山
instance_id; - 火山
product_key; - 火山
device_name; - 火山 Device Secret;
credential_version。
绝不写入设备:
- 火山 Product Secret;
- 平台数据库主密钥/KMS 密钥;
- 其他设备的 Device Secret;
- 固定的会话 nonce 或工具 Token;
- 人设、长期记忆和聊天记录的永久明文副本。
会话侧必须做到:
- 每通先请求平台会话配置,再连接火山;
- 人设、近期摘要和长期记忆以本次
session_config为准; - 动态配置确认前不打开麦克风;
- 设备事件和最终字幕可靠回流平台;
- 平台下发
stop_session时可在一个心跳周期内停止。
12. 当前实现边界
- 当前 API 已实现平台 HMAC、激活状态机、火山动态注册、v3 会话控制面、事件回流和最终字幕上传。
- 设备音频是设备直连火山,平台不代理每一帧音频;平台通过设备事件和字幕回流维护会话权威状态。
- Windows/EchoEar 客户端仍需按目标 SDK 完成真实音频 HAL、火山协议、断线重连和安全存储验收。
/device/v3/hardware-agent-license:ack处理器代码虽曾存在,但没有路由、没有生产契约,也不应由设备调用。- 完整字段定义以
openapi/openapi.yaml为机器可读基准;本文件是设备研发和联调的实施说明。
13. 联调最小验收用例
- 错误平台签名返回
401,日志不泄露设备是否存在。 - 同 nonce 重放被拒绝;相同业务幂等键配新 nonce 可安全重试。
CLAIMABLE设备不能预配,BOUND设备可以预配但不能创建会话。- 首次预配成功取得火山一机一密;重复预配不重复注册。
- Product Secret 在设备固件、NVS、日志和网络响应中均不存在。
ACTIVE设备创建 v3 会话并得到正确 Bot ID、人设和授权上下文。- 未确认动态配置的
session_started被拒绝。 - 正常开始、心跳、最终字幕、结束全部回流平台。
- 平台返回
stop_session后设备关闭连接并补报结束。 - 火山 License 额度耗尽时预配失败,扩容后使用原幂等请求可恢复;设备无需安装 License 或 ACK。