Node-REDを使ってBLEセンサーの値をとってみた
はじめに
仕事の中で、Node-REDを使ってBLEでセンサーデータを取る必要がありました。
node.js未経験・Node-RED未経験の状態だったんですが、頑張ってやってみたので、やったことをまとめておきたいと思います。
node初心者なのでいろいろとツッコミどころがあるかと思うので、バシバシ突っ込んでいただけると嬉しいです。
環境については、Node-REDが標準で入ってるIoTゲートウェイにて開発しました。
開発の準備
BLE取得ライブラリ「noble」
node.jsで周辺のBLEデバイスを探したりペアリングしたりもろもろできるライブラリ「noble」を使って開発しました。
https://github.com/noble/noble
まずは、Node-REDがあるディレクトリに移動してグローバルインストール。
npm install -g noble
ライブラリはこれだけ。
Nodeをつくる
今回のプロジェクトでは、自分の他にコードが書ける人がいなかったため、デバイスのMACアドレスなどを誰でも設定できるようにする必要がありました。
そこで、GUI上で設定変更可能なNodeを作成しました。
(コードを勝手に編集される心配も減ると思います。)
Node-REDのrootディレクトリ配下に以下のディレクトリがあると思います。
/nodes/core/
こちらに、設定用のHTMLファイルとプログラムを実行するjsファイルを作成します。
今回作った設定ファイルの雛形は以下のとおりです。
(業務用のソースから一部変更しているので、おかしな部分があったらごめんなさい)
<script type="text/javascript">
RED.nodes.registerType('ble',{
category: 'ble',
color: '#A5DF00',
defaults: {
filter: {value:""},
serviceuuid: {value:""},
charactaristicsuuid: {value:""},
GWName: {value:""},
appKey: {value:""}
},
inputs:1,
outputs:1,
icon: "dataparsefix.png",
label: function() {
return this.name||"ble";
}
});
</script>
<script type="text/x-red" data-template-name="ble">
<div class="form-row">
*MACアドレス<br>
<label for="node-input-macaddress"> MACアドレスを入力してください</label>
<input type="text" id="node-input-macaddress" placeholder="aabbcc112233" style="width: 300px" maxlength='32'><br><br>
*Service UUID<br>
<label for="node-input-serviceuuid"> Service UUIDを入力してください</label>
<input type="text" id="node-input-serviceuuid" placeholder="UUID" style="width: 300px" maxlength='32'><br><br>
*Characteristics UUID<br>
<label for="node-input-charactaristicsuuid">Characteristics UUIDを入力してください</label>
<input type="text" id="node-input-charactaristicsuuid" placeholder="UUID" style="width: 300px" maxlength='32'><br><br>
*送信するAPIのURLとKEY<br>
<label for="node-input-url">URLを入力してください</label>
<input type="text" id="node-input-url" placeholder="https://xxxxx.xxxxx" style="width: 300px" maxlength='32'><br><br>
<label for="node-input-key">KEYを入力してください</label>
<input type="text" id="node-input-key" placeholder="XXXX-XXXX-XXXX-XXXX-XXXX" style="width: 300px" maxlength='60'><br><br>
</div>
</script>
<script type="text/x-red" data-help-name="ble">
<p>BLEデータを取得します</p><br><br>
</script>
module.exports = function(RED) {
var noble = require('noble');
var gwname = '';
var appkey = '';
function ble(config) {
RED.nodes.createNode(this,config);
this.macaddress = config.macaddress;
this.serviceuuid = config.serviceuuid;
this.charactaristicsuuid = config.charactaristicsuuid;
url = config.url;
key = config.key;
(設定項目には関係ないため省略)
}
(設定項目には関係ないため省略)
RED.nodes.registerType("ble",ble);
}
以上で下準備は完了です。
BLE関連処理
ble.jsで実施したBLE関連の処理を順に書いていきます。
各処理を行う際
以下の中で実行します。
this.on('input', function(msg) {
(各処理を実装)
});
状態管理(接続状態が変わったときに実行される)
スキャン履歴を削除しないと再度同じMACアドレスのデバイスに接続できないので注意
noble.on('stateChange', function(state) {
console.log('stateChange Function : ' + state);
if (state === 'poweredOn') { // stateが'poweredOn'になった時
noble.startScanning(); // BLEのスキャンを開始
} else { // stateが'poweredOn'でなくなった時
noble.stopScanning(); // BLEのスキャンを終了
noble.removeAllListeners(); // スキャン履歴を削除
}
});
スキャン開始・終了
noble.on('scanStart', function() {
console.log('on -> scanStart');
node.status({fill:"green",shape:"dot",text:"started"}); // Node-REDの上にステータス表示をするための記述
});
noble.on('scanStop', function() {
console.log('on -> scanStop');
node.status({fill:"red",shape:"ring",text:"stopped"}); // Node-REDの上にステータス表示をするための記述
});
読み取り
noble.on('discover', function(peripheral) {
if (peripheral.uuid == node.macaddress) { // MACアドレスを確認
noble.stopScanning();
peripheral.connect(function(error) {
peripheral.discoverServices(null, function(error, services) {
var service = services[2];
service.discoverCharacteristics([node.charactaristicsuuid],function(error,characteristics) {
var charData = characteristics[0];
charData.on('data', function(data, isNotification) {
msg.serviceUuid = charData._serviceUuid;
msg.charUuid = charData.uuid;
msg.payload = data.readUInt8(0);
msg.type = 'data';
node.send(msg);
});
charData.subscribe(function(error) {
console.log('notification on');
});
});
var service2 = services[3];
service2.discoverCharacteristics(['2a19'],function(error,characteristics) { // 電池残量のuuid 固定のためソース上に記載
var charData2 = characteristics[0];
charData2.on('data', function(data, isNotification) {
msg.serviceUuid = charData2._serviceUuid;
msg.charUuid = charData2.uuid;
msg.payload = data.readUInt8(0);
msg.type = 'battery';
sendVolt(data.readUInt8(0));
node.send(msg);
});
charData2.subscribe(function(error) {
console.log('notification on');
});
});
});
});
}
peripheral.on('disconnect', function() {
noble.startScanning();
)};
)};
sarvicesの2と3だけほしかったのでこういう書き方をしましたが、本当はもっとイケてる実装があったと思います。。。
内容的にはセンサーデータとバッテリー情報を取得してmsgのpayloadに詰めて送る。という流れです。
正直あってるのか不安になりながらの実装で、「本当に動いてるのかこれ?」って思いながらやってました。
詳しい人いたら教えてー
あとはAPIに送るなりなんなりという感じですので省略します。
まとめ
- BLEが目に見えないので初取得まですごい不安
- ソース上でdisconnectの処理を書いていたら再取得できずに困ってたらnobleのバグでできないらしく、exitでNode-REDを強制的に落とすというやばめの対応をしてしまった
- logを書きまくるの大事(記事では見づらいので極力減らしてます)
以上です。
今回はOpenBlocksで開発したのですが、ライブラリを入れるのにめちゃめちゃ苦労しました。
多分非公式の方法なので、あえて記載していません。
ツッコミ等お待ちしております。よろしくおねがいします。