さくらのIoT PlatformとIBM Bluemix上のnode-REDを接続してみた記録を、半分日記形式で記載しています。
1日目 では、IBM BluemixにNode-REDを導入し、(シミュレータを作って)WebSocketでデータを受信して、Ambientというグラフ表示サービスにデータを連携するところまで進みました。
2日目では、受信したデータを No-SQL DBにとりあえず保存するのと、Webサイトを作るところまで行きました。
2.5日目で、IoT Platformが届いたので、Arduinoを実際に接続してみました。
3日目では、保存したデータを読める形にして、利用率を計算するところまで行きました。
すごく長くて恐縮ですが、適当にかいつまんで眺めていただき、何かの参考になればうれしいです。
目次
- あらまし
- 現状
- 0日目
- IoT Platform の注文
- Arduinoの注文
- 1日目
- node-REDの導入
- パスワードの設定
- Ambientの導入
- 登録
- node-REDへのモジュール追加
- WebSocketのテスト
- 想定データ仕様
- WebSocketで受信したデータをAmbientに送信する
- 一旦global変数に格納
- 定期的にAmbientに送信
- node-REDの導入
あらまし
- 会議室あるある
- なかなか予約が取れない
- 使わないかもしれないけど先に予約しておこう
- 予約したけど使わなかった。でもキャンセル忘れてた
- 使わないのに予約済みの会議室だけ増えていく
- 以下無限ループ
とりあえず、状況の把握だけでもやってみることにしました。
現状
まだ統計情報まではできていません。それは3日目にでもやる予定。
0日目
IoT Platform を買った。
会社内なのに有線無線LANを使わなかったのは、セキュリティの問題があるためです。
完全に社内ネットワークと切り離して、すべてをイントラ側ではなく、インターネット側で実現しようと考えました。
Arduinoを買った。
Arduino UNO互換であれば特になんでもいいとたぶん思います。
純正のUNOを一台持っているので、今回はお試しで安いのを買ってみた。
届くまで少し時間がかかるので、その間にWebサイト側を作ってしまいましょう。
1日目
node-REDの導入
Bluemixは日進月歩で画面が新しくなるので、毎回迷います。
サービスカタログから、 Node-RED Starter を追加します。
ダッシュボードを開き、管理者用のユーザ名、パスワードを環境変数に設定し、保存します。
アプリが再起動するので数分待ちます。
Ambientの導入
Ambientという、データを送るだけで最大8系統のデータを自動で履歴保存し、グラフにしてくれる素晴らしい無料のサービスがあります。これを利用させてもらい、0と1を送信することで、利用時間と空き時間を可視化しようと思います。
Node-REDからAmbientに送信するモジュールのインストール
Ambientにデータを送るには、APIにPOSTすればいいのですが、Node-REDで簡単にその送信ができるモジュールがあるので、それをインストールします。
Wio node + Groveセンサー + Node-RED + Ambientで超簡単IoT を参考にさせていただきました。なお、現在はBluemixのDevOpsサービスは、Continuous Delivery サービスに変更になっており、 ギブハブ GitHubとの連携も簡単にできるようになっています。
Bluemixの Continuous Deliveryサービスの利用
ダッシュボードから、「継続的デリバリー」の有効化をクリックします。
「Create」をおして、GitHubとのアカウントを紐付けると、
GitHubに新しいレポジトリが登録されます。
package.json にAmbientを追加し、Commitすると、自動でBluemixに連携され、Node-REDが再起動します。
WebSocketのテストをする。
他の例をみてみると、さくらIoTとはWebSocketでつなぐのが鉄板のようですので、今回もWebSocketで接続することにします。
しかしながら、まだIoT Platformの発送通知すら来ていないので、先にテストができるよう、テストデータを送信できるようにしてみます。
先ほど設定したユーザ名とパスワードでNode-REDにログインします。
Node-REDの便利な機能の一つに、部品をjson形式で書き出す機能があります。
私が作った稚拙な部品をここに置いておきますので、よろしかったらご利用ください。
[{"id":"9f1852ae.9260a8","type":"websocket out","z":"396465eb.1d8e22","name":"test websocket (/ws/test)","server":"75254220.fd7a5c","client":"","x":700.0000343322754,"y":190.9999828338623,"wires":[]},{"id":"a00899b6.1c5b48","type":"template","z":"396465eb.1d8e22","name":"sakura-iot","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"module\":\"1234567890ab\",\"type\":\"channels\",\"datetime\":\"{{payload.datetime}}\",\"payload\":{\"channels\":[{\"channel\":{{payload.channel}},\"type\":\"I\",\"value\":{{payload.value}}}]}}","x":478.5714797973633,"y":188.14291858673096,"wires":[["9f1852ae.9260a8","88ccffee.743da8"]]},{"id":"99d5057c.56db98","type":"inject","z":"396465eb.1d8e22","name":"15秒おき","topic":"","payload":"true","payloadType":"bool","repeat":"15","crontab":"","once":true,"x":105,"y":102.4285888671875,"wires":[["1566d30d.eb7c95"]]},{"id":"1566d30d.eb7c95","type":"function","z":"396465eb.1d8e22","name":"ランダムなチャンネルにランダムな値を生成","func":"var pdata = {}\n\n \n// 15秒 * 1/4 = 1分に一度の割合で値を更新\nif(Math.random() < 1/4){\n \n // 0 - 5のいずれか\n pdata.channel = Math.floor( Math.random() *6 ) ;\n \n // 0 , 1のいずれか\n pdata.value = Math.round( Math.random()) ;\n \n pdata.datetime = new Date().toJSON();\n msg.payload = pdata;\n\n return msg;\n\n} else {\n return null;\n}\n\n\n","outputs":1,"noerr":0,"x":403.57142639160156,"y":102.42860794067383,"wires":[["a00899b6.1c5b48"]]},{"id":"88ccffee.743da8","type":"debug","z":"396465eb.1d8e22","name":"WebSocket送信データ","active":true,"console":"false","complete":"payload","x":680.714282989502,"y":263.8572196960449,"wires":[]},{"id":"50fc2bf8.59c0b4","type":"comment","z":"396465eb.1d8e22","name":"さくらiotのWebSocketのモック","info":"","x":135,"y":51,"wires":[]},{"id":"6ac7390e.c86848","type":"comment","z":"396465eb.1d8e22","name":"モックのWebSocket から受信","info":"","x":137.8571548461914,"y":355.28572368621826,"wires":[]},{"id":"3b4df7a2.862328","type":"websocket in","z":"396465eb.1d8e22","name":"WebSocket (/ws/test)","server":"","client":"df973c4.18d10c","x":114.28571755545477,"y":405.28572368621826,"wires":[["38a72b0c.319a0c"]]},{"id":"38a72b0c.319a0c","type":"debug","z":"396465eb.1d8e22","name":"WebSocket受信データ","active":true,"console":"false","complete":"payload","x":362.32144927978516,"y":404.5715093612671,"wires":[]},{"id":"75254220.fd7a5c","type":"websocket-listener","z":"","path":"/ws/test","wholemsg":"false"},{"id":"df973c4.18d10c","type":"websocket-client","z":"","path":"ws://your-app-name.mybliemix.net/ws/test","wholemsg":"false"}]
こちらをコピーして右上のメニュー(横線3本のアイコン)からImportできます。
なお、まだ見ぬAPIから送られてくるデータ仕様は、 さくらのIoT Platformを試してみたを参考にさせていただきました。
右上の「Deploy」をおすと、debugタブにデータがどんどん貯まっていくのが分かります。
想定データ仕様
IoT Platformには、128のチャンネルがあり、そのチャンネルそれぞれに、8バイト(オクテット)のデータを送ることができます。
当社の会議室は、利用中以外はドアを開けておくルールになっています。
なので、Arduinoにドアセンサ(こんなの)を接続して、一つの部屋にひとつのチャンネルを割り当て、ドアが開いていればゼロ、ドアが閉まっていればイチを送るようにしました。
また、なるべくリアルタイムにデータを取得できるよう、5秒おきにループを回すことにしたのですが、毎回通信していたらそれなりに通信量がかさんでしまうので、状態が変わったときにだけデータを送ることにします。
実際のArduinoのコードは、0日目に注文したものが届いて、動くことを確認した後にアップします。
WebSocketで受信したデータをAmbientに送信する
前述の通り、IoT Platformからは、ドアの状態に変化が起こったときだけデータを受信することにしました。受け取ったデータを、そのままAmbientに転送し、棒グラフを描画させると、こんな風に、いつ使われているのか全く分からないデータになります。
(画像はイメージです)
そのため、Node-REDの中で、IoT Platformから受信したデータを現在の状態として一時的に貯めておき、Ambientには定期的(1分おきとか)に現在の状態を送信することにしました。
まず、状態を蓄積するために、グローバル変数を定義し、初期化します。
左のNode(それぞれの部品をNodeと呼ぶんですね)の一覧から、「Inject」と「Function」を取り出し、2つを線でつないでください。
それぞれのNodeをダブルクリックすると、設定が出てきます。
Injectの設定では、PayloadでBooleanのtrueを送るようにし、「Inject once at start?」にチェックを入れてください。これで、再起動時(左上のデプロイを押したとき)に必ず実行されます。
Functionの設定では、下記のように、global変数にArrayを追加して、ゼロで初期化しておきます。
設定を変更したら、反映するために「Deploy」を忘れず押しましょう。
つぎに、WebSocketから受信したデータでglobal変数を更新する部分を作ります。
せっかちな方は下記をコピーしてImportしてください。
[{"id":"1603f091.8c119f","type":"debug","z":"396465eb.1d8e22","name":"Global変数に保存された値","active":true,"console":"false","complete":"payload","x":602.7500076293945,"y":473.0000066757202,"wires":[]},{"id":"c6245639.e87ce8","type":"function","z":"396465eb.1d8e22","name":"Global変数の更新","func":" msg.payload.payload.channels.forEach(function(v) {\n context.global.data[v.channel] = v.value;\n\n});\n\nmsg.payload = JSON.stringify(context.global.data);\n\nreturn msg;","outputs":1,"noerr":0,"x":343.75000762939453,"y":472.5000066757202,"wires":[["1603f091.8c119f"]]},{"id":"3e6f4d17.8847f2","type":"json","z":"396465eb.1d8e22","name":"","x":136.875,"y":472.5,"wires":[["c6245639.e87ce8"]]}]
Nodeのリストの上に、検索ボックスがあります。必要なNodeが見つからない場合はここから検索してください。
jsonというNodeは、文字列からjson オブジェクトに変換してくれます。WebSocketから受信した状態だと文字列なので変換が必要です。
これで、global変数に各会議室の利用状態が保存されたので、情報を定期的にAmbientに送信します。
[{"id":"3f480fb6.370088","type":"Ambient","z":"396465eb.1d8e22","name":"Ambient:会議室状況","channelId":"123","writeKey":"0123456789abcdef","x":521.5000019073486,"y":731,"wires":[]},{"id":"30e286bf.69737a","type":"function","z":"396465eb.1d8e22","name":"Arrayから値を取得","func":"\n\nvar data = {\n // 0 or 1 \n \"d1\" : context.global.data[0] ,\n \"d2\" : context.global.data[1] ,\n \"d3\" : context.global.data[2] ,\n \"d4\" : context.global.data[3] ,\n \"d5\" : context.global.data[4] ,\n \"d6\" : context.global.data[5] \n};\nmsg.payload = data;\nreturn msg;","outputs":1,"noerr":0,"x":276,"y":785.0000123977661,"wires":[["3f480fb6.370088","2b6720b9.34f1f8"]]},{"id":"cb556381.39e78","type":"inject","z":"396465eb.1d8e22","name":"1分おきに実行","topic":"","payload":"true","payloadType":"bool","repeat":"60","crontab":"","once":true,"x":109,"y":727,"wires":[["30e286bf.69737a"]]},{"id":"2b6720b9.34f1f8","type":"debug","z":"396465eb.1d8e22","name":"Ambient送信データ","active":false,"console":"false","complete":"payload","x":504,"y":785.0000123977661,"wires":[]}]
これをコピペして、ChannelIDとWriteKeyを適宜書き換えてください。
AmbientのチャネルIDとライトキーはログインすると出てきます。
そうこうやっている間に年が明けてしまったので、2日目に続きます。