yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

Arduinode使って距離センサーとフルカラーLEDを組み合わせる

前に書いたコードの上に距離センサーを組み合わせてみるだけ。
Arduinode使ってNode.jsからフルカラーLEDを制御してみる - yutaponのブログ

前のコードは一定時間毎にLEDをランダムに表示するものでしたが、
今回は距離センサーの値を基準に指定の色を表示してみます。

距離センサーについて

今回使う距離センサーはarduino拡張キットに入ってた
SHARPの2Y0A21というものを使います。

秋月電子だと400円で買えるそうです。
測定距離を見ると10cm〜80cmまで検出できるとのこと。
シャープ測距モジュール GP2Y0A21YK: センサ一般 秋月電子通商 電子部品 ネット通販

このセンサーは赤外線を照射して物体に当たり返ってきた光のズレを見て
距離を計測しているんだとか。
それで値と距離の関係が線形にならないようで、
検出した値から距離を得るには近似式を用いて変換する必要があります。

教本に載っているスケッチによると

int tmp;
int distance;

tmp = analogRead(0); // センサーから値を受け取る

// 4以下の値が取れたら4にする
if (tmp < 4) tmp = 4;

// 距離を計算(cm)
distance = (6787 / (tmp - 3)) - 4;

こんな式で変換可能みたい。


今回は距離センサーから受け取った値に応じた色を表示するプログラムを作ります。

設計

値と色の関係はこんな感じで定義してみた。

距離(cm)
10未満
10 - 19
20 - 29
30 - 39
40 - 49
50 - 59
60以上

この順番はWikipediaの虹の項から頂きました。

Arduinode使ってごりごり書く

出来上がったコードがこれ。

var _         = require('underscore');
var async     = require('async');
var Arduinode = require('arduinode').Arduinode;

// How to find the serial port?
// ls /dev | grep usb
var portName = '/dev/tty.usbmodem641';

function Led(r, g, b) {
    var portR = 9;
    var portG = 6;
    var portB = 10;

    return {
        r : r || 0,
        g : g || 0,
        b : b || 0,

        setRGB : function setRGB(_r, _g, _b) {
            this.r = _r;
            this.g = _g;
            this.b = _b;
        },

        bright : function bright() {
            var self = this;

            async.waterfall([
                function (next) {
                    self._bright('r', next);
                },
                function (next) {
                    self._bright('g', next);
                },
                function (next) {
                    self._bright('b', next);
                }
            ]);
        },

        _bright : function _bright(type, callback) {
            switch (type) {
            case 'r':
                arduinode.analogWrite(portR, this.r, function (err, res) {
                    callback && callback();
                });
                break;
            case 'g':
                arduinode.analogWrite(portG, this.g, function (err, res) {
                    callback && callback();
                });
                break;
            case 'b':
                arduinode.analogWrite(portB, this.b, function (err, res) {
                    callback && callback();
                });
                break;
            }
        }
    };
}

function Color() {
    // 色定義, rgbはカンマ区切りで記述
    var colors = [
        { name: "red",    rgb: "255,0,0" },
        { name: "orange", rgb: "255,165,0" },
        { name: "yellow", rgb: "255,251,0" },
        { name: "green",  rgb: "0,128,0" },
        { name: "blue",   rgb: "0,0 255" },
        { name: "indigo", rgb: "46,8,84" },
        { name: "purple", rgb: "128,0,128" }
    ];

    return {
        /**
         * 引数に与えられた数値に基づいた色をRGBオブジェクトで返す
         * @public
         * @param  {Number} num  0〜700くらいの数値を想定
         * @return {Object}
         */
        toRGB : function toRGB(num) {
            var color;

            if (!_.isNumber(num) || _.isNaN(num)) {
                throw new Error('invalid parameter');
            }

            color = this._numToColor(num);

            return color;
        },


        /**
         * 引数に取った数値を色に変換する
         * @param  {Number} num
         * @return {Object}
         */
        _numToColor : function _numToColor(num) {
            var distance = this._transrateCentimeter(num);
            var index;
            var color;
            var rgb;

            // 距離を10で割ってcolorsのインデックス番号にする
            index = distance / 10 | 0;
            if (index >= colors.length) {
                index = colors.length - 1;
            } else if (index < 0) {
                index = 0;
            }

            color = colors[index];
            rgb   = color.rgb.split(',');

            // 文字列から数値にキャストして入れる
            color.r = Number(rgb[0]);
            color.g = Number(rgb[1]);
            color.b = Number(rgb[2]);

            return color;
        },

        /**
         * 距離センサーの値をcmに変換する
         * @param  {Number} val
         * @return {Number}
         */
        _transrateCentimeter : function _transrateCentimeter(val) {
            if (val < 4) {
                val = 4;
            }
            return (6787 / (val - 3)) - 4;
        }
    };
}

var arduinode = new Arduinode(portName, function(err, result) {

    if (err) {
        return console.log(err);
    }
    console.log('open');


    var DISTANCE_PORT = 0;
    var AI_INTERVAL   = 100;

    var led   = new Led();
    var color = new Color();


    // setup
    async.waterfall([
        function (next) {
            // 距離センサーの値を読み取るための設定
            arduinode.analogStreamOn(DISTANCE_PORT, AI_INTERVAL, function (err, result) {
                if (err) {
                    return next(err);
                }
                console.log('analogStreamOn', result);
                next();
            });
        }
    ], function (err, result) {
        if (err) {
            return console.log(err);
        }
    });


    // ストリームを設定したら、ここでイベントハンドリングする
    arduinode.on('event', function (data) {
        switch (data.event) {
        // analog input
        case 'ai':
            var distanceValue = data.data.val;
            var c = color.toRGB(distanceValue);

            // LED光らせる
            led.setRGB(c.r, c.g, c.b);
            led.bright();
            break;
        }
    });


    // TODO: close処理を書く
});

回路図無いけどこんな感じ。
f:id:sskyu:20140209170813j:plain

これでとりあえずは動いた。
距離センサーの上に手を置いて上下させるとLEDの色が変わるのがわかる。


前回書いたコードからの変更点はasync使ってるところとか、
ArduinodeのStreamAPIを使っているところです。

それとStreamAPIを使っていて、プログラムをCtrl+Cで止めた後に
もう一度プログラムを実行しようとするとこんなエラーが発生します。

JSON parse error : SyntaxError: Unexpected end of input

/Users/sskyu/dev/node/arduino/arduinode/node_modules/arduinode/arduinode.js:134
          throw e;
                ^
SyntaxError: Unexpected end of input
    at Object.parse (native)
    at SerialPort.<anonymous> (/Users/sskyu/dev/node/arduino/arduinode/node_modules/arduinode/arduinode.js:118:27)

当該のコードを見ると、

        var msg = new Buffer(self.buf).toString();
        try{
          // streamのjsonが破壊されていた場合、エラーを通知する方法が現在のところ無い。
          // streamのデータなのかなのか普通のリクエストに対するレスポンスなのかを
          // 判別する方法が無いため.
          var json = JSON.parse(msg);
          if(json.event){
            self.emit("event", json);
          }else{
            if(json.msg == "NG"){
              var error = new Error();
              error.name = "Command error.";
              error.message = json.error;
              self._execCallback(error, json);
            }else{
              self._execCallback(null, json);
            }
          }
        }catch(e){
          // これがベストなのか分からないが、とりあえずthrowする.
          console.error("JSON parse error : " + e);
          throw e;
        }

それでこの時のmsgがどんな状態かというと

{"event":"ai","data":{"K","port":0,"val":46}}

正しい状態はこっち

{"event":"ai","data":{"msg":"OK","port":0,"val":46}}

dataが壊れちゃってます。
この現象、普通にnode-serialport使っているときによく見ました。
こうならないようにちゃんとクローズ処理を入れないといかんですね。


おわりに

やはりCを使ってArduinoを制御するよりもJS使ったほうがお手軽です。
やり残している終了処理はどうしたものか悩ましい。
トグルスイッチを入れてON、OFFできるようにしたいところだけど、
どう回路作ればいいのかわからないという・・orz

Arduinode使ったプログラミング(というか電子工作プログラミング)
は大きく分けて3つ山がありまして、

  • Analog IO / Digital IO
  • Streaming API
  • External Interrupt API

2つはもう試してみたけど、最後の割り込みの使い方がまだよくわからない。
次はこれを使ってみる。