LeafonyとWeb Bluetoothでラクラク！クロスプラットフォームIoT開発入門

#はじめに
前回、[2cm角の極小Arduino互換基板Leafony](https://qiita.com/s_nkg/items/de62b1b3900e730e986c)を紹介しました。

このLeafonyではBluetoothを使ってセンサのデータをPCやスマホに送信するために、**Web Bluetooth**を使ったサンプルを提供しています。Web Bluetoothを使えば、**WebアプリでBluetoothを使用できる**ので、スマホやPCなどブラウザが入っているOSであれば同じコードで動作して便利です。わざわざAndroid用やWindows用、MacOS用のようにソフトウェア開発を行わなくても良くなります。

今回はそのような、**LeafonyとWeb Bluetoothを使ったクロスプラットフォームIoT開発**について説明します。

#まずはWeb Bluetoothを試す
まずはそもそもWeb Bluetoothを使ったアプリとはどんな動作をするのか、サンプルのアプリを使って見ていきましょう。

### 用意するもの
* Google Chromeが動く端末 (スマホ、タブレット、PCなんでも可)
    * iPhoneまたはiPadの場合は[WebBLE](https://apps.apple.com/jp/app/webble/id1193531073)が必要
* Leafony Basic Kit

### サンプルアプリのページ
1. Leafonyの電源を入れる
2. Chromeで[サンプルアプリのページ](https://trillion-node.org/wp/wp-content/SW/sensor-trillion-app/index.html)にアクセス
3. 「Connect」ボタンを押して、**Leafony_AC01**に接続
4. 温度や照度などのセンサデータが表示される

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410452/2bf817e2-74d8-caf3-7a60-7a342baaa7cf.gif)


下記のページにスマホやPCでサンプルアプリを動かす方法について書かれています。
https://trillion-node.org/example/webbluetooth/
PC版のChromeの場合、
`chrome://flags/#enable-experimental-web-platform-features`の`Experimental Web Platform features`をEnableとする必要があります。

#サンプルアプリの中身を見ていく
サンプルアプリのコードはgithub上に置かれています。
https://github.com/Leafony/WebBluetooth_for_Leafony_app

`index.html`と`app.js`と`leafony.js`の3つのファイルで構成されています。

`index.html`はボタンや表の配置を行ったり、他2つのjavascriptのファイルを読み込むだけの単純なものです。
`app.js`はボタンが押されたときの処理などメインの処理が記述されています。
`leafony.js`はWeb Bluetoothを使ってLeafonyと接続するための具体的な処理がかかれています。

簡単に中のコードを追って見ていきましょう。

### LeafonyとBluetoothで接続する処理
`app.js`内の以下の部分に「Connect」ボタンが押された時の処理が書かれています。(説明用のコメントを追加しました。)
データを受信した時に呼び出される関数の指定や、Sleepモードの設定をしてから`leafony.connect()`で接続します。
(現状はSleepモードのチェックボックスはUI側で非表示になっていて、常に無効となっています。)

```Javascript:app.js
buttonConnect.addEventListener( 'click', function () {

	leafony = new Leafony();
	// Leafonyからデータが送られてきた時に実行される処理
	leafony.onStateChange( function ( state ) {
		updateTable( state );
	} );
	// LeafonyのSleep機能を使うかどうか
	// 現状は動作が不安定なのでswitchSleepModeのチェックボックスが押せなくなっています。
	// leafony.disableSleep()側が呼び出されます。
	if ( switchSleepMode.checked ) {
		leafony.enableSleep();
	} else {
		leafony.disableSleep();
	}
	// Leafonyと接続 ここで周辺のBluetoothを検索するポップアップが出現します。
	leafony.connect();

	buttonConnect.style.display = 'none';
	buttonDisconnect.style.display = '';

} );
```

`leafony.connect()`の中ではWeb Bluetoothを呼び出してLeafonyと接続する処理が書かれています。

```Javascript:leafony.js
    // LeafonyのBluetoothのUUID
    const SERVICE_UUID = "442f1570-8a00-9a28-cbe1-e1d4212d53eb";
    const CHARACTERISTIC_READ_UUID = "442f1571-8a00-9a28-cbe1-e1d4212d53eb";
    const CHARACTERISTIC_WRITE_UUID = "442f1572-8a00-9a28-cbe1-e1d4212d53eb";

    // 省略

    /**
     * Bluetoothリーフと接続する関数
     * @param none
     * @return none
     */
    async function connect () {

        try {
            // Web Bluetoothのデバイス検索ポップアップを表示する
            device = await navigator.bluetooth.requestDevice( {
                acceptAllDevices: true,
                optionalServices: [ 'generic_access', SERVICE_UUID ],               
            } );

            console.log( '> Unique Name: ' + device.name );
            console.log( '> ID: ' + device.id );
            console.log( '> Connected: ' + device.gatt.connected );
            uniqueName = String( device.name );
            // 切断時に呼び出される関数
            device.addEventListener( 'gattserverdisconnected', onDisconnected );
            // GATTと接続
            server = await device.gatt.connect();
            // ServiceやCharacteristicと接続
            connectService( server );
        } catch ( error ) {
            console.log( error );
        }

    }
```

### データを受信した時の処理
データを受信したら`updateTable()`が呼び出されます。
受信したデータは`state`変数の中に入っているので、次のようにテーブルに表示しています。

```Javascript:app.js
function updateTable ( state ) {
	// 省略
	// Bluetoothで受信したデータをテーブルに表示
	textDeviceName.innerText = state.devn;
	textUniqueName.innerText = state.unin;
	textDateTime.innerText = datetime;
	textTemp.innerText = state.temp;
	textHumid.innerText = state.humd;
	textIllum.innerText = state.illm;
	textTilt.innerText = state.tilt;
	textBatt.innerText = state.batt;
	textDice.innerText = state.dice;

	// 省略
}
```

もしLeafony側で送信するデータを変更した場合は、`leafony.js`内の以下の部分を変更すれば任意のデータを扱えます。
`state.xxxx`のような名前も以下の部分でつけることができます。
Leafony側で送信するデータの変え方は次の章で説明します。

```Javascript:leafony.js
    /**
     * Characteristicの値が変化した時に呼び出される関数
     * @param {*} event 
     */
    function handleData( event ) {
        // 受信したデータの文字コードを変換して、カンマでデータを分けている
        // このサンプルではLeafonyは
        // 温度,湿度,傾き,電池の電圧
        // のようにカンマ区切りのテキストを送信している。
        let data = event.target.value;
        let decoder = new TextDecoder( 'utf-8' );
        data = decoder.decode( data );
        data = data.replace( /\r?\n/g, '' );
        data = data.split( ',' );

        state.devn = deviceName;
        state.unin = uniqueName;
        state.temp = data[0];
        state.humd = data[1];
        state.illm = data[2];
        state.tilt = data[3];
        state.batt = data[4];
        state.dice = data[5];
        // ここで updateTable() が実行されている
        onStateChangeCallback( state );

        // 省略
    }
```

### データを送信する処理
また、「LED+」「LED-」ボタンを押すと、LeafonyのLEDの点滅スピードを変えることができます。
ボタンが押された時にLeafonyに`PLS`や`MNS`といったコマンドを次のように送ることで、Leafonyのコントロールも可能です。

```Javascript:app.js
buttonLedPls.addEventListener ( 'click', function () {

	console.log( 'LED Plus Button Clicked' );
	leafony.sendCommand( 'PLS' );

});
```

このように、LeafonyのサンプルアプリではとてもシンプルにBluetoothとの接続やデータのやり取りを記述しています。
今度は、Leafony側がどのようにしてセンサデータをBluetoothで送っているかを見ていきましょう。

#Leafony側ではどのようにデータを送信しているか
Leafony側に書き込まれているArduinoスケッチは次のリポジトリにあります。
https://github.com/Leafony/Sample-Sketches/blob/master/4-Sensors_BLE.zip

`4-Sensors_BLE.ino`の1030行目付近に次のような記述があり、この部分でテキストデータをBluetoothで送信しています。
このテキストを変えることで任意のデータを送信可能です。

```C:4-Sensors_BLE.ino
sendLen = sprintf(sendData, "%04s,%04s,%04s,%04s,%04s,%01s\n", temp, humid, light, tilt, battVolt, pips)
```

# Web Bluetoothの注意点
2019年10月時点ではWeb Bluetoothは以下の点に注意しなければいけません。

* iPhoneのchromeでは動作しません。[WebBLE](https://apps.apple.com/jp/app/webble/id1193531073)という有料のアプリを使えば利用可能です。
* iBeaconやEddystoneのようなビーコンは非対応です。Advertisementパケットに含まれるデータを取ることにはまだ対応していないようです。

# まとめ
これまで様々な環境でBluetoothを用いたアプリをつくるのは大変でしたが、**Web Bluetoothを使えば簡単にクロスプラットフォームなIoTアプリを実現可能**となります。
LeafonyとWeb Bluetoothを組み合わせる事によって、難しいハードウェアの設計などをすることなく、楽にバッテリー駆動のIoT機器を実現し、クロスプラットフォームなBluetoothアプリまでも簡単に作ることができるようになりました。

# 参考
* [Web Bluetooth Samples](https://googlechrome.github.io/samples/web-bluetooth/)
* [トリリオンノード研究会](https://trillion-node.org/)
* [Leafonyの公式ページ](https://leafony.com/)