この記事は Node-RED Advent Calendar 2018 1日目です。
いよいよスタートです!参加される、Node-RED UG のみなさまよろしくおねがいします!
今回のネタ
さて、今回のネタはobniz×M5Stack×NefryBT三つ巴Meetup!【ESP32開発ボードLT祭】 – connpassで使った、obnizとNode-REDをつなげるためにMQTT over WebSocketを使ったメモです。
なぜ、MQTT over WebSocketを使いたいのか?
このイベントでは3つのデバイスを動かす必要がありました、そのときに、MQTTでIoTを動かしやすい状況でしたので、obnizクラウドではフロントエンドだけWebSocketを連携しても良かったんですが、技術的挑戦として「MQTTで全部揃えてしまいたい!」というものがありました。
これを実現するためにMQTT over WebSocketです。
初めての MQTTにあるように、
MQTT over WebSocket
MQTT はプロトコル、つまり通信規格の一つで基本的には TCP/IP 上での動作を前提としています。ただし、WebSocket 上でも動作させることが出来ます。つまりブラウザ上で MQTT を使える用になります。
もともはセンサーや、何かしらのリアルタイムな情報を WebSocket を使ってブラウザに実際にグラフを作ったりするのが目的で使われ始めた機能のようです。
実際、管理画面側でのブラウザで見れるグラフを作成するのにとても向いている用です。
ということで、こちらを採用してMQTTの作法に揃えることにしました。
Node-REDはさくらのクラウドで用意
Node-REDはさくらのクラウド用意しています。最近の展示ごとでは、かなり重宝しています。
なぜ、さくらのクラウドかと言うとスタートアップスクリプトでNode-REDが揃っていて使いやすいからです。
sakura-internet/cloud-startupscripts: さくらのクラウド スタートアップスクリプト用リポジトリ
実際、CentOSにインストール後、使えるようにするには、フロントエンジニアの私としては一息で作れるものではないので、こういうスクリプトはありがたいですね!
さくらのクラウドにNode-REDでMQTTブローカーを立てる
ということで、さくらのクラウドにNode-REDでMQTTブローカーを立てます。
詳しくはこちらの記事をご参考ください。
さくらのクラウドのスタートアップスクリプトでNode-REDをインストールするメモ – 1ft-seabass.jp.MEMO
現状では、さらにIDとパスワードも設定できるようにもなっています。このあたり、PllRequestでちょっと私もサポートさせていただきました!
私は、CentOS7のイメージでインストールしています。
Node-REDのフローの準備
サーバーが出来上がったら、Node-REDのフローの準備です。MQTTブローカーのノード(node-red-contrib-mqtt-broker)があるのでこちらでシンプルに構成します。
詳しい node-red-contrib-mqtt-broker の使い方は、Choregraphe入っているPCにNode-REDを入れてPepperとMQTT連携するメモにも書いてありますのでご参考ください。
注意点としてはMQTTとMQTT over WebSocket用のポートを開ける必要がある
さて、MQTTブローカーもこれで立てれたのですが、さくらのクラウドでは注意点があります。
MQTTブローカーノードの設定を見てみますとポートがわかります。つまり、MQTT 1880番とMQTT over WebSocket用の8080番のポートを開ける必要があります。
ここは、さくらのクラウドでサーバーに入って、SSHターミナルからfirewall-cmdでポートを開けてやります。
firewall-cmd --add-port=8080/tcp --permanent
まず、MQTT over WebSocketの8080ポートを開ける設定をします。successと出たら成功です。
firewall-cmd --add-port=1883/tcp --permanent
まず、つづいてMQTTの1883ポートを開ける設定をします。successと出たら成功です。
firewall-cmd --reload
最後にファイアウォールを再起動して設定は完了です。
obnizクラウドのソースコード
obnizクラウドではこのようにソースコードを使います。
MQTT_OVER_WEBSOCKET_SERVER_URLは今回立てた、さくらのクラウドのサーバーのURLを指定しましょう。
<html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://obniz.io/js/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@1.13.1/obniz.js" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.2/mqttws31.min.js"></script> </head> <body> <div id="obniz-debug"></div> <h1>obniz instant html</h1> <button id="on">ON</button> <button id="off">OFF</button> <div id="print"></div> <script> var obniz = new Obniz("OBNIZ_ID_HERE"); obniz.onconnect = async function () { // Create a client instance console.log("Create a client instance"); var client = new Paho.MQTT.Client('MQTT_OVER_WEBSOCKET_SERVER_URL', Number(8080), "obniz001"); // set callback handlers client.onConnectionLost = onConnectionLost; client.onMessageArrived = onMessageArrived; // connect the client client.connect({ onSuccess: onConnect }); // called when the client connects function onConnect() { // Once a connection has been made, make a subscription and send a message. console.log("onConnect"); client.subscribe('/sub/obniz'); var message = new Paho.MQTT.Message(JSON.stringify({ "message": "Hello obniz" })); message.destinationName = "/pub/obniz"; client.send(message); } // called when the client loses its connection function onConnectionLost(responseObject) { if (responseObject.errorCode !== 0) { console.log("onConnectionLost:" + responseObject.errorMessage); } } // called when a message arrives function onMessageArrived(message) { console.log("onMessageArrived:" + message.payloadString); // console.log('Message from server ', event.data); var json = JSON.parse(message.payloadString); console.log('Message from server ', json.message); obniz.display.clear(); obniz.display.print(json.message); } obniz.display.clear(); obniz.display.print("Hello World"); obniz.switch.onchange = function (state) { if (state === "push") { obniz.display.clear(); obniz.display.print("push"); var sendData = { "flag": "push" }; var message = new Paho.MQTT.Message(JSON.stringify(sendData)); message.destinationName = "/pub/obniz"; client.send(message); } else if (state === "right") { obniz.display.clear(); obniz.display.print("right"); var sendData = { "flag": "right" }; var message = new Paho.MQTT.Message(JSON.stringify(sendData)); message.destinationName = "/pub/obniz"; client.send(message); } else if (state === "left") { obniz.display.clear(); obniz.display.print("left"); var sendData = { "flag": "left" }; var message = new Paho.MQTT.Message(JSON.stringify(sendData)); message.destinationName = "/pub/obniz"; client.send(message); } else { obniz.display.clear(); obniz.display.print("--"); } } $('#on').click(function () { obniz.display.clear(); obniz.display.print("ON"); }); $('#off').click(function () { obniz.display.clear(); obniz.display.print("OFF"); }); } </script> </body> </html>
仕組みとしては、paho-mqttのフロントエンドライブラリをCDN経由で呼び出してあげて、Paho.MQTT.Messageでやりとりをしています。MQTT over WebSocketでは、publish関数ではなくてPaho.MQTT.MessageでdestinationNameという形でトピックを指定しています。
ちょっとここは言葉はtopicとなっていなかったので戸惑いましたが、こういうものかな?と思えば、慣れればなんとかなるかなというレベルです。
実際に動かしてみる
ということで自在に動かしてみるとこうなります。
obnizのボタンを左右に動かしたり押したりすると、Node-REDでメッセージが飛びます。
Node-REDからメッセージを送ると、obnizでメッセージが表示されます。
ということで、フロントエンドでもMQTT連携ができて、プロトコルが揃ってスッキリですね!
obnizクラウドではフロントエンドなJavaScriptやHTML5の実装ができるので、こういった連携をまた進めてみようと思います!
明日は
arrowmeiwaracing さんの「ダイソーBluetoothリモコンシャッター用ノードを作ったよ」です!
楽しみです!