Node.js(3) socket.io

前回作ったNode.jsのWebアプリは、クライアント(ブラウザ)からのリクエストを受けて結果を返す昔ながらの静的なWebスタイルなので、Raspberry Piをサーバとしてリアルタイムにセンサの値を表示させたいといった用途にはあまり向いていない。
JavaScriptを使ってブラウザから定期的にhttpリクエストを送信する事で、リアルタイム性のあるWebアプリを実現する方法は既に広く使われているが、最近ではWebサーバとブラウザ間で直接コネクションして通信を行うWebSocketの規格が登場し、Node.jsでも簡単に使えるようなので試してみた。

Webアプリの機能としては前回と同じくブラウザからの操作でRaspberry PiのLEDをOn/Offするだけだが、リクエストはsocket.ioで送信してレスポンスもsocket.ioのイベントとして受け取る。これによって複数のブラウザから同時にRaspberry Piに接続した場合、どれかがLEDの状態を変えた時に全てのクライアントがその変化をリアルタイムに知ることができる。

プロジェクトの全コードは前回と同じくGitHubに置いた

順番に見ていこう。まずexpressフレームワークで自動生成したプロジェクトのpackage.jsonに、GPIO操作とscoket.ioのパッケージ定義を追加した。

{
  "name": "led_io",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.12.0",
    "cookie-parser": "~1.3.4",
    "debug": "~2.1.1",
    "ejs": "~2.3.1",
    "express": "~4.12.2",
    "morgan": "~1.5.1",
    "serve-favicon": "~2.2.0",
    "onoff": "~1.0.2",
    "socket.io": "^1.3.5"
  }
}

アプリケーションのエントリポイント”/bin/www”で、サーバオブジェクト生成後にsocket.io用のオブジェクト作成と呼び出しを追加。ここは分けずにwwwに直接コードを追加しても良かったのだが、見通しを良くするためにこうした。

var server = http.createServer(app);
var sock = require('../sock'); // 追加
sock(server); // 追加

socket.io用オブジェクトの処理は、クライアントから接続されてイベントを受信した時、パラメータに従ってLEDをOn/Offしてから、その状態を全てのクライアントに対して送信する。

var io = require('socket.io');  
var gpio = require('onoff').Gpio;
var led = new gpio(17, 'out');

function sock(server) {
  var sock = io.listen(server);
  sock.sockets.on('connection', function(socket) {
    socket.on('event', function(data) {   // クライアントからのイベント受信
      console.log('socket id: [' + socket.id + '] data: ' + data.onoff);
      if (data.onoff == 'on') {
        led.writeSync(1);
        sock.sockets.emit('event', { onoff: 'on' });  // 全てのクライアントへ送信
        // 送信元にだけ送信する場合は
        // socket.emit を使う
        // 送信元以外の全てに送信する場合は
        // socket.broadcast.emit を使う
      }
      if (data.onoff == 'off') {
        led.writeSync(0);
        sock.sockets.emit('event', { onoff: 'off' }); // 全てのクライアントへ送信
      }
    });
    socket.on('disconnect', function() {  // 切断
      console.log('disconnect id: [' + socket.id + ']');
    });
  });
}

module.exports = sock;

コメントにもあるように、サーバからクライアントへの送信は、全てのクライアント、イベントを送信したクライアント、イベントを送信したクライアントを除く全て。に対して行うことができる。
今回は使用しなかったが、クライアントをルームに分けて、同じルームに入っているクライアントにだけ送信する事もできる。
サーバ側のコードとしてはこれで終わり。次はクライアント側を見ていこう。

expressの生成したusers.jsでクライアント用のhtmlテンプレートusers.ejsを使用する部分は前回と同じだが、今回はクライアント側でsocket.ioとsocket.io用の処理スクリプトclient.jsを読み込むようになっている。client.jsの処理はusers.ejsの中に書いても良いのだが、これも見通しを良くするために分けてある。

<script src="/socket.io/socket.io.js"></script>
<script src="/javascripts/client.js"></script>
<!DOCTYPE html>
<html>
  <head>
    <title>raspi-node.js</title>
    <style type="text/css">
    input.button {
      width: 150px;
      height: 50px;
      font-size: 1.8em;
    }
    img.led {
      width: 100px;
      height: 100px;
    }
    </style>
  </head>
  <body>
    <p><img id="led_img" class="led"></p>
    <p>
    <input type="button" value="on" onClick="led_on()" class="button"/>
    <input type="button" value="off" onClick="led_off()" class="button"/>
    </p>
  </body>
</html>

client.jsの内容はサーバからのイベントを受けてDOMでLEDのビットマップを切り替える処理と、ボタンクリックでサーバにイベントを送信(emit)する処理になっている。

var socket = io.connect();

socket.on('connect', function() {
  console.log('connect');
  var img = document.getElementById("led_img");
  img.src = "/images/led-off.png";
  socket.on('event', function(data) {
    console.log('data: ' + data.onoff);
    if (data.onoff == 'on') {
      img.src = "/images/led-on.png";
    }
    if (data.onoff == 'off') {
      img.src = "/images/led-off.png";
    }
  });
});

function led_on() {
  console.log('led_on');
  socket.emit('event', { onoff: 'on'});
}

function led_off() {
  console.log('led_off');
  socket.emit('event', { onoff: 'off'});
}

Raspberry PiにGitがインストール済みであれば、以下のようにソースを取得して起動する事ができる。

$ git clone https://github.com/tokoya/led_io.git
$ cd led_io
$ npm install
$ npm start

> led_io@0.0.0 start /home/pi/projects/led_io
> node ./bin/www

アプリを起動してサーバが待ち受け状態になるまで少し待ってから、「Raspberry PiのIPアドレス:3000/users」へアクセスするとLEDのOn/Off操作画面が表示されるはずだ。その状態で複数のブラウザ(タブ)から同様に接続して操作を行うと、全てのブラウザにその結果が反映されるはず。
onoff

とりあえず今回はここまで。
クライアント側をJavaScriptで作成すると、既存のグラフ描画や3D描画ライブラリを使って見た目をリッチにすることもできるので、次はそのあたりを色々と試してみる予定。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中