前の記事に引き続き今回もArduino I/O を操作する Web アプリに、画面の自動更新機能を追加するための説明をします。
今回は、前の記事で検討した下記の機能追加項目について実現していきます
(2) Web アプリを実行する複数のブラウザ画面のコネクションを管理して、Arduino I/O が変化した時にサーバーPC から最新のI/O データを配信する
最初に今回のアプリで要求される、多数のブラウザのコネクションを継続的に維持して、ブロードキャストメッセージを送信する機能について DeviceServer のみで実現可能かどうかを検討します。
DeviceServer は比較的リアルタイム指向的な制御を行うように設計しています。サーバーに接続されたセンサーや計測機器、リモートデバイスからのイベントは全て別スレッドで処理を開始させて、各々の処理(イベントハンドラやスクリプト)は独立したインスタンスで実行されます。このため、どれか一つの処理に時間がかかったり、エラーが発生した場合でもセンサーネットワークシステム全体には影響を与えないようにしています。
今回検討している複数のブラウザのコネクションをDeviceServer で直接維持・管理すると、スレッドの数が接続中のブラウザ分だけ増加して、すぐにサーバー内部のリソースが不足してしまいます。このため、今回の様なアプリケーションの実現に適した node.js ( http://nodejs.org/ ) と soket.io ( http://socket.io/ )を採用して、DeviceServer と併用してアプリを構築することにします。
node.js と socket.io を利用した配信用サーバーを作成して、DeviceServer とは別に実行します。この配信用サーバーでは、複数ブラウザのコネクション管理とメッセージ配信に特化した機能を実現します。Arduino I/O が変化すると最新の I/O データ値がシリアルポート経由の Firmata パケットで DeviceServer に送信されてきます。その I/O データ値を、配信用サーバー(node.js と socket.io で作成した)にソケット通信で送信します。配信用サーバーはWebアプリ中で接続中の全ブラウザに対して、I/O データ値を WebSocket (socket.io でカプセル化されたコネクション)を使用して配信します。
例えると、これらの2つのサーバー機能は放送局(DeviceServer)と通信衛星(配信用リレーサーバー)の様な関係になります。放送局(DeviceServer)から配信したいデータ(Arduino I/O ポート値)を、一旦通信衛星(配信用サーバー)に送信(uplink) します。その後、通信衛星からは各家庭のテレビ(Webブラウザ)に対してブロードキャスト(downlink) されてきます。また、各家庭のテレビからリモコンの4色ボタンでデータ送信やコマンド要求(Arduino I/O をチェックボックスで操作など) するときには通信衛星(配信用サーバー)ではなく電話回線やインターネット回線(Web API)を使用して直接放送局(DeviceServer の HTTP サーバー)に接続します。
配信用サーバーに使用する WebSocket は双方向通信にも使用できますが、DeviceServer 側を操作するときに各リクエスト毎の認証が必要なため、ブラウザからの通信は Web API (HTTP Server の GET リクエスト) 経由のみに限定しています。
さっそく node.js のインストールから始めます。今回は DeviceServer の動作している Windows PC に node.js をインストールしていますが、別のPC で動作している linux 等にインストールしても構いません。ただし別 PC に配信機能を分けた場合には DeviceServer から配信データを送信(uplink) する部分のホスト名(“localhost”) と、Web アプリ中からアクセスする WebSocket のURLアドレス(“/”) を変更する必要があります。
node.js のホームページ http://nodejs.org/ から最新の Windows 版パッケージをダウンロードしてインストールしてください。node.js のインストール完了後に追加パッケージの socket.io もインストールします。追加パッケージは node.js で提供されている npm プログラムをコマンドプロンプトから実行してインストールします。(下記画面参照。インストール作業を行うディレクトリに注意してください)
sokcet.io インストール後に、Windows のコントロールパネルから “システム” を選択して環境変数 “NODE_PATH” を下記の例の様に設定します。
これで、node.js と socket.io のインストールは完了しました。次に node.js で実行する配信用サーバーのスクリプトファイル(JavaScript)を作成します。配信用サーバーのスクリプトファイルはどこに配置しても構いませんが、今回は “C:\Projects\node\arduino_relay_server.js” に格納しています。
下記に、arduino_relay_server.js ファイルの内容を示します。この後の記事で説明するファイルを含めてここからダウンロードすることができます。
// RelayServer から複数の Webブラウザへデータを配信するためのポート // 最初にWeb ブラウザ側から WebSocket(socket.io) を使用してコネクションが張られる。 // コネクションが確立した後ブラウザ側は RelayServer から送信される // ブロードキャストメッセージを受信する。 var downlink_port = 9090; // 予め socket.io を npm を使用してインストールしておくこと var downlink = require('socket.io').listen(downlink_port); downlink.configure(function(){ downlink.set('log level', 1); // socket.io の debug ログを抑止 }); // DeviceServer からWeb ブラウザに配信するためのデータを受け付けるためのポート // 通常のソケットサーバー(net)経由で JSON 文字列で記述された配信用データを受信する // 文字列の終端は \n で判断して、受信後に短い ACK 文字列をDeviceServer 側に送信する。 // この ACK 文字列は、DeviceServer側 API tcd_send_recv_data()が、通信が完了したことを判断する // ために使用している。 var uplink_port = 9080; var net = require('net'); var relay_server = net.createServer(function (uplink_stream) { var buffer = ""; var msg = ""; uplink_stream.setEncoding("utf8"); uplink_stream.on("data", function (data) { // 配信用データをDeviceServer から受信する if ( data.indexOf('\n') < 0) { buffer += data; } else { // 配信用データ末尾の空白文字を除去 msg = buffer + data.substring(0, data.indexOf('\n') ); msg.replace(/\s+$/g, ""); // trim right // DeviceServer に受信完了を知らせる uplink_stream.write("OK\n"); // sendback a ack uplink_stream.end(); // WebSocketで接続中の全ブラウザに対して配信データを送信する downlink.sockets.emit('broadcast',{ message: msg }); console.log('broadcast:' + msg); } }); }); // 配信用 RelayServer起動 relay_server.listen(uplink_port);
スクリプトの最初の部分で定義された “downlink” が Webアプリから接続される WebSocket(socket.io) になります。接続後は、配信用サーバーから送信されるブロードキャストメッセージの到着を待ち続けます。
uplink_port と relay_server は、それぞれ DeviceServer から最新の Arduino I/O データを受信するためのTCPソケットのポートとTCP サーバーになります。DeviceServer から改行文字で終端された文字列データを受信すると、その文字列データに ‘message’ タグに対応させた連想配列データを作成して、WebSocket でブラウザが接続している downlink に対してブロードキャスト送信します。送信データは下記の様なJSON フォーマットになっています。
* uplink ポートにDeviceServer から送信されてくる文字列例
{“PORT_0″:”00″,”PORT_1″:”00″}
*WebSocket(downlink) で接続中の全Webブラウザに送信されるデータ。uplink ポートで受信したJSON データを文字列として”message” タグに関連づけて、その全体のJSON データを送信しています。
{“message”: “{“PORT_0″:”00″,”PORT_1″:”00″}” }
配信用サーバーを実行するPC でファイアウォールが設定されている場合には、TCPポート番号(9090、9080)で通信可能に設定して下さい。またブラウザからWebアプリでサーバーPCにアクセスするときに、ルータ等を経由する場合には、適切に設定して通信が可能な状態にしておきます。
配信用サーバーはコマンドプロンプトから node コマンドで起動します。下記の実行例画面中にある “broadcast:xxx” のメッセージは、実際にWebアプリを動作させた時に表示されますので今は表示されません。
これで、配信用サーバーの準備ができました。
これまでの作業で Arduino 側の I/O 変化時のデータ送信機能とブラウザ側へのデータ配信機能の作成が完了しました。続きの作業は、後の記事で説明します….
それではまた。