node-serialportを使ってNode.jsでArduinoと通信する

node-serialportとは

Node.jsでシリアルポートを制御できるようにしたライブラリ。今回はこれを使ってNode.jsとArduinoをシリアル通信で接続します。今回のターゲットは2種類。まずはMac + Arduinoで動作を確認した後、Raspberry Pi + Arduinoで動作させてみます

※2013/3/30 データ受信取りこぼしの件で、修正パッチを頂きました。 @kei_os さんありがとうございます

node-serialportのインストール

まずはMacの方でインストールします。ここを参考にexpressをインストールし、Socket.ioもインストールしておきます。node-serialportのインストールはnpmを使います。

$ npm install serialport

サーバの記述

serialportの使い方だけ抜粋します。

serialportの宣言をしておきます。

var express = require('express')
    , routes = require('./routes')
    , path = require('path')
    , serialport = require('serialport');

MacでArduinoを接続した時のシリアルポートを調べておき、portNameに指定します(接続時に毎回変わる場合もある)通信設定はよくあるパターンですね。パースの単位を改行で行うように設定し、ArduinoからのJSON形式の送信データの末尾に改行を付加するようにします。今回は通信速度を115200にしていますが、なぜか9600だとデータ受信で取りこぼす確率が高く使い物になりませんでした。ただし115200にしてもデータを取りこぼすことが結構あり、どうしてこうなるのか原因調査等した方がいらっしゃいましたら教えて下さい。(正直言って信頼性に欠けるので、プロトタイプ以外には全然ダメ)※2013/3/30修正パッチでbugfixしました

// Serial Port
var portName = '/dev/tty.usbmodemfd13431'; // Mac環境
var sp = new serialport.SerialPort(portName, {
    baudRate: 115200,
    dataBits: 8,
    parity: 'none',
    stopBits: 1,
    flowControl: false,
    parser: serialport.parsers.readline("\n")   // ※修正:パースの単位を改行で行う
});

Arduino → Node.jsの受信の部分です。データ列として受信するので、一旦Bufferで受けておいてJSONでパースしています。データ列が正常に完全な形で受信できなかった場合は、無視するようにしています。

// data from Serial port
sp.on('data', function(input) {

    var buffer = new Buffer(input, 'utf8');
    var jsonData;
    try {
        jsonData = JSON.parse(buffer);
        console.log('temp: ' + jsonData.temp);
        console.log('led: ' + jsonData.led);
    } catch(e) {
        // データ受信がおかしい場合無視する
        return;
    }
    // つながっているクライアント全員に送信
    io.sockets.json.emit('message', { value: jsonData });
});

Arduino側の実装

Arduinoには温度センサー(LM35Z)とLEDを載っけてあるので、それを制御してみます。loop()を5秒で回して、温度とLEDのON/OFFをJSON文字列としてシリアル送信します。Node.js側では受信した温度の表示を行い、ブラウザを通してArduino上のLEDのON/OFFを行えるようにしました。 コードはここ

Macで動かしてみる

気温が表示され、LEDのON/OFFコントロールがブラウザ上からできるようになっています。ただし、Arduinoのループ周期が5秒に設定してあるので、LEDのコントロールは最大5秒待たされることになります。

f:id:tomo_watanabe:20130323204520j:plain

Raspberry Piで動かしてみる

同じコードをRaspberry Piで動かしてみます。コードはGithubで管理しているので、Raspberry Piにログインしてから、git cloneしてコードを取ってきます。ここでは、すでにRaspberry Piにはnvm, npmおよびNode.jsがインストールされているものとします。ArduinoをUSBでRaspberry Piに接続してlsするとそれっぽいのが見つかります。ArduinoはttyACM0になりますので、app.jsのportNameを書き換えます。(ttyAMA0はRaspberryPiの内蔵シリアルポート)

$ ls /dev/tty*
 /dev/ttyACM0
 /dev/ttyAMA0

なにはともあれ動かしてみます(えいやっ

$ node app.js
/home/pi/work/Node-SerialPort/NodeServer/node_modules/serialport/node_modules/bindings/bindings.js:77
        throw e
              ^
Error: /home/pi/work/Node-SerialPort/NodeServer/node_modules/serialport/build/Release/serialport.node: invalid ELF header
    at Object.Module._extensions..node (module.js:485:11)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:362:17)
    at require (module.js:378:17)
    at bindings (/home/pi/work/Node-SerialPort/NodeServer/node_modules/serialport/node_modules/bindings/bindings.js:72:15)
    at Object.<anonymous> (/home/pi/work/Node-SerialPort/NodeServer/node_modules/serialport/serialport.js:7:44)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:356:32)

さすがにnode-serialportはmacと同じ環境ではないのでダメなようです。一旦serialportをアンインストールして、再インストールします。Raspberry Piではnode-serialportをグローバルインストールしてリンクを作成して使うようにしました(他でも使うかもしれないので)

$ npm uninstall serialport
$ npm install -g serialport
$ npm link serialport

これでもう一度起動してみます。

f:id:tomo_watanabe:20130323222525j:plain

先ほどのMacと同じように動きました。

今回のコードはGithubに置いてあります。コードはMacで動作するコードなので注意して下さい。 ※2013/3/30 データ取りこぼしの件bugfixしました