node.jsを使ってWebからエアコンを遠隔操作できるようにしてみた

  • 89
    いいね
  • 0
    コメント

はじめに

この記事はGizumoエンジニア Advent Calendar 2015の2日目の記事です。

株式会社Gizumoというまだ出来て半年という若い会社でWebアプリエンジニアやってる@suzumiです。
今回はraspberry piを使って電子工作に手を出してみました。
11月頭にあった東京Node学園祭でハードウェア+node.jsのセッションがあり、それを聞いて心ときめいたのがキッカケです。

raspberry piには前々から興味が合ったのですが、こちらの記事を見てさらに心ときめき、nodeを使ってwebからエアコンを操作してみようと思い立ったわけです。

完成形

というわけで早速完成形を見てみましょう。

これがWebのUIです。
CSSフレームワークとしてmaterializeを使用しています。

aircon.gif

アクセス時にエアコンの状態を取得し、それに応じてボタンを押すとraspberry piから赤外線が飛んでon,offを切り替えるようになっています。

Photo 12-1-15, 01 15 04.jpg

実現方法

これをどのように実現しているかというと、node.js + Firebaseを使用して実現しています。
Firebaseというのはリアルタイムの双方向通信を簡単に実現することができるBaasです。
wantedlyさんが展開しているチャットサービスsyncが(確か)Firebaseを使って実装されているとの記事をどこかで読んで気になっていたので今回採用してみました。

似たようなサービスにPubNubPusherなどがあるようです。

話を戻しますが、静的ページからon/offボタンを押すとFirebaseへPublishされます。
そしてraspberry pi上のnode.jsがSubscribeしているので即座にon/offが通知されエアコンに対して赤外線を飛ばす、という方法で実現しています。

買ったもの

Amazonで購入

秋月電子で購入

実際に使ったやつ

  • ブレッドボード・ジャンパーワイヤ(オス-メス) 15cm(赤)
  • ブレッドボード BB-801
  • LED光拡散キャップ(5mm) 青 (50個入)
  • 高輝度5mm青色LED 5cd60度 OSUB5161A-PQ(10個入)
  • 5mm赤外線LED OSI5LA5113A (10個入)
  • 赤外線リモコン受信モジュール PL-IRM2161-XD1(2個入)

買ったけど(まだ)使ってないやつ

  • トランジスタ2SC1815GR 60V150mA (20個入)
  • 温湿度センサ モジュール AM2320
  • カーボン抵抗(炭素皮膜抵抗) 1W39Ω (100本入)
  • カーボン抵抗(炭素皮膜抵抗) 1/4W 2kΩ (100本入)

その他必要なもの

  • microSDカード(意外と忘れがち。こいつがハードディスクの役目になります)
  • キーボード
  • マウス
  • モニタ

microSDカードにRaspbianというラズパイ向けにカスタマイズされたOSが公式サイトで配布されているのでそれをインストールします。
microSDカードにOSのイメージファイルをコピーするのにmicroSDカードリーダー&ライターが必要なので忘れないようにしましょう。
iMac使ってる人は背面にSDカードスロットがあるのでmicroSD→SDカード変換アダプタがあれば書き込むことができます。

nodeからLEDを点灯してみる

ラズパイの準備ができたらターミナルを開きます。
とりあえずrootユーザーに変身しましょう。

$ sudo su -

配線はこのようにつないでいます。
led_kairo.jpg

以下のコマンドでLEDを点灯してみます

$ echo 25 > /sys/class/gpio/export # GPIO25ピンを使う
$ echo out > /sys/class/gpio/gpio25/direction # GPIO25を出力用設定
$ echo 1 > /sys/class/gpio/gpio25/value # 点灯
$ echo 0 > /sys/class/gpio/gpio25/value # 消灯
$ echo 25 > /sys/class/gpio/unexport # GPIO25のファイルを終了

うまくいくとLEDが点灯します。

同様にnodeでLEDを点灯させます。

先にnodeをインストールしましょう。
今回はnodebrewから(現時点で最新の)v5.1.0をインストールします。

2秒後にLEDが消灯するようにするプログラムです。

led_on.js
var fs = require('fs');

var dir = '/sys/class/gpio';
var gpio25 = dir + '/gpio25';

fs.writeFileSync(dir + '/export', 25); 
fs.writeFileSync(gpio25 + '/direction', 'out');
fs.writeFileSync(gpio25 + '/value', 1);

setTimeout(function(){
  fs.writeFileSync(gpio25 + '/value', 0);
  fs.writeFileSync(dir + '/unexport', 25);
}, 2000);

$ node led_on.jsで実行すれば点灯するはずです。

次にLEDをチカチカ点滅させます。
これをLチカという呼ぶそうです。(電子工作でのHello worldらしいです)

500ミリ秒、つまり0.5秒ごとに0と1を交互に送り点滅させます。

led_onoff.js
var fs = require('fs');

var dir = '/sys/class/gpio';
var gpio25 = dir + '/gpio25';

fs.writeFileSync(dir + '/export', 25); // 25pin
fs.writeFileSync(gpio25 + '/direction', 'out');

var cnt = 0;

setInterval(function(){
  cnt++;
  fs.writeFileSync(gpio25 + '/value', cnt % 2);
},500);

エアコンを操作してみる

LEDをnodeからチカチカさせることができたので実際にエアコンを操作してみたいと思います。
基本的な流れはこちらの記事を参考にしていったんですが、
この通りだと動かないことがちょくちょくあったので若干内容が違います。

赤外線の信号を学習する

lircというLinuxで赤外線の信号を受信したり発信したりするライブラリをインストールします。

$ sudo apt-get install lirc

/boot/config.txtにdtoverlayの項目がコメントアウトになってると思いますがコメントアウトを外して、続けてlircへ入出力用のgpioを記述します。
24ピンが入力用(受信側)、25ピンが出力用(送信側)に設定しています。
何が受信、送信用なのかというと、赤外線リモコンからの信号を学習するための入力用(GPIO24)、逆にエアコンに向かって信号を飛ばすのを出力用(GPIO25)としています。

/boot/config.txt
- #dtoverlay=lirc-rpi
+ dtoverlay=lirc-rpi, gpio_in_pin=24 gpio_out_pin=25

/etc/lirc/hardware.confを編集します。

/etc/lirc/hardware.conf
# 変更する項目
LIRCD_ARGS="--uinput"
LOAD_MODULES=true
DRIVER="default"
DEVICE="/dev/lirc0"
MODULES="lirc_rpi"

/etc/modulesに追記し、モジュールを追加します。

(中略)
i2c-dev
lirc_dev # 追加

$ shutdown -r nowで一旦再起動します。
再起動したらデバイスファイルができているか確認します。

$ ls -l /dev/lirc*
crw-rw---T 1 root video 245, 0 11月 29 06:34 /dev/lirc0

次にGPIOのアサインを確認します

$ sudo mount -t debugfs debugfs /sys/kernel/debug
$ sudo cat /sys/kernel/debug/gpio
GPIOs 0-53, bcm2708_gpio:
 gpio-24  (lirc_rpi ir/in      ) in  hi
 gpio-25  (lirc_rpi ir/out     ) in  lo
 gpio-47  (led0                ) out lo

下図のように配線します。
(赤外線リモコン受信モジュールのパーツ素材がなかったので別のものになってますが脳内変換してください)
3本のうち、左足が入力用(GPIO24)、真ん中がGNDピン(電気を流すアースの役割)、右足が3.3Vの電源ピンへ繋がっています。
赤外線リモコンモジュール.jpg

赤外線リモコン受信モジュールの動作確認のためlircを停止します。

$ /etc/init.d/lirc stop

以下のコマンドを実行した後、赤外線リモコン受信モジュールに対してエアコンのリモコンのボタンをテキトーに押します。
受信できていれば文字と数値がバァーっと出てきます。

$ mode2 -d /dev/lirc0
pulse 1444335
space 3458
pulse 8787
space 535
pulse 454
space 121
pulse 1322
space 932
pulse 32

では実際に赤外線の信号を記録しましょう。
それぞれコマンドを実行した後にリモコンのボタンを押します。

$ mode2 -d /dev/lirc0 | tee AIRON
(エアコンをonにするボタンを押す → Ctrl+C)

$ mode2 -d /dev/lirc0 | tee AIROFF
(エアコンをoffにするボタンを押す → Ctrl+C)

おそらく今いるディレクトリにAIRONAIROFFの2つのファイルができていると思います。
中身をvimか何かで覗いて見るとさっき動作確認した文字と数値がずらずらと書かれています。
この数値だけを設定ファイルに書く必要があるのですが、300行近くあるので面倒くさいですね。
フォーマットして数値だけを取り出しましょう。

infrared-parse.js
var fs = require('fs');
var rs = fs.createReadStream('./AIRON'); // 読み込むファイル名は適宜変えてください
var readline = require('readline');

var rl = readline.createInterface(rs, {});
var res;
rl.on('line', function(line){
  var line = line.split(" ").slice(1, 2);
  res += line + " ";
});

setTimeout(function(){
  console.log(res);
}, 3000);

以下のコマンドで実行すると数値だけが出力されていると思います。
最初のundefined1916837は要りません。(リモコンのスイッチを押すまでの時間を表したものらしいです。)
次の4355〜...からコピーしてください。

$ node infrared-parse.js
undefined1916837 4355 4439 ....

/etc/lirc/lircd.confを編集します。
#UNCONFIGURED〜とコメントアウトされて書かれていますが

http://make.bcde.jp/category/27/

/etc/lirc/lircd.conf#UNCONFIGUREDというコメントが残っているとlircが動かないので注意してください。

との事なので僕は全部消して丸ごと差し替えしました。

/etc/lirc/lircd.conf
# Please make this file available to others
# by sending it to <lirc@bartelmus.de>
#
# this config file was automatically generated
# using lirc-0.9.0-pre1(default) on 2015
#
# contributed by <% YOUR NAME %>
#
# brand: <% BRAND %>
# model no. of remote control: <% MODEL No. %>
# devices being controlled by this remote: <% DEVICE %>
#

begin remote

  name  aircon
  flags RAW_CODES
  eps            30
  aeps          100

  gap          200000
  toggle_bit_mask 0x0

      begin raw_codes
    name on
4355 4439 ....

    name off
3465 1733 ....

      end raw_codes

end remote

赤外線ファイルができたので実際に赤外線を飛ばしてみましょう。
購入してきた赤外線LEDを回路に組み込みます。
出力用にGPIO25を設定したはずなので、25ピンと赤外線LEDを繋げ、どっか空いてるGNDに電気が流れるようにします。
普通にLEDをチカチカさせた時と同じ要領です。

赤外線LED追加.jpg

lircを起動スクリプトに登録して、自動起動するようしましょう。

$ sudo update-rc.d lirc defaults
$ sudo /etc/init.d/lirc start

lircの設定ファイルが正しく読み込まれているか確認します。

$ irsend LIST '' ''
irsend: aircon

$ irsend LIST aircon ''
irsend: 0000000000000001 on
irsend: 0000000000000002 off

上記の様に表示されていればOKです。

では実際に赤外線を飛ばしてみましょう。

# エアコンon!!
$ irsend SEND_ONCE aircon on

# エアコンoff!!
$ irsend SEND_ONCE aircon off

うまくいくとエアコンが動作します。
完成形の写真を見てもらうと赤外線LEDに青い帽子を被せているんですが、これはLED光拡散キャップってやつです。
拡散させることで赤外線がエアコンに届きやすくしています。
(ラズパイ設置場所からエアコンまで3m離れていても問題なく動作しました。)

Webから操作できるようにしてみる

コマンドラインからエアコンのon/offを切り替えられるようになりました。
ただ遠隔操作をしたいのでまだここで満足するわけにはいきません。
IoTなのでWebから操作できてなんぼですよね。

ということで冒頭で紹介したFirebaseを使います!
会員登録は済ませておきましょう。

会員登録したらFirebaseにログインしてアプリケーションを作成しましょう。
APP NAMEは何でもいいと思います。
URLもそのままで。
スクリーンショット 2015-12-02 1.55.45.png

アプリケーションを作ったらURLをコピっておきます。
スクリーンショット 2015-12-02 1.57.46.png

操作するUIを作ります。
このソースをコピれば見た目出来上がってます。
Firebaseのインスタンスを作るときのURLはコピっておいた自分のアプリケーションURLを貼りましょう。
index.htmlはどっかサーバーにあげておきます。
お手軽にならgithub.ioでもいいかもしれません。(セキュリティは別として。。)

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>うちコン</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.3/css/materialize.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
    <style>
        .u-mb20 {
            margin-bottom: 20px;
        }
        .gbl-nav {
            background-color: #444444;
        }
        .sub-title {
            font-size: 20px;
            border-bottom: 1px solid #ccc;
        }

    </style>
  </head>
  <body>
    <nav class="gbl-nav u-mb20">
      <div class="nav-wrapper">
        <a href="#" class="brand-logo center">うちコン</a>
        <ul class="right hide-on-med-and-down">
          <li>
            <a href="#!">エアコン</a>
          </li>
          <li>
            <a href="#!">他のなんか</a>
          </li>
        </ul>
        <ul id="slide-out" class="side-nav">
          <li>
            <a href="#!">エアコン</a>
          </li>
          <li>
            <a href="#!">他のなんか</a>
          </li>
        </ul>
        <a href="#" data-activates="slide-out" class="button-collapse">
          <i class="mdi-navigation-menu"></i>
        </a>
      </div>
    </nav>
    <div class="container">
      <div class="sub-title u-mb20">エアコン設定</div>
      <div class="u-mb20">
        <h6>
          <i class="fa fa-power-off"></i>現在のエアコンの状態は
        </h6>
        <span class="js-state-output">
          <div class="progress">
            <div class="indeterminate"></div>
          </div>
        </span>
      </div>
      <div class="row u-mb20">
        <div class="col s6">
          <a class="waves-effect waves-light btn-large js-aircon-on">電源ON</a>
        </div>
        <div class="col s6">
          <a class="waves-effect waves-light btn-large js-aircon-off">電源OFF</a>
        </div>
      </div>
    </div>
    <script src="https://cdn.firebase.com/js/client/2.1.1/firebase.js"></script>
    <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.3/js/materialize.min.js"></script>
    <script>
        $('.button-collapse').sideNav();

        var myFirebaseRef = new Firebase("https://[YourApplication].firebaseIO.com/");

        // Subscriber
        myFirebaseRef.child("messages").on("value", function(snapshot) {

            var state;
            if (snapshot.val()) {
                state = "<h5 class='center'><i class='fa fa-play-circle-o'></i>稼働中です</h5>";
                $('.js-aircon-on').addClass('disabled');
                $('.js-aircon-off').removeClass('disabled');
            } else {
                state = "<h5 class='center'><i class='fa fa-stop-circle-o'></i>稼働していません</h5>";
                $('.js-aircon-off').addClass('disabled');
                $('.js-aircon-on').removeClass('disabled');
            }

            $('.js-state-output').html(state)
        });

        // Publisher
        $('.js-aircon-on').click(function() {
            myFirebaseRef.set({
                messages: true
            });
        });
        $('.js-aircon-off').click(function() {
            myFirebaseRef.set({
                messages: false
            });
        });

    </script>
  </body>
</html>

ラズパイ上のnode側でSubscribeしてあげます。

$ npm i firebase --save
air-conditioner.js
var Firebase = require("firebase");
var exec = require("child_process").exec;

var myFirebaseRef = new Firebase("https://[YourApplication].firebaseIO.com/");
var command;

myFirebaseRef.child("messages").on("value", function(snapshot){
    command = snapshot.val() ? "irsend SEND_ONCE aircon on" : "irsend SEND_ONCE aircon off";
    exec(command, function(err, stdout, stderr){
        if(!err) {
            console.log("stdout: " + stdout);
            console.log("stderr: " + stderr);
        } else {
          console.log(err);
        }
    });
});

バックグラウンドでプロセス走らせときます。

$ node air-conditioner.js &

試しにスマホからWebページにアクセスして電源onしてみましょう。
正しくPublish/Subscribeしていればエアコンがつくと思います!

帰宅中にスマホからポチッと電源をつけておけば、帰宅すると暖かい空気がお迎えしてくれるでしょう。

さいごに

Publish/Subscribeを提供しているBaaSを使うとこんな簡単にIoTが実現できてしまうんです!
5ドルのボードコンピュータ「Raspberry Pi Zero」発表」なんて記事が発表されるなど、更に色々な使い道が出てくることだと思います。
年末年始に電子工作を始めてみてはいかがでしょうか?

Happy Hardware hacking !

参考になったサイト

Raspberry PiでLED点灯(Lチカ)してみよう。
Raspberry PiでLIRCをインストールする
[Raspberry Pi]赤外線リモコンを使う
RaspberryPiでLIRCする