OpenBlocks IoT BX1でTI製センサータグ(CC2650)のセンサーデータ(10種類)を取得する

2015.09.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、せーのです。今日もOpenBlocks IoT BX1を使って色々あそんでみましょう。

センサータグのデータ種類

さて、今回使うのはこちら。

getDatafromsensorTag1

これはTEXAS INSTRUMENTS社より出ている[Simple Link(CC2650)]というセンサータグです。こちらはマクニカさんからお借りしました。ありがとうございます! こんな小さいのにこのタグから

  • ambientTemperature: 周囲温度(気温)
  • objectTemperature: 物体の温度(IR温度)
  • humidity: 湿度
  • barometricPressure: 気圧
  • accelerometer: 加速度計(9軸)
  • gyroscope: ジャイロスコープ(傾き)
  • magnetometer: 磁力計
  • luxometer: 光量

という様々な環境データを取得することが出来ます。 通信はBluetooth Low Energy(BLE)になります。今回はアドレスを指定してペアリングしますが、コードの書き方次第では複数のタグを自動でペアリングさせることも可能かと思います。今度試してみます。

BX1側の準備

それでは早速やってみましょう。前提としてBX1がインターネットに繋がっている状態とします。そこまでいっていない方は以下の記事を参考にして下さい。

さて、準備としてBX1にインストールするものは以下となります。

  • nvm,node,npm > node 0.11以上
  • curl(入って無ければ) > curl 7.18以上
  • libbluetooth-dev >= 4.101以上
  • jq >= 1.3以上。apt-getにはjqパッケージがないのでsource.listにdebを追加して対応する。

※以下は今回使いませんが次回以降使うので今入れておきます

  • pip, aws-cli >= aws-cli 1.7.19以上
  • aws sdk for JavaScript > 最新版

これらをひたすら続々入れていきます。その前にntpの設定やlocaleの設定も一応やっちゃいます。Web UI画面でこれらを設定している方は飛ばしてしまって構いません。

vi /etc/apt/source.list
 #jqパッケージ用に↓を追記する
 deb http://ftp.jp.debian.org/debian wheezy-backports main contrib non-free
apt-get update
apt-get install -y ntpdate ntp
ntpdate ntp1.jst.mfeed.ad.jp
apt-get install -y locales
sed -i 's/^# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen
dpkg-reconfigure -f noninteractive locales
apt-get install -y curl
curl https://raw.githubusercontent.com/creationix/nvm/v0.24.0/install.sh | bash
source ~/.nvm/nvm.sh
nvm install v0.12.2
nvm alias default v0.12.2
echo -e "\nexport NVM_DIR=\"$NVM_DIR\"\n[ -s \"\$NVM_DIR/nvm.sh\" ] && . \"\$NVM_DIR/nvm.sh\" >> ~/.bashrc
apt-get install -y libbluetooth-dev
apt-get install -y jq
curl -kL https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
pip install awscli

作業ディレクトリの作成

次にセンサーの取得スクリプトを書く作業ディレクトリを作ります。名前は何でも構いません。

mkdir sensortest | cd $_

作業ディレクトリを作ったらセンサーの取得ライブラリをインストールします。 ここで次回以降に使うmoment-timezoneというタイムゾーンの管理ライブラリとAWS SDK for Node.jsも入れておきます。

 npm install sandeepmistry/node-sensortag moment-timezone aws-sdk

ソースを書いていく

それではソースを書いていきます。ファイル名は何でも構いません。大きく分けると「接続部分」と「取得部分」の2つに分かれます。

接続部分

まずは接続部分です。ライブラリにはdiscoverByAddress(アドレスから取得)、discoverById(IDから取得)、discoverByUUID(UUIDから取得)という3種類の接続方法とdiscoverというこれらのラッパー関数が用意されています。今回はこの中からdiscoverByAddressを使って接続してみます。

センサータグのアドレスはどうやって調べたらいいのでしょう。これはBX1のWebUIからか、もしくはコンソールから調べることができます。

まずはBX1のWebUIにログインします。

cc2650tobx1-3

次にBluetoothが有効になっていることを確認し

cc2650tobx1-4

Bluetoothのペアリングの画面からBLE(下の方のボタン)の検索ボタン[detecting]をクリックします。センサータグが見つかると画面の方にセンサータグの情報が出てきます。

cc2650tobx1-1

見つからないという場合、センサータグは一定時間通信を行っていないと低電力モードになるので電源ボタンを押すと表示されるかと思います。

cc2650tobx1-2

コンソールではhcitoolというコマンドを使用します。まずはデバイスを確認します。

hciconfig
hci0:   Type: BR/EDR  Bus: UART
        BD Address: 98:4F:EE:04:A8:72  ACL MTU: 1021:8  SCO MTU: 64:1
        UP RUNNING PSCAN
        RX bytes:42009 acl:0 sco:0 events:1465 errors:0
        TX bytes:1821 acl:0 sco:0 commands:79 errors:0
rfkill list
2: bcm43xx Bluetooth: Bluetooth
        Soft blocked: no
        Hard blocked: no
3: phy1: Wireless LAN
        Soft blocked: no
        Hard blocked: no
4: brcmfmac-wifi: Wireless LAN
        Soft blocked: no
        Hard blocked: no
5: hci0: Bluetooth
        Soft blocked: no
        Hard blocked: no

[hci0]というデバイスが見えていればOKです。見えない場合はEdison側でソフトブロックされている可能性があります。解除コマンドを流します。

bluetooth_rfkill_event &
rfkill unblock bluetooth

BLEデバイスのスキャンは[lescan]というコマンドになります。

hcitool lescan
LE Scan ...
F4:F9:51:E2:E8:78 (unknown)
F4:F9:51:E2:E8:78 (unknown)
B0:B4:48:B9:59:05 (unknown)
B0:B4:48:B9:59:05 CC2650 SensorTag

このアドレスを使用します。

では接続部分のコードを書いていきましょう。こんな感じになります。

var myAddress = process.env["TI_ADDRESS"] || "YOUR_TI_sensor_tag_ADDRESS";

var SensorTag = require('sensortag');
console.info(">> STOP: Ctrl+C or SensorTag power off");
console.info("start");
console.info("waiting for connect from " + myAddress);
SensorTag.discoverByAddress(myAddress, function(sensorTag) {
	console.info("found: connect and setup ... (waiting 5~10 seconds)");
	sensorTag.connectAndSetup(function() {
		sensorTag.readDeviceName(function(error, deviceName) {
			console.info("connect: " + deviceName);
                        //ここにデータ収集の関数を登録する。

		});
	});
	/* In case of SensorTag PowerOff or out of range when fired `onDisconnect` */
	sensorTag.on("disconnect", function() {
		console.info("disconnect and exit");
		process.exit(0);
	});
});

わりとサラッとしたnodeのコードかと思います。これもわかりやすいライブラリのおかげですね。ちなみにこの接続部分のライブラリはこうなってます。

SensorTag.discoverByAddress = function(address, callback) {
  address = address.toLowerCase();

  var onDiscoverByAddress = function(sensorTag) {
    if (sensorTag._peripheral.address === address) {
      SensorTag.stopDiscoverAll(onDiscoverByAddress);

      callback(sensorTag);
    }
  };

  SensorTag.discoverAll(onDiscoverByAddress);
};

discoverAllというタグの検索メソッドにぶん投げつつ、アドレスを小文字化してチェックして、期待したタグのアドレスであればcallback関数(今回はconnectAndSetup())にチェインしていく、という形です。これ以降の関数も基本的にはこの形です。

データ取得部分

次にデータの取得部分を書きます。どのセンサーデータも基本は全て同じで

  • enableメソッドでセンサーデータの使用を宣言
  • notifyメソッドで一定間隔ごとに通知(今回は1秒ごとにしています)
  • 各センサーのChangeイベントでデータ値を取得する

という流れをネストで書いていきます。では書いていきましょう。ジャイロスコープであればこのように書きます。

function ti_gyroscope(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableGyroscope(function() {
		conned_obj.setGyroscopePeriod(period, function() {
			conned_obj.notifyGyroscope(function() {
				console.info("ready: notifyGyroscope");
				console.info("notify period = " + period + "ms");
				conned_obj.on('gyroscopeChange', function(x, y, z) {
					console.log('gyro_x: ' + x, 'gyro_y: ' + y, 'gyro_z: ' + z);
				});
			});
		});
	});
}

温度計であればこのように取ります。

function ti_ir_temperature(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableIrTemperature(function() {
		conned_obj.setIrTemperaturePeriod(period, function() {
			conned_obj.notifyIrTemperature(function() {
				console.info("ready: notifyIrTemperature");
				console.info("notify period = " + period + "ms");
				conned_obj.on('irTemperatureChange', function(objectTemperature, ambientTemperature) {
            console.log('\tobject temperature = %d °C', objectTemperature.toFixed(1));
            console.log('\tambient temperature = %d °C', ambientTemperature.toFixed(1));
				});
			});
		});
	});
}

そしてこれらのファンクションを先ほどの接続部分の接続後に登録しておけばOKです。

var myAddress = process.env["TI_ADDRESS"] || "YOUR_TI_sensor_tag_ADDRESS";

var SensorTag = require('sensortag');
console.info(">> STOP: Ctrl+C or SensorTag power off");
console.info("start");
console.info("waiting for connect from " + myAddress);
SensorTag.discoverByAddress(myAddress, function(sensorTag) {
	console.info("found: connect and setup ... (waiting 5~10 seconds)");
	sensorTag.connectAndSetup(function() {
		sensorTag.readDeviceName(function(error, deviceName) {
			console.info("connect: " + deviceName);
                        //ここにデータ収集の関数を登録する。
                        ti_gyroscope(sensorTag);
                        ti_ir_temperature(sensorTag);
		});
	});
	/* In case of SensorTag PowerOff or out of range when fired `onDisconnect` */
	sensorTag.on("disconnect", function() {
		console.info("disconnect and exit");
		process.exit(0);
	});
});

こんな感じで全種類のセンサーを取る処理がこのようになります。

/*
* $ npm install sandeepmistry/node-sensortag ## (require `libbluetooth-dev`)
* $ TI_UUID=your_ti_sensor_tag_UUID node this_file.js
*/
//var myUUID = process.env["TI_UUID"] || "YOUR_TI_sensor_tag_UUID";
var myAddress = process.env["TI_ADDRESS"] || "YOUR_TI_sensor_tag_ADDRESS";

function ti_simple_key(conned_obj) {
	conned_obj.notifySimpleKey(function() {
		console.info("ready: notifySimpleKey");
		console.info("/* left right (true = pushed, false = released) */");
		conned_obj.on("simpleKeyChange", function(left, right) { /* run per pushed button */
			console.log(left, right);
		});
	});
}

function ti_gyroscope(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableGyroscope(function() {
		conned_obj.setGyroscopePeriod(period, function() {
			conned_obj.notifyGyroscope(function() {
				console.info("ready: notifyGyroscope");
				console.info("notify period = " + period + "ms");
				conned_obj.on('gyroscopeChange', function(x, y, z) {
					console.log('gyro_x: ' + x, 'gyro_y: ' + y, 'gyro_z: ' + z);
				});
			});
		});
	});
}

function ti_ir_temperature(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableIrTemperature(function() {
		conned_obj.setIrTemperaturePeriod(period, function() {
			conned_obj.notifyIrTemperature(function() {
				console.info("ready: notifyIrTemperature");
				console.info("notify period = " + period + "ms");
				conned_obj.on('irTemperatureChange', function(objectTemperature, ambientTemperature) {
            console.log('\tobject temperature = %d °C', objectTemperature.toFixed(1));
            console.log('\tambient temperature = %d °C', ambientTemperature.toFixed(1));
				});
			});
		});
	});
}

function ti_accelerometer(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableAccelerometer(function() {
		conned_obj.setAccelerometerPeriod(period, function() {
			conned_obj.notifyAccelerometer(function() {
				console.info("ready: notifyAccelerometer");
				console.info("notify period = " + period + "ms");
				conned_obj.on('accelerometerChange', function(x, y, z) {
            console.log('\taccel_x = %d G', x.toFixed(1));
            console.log('\taccel_y = %d G', y.toFixed(1));
            console.log('\taccel_z = %d G', z.toFixed(1));
				});
			});
		});
	});
}

function ti_humidity(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableHumidity(function() {
		conned_obj.setHumidityPeriod(period, function() {
			conned_obj.notifyHumidity(function() {
				console.info("ready: notifyHumidity");
				console.info("notify period = " + period + "ms");
				conned_obj.on('humidityChange', function(temperature, humidity) {
            console.log('\ttemperature = %d °C', temperature.toFixed(1));
            console.log('\thumidity = %d %', humidity.toFixed(1));
				});
			});
		});
	});
}

function ti_magnetometer(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableMagnetometer(function() {
		conned_obj.setMagnetometerPeriod(period, function() {
			conned_obj.notifyMagnetometer(function() {
				console.info("ready: notifyMagnetometer");
				console.info("notify period = " + period + "ms");
				conned_obj.on('magnetometerChange', function(x, y, z) {
            console.log('\tmagnet_x = %d μT', x.toFixed(1));
            console.log('\tmagnet_y = %d μT', y.toFixed(1));
            console.log('\tmagnet_z = %d μT', z.toFixed(1));
				});
			});
		});
	});
}

function ti_barometric_pressure(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableBarometricPressure(function() {
		conned_obj.setBarometricPressurePeriod(period, function() {
			conned_obj.notifyBarometricPressure(function() {
				console.info("ready: notifyBarometricPressure");
				console.info("notify period = " + period + "ms");
				conned_obj.on('barometricPressureChange', function(pressure) {
            console.log('\tpressure = %d mBar', pressure.toFixed(1));
				});
			});
		});
	});
}

function ti_luxometer(conned_obj) {
	var period = 1000; // ms
	conned_obj.enableLuxometer(function() {
		conned_obj.setLuxometerPeriod(period, function() {
			conned_obj.notifyLuxometer(function() {
				console.info("ready: notifyLuxometer");
				console.info("notify period = " + period + "ms");
				conned_obj.on('luxometerChange', function(lux) {
        	console.log('\tlux = %d', lux.toFixed(1));
				});
			});
		});
	});
}

var SensorTag = require('sensortag');
console.info(">> STOP: Ctrl+C or SensorTag power off");
console.info("start");
console.info("waiting for connect from " + myAddress);
//SensorTag.discoverByUuid(myUUID, function(sensorTag) {
SensorTag.discoverByAddress(myAddress, function(sensorTag) {
	console.info("found: connect and setup ... (waiting 5~10 seconds)");
	sensorTag.connectAndSetup(function() {
		sensorTag.readDeviceName(function(error, deviceName) {
			console.info("connect: " + deviceName);
			ti_simple_key(sensorTag);
			ti_gyroscope(sensorTag);
      ti_ir_temperature(sensorTag);
      ti_accelerometer(sensorTag);
      ti_humidity(sensorTag);
      ti_magnetometer(sensorTag);
      ti_barometric_pressure(sensorTag);
			ti_luxometer(sensorTag);

		});
	});
	/* In case of SensorTag PowerOff or out of range when fired `onDisconnect` */
	sensorTag.on("disconnect", function() {
		console.info("disconnect and exit");
		process.exit(0);
	});
});

これを先ほどのアドレスを引数にして流してみます。

TI_ADDRESS=B0:B4:48:B9:59:05 node ti_all.js
>> STOP: Ctrl+C or SensorTag power off
start
waiting for connect from B0:B4:48:B9:59:05
found: connect and setup ... (waiting 5~10 seconds)
connect: SensorTag 2.0
ready: notifySimpleKey
/* left right (true = pushed, false = released) */
ready: notifyAccelerometer
notify period = 1000ms
ready: notifyMagnetometer
notify period = 1000ms
ready: notifyGyroscope
notify period = 1000ms
ready: notifyIrTemperature
notify period = 1000ms
ready: notifyHumidity
notify period = 1000ms
ready: notifyBarometricPressure
notify period = 1000ms
	accel_x = 0 G
	accel_y = 0 G
	accel_z = 0 G
gyro_x: 0 gyro_y: 0 gyro_z: 0
	magnet_x = 0 μT
	magnet_y = 0 μT
	magnet_z = 0 μT
ready: notifyLuxometer
notify period = 1000ms
	temperature = 27.9 °C
	humidity = 62.9 %
	object temperature = 21.7 °C
	ambient temperature = 27.8 °C
	pressure = 1009.9 mBar
	lux = 30

これで各種センサーデータを取ることに成功しました!

まとめ

いかがでしたでしょうか。node部分が慣れてない人にはちょっと引っかかるかもしれませんが全体的には簡単なプログラムでデータが取得できたかと思います。 むしろハマるのは環境設定の方でした。私も最初は環境設定6時間、コーディング30分、という悲しい結果になりました。IoTはここが難しい。 みなさんは是非ハマらずに設定ができることを祈っています。次回はこの取得したデータをKinesisに流してみたいと思います。

参考サイト