読者です 読者をやめる 読者になる 読者になる

BppLOG

Berlin → Tokyo

Web Bluetooth API を使ってBLEデバイスをブラウザから操作する

WebBluetooth javascript プログラミング

日本で一番簡単にビットコインが買える取引所 coincheck bitcoin

WebBluetoothAPI を利用すれば、ブラウザからBluetoothLE(BEL)デバイスを操作できるようになります。

従来であれば、BLEデバイスを操作するには iOS や Android のアプリケーションを用意する必要がありましたが、
このAPIを利用すれば、javascriptのみでブラウザからBLEデバイスを操作できるようになります。
基本的には下記の5ステップで実装をしていきます。

  1. BLEデバイスをスキャンして探す
  2. BLEデバイスに接続する
  3. BLEデバイスの Service オブジェクトを取得する
  4. Service オブジェクトから Characteristic オブジェクトを取得する
  5. Characteristic オブジェクトのRead/Write でデバイスを制御する

上記5ステップと、接続の解除、デバイスからの通知の受け取り、の7項目について説明していきます。

なお、動作環境として以下の制約があります。

1. BLEデバイスをスキャンして探す

BLEデバイスのスキャンは navigator.bluetooth.requestDevice メソッドを使用します。
Promise を介して BluetoothDevice オブジェクトが取得できます。

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]}) 
  .catch(error => console.log(error));

引数として、BLEデバイスのフィルター条件を指定します。
上記の例では、heart_rate サービスを保持するデバイスが検出されます。services の代わりに name を指定すれば、指示したデバイス名を持つデバイスが検出できます。

なお、セキュリティ上の理由で、このメソッドを呼び出すためにはクリックアクションなどのユーザジェスチャーが必須となっています。

ちなみに、現時点では、検出可能な全てのデバイスを検出するには下記のように実装すれば可能です。

navigator.bluetooth.requestDevice({filters: anyDevice()})

function anyDevice() {
  return Array.from('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
      .map(c => ({namePrefix: c}))
      .concat({name: ''});
}

2. BLEデバイスに接続する

BLEデバイスに接続するためには BluetoothDevice#gatt.connect() を使用します。
Promise を介して BluetoothRemoteGATTServer オブジェクトが取得できます。

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]})
 .then(device => device.gatt.connect())
 .catch(error => console.log(error));

3. BLEデバイスの Service オブジェクトを取得する

BluetoothGATTService オブジェクトの取得は BluetoothGATTRemoteServer#getPrimaryService() を使用します。
Promise を介して取得できます。

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]})
  .then(device => device.gatt.connect())
  .then(server => server.getPrimaryService('heart_rate'))
  .catch(error => console.log(error));

4. Service オブジェクトから Characteristic オブジェクトを取得する

BluetoothRemoteGATTCharacteristic オブジェクトの取得は BluetoothGATTService#getCharacteristic() を使用します。
こちらも Promise を介して取得できます。

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]})
  .then(device => device.gatt.connect())
  .then(server => server.getPrimaryService('heart_rate'))
  .then(service => service.getCharacteristic('body_sensor_location'))
  .catch(error => console.log(error));

また、複数の Characteristic を取得する場合には Promise.all を利用します。

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]})
  .then(device => device.gatt.connect())
  .then(server => server.getPrimaryService('heart_rate'))
  .then(service => {
    chosenHeartRateService = service;
    return Promise.all([
      service.getCharacteristic('body_sensor_location')
        .then( /* do some processing */),
      service.getCharacteristic('heart_rate_measurement')
        .then( /* do some processing */),
    ]);
  })
  .catch(error => console.log(error));;

5. Characteristic オブジェクトのRead/Write でデバイスを制御する

BluetoothRemoteGATTCharacteristic の値を書き込むには BluetoothGATTCharacteristic#writeValue() を使用します。
なお、writeValue に渡す値は ArrayBuffer オブジェクトである必要があります。
また、読み取る場合には BluetoothGATTCharacteristic#readValue() を使用します。

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]})
  .then(device => device.gatt.connect())
  .then(server => server.getPrimaryService('heart_rate'))
  .then(service => service.getCharacteristic('heart_rate_control_point'))
  .then(characteristic => {
      let resetEnergyExpended = new Uint8Array([1]);
      return characteristic.writeValue(resetEnergyExpended);
   })
  .catch(error => console.log(error));

6. BLEデバイスとの接続を解除する

参考までに接続を解除する方法もご紹介します。
解除に関しては2パターンあり

  • BLEデバイスの電源が切れるなど、コネクションがロストしてしまう時
  • ブラウザから意図的に接続を解除する時

です。

まずは1つめのパターン。

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]})
 .then(device => {
    device.addEventListener('gattserverdisconnected', onDisconnected);
    device.gatt.connect() 
  })
 .catch(error => console.log(error));

function onDisconnected() {
  console.log('> Bluetooth Device disconnected');
}

BLEデバイスと接続する時に gattserverdisconnected のイベントリスナを追加しておきます。
そうすると、デバイスとの通信が切れてしまった時に、コールバックに指定したイベントが発火します。

次に、意図的に接続を解除する場合は BluetoothDevice#gatt.disconnect() を使用します。

function onDisconnectButtonClick() {
  if (!bluetoothDevice) {
    return;
  }
  console.log('Disconnecting from Bluetooth Device...');
  if (bluetoothDevice.gatt.connected) {
    bluetoothDevice.gatt.disconnect();
  } else {
    console.log('> Bluetooth Device is already disconnected');
  }
}

のように処理をします。
gatt.connect() と異なり、gatt.disconnect() はユーザアクションでなくても構いません。

7. BLEデバイスからの通知を受け取る。

readValue を利用する時は、ブラウザから指示をする必要があるため能動的です。
受動的に、Characteristic の値が変化した時に、BLEデバイスからブラウザへの通知を受け取ることも可能です。
characteristicvaluechanged イベントを利用します。

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]})
  .then(device => device.gatt.connect())
  .then(server => server.getPrimaryService('heart_rate'))
  .then(service => service.getCharacteristic('heart_rate_measurement'))
  .then(characteristic => {
    characteristic.addEventListener('characteristicvaluechanged', onHeartRateChanged);
   })
  .catch(error => console.log(error));

function onHeartRateChanged(event) {
  let characteristic = event.target;
  console.log(characteristic.value);
}

このように Characteristic を取得した後に、イベントリスナーを登録することで、値が変化する度にブラウザに通知され、コールバックで指定した処理を実行できます。

注意事項として、この characteristicvaluechanged イベントは値が変化した時以外にも、 readValue を実行した後にも発生してしまいます。

characteristicvaluechanged
Fired on a BluetoothRemoteGATTCharacteristic when its value changes, either as a result of a read request, or a value change notification/indication.
https://webbluetoothcg.github.io/web-bluetooth/#event-types

開発中になぜか notification が大量発生したことがあり、調べてみると仕様であることがわかりました。お気をつけ下さい。

参考

WebBluetoothAPI 公式ドキュメント
https://webbluetoothcg.github.io/web-bluetooth/ (英語)
https://tkybpp.github.io/web-bluetooth-jp/ (日本語)
日本語に翻訳してみましたのでよければご参照ください。

いくつかのBLEデバイスを利用してサンプルデモも公開しています。
GitHub - tkybpp/simple-webbluetooth

サンプルで利用しているBLEデバイスは以下の3つです。

BB-8を利用してみましたが、とても可愛いです。おすすめです。

他にも Google や WebBluetoothコミュニティグループがサンプルを公開しています。

Google Codelabs - PLAYBULB Tutorial
Control a PLAYBULB candle with Web Bluetooth
Web Bluetooth Samples
https://googlechrome.github.io/samples/web-bluetooth/index.html
Web Bluetooth Demos
GitHub - WebBluetoothCG/demos: Demo applications showing off Web Bluetooth


手元にBLEデバイスが無いけど使いたい

大丈夫です。
手元のスマートフォンをBLEのシミュレータとして扱えるアプリが用意されています。


このAPIを利用すれば、javascript のみでBLEデバイスを操作できますが、より複雑なライブラリやアプリケーションを実装するには、Bluetooth自体の知識も必要になってきます。BLEに関してはこの2冊がとても詳しく説明してくれているのでおすすめです。

Bluetooth Low Energy: The Developer's Handbook

Bluetooth Low Energy: The Developer's Handbook

iOS×BLE Core Bluetoothプログラミング

iOS×BLE Core Bluetoothプログラミング

(関連記事)

tkybpp.hatenablog.com