前言
前端连接低功耗蓝牙设备的方法有多种,比如微信小程序、H5和Web,但这些方法都或多或少存在一些问题。下面我们以H5连接低功耗蓝牙设备为例,介绍在编写过程中可能会遇到的坑。
h5+plus的官网链接:HTML5+ API Reference (html5plus.org)
一般流程
首先,让我们介绍一下连接低功耗蓝牙设备的一般流程:
- 开启蓝牙设备;
- 搜索附近的低功耗蓝牙设备;
- 连接蓝牙设备;
- 获取蓝牙设备的服务;
- 获取指定服务的特征值;
- 根据服务 ID 和特征值进行蓝牙设备的读写等操作;
- 断开连接。
看起来,这个流程并不复杂。不过,其中还有一些需要额外注意的点。如果没有相关的经验,可能要花费很长时间去理解和实践。
蓝牙设备结构
蓝牙设备通常由服务和特征值构成。根据蓝牙设备的规范,一个设备可以包含多个服务,而每个服务可以包含多个特征值。
服务和特征值通常用于描述蓝牙设备中不同的功能和数据点。举例来说,智能手表可能包含多个服务,例如心率监测、运动追踪和通知推送等服务。每个服务都有一个唯一的 UUID 描述它,同时还可能包含多个特征值,例如心率数据的读取和写入特征值。
特征值可以包含许多不同的属性,例如读取和写入权限、数据类型和格式等。这些属性可以帮助开发者更好地了解蓝牙设备所提供的功能和数据特性。
因此,在进行蓝牙设备开发时,了解服务和特征值的概念非常重要。这将有助于开发者更好地设计和实现蓝牙设备的交互流程,同时可以更好地解决可能遇到的问题。
开启蓝牙设备
这里其实没什么要说的,因为蓝牙设备开启不成功,很大的可能是移动设备的蓝牙没有开启,给个相应的提示,引导用户开启即可。
返回值为ArrayBuffer的话在js里可以使用以下代码进行解码,转换为字符串。
// 解码
function buf2hex(buffer) { // buffer is an ArrayBuffer
// create a byte array (Uint8Array) that we can use to read the array buffer
const byteArray = new Uint8Array(buffer);
// for each element, we want to get its two-digit hexadecimal representation
const hexParts = [];
for (let i = 0; i < byteArray.length; i++) {
// convert value to hexadecimal
const hex = byteArray[i].toString(16);
// pad with zeros to length 2
const paddedHex = ('00' + hex).slice(-2);
// push to array
hexParts.push(paddedHex);
}
// join all the hex values of the elements into a single string
return hexParts.join('');
}
若为对象或值的形式,可以使用JSON.stringify()进行转换
搜索附近蓝牙设备
接口startBluetoothDevicesDiscovery
这里的搜索可以根据蓝牙设备的某些特点来进行筛选。因为h5是将搜索到的蓝牙设备添加到onBluetoothDeviceFound的回调的res.devices这个数组里,所以可以遍历一下数组,再根据需求,按照设备名称或设备的mac地址等信息进行筛选,获取到想要的设备id。
由于搜索蓝牙设备时会消耗较大的性能资源,因此建议在搜索到所需设备后即停止搜索,以避免多余的性能浪费。
连接蓝牙设备
连接蓝牙设备需要相应设备的id,这里可以通过前面的搜索接口的成功回调来获取,res.devices[i].deviceId。这里有个注意点,连接设备最好是加一个延时器,时间可以设置为 200 毫秒,这可以避免连接出现错误。还可以设置连接超时时间,如果超时就重连。
由于蓝牙设备并不稳定,因此并不是每次都能成功连接上。在连接失败的回调函数中,可以再次尝试连接蓝牙设备,但需要限制连接的次数。可以定义一个变量,判断连接失败的次数,小于五次或者十次(可以自己定义)就进行重连。此外,如上所述,延时器是非常有用的,可以在重连的时候间隔 200 毫秒,以避免在短时间内多次连接失败。
获取蓝牙设备服务
参数为设备的id这里也可以通过一些条件判断来只获取自己想要的服务id。同样推荐加一个延时器,防止出错。
获取蓝牙特征值
接口:getBLEDeviceCharacteristics
参数为设备 ID 和服务 ID,服务 ID 需要通过前面获取服务的接口来获取。一个服务中包含多个特征值,而特征值又有属性,例如读、写、通知等。但并不是所有特征值中的属性都是有效的,有些特征值可能只有写属性为 true,其他属性则为 false。而在另一个特征值中,通知属性为 true,其他属性则为 false。具体情况需要根据不同的设备来判断,有时需要操作多个特征值。
开启通知
接口:notifyBLECharacteristicValueChange
如果想要获取设备的反馈,但不是通过读取设备来实现,可以在向设备写入数据之前开启 notify 功能,订阅相应的特征值,并编写特征值改变的回调函数,当特征值发生改变时,回调函数会被执行。一般来说,在设备收到写入数据且数据格式正确的情况下,设备会返回响应信息,此时可以通过回调函数来获取设备的一些返回信息。
需要注意的是,开启通知的操作应在写入数据之前进行,否则无法正常订阅特征值。
具体的格式可以参考 H5 官网的文档。
写入数据
接口:writeBLECharacteristicValue
在写入数据之前需要注意以下几个问题:
-
写入的数据需要先转换为 ArrayBuffer 格式。普通字符串无法直接写入设备。
-
转换数据时需要注意数据的进制等格式问题,确保数据转换正确。
-
转换数据完成后,可以使用开启蓝牙设备时使用的 buf2hex 方法来解码,并检查是否是要写入的数据。
-
一次最多可写入 20 个字节的数据,如果数据长度超过 20,需要分包发送。具体实现方式可以参考以下示例代码,其中 printArrayBuffer 是一个自定义的解码方法:
// 由于低消耗蓝牙设备机制,每次只能发送20个字节,所以使用分包写入
let Num = 0;
let ByteLength = _buffer.byteLength;
let Maxbyte = Math.ceil(ByteLength / 20);
for (let f = 1; f <= Maxbyte; f++) {
let typeBuff;
typeBuff = _buffer.slice(Num, Num + 20);
printArrayBuffer(typeBuff)
Num += 20;
ByteLength -= 20;
setTimeout(() => {
plus.bluetooth.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: writeId,
value: typeBuff,
writeType: 'write',
success: function(e) {
console.log(`第${f}包写入成功,返回值: ` + JSON
.stringify(e));
return;
},
fail: function(e) {
console.log(`第${f}包写入失败,错误信息: ` + JSON
.stringify(e));
// 断开连接
closeConnection(deviceId)
}
});
}, 200 * (f - 1))
}
每次传包的时候必须要有一个时间间隔,不能同时传包,这个时间间隔最好也不要太长。
- 如果数据写入正确,那么前面开启的订阅功能应该就会执行回调,获取到设备返回的信息。如果回调没有执行,请反复检查传入数据的格式是否正确,也可以在适当的地方加上延时器,防止出错
- 这里提供一个十六进制数据转换为ArrayBuffer的代码例子
// 十六进制转换为ArrayBuffer
function hexToArrayBuffer(hex) {
if (hex.length % 2 !== 0) {
throw new Error('无效的十六进制输入。长度必须是偶数.');
}
const length = hex.length / 2;
const result = new ArrayBuffer(length);
const view = new Uint8Array(result);
for (let i = 0; i < length; i++) {
const start = i * 2;
const end = start + 2;
const byte = parseInt(hex.substring(start, end), 16);
view[i] = byte;
}
return result;
}
断开连接
通常情况下,设备在成功接收到数据并进行处理后,会在短暂时间后主动断开连接。当然,你也可以在适当的时机通过编码实现主动断开连接,比如获取服务号、获取设备号、写入数据失败等情况下可以进行操作,避免影响下次连接。
需要注意的是,如果在设备还未处理完数据就主动断开连接,可能会导致设备数据处理不完整或者出现其他问题。因此,在断开连接之前,需要确保设备已经成功地接收到并处理完了数据,以免造成不必要的麻烦。
感受
在开发过程中,可能会遇到一些未曾预料到的情况和问题。针对这些问题,我们需要不断地探索和尝试,才能真正理解和掌握蓝牙连接的技术和方法。
虽然每个问题都有各自的解决方法,但总的来说,我们需要坚持不懈地进行尝试和实践,才能获得最佳解决方案。只要不放弃,只要不断摸索,终究可以解决一切问题,达到预期的目标。