ArduinoをLEDライトのリモコン化

## 概要

スマートLEDライトはすでに製品として沢山ありますが，すでにある天井LEDライトをスマート化できないかと思い，取り組んだ際のログです．もちろんスマートリモコンは販売されていますが，少し値が張りますしね.. ライブラリもあるので簡単にできると思いきや，対象とした**Panasonic製のLEDライトはデフォルトのAPIに対応しておらず**，信号を解析した上で実装する羽目になりました．

### 環境

- Mac OS 10.12.6
- Arduino IDE 1.8.3
- ライブラリ: IRremote 2.2.3
- ボード: Arduino Uno (Elements14製)
- Panasonic製 LEDライト

もう少し古いIDEから使ってはいましたが，現在の環境を掲載しておきます．

## 導入

1. Arduino IDE起動後，[スケッチ]-[ライブラリをインクルード]-[ライブラリを管理]ダイアログを開く
2. IRremoteを選択してインストール
3. ArduinoのデフォルトライブラリにIRremote.cppというファイルやIRrecvというクラスがあるため，このままではエラーが発生します．そのためArduino.appの`Contents/Java/libraries/Robot_IR_remote`をlibrariesフォルダの外に退避

## パーツと接続

- 赤外線受信モジュール
    - 三洋半導体製 [SPS440-1](https://akizukidenshi.com/catalog/g/gI-00614/)
- 赤外線LED
    - OSI5LA5113A (Vf 1.35-1.6V, 55mW@50mA)

赤外線受信モジュールはデータシートに書いてある通り，立てた時に３本足がある方の，左からGND(0V)，OUT(出力)，VCC(電源)です．以下のように接続します．

|受信機|Arduino Uno|
|:--|:--|
|3 GND|GND|
|4 OUT|D11|
|5 VCC|+5V|

赤外線LEDは100Ω介し，AnodeをD3に，CathodeをGNDに接続しています[^1]．

[^1]: IRremoteのバージョンによっては出力を変更できたようですが，手持ちのバージョンでは固定でした．

## テスト1-問題確認

赤外線信号のデコードはIRremoteのスケッチ例IRrecvDumpを使って，解釈できればデコード結果が出ます．なおスケッチ例は[ファイル]-[スケッチ例]-[IRremote]-[IRrecvDump]から開けます．
例えば(確かLGの)テレビリモコンのON/OFFボタンを押すと`Decoded NEC: 20DF10EF`のようなログがでるわけです．これをIRsendDemoで `sendNEC(0x20DF10EF, 32)` のようにすればテレビが点きます．

しかし同じことをLEDライトのリモコンで実施したところ全く動作しませんでした．

## テスト2-生信号受信

IRremoteのスケッチ例 IRrecvDump を参考に以下のコードでテストしました．

```cpp:IRrecv1.ino
#include <IRremote.h>
int RECV_PIN = 11;
IRrecv irrecv(RECV_PIN);
decode_results results;

void setup()
{
  Serial.begin(9600);
  irrecv.enableIRIn();
}

void loop() {
  if (irrecv.decode(&results)) {
    Serial.println(results.value, HEX);
    for ( i=0 ; i<results.rawlen ; i++ ){
      Serial.print(results.rawbuf[i]);
      Serial.print(",");
    }
    Serial.println();
    irrecv.resume();
  }
  delay(100);
}
```

そもそも赤外線リモコン信号は次のような形式でやりとりされています．
![fig_arduino_led_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96281/24958c0e-a456-2d5e-b692-d5c2033c3954.png)
赤外線[^2]が出ている時間をMark，出ていない時間をSpaceとし，MarkとSpace１対で一つのデータを表します．Mark<Spaceであれば "1"，Mark〜Spaceであれば "0" のビットとなり，データ列の前に比較的長い時間のMarkとSpace(Header)があります．

ここで`results.rawbuf`にはHeader開始前のSpaceを[0]としてMark，Spaceそれぞれの時間が入っていくので，Headerは配列の[1],[2]，データは[3]以降です．時間は50us単位になっています(IRremote.cppより)．

[^2]: 正確には変調された信号．SPS-440-1では38kHzが受信できます．

シリアルモニタを開きながらLEDライトのリモコンの点灯ボタンを押すと次のようなデータとなりました．

```
F6B92168
1956,72,32,11,6,11,6,11,24,10,24,10,7,11,24,10,7,10,7,10,7,11,24,10,7,10,7,10,25,10,7,10,24,10,8,9,25,10,7,10,7,10,25,9,8,10,7,10,8,9,8,9,25,9,9,9,24,10,25,9,9,9,25,9,8,9,8,10,8,9,8,9,25,9,8,10,8,9,25,9,8,10,8,9,
```
ここから各時間はおよそ

|    |Mark (us)|Space (us)|
|----|----|-----|
|Header|72 (3600)|32 (1600)|
|Data 0|10 (500)| 8 (400)|
|Data 1|10 (500)|24 (1200)|

のようになり，データ列は

```
0011 0100 0100 1010 1001 0000 1011 0100 0010 0100
```

となります．
他のボタンで試した結果

```
点灯: 0011 0100 0100 1010 1001 0000 1011 0100 0010 0100
消灯: 0011 0100 0100 1010 1001 0000 1111 0100 0110 0100
```

となり，初めの24bitは共通で，後の16bitについても，

```
(1011 0100)xor(1001 0000) = 0010 0100
(1111 0100)xor(1001 0000) = 0110 0100
```

となることから，前8bitを`1001 0000`とのxor取ったものが後8bitでした．

## 赤外線信号の送信

結果的には，取得したデータ列`0x344a90b424`を送信すればOKです．送信にはIRsendクラスを使います．使い方は ir_<メーカ名>.cpp ファイルを参考にできます．

```cpp:send.ino
#include <IRremote.h>

#define T_HDR_M 3600
#define T_HDR_S 1600
#define T_BIT_M  500
#define T_ONE_S 1200
#define T_ZERO_S 400

IRsend irsend;

void sendIr(unsigned long data, int nbits)
{  
  irsend.enableIROut(38); //38kHzでの送信設定
  // Header
  irsend.mark(T_HDR_M);
  irsend.space(T_HDR_S);
  // Data
  for (unsigned long mask = 1UL << (nbits - 1); mask; mask >>= 1) {
    if (data & mask) {
      irsend.mark(T_BIT_M);
      irsend.space(T_ONE_S);
    } else {
      irsend.mark(T_BIT_M);
      irsend.space(T_ZERO_S);
    }
  }
  // Footer
  irsend.mark(T_BIT_M);
  irsend.space(0);  
}
void setup()
{
  Serial.begin(9600);
  sendIr(0x344a90b424UL, 40);
}

```
電源を入れると電気が点くはずです
（元のソースから切り貼りしているのでうまくいかなければコメントください..）

## 後記
これの後の話として，安価にIoTできるESP32で同じことを実施しようとしたのですが，純正バージョンでは送信側(IRsend)が対応していないため，別バージョンを探すことに．またいざできても電圧が5Vではなく3.3Vによる弊害が色々と出て難儀しました．ニーズあればまた記事書きます．