全ての記事

web bluetooth basic API usage

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

付近のbluetooth デバイスを探知navigator.bluetooth.requestDevice

filterで条件を渡せば、必要のデバイスだけ取得できる。例えば:

// battery_serviceをアドバタイジングしてるデバイスだけ取得する
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* ... */ })
.catch(error => { console.log(error); });

標準ではデバイスでは、device id (bluetooth UUID または16bit, 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

ウェブサイトではDNSサーバーからipアドレスをわかると、httpでリクエストするのですね。 ブルートゥースの場合も、requestDeviceからデバイスを取得して、そこからgattプロトコルで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を取得して、さらにserviceの下のCharacteristicをみる。

これはまさかurlみたいに device/{service}/characteristicのようなものですね。

これはおそらく複雑になるを避ける為、階層を2までしてるのですかね。

device => device.gatt.connect()
.then(server => {
  // バッテリーサービスを取得する
  return server.getPrimaryService('battery_service');
})
.then(service => {
  // バッテリー残量のキャラクターリスティを取得
  // カスタマイズのキャラクターリスティくであれば、UUIDを渡す。
  return service.getCharacteristic('battery_level'); 
})
.then(characteristic => {
  // characteristicの変更イベントをlistenできる。
  // まずstartNotificationする必要がある。
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleBatteryLevelChanged);
  // characteristicの値を取得する
  return characteristic.readValue();
.then(value => {
  console.log('Battery percentage is ' + value.getUint8(0));
})
.catch(error => { console.log(error); });

データを書き込む

デバイスにデータを書き込むのも、取得と同じくまずcharacteristicがまず特定する。

service => service.getCharacteristic('heart_rate_control_point')
.then(characteristic => {
  // resetする為1 を書き込む
  var resetEnergyExpended = Uint8Array.of(1);
  return characteristic.writeValue(resetEnergyExpended);
})

あと全てのメソッドはasyncです。

GATT Notificationでアドバタイジングをlistenする

ブルートゥースデバイスではラジオみたいにアドバタイジングすることができます。例えば beacon?

server => server.getPrimaryService('heart_rate')
.then(service => service.getCharacteristic('heart_rate_measurement'))
//characterisitic取得できたら、notificationをonにする。
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
  //これでチェンジイベントをlistenできる。
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleCharacteristicValueChanged);
  console.log('Notifications have been started.');
})

ここはちょっとおかしいですが、startNotifications呼ばないとnotificationがこない。なぜ?

  1. GATT serverはリクエストに対してレスポンスを返すだけ。他のことはしない
  2. Notificationをonにしたら、GATTがchange eventをトリガーする。これはコネクトしてるデバイスだけのことですね?

characteristic.stopNotifications()で通知を止めることができる。removeListenerにもご注意を。

コネクションが切断されたときgattserverdisconnected

このイベントをlistenすることができる。gatt.disconnect()でも切断できます。

navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
  device.addEventListener('gattserverdisconnected', onDisconnected);

  return device.gatt.connect();
})

GATT descriptors

これはcharacteristicの説明文?ですかね。

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

ブルートゥースのAPIではbinary dataを使う場面が多くで、これについて自分の別の記事「JavaScript String, Binary Data と Base64」を読むのはお勧めします。