Androidiot蓝牙配网简单封装
1.蓝牙配网流程图:

2.蓝牙配网时序图:

Iot_ BLE配网流程:
- APP扫描BLE设备,过滤名称前缀IPC_XX_
- 进入配网页,关闭tange、ali其他配网分支
- APP调用preAdd,云端生成会话,拿到addToken
- BLE下发wifisetup报文(SSID+密码)
- 设备BLE返回0x04(WiFi配置报文已收到)
- BLE下发bindinfo(addToken/userId/deviceMac/homeId等)
- 设备返回0x10成功 /0x11失败(bindinfo接收应答)
- 设备连接WiFi,携带addToken主动向IOT云端注册绑定
- APP轮询pollAddResult(addToken)查询绑定结果
- 云端返回device_id,配网成功
3.iot配网管理整体架构:

4.实现步骤:
ini
用户点击开始配网
↓
进入Iot BLE Flow(暂停 Tuya 旧 BLE)
↓
BLE 连接设备
↓
发送 wifisetup(78 字节)
↓
收到 notify ACK = 0x04
↓
【Bind Phase 开始】
↓
调用 preAdd → 获取 add_token
↓
发送 bindinfo(包含 add_token / uid / deviceId / homeId)
↓
收到 notify ACK = 0x10 / 0x11
↓
开始 pollAddResult(add_token)
↓
轮询成功 → 返回 device_id
↓
UI 显示"添加成功"•
5.iot设备搜索:
5.1 设备搜索流程图:

5.2 核心通信流程图:
CoolKit 设备通信流程
css
flowchart TD
A([connectGatt<br/>ssid, psd, deviceId]) --> B[HTTP 获取 secretKey<br/>GET /v2/device/fdbaseinfo]
B --> C[connectGatt<br/>autoConnect=false<br/>TRANSPORT_LE + PHY_LE_2M]
C --> D{onConnectionStateChange}
D -->|STATE_CONNECTED| E[gatt.discoverServices]
D -->|STATE_DISCONNECTED| F{reConnectTime < 3<br/>且未成功?}
F -->|是| C
F -->|否| G[bleConnStatus = false<br/>结束]
E --> H[onServicesDiscovered<br/>requestMtu512]
H --> I{遍历特征值}
I -->|UUID_WRITE<br/>0000bbb0| J[缓存 writeCharacteristic<br/>延迟2s写入数据]
I -->|UUID_READ<br/>0000bbb1| K[缓存 readCharacteristic<br/>延迟1s开启 Notify]
J --> L[写入 AES加密UUID<br/>握手请求]
K --> M[等待设备上报]
M -->|0x01| N[解析 deviceCode<br/>回传 AESUUID_deviceCode]
N --> M
M -->|0x03| O{data[4] == 0x00?}
O -->|是| P[回写 {4,0,3,0,1,0,5}<br/>启动 WiFi 扫描轮询]
O -->|否| Q[握手失败<br/>bleConnStatus=false]
P --> R[每5s下发扫描指令<br/>直至收到 0x06]
M -->|0x05| S[解析 SSID/BSSID/RSSI<br/>匹配目标 WiFi]
S --> T[构建 WiFi 连接请求包<br/>密码 AES 加密]
M -->|0x06| U[扫描结束<br/>发送 connWifiData]
M -->|0x08| V{WiFi 连接状态}
V -->|0x00| W[连接成功]
V -->|0x02| X[密码错误<br/>bleConnStatus=false]
V -->|其他| Y[连接失败<br/>bleConnStatus=false]
W --> Z[0x0A 上报设备信息<br/>deviceId / apikey / chipId]
Z --> AA[addDevice 注册到 CoolKit 云]
AA --> AB[下发调度服务器地址<br/>0x0B 指令]
AB --> AC[0x0C 配网完成<br/>status=0x00 成功]
AC --> AD[bleConnStatus = true<br/>close GATT]
style AA fill:#d4edda
style G fill:#f8d7da
style X fill:#f8d7da
style Y fill:#f8d7da
TanGe / IoT 设备通信流程

5.3 GATT 服务与特征值:
| 设备类型 | 方向 | UUID | 属性 | 说明 |
|---|---|---|---|---|
| CoolKit | App → 设备 | 0000bbb0-0000-1000-8000-00805f9b34fb |
Write | 指令下发 |
| CoolKit | 设备 → App | 0000bbb1-0000-1000-8000-00805f9b34fb |
Read / Notify | 状态与数据上报 |
| TanGe/IoT | App → 设备 | 00009999-0000-1000-8000-00805f9b34fb |
Write | WiFi 包 / 绑定信息写入 |
| TanGe/IoT | 设备 → App | 00008888-0000-1000-8000-00805f9b34fb |
Read / Notify | ACK / JSON 数据上报 |
5.5 扫描广播包解析(CoolKit PN 值)
| 偏移(相对 AD Type 段) | 长度 | 内容 | 字节序 |
|---|---|---|---|
| +2 ~ +3 | 2 bytes | PN 值 | 小端(需反转读取) |
| +4 | 1 byte | 保留值 | - |
| +5 | 1 byte | 产品类型 | - |
5.5 CoolKit 设备通信协议
通用包结构(设备上报与 App 下发均遵循):
csharp
[0] cmd_id // 指令标识
[1] 0x00 // 保留
[2] len_low // 后续数据长度(低字节)
[3] len_high // 后续数据长度(高字节)
[4..N] payload // 数据载荷
指令集
| cmd_id | 方向 | 指令 | Payload 说明 |
|---|---|---|---|
0x01 |
设备→App | 握手响应 | [len(2B), AES(deviceCode)] |
0x03 |
设备→App | 握手确认 | [status: 0x00=成功] |
0x05 |
设备→App | WiFi 扫描结果 | [ssidLen, ssid..., bssid[6], rssi, isConnect] |
0x06 |
设备→App | WiFi 扫描结束 | 无 payload |
0x08 |
设备→App | WiFi 连接状态 | [status] 0成功 1失败 2密码错 3无AP 4超时 |
0x0A |
设备→App | 设备身份信息 | [deviceIdLen, deviceId..., apikeyLen, apikey..., chipIdLen, chipId...] |
0x0C |
设备→App | 配网完成状态 | [status: 0x00=成功] |
0x0E |
设备→App | IP 获取结果 | [status: 0x00=成功] |
5.6 App 下发关键指令示例
| 场景 | 字节流 | 说明 |
|---|---|---|
| 初始握手 | {0,0,(byte)appCodeLen,0} + AES(UUID) |
UUID 为随机生成,AES 加密 |
| 握手回传 | {2,0,(byte)secretDataLen,0} + AES(UUID_deviceCode) |
设备返回 deviceCode 后回传 |
| 触发 WiFi 扫描 | {4,0,3,0,1,0,5} |
固定指令,每 5s 轮询发送 |
| WiFi 连接请求 | {7,0,dataLen,0, bssid[6], isConnect, timeout, pwdLen} + AES(pwd) |
匹配到目标 SSID 后发送 |
| 下发调度地址 | {11,0,(byte)(urlLen+2),0,0,(byte)urlLen} + urlBytes |
CoolKit 云注册成功后下发 |
AES 加密参数
| 参数 | 值 |
|---|---|
| 算法 | AES/CBC/PKCS7Padding |
| 密钥 | secretKey(Hex 解码为 16 字节) |
| IV | 1111111111111111(16 字节 ASCII)这里只是举例 |
| Base64 | Base64.NO_WRAP |
5.7 TanGe / IoT 设备通信协议
78 字节 WiFi 配置包(App → 设备)
通过 00009999 特征写入,固定 78 字节:
| 字节偏移 | 长度 | 内容 | 填充 |
|---|---|---|---|
| 0 | 1 | 0x01(帧头) |
- |
| 1 ~ 12 | 12 | "wifisetup"(UTF-8) |
不足补 0x00 |
| 13 ~ 44 | 32 | SSID(UTF-8) | 不足补 0x00 |
| 45 ~ 76 | 32 | Password(UTF-8) | 不足补 0x00 |
| 77 | 1 | 0x04(帧尾) |
- |
设备响应(设备 → App,通过 8888 Notify)
| 数据格式 | 内容 | App 处理 |
|---|---|---|
单字节 0x01 |
与下发包格式相同 | 忽略 |
单字节 0x02 |
WiFi 连接失败 | bleConnStatus = false |
单字节 0x03 |
WiFi 密码错误 | bleConnStatus = false |
| JSON 字符串 | 设备信息(DeviceInfoTanGeBean) |
解析后上报云端,关闭 GATT,标记成功 |
6.关键机制说明:
| 机制 | 实现细节 |
|---|---|
| 连接重试 | 非探鸽设备断开时自动重连,最多 3 次 (reConnectTime 计数) |
| 探鸽超时 | 独立 3 分钟(180s)超时定时器,超时未成功则 bleConnStatus = false |
| 总超时 | 180 秒 countdownTask 心跳超时,强制结束配网 |
| WiFi 扫描轮询 | 每 5 秒 通过 scanWifiTask 发送一次 0x05 扫描触发指令,直到收到 0x06 扫描结束 |
| 资源清理 | resetData() 统一释放:停止扫描、关闭 GATT、清空设备列表、取消所有 Disposable、重置状态位 |
| 数据写入 | 所有下发数据统一走 setWriteCharacteristicData(byte[]),强制 WRITE_TYPE_NO_RESPONSE |
7.Iot设备绑定:
7.1 绑定成功:
scss
/**
* 绑定成功
*/
private void showBindSuccess() {
closeCountDown(); // 立即停止倒计时
mDataBinding.ivFaild.setVisibility(View.GONE);
mDataBinding.tvProgress.setVisibility(View.VISIBLE);
mDataBinding.vProgress.setVisibility(View.VISIBLE);
mDataBinding.btnProblem.setVisibility(View.GONE);
mDataBinding.btnRebind.setVisibility(View.GONE);
mDataBinding.tvBindingStatus.setText(UIUtils.getString(R.string.ty_bind_email_success));
mDataBinding.tvBindingDesc.setText("Iot设备绑定成功");
mDataBinding.titleview.setTitle(UIUtils.getString(R.string.ty_bind_email_success));
mDataBinding.titleview.showLeftImage(false);
}
/**
* 进入下一步,绑定成功显示界面然后跳转到成功界面
*/
public void gotoNext() {
if (mData != null) {
SharedPrefs.getInstance().setBindDevicePic(mData.getPic());
DeviceBindSuccessActivity.startActivity(mContext, mDevId, mData.getHome_device_id(), mProductId);
} else {
LogUploadUtils.d("bind device viewModel gotoNext mData is null");
bindResult.setValue(false);
}
}
7.2 绑定失败:
scss
/**
* 绑定失败ui
*/
private void showBindFailed() {
if (isBindSuccess) return;
mDownCount = 0;
setClickText();
mDataBinding.titleview.setTitle(UIUtils.getString(R.string.text_bind_faild));
if (TextUtils.isEmpty(mRobotViewModel.bindErrorMsg)) {
mDataBinding.tvErrorBind.setVisibility(View.GONE);
} else {
mDataBinding.tvErrorBind.setText(mRobotViewModel.bindErrorMsg);
mDataBinding.tvErrorBind.setVisibility(View.VISIBLE);
}
mDataBinding.tvErrorBind.setVisibility(View.VISIBLE);
mDataBinding.ivFaild.setVisibility(View.VISIBLE);
mDataBinding.tvProgress.setVisibility(View.INVISIBLE);
mDataBinding.vProgress.setVisibility(View.INVISIBLE);
mDataBinding.btnProblem.setVisibility(View.VISIBLE);
mDataBinding.btnRebind.setVisibility(View.VISIBLE);
mDataBinding.llFailTipsNew.setVisibility(View.VISIBLE);
mDataBinding.tvBindingStatus.setVisibility(View.GONE);
mDataBinding.tvBindingDesc.setVisibility(View.GONE);
mDataBinding.titleview.showLeftImage(false);
mDataBinding.btnRebind.setOnClickListener(v -> {
mRobotViewModel.onDestroyed();
ProductListActivity3.startActivity(mContext, false, false);
});
}
8.总结
- CoolKit 设备 采用 "AES 加密握手 + 分阶段指令交互" 的私有协议,通过
bbb0/bbb1完成身份校验、WiFi 扫描、连接、云端注册的全流程。 - TanGe/IoT 设备 采用 "78 字节定长 WiFi 包 + 单字节 ACK / JSON 响应" 的简化协议,通过
9999/8888完成配网,成功后以 JSON 上报设备信息。 - 两者均依赖
BluetoothLeScanner进行设备发现,通过名称前缀区分设备类型,并在BleScanUtils内部以静态状态机管理连接生命周期。 - 每个iot项目的配网协议不一样,但是核心差不多,搭建根据自己的进行封装.