RaspberryPiでスマートホーム 〜赤外線リモコンでエアコン操作 その3〜
エアコンのコマンドを組み立ててUSB赤外線リモコンアドバンス用ファイルを出力。そのファイルを使ってbto_advanced_USBIR_cmdコマンドでエアコンを操作できるようになりました。
次は以前作った自作のスマートホームシステムに組み込みます。
受信側
ラズパイ側の受信プログラムを修正します。リソース”aircon”の追加です。
他の機器と違うところはコマンドファイルを生成するという処理が間に入ります。生成した後は同じように”ir.sh”で送信します。
あと、複数のコマンドを一度に受け付けられるように、受信するJSONに”extras”という配列を追加しています。これは冷房をつける場合に、”電源をつける”、”モードを冷房にする”、という2つのコマンドを同時に送信できるようにするためです。
1 2 3 4 5 6 7 8 |
{ 'resource': 'aircon', 'command': 'power', 'parameters': 'on', 'extras': [ {'command': 'mode', 'parameters': 'cold'} ] } |
上記のJSONを受け取ると
python daikin.py --power on --mode cold
を実行します。
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 |
import paho.mqtt.client import ssl import subprocess import json port = 8883 topic_to = "homeapp/to" topic_from = "homeapp/from" endpoint = "(エンドポイント).amazonaws.com" cert = "certificate.pem.crt" key = "private.pem.key" rootCA = "VeriSign-Class 3-Public-Primary-Certification-Authority-G5.pem" macaddr = {"pc":"70:85:C2:04:03:55"} def on_connect(client, userdata, flags, respons_code): #print('status {0}'.format(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) else: if (data["resource"] == "aircon"): option = "--" + data["command"] + " " + data["parameters"] + " " if ("extras" in data and isinstance(data["extras"], list)): for item in data["extras"]: option += "--" + item["command"] + " " + item["parameters"] + " " subprocess.call("python daikin.py " + option, shell=True) #print("python daikin.py " + option) par = "daikin" else: par = data["resource"] + "_" + data["command"] + ("_" + data["parameters"] if data["parameters"] else "") subprocess.call("/bin/bash ir.sh " + par, shell=True) #print("/bin/bash ir.sh " + par) 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() |
送信側
次は送信側です。Javascript版とAlexaのスマートホームスキル版を作ってましたので、それらを修正します。
Javascript
こちらは簡単です。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 |
<!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> 【エアコン】<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)">キャンセル </body> </head> |
こんだけ。
Alexaスマートホーム
こちらはAWSのLambdaの修正になります。が、色々と問題があります…
Alexaのスマートホームスキルには〇〇Controllerという名前でいろんな家電を操作するための機能インターフェースがあります。簡単に言うと”操作するために使える言葉”という感じでしょうか。
例えば電源のオンオフはPowerControllerを使って実現しています。スマートスキルにはThermostatControllerという温度を設定するためのインターフェースも用意されているのですが、日本語に対応してないため日本では使えません。
汎用的なインターフェースであるPercentageControllerは日本語でも使えそうですが、最後に”パーセントにして”とつけないと駄目っぽいかんじ。つまり温度を25℃にするためには…
一言だけど面倒くさいのと、ちょっと違和感が…我慢してこれで行くか、カスタムスキルを併用するか…
という訳で電源のオンオフはすぐにでもできますが、温度・風量・タイマーといった機能がそのままでは無理そうです。どうしたものか。
とりあえず電源だけ対応させます。”エアコン”、”暖房”、”冷房”の3つを追加しました。Discoveryデータを追加したのと、endpointが”暖房”、”冷房”の時はmodeコマンドも合わせて送るようにしてます。
”暖房をつけて”、”冷房をつけて”はそれぞれ電源をオンと同時にモードを切り替えます。”エアコンをつけて”は前回のモードで電源が入ります。
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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
const AWS = require('aws-sdk'); const ENDPOINT = "(AWS Iotのエンドポイント)"; exports.handler = function (request, context) { switch (request.directive.header.namespace) { case "Alexa.Discovery": if (request.directive.header.name === 'Discover') { handleDiscovery(request, context, ""); } break; case "Alexa.PowerController": handlePowerControl(request, context); break; } function handleDiscovery(request, context) { var payload = { "endpoints": [ { "endpointId": "LIGHT-001", "manufacturerName": "my-scribble.net", "friendlyName": "照明", "description": "部屋の照明", "displayCategories": ["LIGHT"], "cookie": {}, "capabilities": [ { "type": "AlexaInterface", "interface": "Alexa.PowerController", "version": "3", "properties": { "supported": [{ "name": "powerState" }], "retrievable": true } } ] }, { "endpointId": "LIGHT-001", "manufacturerName": "my-scribble.net", "friendlyName": "電気", "description": "部屋の照明", "displayCategories": ["LIGHT"], "cookie": {}, "capabilities": [ { "type": "AlexaInterface", "interface": "Alexa.PowerController", "version": "3", "properties": { "supported": [{ "name": "powerState" }], "retrievable": true } } ] }, { "endpointId": "AC-001", "manufacturerName": "my-scribble.net", "friendlyName": "エアコン", "description": "部屋のエアコン", "displayCategories": ["THERMOSTAT"], "cookie": {}, "capabilities": [ { "type": "AlexaInterface", "interface": "Alexa.PowerController", "version": "3", "properties": { "supported": [{ "name": "powerState" }], "retrievable": true } } ] }, { "endpointId": "AC-001-COLD", "manufacturerName": "my-scribble.net", "friendlyName": "冷房", "description": "部屋の冷房", "displayCategories": ["THERMOSTAT"], "cookie": {}, "capabilities": [ { "type": "AlexaInterface", "interface": "Alexa.PowerController", "version": "3", "properties": { "supported": [{ "name": "powerState" }], "retrievable": true } }, ] }, { "endpointId": "AC-001-HEAT", "manufacturerName": "my-scribble.net", "friendlyName": "暖房", "description": "部屋の暖房", "displayCategories": ["THERMOSTAT"], "cookie": {}, "capabilities": [ { "type": "AlexaInterface", "interface": "Alexa.PowerController", "version": "3", "properties": { "supported": [{ "name": "powerState" }], "retrievable": true } }, ] }, { "endpointId": "PC-001", "manufacturerName": "my-scribble.net", "friendlyName": "パソコン", "description": "部屋のパソコン", "displayCategories": ["OTHER"], "cookie": {}, "capabilities": [ { "type": "AlexaInterface", "interface": "Alexa.PowerController", "version": "3", "properties": { "supported": [{ "name": "powerState" }], "retrievable": true } } ] }, ] }; var header = request.directive.header; header.name = "Discover.Response"; context.succeed({ event: { header: header, payload: payload } }); } function handlePowerControl(request, context) { var requestMethod = request.directive.header.name; var endPoint = request.directive.endpoint.endpointId; var resource = ""; var extras = []; var command = ""; var option = ""; var responseNamespace = ""; var responseName = ""; var responseResult = ""; switch (endPoint) { case "LIGHT-001": resource = "light"; break; case "AC-001": resource = "aircon"; break; case "AC-001-COLD": resource = "aircon"; extras.push({"command":"mode", "parameters":"cold"}); break; case "AC-001-HEAT": resource = "aircon"; extras.push({"command":"mode", "parameters":"heat"}); break; case "PC-001": resource = "pc"; break; } switch (requestMethod) { case "TurnOn": command = "power"; option = "on"; responseNamespace = "Alexa.PowerController"; responseName = "powerState"; responseResult = "ON"; break; case "TurnOff": command = "power"; option = "off"; responseNamespace = "Alexa.PowerController"; responseName = "powerState"; responseResult = "OFF"; break; } if (resource && command && option) { submit(resource, command, option, extras, function(){ sendResponse(responseNamespace, responseName, responseResult); }); } } function sendResponse(namespace, name, value, payload) { var contextResult = { "properties": [{ "namespace": namespace, "name": name, "value": value, "timeOfSample": "2017-09-03T16:20:50.52Z", //retrieve from result. "uncertaintyInMilliseconds": 50 }] }; var responseHeader = request.directive.header; responseHeader.namespace = "Alexa"; responseHeader.name = "Response"; responseHeader.messageId = responseHeader.messageId + "-R"; var response = { context: contextResult, event: { header: responseHeader }, payload: payload }; context.succeed(response); } function submit(resource, command, params, extras, callback) { var data = { topic: 'homeapp/to', payload: JSON.stringify({ "resource": resource, "command": command, "parameters": params, "extras": extras, }), qos: 0 }; var iotdata = new AWS.IotData({endpoint: ENDPOINT}); iotdata.publish(data, function(err, data){ if(err){ console.log("submit(): failure - " + data); } else{ console.log("submit(): success"); } if (callback) { callback(); } }); } }; |
続く…