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で取得してみることにしました。

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

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 で保存します。
実行してみる
実行してみると、無事ログが出てきて接続に成功します。

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

こちらで接続完了です。

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

今回は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 を!