MQTTを利用して、家の外からエアコンを操作できるようにしてみました
この季節、外から家に帰って暖房をつけてもリビングが温まるまで時間がかかって辛いことが多いです🥶。前はリビングが小さくてすぐ暖まったんですが今の家では我慢できない…
ということで遠隔にエアコンを操作できるようにして、外からエアコンのスイッチを入れれようにしてみました。
全体構成
作るんだったら、今後温度計やカメラなどつけた時も汎用的に作れるようにしてみようと思い、最終的には以下の構成になりました。
赤外線リモコンの設定
ラズパイからエアコンに赤外線を飛ばすリモコンが必要です。
自分でブレッドボードに赤外線LEDを埋め込んで操作するのでもよいのですが、3年前に組んだときには電流の強さ?が足りなくてまともに使えなかった(赤外線が1mも届かないしLEDの向きを正しく対象に向け流必要があった)のでそれもなし。
そこでUSBに接続するタイプを探すと、irmagicianが有名のようなんですが、価格が4500円ほどと結構高めなので、代わりにBitTradeOneから発売されているAD00020P USB接続 赤外線リモコンを購入しました。アダプタがminiBというあまり使わないタイプなので注意してください、届いた後に気づきました。。
ラズパイでこのリモコンが動くかどうか分からなかったのですが、amazonのレビューにライブラリを利用して動作した報告があったのでそれだけを信じましたw
ライブラリはこちらで公開されています
導入手順はこんな感じ。
pi@raspberrypi:~ $ sudo apt-get update
pi@raspberrypi:~ $ sudo apt-get install libusb-1.0-0-dev
pi@raspberrypi:~ $ git clone https://github.com/kjmkznr/bto_ir_cmd.git
pi@raspberrypi:~ $ cd bto_ir_cmd
# warningが出るが無視
pi@raspberrypi:~/bto_ir_cmd $ make
cc -c bto_ir_cmd.c
bto_ir_cmd.c: In function ‘open_device’:
bto_ir_cmd.c:80:3: warning: ‘libusb_set_debug’ is deprecated: Use libusb_set_option instead [-Wdeprecated-declaration]
libusb_set_debug(ctx, 3);
^~~~~~~~~~~~~~~~
In file included from bto_ir_cmd.c:6:
/usr/include/libusb-1.0/libusb.h:1300:18: note: declared here
void LIBUSB_CALL libusb_set_debug(libusb_context *ctx, int level);
^~~~~~~~~~~~~~~~
cc -Wall -o bto_ir_cmd bto_ir_cmd.o -lusb-1.0
# 動作確認
pi@raspberrypi:~/bto_ir_cmd $ ./bto_ir_cmd -h
usage: bto_ir_cmd <option>
-r Receive Infrared code.
-t <code> Transfer Infrared code.
-e Extend Infrared code.
Normal: 7 octets
Extend: 35 octets
キャプチャするときはこんな感じ
pi@raspberrypi:~/bto_ir_cmd $ sudo ./bto_ir_cmd -e -r
Extend mode on.
Receive mode
Received code: 02B800603813012D005000000015130000000008050006000071000000000000000000
ファイルに固めて
pi@raspberrypi:~/bto_ir_cmd $ echo 02B800603813012D005000000015130000000008050006000071000000000000000000 > codes/air_hot
いつでも送信できるようになりました
pi@raspberrypi:~/bto_ir_cmd $ sudo ./bto_ir_cmd -e -t `cat ./codes/air_hot`
接続方法
家の外のネットワークから、家の中のラズパイに命令を送る必要があります。
HTTPSでの通信を考えると、家のネットワークへのグローバルな固定IPアドレスが提供されていれば良いのですが、月額1000円の有料オプションだったので諦めました
そこで、グローバル固定IPアドレスがなくても双方向通信が可能なMQTTを使ってみることにしました。
MQTTについては以下の記事がとても分かり易かったです。
MQTT はメッセージング・キューとはまったく関係がなく、パブリッシュ/サブスクライブ・モデルに従っています。2014 の後半になって MQTT は正式に OASIS オープン・スタンダードになりました。現在は、よく使われているプログラミング言語で、さまざまなオープンソース実装を使用して MQTT がサポートされるようになっています。
https://www.ibm.com/developerworks/jp/iot/library/iot-mqtt-why-good-for-iot/index.html?lnk=hm
MQTTは
- publisher
- subscriber
- broker
の3要素で構成されていて、今回の場合は、おうちダッシュボードからpublishしたトピックが、brokerを経由して、それをsubscribeしていたラズパイに送信されるようにします
broker(beebotte)
publishとsubscribeを扱う重要な要素がbrokerなのですが、brokerを立てるのにあまりお金をかけたくなかったのでbeebotteと言うサービスを利用しました
ちまたの記事では、herokuにCloudMQTTアドオンを載せれば無料で立てれる と書かれていましたが、2021/01/16現在では在庫切れ?らしくできませんでした
Item could not be created:
An error was encountered when contacting the add-on partner to create cloudmqtt:cat: Plan is out of stock
beebotteでは、若干の利用制限はあるものの無料でbrokerを建てられます。
細かい利用方法は以下のgistがとても参考になりました
https://gist.github.com/yoggy/28196ba084f9c406c75967289fbb3dca
subscribe(ラズパイ)
ラズパイでは、brokerに対してsubscribeし、メッセージが来ると先ほどの赤外線リモコンから赤外線を飛ばすコマンドを実行する必要があります。mosquitto_sub(CLIのMQTTクライアント)でもできそうな雰囲気なのですが、シェルの理解が浅いのでnode.jsで実装することにしました。
MQTTクライアントを実装する場合、Node.jsだとmqttというライブラリおよびそれをPromiseに対応したasync-mqttを利用でき簡単に実装できておすすめです
import { connect } from "async-mqtt";
const { exec } = require("child_process");
const formatDate = (date: Date) => {
return `${
date.getMonth() + 1
}月${date.getDate()}日 ${date.getHours()}:${date.getMinutes()}`;
};
const client = connect("mqtt://mqtt.beebotte.com", {
port: 1883,
username: process.env.MQTT_USERNAME,
password: "",
});
client.on("connect", () => {
/**
* 疎通確認
*/
const intervalId = setInterval(() => {
client.publish("my_house/pi_connected", formatDate(new Date()));
}, 60 * 1000);
/**
* 各種subscribe
*/
client.subscribe("my_house/air_off");
client.subscribe("my_house/air_hot");
client.subscribe("my_house/air_cool");
client.on("message", handleMessage);
});
const COMMAND = (topic: string) => `sudo ~/bto_ir_cmd/bto_ir_cmd -e -t \`cat ~/bto_ir_cmd/codes/${topic.replace("my_house/", "")}\``
const handleMessage = (topic, message) => {
console.log("topic", topic);
// topicをもとに発火
exec(
COMMAND(topic),
(err, stdout, stderr) => {
if (err) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
}
);
};
自動実行
ラズパイ起動時に自動実行されるように、serviceにしておきます
[Unit]
Description=mqtt
After=network.target
StartLimitIntervalSec=30
[Service]
Type=simple
Restart=always
RestartSec=10
User=pi
ExecStart=yarn raspberrypi
StandardOutput=inherit
StandardError=journal
Environment="MQTT_USERNAME=XXXXXXXXXXX"
WorkingDirectory=/home/pi/mqtt-house-subscriber
[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable mqtt
$ sudo systemctl start mqtt
# ログ確認
$ journalctl -u mqtt.service
publish
外からpublishを行うため、webで操作できる画面およびサーバーが必要になります。
実は、beebotteでpublishおよびsubscribeができるダッシュボードが提供されているのでそれを利用しました。
ただ、ダッシュボードをpublicにする設定をしているのにログインしていないとpublishができないのは少し不便です
完成
PCまたはスマホからダッシュボードのボタンを押してpublishされ、それが無事にbrokerを経由し家のラズパイに届き、そこから指定した赤外線のコードを送信することができました、
完成形はこんな感じです↓
今後、温度計をつけて自動的に温度を調整できるようにしたり、カメラをつけて動体検知を動かして自前SECOMを作ったり、夢が広がりますね!!
ただ、beebotteのダッシュボードは、publishするにはログインが必要で不便なので、また気が向いたら自前でダッシュボードを作ろうかなぁと考えてます
利用したコードは以下の通り
- https://gitlab.com/morifuji/mqtt-house-subscriber
- ラズパイで起動させているNode.jsのコード
- Typescriptで書きました
- ラズパイで起動させているNode.jsのコード
- https://github.com/diggymo/bto_ir_cmd
- kjmkznr/bto_ir_cmdをforkして家で利用するコードを
codes
ディレクトリに配置しました
- kjmkznr/bto_ir_cmdをforkして家で利用するコードを