# 设备配网、绑定与管理接口(BLE 明文 v2) 更新时间:2026-09-30。对应小程序 `wx4553119e05f11660`。先添加设备、后创建家庭老人,见 [账号优先流程](device-account-first-20260918.md)。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 窗口结束而删除任务。出门前检查办法见 [重新联网与配网检查](device-reconnect-20260930.md)。 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`。客户端重连后查询结果,不把超时当作未执行。 ### 获取设备信息 输入: ```json {"cmd":"get_info"} ``` 输出: ```json {"device_id":"KP01-B43A451634A8","model":"ECHOEAR-S3","protocol_version":2} ``` 微信扫描返回的 `deviceId` 是手机系统的 BLE 地址/标识,只用于微信蓝牙 API;上面的 `device_id` 是工厂编号,两者不要混用。信息是设备自报,明文协议不提供无线设备身份认证。是否绑定和账号权限以服务器为准。 ### 发送配网信息 未绑定: ```json {"cmd":"configure","wifi_ssid":"HomeWiFi","wifi_password":"Example123","bind_token":"<服务器返回的43字符令牌>"} ``` 已经绑定,仅修改 Wi-Fi: ```json {"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 接口 共同请求头: ```http Authorization: Mini customer. 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: ```json {"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: ```json {"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`) 输入: ```json {"action":"unbind","expected_binding_id":"<影响检查返回的binding_id>","acknowledged":true,"reason":"小程序设备管理解除绑定"} ``` 输出 data: ```json {"device_id":"<设备UUID>","action":"unbind","previous_binding_id":"<旧绑定UUID>","changed_at":"<时间>"} ``` 服务器再次校验权限、当前绑定版本及阻塞任务。旧绑定不删除,而是记录解除时间;历史通话保留,旧设备运行权限/待绑定状态按现有生命周期机制失效。需要重新绑定后使用,语音服务可能需要重新开通。小程序刷新列表后设备消失。 ### HTTP 响应包装与错误 `/mini/*` 和 `/device/ble-*`: ```json {"ok":true,"code":"OK","request_id":"","server_time_ms":1789280000000,"data":{}} ``` 错误:`{"ok":false,"code":"DEVICE_UNAVAILABLE","message":"...","request_id":""}`,必须同时检查 HTTP 状态。 已有 `/devices/*` 管理接口成功包装为 `{"request_id":"","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":"","client_nonce":"<22字符随机串>"}`。 输出不是普通信封:`{"payload_b64":"","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 `、`Content-Type: application/json`。 签名内容逐行拼接,无结尾换行: ```text DEVREQ1 POST <工厂设备编号> <毫秒时间戳> <原始请求体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` 接口仅为历史兼容保留,当前客户端不调用它。 - 真机小程序访问后端仍受微信合法域名/开发调试设置限制;本次接入不等于解决域名备案和正式发布审核。