微信小程序与BLE(低功耗蓝牙)设备实现OTA(Over-The-Air)升级,核心是通过小程序的BLE API与设备端OTA服务协同,完成「固件分片传输、校验、升级执行」全流程。需兼顾BLE的低带宽、MTU限制、可靠性传输等特性,同时适配小程序的蓝牙接口能力。以下是分阶段的完整实施方案:
一、前期准备:设备端与小程序的协同定义
OTA升级需「设备端支持」与「小程序端适配」双向配合,首先明确核心协议与参数定义:
1. 设备端OTA基础能力
OTA模式切换:设备需支持「正常工作模式」与「OTA模式」切换(如小程序发送特定指令 `0x01` 触发OTA模式,或设备长按物理按键进入),OTA模式下设备广播名称/服务UUID需与正常模式区分(如广播名称后缀 `_OTA`,OTA服务UUID为 `0000FF00-0000-1000-8000-00805F9B34FB`)。
OTA服务与特征值定义:
设备需提供专属OTA服务,包含2个核心特征值(Characteristic):
| 特征值类型 | UUID示例 | 功能描述 |
|------------------|-------------------------|--------------------------------------------------------------------------|
| 控制特征值(可写)| 0000FF01-0000-1000-8000-00805F9B34FB | 接收小程序的控制指令(如「开始OTA」`0x02`、「发送校验值」`0x03`、「结束OTA」`0x04`) |
| 数据特征值(可写)| 0000FF02-0000-1000-8000-00805F9B34FB | 接收小程序发送的固件分片数据(核心数据通道) |
| 通知特征值(可通知)| 0000FF03-0000-1000-8000-00805F9B34FB | 向小程序反馈状态(如「分片接收成功」`0x0A`、「校验失败」`0x0B`、「升级完成」`0x0C`) |
固件存储与校验:设备需预留OTA固件存储分区(如Flash),支持CRC32/MD5校验,避免固件损坏导致升级变砖。
2. 小程序端基础准备
固件文件处理:OTA固件通常为 `bin` 或 `hex` 格式(需与设备端一致),需提前将固件文件托管在服务器(如OSS),小程序通过下载API获取二进制文件;或支持用户本地选择固件文件(通过 `wx.chooseMessageFile` 选择本地文件)。
蓝牙权限与兼容性:小程序需申请蓝牙权限(`scope.bluetooth`),且仅支持BLE 4.0及以上设备,需在代码中处理蓝牙开关未打开、设备不支持BLE等异常。
二、核心流程:小程序端OTA升级步骤
完整OTA流程分为「设备连接→OTA初始化→固件分片传输→校验升级→结果反馈」5个阶段,小程序端需按顺序调用微信BLE API实现:
阶段1:蓝牙初始化与设备连接(OTA模式)
1. 初始化蓝牙适配器:调用 `wx.openBluetoothAdapter` 初始化,监听蓝牙状态变化(如蓝牙关闭时提示用户打开)。
2. 扫描OTA模式设备:调用 `wx.startBluetoothDevicesDiscovery` 扫描设备,过滤出OTA模式的设备(如广播名称含 `_OTA`,或服务UUID匹配预设值)。
3. 建立BLE连接:调用 `wx.createBLEConnection` 连接目标设备,连接成功后停止扫描(`wx.stopBluetoothDevicesDiscovery`)。
4. 获取OTA服务与特征值:
调用 `wx.getBLEDeviceServices` 获取设备所有服务,筛选出OTA服务(UUID匹配预设值);
调用 `wx.getBLEDeviceCharacteristics` 获取OTA服务下的3个特征值(控制、数据、通知),并记录特征值的 `uuid` 和 `properties`(如是否支持 `write`、`notify`)。
5. 启用通知监听:调用 `wx.notifyBLECharacteristicValueChange` 启用「通知特征值」的监听,用于接收设备的状态反馈(如分片接收结果)。
阶段2:OTA初始化(触发设备准备接收固件)
1.发送「开始OTA」指令:通过「控制特征值」发送初始化指令,指令需包含固件元信息(如固件总大小、总分片数、校验值),格式示例(二进制):
| 指令标识(1字节) | 固件总大小(4字节,大端) | 总分片数(2字节,大端) | CRC32校验值(4字节,大端) |
|--------------------|---------------------------|-------------------------|-----------------------------|
| 0x02(开始OTA) | 0x00012345(74565字节) | 0x04E2(1250片) | 0x12345678 |
(注:需将指令转为 `ArrayBuffer` 格式,通过 `wx.writeBLECharacteristicValue` 发送)。
2.等待设备就绪反馈:监听「通知特征值」的回调,若收到设备返回的 `0x0A`(初始化成功),进入下一阶段;若收到 `0x0B`(参数错误),需重新发送初始化指令。
阶段3:固件分片传输(核心步骤)
BLE的MTU(最大传输单元)默认较小(通常23字节,有效负载约18-20字节),需将固件按MTU拆分成分片,逐包发送并确认:
1. 固件分片处理:
读取固件二进制文件(`ArrayBuffer` 格式),计算分片大小(建议为「MTU-3」,预留3字节用于分片序号);
每片数据格式:`[分片序号(2字节,大端)][数据(MTU-3字节)]`,示例:
| 分片序号(2字节) | 数据(18字节) |
|--------------------|----------------|
| 0x0000(第1片) | 固件前18字节 |
| 0x0001(第2片) | 接下来18字节 |
代码示例(分片逻辑):
```javascript
// 假设固件二进制数据为 firmwareBuffer(ArrayBuffer),MTU=23,分片数据长度=20(23-3)
const mtu = 23;
const chunkDataLen = mtu - 3; // 分片数据部分长度(预留3字节:2字节序号+1字节校验?或仅2字节序号)
const totalChunks = Math.ceil(firmwareBuffer.byteLength / chunkDataLen);
const chunks = [];
for (let i = 0; i < totalChunks; i++) {
// 计算当前分片的起始和结束位置
const start = i * chunkDataLen;
const end = Math.min(start + chunkDataLen, firmwareBuffer.byteLength);
const chunkData = firmwareBuffer.slice(start, end);
// 构建分片:2字节序号(大端) + 数据
const chunk = new Uint8Array(2 + chunkData.byteLength);
chunk[0] = (i >> 8) & 0xff; // 序号高8位
chunk[1] = i & 0xff; // 序号低8位
chunk.set(new Uint8Array(chunkData), 2); // 填充数据
chunks.push(chunk.buffer); // 存入分片数组
}
```
2. 逐包发送与确认:
采用「阻塞式发送」:发送1个分片后,等待设备通过「通知特征值」返回 `0x0A`(分片接收成功),再发送下一个分片;
处理重传:若超时(如3秒)未收到确认,或收到 `0x0B`(分片错误),重新发送当前分片(重试次数建议设为3次,超过则终止升级);
进度更新:通过 `this.setData` 更新升级进度(如 `progress: Math.floor((currentChunk / totalChunks) * 100)`),展示给用户。
阶段4:固件校验与升级执行
1. 发送「校验指令」:所有分片发送完成后,通过「控制特征值」发送 `0x03`(校验指令),触发设备对接收的固件进行CRC32/MD5校验。
2. 等待校验结果:
若设备返回 `0x0C`(校验成功),继续发送 `0x04`(开始升级指令);
若返回 `0x0D`(校验失败),提示用户“固件校验失败,需重新升级”,并终止流程。
3. 等待升级完成:设备开始升级后(通常耗时10-30秒,期间可能断开蓝牙连接),小程序需监听蓝牙连接状态,若设备升级完成后重新广播(正常模式),可提示“升级成功”;若长时间未恢复连接,提示“升级超时,请检查设备”。
阶段5:异常处理与流程终止
连接断开:升级过程中若蓝牙断开,尝试重新连接设备(需设备支持OTA模式记忆,保留已接收的分片),恢复传输;若无法重连,提示用户“连接中断,建议重启设备后重试”。
固件错误:若设备返回「不支持的固件版本」「存储空间不足」等错误(如 `0x0E`、`0x0F`),需解析错误码并提示用户(如“设备存储空间不足,无法升级”)。
三、关键技术点与优化方案
1. MTU协商(提升传输效率)
微信小程序支持通过 `wx.setBLEMTU` 主动设置MTU大小(最大517字节),建议在连接设备后协商更大的MTU(如256字节),减少分片数量:
```javascript
// 连接设备后协商MTU
wx.setBLEMTU({
deviceId: deviceId,
mtu: 256, // 建议值,需设备端支持
success: (res) => {
console.log('MTU协商成功,当前MTU:', res.mtu);
// 后续分片按新MTU计算
},
fail: (err) => {
console.log('MTU协商失败,使用默认值:', err);
}
});
```
2. 可靠性传输(避免丢包)
带响应写入:发送分片时,使用 `writeType: 'writeWithResponse'`(微信BLE API默认),确保设备收到分片后返回ACK,避免盲目发送;
序号校验:设备端需校验分片序号的连续性(如当前应接收第10片,却收到第12片,需返回错误并要求重发第10片),小程序端需记录已发送成功的最大序号,重连后从该序号继续发送。
3. 固件文件处理(本地/远程)
远程固件:小程序通过 `wx.downloadFile` 从服务器下载固件(需配置download域名),下载完成后通过 `wx.getFileSystemManager()` 读取为 `ArrayBuffer`;
本地固件:通过 `wx.chooseMessageFile` 让用户选择本地固件文件(仅支持 `bin`/`hex` 格式),再通过 `wx.getFileSystemManager().readFile` 读取为二进制数据。
四、示例代码:小程序核心逻辑片段
以下是关键步骤的代码示例(简化版),需结合实际UUID和指令格式调整:
```javascript
Page({
data: {
deviceId: '', // 连接的设备ID
otaServiceId: '0000FF00-0000-1000-8000-00805F9B34FB', // OTA服务UUID
ctrlCharId: '0000FF01-0000-1000-8000-00805F9B34FB', // 控制特征值UUID
dataCharId: '0000FF02-0000-1000-8000-00805F9B34FB', // 数据特征值UUID
notifyCharId: '0000FF03-0000-1000-8000-00805F9B34FB', // 通知特征值UUID
progress: 0, // 升级进度
isUpgrading: false // 是否正在升级
},
// 1. 初始化蓝牙并扫描OTA设备
initBluetooth() {
const that = this;
wx.openBluetoothAdapter({
success: () => {
// 监听蓝牙状态
wx.onBluetoothAdapterStateChange((res) => {
if (!res.available) {
wx.showToast({ title: '蓝牙已关闭,请打开', icon: 'none' });
}
});
// 扫描设备
wx.startBluetoothDevicesDiscovery({
services: [that.data.otaServiceId], // 只扫描OTA服务设备
success: () => {
// 监听扫描到的设备
wx.onBluetoothDeviceFound((res) => {
const device = res.devices[0];
if (device.name.includes('_OTA')) { // 筛选OTA模式设备
that.setData({ deviceId: device.deviceId });
that.connectDevice(); // 连接设备
wx.stopBluetoothDevicesDiscovery(); // 停止扫描
}
});
}
});
},
fail: (err) => {
wx.showToast({ title: '蓝牙初始化失败:' + err.errMsg, icon: 'none' });
}
});
},
// 2. 连接设备并获取OTA特征值
connectDevice() {
const that = this;
const { deviceId, otaServiceId } = this.data;
wx.createBLEConnection({
deviceId,
success: () => {
wx.showToast({ title: '设备连接成功' });
// 获取OTA服务
wx.getBLEDeviceServices({
deviceId,
success: (res) => {
const otaService = res.services.find(s => s.uuid === otaServiceId);
if (!otaService) {
wx.showToast({ title: '未找到OTA服务', icon: 'none' });
return;
}
// 获取特征值
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId: otaServiceId,
success: (res) => {
// 启用通知监听
const notifyChar = res.characteristics.find(c => c.uuid === that.data.notifyCharId);
if (notifyChar.properties.notify) {
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId: otaServiceId,
characteristicId: that.data.notifyCharId,
state: true,
success: () => {
// 监听通知数据
wx.onBLECharacteristicValueChange((res) => {
that.handleDeviceNotify(new Uint8Array(res.value));
});
}
});
}
// 开始OTA初始化(假设已获取固件元信息)
that.initOTA();
}
});
}
});
},
fail: (err) => {
wx.showToast({ title: '设备连接失败:' + err.errMsg, icon: 'none' });
}
});
},
// 3. 处理设备通知(状态反馈)
handleDeviceNotify(data) {
const cmd = data[0]; // 指令标识
switch (cmd) {
case 0x0A: // 操作成功(初始化成功/分片接收成功)
if (this.data.isUpgrading) {
this.sendNextChunk(); // 发送下一个分片
} else {
this.startFirmwareTransfer(); // 初始化成功,开始传输固件
}
break;
case 0x0B: // 操作失败
wx.showToast({ title: '设备反馈失败', icon: 'none' });
this.abortOTA(); // 终止升级
break;
case 0x0C: // 校验成功
wx.showToast({ title: '固件校验成功,开始升级' });
this.sendUpgradeCmd(); // 发送开始升级指令
break;
case 0x0D: // 校验失败
wx.showToast({ title: '固件校验失败,请重试', icon: 'none' });
this.abortOTA();
break;
case 0x0E: // 升级完成
wx.showToast({ title: 'OTA升级成功!' });
this.setData({ isUpgrading: false, progress: 100 });
break;
}
},
// 后续步骤:initOTA(发送初始化指令)、startFirmwareTransfer(分片传输)、sendNextChunk(下一分片)等方法需根据实际逻辑实现
// ...
});
```
五、注意事项
1. 设备端配合:小程序OTA流程依赖设备端的正确响应,需确保设备端的OTA服务、特征值UUID、指令格式与小程序完全一致,建议先通过蓝牙调试工具(如LightBlue)验证设备端OTA功能。
2. 微信API限制:小程序BLE API存在连接超时(默认10分钟)、数据发送频率限制(建议每秒不超过10包),需控制发送节奏,避免触发微信的接口限流。
3. 用户体验:升级过程中需展示清晰的进度和状态(如“正在传输固件:30%”“校验中…”),避免用户误操作断开蓝牙;升级完成后提示用户重启设备(部分设备需手动重启生效)。
通过以上方案,可实现小程序与BLE设备的稳定OTA升级,适用于智能硬件(如手环、传感器、智能家居设备)的固件迭代场景。