LoginSignup
30
30

More than 5 years have passed since last update.

【enebularの本気】enebularをフル活用して外出先から操作できるスマートエアコンを実現する

Posted at

enebularの本気

過去記事で enebular + RaspberryPi + Slack を連携させて簡単な遠隔Lチカを実現しました。

【超入門】初めてのenebular!

eneblarの始め方から、"hello,world"に"Lチカ"と簡単なチュートリアル
createproject.png

しかし、「enebularの本気はまだまだこんなもんじゃない・・・」と思い、なるべくenebularの特徴を十分に活かした開発をしてみました。

作るもの

今回は、スマホで外出先からも操作できるスマートエアコンを実現しました。
外出先から操作できるスマートエアコンがあれば家に帰る直前にエアコンをONにして、帰宅した時には部屋がポッカポカというわけです。
また、いつも持ち歩いているスマホで操作できるので、部屋のどこに置いたか忘れたリモコンを探し回る必要もありません。
私のように寒い地域 (岩手県:この記事を書いている時点で-9℃) に住む人には嬉しいデバイスだと思います。

完成したもの

で、できたものがこんな感じです。
動画で見ると地味ですが実際に動いた時は結構感動します。
AIRCON.gif

全体の構成

構成を考える

エアコンを操作したり、部屋の温度を取得したりするのはRaspberryPi Zero WH(ラズパイ)に任せます。
そして、リモコンはWebアプリとしてHerokuに作成します。

enebularを活用する

しかし、こうなってくると「enebularで作成したフローをラズパイのNode-REDにコピーして・・・」「ラズパイと連携するようにHerokuでアプリ作って・・・」と、色んなサービスやハードウェアを行ったり来たりしなければいけません。
そこで、今回ラズパイHerokuのコーディングとデプロイをenebularのみで行います。
この、作成したフローのHerokuへのデプロイAWS IoTを経由したRaspberryPiやmbedなどのハードへのデプロイが簡単にできることがenebularの特徴の一つでもあります。

準備するもの

  1. Raspberry Pi
  2. 赤外線受光モジュール( GP1UXC41QS )
  3. 赤外線LED( OSI5FU5111C-40 )
  4. 電解コンデンサ 47μF
  5. 抵抗器 130Ω 47Ω
  6. ブレッドボード + ジャンパーワイヤ

用意する部品の型番を参考までに書きましたが、他の製品でも大体動くと思います。
私は、赤外線受光モジュールは VS838 、赤外線LEDはジャンクのリモコンから抜いたものを使って今回作りましたが、一応動きました。

ラズパイで赤外線を送受信できるようにする

Raspberry Pi Zero で赤外線リモコンを作る
https://qiita.com/ww24/items/32d2a78c20e6dcc2e68c
by @ww24

大部分で、こちらの記事を参考にさせていただきました。ありがとうございます。
ラズパイで赤外線を送受信できるようにする手順は、基本的には上記の記事の通りなのですが、所々簡略化したので内容が多少ダブりますが解説します。

回路図

赤外線受信

受信部は参考記事の回路と同じです。

赤外線送信

ラズパイのセットアップ

回路が組めたら、リモコンの赤外線を学習・送信できるようにラズパイをセットアップしましょう。

lirc のインストールと設定

Linux上から赤外線の制御を簡単にできるようにする、LIRCというデーモンを入れます。

install
sudo apt update
sudo apt upgrade
sudo apt install lirc

無事インストールできたら、
lirc-rpiを有効化回路につながっているGPIOピンを設定します。
以下のコマンドで設定ファイルを開いて、

sudo vi /boot/config.txt

50行目あたりを以下のように書き換えます。

50行目あたり
# Uncomment this to enable the lirc-rpi module
# dtoverlay=lirc-rpi

50行目あたり(変更後)
# Uncomment this to enable the lirc-rpi module
dtoverlay=lirc-rpi,gpio_in_pin=24,gpio_out_pin=25

設定が完了したら、sudo reboot nowでラズパイを再起動します。
参考記事に書かれていますが、以下のことに注意が必要です。

⚠上記の設定によって lircd は自動起動するので、 systemctl enable 等は不要です。逆に他の方法で自動起動を設定してしまうことで GPIO の割り当てられるピンが変わってしまうなど不具合が起こる事もあり、注意が必要です。

リモコンの学習

以下のコマンドを実行した状態で、赤外線受光モジュールにリモコンを向けてボタンを押してみてください。

mode2 -d /dev/lirc0 | cat

出力(謎の数字列)が得られれば、赤外線を無事に受信できています!

pulse 3774
space 1858
pulse 515
space 426
pulse 569
space 1330
pulse 538
space 405
・・・

受信できていることを確認したら、lircd.confというファイルに、受信した信号を整形したものを記述して、ラズパイに記憶させる必要があるのですが・・・
今回はそこらへんの処理を全てshellスクリプトにまとめました。

赤外線信号を記憶する

【赤外線受信情報を保存するスクリプト】

read_pulse.sh
#!/bin/bash

if [ $# -ne 1 ]; then
  echo "ERROR: missing file operand"
  echo "Usage: $0 output"
  exit 1
fi

# 読み取り
echo "[Send a signal within 5 seconds!!]"
timeout 5 mode2 -d /dev/lirc0 | sed -ue '1d' | tee $1

# 整形
echo "================================="
cat $1 | awk '{if(NR % 30) ORS=" "; else ORS="\n"; print $1}' | { cat -; echo; } | tee $1

コピペして保存したら、chmod +x read_pulse.shで実行権限を付与してください。

使い方
以下のように実行して、5秒以内に赤外線受光モジュールに向けてリモコンのボタンを押せば、読み取った数値がonというファイル名で保存されます。

./parse_pulse.sh on

同じ要領で、offボタンの信号も読み取っておきましょう。

【赤外線情報を LIRC に登録するスクリプト】

mklircd.sh
#!/bin/bash

output="lircd.conf"

# ==========================

head="begin remote

  name            $1
  flags           RAW_CODES
  eps             30
  aeps            100
  gap             200000
  toggle_bit_mask 0x0

  begin raw_codes
"

# ==========================

end="  end raw_codes

end remote
"

# ==========================

if [ $# -lt 2 ]; then
  echo "ERROR: Argument is missing"
  echo "Usage: $0 name Signal_file [Signal file ...]"
  exit 1
fi

echo "$head" | tee $output

shift

for x in "$@"
do
  echo "  name $x"
  cat  "$x"
  echo
done | tee -a $output

echo "$end" | tee -a $output

上と同じく、コピペして保存したらchmod +x mklircd.shで実行権限を付与してください。

使い方
以下のように、./mklircd.sh [リモコンの名前] [登録したい信号ファイル] [登録したい信号ファイル] ...という形でコマンドを実行するとlircd.confというLIRCの設定ファイルを自動で生成してくれます。

./mklircd.sh AIR on off

設定を反映

設定ファイルがうまく生成されたら、以下のコマンドで設定を適用します。

sudo cp lircd.conf /etc/lirc/lircd.conf

設定ファイルを適用したら、/etc/lirc/hardware.confを以下のように変更。

# Run "lircd --driver=help" for a list of supported drivers.
DRIVER="UNCONFIGURED"
# usually /dev/lirc0 is the correct setting for systems using udev
DEVICE=""
MODULES=""

# Run "lircd --driver=help" for a list of supported drivers.
DRIVER="default"
# usually /dev/lirc0 is the correct setting for systems using udev
DEVICE="/dev/lirc0"
MODULES="lirc_rpi"

以上が完了したら、LIRC を再起動して設定を反映します。

sudo systemctl restart lirc
sudo systemctl status lirc

信号の送信

以下のように、irsend LIST '' ''irsend LIST AIR ''でリモコンのボタンが登録されたか確認できます。

$ irsend LIST '' ''
irsend: AIR

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

長かったですが、やっと信号を送出します。
実行するときは赤外線LEDをエアコンの赤外線受信部に十分近づけて実行してください。

irsend SEND_ONCE AIR on

これでエアコンがONになれば成功です。
とりあえずお疲れ様です・・・

ラズパイをAWS IoTに繋いでenebularと連携する

ここの部分についてはこちらの記事にまとめています。

RaspberryPiをAWS IoTに繋いでenebularと連携する
https://qiita.com/TakedaHiromasa/items/b6828e4ac434bf99325d

いよいよenebularで開発!

ここまで下準備が長かったですがやっとフローの作成です・・・!
今回はSmartAirconというプロジェクトに Device_raspberrypi Controller_heroku という2つのフローを作成しました。

「Device_raspberrypi」 フロー

まずはDevice_raspberrypiのフローから。
スクリーンショット 2019-01-26 22.21.53.png

動きとしては以下のような感じ。

  1. MQTT"ON" or "OFF"のテキストを受信して、それぞれのケースに分岐。
  2. execirsend SEND_ONCE AIR onとかを実行。
  3. 実行エラーなら0、成功なら"ON" or "OFF"のテキストをMQTTで返す。

フローのソースは以下の通り。

Device_raspberrypi
[
    {
        "id": "c2e2e67c.0eb118",
        "type": "switch",
        "z": "e5de4f7.bd044b",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "ON",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "OFF",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 210,
        "y": 140,
        "wires": [
            [
                "41c9a9ae.849118"
            ],
            [
                "853d6803.816448"
            ]
        ]
    },
    {
        "id": "41c9a9ae.849118",
        "type": "exec",
        "z": "e5de4f7.bd044b",
        "command": "irsend SEND_ONCE AIR on",
        "addpay": false,
        "append": "",
        "useSpawn": "",
        "timer": "5",
        "oldrc": false,
        "name": "AIR_on ",
        "x": 360,
        "y": 100,
        "wires": [
            [],
            [],
            [
                "2cd16419.b2c07c"
            ]
        ]
    },
    {
        "id": "853d6803.816448",
        "type": "exec",
        "z": "e5de4f7.bd044b",
        "command": "irsend SEND_ONCE AIR off",
        "addpay": false,
        "append": "",
        "useSpawn": "",
        "timer": "5",
        "oldrc": false,
        "name": "AIR_off",
        "x": 360,
        "y": 180,
        "wires": [
            [],
            [],
            [
                "b70a29c9.1df7e8"
            ]
        ]
    },
    {
        "id": "1c821c08.b3f094",
        "type": "mqtt in",
        "z": "e5de4f7.bd044b",
        "name": "",
        "topic": "",
        "qos": "0",
        "broker": "f6b7453d.c2a968",
        "x": 90,
        "y": 140,
        "wires": [
            [
                "c2e2e67c.0eb118"
            ]
        ]
    },
    {
        "id": "2cd16419.b2c07c",
        "type": "switch",
        "z": "e5de4f7.bd044b",
        "name": "error",
        "property": "payload.code",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "0",
                "vt": "num"
            },
            {
                "t": "neq",
                "v": "0",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 490,
        "y": 100,
        "wires": [
            [
                "1c1b151b.96394b"
            ],
            [
                "6dec0c3d.e2aa34"
            ]
        ]
    },
    {
        "id": "b70a29c9.1df7e8",
        "type": "switch",
        "z": "e5de4f7.bd044b",
        "name": "error",
        "property": "payload.code",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "0",
                "vt": "num"
            },
            {
                "t": "neq",
                "v": "0",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "outputs": 2,
        "x": 490,
        "y": 180,
        "wires": [
            [
                "3c7f5948.87ea06"
            ],
            [
                "66026380.b53dcc"
            ]
        ]
    },
    {
        "id": "1c1b151b.96394b",
        "type": "template",
        "z": "e5de4f7.bd044b",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "ON",
        "x": 630,
        "y": 60,
        "wires": [
            [
                "66471905.764468"
            ]
        ]
    },
    {
        "id": "3c7f5948.87ea06",
        "type": "template",
        "z": "e5de4f7.bd044b",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "OFF",
        "x": 630,
        "y": 160,
        "wires": [
            [
                "143a4a17.b41a96"
            ]
        ]
    },
    {
        "id": "e74a2173.54aec",
        "type": "mqtt out",
        "z": "e5de4f7.bd044b",
        "name": "",
        "topic": "",
        "qos": "2",
        "retain": "true",
        "broker": "f6b7453d.c2a968",
        "x": 890,
        "y": 120,
        "wires": []
    },
    {
        "id": "1a0f4483.07de2b",
        "type": "link in",
        "z": "e5de4f7.bd044b",
        "name": "",
        "links": [
            "143a4a17.b41a96",
            "66026380.b53dcc",
            "66471905.764468",
            "6dec0c3d.e2aa34"
        ],
        "x": 795,
        "y": 120,
        "wires": [
            [
                "e74a2173.54aec"
            ]
        ]
    },
    {
        "id": "66471905.764468",
        "type": "link out",
        "z": "e5de4f7.bd044b",
        "name": "",
        "links": [
            "1a0f4483.07de2b"
        ],
        "x": 715,
        "y": 60,
        "wires": []
    },
    {
        "id": "6dec0c3d.e2aa34",
        "type": "link out",
        "z": "e5de4f7.bd044b",
        "name": "",
        "links": [
            "1a0f4483.07de2b"
        ],
        "x": 715,
        "y": 100,
        "wires": []
    },
    {
        "id": "143a4a17.b41a96",
        "type": "link out",
        "z": "e5de4f7.bd044b",
        "name": "",
        "links": [
            "1a0f4483.07de2b"
        ],
        "x": 715,
        "y": 160,
        "wires": []
    },
    {
        "id": "66026380.b53dcc",
        "type": "link out",
        "z": "e5de4f7.bd044b",
        "name": "",
        "links": [
            "1a0f4483.07de2b"
        ],
        "x": 715,
        "y": 200,
        "wires": []
    },
    {
        "id": "f6b7453d.c2a968",
        "type": "mqtt-broker",
        "z": "",
        "broker": "iot.eclipse.org",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": true,
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "willTopic": "",
        "willQos": "0",
        "willPayload": ""
    }
]

MQTT入力・出力のトピックは自分で他の人と被らなそうなトピック名をつけてください。(名前+日時とか)

デプロイ

デプロイボタンの右にある矢印を押して出てくるメニューからExport to Other Servicesを選択してAWS IoT経由でラズパイにフローをデプロイしましょう!

デプロイが完了したら、以下のようなフローを別に作ってエアコンがON/OFFするか"ON "OFF"テキストが返ってくるかテストしてみるといいでしょう。
(先ほど設定したMQTTトピックとトピック名を合わせるようにすること)
スクリーンショット 2019-01-26 22.45.55.png

「Controller_heroku」 フロー

今回は、WebアプリのUI作成にnode-red-dashboardを使いたいので、enebularのメニューからパレットの管理を開いて、ノードを追加node-red-dashboardを検索・追加します。

ノードが追加できたら以下のようにフローを組みます。

やってることはフローの形からなんとなくわかると思います。

フローのソースは以下の通り。

Controller_heroku
[
    {
        "id": "b5570c93.3f03f",
        "type": "ui_button",
        "z": "b2efaedb.75299",
        "name": "ON",
        "group": "720e73f5.43930c",
        "order": 2,
        "width": 0,
        "height": 0,
        "passthru": false,
        "label": "ON",
        "color": "",
        "bgcolor": "red",
        "icon": "",
        "payload": "ON",
        "payloadType": "str",
        "topic": "",
        "x": 90,
        "y": 140,
        "wires": [
            [
                "3ca1dc71.6ffbd4"
            ]
        ]
    },
    {
        "id": "6ab44bc.2e6a5b4",
        "type": "ui_text",
        "z": "b2efaedb.75299",
        "group": "720e73f5.43930c",
        "order": 1,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "現在の状態:",
        "format": "{{msg.payload}}",
        "layout": "row-center",
        "x": 360,
        "y": 80,
        "wires": []
    },
    {
        "id": "8cc4529a.c0c0d",
        "type": "ui_button",
        "z": "b2efaedb.75299",
        "name": "OFF",
        "group": "720e73f5.43930c",
        "order": 0,
        "width": 0,
        "height": 0,
        "passthru": false,
        "label": "OFF",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "OFF",
        "payloadType": "str",
        "topic": "",
        "x": 90,
        "y": 220,
        "wires": [
            [
                "3ca1dc71.6ffbd4"
            ]
        ]
    },
    {
        "id": "3ca1dc71.6ffbd4",
        "type": "mqtt out",
        "z": "b2efaedb.75299",
        "name": "",
        "topic": "",
        "qos": "0",
        "retain": "true",
        "broker": "76ac36e8.8ca7c8",
        "x": 330,
        "y": 180,
        "wires": []
    },
    {
        "id": "939da3fa.9c869",
        "type": "mqtt in",
        "z": "b2efaedb.75299",
        "name": "",
        "topic": "",
        "qos": "2",
        "broker": "76ac36e8.8ca7c8",
        "x": 90,
        "y": 80,
        "wires": [
            [
                "6ab44bc.2e6a5b4"
            ]
        ]
    },
    {
        "id": "720e73f5.43930c",
        "type": "ui_group",
        "z": "",
        "name": "Default",
        "tab": "4a68af23.3436b",
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "76ac36e8.8ca7c8",
        "type": "mqtt-broker",
        "z": "",
        "name": "",
        "broker": "iot.eclipse.org",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": true,
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "willTopic": "",
        "willQos": "0",
        "willPayload": ""
    },
    {
        "id": "4a68af23.3436b",
        "type": "ui_tab",
        "z": "",
        "name": "Home",
        "icon": "dashboard"
    }
]

Herokuにデプロイ

下記の公式ドキュメント通りの手順を踏むと、作成したフローをHerokuにDeployできます!

Heroku へのデプロイ
https://docs.enebular.com/ja/Deploy/DeployFlow/Heroku/

https://[Herokuアプリ名].herokuapp.com/uiにアクセスするとエアコン操作画面が出てきます。

ついに完成・・・!

最初に載せたのと同じ動画。
動くとやっぱり感動します。
AIRCON.gif

enebularの凄さ

今回は赤外線を扱うために回路作りやモジュールの設定など事前準備があったため、前半やることがみっちりでしたが、enebularでの開発に入ってからはいたってスムーズでした。
エッジデバイス(回路含め)さえ作成・設置すれば、あとは実物が手元になくてもブラウザ1つで開発・反映・確認ができるので、自分の好きな時に好きな場所から修正・検証できます。
また、今回のようにRaspberryPi , Heroku と、複数の環境をまたいで開発する場合でも、各環境用のコードがバラバラにならず、プロジェクトとしてenebular上で一括管理できるのも嬉しいポイントです。これによって共有や共同開発も簡単にできます。
実際にenebular中心の開発をしてみることで、「あらゆるデバイスとクラウドサービスを簡単に『つなぐ』、IoTオーケストレーション・サービス」と銘打っている理由がなんとなくわかった気がします。

おわりに

私は集合住宅に住んでいて、ポート開放やグローバルIPの取得ができないので、今まで家にラズパイを置いたまま外部から遠隔開発するのは絶望的だったのですが、AWSIoTとラズパイを連携したらグローバルIPがなくても遠隔デプロイできたのが地味に嬉しかったです。
ちょっと長い記事になってしまいましたが最後まで読んでいただきありがとうございました!
みなさん、良きenebularライフをー!

30
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
30