全部文章

web bluetooth 基本用法

2018-01-25 @sunderls

js bluetooth

web bluetooth的API稍微阅读了一下,做一个笔记。

ref: https://developers.google.com/web/updates/2015/07/interact-with-ble-devices-on-the-web

一些名词

noun desc
bluetooth 蓝牙技术
BLE 低功耗的蓝牙连接
GATT Generic Attribute Profile , 一种蓝牙设备间的通讯协议

API

查询附近的蓝牙设备 navigator.bluetooth.requestDevice

查询API中支持传入filter来过滤,filter中可以选择service 比如:

// 查找返回在发送battery_service的设备
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* ... */ })
.catch(error => { console.log(error); });

对于非标准的蓝牙设备,可以传入device id (bluetooth UUID 或者16, 32bit形式)

navigator.bluetooth.requestDevice({
  filters: [{
    services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
  }]
})
.then(device => { /* ... */ })
.catch(error => { console.log(error); });

也可以传入设备name,但是这种情况下也需要同时传入service

navigator.bluetooth.requestDevice({
  filters: [{
    name: 'Francois robot'
  }],
  optionalServices: ['battery_service']
})
.then(device => { /* ... */ })
.catch(error => { console.log(error); });

可以用acceptAllDevices: true来获取所有设备。但是这太费电了。

navigator.bluetooth.requestDevice({
  acceptAllDevices: true,
  optionalServices: ['battery_service']
})
.then(device => { /* ... */ })
.catch(error => { console.log(error); });

连接蓝牙设备 device.gatt.connect

设备传输信息靠的是gatt,所以在得到的device中,直接connect。

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
  // Human-readable name of the device.
  console.log(device.name);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* ... */ })
.catch(error => { console.log(error); });

获取设备中的信息 Service.Characteristic

连接好设备过后,就去获取一个Service(和下面的Characteristic)。 我的理解就是,GATT协议支持的一个Object最多两层而已,第一层是service,第二层是charactersistic

device => device.gatt.connect()
.then(server => {
  // 获取电源服务
  return server.getPrimaryService('battery_service');
})
.then(service => {
  // 获取电量值特征
  // 如果是自定义的特征名,可以传入UUID
  return service.getCharacteristic('battery_level'); 
})
.then(characteristic => {
  // 可以在这个特征上添加监听器查看变化
  // 首先这个需要开启通知
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleBatteryLevelChanged);
  // 获取电量值特征的值
  return characteristic.readValue();
.then(value => {
  console.log('Battery percentage is ' + value.getUint8(0));
})
.catch(error => { console.log(error); });

写入信息到蓝牙设备

和上面读取一样,首先要得到service然后获取characteristic,然后写入

service => service.getCharacteristic('heart_rate_control_point')
.then(characteristic => {
  // 写入信息 1 用来reset
  var resetEnergyExpended = Uint8Array.of(1);
  return characteristic.writeValue(resetEnergyExpended);
})

所有的方法都是异步的。

监听广播信息 GATT Notification

一个蓝牙设备可以不停的广播(advertising),比如beacon??

server => server.getPrimaryService('heart_rate')
.then(service => service.getCharacteristic('heart_rate_measurement'))
// 得到特征后,开启通知
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
  //然后监听其变化
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleCharacteristicValueChanged);
  console.log('Notifications have been started.');
})

这里有点奇怪,需要针对特征开启startNotifications,才会收到事件

  1. GATT server是回应请求而已,不会做其他的行为
  2. 如果开启了Notification,GATT就会触发change event。 这个是针对所有的连接好的设备还是单独的呢?应该是发起请求的设备吧?

停止通知的时候,characteristic.stopNotifications()即可。注意同时接触监听,removeListener

设备断开连接时的event gattserverdisconnected

可以在device中监听该事件。 gatt.disconnect()可以手动断开连接。

navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
  // 当连接断开的时候。
  device.addEventListener('gattserverdisconnected', onDisconnected);

  return device.gatt.connect();
})

获取和修改特征值说明 GATT descriptors

service => service.getCharacteristic('measurement_interval')
// 获取descriptor
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
// 获取其值
.then(descriptor => descriptor.readValue())
.then(value => {
  let decoder = new TextDecoder('utf-8');
  console.log('User Description: ' + decoder.decode(value));
})

同时也可以写入

characteristic => characteristic.getDescriptor('gatt.characteristic_user_description')
.then(descriptor => {
  let encoder = new TextEncoder('utf-8');
  let userDescription = encoder.encode('新说明');
  return descriptor.writeValue(userDescription);
})

value都是binary ArrayBuffer

二进制需要中间转换一下,可以用到ArrayBuffer。具体利用方法可以看我的另外一篇文章「JavaScript 字符串, 二进制数据 以及 Base64