蓝牙能力与场景
智能秤、手环、血压计、门锁、打印机,这些硬件设备通过低功耗蓝牙和小程序连上,用户不用装 App 就能用,是小程序一个很有想象力的能力边界。
小程序蓝牙主要走 BLE 协议:手机做主机,外设做从机。一次完整交互通常是扫描、连接、发现服务、订阅通知或写数据、断开,五步走完一轮。
蓝牙调试很依赖真机,模拟器基本测不出连接行为。开发时准备一台安卓一台 iOS,两个系统在权限弹窗、扫描结果上的表现差异不小。下面按连接流程逐步讲。
- 扫描adapter 初始化与设备发现。
- 连接BLE 连接与服务特征。
- 收发写特征值与订阅通知。
- 收尾断开、释放与异常恢复。
扫描与发现
开始之前先初始化蓝牙适配器,检查系统蓝牙是否打开。适配器没初始化就调用后面的接口,会直接报错,所以每一步都要判断当前状态。
wx.openBluetoothAdapter({
success: function () {
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: function () {
wx.onBluetoothDeviceFound(function (res) {
// res.devices 里是发现的设备,按名称过滤
const list = res.devices.filter(function (d) {
return d.name && d.name.indexOf('JCY') === 0;
});
renderDeviceList(list);
});
}
});
},
fail: function () {
showToast('请打开手机蓝牙后重试');
}
});
发现的设备对象里,name 是设备广播名,deviceId 是设备唯一标识,RSSI 是信号强度。按名称前缀过滤能筛掉一堆无关设备,列表里显示信号强度,用户也好选。
扫描不要一直开着,找到目标设备就 stopBluetoothDevicesDiscovery 停下来,省电也避免接口冲突。
连接与服务
选中设备后用 createBLEConnection 建立连接。连接成功之后,先取设备的服务列表,再取每个服务下的特征值列表,找到要读写的那几个特征值 UUID。
wx.createBLEConnection({
deviceId: deviceId,
success: function () {
wx.getBLEDeviceServices({
deviceId: deviceId,
success: function (res) {
const service = res.services[0];
wx.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: service.uuid,
success: function (r) {
// r.characteristics 里挑读写特征值
const writeChar = pickWrite(r.characteristics);
const notifyChar = pickNotify(r.characteristics);
readyToUse(writeChar, notifyChar);
}
});
}
});
}
});
服务和特征值的 UUID 由设备厂商提供,一般写在硬件文档里。有些设备 UUID 是 16 位短格式,连接时要把它们补成 128 位标准格式再匹配,不然对不上。
连接要带超时判断:超过几秒没成功就提示失败,并清理这次连接的资源,避免半连接状态卡住用户。
数据收发
向设备发数据用 writeBLECharacteristicValue,参数要转成 ArrayBuffer。设备回传数据一般通过通知,先启用 notify 监听,再在 onBLECharacteristicValueChange 里收。
function sendCommand(buffer) {
wx.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: writeCharId,
value: buffer,
fail: function () {
showToast('指令发送失败');
}
});
}
wx.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: notifyCharId,
state: true
});
wx.onBLECharacteristicValueChange(function (res) {
const data = arrayBufferToHex(res.value);
handleDeviceData(data);
});
十六进制字符串和 ArrayBuffer 互转要写成工具函数,数据解析按设备协议来:帧头、长度、指令、数据、校验位,逐字节解析,协议字段要跟厂商核对清楚。
数据会一帧一帧来,注意粘包问题。收到的数据按协议切帧,不够一帧先攒着,凑满再处理,别丢中间帧。
指令发送频率也要控制,设备处理能力有限,短时间发太多设备会丢指令。发一条等一条的应答,或者按固定间隔排队发,比一股脑全丢出去稳。
状态与断开
蓝牙适配器和设备连接是全局状态,页面切走、小程序切后台、用户关蓝牙,都可能让连接失效。onBLEConnectionStateChange 可以监听连接状态变化,断线后及时提示并走重连流程。
| 事件 | 处理 |
|---|---|
| 页面卸载 | 断开连接、关闭适配器 |
| 连接意外断开 | 提示用户,提供重连按钮 |
| 系统蓝牙关闭 | 提示打开蓝牙,恢复后重试 |
| 扫描未停止 | 离开页面时停止扫描 |
断开用 closeBLEConnection 关连接、closeBluetoothAdapter 关适配器。页面 onUnload 里把监听器清理干净,避免回调泄漏导致重复处理数据。
重连要克制:失败就停,让用户手动点重试,别在后台无限循环重连,既耗电又可能触发系统拦截。
最后,蓝牙相关逻辑建议单独抽成一个蓝牙管理模块,页面只负责 UI 和调用,连接状态、数据解析都在模块里维护。多个页面共用一套蓝牙能力时,模块化能少踩很多重复的坑。
常见蓝牙坑
| 坑 | 现象 | 应对 |
|---|---|---|
| 安卓定位权限 | 扫描不到任何设备 | 先申请定位权限再扫描 |
| 适配器未初始化 | 接口全部报错 | 统一封装蓝牙状态判断 |
| UUID 格式不符 | 特征值匹配不上 | 短 UUID 补 128 位再比对 |
| 通知未开启 | 收不到设备数据 | 先 notify 再发指令 |
落地清单
- 初始化适配器,判断系统蓝牙状态。
- 扫描按名称过滤,找到即停。
- 连接带超时,UUID 补全匹配。
- 数据收发按协议切帧,处理粘包。
- 页面卸载清理监听,断线重连克制。
蓝牙链路每一步都可能断,把状态机管好,体验就稳了。