DAIKINなどのリモコンの解析

# DAIKINのリモコンのフォーマット
**Arduino**でDAIKINのエアコンをコントロールしたい。

**IRremoteのライブラリ** ではなぜかうまくいかない・・・なぜだ・・・

結論から言うと、家電協のフォーマットに従っていた。ただし、IRremoteはうまく扱えなかった。

仕方ないので計測してみよう。
![IMG_20221223_173103082_4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/69d3cba3-e70b-6112-9d4d-91b61b3800db.jpeg)

回路は
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/9f951ec5-b68c-b257-3577-207402fa28b3.png)

フォトトランジスタ（図ではダイオード）につける抵抗は50kΩにしました。抵抗が小さい方がしっかり反応します。

結果は
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/4eb939fc-c611-fa6c-43be-bc64e3506402.png)

ON,OFFが繰り返している様子が見て取れる。

ON時の波形をさらに細かく見ると次のようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/dc8d544d-8936-b2cc-5b25-0e7827b4c9ac.png)
ON時は**37kHzで振動している**。


PWMの割合に関しては50%ではないのが少し気になる（後述50%でも問題なくエアコンは反応した）。

（注意）
フォトトランジスタは光が入ると電流を流すようになるので、出力電圧はLOWに落ちる。
つまり、**赤外線LEDは**フォトトランジスタで計測された結果の**HIGH,LOWを反転させる**必要がある。

# 赤外線リモコンのフォーマットについて

http://elm-chan.org/docs/ir_format.html

にある通り

* NEC
* 家電協
* SONY

の３つがある。
それぞれの特徴としては、

## NEC
38kHz搬送
T=0.562ms

## 家電協
33~40kHz搬送
T=0.35ms~0.5ms

## SONY
40kHz搬送
T=0.6ms



# センサーについて
![4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/09b87e31-dd84-abe3-438a-a97cacc13600.jpeg)
右がフォトトランジスタ、左がよくつかわれる赤外線リモコン用のセンサーだ。

## ピンアサイン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/802dbef0-e4fc-0695-dd30-2cf3b5a25ffb.png)

## 2つの違い
実は違いがある！

赤外線リモコン用のセンサーは**37kHzの光のみを選択的に計測する**。
日常にあふれる光から特定周波数のみを見ることで外乱に強いセンサになる。

一方で出力が37kHzで振動していては、取り扱いにくいため**平滑化**が行われる。

フォトトランジスタの計測結果は
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/5d3642f9-82c0-4a3b-f923-921c14231a6f.png)

赤外線リモコン用のセンサの計測結果は
![5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/acdc2120-ca06-80ee-a41c-a900a52937f5.jpeg)

振動が取り除かれている。


赤外線リモコン用のセンサの方が取り扱いやすいが、赤外線LEDを使ってリモコンを作ろうとしたときに37kHzで振動させることを忘れてしまう点には注意したい。


# 読み取る

Arduinoでパルスの間隔を計測する。pin2にリモコン用センサの出力をつなぐ。
pin2の外部割込みを使用し、立下り（FALLING）の時間間隔をus単位で計測する。
最後に割り込みされてから、一定以上時間が経った時に送信終了とみなし、結果をUSBで転送する。

```C++:Ir_read_us
#define box_length 300

volatile unsigned long time_box[box_length];
volatile int pos = 0;
volatile unsigned long before_time;
volatile boolean flag = false;

void setup() {
  pinMode(2,INPUT);
  attachInterrupt(0,time_save,FALLING);
  Serial.begin(115200);
}

void time_save(){
  unsigned long now_time = micros();
  time_box[pos] = now_time - before_time; 
  before_time = now_time;
  pos++;
  if(pos>box_length){
    pos = 0;
    flag = true;
  }
}

void loop() {
  if(flag == true){
    Serial.println("overflow");
  }
  unsigned long now_time = micros();
  if( now_time - before_time > 500000  and  pos > 0){
    for(int i = 0 ; i <  pos ; i++){
      Serial.print(time_box[i]);
      Serial.print(",");
    }
    Serial.println(" ");
    pos = 0;
  }
  delay(10);

}
```

結果
```
8256416,864,864,868,864,868,25728,5192,1732,868,864,868,1728,868,864,864,868,1732,864,1732,1732,864,1732,1732,1732,1732,1732,864,868,1728,868,864,868,864,868,864,864,868,864,868,1728,868,1732,864,868,864,1732,1732,864,868,864,868,864,864,868,864,868,864,868,864,868,864,868,864,1732,1732,1732,864,1732,864,1732,1732,35064,5192,1732,864,868,864,1732,868,864,864,868,1732,864,1732,1732,868,1728,1732,1732,1732,1732,864,868,1732,864,864,868,760,972,864,868,864,868,788,940,1732,868,864,868,864,1728,868,868,864,868,864,864,868,864,868,868,792,936,868,864,864,868,864,868,864,1732,864,1736,864,1732,864,35064,5196,1728,864,868,868,1728,868,864,868,864,1732,868,1728,1732,868,1732,1732,1728,1732,1732,796,936,1732,864,868,864,868,864,868,864,864,868,864,868,864,864,868,864,868,864,868,1732,864,868,1732,864,864,1732,864,868,864,1736,1728,864,1736,864,868,764,964,864,868,868,864,868,864,864,868,864,868,864,1732,868,1728,1732,1732,1732,1732,864,868,864,868,864,868,864,868,864,868,864,864,868,1728,1736,864,868,860,868,868,864,868,864,864,868,1732,1732,864,868,864,864,868,864,868,864,868,864,868,864,868,864,864,868,864,1732,868,864,868,864,864,1732,1732,868,864,864,868,864,868,864,1732,868,864,864,868,864,868,868,864,1732,864,1732,1732,1732,864,1732,1732, 
```
４００～４５０の倍数の間に大体入っている。

http://elm-chan.org/docs/ir_format.html

の家電協のフォーマットだろう。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/0c351198-44d1-6bc8-f577-682ff4bbdc61.png)
T=430usとして、
0ならT,T
1ならT,3T
みたいな時間間隔なので、このデータを0,1に変換してみよう。

# 自動的に計測結果からデータを読み出す
Python3でデータに変換する。

## 方針
外部割り込みはFALLINGのみである。

* 信号の長さが2Tなら0,4Tなら1として考えられる。
* 12Tなら先頭（Leader）であると考えられる。
* 一番最後のビットは0,1判別不可

![５無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/6395d3e3-e75f-5315-25a2-67e65f6200eb.png)
ということで、一番最後に1を追加する。

* [Leader,data,Delay,data,Delay,data]のような構成をしている。先程の最後のビットと同じでDelayの前のビットも0,1の判別ができない。よって、Delay判定をした際に1をデータ前に追加

## コード

```python
p = "864,864,868,864,868,25728,5192,1732,868,864,868,1728,868,864,864,868,1732,864,1732,1732,864,1732,1732,1732,1732,1732,864,868,1728,868,864,868,864,868,864,864,868,864,868,1728,868,1732,864,868,864,1732,1732,864,868,864,868,864,864,868,864,868,864,868,864,868,864,868,864,1732,1732,1732,864,1732,864,1732,1732,35064,5192,1732,864,868,864,1732,868,864,864,868,1732,864,1732,1732,868,1728,1732,1732,1732,1732,864,868,1732,864,864,868,760,972,864,868,864,868,788,940,1732,868,864,868,864,1728,868,868,864,868,864,864,868,864,868,868,792,936,868,864,864,868,864,868,864,1732,864,1736,864,1732,864,35064,5196,1728,864,868,868,1728,868,864,868,864,1732,868,1728,1732,868,1732,1732,1728,1732,1732,796,936,1732,864,868,864,868,864,868,864,864,868,864,868,864,864,868,864,868,864,868,1732,864,868,1732,864,864,1732,864,868,864,1736,1728,864,1736,864,868,764,964,864,868,868,864,868,864,864,868,864,868,864,1732,868,1728,1732,1732,1732,1732,864,868,864,868,864,868,864,868,864,868,864,864,868,1728,1736,864,868,860,868,868,864,868,864,864,868,1732,1732,864,868,864,864,868,864,868,864,868,864,868,864,868,864,864,868,864,1732,868,864,868,864,864,1732,1732,868,864,864,868,864,868,864,1732,868,864,864,868,864,868,868,864,1732,864,1732,1732,1732,864,1732,1732"

#print(p)


l = p.split(',')
l_int = []

for m in l:
    m = int(m)
    l_int.append(m)

print(l_int )


code = ""

low = 350
high = 500

for time in l_int:
    
    if(low*2 < time < high*2):
        code += '1';
    elif(low*4 < time < high*4):
        code +='0'
    elif(low*12 < time < high*12):
        code +='L'
        
        
    else:
        code += '1'    #Delayの前のデータは1,0判別不能なため
        code += 'D'
        print(time)
        
code += "1"     #末尾のデータは構造上判別不能なのでとりあえず1にしておこう
        
print(code)
        
```
結果

```
'111111DL01110111101001000001101111111111010111001111111111111111000101001DL01110111101001000001101111111111101111011111111111111111110101011DL011101111010010000011011111111111111111101101101110010111111111111111010000011111111111110011111111110011111111111111111011111001111111011111111010001001'
```

# とりあえず、同じようにLEDを発光させる

```
Timer1:37kHz発生
Timer2:450us割り込み
```
として、

```
1-> T,3T
0->T,T
D->30T    (Delay)
L->8T,4T  (Leader)
```
になるようクラスを作る。

## 方針
オブジェクト指向でやってみる。
赤外線データというオブジェクト"Code"を作る

* 送信データ
* 送信位置
* フェーズ情報

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/9b55c44d-4857-bec1-722e-27d29aa4811c.png)

こんな感じに区分けする。タイマー割り込みが入るたびに"phase"はインクリメントされる。

* nextメソッド、タイマー割り込みが入ったときに呼び出される想定

```
1-> T,3T
0->T,T
D->30T    (Delay)
L->8T,4T  (Leader)
```
に従って、"phase"が既定値を超えると送信位置を動かす。

## setup,loop内

```
 /* OC1A,OC1B 37kHz出力
     PWMモード:Fast PWM
     分周比:1
     OCR1A:コンペアマッチLOW
     OCR1B:コンペアマッチLOW
  */
```
かなりレジスタを叩いて作った。
正直読み解けなくなってしまった。

搬送波38kHzを作るためのタイマーと、送信時間単位Tを作るタイマーの２つ必要になるが、
一つにまとめようとして難しくなった記憶がある。

## コード

```C:Code.h
#ifndef CODE
#define CODE
#include <Arduino.h>

class Code {
  private:
    int phase = 0;     //各文字の現在のフェーズ
    int pos = 0;       //文字位置

    String code_string;  
    
  public:
    Code(String n);
    void next();
    int state();
};

#endif
```

```C:Code.cpp
#include "Code.h"



Code::Code(String n) {
  this->code_string = n;
}

int Code::state() {
  char type =  code_string[pos];

  if (type == '1') {      //1
    if ( phase == 0) {
      return 1;
    } else {
      return 0;
    }
  } else if (type == '0') { //0
    if ( phase == 0) {
      return 1;
    } else {
      return 0;
    }
  } else if (type == 'H') { //Header
    if ( phase % 2 == 0 ) {
      return 1;
    } else {
      return 0;
    }
  } else if (type == 'L') { //Leader
    if ( phase <= 7 ) {
      return 1;
    } else {
      return 0;
    }
  }else{
    return 0;
  }
}

void Code::next() {
  phase =  phase + 1;
  char type =  code_string[pos];

  if (type == '1') {      //1
    if ( phase >= 2) {
      pos ++;
      phase = 0;
    }
  } else if (type == '0') { //0
    if ( phase >= 4) {
      pos ++;
      phase = 0;
    }
  } else if (type == 'H') { //Header
    if ( phase >= 12) {
      pos ++;
      phase = 0;
    }
  } else if (type == 'L') { //Leader
    if ( phase >= 12) {
      pos ++;
      phase = 0;
    }
  } else if (type == 'D') { //Delay
    if ( phase >= 50) {
      pos ++;
      phase = 0;
    }
  } else {                 //END
    if ( phase >= 100) {
      pos = 0;
      phase = 0;
    }
  }

}

```

```C:Ir_send
#include "Code.h"

volatile boolean flag = true;
volatile Code code("111111DL01110111101001000001101111111111010111001111111111111111000101001DL01110111101001000001101111111111101111011111111111111111110101011DL011101111010010000011011111111111111111101101101110010111111111111111010000011111111111110011111111110011111111111111111011111001111111011111111010001001E");
//volatile Code code("00");

void setup() {

  pinMode(10, OUTPUT); //OC1B
  pinMode(9, OUTPUT); //OC1A

  /*タイマ1初期化*/
  TCCR1A = 0;
  TCCR1B = 0;
  TCCR2A = 0;
  TCCR2B = 0;

  /* OC1A,OC1B 37kHz出力
     PWMモード:Fast PWM
     分周比:1
     OCR1A:コンペアマッチLOW
     OCR1B:コンペアマッチLOW
  */
  TCCR1A |= (1 << WGM11) | (0 << WGM10)
            | (1 << COM1A1) | (1 << COM1A0);
  TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS10);
  ICR1 = 431;
  OCR1A = 215;

  //450us 割り込み
  TCCR2A |= (1 << WGM21) | (1 << WGM20);
  TCCR2B |= (1 << WGM22) | (1 << CS22) | (0 << CS21) | (0 << CS20)  ;
  OCR2A = 113;
  //OCR2A = 255;

  TIMSK2 |= (1 << OCIE2A);
  //TIMSK2 &= ~(1 << OCIE2A);

}
ISR (TIMER2_COMPA_vect) {

  flag = code.state();
  //digitalWrite(10,flag);
  if (flag) {
    TCCR1A |= (1 << COM1A1) | (1 << COM1A0);
  } else {
    TCCR1A &= ~((1 << COM1A1) | (1 << COM1A0));
  }
  code.next();
}

void loop() {
  
  
}
```

で無事エアコンが反応した。


累計作業時間20hぐらい。
だいぶ時間をかけてしまった。


同じことをやりたい方はロジックアナライザは持っていた方が絶対うまくいく

https://www.amazon.co.jp/Ren-He-8%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB-8CH%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%82%A2%E3%83%8A%E3%83%A9%E3%82%A4%E3%82%B6-USB%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%82%A2%E3%83%8A%E3%83%A9%E3%82%A4%E3%82%B6/dp/B07M8RQMTS/ref=sr_1_7?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=364740QRS1KO9&keywords=%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%82%A2%E3%83%8A%E3%83%A9%E3%82%A4%E3%82%B6&qid=1671835172&sprefix=%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%82%A2%E3%83%8A%E3%83%A9%E3%82%A4%E3%82%B6%2Caps%2C467&sr=8-7

1000円ぐらいなのでぜひ買おう

# 追記：IRIS OHYAMA 照明リモコン解析

https://www.amazon.co.jp/gp/product/B01I0WV9TA/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&th=1

>アイリスオーヤマ LED シーリングライト 6畳 10段階調光タイプ リモコン付き 常夜灯 明るさメモリ機能 おやすみタイマー リビング 寝室 和室 台所 天井照明 CL6D-5.0

を購入したので、こちらでも行ってみる。

前述の手法通り、信号計測から始める
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/e800c838-004c-abdc-dec7-620189cec1cf.png)

38kHzが搬送波であることがわかった。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/78e51194-4ec0-de5b-2cc8-d6139591e309.png)
全体としては、こんな波形

```
白ー＞常夜灯
１回目3445268,3028,6492,2040,2012,1020,1024,1020,1024,1020,1024,2012,1020,1024,1020,1024,1020,1024,2012,1024,2012,1020,1024,1020,1024,1020,1024,1020,1020,1024,1020,1024,1020,1020,1024,1020,2012,1024,2012,2012,1024,2012,2016
２回目
7629524,3008,6536,2016,2012,1024,1020,1020,1024,1020,1024,2012,1020,1024,1020,1020,1024,1020,2012,1024,2012,1020,1024,1020,1020,1024,1020,1020,1024,1020,1024,1020,1020,1024,1020,1024,2012,1020,2016,2012,1024,2012,2012, 


常夜灯ー＞切る
１回目
23184812,3004,6512,2016,2012,1020,1020,1024,1020,1020,1024,2012,1020,1020,1024,1020,1024,1020,2012,1020,2016,1020,1020,1024,1020,1020,1020,1024,1020,1020,1024,1020,1020,1024,1020,1020,2012,1024,2012,2012,1020,2012,2012, 
２回め
9243732,3028,6488,2040,2016,1020,1020,1024,1020,1020,1024,2012,1020,1024,1020,1024,1020,1024,2012,1024,2012,1020,1024,1020,1024,1020,1024,1020,1024,1020,1020,1024,1020,1024,1020,1024,2012,1020,2016,2012,1024,2012,2012, 


切るー＞白
１回目
47751868,3004,6488,2040,2012,1020,1020,1020,1024,1020,1020,2012,1024,1020,1020,1024,1020,1024,2008,1020,2012,1024,1020,1020,1020,1024,1020,1020,1020,1024,1020,1020,1024,1020,1020,1020,2012,1024,2012,2012,1020,2012,2012, 
２回目
8974264,3028,6488,2016,2032,1024,1020,1024,996,1020,1024,2036,1024,1024,1016,996,1024,1020,2040,1020,2012,1000,1016,1024,1024,1044,1020,1020,1000,1020,1020,1024,1044,1020,1020,1024,1988,1020,2036,2012,1024,1988,2012, 
```
測定結果からわかること

```
T=1020として
x,3T,6.5T,2T,2T,[T,2T]
```
の信号列でないかと考えられる。

* 前述のリモコンフォーマット全てに一致しない
* 3つの動作が全て同じ信号である可能性が高い

```python:Analysis_IRsignal_IRIS_OHYAMA
p = "9243732,3028,6488,2040,2016,1020,1020,1024,1020,1020,1024,2012,1020,1024,1020,1024,1020,1024,2012,1024,2012,1020,1024,1020,1024,1020,1024,1020,1024,1020,1020,1024,1020,1024,1020,1024,2012,1020,2016,2012,1024,2012,2012"

#print(p)


l = p.split(',')
l_int = []

for m in l:
    m = int(m)
    l_int.append(m)

print(l_int )


code = ""

low = 900
high = 1100

for time in l_int:
    
    if(low < time < high):
        code += '0';
    elif(low*2 < time < high*2):
        code +='1'
    elif(low*3 < time < high*3):
        code +='L'
        
    elif(low*6.5 < time < high*6.5):
        code +='M'
        
        
    else:
        code += '1'    #Delayの前のデータは1,0判別不能なため
        code += 'D'
        print(time)
        
code += "1"     #末尾のデータは構造上判別不能なのでとりあえず1にしておこう
        
print(code)
```

結果

```
ライトON操作
計測1回目
1DLM11000000100000010100000000000000010110111

計測2回目
1DLM11000000100000010100000000000000010110111

常夜灯操作
1DLM11000000100000010100000000000000010110111
```

3回とも一致する。

つまり、どの操作も同じ信号を使っている。今回のプログラムで正常に解析できたと言える。

## 送信プログラム
ESP32でやってみる。

* 省エネルギー：Deep Sleepを使う

https://lang-ship.com/blog/work/esp32-light-sleep/

でDeep Sleepを使う。複数ピンを外部起動スイッチにするには

http://blog.livedoor.jp/sce_info3-craft/archives/9821232.html

マスクで作る

ここで、BUTTON_PIN_BITMASKの作り方は

pin4なら、0x10=2^5になる。0x08ではない。

## 引っかかったポイント

### 何もしていない状態で何度もDeepSleepから抜けてしまう
TimerEnd(timer)をtimerを起動しない状態で実行したからだと思われる。

## 赤外線LED駆動
単純に抵抗方式では光が弱く、反応が不安定
トランジスタで電流を増やそうとしても不安定。






# 他の照明について
どこの製品かは不明


## 波形
全消
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/87a52fd4-c09d-0c7d-1b95-975faf6293a0.png)

搬送波が捉えられていない

全灯
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/dabcd31f-3b8b-ac22-8cbc-0bdf0c740552.png)

搬送波は38kHzだった。

## 解析
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/879f23b0-9eb2-0cf7-08f5-0394d9e26bb5.png)

この波形からわかること

* Long,shortの２つの情報で構成されている
* Long,shortの２つの時間的長さは同じ
* ON区間の差で２情報に差をつけている。

### Long
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/99ce77c0-d9b4-5bce-0ca1-fd627bf7f059.png)
1305us:384us

### short
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/a2055a74-7efd-62af-2194-148b991ab15b.png)
468us:1205us


Long,short２つで信号の時間的長さは同じなため、前述のプログラムでは変換できない。
さらに1:2みたいなきれいな比率から外れている。

## 解析プログラム
ON区間の時間を計測する方法がある。
一旦、手作業でも変換できるのでそのまま行く

```
LLSLSSSLSLSL
```
となる。


# データを直接覚える方式
わざわざ解析するより、送信IRのONOFF時間を覚えてまるまるコピーして送信するほうが正攻法・省エネルギー・効率的かもしれない。