设备(Device)
把一台 ESP32、一只机器人或任何 edge 客户端接入 NovaMeow Cloud 的标准姿势:登记设备 → 拿一次性激活码 → 设备凭码自己换一把「设备 key」。设备 key 是这台设备的专属身份——绑定了应用(App)的设备,开口说话就是那个应用的人设;之后它的固件版本与「最后在线」会自动出现在控制台。
概念(30 秒)
- 一次性激活码:登记设备时回显一次的 16 位 base32 码,15 分钟内有效、只能兑换一次;服务端只存它的 sha256 哈希,过期/用过就再也换不出 key;
- 设备 key:激活成功后回显一次的
sk-meow-…,与普通 key 同机制(只存哈希、可限流、可撤销),额外带「设备身份」:绑定了 App 即继承该 App 的全部配置; - 最小权限:设备 key 只能干「设备该干的事」(对话/语音/历史/心跳/知识库只读检索),调任何管理面——
/v1/devices*、/v1/apps*、/v1/provider*、知识库写面(POST/DELETE /v1/kb/docs*)——一律403; - 配额:每租户最多 100 台设备;设备名 ≤60 字。
接入全流程(4 步 curl)
# 1) 登记设备(用租户通用 key;可选 app_id = 设备绑应用)
curl -X POST "$BASE/v1/devices" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"name": "客厅的喵伴", "app_id": "<可选的应用 id>"}'
# → 201 {"id":"<device_id>", "activation_code":"ABCD2345EFGH6789", "code_expires_at":"…", …}
# ⚠ activation_code 只显示这一次,15 分钟内有效
# 2) 设备端激活:码换设备 key(无需任何 key——码即凭证)
curl -X POST "$BASE/v1/devices/activate" \
-H "Content-Type: application/json" \
-d '{"code": "ABCD2345EFGH6789"}'
# → 200 {"device_id":"…", "key":"sk-meow-…", "app_id":…}
# ⚠ key 只显示这一次,设备应把它存进安全存储(NVS/Keychain);码即刻作废
# 3) 设备用自己的 key 对话(绑了 App 即该 App 人设)
curl -N "$BASE/v1/agent" \
-H "Authorization: Bearer <设备 key>" -H "Content-Type: application/json" \
-d '{"text": "你好", "session_id": "device-001"}'
# 4) 心跳(可选):上报固件/型号 + 刷新「最后在线」
curl -X POST "$BASE/v1/devices/heartbeat" \
-H "Authorization: Bearer <设备 key>" -H "Content-Type: application/json" \
-d '{"firmware": "1.2.0", "hw_model": "esp32-s3"}'
# → 200 {"device_id":"…", "last_seen_at":"…"}控制台「设备」区是同一套流程的图形版:登记表单(名称 + 选 App)→ 激活码一次性展示(附上面那条 activate curl)→ 列表带在线点(5 分钟内活跃为绿)/固件/最后在线 → 重发码 / 删除。
激活码的安全语义(务必读)
- 只回显一次:登记 / 重发码的响应是唯一能看到明文码的地方,服务端只存哈希,丢了就「重发码」;
- 15 分钟 TTL + 一次性:过期或已兑换的码永远换不出 key——重放已用的码回
410「激活码已使用」;过期、不存在、被重发码作废的统一回401(不区分原因,防探测); - activate 是全站唯一无鉴权端点:码本身就是凭证,所以它额外有按 IP 的限流(每分钟 10 次),爆破 80 bit 随机码在 TTL 内不可行;
- 大小写不敏感:码恒为大写字母 + 数字 2-7(无易混淆的 0/1),手输小写也认;
- 并发用同一码激活,只有一个成功,其余拿 410。
重发码 = 重新配对
POST /v1/devices/:id/recode(或控制台「重发码」)适用于两种场景:码过期没来得及激活、设备刷机/丢 key 要重新接入。语义是重新配对:
- 设备已有的 key 立刻撤销(在用设备马上 401);
- 旧码作废,生成新码(新的 15 分钟 TTL),设备回到「待激活」;
- 凭新码重新 activate,换出一把全新的设备 key。
所以:对一台正在线上干活的设备点「重发码」,等于把它踢下线重新接——控制台会先弹确认。
设备绑 App:设备说话即应用人设
登记时给 app_id(或控制台下拉选应用),激活换出的设备 key 会继承该 App 的全部配置(提示词/人格、模型、温度、工具白名单、知识库开关——见应用的回落规则)。这正是「一设备一助手」的多租户做法:同一租户 100 台设备可以分别绑 100 个不同分身。
- 不绑 App 的设备 = 租户默认行为(等同通用 key 的对话语义);
app_id写入时不校验存在性(与 App 引用人格同一先例):绑的 App 之后被删,设备照常能聊,只是回落到租户默认行为——不会变砖;- 想换绑:目前删除设备重新登记(设备字段编辑接口在路线图上)。
在线状态与心跳
「最后在线」有两个来源,都不需要你额外做事:
- 设备 key 每次调
/v1/agent,服务端尽力而为刷新last_seen_at(零延迟,不影响对话响应); - 显式
POST /v1/devices/heartbeat(仅设备 key 可调,通用/App key 调它 403):适合静默期保活与上报firmware/hw_model,建议设备每 1~5 分钟一次。
控制台在线点的口径:last_seen_at 距今 <5 分钟显示绿色。
关于 OTA(诚实说明)
本期的 firmware 字段只做跟踪与展示:设备心跳上报什么,控制台就显示什么。固件分发/升级指令下发不在本期范围(那是 edge 仓 + 对象存储的事,评估中)——不要基于这个字段实现"云端控制升级"的预期。
管理 API 一览(双面同形)
机器面 /v1/devices* 用 Bearer 租户通用 key(App/设备 key 一律 403);控制台面 /console/devices* 用登录会话,行为逐字节相同:
| 方法与路径 | 作用 |
|---|---|
GET /v1/devices | 列本租户全部设备(含激活态/固件/最后在线) |
POST /v1/devices | 登记(name 必填,app_id/hw_model 可选)→ 一次性激活码 |
GET /v1/devices/:id | 详情 |
DELETE /v1/devices/:id | 删除:其设备 key 同步撤销(立刻 401);会话历史保留(归租户) |
POST /v1/devices/:id/recode | 重发激活码(重新配对:旧 key 撤销 + 新码一次性回显) |
POST /v1/devices/activate | 设备侧,无鉴权:一次性码兑换设备 key |
POST /v1/devices/heartbeat | 设备侧,仅设备 key:上报固件/型号 + 刷在线 |
常见问题
- 设备 key 和 App key 有什么区别? App key 是「一个分身」的运行凭证(你自己签发、自己分发);设备 key 是「一台设备」的身份(设备凭激活码自己换取),并且额外有心跳/在线跟踪。绑了 App 的设备 key 对话行为与该 App 的 key 完全一致。
- 激活码能重发给同一台设备多次吗? 能,每次重发旧码/旧 key 全部作废,永远只有最新一张码有效。
- 设备 key 泄漏了怎么办? 控制台对那台设备点「重发码」(旧 key 立刻撤销)或直接删除设备;也可以在「API Keys」区按 key 撤销。
- 为什么我的设备调
/v1/devices是 403 不是 401? 401 = key 本身无效(已撤销/拼错);403 = key 有效但它是运行凭证,没有管理权——这正是把 key 烧进设备也不怕它越权的设计。