使用JavaScript直连蓝牙设备

之前公司想做一个应用,需要用到蓝牙,当时没有做深入的调研,就直接否定了在h5上做的想法。

今天刷到了这篇帖子,学习整理一下。

虽然当前网络蓝牙 API 规范还没有最终确定,但是已经得到了广泛的支持。比如ChromeOS,安卓6以后的安卓版chrome浏览器,,Mac上Chrome56及之后的版本,windows10上Chrome70及之后的版本。

之前在小程序,app上通过蓝牙能做的事,以后也能通过h5实现了。比如连接蓝牙,向蓝牙发送一个请求,接收来自蓝牙的广播,读写蓝牙数据,监听蓝牙连接和断开,读写蓝牙描述符等。

使用要求

  • 只能在https状态下使用

    为了数据传输安全,h5上的蓝牙模块,只支持在https的状态下使用,http不能用。但是本地使用没有限制,可以使用localhost调试。
  • 需要用户手动触发和蓝牙的连接行为

    同样是为了安全考虑,拒绝使用程序直接连接蓝牙。必须经过用户操作,比如经过点击,触摸等事件来触发蓝牙连接。

js连接蓝牙实战

寻找蓝牙设备

  • 寻找指定蓝牙设备

    现有的浏览器蓝牙连接,支持蓝牙4.0及之后的版本。通过调用navigator.bluetooth.requestDevice函数向用户申请蓝牙权限,浏览器会弹出一个有蓝牙列表的弹框供用户选择连接。

navigator.bluetooth.requestDevice可以通过传一个有filters参数的对象,来获取请求获取蓝牙权限并且配对。例子:

1
2
3
4
5
navigator.bluetooth.requestDevice({filters: [
{ services: ['battery_service'] }] }
]}).then(device => {

}).catch(err => {console.log(err)})

filters数组中可以传入一个或者多个对象,这些对象的字段包括:

  • name 蓝牙名称,可以通过自己的手机或者电脑蓝牙列表看到。
  • namePrefix蓝牙名称的开头部分, 比如蓝牙全程叫’ABCD’,可以放入’A’或者’AB’
  • services是一个数组,里面用来放置蓝牙的服务名称,如果不知道名称,可以放置服务完整UUID或者16或者32比特的UUID。如果不知道的可以在这里查询,或者点击这里直接查看。
  • manufacturerData根据供应商数据选择蓝牙设备,是一个数组。

注意,如果filters用的是services以外的字段,需要另外一个和filters平级的字段optionalServices来加入需要的服务名称。在实际开发中,我们可能比较难知道自己的蓝牙设备是什么服务,所以还不如一开始就用services。用service字段时可以先用其它设备获取当前这批蓝牙的service id,然后开始在h5中集成。也许未来这个接口会改吧,现在这样太不方便了。

  • 寻找附近所有的蓝牙设备

如果想寻找周围所有的蓝牙设备,然后再从设备中去过滤,可以不传入filters,而是传入布尔值acceptAllDevices,并且设置为true,这样就能搜索到所有的蓝牙设备供匹配,如下:

1
2
3
4
5
6
navigator.bluetooth.requestDevice({
acceptAllDevices: true
}).then((data) => {
console.log(data, 'data')
/*这里可以接着进行处理*/
}).catch(err => {console.log(err)})

连接蓝牙设备

找到蓝牙设备,并且配对成功后,接下来就是要连接蓝牙设备了。在配对成功的回掉函数中,我们将返回的结果打印出来,可以看到如下数据结构:
蓝牙配对成功返回的结果

可以看到,返回的对象中有一个gatt对象,它的原型链上有一堆的方法,其中一个就是connect,里面还有一个参数叫connected,用来表示是否已经连接了蓝牙,如果已经连接了,我们可以对应的做一下策略。接下来我们开始连接,在接下来的连接中,为了代码结构清晰一点,将使用await/async

1
2
3
4
5
6
7
8
9
10
11
let device = ''
let service = ''
async function handleBlueTooth () {
try {
device = await navigator.bluetooth.requestDevice({filters: [{namePrefix: '***'}]}) // *部分就是蓝牙名称的前缀,你也可以使用其它filters中的字段
service = await device.gatt.connect()
console.log(server, 'server')
} catch (err) {
console.log(err)
}
}

断开蓝牙设备

前面我们连接蓝牙设备,后面可能还需要断开蓝牙设备。在配对蓝牙设备的截图中,可以看到有一个disconnect,接下来我们用它来断开蓝牙。

1
2
3
4
async function disconnectBluetooth () {
await device.gatt.disconnect()
alert('蓝牙已经断开')
}

获取取蓝牙的服务和特征码

在连接上蓝牙后,我们就可以开始获取蓝牙的服务和特征码。

  • 获取服务
1
let service = await server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e')

从这里开始的数据,都是我根据文档所写,因为我发获取自己设备的服务名称,无法继续在自己手上的蓝牙测试下去。

  • 获取特征码
1
let characteristic = await service.getCharacteristic('battery_level') 

向蓝牙写入数据

获取到characteristic 后,就可以直接用它的writeValue方法写入数据了。如下:

1
characteristic.writeValue(data)

接收来自蓝牙的数据

获取到characteristic 后,可以为它增加一个监听函数,监听来自蓝牙的数据

1
2
3
characteristic.addEventListener('characteristicvaluechanged',(data) => {
console.log(data)
});

js直接拦截蓝牙用起来挺顺畅,但是不知道服务名这一点确实让人头疼

本文参考了Communicating with Bluetooth devices over JavaScript