Web Bluetooth APIを使ってlittleBits Bluetooth(konashi)モジュールを連携したメモ

JS Board Shibuya #6 LTナイトでWeb Bluetooth APIがだいぶ使えるようになってきたと知りlittleBits Bluetooth(konashi)モジュールを連携したメモです。

今回のやりたいこと

このような画面を作りWeb Bluetooth APIが操作するhttps環境をHerokuに制作します。

web-bluetooth-api-meets-littlebits-konashi-module_2

  • effect_startボタンを押すと端末のスキャンから各ピンのCharacteristics保持まで行う。その後、1秒毎に入出力を測定。
  • デジタル出力は1秒毎にON/OFFする。上部の濃いグレーの「–」となっているエリアにはアナログの入力値を表示する。
  • 以前のNodeJSでkonashiとつなげたメモの知見をベースに今回のAPI仕様に合わせていく

effect_startボタンを押すと端末のスキャンから各ピンのCharacteristics保持

effect_startボタンやアナログ入力表示エリアはシンプルに組んで、端末のスキャンから各ピンのCharacteristics保持まではWeb Bluetooth APIのサンプルを参考に組んでいきました。

また、Web Bluetooth API で BLE デバイスにブラウザから接続するこちらの記事でnamePrefixなどのハマリポイントを助けていただきました。奇しくもJS Board Shibuya #6 LTナイトで参加された方で、私と同じように興味を持たれた方でした!

なお、Web Bluetooth APIが本当に動くのか当初不安だったのですがWeb Bluetoothを使ってkonashi2.0でLチカしてみるQiita記事とサンプルを動作させてググッとやる気が湧いてきました!

それぞれの記事ともにありがとうございました。

effect_startボタンを押すと端末のスキャンから各ピンのCharacteristics保持までがこちらです。

// KONASHI自体をnamePrefixで拾うための名称
var KONASHI_NAMEPREFIX = 'konashi';
// KONASHI自体のサービスID
var KONASHI_SERVICE_UUID = '229bff00-03fb-40da-98a7-b0def65c2d4b';
// 操作したいpinのID
var KONASHI_ANALOG_INPUT_PINID   =  '229b3008-03fb-40da-98a7-b0def65c2d4b';
var KONASHI_PIO_SETTING_PINID    =  '229b3000-03fb-40da-98a7-b0def65c2d4b';
var KONASHI_PIO_OUTPUT_PINID     =  '229b3002-03fb-40da-98a7-b0def65c2d4b';
// 各CHARACTERISTICSをcharacteristic格納する変数
var KONASHI_CHARACTERISTICS_ANALOG_READ;
var KONASHI_CHARACTERISTICS_PIO_SETTINGS;
var KONASHI_CHARACTERISTICS_PIO_OUTPUT;
// LED ON/OFF
var led_toggle = false;
var led_interval;


function effect_start() {
    addLog('------------------- effect_start');

    if (navigator.bluetooth) {
        navigator.bluetooth.requestDevice({
            // KONASHI自体をnamePrefixで拾う
            filters: [{
                namePrefix: KONASHI_NAMEPREFIX
            }]
        }).then(device => {
            // デバイスにつなげに行く
            addLog('device.connectGATT()');
            return device.connectGATT();
        }).then(server => {
            // サービスの検索
            addLog('Search Service');
            addLog('server.getPrimaryService');
            addLog(KONASHI_SERVICE_UUID);
            return Promise.all([
                server.getPrimaryService(KONASHI_SERVICE_UUID)
            ]);
        }).then(services => {
            // サービス0番目がkonashiなので各PINIDでCharacteristicを検索
            addLog('server[n].getCharacteristic');
            addLog(KONASHI_ANALOG_INPUT_PINID);
            addLog(KONASHI_PIO_SETTING_PINID);
            addLog(KONASHI_PIO_OUTPUT_PINID);
            return Promise.all([
                services[0].getCharacteristic(KONASHI_ANALOG_INPUT_PINID),
                services[0].getCharacteristic(KONASHI_PIO_SETTING_PINID),
                services[0].getCharacteristic(KONASHI_PIO_OUTPUT_PINID)
            ]);
        }).then(characteristics =>{
            // 取得できたら各変数に記録しておく
            addLog('[characteristics]');
            KONASHI_CHARACTERISTICS_ANALOG_READ = characteristics[0];
            KONASHI_CHARACTERISTICS_PIO_SETTINGS = characteristics[1];
            KONASHI_CHARACTERISTICS_PIO_OUTPUT = characteristics[2];
            // PinModeにUint8Array(1)で1byteデータを作成し
            // 255(8bit全部1)を入力し全Pin出力へON
            var PinModeValues = new Uint8Array(1);
            PinModeValues[0] = 255;
            KONASHI_CHARACTERISTICS_PIO_SETTINGS.writeValue(PinModeValues);
            // デジタル出力は1秒毎にON/OFFとアナログ計測開始
            startBlink();
        }).catch(error => {
            addLog('[error]');
            addLog(error);
        });
    } else {
        addLog('[navigator.bluetooth.requestDevice]');
        addLog('null');
    }
}

ES6のPromiseについてはJavaScript Promiseの本を参考にさせていただきつつ、IntelliJ IDEAのES6コードヒントに助けられながら書きました。

全Pin出力へONに開放するところまで1つのPromiseのフローで行い、デジタル出力は1秒毎にON/OFFとアナログ計測を行うstartBlinkの関数につなげます。Blinkという名前は当初LEDの点滅で検証していたのでご了承ください^^;

地味にNodeJSで255を送るような方法が素のJSでどう書くかをUint8Arrayに至るまでがかなり苦労しましたが良い思い出です。new Uint8Array(255)とか勘違いしまして、そうなると255byte配列が生成されてkonashiに投げ込んでしまい一瞬おかしくしたのも良い思い出です。

デジタル出力は1秒毎にON/OFFとアナログ計測を行う

こちらもsetIntervalで1秒毎に動作させつつアナログ計測するreadValueから実際の点滅でwriteValueするところまでPromiseで一連のフローで書きます。

function startBlink() {
    addLog('[startBlink]');

    stop_led_interval();

    // 実際の点滅させるところ
    led_interval = setInterval(function(){
        // ANALOG_READ
        // アナログデータ取得
        KONASHI_CHARACTERISTICS_ANALOG_READ.readValue(

        ).then(sensorData => {
            // アナログデータ取得後にUint8Array加工
            if (sensorData) {
                var data = new Uint8Array(sensorData);
                var analog_value = (data[1] + data[0]*256)/1000;
                $('#analog_value').html(analog_value);
            }
            // データ取得後に実際の点滅
            // DIGITAL OUTPUT PIO2
            var OutputValues = new Uint8Array(1);
            if(led_toggle){
                // LED ON
                addLog("LED ON");
                // 255(8bit全部1)を入力し全Pin出力へON
                OutputValues[0] = 255;
            } else {
                // LED OFF
                addLog("LED OFF");
                // 0(8bit全部0)を入力し全Pin出力へOFF
                OutputValues[0] = 0;
            }
            return KONASHI_CHARACTERISTICS_PIO_OUTPUT.writeValue(OutputValues);
        }).catch(error => {
            addLog('[error]');
            addLog(error);
        });

        // LED ON/OFFの反転
        led_toggle = !led_toggle;
    }, 1000);

}

アナログデータのところは幸いにも以前のNodeJSでkonashiとつなげたメモの知見があったので迷わず書くことが出来ました。

実際のコード

実際のコードは以下です。

// KONASHI自体をnamePrefixで拾うための名称
var KONASHI_NAMEPREFIX = 'konashi';
// KONASHI自体のサービスID
var KONASHI_SERVICE_UUID = '229bff00-03fb-40da-98a7-b0def65c2d4b';
// 操作したいpinのID
var KONASHI_ANALOG_INPUT_PINID   =  '229b3008-03fb-40da-98a7-b0def65c2d4b';
var KONASHI_PIO_SETTING_PINID    =  '229b3000-03fb-40da-98a7-b0def65c2d4b';
var KONASHI_PIO_OUTPUT_PINID     =  '229b3002-03fb-40da-98a7-b0def65c2d4b';
// 各CHARACTERISTICSをcharacteristic格納する変数
var KONASHI_CHARACTERISTICS_ANALOG_READ;
var KONASHI_CHARACTERISTICS_PIO_SETTINGS;
var KONASHI_CHARACTERISTICS_PIO_OUTPUT;
// LED ON/OFF
var led_toggle = false;
var led_interval;


function effect_start() {
    addLog('------------------- effect_start');

    if (navigator.bluetooth) {
        navigator.bluetooth.requestDevice({
            // KONASHI自体をnamePrefixで拾う
            filters: [{
                namePrefix: KONASHI_NAMEPREFIX
            }]
        }).then(device => {
            // デバイスにつなげに行く
            addLog('device.connectGATT()');
            return device.connectGATT();
        }).then(server => {
            // サービスの検索
            addLog('Search Service');
            addLog('server.getPrimaryService');
            addLog(KONASHI_SERVICE_UUID);
            return Promise.all([
                server.getPrimaryService(KONASHI_SERVICE_UUID)
            ]);
        }).then(services => {
            // サービス0番目がkonashiなので各PINIDでCharacteristicを検索
            addLog('server[n].getCharacteristic');
            addLog(KONASHI_ANALOG_INPUT_PINID);
            addLog(KONASHI_PIO_SETTING_PINID);
            addLog(KONASHI_PIO_OUTPUT_PINID);
            return Promise.all([
                services[0].getCharacteristic(KONASHI_ANALOG_INPUT_PINID),
                services[0].getCharacteristic(KONASHI_PIO_SETTING_PINID),
                services[0].getCharacteristic(KONASHI_PIO_OUTPUT_PINID)
            ]);
        }).then(characteristics =>{
            // 取得できたら各変数に記録しておく
            addLog('[characteristics]');
            KONASHI_CHARACTERISTICS_ANALOG_READ = characteristics[0];
            KONASHI_CHARACTERISTICS_PIO_SETTINGS = characteristics[1];
            KONASHI_CHARACTERISTICS_PIO_OUTPUT = characteristics[2];
            // PinModeにUint8Array(1)で1byteデータを作成し
            // 255(8bit全部1)を入力し全Pin出力へON
            var PinModeValues = new Uint8Array(1);
            PinModeValues[0] = 255;
            KONASHI_CHARACTERISTICS_PIO_SETTINGS.writeValue(PinModeValues);
            // デジタル出力は1秒毎にON/OFFとアナログ計測開始
            startBlink();
        }).catch(error => {
            addLog('[error]');
            addLog(error);
        });
    } else {
        addLog('[navigator.bluetooth.requestDevice]');
        addLog('null');
    }
}

function startBlink() {
    addLog('[startBlink]');

    stop_led_interval();

    // 実際の点滅させるところ
    led_interval = setInterval(function(){
        // ANALOG_READ
        // アナログデータ取得
        KONASHI_CHARACTERISTICS_ANALOG_READ.readValue(

        ).then(sensorData => {
            // アナログデータ取得後にUint8Array加工
            if (sensorData) {
                var data = new Uint8Array(sensorData);
                var analog_value = (data[1] + data[0]*256)/1000;
                $('#analog_value').html(analog_value);
            }
            // データ取得後に実際の点滅
            // DIGITAL OUTPUT PIO2
            var OutputValues = new Uint8Array(1);
            if(led_toggle){
                // LED ON
                addLog("LED ON");
                // 255(8bit全部1)を入力し全Pin出力へON
                OutputValues[0] = 255;
            } else {
                // LED OFF
                addLog("LED OFF");
                // 0(8bit全部0)を入力し全Pin出力へOFF
                OutputValues[0] = 0;
            }
            return KONASHI_CHARACTERISTICS_PIO_OUTPUT.writeValue(OutputValues);
        }).catch(error => {
            addLog('[error]');
            addLog(error);
        });

        // LED ON/OFFの反転
        led_toggle = !led_toggle;
    }, 1000);

}

function addLog(str) {
    $('#logs').append('<li>' + str + '</li>');
    console.log(str);
}

function init() {

    $('#effect_start').on('click', function () {
        effect_start();
    });

    addLog('start');
}

動かす前の準備

動かす前の準備です。まず、Google PlayからAndroidにChromeのDEV版をダウンロードします。

web-bluetooth-api-meets-littlebits-konashi-module_3

インストール。

web-bluetooth-api-meets-littlebits-konashi-module_4

DEV版を起動して、chrome://flags/#enable-web-bluetoothでWebBluetoothを使えるようにします。

こちらを、

web-bluetooth-api-meets-littlebits-konashi-module_5

有効にして、再起動しましょう。

web-bluetooth-api-meets-littlebits-konashi-module_6

LED点滅もいいですがlittleBitsの良さを活かします。

今回はサーボに、

web-bluetooth-api-meets-littlebits-konashi-module_7

接着ネリケシをくっつけられるようにして、

web-bluetooth-api-meets-littlebits-konashi-module_8

マグロ模型でマグロメーターにします。

web-bluetooth-api-meets-littlebits-konashi-module_9

できました。

実際に動かしてみる

Herokuに出来上がったコンテンツにアクセスします。

web-bluetooth-api-meets-littlebits-konashi-module_10

effect_startを押すとnamePrefixでフィルタされたkonashiがペア設定で表示されます。(事前にkonashiはペアリングしておきましょう)

web-bluetooth-api-meets-littlebits-konashi-module_11

konashiを選択して、右下の「ペア設定」を押します。

web-bluetooth-api-meets-littlebits-konashi-module_12

実際の動作画像です。

まずはマグロメーターがデジタル出力がON/OFFされることで動作します。

つづいて、アナログ入力についてもレバーをうごかすことでChrome上の濃いグレーの領域で無事計測値が表示されています。

ということで、無事連携できました!先ほどの記事でも言及されていましたが、

Web Bluetooth API で BLE デバイスにブラウザから接続する – Playground, Tech Blog of Beatrobo, Inc.

まだ不安定。だが期待はできる!

実装自体もまだ始まったばかりの Web Bluetooth API。とても不安定です。実際に開発している最中でも、同じデバイス名が2つ出てきたり、まったく接続できなくなったりします。その時はブラウザを再起動してやればまた正常に動きますのでご安心を。

おっしゃるとおり確かに不安定な面がありますが、アプリをわざわざ作らずにBlueTooth連携できると、イベントごとなどのデバイス連携において、かなりハードルが下がりそうなのでワクワクします!

それでは、よきWeb Bluetooth API+littleBits Lifeを!