ブログ

ορεσικα ψομαναι κυσο βλογ

BMP085とNode-REDでM2Xに気圧データを送る

Node-REDで簡単にI2C接続の気圧センサの取得値をM2Xにアップロードできるだろうとやってみたら、初心者が踏むであろう地雷を余すこと無く踏んでいって手こずったのでメモ。

ハードウェア準備

今回はホストマシンにBeaglebone Blackを、OSに

BeagleBoard.org - latest-images

の Jessie IoT (non-GUI) for BeagleBone via microSD card を選んだ。このイメージは最初からBeaglebone用のNode-REDが動作しているので便利である。BBGでもBBGWでもBBBWでもBBBlueでも同様に動作するはずである。

センサはサインスマート(SainSmart)BMP085 気圧センサーモジュール を使った。選択に積極的な理由は無い。そのへんに転がってたから。

BMP085の配線はI2Cなので

BeagleBoard.org - demo_bmp085

の通りだが、残念ながら新しいOSイメージでは /sys/bus/i2c/drivers/bmp085/ がもはや存在しないのでこのbonescriptデモは動作しない。bme280あたりで動くデモにそろそろ作り直したほうが良いと思う。bonescriptのデモは他にも色々と古くなってて残念な感じである。

bmp085のnode.jsモジュールをインストー

一応、全部をnode.jsで行ってみようと思ったので、読み取りライブラリもnode.jsのを用いた。

www.npmjs.com

の通り、npmでモジュールをインストールする。今回はNode-REDで使いたいので

$ sudo npm -g install bmp085

とグローバルインストールでOK。/usr/local/lib/node_modules/bmp085/にモジュールがインストールされる。

気圧と気温を読み取れるかテストする際には、

var BMP085 = require('bmp085'),
barometer = new BMP085({
  'device': '/dev/i2c-2'
});
barometer.read(function (data) {
  console.log("Temperature:", data.temperature);
  console.log("Pressure:", data.pressure);
});

と、I2Cバスの場所として/dev/i2c-2を明示的に指定しないと失敗する(地雷1)。

M2XのNode-REDモジュールをインストー

www.npmjs.com

$ sudo npm -g install node-red-m2x

でOK。

$ sudo systemctl restart node-red.service

でnode-redを再起動してからNode-REDのwebページを開き、storageカテゴリにM2Xノードが現れていればインストール成功。

f:id:XX-Prime:20170214204619p:plain

M2X準備

M2Xのwebページにログインして、デバイス一つと、その配下にストリーム2つ(気圧用と温度用)を作っておく。

Node-REDフロー構成

一分に一回、気圧と温度データを取得してM2Xにアップロードするフローを構成した。

f:id:XX-Prime:20170214205921p:plain

やることは単純なので直線的でわかりやすいフローである。

測定開始タイマー

injectノードを1分のインターバルでリピート構成した。出力ペイロードは使わないのでデフォルトのタイムスタンプのままにしてある。

f:id:XX-Prime:20170214210659p:plain

気圧測定(BMP085)

問題はここ。functionノードでbmp085モジュールを呼び出して使う部分である。色々あったが動作する状態になったのが以下の図。

f:id:XX-Prime:20170222222823p:plain

topic_idにM2XのDEVICE IDを、sub_topic_idにストリーム名を指定する。

まず、素朴にfunctionノード内でrequireによるbmp085モジュールのロードを試みると

function : (error)
ReferenceError: require is not defined

などと怒られて動かない(地雷2)。

ちょっと調べると、Node-REDで外部モジュールを使う方法が分かった。requireはsettings.jsの中のfunctionGlobalContextに書け、というのが正解である。と言われてもBBB上のnode-redのsettings.jsがどこにあるかがわからんので、/var/logあたりにnode-redの起動ログがあるはずと予想して

root@bbb0001:/var/log# grep -r 'node-red' * | grep 'settings.js'

で探してみたら

daemon.log:Feb 11 11:52:30 bbb0001 Node-RED[2814]: 11 Feb 11:52:30 - [info] Settings file  : /root/.node-red/settings.js

と、あっさり見つかった。このファイルの中を見てみると、

functionGlobalContext: {
// os:require('os'),
// octalbonescript:require('octalbonescript'),
// jfive:require("johnny-five"),
// j5board:require("johnny-five").Board({repl:false})
},

と、外部モジュールのrequire方法がコメントとして例示されている。ありがたい。よって、ここに

bmp085:require("bmp085")

を追加した。functionノード内ではrequireではなく

var BMP085 = global.get('bmp085');

のようにしてグローバルコンテクストの中からロードするとrequireと同様にモジュールを使うことができる。

次に、取得したセンサ値をmsgのペイロードに詰めてreturnで次のノードに送ろうとしたが何も送られなかった(地雷3)。

この問題の原因は、bmp085モジュールのセンサ値取得が非同期で行われるためである。センサの取得値はモジュールのコールバック関数の中でしか触れなく、普通にreturnしてしまうと値の取得が行われない状態でfunctionノードが終了してしまう。node.jsで毎回この手の地雷を踏んでる気がするがいい加減覚えたらどうだ俺。

Node-RED日本ユーザ会 : Functions Nodeの書き方 日本語訳にいくつか誤りがあるので原文を参照したほうが良いかもしれない。いま修正案を作成中。→修正案が受理されてサイトに反映された(2017/02/27)。

の 非同期メッセージ送信 の所に、そのものずばりの答えが書いてあった。node.sendを使えば非同期で呼ばれる関数の中からメッセージを後続のノードに送ることができる。メッセージを複数返せるというのが嬉しい所で、今回は気圧と温度を別のメッセージとして送りたかったので正に2回node.sendを実行している。actionとしてsetStreamValue以外の何かを指定すれば一回で複数ストリームへのputが行えるかもしれないが、もう調べる気力が残ってない。

2017/02/20 追記

functionノード内で、毎回newによるbarometerオブジェクト生成を行っていたが、

Feb 17 09:24:42 bbb0001 Node-RED[11054]: (node) warning: possible EventEmitter memory leak detected. 11 exit listeners added. Use emitter.setMaxListeners() to increase limit.

というエラーがsyslogに出るので、コンテキストを利用して最初の一回だけnewするようにした所、当該エラーが消えた。これも地雷だな(地雷3.5)。Node-REDのノードは実行中に消えずに生き続けるということか。

M2Xへアップロード

M2XノードでAPIキーを指定する。ここは間違いようがない。

f:id:XX-Prime:20170214215216p:plain

Feedの鉛筆ボタンをクリックして

f:id:XX-Prime:20170214215357p:plain

API keyにM2XのデバイスAPI keyを入力してUpdateをクリックする。

確認用出力

このノードは特に要らないが、putされているオブジェクトを見るためにdebugノードを置いている。

出力例

f:id:XX-Prime:20170214215633p:plain

こんな感じで記録されていたら成功。台風の目が近くを通り過ぎると、尖点を持つかなり面白いグラフが得られる。

M2Xは、webサイトに埋め込むためのグラフも自動的に作ってくれるので、自動生成されたimgタグを埋め込むことで以下のように簡単にリアルタイムグラフを表示できる。

二軸までの同時表示が可能。timezoneの指定が出来ないのが残念な所で、UTCのみである。

imgではなくwidgetにはローカル時間表示のオプションがあって機能することがわかった。以下のようにdata-localize-timestamp="true"を指定したwidgetは日本時間(システムの時刻設定を読み込んでるのかな?)で表示される。読み込み中のアニメーションが永久に出るバグはhatenaの仕組みとの不整合によるものだと思われる。普通のwebページに埋め込んだwidgetは問題なく表示される。

ローカル時間のテスト

widgetの仕様はhttps://m2x.att.com/developer/documentation/v2/widgetsに記述がある。

Node-RED自動起動

デフォルトではnode-redサービスは自動起動しない。webブラウザで1880番ポートにアクセスするとxinetd的な何らかの仕組みでnode-redサービスが立ち上がるようだが、自動起動していると勘違いすると痛い目に会う(地雷4)。

$ sudo systemctl enable node-red.service

自動起動するようにしておかないとリブート後にデータアップロードが途切れて号泣する。

まとめ

Node-REDは大変扱いやすいけど、非同期で動作する外部モジュールを使う時にはここで書いたような地雷を踏まないようにしてね。