WiFi 蓝牙绑定流程
约 2511 字大约 8 分钟
2026-07-02
简介
WiFi 蓝牙绑定用于没有屏幕或键盘的设备首次接入。手机小程序通过 BLE 把 WiFi 名称、WiFi 密码和平台绑定 token 写入设备;设备连上路由器后,再通过 MQTT 向联犀平台上报 appBindToken,平台确认后小程序完成设备绑定。
这条链路只把蓝牙作为临时配网通道,设备真正上线和绑定仍以联犀 MQTT 设备身份、设备服务消息和绑定接口为准。设备服务报文可参考 设备服务。
整体流程
BLE 通道
本流程使用的 BLE 配网通道依据 LLSync 蓝牙辅助配网规范整理。为了避免实现时再去翻原始 PDF,本节直接列出本流程需要用到的字段和报文。
LLSync 蓝牙辅助配网只负责网络配置,不直接完成平台设备归属绑定。联犀在设备连上 WiFi 后,通过 MQTT appBindToken 完成平台绑定确认。
| 项目 | 值 |
|---|---|
| Service UUID | 0xFFF0 |
| App 写特征 | 0xFFE1 |
| 设备通知特征 | 0xFFE3 |
| 协议版本 | 0x02 |
| WiFi 模式 | 仅支持 STA:0x01 |
设备广播 manufacturer data 固定携带:
| 字段 | 长度 | 说明 |
|---|---|---|
| Company ID | 2 字节 | BLE manufacturer data 前缀 |
| 协议版本 | 1 字节 | 固定为 0x02 |
| MAC | 6 字节 | 设备 MAC |
| ProductID | 10 字节 | 联犀产品 ID,UTF-8/ASCII 字节,不足 10 字节时可能以 0x00 填充 |
解析偏移示例:
byte[0..1] Company ID
byte[2] 协议版本,当前为 0x02
byte[3..8] MAC
byte[9..18] ProductID,解析后去掉末尾 0x00 填充配网命令
小程序通过 FFE1 写入命令,设备通过 FFE3 通知结果。
| 阶段 | App 写入 | 设备上报 | 成功条件 |
|---|---|---|---|
| 读取设备信息 | E0 | 08 00 LL ... | 能解析设备名、版本、MTU |
| 设置 WiFi 模式 | E1 01 | E0 00 01 00 | result 为 0x00 |
| 写入 WiFi | E2 00 LL SSID/PWD | E1 00 01 00 | result 为 0x00 |
| 请求联网 | E3 | E2 00 LL 01 00 ... | Station 状态为 0x00 |
| 写入 token | E4 00 LL TOKEN | E3 00 01 00 | result 为 0x00 |
| 读取日志 | E5 | E4 00 LL TYPE MSG | 用于失败排查 |
E0 读取设备信息
小程序连接设备并订阅 FFE3 后,先通过 FFE1 写入:
E0设备通过 FFE3 回复:
08 LEN_H LEN_L LLSYNC_VERSION MTU_H MTU_L NAME_LEN DEVICE_NAMEDEVICE_NAME 最长 48 字节。后续平台绑定应使用设备实际返回的设备名,或使用双方约定的 MAC 大写设备名。
E1 设置 WiFi 模式
当前仅支持 STA 模式。
E1 01设备回复:
E0 00 01 RESULTRESULT = 0x00 表示成功,0x01 表示失败。
E2 下发 WiFi 信息
WiFi 信息负载格式:
E2 LEN_H LEN_L SSID_LEN SSID_BYTES PWD_LEN PWD_BYTES其中 LEN 按官方适配器实现只统计 SSID_BYTES.length + PWD_BYTES.length,不包含 SSID_LEN 和 PWD_LEN 两个长度字节。
设备回复:
E1 00 01 RESULTRESULT = 0x00 表示设置成功,0x01 表示失败。
E3 请求 WiFi 连接
小程序写入:
E3设备连接路由器后回复:
E2 LEN_H LEN_L WIFI_MODE STATION_STATUS SOFTAP_STATUS SSID_LEN SSID其中:
| 字段 | 说明 |
|---|---|
WIFI_MODE | 当前仅支持 STA:0x01 |
STATION_STATUS | 0x00 表示已连接,0x01 表示未连接 |
SOFTAP_STATUS | 当前不使用 |
SSID_LEN/SSID | 已连接时返回当前 SSID;未连接时 SSID_LEN = 0 |
E4 下发绑定 token
绑定 token 负载格式:
E4 LEN_H LEN_L TOKEN_BYTES设备拿到 token 后,通过 MQTT 向平台上报 appBindToken。平台返回成功后,设备通过 BLE 回复:
E3 00 01 RESULTRESULT = 0x00 表示 token 处理成功,0x01 表示失败。注意这里的 BLE 成功码仍是 0x00,不要和 MQTT 服务响应里的 code=200 混用。
E5 获取配网日志
配网失败后,小程序可写入:
E5设备通过 E4 返回日志:
E4 LEN_H LEN_L LOG_TYPE MSG_CONTENTLOG_TYPE 含义:
| 值 | 含义 |
|---|---|
0x00 | 设备可能存储的未上报错误日志 |
0x01 | 本次配网产生的错误日志 |
0x02 | 普通配网过程日志 |
小程序侧实现要点
小程序端应以 ProvisionSessionService 作为唯一配网状态机,页面只负责 WiFi 输入、进度展示和结果展示。
const ADV_SERVICE_UUID = '0000FFF0-0000-1000-8000-00805F9B34FB'
const SERVICE_UUID = '0000FFF0-65D0-4E20-B56A-E493541BA4E2'
const WRITE_UUID = '0000FFE1-65D0-4E20-B56A-E493541BA4E2'
const NOTIFY_UUID = '0000FFE3-65D0-4E20-B56A-E493541BA4E2'
function parseAdvertisingData(bytes: number[]) {
const productIDBytes = bytes.slice(9, 19).filter((v) => v !== 0)
return {
protocolVersion: bytes[2],
mac: bytes.slice(3, 9).map((v) => v.toString(16).padStart(2, '0')).join(':'),
productID: new TextDecoder().decode(new Uint8Array(productIDBytes)),
}
}
function buildWifiInfoCommand(ssidBytes: number[], pwdBytes: number[]) {
const len = ssidBytes.length + pwdBytes.length
return new Uint8Array([0xe2, len >> 8, len & 0xff, ssidBytes.length, ...ssidBytes, pwdBytes.length, ...pwdBytes])
}
function buildTokenCommand(tokenBytes: number[]) {
return new Uint8Array([0xe4, tokenBytes.length >> 8, tokenBytes.length & 0xff, ...tokenBytes])
}推荐顺序:
- 扫描
FFF0或解析 manufacturer data,得到ProductID、MAC 和候选设备名。 - 连接设备后订阅
FFE3,再写入E0。 - 收到设备信息后再按
E1 -> E2 -> E3 -> E4顺序推进。 - 设备返回 token 成功后,小程序使用
ProductID + DeviceName + token调用平台绑定接口。 - 任一阶段失败时,优先写入
E5拉取设备日志,再展示失败原因。
设备端绑定报文
设备联网后发布到:
$thing/up/service/{ProductID}/{DeviceName}报文:
{
"method": "appBindToken",
"msgToken": "DEVICE_NAME-123456",
"params": {
"token": "APP_CREATE_BIND_TOKEN"
}
}设备订阅:
$thing/down/service/{ProductID}/{DeviceName}平台响应:
{
"method": "appBindToken",
"msgToken": "DEVICE_NAME-123456",
"code": 200,
"msg": "success"
}设备端只有收到 code == 200 才能把绑定结果视为成功。
MQTT 动态注册与认证
一型一密设备首次联网时,设备本地通常还没有设备级密钥,需要先完成动态注册,再用注册结果发起正式 MQTT 连接。动态注册和正式接入是两套不同的 MQTT 连接与认证规则,不要混用。
动态注册连接
设备本地没有 DevicePsk 时,先使用产品级密钥发起 MQTT 动态注册。
| 项目 | 规则 |
|---|---|
| ClientID | register&{ProductID}&{DeviceName} |
| Username | 可任意填写 |
| Password | 可任意填写 |
| 上行 Topic | $ext/up/register/{ProductID}/{DeviceName} |
| 下行 Topic | $ext/down/register/{ProductID}/{DeviceName} |
| 签名密钥 | 产品密钥 ProductSecret |
| 签名规则 | Base64(HEX(HMAC-SHA1(data, ProductSecret_ASCII))) |
动态注册签名使用 ProductSecret 时,直接使用控制台显示的字符串 ASCII 字节,不要对 ProductSecret 做 Base64 解码。
动态注册成功后,平台返回的 payload 中可能包含:
| 字段 | 说明 |
|---|---|
psk | 签名认证设备使用的设备级密钥 |
clientCert | 证书认证设备使用的客户端证书 |
clientKey | 证书认证设备使用的客户端私钥 |
不要只依赖 encryptionType 判断认证方式。当前版本里 encryptionType 可能固定为 1,设备端应以 psk、clientCert、clientKey 是否为空判断:
| 返回内容 | 认证方式 |
|---|---|
psk 非空,clientCert/clientKey 为空 | 签名认证 |
clientCert/clientKey 非空,psk 为空 | 证书认证 |
正式 MQTT 连接
动态注册拿到设备级密钥或证书后,应断开注册连接,再重新建立正式设备连接。
| 项目 | 规则 |
|---|---|
| ClientID | {ProductID}&{DeviceName} |
| Username | {ProductID}&{DeviceName};12010126;{connid};{expiry} |
| Password | {token};hmacsha256 |
| 签名密钥 | 动态注册返回的 psk,或控制台设备密钥 |
签名认证设备生成 MQTT password 时,应按 mqtt认证 的密钥认证规则处理设备密钥。也就是说,动态注册返回的 psk 是正式 MQTT 认证用的设备级密钥,设备端应按 MQTT 认证文档生成 username/password。
设备端持久化要求
量产设备必须把动态注册返回的 psk、证书或私钥保存到安全存储中,例如 ESP32 NVS 或加密分区。后续重启时,如果本地已经存在设备级密钥,应直接走正式 MQTT 连接,不应每次启动都重复动态注册。
Demo 工程为了方便联调,可能会清空或不持久化设备级密钥,因此重启后再次出现:
dev psk not exist, do dyn reg!这条日志本身不表示失败,只表示当前设备本地没有设备级密钥,正在进入动态注册流程。
ESP32 SDK 示例
ESP32 示例工程为 unitedrhino-iot-ble-esp32。核心改动点:
- BLE 配网通道使用本文上方整理的
FFF0 / FFE1 / FFE3和E0-E5报文。 - WiFi 密码不写入日志,只记录 SSID 和密码长度。
- 设备没有本地
DevicePsk时先走 MQTT 动态注册,注册成功后再走正式 MQTT 认证。 - MQTT 绑定上报使用
appBindToken + msgToken + code=200。
设备端上报 token 的关键代码形态:
HAL_Snprintf(
topic_content,
sizeof(topic_content),
"{\"method\":\"appBindToken\",\"msgToken\":\"%s-%u\",\"params\":{\"token\":\"%s\"}}",
dev_info->device_name,
HAL_GetTimeMs(),
info->token_str
);WiFi 信息日志建议:
ESP_LOGI(TAG, "wifi ssid %s, password length %d", wifi_config.sta.ssid, passwd_len);常见问题
| 现象 | 可能原因 | 排查方式 |
|---|---|---|
| 小程序扫描不到设备 | 设备未广播 FFF0 或 manufacturer data 不完整 | 确认广播中包含协议版本、MAC、ProductID |
| 写入 WiFi 后失败 | SSID/密码长度或编码不正确 | 检查 E2 的 2 字节长度、SSID 长度、密码长度 |
| 设备联网失败 | WiFi 密码错误或路由器不可达 | 看 E2 上报的 Station 状态,并用 E5 拉日志 |
| 动态注册失败 | ProductSecret、签名规则、Broker 域名或注册 Topic 不正确 | 检查 register&ProductID&DeviceName、$ext/up/down/register 和签名 |
| MQTT 认证失败 | 把动态注册签名规则和正式 MQTT 认证规则混用 | 检查正式连接的 clientID、username、password |
| token 阶段失败 | 小程序未创建绑定 token 或设备未上报平台 | 检查 E4 写入内容和 MQTT appBindToken 报文 |
| 平台绑定失败 | ProductID 或 DeviceName 与平台设备身份不一致 | 对比广播 ProductID、设备信息 DeviceName 和平台设备列表 |
更新日志
2026/7/2 18:28
查看所有更新日志
c358e-Merge #4 into master from codex/wifi-ble-bind-doc于
