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

# WebBluetoothAPI 
[WebBluetoothAPI](https://webbluetoothcg.github.io/web-bluetooth/) を利用すれば、ブラウザから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項目について説明していきます。

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

- セキュリティのため SSL 通信が必須です
- [各OSでの実装状況](https://github.com/WebBluetoothCG/web-bluetooth/blob/master/implementation-status.md) (Macではほとんどの機能が対応しています)

## 1. BLEデバイスをスキャンして探す
BLEデバイスのスキャンは <i>navigator.bluetooth.requestDevice</i> メソッドを使用します。
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デバイスに接続するためには <i>BluetoothDevice#gatt.connect()</i> を使用します。
Promise を介して BluetoothRemoteGATTServer オブジェクトが取得できます。

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

## 3. BLEデバイスの Service オブジェクトを取得する
BluetoothGATTService オブジェクトの取得は <i>BluetoothGATTRemoteServer#getPrimaryService()</i> を使用します。
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 オブジェクトの取得は <i>BluetoothGATTService#getCharacteristic()</i> を使用します。
こちらも 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 の値を書き込むには <i>BluetoothGATTCharacteristic#writeValue()</i> を使用します。
なお、writeValue に渡す値は ArrayBuffer オブジェクトである必要があります。
また、読み取る場合には <i>BluetoothGATTCharacteristic#readValue()</i> を使用します。

```
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 のイベントリスナを追加しておきます。
そうすると、デバイスとの通信が切れてしまった時に、コールバックに指定したイベントが発火します。

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

```
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デバイスからブラウザへの通知を受け取ることも可能です。
<i>characteristicvaluechanged</i> イベントを利用します。

```
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 を取得した後に、イベントリスナーを登録することで、値が変化する度にブラウザに通知され、コールバックで指定した処理を実行できます。

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

><b>characteristicvaluechanged</b>
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/  (日本語)
日本語に翻訳してみましたのでよければご参照ください。
>Qiita記事
> [WebBluetoothAPIの公式ドキュメントを翻訳しました](http://qiita.com/tky_bpp/items/b0a58e1b81d7fa370695)

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

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

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

Google Codelabs - PLAYBULB Tutorial
https://codelabs.developers.google.com/codelabs/candle-bluetooth/index.html?index=..%2F..%2Findex#0
Web Bluetooth Samples
https://googlechrome.github.io/samples/web-bluetooth/index.html
Web Bluetooth Demos
https://github.com/WebBluetoothCG/demos

手元にBLEデバイスが無いけど使ってみたい場合には、
スマートフォンをBLEのシミュレータとして扱えるアプリが用意されています。

- [LightBlue Explorer(iOS)](https://itunes.apple.com/jp/app/lightblue-explorer-bluetooth/id557428110)
- [BLE Peripheral Simulator(Android)](https://play.google.com/store/apps/details?id=io.github.webbluetoothcg.bletestperipheral)