エアコンリモコンに相当するアクセサリを作ろうと思いました。これでiPhone/Macの画面に上の図のようなコントローラが現れるはずです。最初のターゲットはパナソニックのエアコンです(他のメーカーでも同様に作れるはずです)。下図のように、(右から)
- HomeKitに接続したiPhone/MacからHomebridgeのアクセサリを操作すると、
- HomebridgeのMqttthingプラグインがMQTT経由でメッセージを出し、
- このMQTTメッセージをESP32が受け取って、
- ESP32が赤外線LEDからリモコン信号を送り、エアコンをコントロール
することを目指します。今回はこのステップの最後の部分、ESP32が赤外線LEDを点灯してエアコンをコントロールする部分を作ります。
残りの右半分の部分、つまり、ESP32がMQTTに接続して、HomeKitのエアコンアクセサリを構成する部分は、また後日進めます。
赤外線リモコン自作計画
今までは、Nature Remoのスマートリモコンを1個買って、エアコンをコントロールしてました。これで何の問題もなく動いていました。Natureは、日本の会社だけあって、国内のエアコンにきめ細かく対応しているようです。
ただ、赤外線リモコン代わりなので、他の部屋のエアコンなどをコントロールしようとすると、部屋ごとにNature Remoが必要です。Nature Remoは結構高価なので、エアコンの台数だけ買うのは辛いです。Tuyaの格安スマートリモコンも試しましたが、無料だったクラウドが有料(年額45万円くらい)になってしまいました。Nature Remoのweb APIも、開発チームのご厚意で使わせてもらっている状況と言えます。回数制限もありますし、いつ閉鎖されるかもしれません。頑張って自作すれば市販品を買わなくて済み、クラウドの変更に振り回される心配も無いと考えました。
前回、ESP32に赤外線LEDをつけて、あらかじめ取得した赤外線信号パターンをを送出して、シーリング照明をオン・オフさせるデバイスを自作しましました。そしてHomeKitの照明アクセサリとして動作させました。
今回は、同じハードウェアで、今度はエアコンを操作します。
照明のon/offとかテレビのon/offのような短い固定パターンを送出するだけならば、Nature RemoにもローカルAPIがあり、クラウドに依存することなく利用できます。
エアコンの場合は設定温度などの情報を含めた赤外線パターンを送出する必要があり、長くて複雑なので、ローカルAPIを使うとしても、ある程度のプログラミングが必要です。なのでESP32に任せることにします。
赤外線情報を確認する
エアコンに送る赤外線リモコン情報は、シーリング照明(2バイトでした)やテレビなどと違い長くて複雑で変動します。今回対象とするパナソニックのリモコンの場合、送出情報は27バイトあります。
一般に赤外線リモコンは、リモコンから本体への一方向通信です。双方向通信ではないため、リモコン側は、エアコンの設定状態を知ることができません。エアコンのリモコンには、運転モード、設定温度、風量風向などが液晶表示されますが、これはエアコン本体から得た情報ではありません。リモコンで勝手に表示している情報です。これとエアコン状態を無理やり一致させるために、毎回全ての設定項目を送出しいます。なのでソフトウェア上に「リモコンオブジェクト」というような仮想のリモコンを作って、それの温度や風量を設定して、最後に全設定を赤外線送出するプログラムを作ります。
今回対象とするエアコンのリモコンは下のものです。
撮影前に掃除にはしたのですが、古くて小汚くてすみません。型番はA75C3777です。検索すると互換リモコンもあります。普及している製品かと思います。
こちらのページで詳しく解析されていました。参考にさせていただきました。少し型番が違うけど、見た目は同じリモコンです。
このリモコンは、19バイトの信号を出しているようです。実際にNature Remoを使ってon/offパターンを取得してみました。
追記:赤外線リモコン受信モジュールを使ってパターンを取得できるようにしました。データも復号できます。これを使うと楽になりました。
27度冷房、風量・風向共に自動の設定では、
{"format":"us","freq":37,"data":[3429,1782,388,481,387,1346,390,479,391,477,392,477,394,474,390,479,388,481,389,479,390,479,390,479,387,481,387,482,419,1311,393,477,390,480,391,476,390,479,390,479,387,481,388,481,390,1347,388,1345,392,1350,385,479,392,477,391,1346,389,479,390,479,388,481,389,479,390,479,390,478,420,444,393,480,387,481,386,478,392,477,392,477,391,477,390,479,389,479,390,479,389,479,390,479,387,482,387,481,389,479,388,481,389,479,388,481,387,482,387,481,386,478,392,477,392,477,391,477,391,1347,389,1348,387,481,390,479,389,479,387,482,387,481,388,10030,3428,1780,391,477,390,1347,392,477,390,479,389,479,422,442,425,443,395,477,389,479,390,479,390,478,390,479,387,482,387,1345,424,442,391,481,391,476,390,479,390,479,421,443,391,481,390,1347,420,1313,391,1346,391,477,392,477,390,1347,422,442,393,479,388,481,389,479,390,479,389,479,420,444,391,482,387,481,386,479,389,479,392,477,391,477,422,1313,391,480,389,479,388,481,387,1346,390,1348,389,480,390,478,390,479,389,1348,390,1347,388,481,387,1346,392,1345,392,477,389,479,390,479,389,479,390,479,388,481,389,479,390,479,387,482,419,1311,391,1348,392,1345,390,1347,390,1347,390,479,390,1348,386,482,387,1350,387,477,390,479,392,476,392,477,389,480,421,442,426,442,392,481,389,480,389,479,387,482,387,481,388,481,386,478,392,477,392,477,389,479,390,1348,389,1348,387,482,419,442,394,481,385,479,392,477,392,477,391,477,390,479,389,480,389,479,388,1350,387,1350,388,481,387,477,392,477,392,476,390,479,390,479,389,479,388,482,386,482,389,479,389,479,390,479,387,482,387,481,387,482,387,481,385,484,387,477,392,477,390,479,421,444,391,479,390,479,388,480,390,1348,389,479,390,479,389,479,388,481,387,482,386,482,385,479,392,477,392,477,389,1348,389,1348,390,479,389,479,388,481,388,481,387,481,386,483,388,476,392,477,392,1345,390,479,387,481,388,481,387,1350,395]}
という 結果が得られました。この情報の読み方は以下です。例えば最初の部分、
[3429,1782,388,481,387,1346,
は、
- 3429μs赤外線をonにして、1782μs赤外線をoffにして、(開始)
- 388μs赤外線をonにして、481μs赤外線をoffにして、(0)
- 387μs赤外線をonにして、1346μs赤外線をoffにする、(1)
という意味です。このデータは家電製品協会(AEHA)フォーマットと呼ばれる赤外線パターンです。425μsくらいが信号の基準長で、これをTとすると、{Tの点灯, Tの消灯}のペアが0、{T, 3T}のペアが1です。最初の長い{8T, 4T}のペアは、開始を表します。また{Tの点灯, 8ms以上の消灯}が終了を表します。またビット順は、バイトデータのLSB(最下位ビット)から先に送信されます。このことから、赤外線信号をバイト列に変換したところ、githubに書かれているA75C4269と全く同じ内容でした。
赤外線on/offパターンをグラフにすると以下のようになりました。横軸が時間、縦軸がon/offです。前半の短い信号と、後半の長い信号がセットになっていて、間に10msの空白時間が入ってます。
前半の短い信号は、8バイトで、毎回必ず同じ内容です。その内容は、
0x0220E004000000 06
です。最後のバイト(上の例では0x06)はチェックサム(その前7バイトの合計の下1バイト)です。後半の長い信号は19バイトです。その内容は、今回の場合は、
0x0220E00400 313680AF000006600000800006 88
でした。長い方の信号も、5バイトまでは同じパターンで始まってます。それに引き続く部分に、エアコンの運転モード(冷房、暖房、除湿、送風など)、設定温度、風速・風向などの情報が入っています。最後の1バイト(上では0x88)はチェックサムで、この前の18バイトの合計の下1バイトです。
この全部で27バイトの情報を適切に作成して、赤外線を送出できればエアコンを操作できます。
赤外線を出すハードウェア
これは前回の記事で作成したものと全く同じですので、そちらをご覧ください。回路図だけを再掲します。MOS FETで赤外線LEDをon/offします。
赤外線を出すプログラム例
これも前回の記事で紹介したIRremoteESP8266というライブラリを使いました。ESP8266 / ESP32用です。前回は、シーリング照明リモコンの単純なパターンをそのまま送出しました。今回は、家電製品ごとに用意されているクラスを利用します。パナソニックのクラスがありましたのでそれを使います。
ライブラリのgithubサイトは以下です。このソースは参考になりました。
このライブラリで、エアコンを赤外線コントロールする手順は以下です。
- エアコンリモコンクラスからインスタンスを作る
- そのインスタンスに運転モード、温度、風量、風向などを設定する
- そのインスタンスに発光コマンドを送る
物理的なリモコンに相当する存在(インスタンス)を作って、それを操作するというオブジェクト指向の概念です。簡単なプログラミング例で動作を確認してみましょう。
Arduinoにライブラリをインストールすると、ファイル・スケッチ例・IRremoteESP8266というメニュー項目ができています。その中に、TurnOnPanasonicACというサンプルプログラムがあります。これを動かしながら、動作を確認しました。これを元に、シンプルなプログラムを作りました。これは15秒ごとに、設定温度27度で冷房をonにするプログラムです。
/* IRremoteESP8266 for Panasonic Aircon */ #include <Arduino.h> #include <IRremoteESP8266.h> #include <ir_Panasonic.h> const uint16_t kIrLed = 4; //GPIO pin to use. IRPanasonicAc pana(kIrLed); //make an instance. void setup() { pana.begin(); Serial.begin(115200); while (!Serial); //wait for serial to connect. } void loop() { pana.on(); //power on the aircon. pana.setTemp(27); pana.setMode(kPanasonicAcCool); pana.setFan(kPanasonicAcFanAuto); pana.setSwingVertical(kPanasonicAcSwingVAuto); pana.send(); //send IR signal. delay(15000); //15 sec delay. }
最初に、IRPanasonicACクラスのインスタンスを作ります。この時に、赤外線LEDを接続したGPIOポート番号(4番が推奨)を書いておきます。
次にこのインスタンスの、on(), setTemp()メソッドを呼び出してそれぞれを設定します。またsetMode(), setFan(), setSwingVertical()メソッドを使って、運転モード、風量・風向を自動に設定しました。パラメータの値は、ir_Panasonic.hファイルの中に書いてあります。設定が終了したところで、send()メソッドで赤外線を送出します。
この結果、以下のバイト列が赤外線LEDから送出されました。
0x0220E004000000 06
0x0220E00400 313680AF00000EE00000810000 0B
赤外線パターンが実リモコンと一致しない
ESP32が出した赤外線パターンと、実リモコンのパターンは多少違ってました。27度冷房、風量・風向を自動にした場合の赤外線パターンを比較します。2つのパートでできているパターンを繋げて、27バイトの比較をします。最初の8バイト、次の5バイトは固定です。最後の1バイトはチェックサムです。それぞれのパートの最後の1バイトは、チェックサムです。
実リモコンのパターンは、以下でした。
0x0220E004000000 06 0220E00400313680AF000006600000800006 88
これに対してESP32が作ったパターンは、以下でした。
0x0220E004000000 06 0220E00400313680AF00000EE00000810000 0B
20, 21, 24, 26バイト目の合計4箇所で違いがあります。
最初に参照した解析ページによると、20, 21バイト目 (0660と0EE0) はon/offタイマーの値だそうです。実リモコンが出す値は0660で、これがon/offタイマーを使わない時の値だそうです。0EE0は実リモコンでは使われない値でした。一方、24バイト目 (80と81) の場所は実際の実リモコンでは80、ESP32では81、さらに26バイト目は、実リモコンで06、ESP32で00です。24, 26バイト目の内容は不明とのことです。
いずれも些細な違いなので、このままでも問題ないと思われますし、実際、エアコンもこれで反応します。このままでも良かったのですが、せっかくだからもう少し調整して、ESP32で作った赤外線パターンを、実リモコンと完全に一致させたいと思います。
setModel()で調整する
実リモコンには、色々なモデルがあって、多少ビット構成が違うようです。それを調整するためのメソッドに、setModel()がありました。最初はこれを使って調整できると思いました。Panasonicエアコンの細かいモデルの設定のようです。ソースコートを見ると、
- クラスのコンストラクタで、動作確認できている赤外線信号パターンを初期値として設定して、
- setModel()メソッドで、特定のバイトやビットを書き換えて、赤外線パターンの修正をしているようです。
パナソニック製品でビットパターンが違うところを修正しているのかと思います。ただ、ソースコードにあるすべてのモデルを試してみましたが、setModel()を行わない初期値が手元のリモコンに一番近い値(上の結果)でした。ということで、setModel()は省略してしまいました。
setRaw()で調整する
setModel()でパターンを一致させられなかったので、別の方法を考えました。最初は、RPanasonicACクラスのサブクラスを作って、調整するメソッドを追加しようかと思ったのですが、信号パターンの変数がprivate宣言されていてアクセスできませんでした。一方で、setRaw()メソッドが用意されていて、これで信号パターンを置き換えることができそうでした。そこで、A75Cリモコンに合わせた27バイトの信号パターンを定義して、インスタンス化した直後に、setRaw()メソッドで初期値として設定しておくことにしました。
以下のように先のプログラムを書き換えました。変更部分は太字の部分です。
/* IRremoteESP8266 for Panasonic Aircon */ #include <Arduino.h> #include <IRremoteESP8266.h> #include <ir_Panasonic.h> const uint16_t kIrLed = 4; //GPIO pin to use. IRPanasonicAc pana=IRPanasonicAc(kIrLed); //make an instance. const uint8_t kPanasonicA75CState[kPanasonicAcStateLength] = { 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x00}; void setup() { pana.setRaw(kPanasonicA75CState); pana.begin(); Serial.begin(115200); while (!Serial); //wait for serial to connect. } void loop() { pana.on(); //power on the aircon. pana.setTemp(27); pana.setMode(kPanasonicAcCool); pana.setFan(kPanasonicAcFanAuto); pana.setSwingVertical(kPanasonicAcSwingVAuto); pana.send(); //send IR signal. delay(15000); //15 sec delay. }
これで、生成される赤外線パターンは、
0x0220E004000000 06 0220E00400313680AF000006600000800006 88
となり、リモコンと一致しました。27度冷房、風量・風向共に自動という条件で、リアルリモコンと100%同一のビット列を作ることができました。
光パターンを確認する
今回も、青色LEDに差し替えて、動作確認しました。点灯している前後の2秒を切り出した動画です。
2バイトしか送っていなかったシーリング照明の時の点灯
と比べると、今回は27バイトなので長いです。
赤外線LEDに差し替えて、Nature Remoでパターンを取得して、点灯・消灯時間列の情報を取得しました。これを元にグラフにしました。リアルリモコンと全く同じビットパターンになってました。安心です。
冷房と暖房の設定を保存する
プログラム上でもう一つ細かい工夫をしました。例えば以下のようなシナリオを考えます。
- 冷房運転にする
- 室温を27度に設定する
- 暖房運転にする
- 室温を20度に設定する
- 冷房運転にする
これを単純にプログラムすると、
- pana.setMode(kPanasonicAcCool); on(); send(); //冷房運転にする
- pana.setTemp(27); send(); //室温を27度に設定する
- pana.setMode(kPanasonicAcHeat); on(); send(); //暖房運転にする
- pana.setTemp(20); send(); 室温を20度に設定する
- pana.setMode(kPanasonicAcCool); on(); send(); //冷房運転にする
となりますが、最後のステップ5で、冷房の設定温度が20度になってしまいます。実リモコンでは、冷房・暖房のそれぞれで設定温度を別に記憶しています。温度だけでなく、風量は風向などの設定も、運転モードごとに記憶してます。なので運転モードを切り替えると、そのモードで設定した値に戻してくれます。
これを実現するために、冷房用と暖房用のインスタンスをそれぞれ作成する方法を試しましたが、うまくいきませんでした。ということで、27バイトの状態変数を保存する方法にしました。先に、初期値として設定したのと同じような方法です。
まずは、冷房用と暖房用にそれぞれ27バイトの状態変数を用意します。このリモコンにはリセットボタンがついているので、リセット直後の冷房・暖房の設定での赤外線パターンをそれぞれの状態変数としました。
また、冷房・暖房の切り替えのために、switchMode()という関数を作って、そこで切り替えるようにしました。この関数の中で、冷房・暖房の27バイトの変数を保存し、次の切り替えの時に復帰させてます。
#include //for std::memcpy method (略) IRPanasonicAc pana=IRPanasonicAc(kIrLed); //Remo. //base value for the IR Remote state (Panasonic AC) uint8_t coolState[kPanasonicAcStateLength] = { //Cool 28, auto 冷房用の初期データ 0x02,0x20,0xE0,0x04,0x00,0x00,0x00,0x06, 0x02,0x20,0xE0,0x04,0x00,0x31,0x38,0x80,0xAF,0x00,0x00,0x06,0x60,0x00,0x00,0x80,0x00,0x16,0x9A }; uint8_t heatState[kPanasonicAcStateLength] = { //Heat 20, auto 暖房用の初期データ 0x02,0x20,0xE0,0x04,0x00,0x00,0x00,0x06, 0x02,0x20,0xE0,0x04,0x00,0x41,0x28,0x80,0xAF,0x00,0x00,0x06,0x60,0x00,0x00,0x80,0x00,0x06,0x8A }; void setup() { pana.begin(); pana.setRaw(coolState); (略) } //recover the temperature, fan, and swing on mode switch. void switchMode(const uint8_t targetmode){ uint8_t currentmode; currentmode = pana.getMode(); if(targetmode == currentmode) return; //no switch, do nothing const uint8_t *raw=pana.getRaw(); if((currentmode == kPanasonicAcHeat) && (targetmode == kPanasonicAcCool)) { std::memcpy(heatState, raw, kPanasonicAcStateLength); pana.setRaw(coolState); } if((currentmode == kPanasonicAcCool) && (targetmode == kPanasonicAcHeat)) { std::memcpy(coolState, raw, kPanasonicAcStateLength); pana.setRaw(heatState); } } void loop() { switchMode(kPanasonicAcCool); pana.on(); pana.setTemp(27); pana.send(); //冷房運転にする delay(180000); //3分間隔 switchMode(kPanasonicAcHeat); pana.on(); pana.setTemp(20); pana.send(); //暖房運転にする delay(180000); //3分間隔 switchMode(kPanasonicAcCool); pana.on(); pana.send(); //冷房運転にする delay(180000); //3分間隔 }
これで、温度、風量、風向(垂直)の状態は冷房・暖房それぞれで保持されます。これ以外にも、除湿とか送風での保存、水平方向の風向など、保持しても良い項目はあります。でも手元のエアコンには水平方向の調整はできず、HomeKitには除湿の項目が無いので、そこまでは作り込みしません。
まとめ
ESP32に赤外線LEDをつけて、エアコンをリモコンできるようにしました。次はこれをHomeKitに接続して、iPhoneやMacからエアコンを設定したいと思います。
エアコンのリモコン操作では、温度や風量・風向などのデータも同時に送付するので、簡単な学習リモコン方式では実現できません。実リモコンを使って解析した情報がネット上にたくさんありました。ただ、リモコン操作で変化しないために、用途が不明なビットが多くあります。機種によって違ったり、後の新機能のために予約されているビットかと思います。そのためNature RemoやSwitch Botのサポートの人たちは大変だと思いました。スマートリモコンの設定がたまたま動いた状態であって、他の機能に干渉している可能性もあります。手持ちのリモコンの必要な機能を再現できているかどうか確認するためには、実際に信号を解析する必要があると思いました。
ハードウェアは前回の記事と同じで、安価にDIYできます。リモコンパターンの完璧さを求めることも可能なので、高いスマートリモコンを買ってくるよりは、DIYすることがおすすめだと思いました。
以下に続きます:
コメント