ESP32でDIYしたスマートリモコンに、赤外線リモコン受信モジュールを組み込みました。これで各種リモコンの信号パターンを測定し、その結果をMQTTメッセージ経由で取得します。
ESP32でDIYしたスマートリモコン
ESP32を使って、色々な機器やエアコンに対応したスマート赤外線リモコンを自作してきました。
赤外線リモコン
このDIYリモコンは、MQTTのメッセージで動作します。HomeKitに接続したHomebridgeサーバが、MQTT経由でコマンドを出しています。
スマートリモコンは、Nature Remo, Tuya, SwitchBotなどのメーカー製品を、Homebridge / HomeKit経由でいくつか使いました。しかし、エアコンが正しく動作することを考えると、実際のリモコン信号パターンに合わせ込んだDIYが一番安心です。なので、このDIYスマートリモコンを使ってます。
赤外線受信モジュールを取り付ける
今回はこのESP32に、赤外線受信モジュールを追加しました。赤外線受信モジュールは、秋月電子で110円で売られているPL-IRM0101-3という製品です。
以前の記事では、これをRaspberry Piに取り付けて、赤外線信号記録に使いました。
このモジュールを取り外して、ESP32に取り付けることにしました。PL-IRM0101-3のピンは3本で、左から、(1)信号、(2)GND、(3)Vcc (3.3~5V)です。
信号ピンは、通常は1で、赤外線を検出すると0になります。実は、
で作ったスマートリモコン用プリント基板には、すでにPL-IRM0101のためのパターンを作り込んでありました。でも実際には、まだ取り付けていませんでした。
なので今回は、ここに受信モジュールをハンダづけします。
ただ、ちょっとだけ回路変更しました。プリント基板を作った時の回路では、赤外線モジュールの信号ピン(1番ピン)をESP32のD2に接続してました。でもその後、D2は、ESP32 dev.基板上の青色LEDに接続されていることを知りました。せっかくなのでこのLEDは、動作モニター用に使いたいと思います。なので、赤外線モジュールを、その隣のピン、D15に接続することにしました。変更した回路図は以下です。
プリント基板は、もう完成してしまってますので、D2に接続されたパターンをカッターナイフで切断し、抵抗の足の切れ端を使ってD15に接続しました。
かっこ悪いけど仕方ないです。よくある仕様変更だと思います。
MQTT動作仕様を決める
スマートリモコン部分は、MQTTからのメッセージで動作しています。HomeKitに接続したHomebridgeサーバが、MQTT経由でコマンドを出しています。この基板には、赤外線LEDの他に、温度・湿度センサーDHT20も搭載されているので、その情報もMQTT経由で受け取ってます。
以下に、HomebridgeのプラグインであるMqttthingの設定部分を抜き出しました。これにあるように、MQTTの7個のトピックスを使って、エアコンを操作してます。例えば、mqttthing/irESP32/set/Activeトピックスにメッセージtrueを送るとエアコンがOnになり、falseを送るとOffになります。
{
"type": "heaterCooler",
"name": "Aircon",
"url": "mqtt://localhost:1883",
"topics": {
"setActive": "mqttthing/irESP32/set/Active",
"setCoolingThresholdTemperature": "mqttthing/irESP32/set/CoolingThresholdTemperature",
"getCurrentTemperature": "mqttthing/irESP32/get$.temperature",
"setHeatingThresholdTemperature": "mqttthing/irESP32/set/HeatingThresholdTemperature",
"setRotationSpeed": "mqttthing/irESP32/set/RotationSpeed",
"setSwingMode": "mqttthing/irESP32/set/SwingMode",
"setTargetHeaterCoolerState": "mqttthing/irESP32/set/TargetHeaterCoolerState"
},
"accessory": "mqttthing",
},
この仕組みに合わせて、赤外線信号記録機能もMQTTで動作させることにします。Homebridgeを経由せずに、Mosquittoに直接MQTTアクセスします。
それで、ESP32が対応するトピックスを拡張して、mqttthing/irESP32/set/RecordIRというトピックスを用意しました。このトピックスに、数値のメッセージを送ると、その秒数だけリモコンからの信号を待って、記録することにしました。例えば5を送ると、5秒間、リモコンからの信号を待ちます。
記録した結果は、mqtttghing/irESP32/DebugトピックスにJSON形式の配列で送り返すことにします。なので、以下の手順で赤外線リモコンパターンを取得できます。
- mosquitto_pubコマンドなどでRecordIRトピックスにタイムアウト秒数を送る
- タイムアウト秒以内に、赤外線リモコン信号をESP32に当てる
- mosquitto_subコマンドなどでDebugトピックスを確認してパターンを得る
こうして得たパターンは、Pythonプログラムなどで解析したり、スマートリモコンプログラムに組み込むなど、活用できます。
ESP32プログラムの流れ
ESP32でのプログラムの流れを考えます。元々のスマートリモコンのESP32プログラムでは、mqttthing/irESP32/set/トピックスのメッセージに合わせて、赤外線信号を出していました。これに加えて、以下の機能を追加します。
- RecordIRトピックスのメッセージがきたら、以下を実行する:
- IR信号が0になる(赤外線信号有り)まで、指定されたタイムアウト秒数だけ待つ。もしタイムアウト秒以内に信号が有れば、以下の2行を無限ループで実行する。
- 1(信号無し)になるまでの時間を記録する。1になったら、
- 0(信号有り)になるまでの時間を記録する。ただし1秒以上変化無しなら無限ループを終了
- 記録した内容をDebugトピックスに流す。
以上です。
ESP32プログラムを作る
作成したESP32のプログラムは(長いので)こちらでご覧ください。
ほとんどが、前回作成したパナソニックエアコン用スマートリモコンプログラムそのままです。追加した部分を以下で説明します。
まずは、MQTTのメッセージを受け取った時に動作する関数の部分です。これに、RecordIRトピックスを受け取った時の処理を追加しました。タイムアウト秒数を得て、最大を10秒に制限し、LEDとMQTTメッセージで記録開始・終了を表示してます。記録の過程は、別に用意したrecordIR()という関数で行ってます。
//MQTT: subtopic call back
void onMessageReceived(const String& topic, const String& message) {
String command = topic.substring(topic.lastIndexOf("/") + 1);
if (command.equals("Active")) {
//本来のスマートリモコンのためのプログラムを省略
}else if(command.equals("RecordIR")){
int timeouts=message.toInt(); //read time out value (sec.)
if(timeouts > 10) timeouts=10; //too long, max 10 sec.
client->publish(DEBUG,"Recording starts.");
digitalWrite(RECLED,HIGH); //青色LEDをon
String result = recordIR(1000000L * timeouts);
client->publish(DEBUG,result );
digitalWrite(RECLED,LOW); //青色LEDをoff
client->publish(DEBUG,"Recording ends.");
}
}
以下がrecordIR()関数です。recordIR()関数の引数は、timeoutのマイクロ秒にしてます。なので、上のコードでは、RecordIRで受け取ったMQTTメッセージの数字(秒)を1000000倍にして渡してます。
String recordIR(unsigned long timeoutmicro){
//returns like this: {"sequence": [ 123, 456, 789 ]}
String result="{\"sequence\":[";
unsigned long zerostarttime, onestarttime;
bool isTimeout=false;
//wait for the first zero for timeoutmicro, otherwise set isTimeout to true
onestarttime=micros();
while(digitalRead(RECMODULE) == 1){
if((micros()-onestarttime) > timeoutmicro){
isTimeout=true;
break;
}
}
if(isTimeout) { //timeout
result.concat(String("]}"));
return result; //returns zero result: {"sequence": []}
}
while(true) { //IR signal started.
zerostarttime=micros(); //IR zero signal started.
while(digitalRead(RECMODULE) == 0); //wait until zero ends
result.concat(String(micros()-zerostarttime));
onestarttime=micros();
while(digitalRead(RECMODULE) == 1){
if((micros()-onestarttime)>1000000L) {
isTimeout=true;
break;
}
}
if(!isTimeout) { //Sequence continues...
result.concat(String(","));
result.concat(String(micros()-onestarttime));
result.concat(String(","));
}else{ //Sequence ends, by timeout
result.concat(String("]}"));
return result;
}
}
}
この関数では、最初に、指定されたタイムアウト秒数だけ、リモコン入力を待ちます。リモコンからの信号がなければ、空の配列をMQTTにパブリッシュします。
リモコンからの信号があれば、off/onの時間を測定して、信号が1秒以上来なくなったら終了にします。そして結果の配列をMQTTにパブリッシュします。
リモコン信号を記録する
このプログラムをArduino IDEでコンパイルし、ESP32にダウンロードすれば完成です。今までのパナソニック用スマートリモコン機能に加えて、リモコン信号記録機能が追加されました。
MacやRaspberry Piのコマンドラインから、
% mosquitto_pub -t mqttthing/irESP32/set/RecordIR -m 3
とすると、3秒間、リモコン信号を待ち、受信できなければ空の配列をMQTTメッセージで返します。信号記録中は、開発ボード上の青色LEDを点灯させるようにしてあります。なので3秒間、点灯します。
この時、赤外線信号は取得できないので、MQTTメッセージには、空の配列が返されます。mosquitto_subコマンドで確認すると以下のようになります。
% mosquitto_sub -t mqttthing/irESP32/# -v mqttthing/irESP32/set/RecordIR 3 mqttthing/irESP32/debug Recording starts. mqttthing/irESP32/debug {"sequence":[]} mqttthing/irESP32/debug Recording ends.
青色LEDが点灯中に、こちらで使ったシーリングライトのリモコン
を押して、赤外線信号を記録させると、以下のように結果が得られました。
% mosquitto_sub -t mqttthing/irESP32/# -v mqttthing/irESP32/set/RecordIR 3 mqttthing/irESP32/debug Recording starts. mqttthing/irESP32/debug {"sequence":[6894,4516,328,1340,335,507,334,1340,326,512,334,1340,327,515,334,1340,327,515,333,511,326,1345,334,511,326,517,333,1340,327,1342,357,1309,334,1345,335]} mqttthing/irESP32/debug Recording ends.
これは、6894μsだけ赤外線がon, 4516μs off, 328μs on,1340μs offなどと続く信号であることを示します。1秒以上、赤外線が発光しない場合に記録終了します。
この配列をPythonプログラムにコピーペーストして、1, 0を出力するプログラムを作りました。その結果をNumbersでグラフにしました。1が赤外線信号有り、0が無しの状態です。
エアコンのリモコン信号の記録も試しました。使ったのは三菱のエアコンリモコンです。(部屋ではパナソニックのエアコンが動いているので、誤動作しないように、別部屋のリモコンを使いました)。最初に何度か試みたところ、MQTTのメッセージが送られてきませんでした。天井リモコンは取得できるけど、エアコンリモコンは取得できません。
調べたところMQTTライブラリのパケットサイズ制限が原因でした。MQTTのメッセージは、通常は小さいので、ライブラリの初期値では小さめに設定されているようです。エアコンリモコンの信号は、シーリングライトリモコンより長く、文字列にして2kBくらいになります。そこで、Arduinoのsetup()関数の初期化のところで、以下のようにパケットサイズ上限を3kBに設定しておきました。この設定には、setMaxPacketSizeメソッドを使用します。
void setup() {
//
//略
//
client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT);
client->setMaxPacketSize(3000);
}
この結果、エアコンリモコンでも以下のように結果を取得できました。
mqttthing/irESP32/debug Recording starts. mqttthing/irESP32/debug {"sequence":[3364,1664,385,1290,392,1280,391,445,382,445,390,447,379,1290,391,445,382,445,390,1290,384,1289,398,439,382,1290,390,453,375,444,391,1298,382,1290,391,438,390,1290,389,1283,391,445,380,447,388,1290,383,444,391,445,383,1289,391,445,383,445,390,444,383,446,389,446,380,445,390,446,381,446,389,445,382,445,390,445,382,445,390,445,382,445,390,445,383,444,391,445,382,446,389,445,382,445,389,1291,382,445,390,445,383,444,390,447,381,446,389,1289,385,1289,391,438,388,447,389,438,389,446,389,438,390,1291,389,1282,391,445,391,439,389,445,389,438,390,444,390,1282,391,1290,382,447,388,446,382,446,389,444,383,446,388,447,380,446,390,445,382,446,389,447,381,446,389,1291,381,446,389,445,382,446,388,445,381,445,391,446,381,446,389,448,380,446,388,447,380,445,390,445,382,445,390,446,381,446,389,447,381,445,390,446,380,446,389,445,382,446,389,447,381,444,391,445,382,446,389,445,383,446,389,446,381,445,389,446,382,445,390,445,383,445,390,445,382,447,388,445,383,445,390,445,382,446,388,445,382,446,389,444,382,446,389,448,380,446,389,444,383,446,388,447,380,445,390,445,383,447,388,472,354,446,389,445,382,446,389,445,383,447,387,1290,383,1290,391,1297,383,1291,390,1284,389,445,390,439,388,1290,383,13111,3356,1710,384,1289,390,1283,390,445,390,439,389,447,388,1283,391,445,389,439,389,1290,383,1291,390,445,382,1291,389,446,382,446,389,1291,383,1289,390,454,381,1290,390,1283,390,446,390,439,388,1291,390,436,391,446,388,1283,390,446,382,446,389,446,381,447,388,447,381,445,390,446,382,446,388,447,381,446,389,448,379,446,389,446,382,445,389,445,384,445,389,445,382,446,390,445,382,446,390,1291,383,445,389,446,382,446,389,446,381,447,389,1291,382,1291,390,445,383,446,389,445,383,446,389,447,381,1291,389,1299,382,446,389,439,388,446,389,438,389,447,389,1283,390,1291,390,438,390,446,389,438,389,446,388,439,389,444,391,438,390,445,390,440,389,446,389,439,388,1292,382,445,390,446,381,447,388,446,382,446,389,446,382,445,390,446,381,448,387,446,382,447,388,446,382,445,389,445,382,445,390,446,380,447,388,447,381,447,388,445,382,446,390,446,381,446,390,445,381,446,389,446,382,447,387,447,381,446,389,446,380,447,388,447,380,446,389,447,381,446,389,445,382,447,388,446,381,448,386,446,382,447,387,448,380,446,389,446,382,445,390,447,372,446,389,446,382,447,388,446,381,447,388,454,374,447,388,445,383,446,389,446,382,447,388,1291,383,1291,389,1291,383,1291,382,1298,382,446,389,439,388,1291,390]} mqttthing/irESP32/debug Recording ends.
この長い配列も同じく、Pythonにコピーペーストして、1, 0の数値を書き出し、Numbersでグラフ化しました。以下です。三菱エアコンのリモコン信号が正しく取得できているようです。
まとめ
ESP32でDIYしたスマートリモコンに、赤外線リモコン信号パターンを記録する機能を追加しました。これで、市販のスマートリモコンのように、既存のリモコン信号を読み取ることができます。
SwitchBotの新しいスマートリモコンHub 2では、既存のエアコンリモコンの操作を、スマートリモコンの内部状態に反映させる機能が計画されているようです。DIYリモコンでも、デコード機能を追加すれば、実現可能かと思います。
コメント