Hue Tapスイッチが高いのでESP-WROOM-02で物理スイッチを自作する 後編

前回に引き続き、Philips hueの物理スイッチを自力で作りましょうのコーナーです。前回まででESP-WROOM-02を自由にプログラムできるようになったので、後はWi-Fiに繋いで、hueに対してHTTPリクエストを投げるようなプログラムを書いて、物理ボタンを配線してあげるだけです。

実際に作り始める前に、作りたい物理スイッチの要件を確認しておきます。

1. 極力電力を使わない

オフィシャルな物理スイッチであるHue Tapは、なんと無線リモコンであるにも関わらず電源不要です。ボタンを押した力を電力に変えているからですね。

さすがにそんなリッチなテクノロジーを自前で組み込むのは難しそうなので、電池は使いますが、できる限り電池を使わないようにして、ほとんど電池交換が不要と感じるぐらいに長期間駆動させられるようにしたいと思います。

具体的には、待機電力はゼロで、HTTPリクエストを投げるときだけ通電させるようにします。こうすると、照明をON/OFFさせるたびに毎回無線ルーターとの接続が発生してしまうので、どうしてもON/OFFが有効になるまでにそれなりに時間がかかってしまいます。かといって、レスポンス重視で常時電源ONにしてしまうと、電池の消費が激しくなってしまいます。こちらの記事にある3つのスリープモードのうち、Modem-SleepとLight-Sleepのどちらかにすれば接続は維持されたまま省電力モードにできるようですが、省電力でも電力を使うことは使います。自分としてはレスポンスは遅くても普段一切電力を使わないようにしておいた方が気がラクなので、今回は毎回接続を繋ぎ直す方向でいきたいと思います。

2. hueの状態を見てON/OFFリクエストを切り替える

ONだけ、OFFだけ、もしくは交互にON/OFFするだけであれば、比較的簡単に作れそうです(交互にON/OFFは今回みたいに毎回ESP-WROOM-02の本体をOFFにするときには状態保持が難しいですが)。

が、hueを操作する可能性があるのはこの物理スイッチだけじゃなくて、スマホからON/OFFする場合もありますし、自分の場合はRaspberry PiがWeb APIを叩いてONにすることもありますし、hue自身のタイマがOFFにすることもあります。そう考えると、どちらか一方しかできない、もしくは単純にON/OFFを繰り返すだけだと、あんまり使い勝手が良くなさそうです。

幸い、hueはON/OFF操作だけじゃなくて、現在のONF/OFF状態取得もWeb APIでできます。これを使って、まずはhueの状態を確認し、その結果、ON状態ならOFF、OFF状態ならONのHTTPリクエストを投げるようにします。これも、2回HTTPリクエストを投げることになるのでレスポンスの低下に繋がってしまいますが、ON/OFFボタンをそれぞれ一つずつ用意するよりコンパクトになりますし、何よりボタンが一つしかないので操作に迷うことがなくなります。

 

以上を踏まえて、こんな感じでお手製のhue用物理スイッチを配線してみました。

th_hue-switch-1

一番下に電池ボックスを置いて、その上に小さいブレッドボードを張り付けて、その上にESP-WROOM-02やらLEDやらタクトスイッチやらを置いています。

th_hue-switch-5

ESP-WROOM-02の下はこんな感じの配線になっています。あんまり綺麗じゃないですが、普段は見えないので良いのです。

この配線のキモは、電源周りです。単四アルカリ電池x3(=4.5V)が直列に繋げられた電池ボックスのマイナスをGNDにして、プラスを右上の大きなタクトスイッチに繋いでいます。タクトスイッチの先は3.3Vのレギュレータに繋がっていて、レギュレータの出力がESP-WROOM-02の3.3VとかENに繋がっています。電池ボックス自身のスライドスイッチは常時ONなので、ESP-WROOM-02への電力供給はタクトスイッチを押している間だけ行われることになります。普通のArduinoだとあまり行わない配線になりますが、今回はHTTPリクエストを投げ終わったら即電源供給を止めたいので、こういう形にしました。スライドスイッチで電源を管理してしまうと、気をつけないとOFFにし忘れてしまうことがありそうですし。

電力供給については、最初はよりコンパクトにするために3.0VをDC-DCコンバータで3.3Vに昇圧させようと思ったのですが、問題はESP-WROOM-02の消費電流です。いろいろ情報を調べてみると、どうも無線通信時には数百mA消費してしまうようで、自分の手持ちのDC-DCコンバータではその要件を満たすことができませんでした。

そこで、図体が大きくなってしまう上にコンデンサもくっつけてやらないといけないですが、最大1Aの電流出力が可能な3.3VレギュレータTA48033Sを利用することにしました。これに対して単四アルカリ電池x3(=4.5V)から電力を供給してやることで、無事にESP-WROOM-02を動作させることができました。

あと、ESP-WROOM-02とスイッチとレギュレータ以外に、簡単な動作状況を示すためのLEDも配線しておきました。

th_hue-switch-2

横から見た図。なんだかメガドラタワーの基本形(メガCD+メガドラ+スーパー32X)みたいな高さになってしまって、初めはちょっと不格好かなーと思ったのですが、

th_hue-switch-3

これがなんというか、ものすごく手にジャストフィットします。

th_hue-switch-4

右手でも。たまたま採用したタクトスイッチのクリック感の良さもあって、なんだかものすごく押したくなるスイッチが出来上がってしまいました。重さも程よい重量感。文章だと全く伝えられないのがもどかしい。

 

とりあえずモノとしては出来上がったので、あとはプログラムだけです。

完成したプログラムがこちらになります。

#include <ESP8266WiFi.h>
#include <ArduinoJson.h>

#define PIN_LED 13

const char* ssid = "xxxxxx"; // 各自の無線LANルータのSSID
const char* password = "yyyyyy"; // 各自の無線LANルータのパスワード

const char* host = "XXX.XXX.XXX.XXX"; // bridge IP address

String ON  = "{\"on\":true,\"bri\":128,\"xy\":[0.52,0.42],\"sat\":128}";
String OFF = "{\"on\":false}";

void setup() {
  const int BUFFER_SIZE = 1024;
  StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;

  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, LOW);

  Serial.begin(115200);
  Serial.println();
  Serial.print("connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  WiFi.mode(WIFI_STA);
  while(WiFi.waitForConnectResult() != WL_CONNECTED){
    Serial.println("Connection Failed. Rebooting...");
    delay(5000);
    ESP.restart();  
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  String line;

  // ---------- Hue State Check ----------  
  WiFiClient clientGet;

  Serial.print("connecting to ");
  Serial.println(host);

  if (!clientGet.connect(host, 80)) {
    Serial.println("connection failed");
    digitalWrite(PIN_LED, HIGH);
    return;
  }

  clientGet.print("GET /api/xxxxxx/lights HTTP/1.1\r\n"); // xxxxxxの部分は各自のhueのユーザネーム
  clientGet.print("Host: XXX.XXX.XXX.XXX\r\n"); // bridge IP addressに置き換え
  clientGet.print("Connection: close\r\n\r\n");
  delay(10);

  //get rid of the HTTP headers
  while (clientGet.connected()) {
    line = clientGet.readStringUntil('\n');
    if (line == "\r") {
      Serial.println("headers received");
      break;
    }
  }

  //get http content
  String buf="";
  line = clientGet.readStringUntil('\n');
  line.trim();
  buf.concat(line);

  //parse json data
  char json[buf.length() + 1];
  buf.toCharArray(json, sizeof(json));
  Serial.println(json);
  JsonObject& root = jsonBuffer.parseObject(json);
  if (!root.success()) {
    Serial.println("parseObject() failed");
    digitalWrite(PIN_LED, HIGH);
    return;
  }

  JsonObject& num_13 = root["13"]; // "13"は各自の制御したいhueのIDに置き換え
  if (!num_13.success()) {
    Serial.println("parseObject() failed");
    digitalWrite(PIN_LED, HIGH);
    return;
  }
  JsonObject& state = num_13["state"];
  boolean state_on = state["on"];

  clientGet.stop();

  digitalWrite(PIN_LED, HIGH);
  delay(100);
  digitalWrite(PIN_LED, LOW);

  // ---------- Hue ON/OFF ----------
  WiFiClient clientPut;

  Serial.print("connecting to ");
  Serial.println(host);

  if (!clientPut.connect(host, 80)) {
    Serial.println("connection failed");
    digitalWrite(PIN_LED, HIGH);
    return;
  }
  // 以下のxxxxxxは各自のhueのユーザネーム、"13"は各自の制御したいhueのIDに置き換え
  clientPut.print("PUT /api/xxxxxx/lights/13/state HTTP/1.1\r\n"); 
  clientPut.print("Host: XXX.XXX.XXX.XXX\r\n"); // bridge IP addressに置き換え
  clientPut.print("Connection: close\r\n");
  clientPut.print("Content-Type: text/plain;charset=UTF-8\r\n");

  if(state_on){
    clientPut.print("Content-Length: ");
    clientPut.print(OFF.length());
    clientPut.print("\r\n\r\n");
    clientPut.print(OFF);
  }else{
    clientPut.print("Content-Length: ");
    clientPut.print(ON.length());
    clientPut.print("\r\n\r\n");
    clientPut.print(ON);
  }
  delay(10);

  while (clientPut.connected()) {
    line = clientPut.readStringUntil('\n');
    if (line == "\r") {
      Serial.println("headers received");
      break;
    }
  }

  clientPut.stop();

  if(state_on){
    digitalWrite(PIN_LED, HIGH);
    delay(100);
    digitalWrite(PIN_LED, LOW);
    delay(100);
  }else{
    digitalWrite(PIN_LED, HIGH);
    delay(100);
    digitalWrite(PIN_LED, LOW);
    delay(100);
    digitalWrite(PIN_LED, HIGH);
    delay(100);
    digitalWrite(PIN_LED, LOW);
    delay(100);
  }
}

void loop() {
  delay(500);
}

全体として、メインのプログラムはsetup関数内にしか書いていません。起動時に必要なHTTPリクエストを投げてくれたら、あとは何もする必要がないので。

上から簡単に見ていきます。まず、hueはHTTPリクエストに対するレスポンスをJSONで返すようになっているので、それを処理できるようにArduinoJsonライブラリを使用しています。事前にArduino IDEのlibraryディレクトリに突っ込んでおきましょう。

Wi-Fi接続が済んだら、一回目のHTTPリクエスト(GET)を発行します。これは、hueの状態確認用です。「”/api/xxxxxx/lights”じゃなくて”/api/xxxxxx/lights/13″のようにした方が、制御したい電球の情報だけダイレクトに取れて良いのでは?」と思われた方がおられるかもしれません。それは実際その通りなのですが、ウチのhueはなぜか後者のAPIがいつも失敗してしまい。。。苦肉の策として、すべての電球の状態を取得して、それから必要な電球の”state” “on”の情報を探すようにしました。

その後に、二回目のHTTPリクエスト(PUT)を発行しています。ここで、先の”state” “on”の状態取得の結果に基づいて、ON用のPUT情報にするか、OFF用のPUT情報にするかを切り替えています。

今回の実装では、GETとPUTでWifiClientを作り直しています。まとめて扱う方法もあるのかもしれませんが、自分が試した限りではうまくできなかったので、今回はこれで良しとしています。

 

ではでは、実際の動作を見てみましょう。

ちゃんとhueの状態を見てON/OFFのどちらにするかを判断していることを示すため、間に一度、スマホでのON操作を挟んでいます。

ご覧の通り、スイッチを押し始めてから実際にhueが制御されるまで6秒ぐらいかかっています。なので、単純にON/OFFするまでの時間で比べるなら、スマホから操作するのと大差ありません。ショートカットとかを作るようにすれば、むしろスマホの方が早くなると思います。

が、この物理スイッチには、それ以上のメリットがあると思います。具体的には、

  1. いつでも必ずそこにある
  2. やることは、ボタンを押し続けるだけ

ということです。

hueを点けようと思ったときに、いつも手の届く範囲にスマホがあるとは限りません。しかしこの物理スイッチはhueのON/OFFしかしないので、基本的に持ち歩く必要がなく、いつも定位置(自分の場合は机の上)に置いてあります。そのため、ON/OFFしたいときに必ず、hueをON/OFFできます。

また、ONしたかろうがOFFしたかろうが、やることはボタンをしばらく押し続けるだけです。手探りでできるレベルの操作です。スマホで制御するときにどうしても発生する「狙う」という行為が発生しないだけでも、実はかなり快適な操作になります。

ということで、自分としてはかなりお気に入りの工作になりました。もう少し綺麗にプロトタイプを作ることもできると思うのですが、今できているものの謎のフィット感(?)をとても気に入っているので、これを完成形としたいと思います。

ちなみに、この物理スイッチを作るのにかかった費用はこれだけです。

タクトスイッチはいつどこで買ったか忘れましたが、高めに見積もって200円。抵抗とLEDは安物のセットのやつを購入したので、それぞれ1個10円ぐらいとして、あとピンヘッダとかリード線とかまでアバウトに加味すると … 合計およそ1,600円! Hue Tapスイッチの4分の1以下のお値段で物理スイッチを作ることができました。自分は既に持っていたから勘定していませんが、新規に書き込み用のFTDI USBシリアル変換アダプター(1,500円ぐらい)を購入したとしても、半額以下で作ることができます。もちろん、レスポンスが圧倒的に遅いとか、ボタンが実質2個(Hue Tapは実質4個)という制限がありますが、費用を考えれば、個人的には十分許容範囲です。

 

しかしこの物理スイッチ、作った動機はhueの制御のためだけだったのですが、やっていることはHTTPリクエストを投げているだけなので、実はものすごく応用範囲の広いものになっている気がしています。Web APIで制御するものなら何でも操作できますし、また、今回はESP-WROOM-02自身にON/OFFの状態確認を任せてしまっていますが、ESP-WROOM-02自身はある一つのHTTPリクエストをRaspberry Piに投げるだけにして、リクエストを受け取ったRaspberry Pi側で、いろんなセンサ情報などから適切な動作を選択させるようなロジックを載せ込んでしまえば、「状況に応じて自分の望むことが起こるなんでもボタン」のようなものまで作れると思います。シンプルなものしかできないかもしれませんが、楽しそうなので是非やってみたいところです。

 

【参考】

ESP-WROOM-02は大人気なので、Web上には情報が溢れまくっていますね。とても勉強になります。

今回は、特に以下を参考にさせていただきました。

また、やはり自分と同じようなことをやられている方は既におられました。利用シーンとか作り方はちょっと違いますが。

他にも、電源周りについてはこのあたりを参考にさせていただきました。

スリープモードとかにまで踏み込む場合はこちら。今回は不要でしたが、ゆくゆくは必要になりそうです。