ESP32 タイマー付き赤外線リモコンマイコン ESP32 で赤外線リモコンを作ったので紹介します。 (自分で考案したものではなく、ネットで情報を集めてマネしました。) 機能: ・15分のうち13分は「ディープスリープモード」 に入って、ほぼ電力を 消費しない状態になります。(電池駆動の実験はしてませんので、 電池で何日もつか不明です。真夏でも熱くならないようにと念の為 スリープの設定にしています。) ・ディープスリープから復帰すると、プログラムの最初から実行し、 WiFiでインターネットに接続して、正確な時刻を取得します。 「時計がずれる」ということはありません。 ・時刻確認後、予約時刻と照合して、条件分岐します。 ・朝、照明をつける。 ・会社に行く時刻を過ぎたら、照明を消す。 (土日だけ例外扱いすることも可能。) ・夕方の帰宅前、夏は除き、暗めに照明をつける。 (真っ暗だとリモコンの位置すら見えないので。) ・夜、寝る前に照明を暗めにする。 ・寝る時刻には、常夜灯にする。 作り方 1.ハードウェア 秋月電子などで部品を集め、ブレッドボードの上で組み立てます。 本体であるESP32-DevKitは、20年5月時点で1480円です。 他に、赤外線LED、受信モジュール、抵抗、トランジスタが必要です。 ブレッドボードは、サンハヤト SAD-101 です。 2.リモコン信号のコードを調べる。 ESP32とパソコンを、USBケーブルで接続。 Arduino というソフトで、まずは、普通のリモコンの赤外線信号を調べる必要があります。 下記プログラムをESP32に書き込んで実行します。 リモコンを受信モジュールに向けて、ボタンを押すと、 PC画面内のシリアルモニタに文字が表示されます。 この文字をコピペして、メモ帳ソフトなど、どこかにとっておきます。 例 :パナソニックの天井照明ONは、 3468, 1724, 448, 426, 420, 448, 420, 1318, 450, 1288, 454, 420, 446, 1290, 446, 426, 420, 450, 420, 450, 416, 1320, 448, 424,444, 424, 446, 1290, 448, 424, 420, 1318, 420, 450, 420, 1318, 448, 422,448, 420, 448, 1290, 422, 452, 416, 452, 418, 452, 422, 446, 424, 1314,450, 422, 446, 1292, 422, 1316, 422, 452, 446, 1290, 448, 424, 444, 424,444, 426, 420, 450, 420, 1316, 422, 452, 420, 450, 448, 1292, 420, 452, 444, 428, 440 という長いものです。 例:NECの天井照明や、日立の扇風機は、英字入りの短いコードです。 0x41B6659Aとか、0x4184F807 など。 -------------------------------------------------------- 信号解析用プログラム /* * IRremoteESP8266: IRrecvDumpV2 - dump details of IR codes with IRrecv * An IR detector/demodulator must be connected to the input kRecvPin. * * Copyright 2009 Ken Shirriff, http://arcfn.com * Copyright 2017-2019 David Conran * * Example circuit diagram: * https://github.com/crankyoldgit/IRremoteESP8266/wiki#ir-receiving * * Changes: * Version 0.5 June, 2019 * - Move A/C description to IRac.cpp. * Version 0.4 July, 2018 * - Minor improvements and more A/C unit support. * Version 0.3 November, 2017 * - Support for A/C decoding for some protcols. * Version 0.2 April, 2017 * - Decode from a copy of the data so we can start capturing faster thus * reduce the likelihood of miscaptures. * Based on Ken Shirriff's IrsendDemo Version 0.1 July, 2009, */ #include <Arduino.h> #include <IRrecv.h> #include <IRremoteESP8266.h> #include <IRac.h> #include <IRutils.h> // ==================== start of TUNEABLE PARAMETERS ==================== // An IR detector/demodulator is connected to GPIO pin 14 // e.g. D5 on a NodeMCU board. const uint16_t kRecvPin = 35; // The Serial connection baud rate. // i.e. Status message will be sent to the PC at this baud rate. // Try to avoid slow speeds like 9600, as you will miss messages and // cause other problems. 115200 (or faster) is recommended. // NOTE: Make sure you set your Serial Monitor to the same speed. const uint32_t kBaudRate = 115200; // As this program is a special purpose capture/decoder, let us use a larger // than normal buffer so we can handle Air Conditioner remote codes. const uint16_t kCaptureBufferSize = 1024; // kTimeout is the Nr. of milli-Seconds of no-more-data before we consider a // message ended. // This parameter is an interesting trade-off. The longer the timeout, the more // complex a message it can capture. e.g. Some device protocols will send // multiple message packets in quick succession, like Air Conditioner remotes. // Air Coniditioner protocols often have a considerable gap (20-40+ms) between // packets. // The downside of a large timeout value is a lot of less complex protocols // send multiple messages when the remote's button is held down. The gap between // them is often also around 20+ms. This can result in the raw data be 2-3+ // times larger than needed as it has captured 2-3+ messages in a single // capture. Setting a low timeout value can resolve this. // So, choosing the best kTimeout value for your use particular case is // quite nuanced. Good luck and happy hunting. // NOTE: Don't exceed kMaxTimeoutMs. Typically 130ms. #if DECODE_AC // Some A/C units have gaps in their protocols of ~40ms. e.g. Kelvinator // A value this large may swallow repeats of some protocols const uint8_t kTimeout = 50; #else // DECODE_AC // Suits most messages, while not swallowing many repeats. const uint8_t kTimeout = 15; #endif // DECODE_AC // Alternatives: // const uint8_t kTimeout = 90; // Suits messages with big gaps like XMP-1 & some aircon units, but can // accidentally swallow repeated messages in the rawData[] output. // // const uint8_t kTimeout = kMaxTimeoutMs; // This will set it to our currently allowed maximum. // Values this high are problematic because it is roughly the typical boundary // where most messages repeat. // e.g. It will stop decoding a message and start sending it to serial at // precisely the time when the next message is likely to be transmitted, // and may miss it. // Set the smallest sized "UNKNOWN" message packets we actually care about. // This value helps reduce the false-positive detection rate of IR background // noise as real messages. The chances of background IR noise getting detected // as a message increases with the length of the kTimeout value. (See above) // The downside of setting this message too large is you can miss some valid // short messages for protocols that this library doesn't yet decode. // // Set higher if you get lots of random short UNKNOWN messages when nothing // should be sending a message. // Set lower if you are sure your setup is working, but it doesn't see messages // from your device. (e.g. Other IR remotes work.) // NOTE: Set this value very high to effectively turn off UNKNOWN detection. const uint16_t kMinUnknownSize = 12; // ==================== end of TUNEABLE PARAMETERS ==================== // Use turn on the save buffer feature for more complete capture coverage. IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true); decode_results results; // Somewhere to store the results // This section of code runs only once at start-up. void setup() { #if defined(ESP8266) Serial.begin(kBaudRate, SERIAL_8N1, SERIAL_TX_ONLY); #else // ESP8266 Serial.begin(kBaudRate, SERIAL_8N1); #endif // ESP8266 while (!Serial) // Wait for the serial connection to be establised. delay(50); Serial.printf("\nIRrecvDumpV2 is now running and waiting for IR input on Pin " "%d\n", kRecvPin); #if DECODE_HASH // Ignore messages with less than minimum on or off pulses. irrecv.setUnknownThreshold(kMinUnknownSize); #endif // DECODE_HASH irrecv.enableIRIn(); // Start the receiver } // The repeating section of the code void loop() { // Check if the IR code has been received. if (irrecv.decode(&results)) { // Display a crude timestamp. uint32_t now = millis(); Serial.printf("Timestamp : %06u.%03u\n", now / 1000, now % 1000); // Check if we got an IR message tha was to big for our capture buffer. if (results.overflow) Serial.printf( "WARNING: IR code is too big for buffer (>= %d). " "This result shouldn't be trusted until this is resolved. " "Edit & increase kCaptureBufferSize.\n", kCaptureBufferSize); // Display the library version the message was captured with. Serial.println("Library : v" _IRREMOTEESP8266_VERSION_ "\n"); // Display the basic output of what we found. Serial.print(resultToHumanReadableBasic(&results)); // Display any extra A/C info if we have it. String description = IRAcUtils::resultAcToString(&results); if (description.length()) Serial.println("Mesg Desc.: " + description); yield(); // Feed the WDT as the text output can take a while to print. // Output RAW timing info of the result. Serial.println(resultToTimingInfo(&results)); yield(); // Feed the WDT (again) // Output the results as source code Serial.println(resultToSourceCode(&results)); Serial.println(); // Blank line between entries yield(); // Feed the WDT (again) } } 今回使用したリモコンは、パナソニックの天井照明で、 色々な点灯モードがあります。 3.毎日使うためのプログラムを別途作る。 WifiルータのSSIDとパスワード、 リモコン信号、予約時刻 の部分は、各自で入れて下さい。 動作テストとして、5秒おきにオン・オフさせ、 「WiFi につながるか」 「赤外線が出ているか」をテストします。 成功したら、5秒おき、という部分を無効化して、上書きします。 --------------------------------------------- #include <WiFi.h> #include <esp_deep_sleep.h> //ディープスリープのための #include <stdio.h> //曜日を計算 #include <string> //文字列が一致するか比較 #include <IRsend.h> //赤外線リモコン用 char* yyk_asaOn="06:15"; //朝、起床時 char* yyk_asaOff = "07:45"; //朝、出かける前に消す char* yyk_yoruOn[] = {"0","17:30","17:30","17:30", "4不要","5不要","6不要", "7不要","8不要","9不要", "17:30","17:30","17:30"}; //夕方 帰宅前の点灯 1~12月 char* yyk_slp30="21:30"; //寝る30分前 char* yyk_slp15="21:45"; //寝る15分前 char* yyk_slp0="22:00"; //寝る 常夜灯にする時刻 const char* ssid = "●WiFiのSSID●"; //WiFi用 const char* password = "●WiFiのパスワード●"; //WiFi用 struct tm timeInfo; //時刻を格納するオブジェクト char datetime[25]; //日付と時刻文字格納用 char hhmm1[20]; //時刻文字格納用 const int kIrLed = 33; // ESP32の33番 赤外線信号出力 IRsend irsend(kIrLed); // 赤外線準備. // IRrecvDumpV2 で信号解析した結果を貼ってある。 // リビング照明 点灯 uint16_t dLiving_on[83] = {3468, 1724, 448, 426, 420, 448, 420, 1318, 450, 1288, 454, 420, 446, 1290, 446, 426, 420, 450, 420, 450, 416, 1320, 448, 424,444, 424, 446, 1290, 448, 424, 420, 1318, 420, 450, 420, 1318, 448, 422,448, 420, 448, 1290, 422, 452, 416, 452, 418, 452, 422, 446, 424, 1314,450, 422, 446, 1292, 422, 1316, 422, 452, 446, 1290, 448, 424, 444, 424,444, 426, 420, 450, 420, 1316, 422, 452, 420, 450, 448, 1292, 420, 452, 444, 428, 440}; //リビング照明 スポットライト風のモード uint16_t dLiving_spot[83] = {3414, 1778, 396, 476, 394, 476, 394, 1344, 396, 1344, 394, 476, 392, 1344, 396, 476, 394, 476, 394, 476, 394, 1344, 394, 476, 394, 476, 392, 1344, 396, 476, 394, 1344, 396, 476, 394, 1344, 396, 476, 394, 476, 392, 1344, 396, 1344, 396, 1344, 396, 476, 394, 476, 394, 1344, 396, 1344, 396, 476, 394, 476, 394, 1346, 394, 474, 394, 476, 392, 1344, 396, 476, 392, 1344, 396, 476, 394, 1344, 396, 480, 388, 1346, 394, 476, 396, 1342, 394}; //リビング照明 常夜灯よりも明るい、間接照明 uint16_t dLiving_kansetu[83] = {3414, 1780, 396, 476, 392, 476, 392, 1344, 396, 1344, 394, 476, 394, 1344, 396, 476, 394, 474, 394, 476, 392, 1344, 396, 476, 394, 476, 392, 1344, 396, 476, 392, 1344, 396, 476, 392, 1344, 396, 476, 394, 476, 392, 1344, 396, 1344, 394, 1346, 394, 476, 394, 476, 394, 476, 394, 1344, 396, 476, 394, 476, 394, 1344, 396, 476, 394, 476, 394, 1344, 396, 1344, 394, 1344, 396, 476, 396, 1344, 392, 480, 392, 1342, 400, 476, 390, 1344, 396}; // リビング 常夜灯 uint16_t dLiving_small[83] = {3468,1726, 450, 422, 446, 424, 444, 1290, 424, 1318,448, 422, 450, 1288, 448, 424, 442, 426, 444, 424, 420, 1316, 424, 450, 444, 424,452, 1286, 448, 424, 444, 1292, 422, 450, 442, 1296, 450, 422, 446, 422, 448, 1290,424, 452, 418, 450, 416, 454, 416, 452, 444, 424, 446, 1290, 450, 1290, 422, 1318,422, 450, 420, 1316, 450, 420, 446, 424, 446, 1292, 422, 1318, 420, 1320, 452, 420,448, 422, 444, 1292, 422, 452, 416, 452, 418}; // リビング照明 消灯 uint16_t dLiving_off[83] = {3472, 1722, 444, 426, 392, 478, 390, 1346, 454, 1286,454, 416, 450, 1288, 394, 476, 392, 478, 446, 424, 448, 1288, 454, 418, 452, 416,452, 1288, 446, 426, 392, 1346, 454, 418, 452, 1286, 454, 416, 452, 418, 450, 1288,446, 426, 446, 422, 450, 418, 452, 416, 454, 1284, 452, 1288, 448, 1292, 452, 1288,454, 416, 452, 1286, 454, 418, 450, 420, 450, 420, 392, 1344, 454, 1284, 456, 416,454, 416, 452, 1286, 448, 422, 394, 476, 446}; int khz = 38; //赤外線送信に使う void Living_on() { // リビング照明 点灯 irsend.sendRaw(dLiving_on, sizeof(dLiving_on)/sizeof(dLiving_on[0]), khz); Serial.print("Living ON\n"); } void Living_spot() { // リビング照明 スポットライト風のモード irsend.sendRaw(dLiving_spot, sizeof(dLiving_spot)/sizeof(dLiving_spot[0]), khz); Serial.print("Living spot\n"); } void Living_kansetu() { // リビング照明 常夜灯よりも明るい、間接照明 irsend.sendRaw(dLiving_kansetu, sizeof(dLiving_kansetu)/sizeof(dLiving_kansetu[0]), khz); Serial.print("Living kansetu\n"); } void Living_small() { // リビング照明 常夜灯 irsend.sendRaw(dLiving_small, sizeof(dLiving_small)/sizeof(dLiving_small[0]), khz); Serial.print("Living small\n"); } void Living_off() { // リビング照明 消灯 irsend.sendRaw(dLiving_off, sizeof(dLiving_off)/sizeof(dLiving_off[0]), khz); Serial.print("Living OFF\n"); } //参考 NEC方式の場合は、こう書きます //void Bedroom_on() { // 点灯 // irsend.sendNEC(0x41B6659A,32); //} void wifi_setup(){ //WiFi接続 Serial.println(" wifi_setup "); WiFi.mode(WIFI_STA); WiFi.disconnect(); if (WiFi.begin(ssid, password) != WL_DISCONNECTED) { ESP.restart(); } int count = 0; while (WiFi.status() != WL_CONNECTED) { Serial.print(" wifi_waiting "); delay(1000); count++; if (count>10) { ESP.restart(); } Serial.println("."); } Serial.println("Connected to the WiFi network!"); } //時刻合わせを行う関数 void syncTime() { configTime(-9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp"); } // Zeller の公式。 int subZeller( int y, int m, int d ) { if( m < 3 ) { y--; m += 12; } return ( y + y/4 - y/100 + y/400 + ( 13*m + 8 )/5 + d )%7; } // Zeller の公式で週の何日目か調べる。 0が日曜。 int subSatSun( int y, int m, int d ) { int yobicode=subZeller(y,m,d); //日付より、曜日コード。 if( yobicode==0||yobicode==6 ) { //日曜または土曜ならば return 1; } else { return 0; //平日はゼロを返す } } void setup() { Serial.begin(115200); // シリアルモニタのウインドウ 115200bpsの画面にてログを表示。 Serial.println("Start ----------------------"); wifi_setup(); //WiFi接続 irsend.begin(); // 赤外線準備. configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");///NTPの設定 // ここから ディープスリープに必要な記述 esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF); esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF); esp_deep_sleep_pd_config(ESP_PD_DOMAIN_MAX, ESP_PD_OPTION_OFF); } void loop() { getLocalTime(&timeInfo); //tmオブジェクトのtimeInfoに現在時刻を入れ込む sprintf(datetime, " %04d/%02d/%02d %02d:%02d_%02d", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec); //日付と時刻 人間が読める形式に変換 文字列に書き込む Serial.print(datetime); //時間をシリアルモニタへ出力 Serial.println("."); sprintf(hhmm1, "%02d:%02d", timeInfo.tm_hour, timeInfo.tm_min); //時刻 人間が読める形式に変換 文字列に書き込む int month1=timeInfo.tm_mon + 1; //月 int min1=timeInfo.tm_min; //現在時刻の中の分。 int SatSun=subSatSun(timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday); //土日が1となる delay(1000); if ("TEST MODE"=="xxxTEST MODE"){ //メンテ・開発用。5秒おきにオン・オフを繰り返す。60回。 for (int i=1;i<=60;i++){ Living_on(); //照明オン delay(5000); //5秒停止 Living_off(); //照明オフ delay(5000); //5秒停止 } } if (min1==1 || min1==16 || min1==31 || min1==46){ //1時間に4回ディープスリープに入る。OTA使わないのでほとんどスリープしててもよい。 Serial.print(datetime); //時間をシリアルモニタへ出力 Serial.println(" deep_sleep"); uint64_t sleepTime= uint64_t(60*13); // 13分の数値を作る esp_deep_sleep_enable_timer_wakeup(uint64_t(sleepTime*1000000L)); //ディープスリープの時間設定 esp_deep_sleep_start(); //ディープスリープに入る } //取得した時刻 と 予約時刻 を照合。条件分岐。 if (!strcmp(hhmm1,yyk_asaOn)) { //朝 照明をつける Living_on(); delay(61000); //すぐに次のループに入ると、予約時刻が再びきてしまうので、1分保留。 } else if (!strcmp(hhmm1,yyk_asaOff)) { //出かける前の時刻 消灯。 「&& SatSun==0」をつけると、平日のみ作動。 Living_off(); delay(61000); } else if (!strcmp(hhmm1,yyk_yoruOn[month1]) && SatSun==0) { //夕方の予約時刻で、平日なら 間接照明をつける。 Living_kansetu(); delay(61000); } else if (!strcmp(hhmm1,yyk_slp30)) { //夜 寝る30分前 スポット照明モード。 Living_spot(); delay(61000); } else if (!strcmp(hhmm1,yyk_slp15)) { //夜 寝る15分前 間接照明モード。 Living_kansetu(); delay(61000); } else if (!strcmp(hhmm1,yyk_slp0)) { //夜 寝る 常夜灯モード。 Living_small(); delay(61000); } delay(30000); //30秒停止。このあと、loopの最初に戻って、時刻取得から繰り返す。 4.設置 完成したプログラムを書き込んだら、 USBケーブルをパソコンから外します。 ホコリ防止のため、小さなケースに入れるといいです。 (穴をあけて、赤外線LEDだけは顔を出すようにします。) 家電に信号が届く向き・高さの場所を決め、 ESP32を 5VのACアダプタに接続します。 ....