MQTT は IoT の仕組みの中で使われることの多いプロトコルですが、以前からそれだけに使うのは勿体ないなあと思っていました。MQTT のリアルタイム性はチャットなどのメッセージングアプリケーションにも向いていると思っており、実際に Facebook Messenger の仕組みとしても使われているとの情報もあります。というわけで、MQTT を使ったチャットアプリの開発に挑戦してみました。

加えて、IoT といえば IBM Bluemix からも提供されている Node-RED が有名です。今回は IBM Bluemix 環境上の Node-REDIBM IoT Platform サービスの quickstart エディションを使ってチャットを作ってみることに挑戦しました。

何はともあれ、まずは Node-RED 環境を用意します。IBM Bluemix にログインし、ボイラープレートから Node-RED Starter を選択して、Node-RED が使えるアプリケーションサーバーインスタンスを用意します:
2017040301


IBM Bluemix の Node-RED を使わずに、自前等で Node-RED 環境を用意する場合は npm などで node-red-contrib-scx-ibmiotapp ノードをインストールして有効にしておく必要があります:
2017040302

(↑この ibmiot ノードが使える状態にしてください)


では Node-RED でチャットアプリを作ります、といっても実はかなりシンプルです。1つ1つノードを配置してもいいのですが、まずはインポートして中身を確認し、必要に応じて説明を加えることにします。画面右上のハンバーガーメニューを開き、 読み込み→クリックボード を選択します:
2017040303

 
「フローの読み込み」画面になったら、ここの内容をそっくりそのままコピー&ペーストして「読み込み」ボタンをクリックし、フロー定義を作成します:
2017040304


正しく読み込みが完了すると以下のような3本のデータフローが再現されるはずです。上から1つ目は GET /chat 実行時のチャット画面(HTML)の定義、2つ目はチャットメッセージを POST(POST /post) した時の処理、そして3つ目は IBM IoT サービスを使って MQTT 経由でチャット参加メンバーのメッセージを取り出す処理を定義しています。いずれもシンプルな処理で実現できていることが確認できます:
2017040305


画面内に2つの IBM IoT ノードが含まれています(青いノード、INPUT/OUTPUT が1つずつ)。それぞれダブルクリックすると、どちらにも Device Id を入力する項目があり、いずれも初期状態では空になっているはずです。この Device Id にはユニークな値を指定する必要があります。以下の例では "dotnsf.mqtt.chat.001" という値を設定していますが、ここには自分の名前や日付を含めるなどして、誰とも被らないユニークな値を設定します(2つのノード両方の Device Id に同じ値を指定します)。指定後「完了」ボタンをクリックします:
2017040306


また、2つ目のフローの中にある function ノードの中身を確認します。ここはチャット参加者が自分のメッセージを投稿した時に実行されるフローで、HTTP リクエストの本文(msg.req.body)の値を取り出して、その値を IoT の Payload に変換している部分です。これも非常にシンプルな処理内容が記述されていることが確認できます:
2017040307


改めて3つそれぞれのフローの中でどのような処理が定義されているのかを確認してみましょう。1つ目のフローはウェブブラウザから(サーバー名)/chat という URL に(GET リクエストで)アクセスした時に返される HTML の定義です。実際の HTML や CSS/JavaScript 定義そのものは「チャット画面」というテンプレートノードの中で定義されています(後述します):
2017040301


2つ目のフローはチャット画面の中で利用者が自分のメッセージをチャットに流した時に実行される処理です。チャットにメッセージを流すと(サーバー名)/post という URL に名前やメッセージ内容が HTTP POST され、その内容を(上記のように)取り出して MQTT の Payload に変換し、IBM IoT に転送(MQTT の処理でいうと「パブリッシュ」)しています。転送時にユニークな Device Id を指定していることで、同じテンプレートを使っても異なるアプリケーションであるとみなし、他の人が作ったチャットと混線しないようにしています。なお、緑色のノードはデバッグノードで、POST されたメッセージの内容をこの画面内からも確認できるようにしているだけで、実際の処理には無関係です(無くても動きます):
2017040302


そして3つ目のフローは上記2つ目のフローで処理されたメッセージを取り出すフローになります。自分だけでなく、同じチャット画面を見ている他のユーザーがメッセージを流した場合もこの処理が実行され、IBM IoT 経由で送られたメッセージが(サーバー名)/ws/chat という URL の WebSocket に送信されるよう記述されています。実際には1つ目の HTML の中で /ws/chat を監視しており、ここにメッセージが送られてきた場合の処理が記述されています:
2017040303


この状態でデプロイ(画面右上のボタン)をクリックすることで実際にチャットアプリケーションを使うことができるようになります。デプロイ後、PCやスマホのウェブブラウザで https://(Node-RED の動いているホスト名)/chat にアクセスしてみてください。Node-RED の一番上のフローが呼ばれ、テンプレートノードの中で定義された内容の HTML が表示されます。初期状態では↓のように名前の入力を求められます:
2017040301


適当な名前を入力して「入室」ボタンをクリックします(入室のタイミングで IoT と接続します):
2017040302


入室すると画面が切り替わり、自分の名前とメッセージ入力フィールドが画面下に表示されます。画面の大半はチャット履歴が表示される画面ですが、まだ何もメッセージがないので何も表示されていません:
2017040303


では試しに何かメッセージを入力してみます。入力を確定するには PC からであれば Enter キーを、スマホであれば「開く」などでメッセージを確定させてください:
2017040304


入力したメッセージがチャット履歴に表示されます。これは自分のメッセージなので右側に吹き出しがついて、緑色で表示されるようにしています:
2017040305


もう1つメッセージを送ると、メッセージが下に追加されます:
2017040306


試しに別のブラウザや別のスマホなどから同じ URL にアクセスして、別の名前で入室してメッセージを送信してみます。このユーザーから見ると入室前のメッセージは見れないので、自分のメッセージが一番最初に表示されます:
2017040307


が、元のユーザーからは別のユーザーが入室してきてメッセージを送信したことになります。その場合は白背景で、左側に吹き出しがある状態でチャット履歴に記録されます(この UI 見たことありますよね。意識して CSS を作ってます(笑)):
2017040308


同様にして別のユーザーが入室してくると、そのユーザーのメッセージも白背景で左に吹き出しが付く形で表示されていく、というものです。最低限のグループチャットの機能は実現できていると思ってます:
2017040309


さて、ではこのチャット画面の HTML はどうなっているのかを説明します。具体的な内容は PC ブラウザからアクセスして HTML ソース(と埋め込まれた CSS など)を直接参照していただきたいのですが、肝心な部分の JavaScript はこのようになっています(赤字はコメント):
  :

var socket; var wsUrl = 'wss://' + location.hostname + '/ws/chat'; //. WebSocket監視先URL function connect(){ //. 「入室」時に呼ばれる処理 console.log( "connect()" ); socket = new WebSocket(wsUrl); //. WebSocket 接続 socket.onmessage = function(e) { //. WebSocket にメッセージが来たら、以下を実行 var msg = JSON.parse(e.data); //. 送信データ(POST されたデータ)を JSON で取り出し //console.log( msg ); if( msg.id != deviceid ){ //. 自分のメッセージなのか、他人のメッセージなのかを判別 //. 自分以外の発言 var box = "<div class='question_box'><p class='notmymessage'>" + msg.name + "</p><div id='arrow_question'>" + msg.message + "</div></div>"; $('#contents').append( box ); }else{ //. 自分の発言 var box = "<div class='answer_box'><p class='mymessage'>" + msg.name + "</p><div id='arrow_answer'>" + msg.message + "</div></div>" $('#contents').append( box ); } } }

:

2つ目のフローで投稿したメッセージの内容が IBM IoT ノードに(MQTT で)送られていました。自分だけでなく同じチャットルームに入室している全ての人のメッセージがこのように MQTT データとして送信されます。 そしてその内容を3つ目のフローで取得し、/ws/chat というパスに WebSocket データとして送信していました。つまりチャットで誰かがメッセージを送ると、/ws/chat に WebSocket でデータが送られるということになります。そのデータを監視して、自分のメッセージか他人のメッセージかを判別して Dynamic HTML でチャット履歴に追加する、という部分の処理が上記になります。


そしてこれだけでチャットが実現できているということは、(気付いている人もいるかもしれませんが)少なくともここまでの処理に関してはデータベースを一切使わずに実現できていることを意味しています。確かにリアルタイムデータ処理なのでデータを保存する必要はないのですが、実際に保存せずに実現できるというのはなかなか興味深いのではないかと思っています。