(Arduino)ESP32モジュールを用いた赤外線学習リモコン送受信プログラム

#プログラム動作内容
今回は、前回機能（赤外線リモコン受信モジュール(IRM3638)を使用して様々な赤外線リモコンデータ(テレビ、扇風機、エアコン等)を受信したものをArduinoのシリアルモニタにメーカーや受信データを表示）に加え、一時的にその受信データを保存し、スイッチを押すことで受信データを赤外線LED(L53F3BT)を用いて送信するプログラムを作成する。
他機能として、スイッチを２種類用意している。
・受信した赤外線データを送信するタクトスイッチ
・再度赤外線受信モードに変更するタクトスイッチ

#赤外線LED(L-53F3BT)の使用方法
[L-53F3BT](https://pdf1.alldatasheet.jp/datasheet-pdf/view/89553/KINGBRIGHT/L-53F3BT.html)は、電流を流すことによって赤外線を発光させるLEDである。
この赤外線LEDを選んだ理由は、3.3Vで駆動できて購入先の電子工作店にこれがあったため使用している。
接続方法としては、抵抗(電流調整用)を用いて直列に接続するだけで問題はないと思っていたが、ESP32はWiFi接続時の消費電力が大きい特徴がある。
実際に、単純に直列で接続していたが動作が不安定であるケースが見られた。
こちらの原因としては、ESP32から供給される電流値が小さく赤外線LEDの動作に不具合が生じている可能性が高いと考えられた。（テスターが故障していたため、ESP32ピンからの出力電流は不明）
その不具合を解決するために、電流を一定にするためトランジスタを使用して回路を作成することによって不具合動作が解決された。
以下の右側のような回路接続としている。（詳しくは[過去記事](https://qiita.com/pontaman007/items/f47ead88beacf50e9912)参照ください。）
<img width="1766" alt="スクリーンショット 2020-03-22 22.24.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/d03d9948-98a9-0f22-8bbd-c61c9c1e92de.png">

#回路図
[過去の記事](https://qiita.com/pontaman007/items/f47ead88beacf50e9912)で回路図について作成しているので詳細はそちらを確認ください。
ピン接続は以下を参考にしてください。

| 名称 | 使用部品 | データのやり取り | ESP32への接続ピン |
|:---:|:-------:|:-----------:|:---------------:|
| 赤外線受信モジュール | IRM-3638 | 入力 | OUT:17 |
| タクトスイッチ1(赤外線送信用) | タクトスイッチ | 入力 | 34 |
| タクトスイッチ2(赤外線受信許可用) | タクトスイッチ | 入力 | 35 |
| 赤外線LED | L-53F3BT  | 出力 | トランジスタ(ベース):16 |

<img width="1286" alt="スクリーンショット 2020-03-22 22.26.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/fe8f5108-f798-1bb3-6318-b8fbd9a3409f.png">

#作成プログラムについて
前回作成した、[(Arduino)ESP32モジュールを用いた赤外線リモコン受信プログラム](https://qiita.com/pontaman007/items/97f00a6eba5d0b649f91)に一部追加してプログラムを作成した。
実際に動作した状態を以下に示す。
<b>・テレビリモコン(AQUOS_SHARP)の電源ボタン操作</b>　※なぜかPANASONICと認識される
<img width="1678" alt="スクリーンショット 2020-03-22 22.40.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/16d3eb1c-b0b5-103d-00a5-3996de389a39.png">
<img width="907" alt="スクリーンショット 2020-03-22 22.41.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/3474d5da-e612-6564-f63e-6494c774e210.png">
<b>・赤外線送信ボタン(SW1)操作後、リセットボタン(SW2)操作</b>
<img width="597" alt="スクリーンショット 2020-03-22 22.46.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/bff4505b-160d-fcc4-442c-6d71dc19e78d.png">
<img width="907" alt="スクリーンショット 2020-03-22 22.42.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/f3e7127d-bf00-c379-bcf0-a037fba3fed4.png">

以下は今回使用したプログラムなのでコピーして使用してみて下さい。
※前回プログラムからの追加箇所は分かるように記載しています。

```C:02_IR_COMMUNICATION_TEST2
/*  作成日；2020/3/6
    ファイル名；02_IR_COMMUNICATION_TEST2
    プログラム内容；・赤外線リモコンからの情報をシリアルモニターへ出力し、各変数へ受信データを格納して、赤外線受信を禁止する
    　　　　　　　　・SW1(34ピン)を押すと受信したデータを１度送信する
    　　　　　　　　・SW2(35ピン)を押すと赤外線受信を許可する
    使用部品；ESP32、IRM-3638、タクトスイッチ×2、L-53F3BT、
    ESP32へのピン接続；(17:IRM_Vout)、(16:L-53F3BT_トランジスタ(ベース))、(34,35:タクトスイッチ)
 */

//各ライブラリ
#include <Arduino.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.h>

/****************************追加内容(下)****************************/
#include <IRsend.h>
/****************************追加内容(上)****************************/

const uint16_t kRecvPin = 17; //赤外線受信ピン

/****************************追加内容(下)****************************/
const uint16_t kIrLed = 16; //赤外線送信ピン
const uint16_t sw1 = 34;  //赤外線受信データ送信ボタン
const uint16_t sw2 = 35;  //赤外線受信許可変更ボタン
/****************************追加内容(上)****************************/

const uint16_t kCaptureBufferSize = 1024; //受信データ格納バッファ数(赤外線ON/OFFの時間を格納)

/****************************追加内容(下)****************************/
decode_type_t maker_code; //メーカーコード挿入変数
/****************************追加内容(上)****************************/

String recv_code = ""; //受信コード挿入変数
uint16_t recv_bits; //受信コード総ビット数挿入変数
uint16_t recv_onoff_bits; //ONOFF回数挿入変数
uint16_t recv_state_bits; //A/C受信コード挿入変数
uint32_t recv_address; //受信アドレス挿入変数
uint32_t recv_command; //受信コマンド挿入変数
uint64_t recv_data; //受信データ挿入変数

/****************************追加内容(下)****************************/
uint16_t rawData[1000]; //送信データ格納バッファ(AC以外)
unsigned char samsungState[500];  //送信データ格納バッファ(AC)
bool recv_pms_flag = true;  //赤外線受信許可フラグ
/****************************追加内容(上)****************************/


//kTimeout = 赤外線データの終了を判断するmsの値(kTimeoutミリ秒間受信がなければ終了と判断する)
#if DECODE_AC //エアコンの赤外線リモコンの場合
const uint8_t kTimeout = 50;  //50ms
#else   //その他の赤外線リモコン機器の場合
const uint8_t kTimeout = 15;  //15ms
#endif

const uint16_t kMinUnknownSize = 12;  //不明な赤外線受信をカットするための変数

IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);  //IRrecv(赤外線受信ピン、受信データ格納バッファサイズ、データ終了判断時間、値保存機能(true推奨))
decode_results results;  //受信結果を保存するクラスのインスタンスを生成

/****************************追加内容(下)****************************/
IRsend irsend(kIrLed);  //赤外線送信クラスのインスタンスを生成
/****************************追加内容(上)****************************/


/*  初期設定  */
void setup() {

/****************************追加内容(下)****************************/
  pinMode(sw1, INPUT); //SW1を入力
  pinMode(sw2, INPUT); //SW2を入力
  /****************************追加内容(上)****************************/
  
  Serial.begin(115200); //シリアル通信レート
  
#if DECODE_HASH
  irrecv.setUnknownThreshold(kMinUnknownSize);  //不明な赤外線受信データは除外する
#endif

  irrecv.enableIRIn();  //赤外線受信開始

/****************************追加内容(下)****************************/
  irsend.begin(); //赤外線送信開始
/****************************追加内容(上)****************************/
  
}
/*  初期設定  */


/*  メイン本文  */
void loop() {

/****************************追加内容(下)****************************/
  if (irrecv.decode(&results) & recv_pms_flag) {  //赤外線受信および受信許可モードになれば
/****************************追加内容(上)****************************/

/****************************追加内容(下)****************************/
    maker_code = results.decode_type; //メーカーコード取得
/****************************追加内容(上)****************************/
    recv_code = resultToHexidecimal(&results);  //赤外線受信データを16進数で取得
    recv_bits = results.bits; //受信データの総Bit数を取得
    recv_onoff_bits = getCorrectedRawLength(&results);  //受信データのON/OFF切り替え回数を取得

/****************************追加内容(下)****************************/
    Serial.println("Maker : " + typeToString(maker_code,false)); //メーカーコード出力
/****************************追加内容(上)****************************/

    Serial.println("Code : " + recv_code);  //赤外線受信データを16進数で表示
    Serial.println("Bits : " + String(recv_bits)); //受信データの総Bit数を表示

    Serial.print("ON/OFF Data : rawData[" + String(recv_onoff_bits) + "] = {");  //受信データのON/OFF切り替え回数を表示
    for(int i=1; i < recv_onoff_bits + 1; i++){ //受信データのON/OFF切り替え時間を表示
      
/****************************追加内容(下)****************************/
      rawData[i-1] = results.rawbuf[i] * kRawTick;  //受信ONOFFデータを格納
      Serial.print(String(rawData[i-1]) + ", ");
/****************************追加内容(上)****************************/

    }
    Serial.println("}");

    if (hasACState(maker_code)) {  //メーカーコードがACだった場合

      recv_state_bits = recv_bits / 8;
      Serial.print("State Data : state[" + String(recv_state_bits) + "] = {");  //赤外線受信データの分割ビット数を表示
      for(int i = 0; i < recv_state_bits; i++) {  //赤外線受信データの分割したものを16進数で表示
        Serial.print("0x");
        if (results.state[i] < 0x10) {
          Serial.print("0");
        }
        Serial.print(uint64ToString(results.state[i], 16));
        if (i < recv_state_bits - 1) {
          Serial.print(kCommaSpaceStr);
        }
        
/****************************追加内容(下)****************************/
        samsungState[i] = char(results.state[i]); //AC受信データ格納
/****************************追加内容(上)****************************/

      }
      Serial.println("}");
      
    }else{  //メーカーコードがAC以外だった場合
      
      if (results.address > 0 || results.command > 0) { //アドレスとコマンドを16進数で表示

        recv_address = results.address; //アドレスを取得
        recv_command = results.command; //コマンドを取得
        
        Serial.println("Address : 0x" + uint64ToString(recv_address, 16));
        Serial.println("Command : 0x" + uint64ToString(recv_command, 16));

      }

      recv_data = results.value;  //データを取得

      //データを16進数で表示
      Serial.print("Data : 0x");
      serialPrintUint64(recv_data, HEX);
      Serial.println("");
      
    }
    
    Serial.println();

/****************************追加内容(下)****************************/
    recv_pms_flag = false; //赤外線受信許可フラグOFF
    Serial.println("Reception Prohibited");
/****************************追加内容(上)****************************/

  }

/****************************追加内容(下)****************************/
  if(digitalRead(sw1) == HIGH){ //赤外線データ送信ボタン(SW1)が押されたら
    delay(500); //チャタリング防止

    if (recv_pms_flag){
      Serial.println("Data Nothing");
      
    }else if (hasACState(maker_code)) {  //メーカーコードがACだった場合
      
      irsend.send(maker_code, samsungState, recv_state_bits); //AC送信データコード(メーカーコード、Stateデータ、Stateビット数)
      
    }else{  //メーカーコードがAC以外だった場合

      //SHARPのテレビ電源OFF後、1分ちょい開くと1度のデータコード送信だと電源が入らないので、常時リーダーコードを頭につけて送信する
      irsend.sendRaw(rawData,2,38); //赤外線のデータコードを38kHz帯で送信
      delay(10);  //1つの送信データは「リーダー部+8ms以上off」が必要なので10ms待つ
      irsend.send(maker_code, recv_data, recv_bits, 0); //AC以外送信データコード(メーカーコード、送信データ、送信ビット数、リピート回数)
      Serial.println("Data Transmission");
      
    }
  }

  if(digitalRead(sw2) == HIGH){ //赤外線受信許可ボタン(SW2)が押されたら
    delay(500); //チャタリング防止
    recv_pms_flag = true; //赤外線受信許可フラグON
    Serial.println("Cancel Reception Prohibited");
  }
/****************************追加内容(上)****************************/

}
/*  メイン本文  */
```

以下から各ブロックの説明をしていきます。
※追加箇所のみ説明します。

①ライブラリ定義、グローバル変数設定等

```C:02_IR_COMMUNICATION_TEST2(抜粋)
#include <IRsend.h>

const uint16_t kIrLed = 16; //赤外線送信ピン
const uint16_t sw1 = 34;  //赤外線受信データ送信ボタン
const uint16_t sw2 = 35;  //赤外線受信許可変更ボタン

decode_type_t maker_code; //メーカーコード挿入変数

uint16_t rawData[1000]; //送信データ格納バッファ(AC以外)
unsigned char samsungState[500];  //送信データ格納バッファ(AC)
bool recv_pms_flag = true;  //赤外線受信許可フラグ

IRsend irsend(kIrLed);  //赤外線送信クラスのインスタンスを生成
```

「#include <IRsend.h>」：赤外線送信に関する関数ライブラリ
「const uint16_t kIrLed = 16」：赤外線LED送信接続ピン(トランジスタ_ベース)
「const uint16_t sw1 = 34」：赤外線受信データ送信ボタン接続ピン
「const uint16_t sw2 = 35」：赤外線受信許可変更ボタン接続ピン
「decode_type_t maker_code」：前回はString型で定義していたが、後にdecode_type_t型で使用するため変更した
「uint16_t rawData[1000]」：送信データ格納バッファ(AC以外)
「unsigned char samsungState[500]」：送信データ格納バッファ(AC)
「bool recv_pms_flag = true」：赤外線受信許可フラグ(初期状態はON)
「IRsend irsend(kIrLed)」：赤外線送信クラスのインスタンスを生成

②初期設定

```C:02_IR_COMMUNICATION_TEST2(抜粋)
/*  初期設定  */
void setup() {

  pinMode(sw1, INPUT); //SW1を入力
  pinMode(sw2, INPUT); //SW2を入力

  irsend.begin(); //赤外線送信開始
  
}
/*  初期設定  */
```

「pinMode(sw1, INPUT)」：SW1(赤外線送信ボタン)を入力に設定
「pinMode(sw2, INPUT)」：SW2(赤外線受信許可ボタン)を入力に設定
「irsend.begin()」：赤外線送信モード開始

③メイン(ループ)

```C:02_IR_COMMUNICATION_TEST2(抜粋)
/*  メイン本文  */
void loop() {

  if (irrecv.decode(&results) & recv_pms_flag) {  //赤外線受信および受信許可モードになれば

    maker_code = results.decode_type; //メーカーコード取得

    Serial.println("Maker : " + typeToString(maker_code,false)); //メーカーコード出力

    for(int i=1; i < recv_onoff_bits + 1; i++){ //受信データのON/OFF切り替え時間を表示

      rawData[i-1] = results.rawbuf[i] * kRawTick;  //受信ONOFFデータを格納
      Serial.print(String(rawData[i-1]) + ", ");

    }

        samsungState[i] = char(results.state[i]); //AC受信データ格納

    recv_pms_flag = false; //赤外線受信許可フラグOFF
    Serial.println("Reception Prohibited");

  }

  if(digitalRead(sw1) == HIGH){ //赤外線データ送信ボタン(SW1)が押されたら
    delay(500); //チャタリング防止

    if (recv_pms_flag){
      Serial.println("Data Nothing");
      
    }else if (hasACState(maker_code)) {  //メーカーコードがACだった場合
      
      irsend.send(maker_code, samsungState, recv_state_bits); //AC送信データコード(メーカーコード、Stateデータ、Stateビット数)
      
    }else{  //メーカーコードがAC以外だった場合

      //SHARPのテレビ電源OFF後、1分ちょい開くと1度のデータコード送信だと電源が入らないので、常時リーダーコードを頭につけて送信する
      irsend.sendRaw(rawData,2,38); //赤外線のデータコードを38kHz帯で送信
      delay(10);  //1つの送信データは「リーダー部+8ms以上off」が必要なので10ms待つ
      irsend.send(maker_code, recv_data, recv_bits, 0); //AC以外送信データコード(メーカーコード、送信データ、送信ビット数、リピート回数)
      Serial.println("Data Transmission");
      
    }
  }

  if(digitalRead(sw2) == HIGH){ //赤外線受信許可ボタン(SW2)が押されたら
    delay(500); //チャタリング防止
    recv_pms_flag = true; //赤外線受信許可フラグON
    Serial.println("Cancel Reception Prohibited");
  }

}
/*  メイン本文  */
```

「if (irrecv.decode(&results) & recv_pms_flag)」：赤外線受信および受信許可モードになれば
※前回は赤外線を受信した場合のみだったが、そうしてしまうと自身が赤外線LEDでデータを送信したものを拾ってしまいデータが上書きされてしまうため、受信許可フラグもONにならないとデータを更新しないようにしている
「maker_code = results.decode_type」：メーカーコードをdecode_type_t型で取得
※プログラム内でdecode_type_t型で使用するためString変換はやめた
「Serial.println("Maker : " + typeToString(maker_code,false))」：メーカーコードをシリアルモニタへ出力
※maker_codeがdecode_type_t型になったため、表示させる前にString型に変換している
「rawData[i-1] = results.rawbuf[i] * kRawTick」：赤外線データを送信させるための受信ONOFFデータを格納
「samsungState[i] = char(results.state[i])」：AC関係の赤外線データを格納
「recv_pms_flag = false」：赤外線受信フラグをOFF
「Serial.println("Reception Prohibited")」：赤外線受信を禁止する旨の表示

「if(digitalRead(sw1) == HIGH)」：赤外線データ送信ボタン(SW1)が押されたら
「delay(500)」：スイッチによるチャタリングを防止するためのディレイ関数
「if (recv_pms_flag)」：赤外線を1度も受信していない状態で送信ボタンが押された場合

「else if (hasACState(maker_code))」：受信したデータのメーカーコードがACだった場合
「irsend.send(maker_code, samsungState, recv_state_bits)」：AC送信データコード(メーカーコード、Stateデータ、Stateビット数)

受信したデータのメーカーコードがAC以外だった場合
「irsend.sendRaw(rawData,2,38)」：赤外線のデータコードを38kHz帯で送信
※SHARPのテレビ電源OFF後、1分ちょい開くと1度のデータコード送信だと電源が入らないので、常時リーダーコードを頭につけて送信することでスイッチ操作に影響を与えることなく、操作が可能にするために必要な処理
「delay(10)」：1つの送信データは「リーダー部+8ms以上off」が必要なので10ms待つ
「irsend.send(maker_code, recv_data, recv_bits, 0)」：AC以外送信データコード(メーカーコード、送信データ、送信ビット数、リピート回数)
「Serial.println("Data Transmission")」：赤外線送信した旨の表示

「if(digitalRead(sw2) == HIGH」：赤外線受信許可ボタン(SW2)が押されたら
「delay(500)」：スイッチによるチャタリングを防止するためのディレイ関数
「recv_pms_flag = true」：赤外線受信許可フラグON
「Serial.println("Cancel Reception Prohibited")」：赤外線受信許可になった旨の表示

今回のプログラムで赤外線受信したデータ保存し、そのデータをそのまま送信できるようになった。
次回以降すぐに実施はしないが、今後スマートフォンと連携させ、リモコンの各ボタン情報を記憶させてスマートフォンから送信された信号をESP32が受信して、赤外線データを送信するプログラムおよびアプリを構築する予定ですのでまたご覧になってください。

#次回記事予定
温湿度気圧センサ(BME280)から取得した各種データをシリアルモニタへ表示させるプログラムの作成

#参考文献
・http://shrkn65.nobody.jp/remocon/aeha.html