RaspberryPiでスマートホーム 〜温度計で部屋の温度を測る〜
MQTTとラズパイを使った、スマートホームを作成中です。
今回はラズパイで部屋の温度を測定できるようにします。今までは一方的にラズパイへコマンドを送るだけでしたが、今回はラズパイ側から送信したデータを受信する処理が加わります。
必要なもの
ラズパイにつないで使える温度計をアマゾンで購入します。私はこれを購入しました。
レビュー見てると以下の欠点があるようです。
- 付属CDのWindowsのソフトがダメ
- 温度に誤差がある
今回はラズパイなのでWindowsのソフトの問題に関しては使わないので関係なし。温度の誤差は…私は正確な温度計を持ってないので、どうせ比べられないから問題なし。ということで、これに決定しました。この機器でこのくらいの温度の時は肌寒いな、このくらいだと暑いな、程度が分かればいいです。
ちなみに、私はラズパイをダイソーのワイヤーネットで吊るしてるので、こんな感じで設置しました。
レビュー見てると、ラズパイ本体に近いところに設置すると温度があがるそうです。なので、延長ケーブルで少し離しています。
準備
まずは、これをラズパイで使えるようにします。
事前準備としてlibusb-devが必要なので、apt-getしておきます。
sudo apt-get install libusb-dev
次に有志の方が作ってくれたソースをダウンロードします。
gitがある場合は、
git clone https://github.com/bitplane/temper.git
ない場合は、
の”Clone or download”の”Download zip”で落として、展開します。
ソースを取得したら、
make
sudo make install
で、/usr/local/bin/temperにインストールされます。
実行して見ます。
sudo temper
すると…
29-Nov-2018 07:12,20.688406
でました!標準出力に現在の温度が出力されます。なので、標準出力をカンマで区切って後半部分を切り出せば温度がとれます。時間はUTCですが、使用しないので問題なしです。
ただしsudoしないと使えません。それだとスクリプトやcronから呼び出すのに不便なので、stickyビットたてときます。
sudo chmod u+s /usr/local/bin/temper
これで誰が実行しても、rootとして実行されます。
作成中のスマートホームに組み込んで行きます。
スマートホームに組み込む ~ラズパイ~
まずは受信側。resourceは”temperature”、commandは”get”、パラメータはなし、にしました。今後、測定する箇所が増えたらパラメータには”room”といった感じでセットするといいかもしれません。
ソースです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import paho.mqtt.client import ssl import subprocess import json port = 8883 topic_to = "homeapp/to" topic_from = "homeapp/from" endpoint = "(エンドポイント)" cert = "certificate.pem.crt" key = "private.pem.key" rootCA = "VeriSign.pem" macaddr = {"pc":"xx:xx:xx:xx:xx:xx"} def on_connect(client, userdata, flags, respons_code): client.subscribe(topic_to) def on_message(client, userdata, msg): data = json.loads(msg.payload.decode("utf-8")) if (data["resource"] == "pc" or data["resource"] == "mac"): if (data["command"] == "power" and data["parameters"] == "on"): subprocess.call("/bin/bash wol.sh " + macaddr[data["resource"]], shell=True) elif (data["resource"] == "temperature"): if (data["command"] == "get"): p = subprocess.Popen("/usr/local/bin/temper", shell=True, stdout=subprocess.PIPE) p.wait() buf = p.stdout.readlines() if (len(buf) > 0): arr = buf[0].decode().split(",") if (len(arr) > 1): temperature = arr[1].strip() data["response"] = {} data["response"]["value"] = temperature client.publish(topic_from, json.dumps(data)) else: if (data["resource"] == "aircon"): option = "--" + data["command"] + " " + str(data["parameters"]) + " " if ("extras" in data and isinstance(data["extras"], list)): for item in data["extras"]: option += "--" + item["command"] + " " + str(item["parameters"]) + " " subprocess.call("python daikin.py " + option, shell=True) par = "daikin" else: par = data["resource"] + "_" + data["command"] + ("_" + str(data["parameters"]) if data["parameters"] else "") subprocess.call("/bin/bash ir.sh " + par, shell=True) if __name__ == '__main__': client = paho.mqtt.client.Client() client.on_connect = on_connect client.on_message = on_message client.tls_set(rootCA, certfile=cert, keyfile=key, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None) client.connect(endpoint, port=port, keepalive=60) client.loop_forever() |
resourceが”temperature”のところが今回の追加部分です。temperを起動して標準出力を取得、温度を切り出してトピックに投げます。resourceとcommandは、受け取ったものをそのまま返してます。
スマートホームに組み込む 〜WEB〜
まず画面(html)に温度をリクエスト&表示する箇所を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>スマートホーム</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script> <script src="https://sdk.amazonaws.com/js/aws-sdk-2.171.0.min.js" type="text/javascript"></script> <script src="SigV4Utils.js" type="text/javascript"></script> <script src="homeapp.js" type="text/javascript"></script> </head> <body> <input type="hidden" id="topic" value="homeapp/to"> 【照明】<br> 電源: <input type="radio" name="light_power" value="on" data-resource="light" data-command="power" onclick="javascript:send(this)">オン <input type="radio" name="light_power" value="off" data-resource="light" data-command="power" onclick="javascript:send(this)">オフ <br> <br> 【PC】<br> 電源: <input type="radio" name="light_power" value="on" data-resource="pc" data-command="power" onclick="javascript:send(this)">オン <input type="radio" name="light_power" value="off" data-resource="pc" data-command="power" onclick="javascript:send(this)">オフ <br> <br> 【MAC】<br> 電源: <input type="radio" name="light_power" value="on" data-resource="mac" data-command="power" onclick="javascript:send(this)">オン <input type="radio" name="light_power" value="off" data-resource="mac" data-command="power" onclick="javascript:send(this)">オフ <br> <br> 【エアコン】<br> 電源: <input type="radio" name="aircon_power" value="off" data-resource="aircon" data-command="power" onclick="javascript:send(this)">オフ <input type="radio" name="aircon_power" value="on" data-resource="aircon" data-command="power" onclick="javascript:send(this)">オン <br> モード: <input type="radio" name="aircon_mode" value="cold" data-resource="aircon" data-command="mode" onclick="javascript:send(this)">冷房 <input type="radio" name="aircon_mode" value="heat" data-resource="aircon" data-command="mode" onclick="javascript:send(this)">暖房 <br> 風量: <input type="radio" name="aircon_fan" value="0" data-resource="aircon" data-command="fan" onclick="javascript:send(this)">しずか <input type="radio" name="aircon_fan" value="1" data-resource="aircon" data-command="fan" onclick="javascript:send(this)">1 <input type="radio" name="aircon_fan" value="2" data-resource="aircon" data-command="fan" onclick="javascript:send(this)">2 <br> スイング: <input type="radio" name="aircon_swing" value="on" data-resource="aircon" data-command="swing" onclick="javascript:send(this)">オン <input type="radio" name="aircon_swing" value="off" data-resource="aircon" data-command="swing" onclick="javascript:send(this)">オフ <br> 温度: <input type="radio" name="aircon_temp" value="25" data-resource="aircon" data-command="temperature" onclick="javascript:send(this)">25 <input type="radio" name="aircon_temp" value="26" data-resource="aircon" data-command="temperature" onclick="javascript:send(this)">26 <input type="radio" name="aircon_temp" value="27" data-resource="aircon" data-command="temperature" onclick="javascript:send(this)">27 <input type="radio" name="aircon_temp" value="28" data-resource="aircon" data-command="temperature" onclick="javascript:send(this)">28 <input type="radio" name="aircon_temp" value="29" data-resource="aircon" data-command="temperature" onclick="javascript:send(this)">29 <input type="radio" name="aircon_temp" value="30" data-resource="aircon" data-command="temperature" onclick="javascript:send(this)">30 <br> 入タイマー: <input type="radio" name="aircon_delay_on" value="1" data-resource="aircon" data-command="delay" onclick="javascript:send(this)">1 <input type="radio" name="aircon_delay_on" value="2" data-resource="aircon" data-command="delay" onclick="javascript:send(this)">2 <input type="radio" name="aircon_delay_on" value="3" data-resource="aircon" data-command="delay" onclick="javascript:send(this)">3 <br> 切タイマー: <input type="radio" name="aircon_delay_off" value="-1" data-resource="aircon" data-command="delay" onclick="javascript:send(this)">1 <input type="radio" name="aircon_delay_off" value="-2" data-resource="aircon" data-command="delay" onclick="javascript:send(this)">2 <input type="radio" name="aircon_delay_off" value="-3" data-resource="aircon" data-command="delay" onclick="javascript:send(this)">3 <br> タイマーキャンセル: <input type="radio" name="aircon_delay_cancel" value="0" data-resource="aircon" data-command="delay" onclick="javascript:send(this)">キャンセル <br> <br> 【温度】<br> <input type="text" id="temperature" name="temperature" value=""> <input type="button" value="取得" data-resource="temperature" data-command="get" onclick="javascript:send(this);"> </body> </head> |
温度リクエスト用のボタンと表示用のテキストボックスを追加しました。
JavascriptではOnRecv関数を追加しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
var credentials = {}; credentials.accessKeyId = "(アクセスキー)"; credentials.secretAccessKey = "(シークレットキー)"; var requestUrl = SigV4Utils.getSignedUrl('(エンドポイント)', 'us-west-2', credentials); var clientId = "homeapp"; var client = new Paho.MQTT.Client(requestUrl, clientId); var connectOptions = { useSSL: true, timeout: 3, mqttVersion: 4, onSuccess: function(){ client.subscribe("homeapp/from"); } }; client.onMessageArrived = onRecv; client.connect(connectOptions); function buildPayload(resource, command, parameters) { payload = { "resource" : resource, "command" : command, "parameters" : parameters }; return JSON.stringify(payload); } function send(ele) { var resource = ele.getAttribute("data-resource"); var command = ele.getAttribute("data-command"); var parameters = ele.value; var payload = buildPayload(resource, command, parameters); var topic = document.getElementById("topic").value; console.log('topic:"' + topic + '" payload:' + payload); var msg = new Paho.MQTT.Message(payload); msg.destinationName = topic; client.send(msg); } function onRecv(message) { var j = JSON.parse(message.payloadString); if (j.resource == "temperature" && j.command == "get") { var e = document.getElementById("temperature"); e.value = j.response.value; } } |
OnRecv()関数はメッセージが来た時に呼び出されます。関数ではresourceが”temperature”、commandが”get”のJSONを受け取ったら、温度をテキストボックスに表示します。
また、MQTTサーバーへの接続完了時に、トピック”homeapp/from”をサブスクライブするようにしました。
スマートホームに組み込む 〜Alexa〜
ちょっと長くなりそうなので、別記事にします。
感想
温度の測定自体は、この機器と有志の方が作ってくれたソフトのおかげで簡単でした。
今まではラズパイに送信するのがメインでしたが、初めてラズパイ側から情報を返すようにしました。MQTTはメッセージを送りつけるだけで、HTTPみたいに投げたメッセージに対するレスポンスを受ける、ということができないので(1対多ですしね)、返事を受け取るためにはトピックをサブスクライブしないといけません。返信用のトピックhomeapp/fromを作って、そこをサブスクライブして返事を待つようにしました。きちんとやるならメッセージにIDつけて、自分が送ったメッセージに対する返事なのかを判断したり、タイムアウト処理などが必要になってくると思います。
今のところ、リクエストしたときのみラズパイが返事してブラウザで見える仕組みです。今後、ラズパイ側でcron使って1分ごとに監視して温度によってエアコンを自動でつけたり、異常な温度になったらメール送信・Slack・電話をかける、なども簡単にできそうです。