micro:bitのBLEのA・BボタンイベントをNode.js nobleで取得してみるメモ

micro:bitのBLEのA・BボタンイベントをNode.js nobleで取得してみるメモです。

いきさつ

まず、BLEは特にデリケートなので、参考までに使用Raspberry Piのインストール情報を記載しておきます。

[Kernel]

No LSB modules are available.
Distributor ID: Raspbian
Description: Raspbian GNU/Linux 9.3 (stretch)
Release: 9.3
Codename: stretch
Linux version 4.9.59-v7+ (dc4@dc4-XPS13-9333) (gcc version 4.9.3 (crosstool-NG crosstool-ng-1.22.0-88-g8460611) ) #1047 SMP Sun Oct 29 12:19:23 GMT 2017

[Node.js]

v6.13.0

2017-11-29-raspbian-stretch.img をベースに設定しています。nobleを使うにあたってのBluetooth apt-get も済ませている状況です。→参考:noble/noble: A Node.js BLE (Bluetooth Low Energy) central module

私の環境ですと、BLE自体はいくらか動作するものの、2018/02/18時点で node-bbc-microbit がうまく動かせないので、ひとまず、micro:bitのBLEのA・BボタンイベントをNode.js nobleで取得してみることにしました。

image

node-bbc-microbit サンプルのbutton-listener.jsを参考にやってみたものの、connectingにはなるものの、connectedにはならない。このあたりは私の理解不足がありそうなので、いずれ再挑戦したいです。

micro:bit

micro:bit側ソースコードはこちらです。

image

micro:bitとHoloLensをBluetoothで接続できたメモをベースに、接続時・切断時・起動時にLEDサービスとボタンサービスが立ち上がる仕組み。

JavaScriptとしては以下のように書かれています。

bluetooth.onBluetoothConnected(() => {
    basic.showIcon(IconNames.Happy)
})
bluetooth.onBluetoothDisconnected(() => {
    basic.showIcon(IconNames.Asleep)
})
bluetooth.startLEDService()
bluetooth.startButtonService()
basic.showIcon(IconNames.Heart)

Raspberry Pi側 Node.js

nobleインストール

以前の記事、

NodeJSでBLE通信ができるnobleライブラリでkonashiとつなげたメモ – 1ft-seabass.jp.MEMO

こちらを参考に、 noble@1.9.0 が入りました。

micro:bit ボタンサービスのUUIDを把握する

このあたりは node-bbc-microbit/button-service.js の情報がわかりやすいので参考にします。

var BUTTON_SERVICE_UUID          = 'e95d9882251d470aa062fa1922dfa9a8';
var BUTTON_A_CHARACTERISTIC_UUID = 'e95dda90251d470aa062fa1922dfa9a8';
var BUTTON_B_CHARACTERISTIC_UUID = 'e95dda91251d470aa062fa1922dfa9a8';

noble側ソースコード

以前のnoble記事 を元に書いていきます。UUIDも把握できてますし、一度、実装の経験があるとわかりやすいですね。

var noble = require('noble');
 
var BUTTON_SERVICE_UUID          = 'e95d9882251d470aa062fa1922dfa9a8';
var BUTTON_A_CHARACTERISTIC_UUID = 'e95dda90251d470aa062fa1922dfa9a8';
var BUTTON_B_CHARACTERISTIC_UUID = 'e95dda91251d470aa062fa1922dfa9a8';
var BUTTON_A_CHARACTERISTIC;
var BUTTON_B_CHARACTERISTIC;

// 状態がパワーONだったらスキャンに移行
noble.on('stateChange', function(state) {
    console.log('on -> stateChange: ' + state);
 
    if (state === 'poweredOn') {
        noble.startScanning();
    } else {
        noble.stopScanning();
    }
});
 
noble.on('scanStart', function() {
    console.log('on -> scanStart');
});
 
noble.on('scanStop', function() {
    console.log('on -> scanStop');
});
 
// discover 機器が発見されたら
noble.on('discover', function(peripheral) {
    console.log('on -> discover: ' + peripheral);
    // まずスキャンをとめる
    noble.stopScanning();
 
    // 接続時のイベント
    peripheral.on('connect', function() {
        console.log('on -> connect');
        this.discoverServices();
    });
    // 切断時のイベント
    peripheral.on('disconnect', function() {
        console.log('on -> disconnect');
    });
 
    // 見つけたサービス(機器)へのアクセス
    peripheral.on('servicesDiscover', function(services) {
 
        for(i = 0; i < services.length; i++) {
 
            // サービスがBUTTON_SERVICE_UUIDと一致した時だけ処理
            if(services[i]['uuid'] == BUTTON_SERVICE_UUID){
 
                // サービスのcharacteristic捜索
                services[i].on('includedServicesDiscover', function(includedServiceUuids) {
                    console.log('on -> service included services discovered [' + includedServiceUuids + ']');
                    this.discoverCharacteristics();
                });
 
                // characteristic取得イベント
                services[i].on('characteristicsDiscover', function(characteristics) {
 
                    // characteristics配列から必要なCHARACTERISTICSをUUIDから判断してcharacteristic格納
                    for(j = 0; j < characteristics.length; j++) {
                        if( BUTTON_A_CHARACTERISTIC_UUID == characteristics[j].uuid ){
                            console.log("BUTTON_A_CHARACTERISTIC_UUID exist!!");
                            BUTTON_A_CHARACTERISTIC = characteristics[j];
                            BUTTON_A_CHARACTERISTIC.on('read',function(data,isNotice){
                            	console.log("BUTTON_A_CHARACTERISTIC");
                            	console.log(data);
                            });
                            BUTTON_A_CHARACTERISTIC.subscribe(function(error){
                            	console.log("BUTTON_A_CHARACTERISTIC subscribe start");
                            	console.log(error);
                            });
                        }
                        if( BUTTON_B_CHARACTERISTIC_UUID == characteristics[j].uuid ){
                            console.log("BUTTON_B_CHARACTERISTIC_UUID exist!!");
                            BUTTON_B_CHARACTERISTIC = characteristics[j];
                            BUTTON_B_CHARACTERISTIC.on('read',function(data,isNotice){
                            	console.log("BUTTON_B_CHARACTERISTIC");
                            	console.log(data);
                            });
                            BUTTON_B_CHARACTERISTIC.subscribe(function(error){
                            	console.log("BUTTON_B_CHARACTERISTIC subscribe start");
                            	console.log(error);
                            });
                        }
                    }
 
                });
 
                services[i].discoverIncludedServices();
            }
        }
 
    });
 
    // 機器との接続開始
    peripheral.connect();
});

こちらを button_listener_noble.js で保存します。

実行してみる

実行してみると、無事ログが出てきて接続に成功します。

image

BUTTON_A_CHARACTERISTIC subscribe start
null
BUTTON_B_CHARACTERISTIC subscribe start
null

となっているのは、nullはエラーがないことを示しているので成功です。

image

こちらで接続完了です。

image

早速、A・B・A・Bと交互に押してみましょう。

image

今回はreadで受信したデータ内容も表示しています。Bufferとして、押された時は Buffer01、話した時はBuffer00で受け取れていますね!

余談:うまく接続できない一部のケースはBluetooth再起動で回復できる

もしdiscoverで止まったりnoble warning: unknown peripheralで止まったりうまく接続できないケースがあります。

以下のようなケース。

pi@raspi-main:~/Desktop/noble_test $ node noble_sample.js 
on -> stateChange: poweredOn
on -> scanStart
on -> discover: {"id":"    ","address":"    ","addressType":"public","connectable":true,"advertisement":{"manufacturerData":{"type":"Buffer","data":[    ]},"serviceData":[{"uuid":" ","data":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}}],"serviceUuids":[" "],"solicitationServiceUuids":[],"serviceSolicitationUuids":[]},"rssi":-66,"state":"disconnected"}
on -> scanStop

idやaddressは伏せていますが、Bufferデータはあるものの、つながらない状態。

pi@raspi-main:~/Desktop/noble_test $ node noble_sample.js 
on -> stateChange: poweredOn
on -> scanStart
on -> discover: {"id":"   ","address":"   ","addressType":"random","connectable":true,"advertisement":{"localName":"BBC micro:bit [ ]","serviceData":[],"serviceUuids":[],"solicitationServiceUuids":[],"serviceSolicitationUuids":[]},"rssi":-40,"state":"disconnected"}
on -> scanStop
on -> connect
noble warning: unknown peripheral <  id  >

もうひとつ。connect できるけど noble warning: unknown peripheral になるケース。

これらはBluetoothを手動で down → up 再起動で復帰できることがあります。

sudo hciconfig hci0 down
sudo hciconfig hci0 up

要するに、一回停止して起動してやるのですが、調子良く動きだすことが多いです。ちなみに、 sudo hciconfig hci0 reset だと何かが残っているらしく回復しませんでした。

もし、なにか接続できないトラブルが起きたら、Raspberry Pi全体の再起動をかける前に試してみて下さい。

まとめ

ということで、micro:bitのBLEのA・BボタンイベントをNode.js nobleで取得してみました。

これができると、HoloLensでのC#で頑張って接続プログラムを検証する前に、一旦Node.jsで実装やデータの送受信の仕方を試してから確信を持って対応できますし、もしつまづいたときには、イチからやり直すわけでなく、中間のポイントを持てるので、少し安心できますね。

それでは、よき micro:bit & Node.js Life を!