Arduinoで照明を制御するリモコンを作ってみた

拙宅で使用しているLEDシーリングライトには、昼白色の「全灯」モードの他に、白熱灯のような「電球色」や、明るさを調整する機能があります。寝る前の照明はあまり青白くない方がいいのではないか、でも自分で調整するのは面倒だし忘れる。そんなきっかけから、照明を制御する機械を作ってみました。

開発した機械。名付けて「調光君1号」
![部品を実装して起動した様子](https://qiita-image-store.s3.amazonaws.com/0/157092/2babdddb-7086-7479-c548-d3b59343df8f.jpeg)

# 要件定義
- 東芝 LEDH96040-LC と、そのリモコン FRC-194T(W) を使う
- 21:00から翌04:59の間、部屋が明るいとき、照明の色を「電球色」にする
- この時間帯でも照明を制御してほしくないときがあるので、制御しないモードを用意する(以下、夜更しモードと呼ぶ)
- 明るさが10ルクス以上(暫定)のとき「部屋が明るい」とする
- 一度照明の色を変えたときは、次に暗くなって明るくなるまで制御しない
    - 連続でコマンドが送信されないようにする
    - 部屋の明るさは感知しても、色までは感知しない
- せっかくRTCがあるので、時計としても使えるようにする

明るさのしきい値は、環境によって調整が必要です。高すぎると、少しセンサの前を横切っただけでも「暗くなった」と判定してしまいます。逆に低すぎると、常夜灯などのわずかな光源でも「明るい」と判定してしまい、コマンドを送信して電球色の照明が灯ってしまいます。
Arduino は本体ルーチンを無限にループさせる仕組みになっているため、連続でコマンドが送信されないようにする必要があります。ここでは、コマンドを送信したかどうかを記憶するようにしました。フローチャートでは以下のようになります。
![FlowChart.png](https://qiita-image-store.s3.amazonaws.com/0/157092/bc50a2e0-76f2-45bc-7fc3-8219889c32ed.png)

# 用意したもの
- Arduino とその開発環境
- 赤外線受信モジュール ローム RPM6938 [データシート](http://rohmfs.rohm.com/jp/products/databook/datasheet/opto/remote_module/lead/rpm6900-j.pdf)
- 赤外線LED ローム SIR-56ST3F [データシート](http://rohmfs.rohm.com/jp/products/databook/datasheet/opto/optical_sensor/infrared_led/sir-56st3f-j.pdf)
- 光センサ [スイッチサイエンス TSL2561デジタル光センサボード](https://www.switch-science.com/catalog/2498/)
- RTC [Adafruit DS3231 Precision RTC Breakout](https://www.adafruit.com/products/3013)
- 7セグLED [Adafruit 0.56" 4-Digit 7-Segment Display w/I2C Backpack - Red](https://www.adafruit.com/products/878)
- トグルスイッチ(照明を制御する/しないの切り替え)
- その他、蛇の目基盤やリード線、ブレッドボードなど

# やったこと
## 赤外線のコマンドを調べる
赤外線受光モジュール単独では、外部のノイズをかなり拾います。太陽をはじめ、生活環境はノイズだらけです。このため、きちんと対策しないとコマンドだけを拾うことができません。ここでは[後閑哲也氏の電子工作室の記事](http://www.picfun.com/Sensor/equipj86.html)を参考にしました。記事では10μFのキャパシタを使用しますが、47μFでも問題ないようです。

![ブレッドボードで組み立てた受信回路](https://qiita-image-store.s3.amazonaws.com/0/157092/c30f5bea-81d8-bf37-b165-ef0499de1396.jpeg)

プログラムは、[IRremote](https://github.com/z3t0/Arduino-IRremote)と、それに付属するサンプルのIRrecvDumpV2を使います。受信した結果をシリアルで取得できます。
![Arduinoシリアルモニタでコマンドを取得した様子](https://qiita-image-store.s3.amazonaws.com/0/157092/23b0013e-eda7-99d3-ebe9-c5b6c0e2e076.png)

これらで試したところ、必要なコマンドは以下の信号であることが分かりました。

```
Encoding  : NEC
Code      : E73017E8 (32 bits)
Timing[131]:
     +10200, -5050     + 650, -1850     + 700, -1850     + 700, -1850
     + 700, - 550     + 700, - 600     + 700, -1850     + 700, -1850
     + 650, -1900     + 650, - 600     + 700, - 600     + 650, -1900
     + 650, -1900     + 650, - 600     + 700, - 550     + 700, - 600
     + 650, - 600     + 700, - 600     + 650, - 600     + 700, - 600
     + 650, -1900     + 650, - 600     + 650, -1900     + 650, -1900
     + 650, -1900     + 650, -1900     + 650, -1850     + 700, -1850
     + 700, - 600     + 650, -1900     + 650, - 600     + 700, - 600
     + 650, - 600     + 700, - 600     + 650, -1900     + 650, - 600
     + 650, -1900     + 650, - 600     + 700, - 600     + 650, - 600
     + 700, -1850     + 650, - 600     + 700, - 600     + 650, - 600
     + 700, -1850     + 700, - 600     + 650, - 600     + 700, - 600
     + 650, - 600     + 700, - 550     + 700, - 600     + 650, -1900
     + 650, - 600     + 700, -1850     + 700, - 600     + 650, - 600
     + 650, - 600     + 700, -1900     + 650, -1850     + 700, -1850
     + 700, - 550     + 700, - 600     + 700, -1850     + 650, -1900
     + 650, - 650     + 650
unsigned int  rawData[131] = {10200,5050, 650,1850, 700,1850, 700,1850, 700,550, 700,600, 700,1850, 700,1850, 650,1900, 650,600, 700,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 650,600, 700,600, 650,600, 700,600, 650,1900, 650,600, 650,1900, 650,1900, 650,1900, 650,1900, 650,1850, 700,1850, 700,600, 650,1900, 650,600, 700,600, 650,600, 700,600, 650,1900, 650,600, 650,1900, 650,600, 700,600, 650,600, 700,1850, 650,600, 700,600, 650,600, 700,1850, 700,600, 650,600, 700,600, 650,600, 700,550, 700,600, 650,1900, 650,600, 700,1850, 700,600, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,550, 700,600, 700,1850, 650,1900, 650,650, 650};  // NEC E73017E8
unsigned int  data = 0xE73017E8;
```

NEC? まあ気にしないことにしましょう。
この rawData の値は、後の送信でコピペできるのでメモっておきます。

## コマンドを赤外線LEDで送ってみる
赤外線LEDのアノードをArduinoの3番ピンに、カソードをGNDに接続します。本来、LEDを点灯させるときは抵抗が必要なのですが、ほんの一瞬しか電流が流れないことと、流れても38kHzのPWMであることから省略しています。設計段階では抵抗を入れていたのですが、どうも赤外線が出ていないようなので外しました。5Vかかっている気もしますが、こまけぇこたぁいいんだよ。

![IMG_3352.jpg](https://qiita-image-store.s3.amazonaws.com/0/157092/0cff50d2-a3f9-0f1d-40b9-421a4aa6ee90.jpeg)

送信には、IRremoteに付属するサンプル IRSendRawDemo を流用します。前項で調べたコマンドをそのままペーストして実行し、機器が意図した挙動になることを確かめます。

```c:IRSendRawDemo_.ino
#include <IRremote.h>

IRsend irsend;

void setup()
{
  int khz = 38;
  unsigned int irSignal[] = {10200,5050, 650,1850, 700,1850, 700,1850, 700,550, 700,600, 700,1850, 700,1850, 650,1900, 650,600, 700,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 650,600, 700,600, 650,600, 700,600, 650,1900, 650,600, 650,1900, 650,1900, 650,1900, 650,1900, 650,1850, 700,1850, 700,600, 650,1900, 650,600, 700,600, 650,600, 700,600, 650,1900, 650,600, 650,1900, 650,600, 700,600, 650,600, 700,1850, 650,600, 700,600, 650,600, 700,1850, 700,600, 650,600, 700,600, 650,600, 700,550, 700,600, 650,1900, 650,600, 700,1850, 700,600, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,550, 700,600, 700,1850, 650,1900, 650,650, 650};  // NEC E73017E8
  irsend.sendRaw(irSignal, sizeof(irSignal) / sizeof(irSignal[0]), khz);
}

void loop() {
}
```

## 光センサを試す
[TSL2561-Arduino-Library](https://github.com/adafruit/TSL2561-Arduino-Library)を使いました。[スイッチサイエンスのwiki](http://trac.switch-science.com/wiki/TSL2561)に詳しい使い方が載っています。

## RTCを試す
[Arduino DS3232RTC Library](https://github.com/JChristensen/DS3232RTC)を使いました。この製品はバックアップ バッテリを搭載できるので、時刻を一度初期化するだけで済みます。手抜きですが、初期化は以下のように行いました。

```c:set_rtc.ino
#include <DS3232RTC.h>
#include <Time.h>
#include <Wire.h>

void setup() {
  tmElements_t tm;
  tm.Hour = 18;
  tm.Minute = 51;
  tm.Second = 30;
  tm.Day = 23;
  tm.Month = 12;
  tm.Year = 2016 - 1970;
  RTC.write(tm); // 2016-12-23 18:51:30 に設定する
}

void loop() {
}
```

## 7セグを試す
製造元のAdafruitに詳しい解説やライブラリがあります。
- [解説](https://learn.adafruit.com/adafruit-led-backpack/0-dot-56-seven-segment-backpack)
- [ライブラリ](https://github.com/adafruit/Adafruit_LED_Backpack)
- [Adafruit-GFX-Library](https://github.com/adafruit/Adafruit-GFX-Library)

## 組み合わせる
ここまでの部品を一つの回路にまとめます。今回は Arduino Vanilla Shieldに実装しました。基盤の表面はこんな感じです。
![基盤の表面](https://qiita-image-store.s3.amazonaws.com/0/157092/30e1367f-a86b-0177-2f82-0f5b1d837a88.jpeg)

続いて裏面の写真です。
![基盤の裏面](https://qiita-image-store.s3.amazonaws.com/0/157092/d7fdc1d2-0e1e-a5d7-82d7-83a48be1464e.jpeg)

部品を載せて起動すると、こんな感じになります。
![部品を実装して起動した様子](https://qiita-image-store.s3.amazonaws.com/0/157092/2babdddb-7086-7479-c548-d3b59343df8f.jpeg)

``` c:auto_light_adjuster.ino
#include <DS3232RTC.h>            //http://github.com/JChristensen/DS3232RTC
#include <Time.h>                 //http://www.arduino.cc/playground/Code/Time  
#include <Wire.h>                 //http://arduino.cc/en/Reference/Wire
#include <Adafruit_GFX.h>         //https://github.com/adafruit/Adafruit-GFX-Library
#include "Adafruit_LEDBackpack.h" //https://github.com/adafruit/Adafruit_LED_Backpack
#include <IRremote.h>             //https://github.com/z3t0/Arduino-IRremote
#include "TSL2561.h"              //https://github.com/adafruit/TSL2561-Arduino-Library

Adafruit_7segment disp = Adafruit_7segment();
IRsend irsend;
//CH1 電球色
unsigned int irSignal[] = {10200,5050, 650,1850, 700,1850, 700,1850, 700,550, 700,600, 700,1850, 700,1850, 650,1900, 650,600, 700,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 650,600, 700,600, 650,600, 700,600, 650,1900, 650,600, 650,1900, 650,1900, 650,1900, 650,1900, 650,1850, 700,1850, 700,600, 650,1900, 650,600, 700,600, 650,600, 700,600, 650,1900, 650,600, 650,1900, 650,600, 700,600, 650,600, 700,1850, 650,600, 700,600, 650,600, 700,1850, 700,600, 650,600, 700,600, 650,600, 700,550, 700,600, 650,1900, 650,600, 700,1850, 700,600, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,550, 700,600, 700,1850, 650,1900, 650,650, 650};  // NEC E73017E8
//CH2 電球色
//unsigned int irSignal[] = {10200,5000, 700,1850, 700,1850, 700,1850, 700,600, 650,600, 700,1850, 650,1900, 650,1900, 650,600, 700,600, 650,1900, 650,1850, 700,600, 650,600, 700,600, 650,600, 650,650, 650,600, 650,650, 650,1850, 700,600, 650,1900, 650,1900, 650,1900, 650,1850, 700,1850, 650,1900, 650,650, 650,1900, 650,600, 650,600, 650,650, 650,600, 650,1950, 600,650, 650,1900, 650,600, 650,650, 600,650, 650,600, 650,650, 650,650, 600,650, 650,1900, 650,600, 650,600, 700,600, 650,600, 650,650, 650,650, 600,1900, 650,650, 650,1900, 650,600, 650,650, 600,650, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 650,1900, 650,1900, 650,1900, 650};  // AIWA_RC_T501 FFFFEB8C
int khz = 38;
TSL2561 tsl(TSL2561_ADDR_FLOAT);

/**
 * リモコンの信号を発射した後の場合、trueとなる。
 * 暗くなったらfalseになる。
 */
boolean fired;

/**
 * 夜更しモ-ドのとき、Trueを返す
 */
boolean isYofukashiMode() {
  return HIGH == digitalRead(2);
}

/**
 * 部屋に明りが点いている場合(調光が必要な場合)にtrueを返す。
 * 10 lx以上であれば「明るい」とみなす。
 * スイッチサイエンスのwikiを参考にした。
 * http://trac.switch-science.com/wiki/TSL2561
 */
boolean isRoomBright() {
  uint16_t x = tsl.getLuminosity(TSL2561_VISIBLE);
  uint32_t lum = tsl.getFullLuminosity();
  uint16_t ir, full;
  ir = lum >> 16;
  full = lum & 0xFFFF;
  int lux = tsl.calculateLux(full, ir);
  return 10 <= lux;
}

/**
 * 時計の表示を更新する
 * @param brightness 画面の明るさ。0x0 <= brightness <= 0xf
 * @param hh 時
 * @param mm 分
 * @param ss 秒
 */
void updateWatch(int hh, int mm, int ss) {
  disp.writeDigitNum(0, hh / 10);
  disp.writeDigitNum(1, hh % 10);
  disp.writeDigitNum(3, mm / 10);
  disp.writeDigitNum(4, mm % 10);
  if(isRoomBright()) {
    disp.drawColon(0 == ss % 2); // 部屋が明るときだけ、colonを点滅させる
    disp.setBrightness(0x9); // 明るさを9/15にする (最大だと明るすぎる)
  } else {
    disp.drawColon(true); // Colonは点灯
    disp.setBrightness(0x0); // 明るさ最小
  }
  disp.writeDisplay();
}

/**
 * 赤外線の信号を発射する
 */
void fire() {
    irsend.sendRaw(irSignal, sizeof(irSignal) / sizeof(irSignal[0]), khz);
    fired = true;
}

void setup() {
  // 夜更しモ-ド切り替えスイッチ
  pinMode(2, INPUT_PULLUP);
  
  // 7seg
  disp.begin(0x70);
  
  // RTC
  setSyncProvider(RTC.get);
  
  // 照度センサ
  tsl.setGain(TSL2561_GAIN_0X);
  tsl.setTiming(TSL2561_INTEGRATIONTIME_101MS);
}

void loop() {
  int hh, mm, ss;
  hh = hour();
  mm = minute();
  ss = second();
  updateWatch(hh, mm, ss);
  if(!fired && !isYofukashiMode() && (hh <= 4 || 21 <= hh) && isRoomBright()) {
    fire();
  } else if(fired && ! isRoomBright()) {
    fired = false;
  }
}
```

# 今後(そのうち気が向いたら)
- [ ] ケースに入れる

# うまく動かないときは
## IRrecvDumpV2 で "IR code too long. Edit IRremoteInt.h and increase RAWLEN" というエラーが出る
Arduino/libraries/Arduino-IRremote/IRremoteInt.h で定義してあるRAWBUFの値を大きくします。デフォルトでは101となっていますが、拙宅で試した範囲では長さ131の信号もありました。ざっくり301ぐらいにしておくといいと思います。
## 赤外線が出ているか分からない
可視光のLEDに交換して調べます。デジカメの撮像素子によっては赤外線を視ることができるので、こちらで観察する方法もあります。手元のガラケーで撮ったところ、紫色の発光が確認できました。
![ガラケーのカメラで赤外線を視る。紫色の発光が確認できる](https://qiita-image-store.s3.amazonaws.com/0/157092/c1ee67c2-5c5a-58bc-5220-d20a0e581b38.jpeg)

## 赤外線は出ているし、コマンドは正しいはずだが、機器が検知しない
赤外線LEDには指向性があります。向きを調整してみてください。

# 謝辞とか
この工作は妻の「9時を過ぎたら勝手に電球色になったらいいのに」という一言から生まれました。リビングに基板むき出しの試作品を置いても大目に見てくれる妻に感謝します。