赤外線リモコンのON/OFF専用コードを発見する

#赤外線リモコンのON/OFF専用コードを発見する

赤外線リモコンを使ってTVを制御する時に困るのが「電源」ボタンがトグル動作だということです。ONしようと思ったらOFFになってしまったりという問題が起きます。
加えて、送受信エラーを防ぐために複数回の送信をするといったこともできません。

おそらくトグルではなくON専用、OFF専用のコードがあるだろうと思って調べてみました。

##検索してみる
赤外線リモコンの送信データはインターネットのどこかにあるだろうと思って調べてみたのですが、決定版的なものは見つかりませんでした。
以下は比較的多くのデータが登録されているので、まずはここで確認するのが良いと思います。

https://github.com/probonopd/irdb

例えば、東芝のTVのON/OFF専用コードは codes/Toshiba/TV/64,-1.csv に記載されています。

```ToshibaTV-SendPowerON.cpp
uint32_t data = irsend.encodeNEC(64,126); //device=64,function=126
irsend.sendNEC(data,32);
```

##探してみる
検査で見つからなかった場合の探し方がこの記事です。
方法は原始的で、片っ端から送ってみる方法です。

##範囲を絞る
探したかったAQUOSで、まずはコードを調べます。
ESP8266に赤外線リモコン読み取りデバイスを接続して、実際の赤外線リモコンのボタンを押して、表示されたコードから解析を開始します。

秋月電子で販売しているOSRB38C9AA(2個で100円)を使用しました。

https://akizukidenshi.com/catalog/g/gI-04659/

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/9c4f081c-658d-2e07-5c85-bf52ae305862.jpeg">

このデバイスは3.3Vでも5.0Vでも利用でき、それぞれに応じた電圧のアウトプットをしてくれます。また、ノイズの少ない環境では外付けのコンデンサや抵抗なども必要なく、実際ESP8266に３本直結でOKでした。

読み取りに使ったソフトウエアは、ESP8266 / IRRemote8266 です。OSRB38C9AAのデータピンはソフトにあるようにGPIO14に接続します。

https://github.com/crankyoldgit/IRremoteESP8266

IRremoteESP8266 の examples/IRrecvDumpV2 をそのまま使って調べました。

AQUOSのデータはPanasonicの48bitデータフォーマットで、電源ON/OFFボタンは 0x555AF148688B でした。

```stdout.txt
Protocol  : PANASONIC
Code      : 0x555AF148688B (48 Bits)
uint16_t rawData[99] = {3402, 1650,  440, 390,  446, 1234,  442, 394,  432, 1244,  440, 394,  442, 1228,  446, 396,  440, 1214,  462, 394,  442, 1234,  440, 394,  440, 1182,  486, 1234,  442, 396,  444, 1226,  446, 394,  442, 1230,  442, 1228,  
446, 1232,  442, 1230,  390, 478,  412, 390,  448, 390,  444, 1228,  446, 398,  438, 1236,  436, 392,  446, 392,  442, 1262,  412, 390,  448, 388,  392, 446,  440, 396,  444, 1226,  444, 1230,  448, 388,  444, 1262,  414, 396,  436, 396,  446, 388,  446, 1228,  446, 390,  444, 390,  446, 390,  440, 1234,  448, 392,  444, 1230,  442, 1230,  444};  // PANASONIC 555AF148688B
uint32_t address = 0x555A;
uint32_t command = 0xF148688B;
uint64_t data = 0x555AF148688B;
```
:::note info
IRrecvDumpV2/V3 でリモコンデータを読み込んだ時、Protocol:'UNKNOWN' と表示された場合の Code: はrawDataを素直にコード化したものではありません。
UNKNOWNのコードを解析する場合、独自にRawdataを解析するプログラムを組む必要があると思います。
:::

総当りで送るにしても、ある程度範囲を絞らないと現実的ではありません。最低限のトライで済ませるためにデータを解析します。
他のいくつかのコードはこんな感じ。

| コード|ボタン |
|:--|--:|
| 0x555AF148688B |電源 |
| 0x555AF148724C |1 |
| 0x555AF148F244 |2 |
| 0x555AF1480A43 |3 |
| 0x555AF1488A4B |4 |
| 0x555AF1484A47 |5 |

赤外線リモコンフォーマットの概要から推察すると、これは家製協(AEHA)フォーマットっぽいです。
http://elm-chan.org/docs/ir_format.html

555Aがカスタマーコード、5^5^5^A=Fでパリティ。
148688がデータで、1^4^8^6^8^8=Bがパリティです。

|カスタマーコード|パリティ|コード|パリイティ|ボタン|
|--:|--:|--:|--:|--:|
|555A|F|148688|B|電源 |
|555A|F|148724|C|1|
|555A|F|148F24|4|2|
|555A|F|1480A4|3|3|
|555A|F|1488A4|B|4|
|555A|F|1484A4|7|5|

まだコード部分が24bitもあります。
コード部分の 148 は固定っぽいので除いて12bit=4096個。これくらいなら試せそうです。

おそらく連続コードになっていそうな、1,2,3,4,5のコード部分に注目すると、エンディアンが逆だと推測できます。

|コード|エンディアン逆転|ボタン|
|--:|--:|--:|
|688|116|電源 |
|724|247|1|
|F24|24F|2|
|0A4|250|3|
|8A4|251|4|
|4A4|252|5|

だいぶ構造が見えてきました。
エンディアンを逆にした番号順に試してみることにしました。

##送信装置
送信装置は秋月の赤外線LED OSI5FU5111C-40 をトランジスタでドライブします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/00280529-fd0e-938d-f167-6d43828e7643.jpeg" width="600">

https://akizukidenshi.com/catalog/g/gI-03261/

パルスで1Aまで流せて値段も手頃です。
ざっと400mAくらい流せるように抵抗を選んでみましたが、実測では300mA程度しか流れていませんでした。（電源かトランジスタの能力不足）
コンデンサはなくても動くと思います。
抵抗が４つなのは手持ちが1/4W抵抗だった対策なので、これもパルスなら大丈夫かもしれません。
手持ちがトランジスタだったので使いましたが、MOSFETのほうがいいと思います。

##総当り
ここからは全データ送信して総当りします。
イテレーションを早くするために、スキャンするコードはMQTTでリアルタイムに制御します。
ここでは自宅のMQTTブローカーを使っています。（Raspberry Pi)

``` IRTest.cpp
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>   // MQTT

#define ASSETID 0x555a
#define DEVICEID  0x148

IPAddress MQTT_SERVER_IP(192, 168, 1, 111);  // 自宅のMQTTサーバ
#define MQTT_CLIENTID   "ESP8266-0000"
#define MQTT_SUBSCRIBE  "IR/#"

#define IR_LED  4
IRsend irsend(IR_LED);  // Set the GPIO to be used to sending the message.
WiFiClient wifiClient;

uint32_t  startData = 1;
uint32_t  endData = 0;
int   lastMillis;

// 4bit xor チェックディジット
uint8_t checkDigit(uint64_t data)
{
  uint8_t c = 0;
  for(int i=0;i<(64/4);i++){
    c ^= data & 15;
    data >>= 4;
  }
  return  c;
}

uint64_t makeData(uint64_t maker, uint64_t data)
{
  uint64_t  ret = 0;
  uint64_t  cd = checkDigit(maker);
  
  ret = maker << 32;
  ret |= (cd << (32-4));

  cd = checkDigit(data);
  ret |= (data << 4);
  ret |= cd;

  return  ret;
}

void callback(char *topic, byte *payload, unsigned int length) {
  for (unsigned int i = 0; i < length; i++) {
    Serial.print((char) payload[i]);
  }
  Serial.println();

  char  *buff = new char[length+1];
  memcpy(buff,payload,length);
  buff[length] = '\0';

  char  *nextPos;
  char  *p = buff;

  while(isspace(*p))  p++;
  startData = strtol(p,&nextPos,16);
  p = nextPos;
  while(isspace(*p))  p++;
  if( *p == '-'){
    p++;
    while(isspace(*p))  p++;
    endData = strtol(p,&nextPos,16);
  }
  else{
    endData = startData;
  }
  delete buff;
}

PubSubClient client(MQTT_SERVER_IP, 1883, callback,  wifiClient);

void reconnect(const char *clientID,const char *subscrive) {
  while (!client.connected()) {
      Serial.println("connect to MQTT");
      if (client.connect(clientID)) {
        Serial.println("connected");
        if(subscrive!=0){
          client.subscribe(subscrive);
        }
      }
      else {
        delay(5000);
      }
  }
}

// エンディアン逆転
uint32_t  endianRev(uint32_t d,int bit)
{
  uint32_t  ret = 0;
  for(int i=0;i<bit;i++){
    ret <<= 1;
    ret |= d&1;
    d >>= 1;
  }
  return  ret;
}

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

  WiFi.mode(WIFI_STA);  
  WiFi.begin("SSID","PASSWD");
  while( WiFi.status() != WL_CONNECTED ) {
    delay(500);
  }
  irsend.begin();
  lastMillis = millis();
}

void loop()
{
  reconnect(MQTT_CLIENTID,MQTT_SUBSCRIBE);

  if( (millis()-lastMillis) >= 100 ){
    lastMillis = millis();
    if( startData <= endData ){
      uint32_t d = endianRev(startData,12) & 0xfff | (DEVICEID<<12);
      uint64_t  data;
      data = makeData(ASSETID,d);
      Serial.printf("%lx %llx\n",startData,data);
      irsend.sendPanasonic64(data,48,0);
      startData ++;
    }
  }

  client.loop();
  delay(1); 
}
```

##スキャンの指示はMQTTLensで
スキャンの指示はChromeのアプリのMQTT Lensを使いました。
IR/1 にメッセージとして、開始番号-終了番号 をPublishすると、100msごとに順番に赤外線信号を送ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/bfa616a8-edc7-a414-38b0-0c69fce4f59a.png)

TVを電源OFFにした状態でデータを送り始め、電源がONになったら、そのあたりの番号をさらに絞り込んでゆく地道な作業です。

##結果
結果として無事に電源ON、OFFのコードを発見できました。

| コード|(番号)|ボタン |
|:--|--:|--:|
| 0x555AF148688B |116 |ON/OFF |
| 0x555AF148524E |24A |ON |
| 0x555AF148D246 |24B |OFF|