从零到一:在uni-app中构建低功耗蓝牙设备通信全流程(微信小程序通用)

张开发
2026/4/15 19:38:26 15 分钟阅读

分享文章

从零到一:在uni-app中构建低功耗蓝牙设备通信全流程(微信小程序通用)
1. 低功耗蓝牙开发基础认知第一次接触低功耗蓝牙开发时我盯着文档里那些UUID、特征值之类的术语发懵这感觉就像突然要和一个说外星语的外星人交流。后来才发现理解蓝牙通信的关键在于建立正确的认知模型。低功耗蓝牙BLE和我们熟悉的WiFi、4G网络有本质区别。传统网络通信像是打电话建立连接后双方可以持续对话而BLE更像是收发快递每次交互都是独立包裹且快递员蓝牙信号还可能迷路。我在开发智能手环项目时就吃过亏以为发送指令后设备会立即响应结果等了半天没反应后来才明白需要主动监听设备回传的数据包。uni-app提供的蓝牙API与微信小程序完全一致这意味着你开发的代码可以无缝迁移。这个设计非常贴心我去年做的健身器材控制项目就是先用微信小程序调试蓝牙功能再移植到uni-app打包成App整个过程就像复制粘贴那么简单。2. 开发环境准备工欲善其事必先利其器我的HBuilder X总是保持最新版本当前3.4.7因为不同版本对蓝牙调试的支持可能有细微差别。记得有次帮客户排查问题最后发现是旧版IDE的蓝牙适配器初始化存在兼容性问题更新后立即解决。创建uni-app项目时建议选择vue3模板。新版composition API写蓝牙控制逻辑特别顺手所有功能都可以封装成独立函数。比如我会把蓝牙初始化、设备搜索这些操作都放在setup()里代码结构清晰得像乐高积木。真机调试是必须的模拟器跑不了蓝牙功能。安卓设备要开启定位权限是的蓝牙搜索需要定位权限iOS设备则要注意蓝牙规格限制。我习惯先用安卓手机开发调试因为iOS对后台蓝牙操作的限制更多容易踩坑。3. 蓝牙设备发现与连接3.1 蓝牙模块初始化第一次写初始化代码时我犯了个低级错误——没检查用户是否开启手机蓝牙。结果测试时一直报错10001查了半天文档才恍然大悟。现在我的初始化函数都会先弹窗提醒用户function initBlue() { uni.openBluetoothAdapter({ success(res) { console.log(蓝牙适配器已激活); startDiscovery(); // 自动开始搜索 }, fail(err) { if (err.code 10001) { uni.showModal({ title: 提示, content: 请先开启手机蓝牙功能, showCancel: false }) } } }); }3.2 设备搜索优化技巧搜索附近设备时不加限制的话会把所有蓝牙设备都列出来包括那些鼠标、键盘之类的无关设备。后来我学乖了通过services参数过滤目标设备uni.startBluetoothDevicesDiscovery({ services: [0000FFE0-0000-1000-8000-00805F9B34FB], // 目标设备服务UUID success(res) { uni.onBluetoothDeviceFound(device { if(device.devices[0].name 我的智能秤){ // 找到目标设备 } }); } });搜索到设备后要立即停止扫描这个经验是用手机电量换来的。有次忘记调用stopBluetoothDevicesDiscovery两小时后手机电量直接见底设备还发烫得能煎鸡蛋。4. 数据通信实战4.1 服务与特征值解析连接设备后要获取服务列表这里有个坑某些设备服务需要延迟获取。我在开发中遇到过连接后立即getBLEDeviceServices返回空数组的情况后来加了个setTimeout就好了setTimeout(() { uni.getBLEDeviceServices({ deviceId: deviceId.value, success(res) { const targetService res.services.find( s s.uuid 0000FFE0-0000-1000-8000-00805F9B34FB ); } }); }, 1000); // 延迟1秒特征值(characteristic)是通信的核心每个特征值都有读写属性。硬件工程师应该提供特征值对照表标明哪个特征值用于发送指令哪个用于接收数据。没有这个就像没有密码本的情报员看着一堆乱码干瞪眼。4.2 数据收发处理接收到的蓝牙数据是ArrayBuffer类型需要转换才能读懂。我封装了个万能转换工具函数function bufferToString(buffer) { // ArrayBuffer转16进制字符串 const hex Array.from(new Uint8Array(buffer)) .map(b b.toString(16).padStart(2, 0)) .join(); // 16进制转ASCII let str ; for (let i 0; i hex.length; i 2) { str String.fromCharCode(parseInt(hex.substr(i, 2), 16)); } return str; }发送数据时要特别注意字符串需要转成ArrayBuffer。有次发送ON指令没转换设备直接死机后来发现是数据格式错误导致固件崩溃function stringToBuffer(str) { const buffer new ArrayBuffer(str.length); const view new DataView(buffer); for (let i 0; i str.length; i) { view.setUint8(i, str.charCodeAt(i)); } return buffer; } uni.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: stringToBuffer(TEMP:25) // 设置温度指令 });5. 稳定性优化方案5.1 错误重试机制蓝牙通信最大的特点就是不稳定。我设计了三重保障机制首次失败后立即重试再次失败则延迟重试第三次失败才报错。这个方案在智能家居项目中将成功率从70%提升到99%function safeWrite(data, retry 0) { uni.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: data, success() { // 成功处理 }, fail() { if(retry 2) { setTimeout(() { safeWrite(data, retry 1); }, retry * 500); // 延迟重试 } else { uni.showToast({ title: 指令发送失败, icon: error }); } } }); }5.2 连接状态维护蓝牙连接可能随时断开需要持续监听连接状态。我在项目中会维护一个心跳检测机制每隔10秒检查一次连接发现断开就自动重连let heartbeat null; function startHeartbeat() { heartbeat setInterval(() { uni.getBLEDeviceServices({ deviceId, success() {}, // 连接正常 fail() { reconnect(); // 重新连接流程 } }); }, 10000); } function stopHeartbeat() { clearInterval(heartbeat); }实际开发中蓝牙模块的每个环节都需要异常处理。我的经验法则是每个API调用都要写fail回调重要的操作要添加超时检测关键数据要本地缓存。这些细节决定用户体验的好坏也是区分初级和高级开发者的重要标准。

更多文章