noble-device を使って node から扱える BLE のライブラリを実装する


node.js から BLE のデバイスと通信したい。noble というライブラリで BLE のデバイスに node 側がセントラルとして通信できる。

- https://github.com/sandeepmistry/noble

noble は BTスタックとしては、Mac なら XPC で com.apple.blued と通信し([XPC 通信の node のライブラリ](https://github.com/sandeepmistry/node-xpc-connection)も同じ作者作、すごい)、Linux なら HCI で通信し、それらを抽象化してあつかってるので、Mac でも Linux でも、同一コードで取り扱える。

# セントラルとして通信

BLE で node 側(PC)がセントラルとして通信するには、ざっくりこんな手段になる。

1. アドバタイズメントパケットをスキャンする
2. アドバタイズメントパケットの中から、目的のペリファラルとなるデバイスを発見する
3. ペリファラルと接続する
4. 提供しているサービスやキャラクタリティクスを調べる
5. それらに対して書き込みや読み込み、通知(notify)を受け取る

これらを noble で一つ一つ行う(ある程度は抽象化されてる)必要があるが、noble-device を使うと良い感じに書くことが出来る。

- https://github.com/sandeepmistry/noble-device

# Heart Rate Measurement サービスのライブラリを実装する

試しに HRM のライブラリを実装してみる。

別に GATT のベールプロファイルであろうが、自前の独自プロファイルであろうが実装できるが、今回は HRM で。なおなぜ HRM かというと、mbed のペリファラル BLE 実装のサンプルコードが HRM だったから…。

定義は以下の通りで、シンプル。

- https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.heart_rate.xml

0x180D のサービスと、キャラクタリティクス各種、measurement の 0x2A37 (notify) と体の位置情報の Body Sensor Location 0x2A38 (read)、Heart Rate Control Point (write) に対応すれば良い。

```javascript
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自体本家にマージされました)

- https://github.com/sandeepmistry/noble-device/blob/master/lib/heart-rate-measument-service.js

なお、noble-device の参考コードとしては、様々なセンサーやボタンが搭載されている TI の CC2650 などの BLE のセンサータグを noble-device を用いて実装してるコードが大変参考になった。

- https://github.com/sandeepmistry/node-sensortag