DreamerDreamのブログ

夢想家の夢です。〜揚げたてのモヤっとしたものをラフレシアと共に〜

エアコンのリモコン信号をESP32でコピーしてみる実験 備忘録

エアコンの赤外線リモコンコードをコピーして、外出先で帰宅前にエアコンを入れたり、寝室のエアコンを寝る前に遠隔で入れられたら便利ですよね。

 

以前にラズパイでこのシステムを作っていたのですが、サーバーを改修してからしばらくリモコン機能を停止しています。

便利だったのですが、少し送信に難ありで2〜3回ぐらい送信してやっと効くという状態だったのと、家具の配置によって線の取り回しが面倒ということで取り外してしまいましたが、はやり必要。ということで作り直します。

dreamerdream.hateblo.jp

今までラズパイサーバーから直接有線で赤外線LED制御していたものを、サーバーから無線でESP32に信号を飛ばして赤外線LED制御するという算段です。線が無くなるのでスッキリします。

 

ESP32からの赤外線送受信はこちらのブログが大変参考になりすます。(コピーする信号が決まっていて組み込むだけならほぼそのまま作れば良いです)

asukiaaa.blogspot.com

 

遠隔操作するにはまずは赤外線の受信送信が出来ないと始まらないので、実験あるのみです。

 

受信機プログラムの内容です。

最終的にはSocketで受信信号を文字列で受け取りたいので、信号はString文字列に格納するようにしました。

receiver.py

#include <Arduino.h>

#include "PinDefinitionsAndMore.h" // Define macros for input and output pin etc.

#if !defined(RAW_BUFFER_LENGTH)
#  if RAMEND <= 0x4FF || RAMSIZE < 0x4FF
#define RAW_BUFFER_LENGTH  180  // 750 (600 if we have only 2k RAM) is the value for air condition remotes. Default is 112 if DECODE_MAGIQUEST is enabled, otherwise 100.
#  elif RAMEND <= 0x8FF || RAMSIZE < 0x8FF
#define RAW_BUFFER_LENGTH  600  // 750 (600 if we have only 2k RAM) is the value for air condition remotes. Default is 112 if DECODE_MAGIQUEST is enabled, otherwise 100.
#  else
#define RAW_BUFFER_LENGTH  750  // 750 (600 if we have only 2k RAM) is the value for air condition remotes. Default is 112 if DECODE_MAGIQUEST is enabled, otherwise 100.
#  endif
#endif


#define MARK_EXCESS_MICROS    20    // Adapt it to your IR receiver module. 20 is recommended for the cheap VS1838 modules.


#define LED_BUILTIN 26
#define RAW_BUFFER_LENGTH 400
#define RECORD_GAP_MICROS 12000

#include <IRremote.hpp>


void setup() {
    pinMode(LED_BUILTIN, OUTPUT);

    Serial.begin(115200);   // Status message will be sent to PC at 9600 baud
#if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/|| defined(SERIALUSB_PID) || defined(ARDUINO_attiny3217)
    delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor!
#endif
    // Just to know which program is running on my Arduino
    Serial.println(F("START " __FILE__ " from " __DATE__ " Using library version " VERSION_IRREMOTE));

    // Start the receiver and if not 3. parameter specified, take LED_BUILTIN pin from the internal boards definition as default feedback LED
    IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);

    Serial.print(F("Ready to receive IR signals of protocols: "));
    printActiveIRProtocols(&Serial);
    Serial.println(F("at pin " STR(IR_RECEIVE_PIN)));

    // infos for receive
    Serial.print(RECORD_GAP_MICROS);
    Serial.println(F(" us is the (minimum) gap, after which the start of a new IR packet is assumed"));
    Serial.print(MARK_EXCESS_MICROS);
    Serial.println();
    Serial.println(F("Because of the verbose output (>200 ms at 115200), repeats are probably not dumped correctly!"));
    Serial.println();
}


void loop() {
  int waitCnt = 0; //空き時間計測用
  String ProntoHEX ="";  //文字列保存用
  while(1){
    if (IrReceiver.decode()) {  //受信したら      
        ProntoHEX += " " + String( waitCnt ) + " "; //空き時間を文字列に加える
        waitCnt = 0; //空き時間リセット
        IrReceiver.compensateAndStorePronto(&ProntoHEX); //文字列にコードを追記
        IrReceiver.resume();  // 次の受信へ備える
    }
    
    waitCnt++; //カウント
    delay(1);
    
    if( 1000 < waitCnt ){ //1秒以上空いたら文字列を吐き出す
      if( ProntoHEX != ""){ //文字列に文字があれば
          //最初の待ち時間は不要なので削除
          int retPos = ProntoHEX.indexOf(" ", 1);
          ProntoHEX = ProntoHEX.substring(retPos+1 , ProntoHEX.length() );
          Serial.println( "========= String ========" );
          Serial.println( ProntoHEX ); <-テキスト保存用文字列
          
      }
      //待ち時間と文字列をリセット
      waitCnt = 0;
      ProntoHEX ="";
    }
  }
}

最終的に文字列「ProntoHEX」へ信号を格納しています。これをsocketで送れば沢山の信号をサーバー側で管理することが出来ますね。

IrReceiver.compensateAndStorePronto()関数で予め用意しておいた文字列に受信機からの信号が追記できるようです。

 

空き時間を計測して再度受信しているのには理由があります。

テレビのようなリモコンは短い同じ信号を繰り返し送る仕様になっていますから1度受信した信号を繰り返せば良いのですが、今回はもっと複雑なエアコンリモコンのコピーなので、リモコンの受信間の間が1秒未満のものは空き時間を計測して空き時間も計測して連続して受信し、1秒以上空いた場合は受信完了として蓄積したコードを文字列で出力するようにしています。

(受信してからなんやかんやしているので、実際の待ち時間は送信時に調整することにします。)

 

数種類のエアコンで試したところ、エアコンのリモコンによって一気に長いコードを送ったり、数回に分けて送る仕様であったりと、その方法はメーカーによって様々なようです。

こちらは一気に長いコードを送るリモコン。

 

例えば、TVのリモコンが

"0000 006D 000D 0000 005F 0014 0031 0014 001A 0012 0033 0012 001A 0014 0031 0012 001C 0012 001A 0014 0031 0014 001A 0012 001C 0012 001A 0014 001A 06C3"

という信号を繰り返し送るのに対し、このエアコンのリモコンは

"0000 006D 0092 0000 0089 003F 0014 002B 0014 002D 0014 000D 0014 000D 0012 000D 0014 002D 0014 000D 0014 000D 0014 002B 0014 002D 0014 000D 0014 002B 0016 000B 0014 000D 0014 002D 0012 002D 0014 000D 0014 002D 0012 002D 0014 000D 0014 000D 0014 002B 0014 000D 0014 000D 0014 002D 0014 000B 0016 000B 0014 000D 0014 000D 0014 000D 0012 000D 0016 000B 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0014 000D 0016 000B 0014 000D 0014 000D 0012 000D 0014 002D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 002D 0012 002D 0016 000B 0014 000D 0014 000D 0012 002D 0014 002D 0014 000D 0014 002B 0016 000B 0014 000D 0014 000D 0014 000B 0014 000D 0016 002B 0014 002D 0014 000B 0016 002B 0014 002D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0016 000B 0014 000D 0014 002D 0012 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0016 000B 0014 000D 0014 000D 0014 000B 0014 000D 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0014 000D 0016 000B 0014 000D 0014 000D 0012 000D 0014 000D 0016 000B 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0016 000B 0014 002D 0014 002B 0014 002D 0014 000D 0014 000D 0012 002D 0014 002D 0014 06C3 "

この信号を一気に送っています。

他のエアコンのリモコンでは長さの違うコードを数回に分けて送っているものもあります。

 

ちなみに、この信号は赤外線のON-OFFの生の計測時間を16進数で表したもので、属に言うコードでは無いので、多少の誤差は許容されるようです。

例)受信ごとに微妙に変わる赤外線の時間差

1度目

0000 006D 0092 0000 008B 003D 0014 002D 0014 002B 0014 000D 0014 000D 0014 000D 0014 002B

2度目

0000 006D 0092 0000 0089 003D 0014 002D 0014 002D 0012 000D 0014 000D 0014 000D 0014 002D

3度目

0000 006D 0092 0000 0089 003F 0014 002B 0014 002D 0014 000D 0014 000D 0012 000D 0014 002D

 

 

受信した信号の文字列はそのままではダメで、きちんとcharに格納できるとライブラリで送信できるようになります。少し手間がかかりますがこの方法ならどんな複雑な信号でもサーバーからリクエストする事が可能です。

sender.py

#include <Arduino.h>

#define PIN_SEND 4
//#define PIN_BUTTON_TURN_ON 0
//#define PIN_BUTTON_TURN_OFF 18

#define IR_SEND_PIN PIN_SEND
#include <IRremote.hpp>

String aircon = "0000 006D 0092 0000 0089 003F 0014 002B 0014 002D 0014 000D 0014 000D 0012 000D 0014 002D 0014 000D 0014 000D 0014 002B 0014 002D 0014 000D 0014 002B 0016 000B 0014 000D 0014 002D 0012 002D 0014 000D 0014 002D 0012 002D 0014 000D 0014 000D 0014 002B 0014 000D 0014 000D 0014 002D 0014 000B 0016 000B 0014 000D 0014 000D 0014 000D 0012 000D 0016 000B 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0014 000D 0016 000B 0014 000D 0014 000D 0012 000D 0014 002D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 002D 0012 002D 0016 000B 0014 000D 0014 000D 0012 002D 0014 002D 0014 000D 0014 002B 0016 000B 0014 000D 0014 000D 0014 000B 0014 000D 0016 002B 0014 002D 0014 000B 0016 002B 0014 002D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0016 000B 0014 000D 0014 002D 0012 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0016 000B 0014 000D 0014 000D 0014 000B 0014 000D 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0014 000D 0016 000B 0014 000D 0014 000D 0012 000D 0014 000D 0016 000B 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0014 000D 0014 000D 0014 000B 0014 000D 0014 000D 0014 000D 0014 000D 0012 000D 0014 000D 0016 000B 0014 002D 0014 002B 0014 002D 0014 000D 0014 000D 0012 002D 0014 002D 0014 06C3 ";

 

String tvSw = "0000 006D 000D 0000 005F 0014 0031 0014 001A 0012 0033 0012 001A 0014 0031 0012 001C 0012 001A 0014 0031 0014 001A 0012 001C 0012 001A 0014 001A 06C3 60 0000 006D 000D 0000 0061 0012 0031 0014 001A 0012 0033 0014 0018 0014 0031 0014 001A 0012 001C 0012 0031 0014 001A 0014 001A 0012 001C 0012 001A 06C3 60 0000 006D 000D 0000 0061 0012 0031 0014 001A 0014 0031 0012 001C 0012 0031 0014 001A 0014 001A 0012 0031 0014 001A 0014 001A 0014 001A 0012 001C 06C3";

 

void setup() {
  Serial.begin(115200);
  IrSender.begin();
}

//改行コードの数

int countReturns(String str) {
  int dashCount = 0;
  int startPos = 0;
  int dashPos = str.indexOf(" ", startPos);

  while (dashPos != -1) {
    dashCount++;
    startPos = dashPos + 1;
    dashPos = str.indexOf(" ", startPos);
  }

  return dashCount;
}

void loop() {
  delay(2000);

  String sc = aircon; //今回はエアコンのコードを代入
  int len_ret = countReturns(sc); //改行コードの数
  if( 0 < len_ret){ //改行コードがある場合
    String code[len_ret/2 +1];
    int wait[len_ret/2];
    int startPos = 0;
    int maxLen = 0;
    for(int i = 0; i < len_ret +1 ; i++){
      int endPos = sc.indexOf(" ", startPos);
      String line = sc.substring(startPos, endPos);
      if(maxLen < endPos+1 ){
        maxLen = endPos +1;
      }
      if(i%2 == 0){ //偶数=コード
        code[i/2] = line;
      }else{ //奇数=待ち時間
        //素直にdelayに入れると送信時に遅れが生じるのでここで調整
        wait[i/2] = (line.toInt()) * 6/10; 
      }
      startPos = endPos + 1;
    }

    //String -> char へ変換、変換しながら送ると時間がかかるので後で一気に送信
    int codesize = sizeof(code) / sizeof(code[0]);
    char charArray[ codesize ][maxLen];
    int waitsize = sizeof(wait) / sizeof(wait[0]);
    for(int i = 0; i < codesize  ; i++){
      Serial.print("code:");
      //Serial.println( code[i] );
      code[i].toCharArray(charArray[i], maxLen );
      Serial.println( charArray[i] );
      if(i < waitsize ){
        Serial.print("wait:");
        Serial.println( wait[i] );
      }
    }
    //ここで一気に送信
    for(int i = 0; i < codesize  ; i++){
      IrSender.sendPronto( charArray[i] );
      if(i < waitsize ){
        delay( wait[i] );
      }
    }
  }else{ //改行コード無しの場合、そのまま送信
    char Buf[sc.length()];
    sc.toCharArray(Buf, sc.length()); //char型に変換
    Serial.println( Buf );
    IrSender.sendPronto( Buf );
  }


  delay(20000);
}

 

受信した文字列を改行コードで分割してcharに変換し、コードは送信、待ち時間はdelayで待つ。

というコードです。

文字列を変換しながら送信すると時間がかかってしまうので、変換は事前に済ませて最後にまとめて一気に送るようにしため無駄にループが多くなっています。

 

送信機からの信号が狙い通りの長さで受信機で受信できているか?も確認します。

 

 

僕の場合はここで問題が発覚しました。

信号は受信できるのに実際にTVやエアコンに向けても効かない。。。???

 

なんでー????っといろいろ調べてみましたら、なんと赤外線受信モジュールが5V仕様ものを3.3Vで使っていたことが判明!!

モジュールの電源がきちんと確保できていないので、一見信号を受信しているけれど、実際にはまともに信号が受信できていなかった・だけ。というオチでした。

 

dreamerdream.hateblo.jp

 

ダイオードを使って5V仕様のモジュールを3.3V仕様に変更しました。

空中配線で無駄にごちゃごちゃしてますが、実験回路なのでご愛嬌。

 

dreamerdream.hateblo.jp

 

きちんとした信号が受信できていればTVもエアコンもきちんと反応してくれました。

 

3.3Vで駆動可能な受光モジュール↓

 

 

素直にこれを使えばごちゃごちゃせずに済みますね。

 

送信には赤外線送信用LED。

 

参考ページでは4ΩでMOSFETを使って5Vで送信されていましたが、LED1発ならFETを使うぐらい電力は消費しないと思います。

僕の実験環境ではGPIOからの3.3Vに47Ω抵抗で光らせています。それでも1〜2mぐらいで機器は反応していますし、以前ラズパイで作ったものでは数発をトランジスタ2SC1815でドライブさせていました。

 

追記、バッファメモリが不足するとうまく動作しません

dreamerdream.hateblo.jp

 

 

ひとまず赤外線受信送信の実験は成功しました。

次はSocket通信です。

dreamerdream.hateblo.jp

 

 

 

kampa.me