便利ページ：Web Bluetooth APIでBLEデバイスに接続


「[便利ページ：Javascriptでちょっとした便利な機能を作ってみた](https://qiita.com/poruruba/items/6c67c7eef34ed302feb3 )」のシリーズものです。

久しぶりのネタですが、今回はWeb Bluetooth APIを使って、BLEデバイスに接続してみます。
以下のことができるようなページです。

* BLE Peripheral デバイスに接続し、プライマリサービスを一覧表示します。
* プライマリサービスにあるキャラクタリスティックを一覧表示します。
* キャラクタリスティックに対して、任意のデータをRead・Writeをしたり、Notificationを有効にして、Notificationを受信します。

ソースコードは以下にあります。過去のいろいろな詰め合わせになってます。

poruruba/utilities
　https://github.com/poruruba/utilities

使いたいだけであれば、以下にアクセスしてみてください。ユーティリティの「BLECentral」のタブを選択してみてください。
　https://poruruba.github.io/utilities/

(参考)
　https://webbluetoothcg.github.io/web-bluetooth/
　https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API


# BLEデバイスの検索

Web Bluetooth APIの``` navigator.bluetooth.requestDevice() ``` で、BLEデバイスの検索を開始します。
ちなみに、requestDeviceはユーザジェスチャ(ボタン押下など)から呼び出される必要があります。

```js:public/js/start.js
    var additional_services = [];
    if( this.ble_additional_services() ){
        additional_services = this.ble_additional_services.split(/\r\n|\r|\n/)();
        for( var i = 0 ; i < additional_services.length ; i++ ){
            if( additional_services[i].length == 4 )
            additional_services[i] = parseInt(additional_services[i], 16);
        }
    }
    var optionalServices = additional_services.concat(Object.keys(serviceUuidList).map(x => parseInt(x)));

    console.log('Execute : requestDevice');
    var device = await navigator.bluetooth.requestDevice({
        acceptAllDevices:true,
        optionalServices: optionalServices
    });
```

通常は、どのBLEデバイスに接続するかをフィルタ指定するのですが、今回のように任意のBLEデバイスに接続する場合は、フィルタとして、「acceptAllDevices: true」を指定します。ですが、これだけですと、以降のプライマリサービス検索時に、任意のプライマリサービスを検索できないため、考えられるプライマリサービスのリストをoptionalServicesに指定します。標準的なプライマリサービスは含めていますが、独自のプライマリサービスを含めたい場合は、ble_additional_servicesに行単位でUUIDを指定できるようにしました。

# プライマリサービスの検索

以下の部分です。

```js:public/js/start.js
    var server = await this.ble_device.gatt.connect()
    console.log('Execute : getPrimaryServices');
    var services = await server.getPrimaryServices();
    console.log(services);
```

通常接続したいプライマリサービスのUUIDを知っているのであれば、```server.getPrimaryService(UUID)```で良いのですが、今回は任意のプライマリサービスを検索したいため、```server.getPrimaryServices()```を使っています。
ble_additinal_servicesに独自のUUIDを含めていなければ、ここでは独自のUUIDは検索に引っかかりませんのでご注意ください。(一度取得してペアリング済みにすれば次からは指定不要のようです。。。)
キャラクタリスティックの一覧の取得は、```service.getCharacteristics()```です。こちらも同様に、UUIDを知っていれば、```service.getCharacteristic(UUID) ```を使います。

一緒に、コールバック関数も登録しています。
このコールバックは、キャラクタリスティックReadした値の取得や、Notificationを受信すると呼び出されます。

```js:public/js/start.js
characteristic.addEventListener('characteristicvaluechanged', this.ble_onDataChanged.bind(this.ble_onDataChanged) );
```

#キャラクタリスティックRead

キャラクタリスティックのバリューReadは以下の部分です。

```js:public/js/start.js
        async value_read(characteristic){
            try{
                await characteristic.characteristic.readValue();
                this.$set(characteristic, "value_changed", false);
            }catch(error){
                console.error(error);
            }
        },
```

読み出した値は、readValue()でも返るのですが、先ほど登録したコールバック関数``` this.ble_onDataChanged``` でも受信されるため、そこでまとめて実施しています。
値はDataView型で取得されるため、Uint8Arrayに変換処理をしています。

# キャラクタリスティックWrite

キャラクタリスティックのバリューWriteは以下の部分です。
バリューはUint8Arrayで渡す必要があります。

```js:public/js/start.js
        async value_write(characteristic){
            try{
                var array = this.hex2array(characteristic.value_hex);
                if( characteristic.characteristic.properties.writeWithoutResponse )
                    await characteristic.characteristic.writeValueWithoutResponse(array);
                else
                    await characteristic.characteristic.writeValueWithResponse(array);
            }catch(error){
                console.error(error);
            }
        },
```

# キャラクタリスティック Notification有効化

キャラクタリスティックのNotification有効化は以下の部分です。

```js:public/js/start.js
        async notify_enable(characteristic, forceEnable = false){
            if( characteristic.notify_enabled && !forceEnable ){
                characteristic.characteristic.stopNotifications();
                this.$set(characteristic, "notify_enabled", false);
            }else{
                characteristic.characteristic.startNotifications();
                this.$set(characteristic, "notify_enabled", true);
            }
        },
```

※this.$set・・・ は、Web Bluetooth APIの処理ではなく、Vueの特有の処理です。

#使ってみる

まずは、Webページを開き、BLECentralタブを選択します。

　https://poruruba.github.io/utilities/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/c57c998a-9b91-614b-0a42-eb3a2f995782.png)

このまま「Connect」ボタンを押していただければ、一般的なBLEデバイスを検出できます。
今回は、BLEデバイスとして優しいPlayBulbを操作してみます。

以下を参考にしています。
　[GoからBLE制御でPLAYBULB Candleの操作をしてみる](https://qiita.com/n0bisuke/items/d3966b11b2e9143d6651)

これを見ると、プライマリサービスは標準的なものではなく、独自のもので、UUIDは「ff02」のようです。ですので、Additional Servicesに「ff02」と入力してから「Connect」ボタンを押下します。

BLEデバイスの選択ダイアログが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/0857196c-64b0-8635-e3ca-a98aed140dfa.png)

「PLAYBULB CANDLE」が見つかりました。選択してペア設定ボタンを押下します。
そうすると、プライマリサービスを検索して一覧表示してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/27b0da51-5eb9-6d82-3fc3-00bfb5104a2b.png)

Device Informationサービス、Battery Service、Generic Attribute、Generic Accessサービスに加えて、FF02というプライマリサービスがあります。

投稿を参考にすると、キャラクタリスティックFFFFには名前があり、PlayBulbとなっているとのこと、Readしたら値が読み出せました。
FFFCに、00FF0000としてWriteすると、PlayBulbの明かりが赤色の点灯に切り替わりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/9a140654-058b-800f-0c85-fc1b1fe985bd.png)

# 補足

Web Bluetooth APIを使ったBLEデバイスの接続の実装は以上ですが、今回ついでに、本UtilityページをPWA化しています。
Chromeでページを開くと、アドレスバーの右側に⊕マークがあるので、それをクリックすると、アプリとして登録できます。そうすると、WindowsスタートメニューのChromeアプリのところに登録されるため、今後は素早く起動でき、Chromeのタブとは分離して利用することができますので便利です。

以上