Google Chrome + ChromeAppsのchrome.serial API で取得したデータを、WebSocketを使ってSinatraへ飛ばしてみる

この前はシリアルポートから受信したデータをChromeAppsの画面に表示しました。

それだけではあまり使い道がないので、その受信したデータをSinatraへ飛ばしてみようとChromeAPIを探してみたところ、chrome.sockets.tcpchrome.sockets.udpAPIがありました。

 
ただ、それぞれのsend()メソッドを調べてみたところ、送信するデータ型がArrayBufferSinatraでは扱いが面倒そうだったので、今回はChromeAPIを使うのは見送ることにしました。

他の手段を試してみたところ、ChromeAppsでWebSocketが使えることが分かりました。そこで、気になっていたWebSocketの勉強を兼ねて、ChromeAppsからSinatraへWebSocketを使ってデータを送信してみました。

 

環境

 

全体の構成

今回はWebSocket送信の成否は判定していないため、それぞれ片方向通信となっています。

ChromeApp ---(WebSocket)---> Sinatra (port 4567) ---(WebSocket)---> SinatraのView (index.erb)

 

ChromeApps側

ChromeAppsでWebSocketが使えるかどうかが問題でしたが、特に問題なく動作しました。
そのため、前回のコードを少し変更するだけで済みました。

シリアルポートからの送信は一回行うだけなので、「ボタンを押したら接続」「データ送信」「送信後に接続を閉じる」という簡単な実装になりました。

const WEBSOCKET_HOST = "ws://192.168.0.1:4567";
var clickedWebSocketListener = function() {
  var ws = new WebSocket(WEBSOCKET_HOST);

  ws.onopen = function() {
    console.log('opened');
    ws.send(arrayReceived.join(''));

    //  送ったら閉じとく
    ws.close();

    //  変数の初期化とlistのクリア
    stringReceived = '';
    arrayReceived = [];
    var upload = document.getElementById('upload');
    while (upload.firstChild) {
      upload.removeChild(upload.firstChild);
    }
  };

  ws.onclose = function() {
    console.log('closed');
  };
};
document.getElementById('connectWebSocket').addEventListener("click", clickedWebSocketListener, false);

 

Sinatra

SinatraでWebSocketを扱うようなものがないかを探してみたところ、stackoverflowに以下のような回答がありました。
ruby - Any success with Sinatra working together with EventMachine WebSockets? - Stack Overflow

今回は、sinatra-websocket gemを使うことにしました。
simulacre/sinatra-websocket - GitHub

Sinatra部分は、ほぼ sinatra-websocket のサンプルと同じです。
bundle exec ruby app.rb -e production にて起動します。

get '/' do
  logger.info('accessed')

  unless request.websocket?
    erb :index
  else
    logger.info('received')
    request.websocket do |ws|
      ws.onopen do
        logger.info('opened')
        settings.sockets << ws
      end

      ws.onmessage do |msg|
        logger.info(msg)
        EM.next_tick { settings.sockets.each{|s| s.send(msg) } }

      end
      ws.onclose do
        logger.info('closed')
      end
    end
  end
end

 
なお、SinatraのViewに記載したJavaScriptはChromeApps側とほぼ同じになっているため、記述は省略します。

 

画面イメージとコンソール出力

ChromeApps - シリアルポートからのデータ受信前

f:id:thinkAmi:20140605045946p:plain

 

ChromeApps - シリアルポートからの受信後

f:id:thinkAmi:20140605050117p:plain

 

ChromeApps - シリアルポート受信~WebSocket送信までのコンソールログ

f:id:thinkAmi:20140605050254p:plain

 

Sinatra - WebSocket受信前

f:id:thinkAmi:20140605050147p:plain

 

Sinatra - WebSocket受信後

f:id:thinkAmi:20140605050205p:plain

 

ソースコード

GitHubに上げておきました。
ChromeApps-sample/serial_websocket at master · thinkAmi/ChromeApps-sample

 

参考

WebSocket

上記のstackoverflowの回答以外のgemなどを探してみたところ、faye-websocket gemがありました。こちらの方がStar数が多かったので、メジャーなのかもしれません。
faye-websocket-ruby

試している日本語のBlogなどは以下がありました。
Sinatra + Faye::WebSocketでlambda do |env|したくない - 技術ネタ書ける人になりたい

WebSocketに関するその他の参考記事

 

JavaScript