Ratocの家電リモコンをIFTTT経由のPythonで制御


これは何?

Ratocの家電リモコンwfirexシリーズを、PythonからIFTTT経由でコントロールする方法です。
リモコンコードを直接Pythonから送信するので、IFTTT側で用意するアプレットは1つで済みます。(実質2つですが。)
リモコンコードは、スマホの家電リモコンアプリからエクスポートしてきたデータを使用します。

IFTTTのRatocの赤外線コマンド送信機能を利用していますが、以下のような注意点が記載されています

【注意】
このアクションは、メンテナンス用として残されているものです。
将来予告なしに削除される予定です。
ご使用される場合は、お客様の自己責任でお使いください。
また、使用方法や設定値についてのご質問には回答できません。
サポート外であることを予めご了承ください。

準備

IFTTTのWebhooksのkeyを取得

IFTTT上のWebhooksのページにある「Documentation」をクリックすると表示されます。
後で必要になりますので、メモしておいてください。

IFTTTのアプレットを作成

IFFFTで、「Webhooks --> Ratoc赤外線コマンド送信」というアプレットを2種類作成します。
家電リモコンの制御コードには、非ソニータイプとソニータイプがあり、IFTTTのRatoc赤外線コマンド送信は個別に準備が必要です。
最後の赤外線データの欄は、Add ingredientから、Value1を選択してください。
こうすることで、PythonからWebhook経由でリモコンコードを受け取る事ができます。
ソニーの機器でなくてもソニータイプの制御コードを使用していることがありますので、アプレットは2種類作成するようにしてください。
ここでは、
1. living_send0(非ソニー用)
2. living_send1(ソニー用)
という2つのイベントを作成しました。2つのイベント名をメモメモ

家電リモコンの制御コード取り出し

スマホの家電リモコンアプリで、制御したいwfirexを選択、「リモコンデータ受け渡し」を選択して、「エクスポート」を選択、「メールで送信」を選択して、自分宛てに送信します。
届いたメールに添付されている、export.xmlをPythonコードと同じフォルダにコピーしておきます。
複数台のwfirexがある場合は、xmlのファイル名が重複しないように変更しておきます。
保存したxmlファイル名をメモ

Pythonの準備

実行環境

Raspberry PI Zero WのPython3.5で動作を確認しています。
print文などを書き換えればPython2でも動くのではないでしょうか。

requestsはpipで取得してください。

python3のpipを実行する場合は、sudo python3 -m pip install requestsなどとしてください。(venvなどを使っていない場合)

Pythonコード

ここまででメモしておいた情報を、下記のPythonコードの「wfirex = Wfirex(〜」行の各パラメータに置き換えます。

wfirex.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import xml.etree.ElementTree as ET
import requests

class Wfirex:
    def __init__(self, xml_filename, eventid0, eventid1, ifttt_key):
        self.filename = xml_filename
        self._read_xml(self.filename)
        self._eventid0 = eventid0
        self._eventid1 = eventid1
        self._ifttt_key = ifttt_key

    def _read_xml(self, filename):
        self.tree = ET.parse(filename)
        self.root = self.tree.getroot()

    def reload_xml(self):
        self._read_xml(self.filename)

    def enum_remocon(self):
        ret_list = []
        for child in self.root:
            for child2 in child:
                for child3 in child2:
                    if child3.tag == "remoconname":
                        ret_list.append(child3.text)
        return ret_list

    def enum_button(self, remocon_name):
        ret_list = []
        for child in self.root:
            skip_node = None
            for child2 in child:
                if skip_node == True:
                    break
                for child3 in child2:
                    if child3.tag == "remoconname":
                        if child3.text == remocon_name:
                            skip_node = False
                        else:
                            skip_node = True
                            break
                    if skip_node == False:
                        for child4 in child3:
                            if child4.tag == "buttonname":
                                ret_list.append(child4.text)
        return ret_list

    def get_code(self, remocon_name, button_name):
        for child in self.root:
            skip_node = None
            for child2 in child:
                if skip_node == True:
                    break
                for child3 in child2:
                    if child3.tag == "remoconname":
                        found_button = None
                        if child3.text == remocon_name:
                            skip_node = False
                        else:
                            skip_node = True
                            break
                    if skip_node == False:
                        for child4 in child3:
                            if child4.tag == "buttonname":
                                if child4.text == button_name:
                                    found_button = True
                                else:
                                    found_button = False
                            if child4.tag == "format":
                                if found_button == True:
                                    format = child4.text
                            if child4.tag == "code":
                                if found_button == True:
                                    code = child4.text
                                    return {'format': format, 'code': code}
        logging.info("code not found.")
        return None

    def send_ifttt(self, remocon_name, button_name):
        ret = self.get_code(remocon_name, button_name)
        if ret is not None:
            payload = {"value1": ret["code"]}
            eventid = self._eventid0 if ret["format"] == "0" else self._eventid1
            url = "https://maker.ifttt.com/trigger/" + eventid + "/with/key/" + self._ifttt_key
            response = requests.post(url, data=payload)
            return response
        return None

def main():
    #事前に準備した、xmlファイル名、IFTTTイベント名(2種類)、IFTTT key名を設定する。
    wfirex = Wfirex("export.xml", "living_send0", "living_send1", "your IFTTT key")
    #リモコン名、ボタン名でsend_iftttをコールすると、数秒後にwfirexから信号が発信されます。
    ret = wfirex.send_ifttt("エアコンリビング", "運転停止")
    print(ret)
    sys.exit()

if __name__ == '__main__':
    main()

使い方と注意点

send_iftttをコールしている行の、リモコン名とボタン名を実行したい制御に書き換えて実行します。
コマンドライン引数を受け取るように変更して、CLIツールにしてシェルスクリプトから呼び出したり、
APIサーバ化すると便利ではないでしょうか。こちらではMQTTと組み合わせています。
エアコンはボタン名がスマホ上の家電リモコンアプリでの表示と異なるようなので、xmlをツールやブラウザ読むか、enum_button("リモコン名称")を実行してボタン名を確認してみてください。未検証ですが、エアコンの動作温度別のボタンも定義されているようです。

反省点

xmlのデータ検索のやり方がかっこ悪いです。findも試したけれど、しっくりきませんでした。
もしくは、xmlのデータ検索に若干時間がかかるので、サーバ化するなら読み込み時に辞書型に変換してしまった方が良いかも。