2020年7月18日土曜日

【Arduino】赤外線でエアコンを操作する

どうもcaketetuです。現在、自宅のスマートホーム化を進めています。
その中でエアコンの自動化は必要不可欠と言えます。IoT対応のエアコンを
買えば楽ですが、価格が高く、賃貸なので簡単にエアコン交換ができません。
よって今回は後付けでもエアコン自動化できるように赤外線LEDとセンサーを使って
Arduinoから操作してみます。

早速アマゾンで赤外線リモコンセットを購入します。
私が購入したのはこれです。
もう売り切れでしたが、同じようなやつはたくさんありました。


・赤外線信号を調べる

「Arduino 赤外線」とかで調べると赤外線コードやライブラリがたくさん出て
きますが、残念なことに我が家のエアコンは対応していませんでした。
なのでエアコンのリモコンから出ている信号を調べなければなりません。

Arduinoに赤外線センサーを取り付けて信号を読み出します。
以下のプログラムを使用します。

#define READ_PIN 2
#define LOW_STATE 0
#define HIGH_STATE 1

void setup(){
  Serial.begin(115200UL);
  pinMode(READ_PIN,INPUT);

  Serial.println("Ready to receive");
}

void waitLow() {
  while (digitalRead(READ_PIN)==LOW) {
    ;
  }
}

int waitHigh() {
  unsigned long start = micros();
  while (digitalRead(READ_PIN)==HIGH) {
    if (micros() - start > 5000000) {
      return 1;
    }
  }
  return 0;
}

unsigned long now = micros();
unsigned long lastStateChangedMicros = micros();
int state = HIGH_STATE;

void loop() {
    if (state == LOW_STATE) {
      waitLow();
    } else {
      int ret = waitHigh();
      if (ret == 1) {
        Serial.print("\n");
        return;
      }
    }

    now = micros();
    Serial.print((now - lastStateChangedMicros) / 10, DEC);
    Serial.print(",");
    lastStateChangedMicros = now;
    if (state == HIGH_STATE) {
      state = LOW_STATE;
    } else {
      state = HIGH_STATE;
    }
}

また、Arduinoとセンサの配線は次の通りです。

Arduino センサー
 IO2-----OUT
 GND----GND
 5V-----VCC

実行しリモコンを照射しながらシリアルモニタで見てみると数字の羅列が出力されます。
これは赤外線のONとOFFが切り替わる間の時間を表しています。これを解析していきます。

おそらくAEHAフォーマットで40ぐらいが1Tで120ぐらいが3Tでしょうか。
ボタンを何回も押していき、変化した値を見て信号を解析するのですが
いちいちやっていては大変なのでデコードプログラムをVSのC#で作りました。
GUIのプログラムを全部乗せるのは大変なのでコア部分だけ載せます。

		private void TimerSerial_Tick(object sender, EventArgs e)
        {
            //二秒ごとにバッファからすべて吐き出す
            String buffer;  //シリアルバッファ―
            buffer = Serial1.ReadExisting();    //バッファからすべて読み出す
            RTB_Raw_Data.Text = buffer;         //バッファをすべて書き出す
            if (buffer.Length > 500)
            {
                string[] arr = buffer.Split(',');   //','で区切って取り出す
                int ms, digit=0;        //時間、桁数
                int code_size=0;        //HEXコードサイズ
                UInt16 ir_sig = 0x00;   //HEXデータ
                //有功データから一つとびに値を見る
                for (int i = 4; i < arr.Length; i+=2)
                {
                    int.TryParse(arr[i], out ms);   //データを整数に直す
                    if (ms > 80) ir_sig |= 0x80;    //データが80以上(=1)なら先頭ビットを1に

                    digit++;        //桁数プラス
                    if ( digit>=8 ) //桁数が8なら表示
                    {
                        digit = 0;  //桁数リセット
                        String sighex = string.Format("{0,3:X2}", ir_sig);  //HEXにフォーマット指定
                        RTB_IR_Signal.Text += sighex + " "; //表示
                        ir_sig = 0x00;  //ir_sigリセット
                        code_size++;    //コードサイズ更新
                    }

                    ir_sig /= 2;    //ir_sigの桁をひとつ下げる
                }
                RTB_Raw_Data.Text += "  DataSize=" + arr.Length.ToString() + "\n";  //データサイズ表示
                RTB_IR_Signal.Text += "  DataSize=" + code_size.ToString() + "\n";  //データサイズ表示

            }
        }

簡単に説明すると
  1. シリアルバッファからすべて読み出す
  2. ","ごとに分割してそれぞれをint型に直す。
  3. 最初のいらない部分を除いて一つ飛ばしに値を読んでいき80以上なら一番左のbitを1に、それ以外なら0にする。その後右にビットシフト(/2)する。
  4. 8回ごとにデータを16進数でTEXTに追加する。
VC#を選んだのは実装がラクだったからです。アルゴリズムが分かれば他言語でも簡単に実装できると思います。

検証の様子です。
結構取りこぼしてますが信号数33個が正常なデータと見ていいでしょう。



以下が検証から得られたデータです。~はビット反転を表します。


・信号を送信する

調べられた値を使って信号を送信してみます。解析時と逆の手順で16進数の
コードをON、OFF信号に変換して送信します。

メインループ
#include "IR_Ctrl.h"

//赤外線送信データ
byte ir_hexdata[33];

void setup() {
  // put your setup code here, to run once:
  ir_Init();                  //赤外線モジュール初期化
  for(int i=0;i<33;i++){
    ir_hexdata[i]=i;
  }
  delay(500);
}

void loop() {
  // put your main code here, to run repeatedly:
  ir_send(ir_hexdata);    //赤外線照射
  delay(2000);
}
赤外線リモコン送信ヘッダ
//*********************************************************
//    IR Control header
//    
//*********************************************************
#define IR_SEND_PIN  6  //define ir-signal send pin
#define IR_BUTTON 11
#define IR_MODE 25
#define IR_TEMP 13
#define IR_POWER 27

#define IR_POWER_ON  0xD1
#define IR_POWER_OFF 0cC1

#define IR_BTN_OFF 0x13
#define IR_BTN_UP  0x44
#define IR_BTN_DOWN 0x43
#define IR_BTN_TISTOP 0x31
#define IR_BTN_TIWKUP 0x22
#define IR_BTN_WIND_LEVEL 0x42
#define IR_BTN_WIND_DIRECTION 0x81

#define IR_WIND_AUTO    0x50
#define IR_WIND_STRONG  0x40
#define IR_WIND_NOMAL   0x30
#define IR_WIND_WEAK    0x20

#define IR_MODE_COOLING  0x03
#define IR_MODE_DEHUMID  0x05
#define IR_MODE_HEATING  0x06


//----------------------------------------------------------
//    sumple send data
//----------------------------------------------------------
byte ir_heating[33] = {0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 
                      0x33, 0x92, 0x6D, 0x13, 0xEC, 0x50, 0xAF, 0x00,
                      0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,  
                      0xFF, 0x56, 0xA9, 0xD1, 0x2E, 0x00, 0xFF, 0x00, 0xFF};

//01  10  00  40  BF  FF  00  CC  33  92  6D  13  EC  50  AF  00  FF  00  FF  00  FF  00  FF  00  FF  56  A9  D1  2E  00  FF  00  FF 


byte ir_cooling[33] = {0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 
                      0x33, 0x92, 0x6D, 0x13, 0xEC, 0x68, 0x97, 0x00,
                      0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,  
                      0xFF, 0x53, 0xAC, 0xD1, 0x2E, 0x00, 0xFF, 0x00, 0xFF};

//01  10  00  40  BF  FF  00  CC  33  92  6D  13  EC  68  97  00  FF  00  FF  00  FF  00  FF  00  FF  53  AC  D1  2E  00  FF  00  FF 

byte ir_dehumid[33] = {0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 
                      0x33, 0x92, 0x6D, 0x13, 0xEC, 0x68, 0x97, 0x00,
                      0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,  
                      0xFF, 0x25, 0xDA, 0xD1, 0x2E, 0x00, 0xFF, 0x00, 0xFF};

// 01  10  00  40  BF  FF  00  CC  33  92  6D  13  EC  68  97  00  FF  00  FF  00  FF  00  FF  00  FF  25  DA  D1  2E  00  FF  00  FF 

byte ir_off[33]      = {0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 
                      0x33, 0x92, 0x6D, 0x13, 0xEC, 0x6C, 0x93, 0x00,
                      0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,  
                      0xFF, 0x53, 0xAC, 0xC1, 0x3E, 0x00, 0xFF, 0x00, 0xFF};


//----------------------------------------------------------
//    Initialize
//----------------------------------------------------------
void ir_Init( void ){
      //check led setup
      pinMode(IR_SEND_PIN,OUTPUT);    //led pin set output
      digitalWrite(IR_SEND_PIN,LOW); //set initial value(high-level off)  
}

//----------------------------------------------------------
//    IR send signals
//----------------------------------------------------------
void sendSignal( unsigned short *data ,int data_len) {
  //int dataSize = sizeof(data) / sizeof(data[0]);
  int dataSize = data_len;
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data[cnt];
    unsigned long us = micros();
    do {
      digitalWrite(IR_SEND_PIN, 1 - (cnt&1));
      delayMicroseconds(10);
      digitalWrite(IR_SEND_PIN, 0);
      delayMicroseconds(8);
    } while (long(us + len - micros()) > 0); // 送信時間に達するまでループ
  }
}

//----------------------------------------------------------
//    IR send bytes
//----------------------------------------------------------
void ir_send(byte *data){
    int khz = 38; // 38kHz carrier frequency for the NEC protocol
    unsigned short send_buf[16] = {390, 390, 390, 390, 390, 390, 390, 390,
                    390, 390, 390, 390, 390, 390, 390, 390};    //send buffer
    unsigned short for_data[2] = {3340,1700};   //head data
    unsigned short low_data[2] = {1240,390};    //tail data
    byte hex;
    
    sendSignal(for_data, sizeof(for_data) / sizeof(for_data[0]));
    
    for(int i=0;i<33;i++){
        hex = data[i];
        for(int j=1;j<16; j+=2){
          send_buf[j] = 410;
          if( hex&0x01 == 0x01 ){ send_buf[j] = 1240;}
          hex/=2;
        }
        sendSignal(send_buf, sizeof(send_buf) / sizeof(send_buf[0]));
    }
    
    sendSignal(low_data, sizeof(low_data) / sizeof(low_data[0]));
}
実行環境によって多少の調整が必要です。
delayMicroseconds()の値を調整するとうまくいくかもしれません。

Arduinoを二つ使って受信、送信をテストします。
テストでは、01,02,03・・・の信号を送信しています。



・エアコンを操作する

実際にエアコンを操作してみます。LEDをピン直結では発光が弱いので
LEDを3個使いトランジスタで信号を増幅して照射します。

照射モジュール


動作の様子
無事信号が認識されました。ちょっと光軸がずれていても認識してくれます。




無事Arduinoからエアコンを操作することができました。情報も結構出ていたので
簡単なのかなと思いましたが、意外と解析にてこずって時間がかかりました。
赤外線の知識、信号の作り方、信号の検証となかなかハードです。ライブラリ
もあるので対応していたらそちらを使ったほうがラクにできると思います。

スマートホーム化に際しては、これをWEB経由で操作できるようにしてみます。


0 件のコメント:

コメントを投稿