物联网 (IoT) 正经历着快速的发展,并持续改变着我们的生活、工作以及与世界的互动方式。低功耗蓝牙 (Bluetooth Low Energy)为物联网设备提供了一种低功耗、低成本、短距离的无线通信方式。
在低功耗蓝牙中,读写特征值是与外部设备交互的主要方式。特征值是位于服务内的基本数据单元,可以被读取或写入。
通过getBLEDeviceServices和getBLEDeviceCharacteristics获取服务和特征值:

微信小程序中处理超过 MTU(最大传输单元,默认 20 字节) 的蓝牙数据时,需要通过数据分片(分包)发送与接收拼接 的方式解决。
数据包标识
0xAA)示例协议格式
| 字段 | 起始符 | 数据总长度 | 分包序号 | 数据内容 | 校验和 |
|---|---|---|---|---|---|
| 字节数 | 1 | 2 | 1 | ≤20 | 2 |
/**
* 封装数据包
* @param data 原始数据
* @param chunkIndex 分片序号
* @param totalLength 总数据长度
* @returns 封装后的数据包
*/
packData(data: ArrayBuffer, chunkIndex: number, totalLength: number): ArrayBuffer {
const buffer = new ArrayBuffer(data.byteLength + HEADER_SIZE + CHECKSUM_SIZE);
const view = new DataView(buffer);
let offset = 0;
// 1. 写入起始标志
view.setUint8(offset, PACKET_START);
offset += 1;
// 2. 写入数据总长度
view.setUint16(offset, totalLength, true);
offset += 2;
// 3. 写入分片序号
view.setUint8(offset, chunkIndex);
offset += 1;
// 4. 写入数据内容
new Uint8Array(buffer).set(new Uint8Array(data), offset);
offset += data.byteLength;
// 5. 计算并写入校验和
const checksum = calculateCRC16(buffer.slice(0, offset));
view.setUint16(offset, checksum, true);
return buffer;
}
/**
* 分片发送数据
* @param data 要发送的完整数据
* @returns Promise
*/
sendChunkedData(data: ArrayBuffer): Promise<void> {
const totalLength = data.byteLength;
const chunks = Math.ceil(totalLength / MAX_DATA_SIZE);
for (let i = 0; i < chunks; i++) {
const start = i * MAX_DATA_SIZE;
const end = Math.min(start + MAX_DATA_SIZE, totalLength);
const chunk = data.slice(start, end);
const packet = packData(chunk, i, totalLength);
try {
await writeBLECharacteristicValue(packet);
// 添加适当的延时,避免设备处理不及时
await new Promise(resolve => setTimeout(resolve, 50));
} catch (error) {
throw new Error(`发送分片 ${i + 1}/${chunks} 失败: ${error.message}`);
}
}
}
onBLECharacteristicValueChange监听特征值变化事件

发送WiFi配置信息
/**
* 发送WiFi配置信息
* @param ssid WiFi名称
* @param password WiFi密码
* @returns Promise对象
*/
sendWiFiConfig(ssid: string, password: string): Promise<void> {
const encoder = new TextEncoder();
const ssidData = encoder.encode(ssid);
const passwordData = encoder.encode(password);
// 构造配置数据包:[ssid长度(1字节)][ssid内容][password长度(1字节)][password内容]
const configBuffer = new ArrayBuffer(ssidData.length + passwordData.length + 2);
const configView = new Uint8Array(configBuffer);
configView[0] = ssidData.length;
configView.set(ssidData, 1);
configView[ssidData.length + 1] = passwordData.length;
configView.set(passwordData, ssidData.length + 2);
await this.sendChunkedData(configBuffer);
}