
因为开发遇到了项目需要做应用与蓝牙设备连接并下发指令获取和写入数据的操作之前没有过这种经验所以记录一下关于小程序与蓝牙设备通信的方法和步骤。首先先介绍一下相关的蓝牙核心概念再实现低功耗BLE连接和使用的全过程。一、基础概念了解Uniapp蓝牙开发主要针对低功耗蓝牙BLE手机 / 平板常用手环、传感器、蓝牙音箱基本都是 BLE。先来了解几个概念1、BLE特点耗电极低、连接快、适合小数据传输对比传统蓝牙经典蓝牙耗电高用于耳机 / 键盘等UniApp 优先支持 BLE2、设备Device就是你要连接的硬件如蓝牙手环、温湿度传感器关键标识deviceId设备唯一 ID3、服务Service理解设备的 “功能分组”比如一个蓝牙手环有 “心率服务”“电量服务”关键标识serviceUUID每个服务的唯一 ID硬件厂商提供4、特征值Characteristic理解服务里的 “数据通道”比如 “心率服务” 下有 “心率数据特征值”关键标识characteristicUUID硬件厂商提供核心权限Notify设备主动发数据给手机如实时心率Write手机发数据给设备如控制设备开关Read手机主动读取设备数据如查电量5、ArrayBuffer二进制数据蓝牙传输的原始数据格式不能直接传字符串 / 数字开发时需做转换字符串 ↔ 十六进制 ↔ ArrayBuffer二、开发前准备1、环境与设备开发工具HBuilderX我一般用其他AI编辑器然后在HBuilderX打开运行小程序测试设备安卓 /iOS 手机必须真机模拟器不支持蓝牙蓝牙硬件支持 BLE 的设备2、微信小程序端微信公众平台 → 开发 → 开发管理 → 接口设置 → 开启蓝牙接口小程序app.json配置permission:{scope.bluetooth:{desc:用于连接蓝牙设备},scope.userLocation:{desc:用于扫描附近蓝牙设备}}3、必备信息向硬件厂商要设备的serviceUUID服务 ID用于发数据的writeCharacteristicUUID写特征值 ID用于收数据的notifyCharacteristicUUID通知特征值 ID三、蓝牙连接完整流程总体的主流程是申请权限初始化蓝牙搜索设备连接设备获取服务启用通知收发数据断开连接在使用蓝牙设备之前需要先进行权限校验看看手机是否已经开启蓝牙和定位onShow(){requestPermission()}requestPermission(){constpermissions[scope.bluetooth,scope.userLocation];permissions.forEach(scope{uni.authorize({scope,success:()console.log(${scope}权限申请成功),fail:(){uni.showModal({title:权限不足,content:请在设置中开启蓝牙和定位权限,showCancel:false});}});});}如果权限已经具备再进行以下流程。1、初始化蓝牙适配器打开手机蓝牙模块检测蓝牙是否开启// 建议在页面onLoad或onShow中调用initBluetooth(){uni.showLoading({title:初始化蓝牙中...});// 调用API初始化蓝牙uni.openBluetoothAdapter({success:(res){console.log(蓝牙初始化成功,res);// 初始化成功后开始搜索设备this.startSearch();},fail:(err){console.error(蓝牙初始化失败,err);// 常见错误10001手机蓝牙未开启if(err.errCode10001){uni.showModal({title:提示,content:请先开启手机蓝牙,showCancel:false});}},complete:(){uni.hideLoading();}});}2、搜索附近蓝牙设备找到你的硬件扫描周围 BLE 设备获取 deviceId// 页面数据data(){return{deviceList:[],// 存储搜索到的设备isSearching:false// 是否正在搜索};}// 开始搜索startSearch(){this.isSearchingtrue;this.deviceList[];// 清空旧设备// 开启搜索uni.startBluetoothDevicesDiscovery({allowDuplicatesKey:false,// 不重复上报同一设备success:(){console.log(开始搜索蓝牙设备...);// 监听发现新设备uni.onBluetoothDeviceFound((res){constdeviceres.devices[0];// 过滤无效设备name不为空if(device.name!this.deviceList.find(itemitem.deviceIddevice.deviceId)){this.deviceList.push(device);console.log(发现设备,device.name,device.deviceId);}});}});// 5秒后自动停止搜索省电、避免卡顿setTimeout((){this.stopSearch();},5000);}// 停止搜索stopSearch(){if(this.isSearching){uni.stopBluetoothDevicesDiscovery({success:(){console.log(停止搜索);this.isSearchingfalse;// 移除设备监听uni.offBluetoothDeviceFound();}});}}3、连接指定设备选中目标硬件通过连接指定设备获取设备的deviceId建立连接// 连接设备点击设备列表时调用connectDevice(device){uni.showLoading({title:正在连接...});this.stopSearch();// 连接前停止搜索// 建立BLE连接uni.createBLEConnection({deviceId:device.deviceId,timeout:10000,// 超时时间10秒success:(res){console.log(连接成功,res);this.connectedDeviceIddevice.deviceId;uni.showToast({title:连接成功});// 连接成功后获取设备服务this.getServices();// 监听连接断开如设备关机、距离过远this.onDisconnect();},fail:(err){console.error(连接失败,err);uni.showToast({title:连接失败,icon:error});},complete:(){uni.hideLoading();}});}// 监听断开onDisconnect(){uni.onBLEConnectionStateChange((res){if(!res.connected){uni.showToast({title:设备已断开,icon:none});this.connectedDeviceId;}});}5、获取服务和特征值拿到设备的 serviceUUID 和 characteristicUUID是最核心的一步是蓝牙通信必须要用的信息。这里只是给示例具体获取服务和特征值需根据真实出厂设备的数据结构获取。// 1. 获取设备所有服务getServices(){uni.getBLEDeviceServices({deviceId:this.connectedDeviceId,success:(res){console.log(设备服务列表,res.services);// 遍历服务找到目标serviceUUID替换为你的consttargetServiceres.services.find(itemitem.uuidFFF0);if(targetService){this.serviceUUIDtargetService.uuid;// 2. 获取该服务下的所有特征值this.getCharacteristics();}else{uni.showToast({title:未找到目标服务,icon:error});}}});}// 获取特征值getCharacteristics(){uni.getBLEDeviceCharacteristics({deviceId:this.connectedDeviceId,serviceId:this.serviceUUID,success:(res){console.log(特征值列表,res.characteristics);// 找到写特征值FFF1和通知特征值FFF2替换为你的this.writeUUIDres.characteristics.find(itemitem.uuidFFF1).uuid;this.notifyUUIDres.characteristics.find(itemitem.uuidFFF2).uuid;// 启用通知必须否则收不到设备数据this.enableNotify();}});}5、启用通知启用通知让设备主动发数据给手机这是收设备数据的关键enableNotify(){uni.notifyBLECharacteristicValueChange({deviceId:this.connectedDeviceId,serviceId:this.serviceUUID,characteristicId:this.notifyUUID,state:true,// 开启通知success:(){console.log(通知启用成功开始监听数据...);// 监听设备发来的数据this.onReceiveData();},fail:(){uni.showToast({title:启用通知失败,icon:error});}});}// 接收设备数据onReceiveData(){uni.onBLECharacteristicValueChange((res){// res.value是ArrayBuffer转为十六进制字符串consthexthis.arrayBufferToHex(res.value);console.log(收到设备数据十六进制,hex);// 后续可解析数据如转字符串、数字});}6、发送数据给设备控制设备当手机需要获取设备的信息时比如设备版本号、型号、电量等需要下发指令给设备// 发送数据十六进制字符串sendData(hexStr){if(!this.connectedDeviceId){uni.showToast({title:请先连接设备,icon:none});return;}// 十六进制转ArrayBufferconstbufferthis.hexToArrayBuffer(hexStr);uni.writeBLECharacteristicValue({deviceId:this.connectedDeviceId,serviceId:this.serviceUUID,characteristicId:this.writeUUID,value:buffer,success:(){console.log(数据发送成功,hexStr);},fail:(){console.error(数据发送失败);}});}数据格式转换方法// ArrayBuffer转十六进制arrayBufferToHex(buffer){consthexArrArray.prototype.map.call(newUint8Array(buffer),bit(00bit.toString(16)).slice(-2));returnhexArr.join();},// 十六进制转ArrayBufferhexToArrayBuffer(hexStr){constlenhexStr.length;constbuffernewArrayBuffer(len/2);constdataViewnewDataView(buffer);for(leti0;ilen;i2){dataView.setUint8(i/2,parseInt(hexStr.substr(i,2),16));}returnbuffer;}如果在这个过程中搜索不到设备要确认手机蓝牙/定位是否开启在连接过程中deviceId是否正确能连接但收不到数据看一下下发的指令是否正确数据格式是否正确。另外在给设备下发指令时也要区分指令是小端排序还是大端排序严格遵循设备的开发使用说明。