ESP32 タイマー付き赤外線リモコン | ドタバッタンのブログ

ドタバッタンのブログ

DIY、電子工作系

マイコン 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アダプタに接続します。

 

 

 

 

....