Files
story/docs/device-mini-ble-v2.md
T
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

14 KiB
Raw Blame History

设备配网、绑定与管理接口(BLE 明文 v2)

更新时间:2026-09-30。对应小程序 wx4553119e05f11660。先添加设备、后创建家庭老人,见 账号优先流程。SaaS 家庭端的“添加设备”也使用本页 BLE v2 协议,不再展示绑定码入口。

后端地址:https://82.157.201.114。小程序代码:C:\Users\19926\WeChatProjects\voicelink。

1. 完整流程

  1. 登录家庭账号。在“设备”页选择“添加设备”,或展开已有设备的“设备管理 → 重新配网”。
  2. 设备首次使用/升级到 v2 后进入蓝牙配网;完成过 v2 配网的设备长按屏幕底部中央 3 秒重新进入。语音服务未就绪时也可双击屏幕,正常聊天就绪时双击会发起聊天。不要长按电源键。配网广播窗口 5 分钟。已有 BLE 工厂身份的设备开机连不上旧 Wi-Fi 时自动重启进入 BLE;已完成配网的设备进入 BLE 后没有确认新任务,窗口过期会恢复旧 Wi-Fi,保留账号绑定。
  3. 小程序按固定服务 UUID 搜索蓝牙,不按名称过滤。当前固件命名为 vl-型号-xxxxxxxx,本机为 vl-ECHOEAR-S3-26091601;xxxxxxxx 为工厂编号后 8 位。广播型号最多 17 个 UTF-8 字节,完整型号由 get_info 返回。
  4. 点击设备:连接 BLE → 获取服务和特征 → 订阅 TX → 发送 get_info,得到设备编号、型号、协议版本。没有加密握手,也不调用旧 /mini/ble-session。
  5. 小程序调用 /mini/ble-device-state,确认当前账号有权限,并显示是否已经绑定。点击发送时再查询一次,避免使用过期状态。
  6. 未添加:只传设备编号申请 bind_token,不要求家庭或老人;已有账号/家庭归属:不申请令牌,使用空字符串 ""。添加后可创建家庭和老人,再从设备管理中的“绑定老人”完成分配。
  7. BLE 发送 Wi-Fi 和 bind_token。设备回复 received,等待用户在 60 秒内轻点设备屏幕。
  8. 设备确认后连接 Wi-Fi。有令牌时调用 /device/ble-bind;空令牌时调用只读 /device/ble-state,必须仍为已绑定才保存 Wi-Fi。
  9. 设备成功保存后返回 success,约 15 秒后重启。首次绑定可同时轮询云端绑定结果;仅配网以设备 BLE 成功响应为准。断线不能当作成功。
  10. 解绑:小程序先检查影响 → 弹窗确认 → 调用已有绑定变更接口。历史记录不删除;解除账号归属不远程清除设备保存的 Wi-Fi。

普通运行断网会先快速重连 5 次,然后每 10 秒继续尝试旧 SSID;恢复 IP 后重置重试次数。新地点/热点名称或密码不同需要按第 2 步重新配网,不会从远程云端获得新 Wi-Fi。已实体确认且持久化的任务遇到平台超时会继续重试,不因 5 分钟 BLE 窗口结束而删除任务。出门前检查办法见 重新联网与配网检查。

SaaS 家庭端的实体操作顺序相同:点击“搜索附近设备”后,由 Chrome / Edge 的系统蓝牙选择器列出匹配固定服务 UUID 的设备;用户选择一台并读取 get_info,再填写使用者和 Wi-Fi,点击“发送配网信息”。Web Bluetooth 不允许网页静默枚举附近设备,因此设备列表显示在浏览器原生选择器内,而不是网页自绘列表中。SaaS 当前仍显式选择使用者并完成一步绑定;小程序则先添加到账号,之后在设备管理中绑定老人。

空 bind_token 只表示更新 Wi-Fi,不代表解绑,不会改变账号归属,也不等同于省略字段或 null。

2. 蓝牙接口

底层:BLE GATT。业务协议:自定义明文 UTF-8 JSON。无 SRP、AES、session_password、BLE client_nonce/server_nonce/proof,不要求蓝牙配对加密。

用途 UUID 属性
配网服务 8e7a2000-6f4b-4a2d-9c31-7d824f5e6100 Primary Service
RX:手机发给设备 8e7a2001-6f4b-4a2d-9c31-7d824f5e6100 Write,带响应
TX:设备回复手机 8e7a2002-6f4b-4a2d-9c31-7d824f5e6100 Indicate,先订阅

这些 UUID 固定写在固件和客户端中,不是每台设备的身份凭据。

一条完整消息 = JSON 字节长度(uint16,小端,2 字节) + JSON UTF-8 字节。 每次写入/指示最多 20 字节,按顺序切片、拼接。长度头只在整条消息开头出现一次,不带旧版 type 字节。JSON 上限 2048 字节,同时只能有一个请求等待响应。

例如 {"cmd":"get_info"} 长度 18,完整帧前两个字节为 12 00(十六进制)。长度按 UTF-8 字节数计算,不能用中文字符串字符数。

单条消息总超时 45 秒,分片无进展超时 10 秒。长度错误、额外字段、重复字段、无效 JSON 等会导致连接关闭;字段值错误返回 failed。客户端重连后查询结果,不把超时当作未执行。

获取设备信息

输入:

{"cmd":"get_info"}

输出:

{"device_id":"KP01-B43A451634A8","model":"ECHOEAR-S3","protocol_version":2}

微信扫描返回的 deviceId 是手机系统的 BLE 地址/标识,只用于微信蓝牙 API;上面的 device_id 是工厂编号,两者不要混用。信息是设备自报,明文协议不提供无线设备身份认证。是否绑定和账号权限以服务器为准。

发送配网信息

未绑定:

{"cmd":"configure","wifi_ssid":"HomeWiFi","wifi_password":"Example123","bind_token":"<服务器返回的43字符令牌>"}

已经绑定,仅修改 Wi-Fi:

{"cmd":"configure","wifi_ssid":"NewHomeWiFi","wifi_password":"Example456","bind_token":""}
字段 必填 说明
cmd 是 固定 configure
wifi_ssid 是 1–32 个 UTF-8 字节,2.4 GHz Wi-Fi
wifi_password 是 8–63 位可打印 ASCII;本版本不支持开放网络
bind_token 是 未绑定:43 位 base64url;已绑定:空字符串

响应:{"status":"received"}。**只代表收到信息,不代表配网/绑定成功。**账号登录密码、手机号、长期登录 token 不发送给设备。Wi-Fi 密码不发送给后端。

查询配网结果

输入:{"cmd":"get_status"}。

输出:{"status":"wifi_connecting"};失败时如 {"status":"failed","message":"设备已解绑,请重新搜索并绑定账号"}。

status 含义
idle 等待发送配网信息
received 已收到,等待轻点设备屏幕
wifi_connecting 正在连接 Wi-Fi
binding 正在提交绑定,或检查已有绑定状态
success 操作成功且 Wi-Fi 已保存
failed 明确失败,查看 message

确认前不连接新 Wi-Fi。任务进行中重复发送相同内容只返回现有状态,不再创建任务;不同内容被拒绝。确认后暂存任务,断电可重试;首次绑定重试使用同一令牌和确认事件 ID,避免重复绑定。网络异常保留任务,4xx 明确拒绝(408/429 除外)返回失败。

3. 小程序调用的 HTTPS 接口

共同请求头:

Authorization: Mini customer.<access_token中的随机部分>
Content-Type: application/json
X-Request-ID: <每次POST生成的UUID v4>
X-Login-Portal: customer

登录接口返回的 access_token 本身已含 customer. 前缀,实际拼接为 Mini + access_token,不重复加前缀。使用已有手机号密码登录;微信手机号授权登录不在此次改动中。

3.1 查询绑定状态(新增)

POST /api/v1/mini/ble-device-state

输入:{"device_id":"KP01-B43A451634A8"}。

输出 data:

{"device_id":"KP01-B43A451634A8","id":"<平台设备UUID>","model":"ECHOEAR-S3","bound":false}

bound=true 时仅允许所属家庭中有设备管理权限的账号访问;不向无权限账号披露设备归属。id 用于设备管理 URL。未绑定也要求同租户有效家庭管理权限。403 时停止,不向 BLE 发送 Wi-Fi。

3.2 申请绑定令牌(仅未绑定)

POST /api/v1/mini/ble-bind-tokens

小程序输入:{"device_id":"KP01-B43A451634A8"}。

输出 data:{"bind_token":"<43字符令牌>"},有效期 5 分钟。小程序省略 elder_id,只把设备添加到当前账号;SaaS 家庭端显式传入已选择的 elder_id,一步完成使用者绑定。

已绑定返回 409 ALREADY_BOUND,不会覆盖归属。服务器锁定设备并再次检查,所以查询和申请之间发生绑定也不会重复绑定。

3.3 查询绑定结果(仅有令牌时)

POST /api/v1/mini/ble-bind-status

输入:{"bind_token":"<43字符令牌>"}。

输出 data:{"status":"pending"}、{"status":"success"} 或 {"status":"failed","message":"..."}。客户端约每 2.5 秒查询。仅重新配网时不调用此接口,避免传空令牌。

3.4 已绑定设备列表、详情

GET /api/v1/devices → data 为 {"items":[...]}。

GET /api/v1/devices/{id} → data 为设备详情。

主要字段:id(平台设备 UUID)、binding_id(当前绑定 UUID)、model、subject_id、subject_name、household_id、status、firmware_version。可能带 activation_state 等业务字段。不要把“已绑定/已开通”当成实时在线状态。

3.5 解绑影响检查

GET /api/v1/devices/{id}/binding-impact?action=unbind

输入:URL 中的平台设备 UUID。主要输出 data:

{"device_id":"<设备UUID>","binding_id":"<当前绑定UUID>","action":"unbind","allowed":true,"blockers":[],"pending_command_count":0,"active_firmware_task_count":0,"source_household_id":"<家庭UUID>","source_subject_id":"<使用者UUID>","source_subject_name":"家人","evaluated_at":"<时间>"}

有进行中的通话、有效待执行指令、固件任务等时 allowed=false;小程序不发送解绑。

3.6 确认解绑

POST /api/v1/devices/{id}/binding-change(注意是单数 binding-change)

输入:

{"action":"unbind","expected_binding_id":"<影响检查返回的binding_id>","acknowledged":true,"reason":"小程序设备管理解除绑定"}

输出 data:

{"device_id":"<设备UUID>","action":"unbind","previous_binding_id":"<旧绑定UUID>","changed_at":"<时间>"}

服务器再次校验权限、当前绑定版本及阻塞任务。旧绑定不删除,而是记录解除时间;历史通话保留,旧设备运行权限/待绑定状态按现有生命周期机制失效。需要重新绑定后使用,语音服务可能需要重新开通。小程序刷新列表后设备消失。

HTTP 响应包装与错误

/mini/* 和 /device/ble-*:

{"ok":true,"code":"OK","request_id":"<UUID>","server_time_ms":1789280000000,"data":{}}

错误:{"ok":false,"code":"DEVICE_UNAVAILABLE","message":"...","request_id":"<UUID>"},必须同时检查 HTTP 状态。

已有 /devices/* 管理接口成功包装为 {"request_id":"<UUID>","data":{...}},错误使用已有 API 错误包装。401 重新登录;403 无权限;409 状态冲突,刷新后再操作;429 稍后重试。不要循环重发解绑。

4. 设备调用的后端接口

设备联网后继续使用已有 HTTPS + 工厂密钥 HMAC,不是蓝牙加密。此次不删除后端鉴权,工厂密钥不得交给小程序。

4.1 校时

POST http://121.41.46.238/api/v1/device/time-challenges

输入:{"device_id":"<工厂编号>","key_version":1,"boot_id":"<UUID v4>","client_nonce":"<22字符随机串>"}。

输出不是普通信封:{"payload_b64":"<base64url编码JSON>","mac_hex":"<64位HMAC>"}。设备校验 TIME1\n + payload_b64 的 HMAC、原样回传的编号/boot_id/client_nonce 和 15 秒时限,从载荷取 server_time_ms 校时。该接口是唯一 HTTP 特例;Wi-Fi 密码、绑定令牌不在其中。这里的 nonce 是后端请求校时字段,不是已取消的 BLE 握手。

4.2 绑定

POST https://121.41.46.238/api/v1/device/ble-bind

输入:{"bind_token":"<43字符令牌>"};输出 data:{"status":"success"}。

此接口不接受空令牌,具有代次和幂等检查。

4.3 检查已有绑定(新增,只读)

POST https://121.41.46.238/api/v1/device/ble-state

输入:{};输出 data:{"bound":true} 或 {"bound":false}。

设备收到空 bind_token 时调用它。false 或明确拒绝时不保存新 Wi-Fi,提示重新搜索并绑定;true 时只保存 Wi-Fi。此查询不会创建绑定、改变家人或推进绑定代次。

设备 4.2/4.3 请求头:X-Device-ID、X-Key-Version、X-Timestamp-Ms、X-Nonce(16 字节随机数的 base64url)、X-Request-ID、Authorization: Device-HMAC-SHA256 <hex>、Content-Type: application/json。

签名内容逐行拼接,无结尾换行:

DEVREQ1
POST
<URL路径,不含域名>
<工厂设备编号>
<key_version>
<毫秒时间戳>
<X-Nonce>
<X-Request-ID>
<原始请求体SHA256小写hex>

使用 32 字节工厂密钥做 HMAC-SHA256,结果为小写 hex。重试使用新时间戳/nonce,同一任务保留确认事件 request_id;设备校验服务器 TLS 证书。

5. 安全边界与兼容

  • 明文 BLE 的 Wi-Fi 密码和绑定令牌可能被附近攻击者窃听;设备名字和 get_info 可被仿冒。物理点屏确认不能替代无线加密或密码学身份验证。
  • 云端账号权限保护 HTTP 管理操作;不代表任意第三方 BLE 客户端都必须先登录。持有设备并点屏的人可能为已绑定设备更换 Wi-Fi。归属本身仍受后端规则保护。
  • 保留短配网窗口、物理确认、严格字段校验、不记录密码、HTTPS、工厂 HMAC 和账号权限。解绑不是恢复出厂,不清除本地 Wi-Fi 或工厂身份。
  • v2 客户端和固件必须一起升级;旧版网页/小程序的 Security2 蓝牙客户端不能操作 v2 固件。当前 SaaS 家庭端和小程序均已使用明文 v2;旧 /mini/ble-session 接口仅为历史兼容保留,当前客户端不调用它。
  • 真机小程序访问后端仍受微信合法域名/开发调试设置限制;本次接入不等于解决域名备案和正式发布审核。