前回、gatttoolを使ってMicrobitの各種サービス情報を取得し、Notifyイベントを使って、Temperatureサービスの温度情報を取得した。今回は、プログラムで同様の事を行ってみる。本当はBluezライブラリを使ってC/C++プログラミングとも思ったが、ちょっと敷居が高いので、まずは、nobleを使って手っ取り早く実装してみる。最終的にはMicrobitとdualshockコントローラーの間に位置してデバイス間でデータのやりとりをする必要があるので、Node.jsとnobleの構成は愛称が良いので、これでいいのかもしれない。
> npm install noble
まずは、Microbitのキャラクタリスティックスをサービスごとに一覧をダンプしてみる。
// test01.js
var noble = require('noble');
var FOUND_MICROBIT_ADDR1 = 'db:88:10:28:d8:61';
noble.on('stateChange', function(state) {
console.log('[1.stateChange] ' + state);
if (state === 'poweredOn') {
noble.startScanning();
} else {
noble.stopScanning();
}
});
noble.on('scanStart', function() {
console.log('[2.scanStart]');
});
noble.on('scanStop', function() {
console.log('[scanStop]');
});
noble.on('discover', function(peripheral) {
console.log('[3.discover]\n' + peripheral);
if( peripheral.address == FOUND_MICROBIT_ADDR1){
peripheral.on('connect', function() {
console.log('[4.connect]');
this.discoverServices();
});
peripheral.on('disconnect', function() {
console.log('[disconnect]');
});
peripheral.on('servicesDiscover', function(services) {
console.log('[5.servicesDiscover]');
for(i = 0; i < services.length; i++) {
services[i].on('includedServicesDiscover', function(includedServiceUuids) {
//console.log('[6.includedServicesDiscover]');
this.discoverCharacteristics();
});
services[i].on('characteristicsDiscover', function(characteristics) {
console.log('[7.characteristicsDiscover]');
for(j = 0; j < characteristics.length; j++) {
console.log("------------");
console.log(" serviceid:"+characteristics[j]._serviceUuid);
console.log(" uuid:"+characteristics[j].uuid);
console.log(" name:"+characteristics[j].name);
console.log(" type:"+characteristics[j].type);
console.log(" properties:"+characteristics[j].properties);
}
});
services[i].discoverIncludedServices();
}
});
peripheral.connect();
}
noble.stopScanning();
});
特筆すべき点もなく簡単な実装で済んでしまう。このプログラムを実行すると、以下のようなダンプ結果になる。gatttoolと比較すると、UUIDにハイフンがなく小文字で統一されている。
> sudo node test01.js
[1.stateChange] poweredOn
[2.scanStart]
[3.discover]
{"id":"db881028d861","address":"db:88:10:28:d8:61","addressType":"random","connectable":true,"advertisement":{"localName":"BBC micro:bit [vutaz]","serviceData":[],"serviceUuids":[],"solicitationServiceUuids":[],"serviceSolicitationUuids":[]},"rssi":-62,"state":"disconnected"}
[scanStop]
[4.connect]
[5.servicesDiscover]
[7.characteristicsDiscover]
------------
serviceid:1800 // Generic Access
uuid:2a00
name:Device Name
type:org.bluetooth.characteristic.gap.device_name
properties:read,write
------------
serviceid:1800
uuid:2a01
name:Appearance
type:org.bluetooth.characteristic.gap.appearance
properties:read
------------
serviceid:1800
uuid:2a04
name:Peripheral Preferred Connection Parameters
type:org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters
properties:read
[7.characteristicsDiscover]
------------
serviceid:e95d93b0251d470aa062fa1922dfa9a8 // DFU
uuid:e95d93b1251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write
[7.characteristicsDiscover]
------------
serviceid:180a // Device Information
uuid:2a24
name:Model Number String
type:org.bluetooth.characteristic.model_number_string
properties:read
------------
serviceid:180a
uuid:2a25
name:Serial Number String
type:org.bluetooth.characteristic.serial_number_string
properties:read
------------
serviceid:180a
uuid:2a26
name:Firmware Revision String
type:org.bluetooth.characteristic.firmware_revision_string
properties:read
[7.characteristicsDiscover]
[7.characteristicsDiscover]
------------
serviceid:1801 // Generic Attribute
uuid:2a05
name:Service Changed
type:org.bluetooth.characteristic.gatt.service_changed
properties:indicate
[7.characteristicsDiscover]
------------
serviceid:e95d0753251d470aa062fa1922dfa9a8 // Accelerometer
uuid:e95dca4b251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,notify
------------
serviceid:e95d0753251d470aa062fa1922dfa9a8
uuid:e95dfb24251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write
[7.characteristicsDiscover]
------------
serviceid:e95d6100251d470aa062fa1922dfa9a8 // Temperature
uuid:e95d9250251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,notify
------------
serviceid:e95d6100251d470aa062fa1922dfa9a8
uuid:e95d1b25251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write
[7.characteristicsDiscover]
------------
serviceid:e95df2d8251d470aa062fa1922dfa9a8 // Magnetometer
uuid:e95dfb11251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,notify
------------
serviceid:e95df2d8251d470aa062fa1922dfa9a8
uuid:e95d9715251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,notify
------------
serviceid:e95df2d8251d470aa062fa1922dfa9a8
uuid:e95d386c251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write
[7.characteristicsDiscover]
------------
serviceid:e95d9882251d470aa062fa1922dfa9a8 // Button
uuid:e95dda90251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,notify
------------
serviceid:e95d9882251d470aa062fa1922dfa9a8
uuid:e95dda91251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,notify
[7.characteristicsDiscover]
------------
serviceid:e95dd91d251d470aa062fa1922dfa9a8 // LED
uuid:e95d7b77251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write
------------
serviceid:e95dd91d251d470aa062fa1922dfa9a8
uuid:e95d93ee251d470aa062fa1922dfa9a8
name:null
type:null
properties:write
------------
serviceid:e95dd91d251d470aa062fa1922dfa9a8
uuid:e95d0d2d251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write
[7.characteristicsDiscover]
------------
serviceid:e95d93af251d470aa062fa1922dfa9a8 // Event Service
uuid:e95d9775251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,notify
------------
serviceid:e95d93af251d470aa062fa1922dfa9a8
uuid:e95d5404251d470aa062fa1922dfa9a8
name:null
type:null
properties:write
------------
serviceid:e95d93af251d470aa062fa1922dfa9a8
uuid:e95d23c4251d470aa062fa1922dfa9a8
name:null
type:null
properties:write
------------
serviceid:e95d93af251d470aa062fa1922dfa9a8
uuid:e95db84c251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,notify
[7.characteristicsDiscover]
------------
serviceid:e95d127b251d470aa062fa1922dfa9a8 // IO Pin
uuid:e95d5899251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write
------------
serviceid:e95d127b251d470aa062fa1922dfa9a8
uuid:e95db9fe251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write
------------
serviceid:e95d127b251d470aa062fa1922dfa9a8
uuid:e95dd822251d470aa062fa1922dfa9a8
name:null
type:null
properties:write
------------
serviceid:e95d127b251d470aa062fa1922dfa9a8
uuid:e95d8d00251d470aa062fa1922dfa9a8
name:null
type:null
properties:read,write,notify
次に属性にアクセスしてみる。キャラクタリスティックスのオブジェクトにはread
やwrite
といったような属性にアクセスするAPIがある。
// reading data.
characteristic.read(function(error, data){
console.log(data); // <Buffer 30> etc
});
// writing data.
// true : without response, false: need response
characteristic.write(data, false, function(error){
console.log(error); // null
});
// start notification
characteristic.subscribe(function(error){
console.log(error); // null
});
// stop notification
characteristic.unsubscribe(function(error){
console.log(error); // null
});
Temperatureサービスからは温度、Magnetometerサービスからは向きN極からの角度を取得する。
注意点としては、discoverSomeServicesAndCharacteristics
にサービスとキャラクタリスティクスのUUID配列を渡す必要があるが、複数のサービスの属性を取得する場合でも、引数の配列に該当するサービスとキャラクタを追加して一度に実行する必要がある。分けて実行すると、前回コールしたものが取り消されてしまうので注意する。
// test02.js
var noble = require('noble');
var FOUND_MICROBIT_ADDR1 = 'db:88:10:28:d8:61';
// Temperature + Magnetometer
// characteristicsが混在する場合、それぞれのservicesも配列に追加されていないといけない
var service_uuids = ["e95d6100251d470aa062fa1922dfa9a8", "e95df2d8251d470aa062fa1922dfa9a8"];
var character_uuids = ["e95d9250251d470aa062fa1922dfa9a8", "e95dfb11251d470aa062fa1922dfa9a8", "e95d9715251d470aa062fa1922dfa9a8"];
var subscribeCharacteristics = function( peripheral, servs, chars, callback ){
peripheral.discoverSomeServicesAndCharacteristics(
servs, chars, function(error, services, characteristics){
console.log("services = "+services.length);
console.log("characteristics = "+characteristics.length);
for(var ic = 0; ic < characteristics.length; ic++){
characteristics[ic].on('data', function(data, isNotification){
callback( null, this._serviceUuid, this.uuid, data );
});
characteristics[ic].subscribe(function(error){
callback( error, "", "", null );
});
}
});
}
noble.on('stateChange', function(state) {
console.log('[1.stateChange] ' + state);
if (state === 'poweredOn') {
noble.startScanning();
} else {
noble.stopScanning();
}
});
noble.on('scanStart', function() {
console.log('[2.scanStart]');
});
noble.on('scanStop', function() {
console.log('[scanStop]');
});
noble.on('discover', function(peripheral) {
if( peripheral.address == FOUND_MICROBIT_ADDR1){
peripheral.on('connect', function() {
subscribeCharacteristics(this, service_uuids, character_uuids,
function( error, serviceid, characterid, data ){
console.log("error:"+error);
console.log("serviceid:"+serviceid);
console.log("characterid:"+characterid);
console.log(data);
});
});
peripheral.on('disconnect', function() {
});
peripheral.connect();
}
noble.stopScanning();
});
characteristics.subscribe()
を実行すると、指定した属性の通知がオンになり、各属性の通知周期ごとにcharacteristics.on("data", function())
がコールされる。複数の属性に対してオンすると、混在した状態で"data"イベントが発火することになる。以下の例だと、Temperatureの通知が1回(一番最後のログ)に対して、Magnetometerの通知が何度も実行されていることが分かる。
> sudo node test02.js
:
serviceid:e95df2d8251d470aa062fa1922dfa9a8
characterid:e95dfb11251d470aa062fa1922dfa9a8
<Buffer 97 6f 23 e4 fb a6>
error:null
serviceid:e95df2d8251d470aa062fa1922dfa9a8
characterid:e95d9715251d470aa062fa1922dfa9a8
<Buffer fc 00>
error:null
serviceid:e95df2d8251d470aa062fa1922dfa9a8
characterid:e95dfb11251d470aa062fa1922dfa9a8
<Buffer 8b 71 eb e4 b7 a9>
error:null
serviceid:e95df2d8251d470aa062fa1922dfa9a8
characterid:e95d9715251d470aa062fa1922dfa9a8
<Buffer fd 00>
error:null
serviceid:e95d6100251d470aa062fa1922dfa9a8
characterid:e95d9250251d470aa062fa1922dfa9a8
<Buffer 22>
: