node.js から BLE のデバイスと通信したい。noble というライブラリで BLE のデバイスに node 側がセントラルとして通信できる。
noble は BTスタックとしては、Mac なら XPC で com.apple.blued と通信し(XPC 通信の node のライブラリも同じ作者作、すごい)、Linux なら HCI で通信し、それらを抽象化してあつかってるので、Mac でも Linux でも、同一コードで取り扱える。
セントラルとして通信
BLE で node 側(PC)がセントラルとして通信するには、ざっくりこんな手段になる。
- アドバタイズメントパケットをスキャンする
- アドバタイズメントパケットの中から、目的のペリファラルとなるデバイスを発見する
- ペリファラルと接続する
- 提供しているサービスやキャラクタリティクスを調べる
- それらに対して書き込みや読み込み、通知(notify)を受け取る
これらを noble で一つ一つ行う(ある程度は抽象化されてる)必要があるが、noble-device を使うと良い感じに書くことが出来る。
Heart Rate Measurement サービスのライブラリを実装する
試しに HRM のライブラリを実装してみる。
別に GATT のベールプロファイルであろうが、自前の独自プロファイルであろうが実装できるが、今回は HRM で。なおなぜ HRM かというと、mbed のペリファラル BLE 実装のサンプルコードが HRM だったから…。
定義は以下の通りで、シンプル。
0x180D のサービスと、キャラクタリティクス各種、measurement の 0x2A37 (notify) と体の位置情報の Body Sensor Location 0x2A38 (read)、Heart Rate Control Point (write) に対応すれば良い。
var HEART_RATE_MEASUREMENT_SERVICE_UUID = '180d';
var MEASUREMENT_UUID = '2a37';
var BODY_SENSOR_LOCATION_UUID = '2a38';
var CONTROL_POINT_UUID = '2a39';
function HeartRateMeasumentService() {
}
HeartRateMeasumentService.prototype.readBodySensorLocation = function(callback) {
this.readUInt8Characteristic(HEART_RATE_MEASUREMENT_SERVICE_UUID, BODY_SENSOR_LOCATION_UUID, callback);
};
HeartRateMeasumentService.prototype.writeControllPoint = function(data, callback) {
this.writeUInt8Characteristic(HEART_RATE_MEASUREMENT_SERVICE_UUID, CONTROL_POINT_UUID, data, callback);
};
HeartRateMeasumentService.prototype.notifyMeasument = function(callback) {
this.onMeasumentChangeBinded = this.onMeasumentChange.bind(this);
this.notifyCharacteristic(HEART_RATE_MEASUREMENT_SERVICE_UUID, MEASUREMENT_UUID, true, this.onMeasumentChangeBinded, callback);
};
HeartRateMeasumentService.prototype.unnotifyMeasument = function(callback) {
this.notifyCharacteristic(HEART_RATE_MEASUREMENT_SERVICE_UUID, MEASUREMENT_UUID, false, this.onMeasumentChangeBinded, callback);
};
HeartRateMeasumentService.prototype.onMeasumentChange = function(data) {
this.convertMeasument(data, function(counter) {
this.emit('measumentChange', counter);
}.bind(this));
};
HeartRateMeasumentService.prototype.readMeasument = function(callback) {
this.readDataCharacteristic(HEART_RATE_MEASUREMENT_SERVICE_UUID, MEASUREMENT_UUID, function(error, data) {
if (error) {
return callback(error);
}
this.convertMeasument(data, function(counter) {
callback(null, counter);
});
}.bind(this));
};
HeartRateMeasumentService.prototype.convertMeasument = function(data, callback) {
var flags = data.readUInt8(0);
if (flags & 0b00000001) {
// uint16
callback(data.readUInt16LE(1));
} else {
// uint8
callback(data.readUInt8(1));
}
};
notify の対応がちょっと面倒だけど、他はさくっと。ライブラリとしての実装はここまでで、続いてこのライブラリを inherits と mixin でつなぎこんで、noble-device のデバイスに機能追加を行う。
var async = require('async');
var NobleDevice = require('noble-device');
var HRM = function(device) {
NobleDevice.call(this, device);
};
NobleDevice.Util.inherits(HRM, NobleDevice);
// 標準的な DeviceInformationService も mixin する
NobleDevice.Util.mixin(HRM, NobleDevice.DeviceInformationService);
NobleDevice.Util.mixin(HRM, HeartRateMeasumentService);
また、アドバタイズからどのペリファラルと接続するかを is 関数で実装する。今回は localName が HRM-DEVICE なら接続される。
HRM.is = function(device) {
var localName = device.advertisement.localName;
return localName === 'HRM-DEVICE';
};
あとは適当に処理を。
HRM.discover(function(device) {
console.log('discovered: ' + device);
device.on('disconnect', function() {
console.log('disconnected!');
process.exit(0);
});
device.on('measumentChange', function(data) {
console.log("update measument: " + data);
});
device.connectAndSetUp(function(callback) {
console.log('connectAndSetUp');
device.notifyMeasument(function(counter) {
console.log('notifyMeasument');
});
});
});
これを実行し、HRM のサービスを提供してる BLE のペリファラルデバイスと接続できたら以下のような表示になる。
$ node hrm.js
discovered: {"uuid":...}
connectAndSetUp
notifyMeasument
update measument: 103
update measument: 104
update measument: 105
update measument: 106
update measument: 107
update measument: 108
update measument: 109
// BLE 機器の電源を落とした
disconnected!
(追記:HRM Service自体本家にマージされました)
なお、noble-device の参考コードとしては、様々なセンサーやボタンが搭載されている TI の CC2650 などの BLE のセンサータグを noble-device を用いて実装してるコードが大変参考になった。