node.jsでアルプスIoT Smart Moduleのアクセス

Pythonを使ってアルプスのセンサネットワークモジュールでモーションデータの取得」で、python言語と使ってアルプスIoT Smart Moduleをアクセスしましたが、今回は、Raspberry Pi 3にNode.jsをインストールし、Node.jsのBLEモジュール「noble」を用いてアルプスIoT Smart Moduleの地磁気センサと加速度センサからモーションデータを取得します。
nobleモジュールは、Node.jsで動作するBLEモジュールで、次のようなBLEモジュールが提供されています。

  • noble … Node.jsのBLEセントラル実装です。BLE機器を利用するもので、周辺のBLE機器をスキャンし、任意のGATTあるいはサービスに接続を行います
  • bleno … Node.jsのBLEペリフェラル実装です。BLE機器そのもので定期的にアドバタイズを行う。BLEのペリフェラルデバイスを持っていなくてもNode.jsのモジュールblenoを利用することでアドバタイズを発信することができます。

nobleモジュールの詳細な仕様については、「sandeepmistry/noble」を参照してください。

アルプスIoT Smart Moduleへアクセスするための実行環境の作成

nodejsは、「Raspberry Pi 3にNode.jsのインストール」を参考にインストールしてください。

blenoが利用するBluetoothのプロトコルスタックのBlueZのインストールは次のコマンドで行ってください。

$ sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev

blenoはnpmを利用してを次のようにインストールしてください。

$ npm install bleno

確認用のプログラム「nobletest.js」を次に示します。stateがpoweredOnのときにstartScanning()を呼んでいます。 アドバタイズを受信するとnoble.on()のdiscoverに来ますので、受信したペリフェラルの内容、 ここではlocalNameとサービスのUUIDを表示しています。

var noble = require('noble');

noble.on('stateChange', function(state) {
  if (state === 'poweredOn') {
    noble.startScanning();
  } else {
    noble.stopScanning();
  }
});

noble.on('discover', function(peripheral) {
    console.log('Found device with local name: ' + peripheral.advertisement.localName);
    peripheral.connect(function(error) {
         console.log('connected to peripheral: ' + peripheral.uuid);
    });

});

次のコマンドで実行すると、アルプスIoT Smart Moduleのローカル名「SNM00」、UUID「28a183e15896」が取得できます。

$ sudo node nobletest.js
Found device with local name: SNM00
connected to peripheral: 28a183e15896

エラーメッセージ「Error: Module version mismatch. Expected 48, got 57」の処置方法

次のように作成したプログラムを実行すると、モジュールのバーションが不一致であるというエラーメッセージが表示される場合があります。

$ sudo node nobletest.js
module.js:597
  return process.dlopen(module, path._makeLong(filename));
                 ^

Error: Module version mismatch. Expected 48, got 57.
    at Error (native)
    at Object.Module._extensions..node (module.js:597:18)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object. (/home/pi/node_modules/bluetooth-hci-socket/lib/native.js:3:15)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10

この場合、npmが原因のようで、次のようにすでに作成されているディレクトリ「node_modules」を削除して、npmを再インストールします。

$ sudo rm -r node_modules
$ npm install

nodeとnpmのバージョンを次に示します。

$ node -v
v8.4.0
$ npm –version
5.4.2

アルプスIoT Smart Moduleからモーションデータの取得

モーションデータ取得プログラム「writenoble.js」を次に示します。アルプスIoT Smart Moduleのサービスやcharacteristicsを確認するためにダンプルーチンも追加しています。65行目から67行目で取得したcharacteristicsをそれぞれCustom1、Custom2、Custom3に登録しています。69行目から76行目まででアルプスIoT Smart Moduleの設定を行っています。モーションデータはnotifyにより通視されるので、79行目でモーションデータを受け取っています。Custom1、Custom2、Custom3については、「Sensor Network Module評価キットApplication Note Command Guide」のAppendix1 Service1 Databaseを参照してください。

var noble = require('noble');

console.log('noble');

function bytes2str(data){
    var str = "";
    for (var i = 0; i < data.length; i++){
        str += ('00'  + (data[i].toString(16))).slice( -2 );
    }
    return str ; 
}

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');
});
 
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) {
     	console.log('on -> Service: ' + services);

        //各サービスを登録
        var GenericAccess = services[0];
        var GenericAttribute = services[1];
        var PRIMARY_SERVICE = services[2];
 
        GenericAccess.on('characteristicsDiscover', function(characteristics) {
    		console.log('on -> GenericAccess:characteristicsDiscover: ' + characteristics);
        });
 
        GenericAttribute.on('characteristicsDiscover', function(characteristics) {
    		console.log('on -> GenericAttribute:characteristicsDiscover: ' + characteristics);
        });
        
        PRIMARY_SERVICE.on('characteristicsDiscover', function(characteristics) {
     		console.log('on -> PRIMARY_SERVICE:characteristicsDiscover: ' + characteristics);
     		
	        //各characteristicsを登録
	        var Custom1 = characteristics[0];
	        var Custom2 = characteristics[1];
	        var Custom3 = characteristics[2];

            Custom1.write(new Buffer([0x01, 0x00]), true);
            Custom2.write(new Buffer([0x01, 0x00]), true);
            Custom3.write(new Buffer([0x2F, 0x03, 0x03]), true);
            Custom3.write(new Buffer([0x01, 0x03, 0x03]), true);
            Custom3.write(new Buffer([0x04, 0x03, 0x01]), true);
            Custom3.write(new Buffer([0x06, 0x04, 0x64, 0x00]), true);        
            Custom3.write(new Buffer([0x2F, 0x03, 0x01]), true);
            Custom3.write(new Buffer([0x20, 0x03, 0x01]), true); 
            
            Custom1.notify(true);
            Custom1.on('data', function(data, isNotification) {
                console.log('Custom1 = ' +  bytes2str(data)); 
            });
            Custom2.notify(true);
            Custom2.on('data', function(data, isNotification) {
                console.log('Custom2 = ' + data[0].toString(16));
            });
        });
 
        //各サービスのcharacteristicをdiscover実施
        GenericAccess.discoverCharacteristics();
        GenericAttribute.discoverCharacteristics();
        PRIMARY_SERVICE.discoverCharacteristics();
    });
 
    peripheral.connect();
});

実行した結果を次に示します。

$ sudo node writenoble.js
noble
on -> stateChange: poweredOn
on -> scanStart
on -> discover: {"id":"28a183e15896","address":"28:a1:83:e1:58:96","addressType":"public","connectable":true,"advertisement":{"localName":"SNM00","serviceData":[],"serviceUuids":[],"solicitationServiceUuids":[],"serviceSolicitationUuids":[]},"rssi":-72,"state":"disconnected"}
on -> scanStop
on -> connect
on -> Service: {"uuid":"1800","name":"Generic Access","type":"org.bluetooth.service.generic_access","includedServiceUuids":null},{"uuid":"1801","name":"Generic Attribute","type":"org.bluetooth.service.generic_attribute","includedServiceUuids":null},{"uuid":"47fe55d8447f43ef9ad9fe6325e17c47","name":null,"type":null,"includedServiceUuids":null}
on -> GenericAccess:characteristicsDiscover: {"uuid":"2a00","name":"Device Name","type":"org.bluetooth.characteristic.gap.device_name","properties":["read"]},{"uuid":"2a01","name":"Appearance","type":"org.bluetooth.characteristic.gap.appearance","properties":["read"]},{"uuid":"2a02","name":"Peripheral Privacy Flag","type":"org.bluetooth.characteristic.gap.peripheral_privacy_flag","properties":["read","write"]},{"uuid":"2a04","name":"Peripheral Preferred Connection Parameters","type":"org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters","properties":["read"]}
on -> GenericAttribute:characteristicsDiscover: {"uuid":"2a05","name":"Service Changed","type":"org.bluetooth.characteristic.gatt.service_changed","properties":["read","indicate"]}
on -> PRIMARY_SERVICE:characteristicsDiscover: {"uuid":"686a9a3b4c2c4231b8719cfe92cc6b1e","name":null,"type":null,"properties":["read","notify"]},{"uuid":"078ff5d63c9347f5a30c05563b8d831e","name":null,"type":null,"properties":["read","notify"]},{"uuid":"b962bdd15a77479793a1ede8d0ff74bd","name":null,"type":null,"properties":["writeWithoutResponse","write"]}
Custom1 = e01400000000c6830a0101000000000000000000
Custom1 = e01400000000c6830a0101000000000000000000
Custom1 = f214a60052ff7c0049f0e5ff6bfe6400310d0000
Custom1 = f214a9004dff7b004cf0e5ff11fec800310d0001
Custom1 = f214a6004cff7b0049f0f1ff20fe2c01310d0002
Custom1 = f214a90051ff800055f0e8ff1afe9001310d0003
Custom1 = f214aa004fff7c0049f0e2ff38fef401310d0004
Custom1 = f214a7004bff7b004ff0e5ff20fe5802310d0005
Custom1 = f214a70050ff7f004cf0e2ff38febc02310d0006
Custom1 = f214a9004fff7d004ff0e5ff23fe2003310d0007
Custom1 = f214a40052ff7c004cf0e5ff23fe8403310d0008
アルプスIoT Smart Module
address=28:a1:83:e1:58:96 localName=SNM00
uuid name uuid name properties
1800 Generic Access 2a00 Device Name read
2a01 Appearance read
2a02 Peripheral Privacy Flag read write
2a04 Peripheral Preferred Connection Parameters read
1801 Generic Attribute 2a05 Service Changed read indicate
47fe55d8447f43ef9ad9fe6325e17c47 686a9a3b4c2c4231b8719cfe92cc6b1e read notify
078ff5d63c9347f5a30c05563b8d831e read notify
b962bdd15a77479793a1ede8d0ff74bd writeWithoutResponse write