本文为字节教育-成人与创新前端团队成员的文章,已获得ELab授权发布。1.背景我最近开始减肥计划,买了一条心率带,希望在使用划船机的同时监测自己的心率。购买后情况如下:心率带不直接显示数值,需要连接APP或相关设备使用。官方APP仅实时显示心率数据,无法生成心率统计图表。通过咕咚APP连接心率带,开始运动后即可监测心率变化,但划船机不在支持的运动范围内。我简单实现了一个划船机节拍器的小程序。于是萌生了在我的节拍器小程序中监测心率数据的想法,也就是太郎小程序中的蓝牙应用实践。2.简单了解蓝牙的概念。毕竟作者不是专业人士。有兴趣的同学可以通过百科、搜索引擎等渠道了解一下。这里只是简单介绍一下接下来会用到的一些知识。Centraldevice/peripheraldeviceclient(中央设备):在本实践中,为作者的手机。服务器(周边设备):本练习中为心率带、耳机等设备。BLE(BluetoothLowEnergy)蓝牙低功耗技术实现了设备之间的连接和通信。顾名思义,能耗和成本更低,对应的叫经典蓝牙。基于笔者的开发目的,本文简单了解一下设备连接前(GAP)和连接后(GATT)所涉及的两种协议。(了解更多:BluetoothLowEnergy-百度百科[1],一文了解蓝牙技术从1.0到5.0的前世今生[2])GAP(GenericAccessProfile)主要用于控制设备连接和广播。通常,外围设备主动间歇性地广播设备信息,等待中心设备发现并建立连接。需要注意的是,这种连接方式是排他性的,连接建立后外围设备会停止广播。另一方面,也有iBeacon设备只向外广播但不建立连接。GATT(GenericAttributeProfile)定义了两个设备之间的数据传输方式。GATT中有两个关键概念:服务(service)和特征(characteristic)。服务是一个独立的逻辑项,包含一个或多个特征。Characteristic是GATT中最小的逻辑数据单元,它的属性包括properties,标识该characteristic是否可以读、写、通知或指示。notify和indicate的区别在于indicate在发送下一个数据包之前需要一个回复。每个服务和功能都有一个唯一的UUID标识符,其中一些由蓝牙SIG正式定义,AssignedNumbers|Bluetooth?TechnologyWebsite[3],官方定义了设备名称、心率数据等常用属性,以统一规范。另外,UUID也可以由硬件工程师来实现。(了解更多:BLE相关协议(GAP&GATT)[4])三、API介绍Taro中的BluetoothAPITaro.openBluetoothAdapter(option)|芋头文档【5】初始化蓝牙模块console.log("蓝牙环境已经启动:",res);设置初始状态(真);},fail:function(err){if(err.errMsg.includes("failalreadyopened")){console.log("蓝牙环境之前启动过:",err);设置初始状态(真);}else{console.log("蓝牙环境启动失败:",err);设置初始状态(假);}},});Taro.getBluetoothDevices(选项)|Taro文档[6]获取蓝牙模块活跃期间所有发现的蓝牙设备。包括已经连接到本机的设备。discoverInterval=setInterval(()=>{Taro.getBluetoothDevices({success:asyncfunction(res){constcanConnectDevices=res.devices.filter((item)=>//信号强度大于-80item.RSSI>-80&&//包含设备名称!["Unknowndevice","MBeacon"].includes(item.name));console.log("成功获取蓝牙设备列表:",canConnectDevices);setDeviceList(()=>canConnectDevices);},});},2000);也可以使用Taro.onBluetoothDeviceFound(callback)|发现设备芋头文档[7]。Taro.createBLEConnection(选项)|Taro文档[8]连接到低功耗蓝牙设备。如果小程序之前搜索过蓝牙设备并成功建立连接,则可以直接传入之前搜索得到的deviceId,无需搜索直接尝试连接该设备。Taro.createBLEConnection({deviceId,success:function(res){console.log("设备连接成功",res);setConnectDeviceId(deviceId);Taro.onBLEConnectionStateChange(function(res){//可以使用该方法回调处理意外断开等异常情况}});},});Taro.getBLEDeviceServices(选项)|太郎文档[9]获取蓝牙设备的所有服务。Taro.getBLEDeviceServices({//这里的deviceId需要已经通过createBLEConnectiondeviceId与对应的设备建立了链接,success:function(res){console.log('deviceservices:',res.services)}})Taro.getBLEDeviceCharacteristics(选项)|太郎文档[10]获取蓝牙设备的一个服务中的所有特征值(characteristic)。Taro.getBLEDeviceCharacteristics({//这里的deviceId需要已经通过createBLEConnectiondeviceId与对应的设备建立了链接,//这里的serviceId需要在getBLEDeviceServices接口中获取serviceId,success:function(res){console.log('devicegetBLEDeviceCharacteristics:',res.characteristics)}})Taro.readBLECharacteristicValue(选项)|Taro文档[11]读取蓝牙低功耗设备的特征值的二进制数据值。注意:设备的特征值必须支持读取才能成功调用。接口读取的信息需要在onBLECharacteristicValueChange方法注册的回调中获取。Taro.notifyBLECharacteristicValueChange(选项)|Taro文献[12]开启低功耗蓝牙设备特征值发生变化时的notify功能,订阅该特征值。注意:设备的feature值必须支持notify或者indicate才能调用成功。另外必须先开启notifyBLECharacteristicValueChange,才能监听设备characteristicValueChange事件Taro.onBLECharacteristicValueChange(callback)|Taro文献[13]监测低功耗蓝牙设备的特征值变化事件。必须先开启notifyBLECharacteristicValueChange接口才能接收设备推送的通知。WEBBluetoothAPI在此发布一些信息,如果您有兴趣,可以阅读WebBluetoothAPI-WebAPIs|MDN[14]通过JavaScript与蓝牙设备通信[15]4.设备名称(DeviceName)详情首先getBLEDeviceServices获取服务列表:查询信息显示0x1800是我们需要的服务。getBLEDeviceCharacteristics获取其特征列表:查询数据显示0x2A00是我们需要的特征。此时可以看到read属性为true。我们通过onBLECharacteristicValueChange和readBLECharacteristicValue来读取数据。Taro.onBLECharacteristicValueChange(function(characteristic){constbuffer=characteristic.valueconstunit8Array=newUint8Array(buffer);console.log("unit8Array:",unit8Array);//转换字符串constencodedString=String.fromCodePoint.apply(null,unit8Array);console.log('设备名称:',encodedString)});Taro.readBLECharacteristicValue({deviceId,serviceId:DeviceNameService,characteristicId:DeviceNameCharacteristics,});得到输出,一些小米耳机:你应该已经发现了,给定的特征值其实是ArrayBuffer格式的。(了解更多:浅谈JS二进制:File、Blob、FileReader、ArrayBuffer、Base64——掘金[16])此时我们需要将其转换为字符串。除了上述方法,还可以先转十六进制,再转字符串://转十六进制consthexString=Array.prototype.map.call(unit8Array,function(bit){return("00"+bit.toString(16)).slice(-2);}).join("");console.log("hexString:",hexString);//十六进制转字符串consthex2String=(hexString:string)=>{if(hexString.length%2)return"";vartmp="";for(leti=0;i
