Smart HomeをDIYする

Apple HomeKit は対応製品が少なくて高価なのでHomebridgeでがんばります

Zibgee方式のスマート赤外線リモコン

こちらに移転しましたので自動転送します。

AliExpressでZigbee方式のMeosスマート赤外線リモコンを買いました。これです

Zigbee2MQTTでサポートされていて、HomeKitからも使用できました。ただし学習能力に課題があるようで、学習してくれないリモコンパターンが多いです。リモコンパターンらしきデータをMQTTで送受信しているのですが、そのフォーマットが判明したらもう少し活用できそうに思いました。

外観

AliExpressの商品としてはそこそこ高価格なので(2,119円送料無料でした)、外観も高級感があります。全体の形状は、小さなMac miniみたいです。裏蓋もMac miniのようになっていて、回すと外せます。この中に、単4電池を2本入れます。Zigbee動作なので、電池は持ちそうです。

学習と再生機能

こちらで説明されているように、MQTT経由での操作は割と単純です。Zigbeeブローカーからのメッセージで、学習モードをonにできます。赤外線リモコンからのパターンが読めると、Zigbeeにパターン情報が流されます。そのパターンをZigbeeメッセージとして流せば、赤外線信号が発信されます。それぞれのメッセージは以下です。

  1. learn_ir_codeメッセージで学習モードをon/offする
  2. ir_code_to_sendメッセージで取得したパターンを送出する

例えば、zigbee2mqtt/FRIENDLY_NAME/setトピックスに、(FRIEMDLY_NAMEはデバイスのID)

{"learn_ir_code":"ON"}

を流すと学習モードになります。学習に成功すると、zigbee2mqtt/FRIENDLY_NAMEトピックに

{"battery":100,"learned_ir_code":"D7UNtQ2SA3YKQQOSA0EDdgpABQNBA3YK4AMDAJIgDwOSA5IDgBPABwF2CuABA0ATQANAEwFBA0AJAUEDQAUAkmABQAeAAwF2CkAJAUEDQAXgAQMNtQ21DUEDdgpBA5IDQQPgAwdAC+ADA+AQHwIKWgI=","linkquality":138,"voltage":1500}

のように結果がメッセージとして流されます。learned_ir_codeの部分が、学習された赤外線パターンです。文字の羅列から推測するにBASE64フォーマットのデータのようです。

この赤外線パターンを送出させるためには、zigbee2mqtt/FRIENDLY_NAME/setトピックスに、

{"ir_code_to_send": "D7UNtQ2SA3YKQQOSA0EDdgpABQNBA3YK4AMDAJIgDwOSA5IDgBPABwF2CuABA0ATQANAEwFBA0AJAUEDQAUAkmABQAeAAwF2CkAJAUEDQAXgAQMNtQ21DUEDdgpBA5IDQQPgAwdAC+ADA+AQHwIKWgI="}

を流します。

ちなみに、学習モードの操作は、Zigbee2MQTTのwebインタフェースからも実行可能です。以下のように、赤外線パターンが取得できます。

LED照明リモコンは学習失敗

最近のリモコンならば学習できるのですが、少し外れた規格のリモコンは学習できなかったり、学習が不十分だったりしました。学習出来なかった例が、天井照明LEDのリモコンでした。Amazonで昔販売されていた照明器具で、以下のような単純なリモコンです。

これの赤外線パターンは、こんなデータです。赤外線のonとoffの持続時間をマイクロ秒で記述した配列です。

[6830,4629,283,573,283,1409,286,569,286,1426,259,573,291,1399,259,597,283,1409,258,597,285,1408,282,573,281,579,285,1401,280,1407,283,1405,281,1409,310]

グラフにすると以下のようになります。

これは学習出来ませんでした。onとoffの比率が特殊だったのかもしれません。

古いエアコンのリモコンはとりあえず動作

ものすごく古いエアコンのリモコンを試したところ、なんとか動作する程度には学習出来ました。こんなタイプのリモコンです。

このリモコンの赤外線パターンは以下のようになってます。

前半部分と、後半部分に分かれています。色々試したところ、前半部分は、冷暖房の動作モード、温度設定、風量設定などの基本操作で、後半部分はタイマー関係の設定でした。また、前半部分、後半部分のどちらも、同じ内容のパターンが2回繰り返されていることがわかります。

このパターンをこのZigbeeスマートリモコンで学習させ、その再生信号をRaspberry Piに取り付けた赤外線受信モジュールで取得したところ、以下のようなパターンでした。

前半部分の途中で学習を放棄している感じです。前半部分の3/4くらいは学習出来てます。前半部分も後半部分も、同じデータを2回繰り返していましたので、前半部分のデータはなんとか取得できているようです。これをエアコンに流したところ、これだけでも動作しました。冗長な部分が足りなくても、動作してくれるようです。

数値で表すと、元々のエアコンのパターンは以下のようだったのですが、

[3475, 3510, 857, 2637, 852, 895, 851, 2640, 854, 894, 850, 2641, 852, 2641, 854, 2640, 851, 2640, 855, 2638, 856, 892, 852, 2639, 854, 894, 852, 2640, 854, 2639, 854, 2638, 852, 2642, 853, 895, 854, 892, 852, 2640, 854, 893, 855, 891, 853, 894, 855, 892, 853, 892, 855, 891, 854, 894, 855, 2637, 854, 893, 856, 890, 855, 890, 856, 891, 855, 892, 3480, 3506, 856, 2635, 855, 893, 855, 2638, 856, 891, 855, 2636, 856, 2638, 855, 2637, 859, 2634, 857, 2636, 858, 890, 858, 2635, 857, 889, 856, 2637, 856, 2635, 858, 2636, 857, 2635, 857, 892, 854, 891, 856, 2637, 856, 891, 854, 893, 855, 890, 853, 893, 855, 892, 856, 890, 855, 892, 855, 2637, 854, 894, 854, 891, 857, 892, 852, 892, 857, 890, 3478, 3508, 855, 13990, 3477, 3508, 858, 891, 853, 894, 853, 893, 852, 894, 852, 894, 854, 894, 851, 895, 853, 893, 854, 893, 853, 894, 850, 896, 852, 896, 852, 895, 850, 896, 854, 894, 826, 921, 851, 895, 851, 2640, 854, 2639, 852, 895, 851, 2639, 855, 2638, 854, 895, 850, 897, 850, 896, 854, 2637, 854, 2641, 854, 893, 854, 2638, 849, 2643, 854, 893, 852, 896, 3474, 3511, 854, 894, 853, 894, 851, 895, 852, 895, 852, 894, 853, 893, 856, 891, 854, 892, 852, 895, 855, 891, 826, 921, 853, 894, 854, 894, 853, 892, 855, 892, 854, 892, 854, 892, 856, 2637, 854, 2639, 854, 893, 856, 2635, 857, 2637, 855, 893, 852, 893, 857, 891, 855, 2635, 855, 2638, 855, 894, 854, 2636, 856, 2638, 855, 893, 854, 892, 3478, 3505, 856]

実際に取得できたのは以下でした。

[3505, 3538, 896, 2707, 813, 975, 818, 2706, 864, 916, 782, 2741, 760, 2748, 818, 2679, 821, 2682, 891, 2713, 924, 940, 782, 2731, 812, 983, 781, 2733, 793, 2716, 819, 2687, 790, 2700, 795, 977, 782, 976, 811, 2736, 783, 914, 865, 867, 819, 947, 846, 950, 866, 947, 839, 949, 783, 977, 811, 2731, 865, 867, 817, 950, 820, 978, 782, 977, 845, 947, 3503, 3542, 784, 2733, 783, 1003, 782, 2731, 784, 1047, 792, 2700, 818, 2683, 759, 2749, 817, 2676, 816, 2682, 794, 979, 843, 2679, 821, 955, 784, 2734, 805, 2706, 818, 2682, 816, 2685, 634]

フォーマットが謎

上記のパターンを発生させるためには、zigbee2mqtt/FRIENDLY_NAME/setトピックスに、以下のメッセージを流します。

{"ir_code_to_send": "D7UNtQ2SA3YKQQOSA0EDdgpABQNBA3YK4AMDAJIgDwOSA5IDgBPABwF2CuABA0ATQANAEwFBA0AJAUEDQAUAkmABQAeAAwF2CkAJAUEDQAXgAQMNtQ21DUEDdgpBA5IDQQPgAwdAC+ADA+AQHwIKWgI="}

BASE64のデータだと推測されるのですが、デコードしても上記のパターンにはなってませんでした。

% echo -n D7UNtQ2SA3YKQQOSA0EDdgpABQNBA3YK4AMDAJIgDwOSA5IDgBPABwF2CuABA0ATQANAEwFBA0AJAUEDQAUAkmABQAeAAwF2CkAJAUEDQAXgAQMNtQ21DUEDdgpBA5IDQQPgAwdAC+ADA+AQHwIKWgI= | base64 -D | hexdump   
0000000 b50f b50d 920d 7603 410a 9203 4103 7603
0000010 400a 0305 0341 0a76 03e0 0003 2092 030f
0000020 0392 0392 1380 07c0 7601 e00a 0301 1340
0000030 0340 1340 4101 4003 0109 0341 0540 9200
0000040 0160 0740 0380 7601 400a 0109 0341 0540
0000050 01e0 0d03 0db5 0db5 0341 0a76 0341 0392
0000060 0341 03e0 4007 e00b 0303 10e0 021f 5a0a
0000070 0002                                   
0000071

何らかの処理がされているようです。これが判明すれば、自在な赤外線パターンを指定できるので、色々と活用できると思われます。どなたか情報をご存知でしたらよろしくお願いします。

MQTTThingプラグインで設定

上記の赤外線パターンをHomeKitから発信するには、以下のようにHomebridgeを設定します。

{
  "type": "switch",
  "name": "エアコン",
    "topics": {
      "setOn": "zigbee2mqtt/FRIENDLY_NAME/set"
    },
  "onValue": "{\"ir_code_to_send\": \"D7UNtQ2SA3YKQQOSA0EDdgpABQNBA3YK4AMDAJIgDwOSA5IDgBPABwF2CuABA0ATQANAEwFBA0AJAUEDQAUAkmABQAeAAwF2CkAJAUEDQAXgAQMNtQ21DUEDdgpBA5IDQQPgAwdAC+ADA+AQHwIKWgI=\"}",
  "accessory": "mqttthing"
}       

スイッチアクセサリを作って、onで上記のメッセージを流すように設定しました。これでとりあえずはエアコンの電源を入れられるようになりました。

まとめ

Zigbee接続のスマート赤外線リモコンデバイスを使いました。本来は、Zuyaのアプリで動作する製品です。でも、Zigbee2MQTTで操作でき、リモコンの学習と再生ができました。Homebridgeからも使えます。

ただ、Zigbee2MQTTから使う限りでは、学習機能が限られている様子です。Zuyaアプリで正しく使えば、もしかしたら完璧なのかもしれません。BASE64のフォーマットがわかると、Homebridge, Home Assistantからもっと活用できそうな気がしました。

三菱エアコン用スマートリモコンをDIYする

こちらに移転しましたので自動転送します。

エアコンリモコンをコントロールするスマートリモコンをESP32で作りました。以下の記事です。この時はパナソニックのエアコンを対象にしたのですが、今回は三菱のエアコンのためのスマートエアコンを作ります。

diysmarthome.hatenablog.com

ハードウェアとシステムの構成は今までと同じです。下図のように、(右から)HomeKitに接続したHomebridgeサーバから、MQTT経由でコマンドを出し、ESP32で受け取って、赤外線LEDからリモコン信号を送り、エアコンをコントロールします。

 

ユニバーサル基板上に前回作成した回路を使用しました。赤外線LEDを3個搭載して、確認用赤色LEDがついてます。右下に見える黒い部品は温度湿度センサです。

赤外線パターンを確認

対象とする三菱エアコンのリモコンは下のようなものです。現行製品ではなく、古い製品です。上部に穴が開いていてフックに掛けられるのが便利です。裏側に NH112という型番が刻印されてます。

このリモコンからの赤外線パターンを読み込んでみました。使ったのはRaspberry Piに赤外線受信モジュールを取り付けたDIYバイスです。

diysmarthome.hatenablog.com

その結果、赤外線パターンは以下のようになってました。横軸が時間で、縦軸が赤外線強度です。同一データが2回繰り返されていることが確認できました。

 

 

パルス幅のパターンから、家製協フォーマットのようです。1バイトのデータがLSBから先に転送されていると仮定してデコードすると、以下のようでした。まずは、21度冷房のパターンです。これは、リモコンのリセットボタンを押したデフォルト状態です。

23CB260100005805364000000000100000F8 (off)
23CB26010020580536400000000010000018 (on)

次に、27度暖房です。これもリセット後のデフォルト状態です。

23CB26010000480B304000000000100000E8 (off)
23CB26010020480B30400000000010000008 (on)

最後のバイトはチェックサムで、それまでのバイトの合計の256の剰余でした。チェックサムの計算がシンプルに行えるので、LSBから転送されていると考えるのが自然です。

機能への割り当てを探る

この先は、リモコンの設定を色々変えて、ビットへの機能割り当てを探ります。今回は、この世代のリモコンを解析してくれたページがあり、とても参考になりました。型番がNH122だそうで、NH112とは型番が少し異なりますが、信号パターンは同じでした。

qiita.com

このページでフォーマットがまとめられています。

https://camo.qiitausercontent.com/60ad20530cb2a68861767878bf2d203f025284ab/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3234323430322f38323237343836642d393537352d363134372d336335642d3333633865393462343935342e706e67

https://qiita.com/Hiroki_Kawakami/items/37cdb412a4e511a58103

 

例えば、上記のデフォルト状態では、冷暖房に合わせた除湿設定、水平風方向は中央、垂直風方向は自動、風量は自動になっているようです。

HomeKit仕様に合わせた機能選択

HomeKitのHeater Coolerアクセサリを使うので、その仕様に合わせて機能を選択します。

三菱リモコンの運転切り替えを何度か押すと、冷房、除湿、暖房、送風が順番に選択できます。このうちHomeKitで定義されているのは、冷房と暖房だけです。また小さなボタンで「体感」モードのon/off切り替えができます。体感モードはこの世代の三菱エアコンの特徴で、人のいる方向を検知して、そちらを重視して冷やす・温めることで節電しようという仕掛けらしいです。デフォルトで体感モードはonらしいので、onにしておきます。というようなことを考慮して、次の機能を使用することにします。

  • 冷房・暖房・停止をする(ただし体感モードon)
  • 温度調整をする
  • Swing onは水平風向方向スウィングとする
  • Swing offは水平風向方向を中央とする
  • 垂直方向の風向は自動とする(冷暖房に合わせて上下する)
  • 風量はスライダに合わせて、自動、弱、中、強、最強と割り当てる

これに合わせてリモコンデータを変化させると、

  • 運転・停止をする:5バイト目を00または20にする
  • 冷房・暖房を切り替える:6バイト目を58または08にする
  • 温度調整をする:7バイト目に数値を設定
  • Swing onは水平風向方向スウィングとする:8バイト上位4ビットをC
  • Swing offは水平風向方向を中央とする:8バイト上位4ビットを3
  • 除湿は実リモコンと同じ設定:8バイト下位4ビットを冷房は6, 暖房は0
  • 垂直方向の風向は自動とする:9バイト中3ビットを0
  • 風量はスライダに合わせて、自動、弱、中、強、最強と割り当てる:9バイト目下位3ビットを0,1,2,3にする

となります。

赤外線を出すプログラム

これを元に、ESP32にプログラムします。エアコンからHomeKitへの流れのうち、ESP32の部分です。

今回も、IRremoteESP8266というライブラリを使いました。前回は、パナソニックエアコンのクラスを使用しました。今回は、三菱電機のクラスを使います。ライブラリには、三菱電機のものと、三菱重工の2種類がありました。内容を見ると、三菱電機のものが今回のリモコンパターンに近いので、それを使うことにしました。

三菱電機のリモコンも、テレビやエアコン機種ごとに、いくつかの種類が用意されてました。IRMitsubishiACというクラスが、18バイトの信号を出すクラスでした。今回のリモコンと合致するので、これを使いました。それでIRMitsubishiACのインスタンスを作って、温度やon/offをメソッドで設定した後、sendメソッドを呼ぶことにします。sendメソッドの引数は繰り返し回数です。1とすると、本体に加えて1回余分に繰り返すという意味でした。なので1とすると2回繰り返し、実リモコンの動作と一致します。

 

 

HomeKitと連携して設定する部分は、on/off, 冷房暖房切り替え、温度設定、風量設定、スウィングon/offです。このうち、on/off、冷暖房切り替え、温度設定は、IRMitsubishiACクラスのメソッドで問題なく設定できました。一方、風量と風向は実リモコンと振る舞いが違ってしまいました。そこで、この二点については、18バイトの信号を直接書き換えることにしました。

風向の設定

まずは風向の設定です。今回は、HomeKitのスウィング設定で、水平方向の風向を設定することにします。スウィングoffの場合は、水平風向を中央にして、スウィングonの場合は、水平にスウィングさせます。実リモコンのデータを確認すると、リモコンリセット直後(21度冷房)の状態で、水平風向中央と水平風向スウィングの信号パターンは、

23CB2601002058053683000000001010006B center
23CB260100205805C64300000000101000BB swing

でした。太字の8, 9バイト目が変化します。そこでこれを切り替える関数、setSwing()を作りました。引数のswingmodeがtrueかfalseに従い、信号パターンを切り替えます。

void setSwing(bool swingmode){
  uint8_t *raw=mitsubishi.getRaw();
  if(swingmode) {
    raw[8] = (raw[8] & 0x0F) | 0xC0;
    raw[9] = (raw[9] & 0x3F) | 0x40;
  }else{
    raw[8] = (raw[8] & 0x0F) | 0x30;
    raw[9] = (raw[9] & 0x3F) | 0x80;
  }
}

風量の設定

HomeKitでは風量を0から100で設定します。これを適当に切り分けて、自動・弱・中・強・最強に割り当てました。 これもリモコンリセット直後(21度冷房)の状態で、風量切り替えた信号を取得しました信号を取得しました。 その結果、自動・弱・中・強・最強のパターンは、

23CB26010020580536800000000010000058 auto
23CB26010020580536410000000010000019 low
23CB2601002058053642000000001000001A mid
23CB2601002058053643000000001000001B high
23CB2601002058053643000000001010002B powerfull

でした。太字の部分が変化してますが、これもIRMitsubishiACの実装と違う動きをしていました。そこで変化する部分、9バイト目と15バイト目を書き換えることにします。引数は、HomeKitで指定されたファン強度です。

void setSpeed(int speed){
  uint8_t *raw=mitsubishi.getRaw();
  raw[15] = raw[15] & 0xEF; //clear the powerfull bit(?)
  if     (speed < 20) raw[9] = (raw[9] & 0x3C) | 0x80; //auto
  else if(speed < 40) raw[9] = (raw[9] & 0x3C) | 0x41; //low
  else if(speed < 60) raw[9] = (raw[9] & 0x3C) | 0x42; //mid
  else if(speed < 80) raw[9] = (raw[9] & 0x3C) | 0x43; //high
  else { //powerfull
    raw[9] = (raw[9] & 0xC3) | 0x43;
    raw[15] = raw[15] | 0x10; //set the powerfull bit.
  }
}

MQTTと温度センサを追加

これ以外は、前回のパナソニックと同様です。MQTTメッセージを受け取る部分と、温度センサDTH20のI2C部分

を追加して、以下のように完成させました。

/* IRremoteESP8266 over MQTT */

#include <Arduino.h>
#include <ArduinoOTA.h>
#include <cstring> //for std::memcpy method

//IR Remote
#include <IRremoteESP8266.h>
#include <ir_Mitsubishi.h>
const uint16_t kIrLed = 4;  // ESP8266 GPIO pin to use.
IRMitsubishiAC mitsubishi=IRMitsubishiAC(kIrLed);
//DHT sensor
#include "DHT20.h"
#define GPIO_SDA 21 //I2C for DHT20
#define GPIO_SCL 22 //I2C for DHT20
DHT20 dht; //instance of DHT20
//MQTT
#include <EspMQTTClient.h>
EspMQTTClient *client; //instance of MQTT client

//WiFi & MQTT
const char SSID[] = "XXXXXXXX"; //WiFi SSID
const char PASS[] = "xxxxxxxx"; //WiFi password
char CLIENTID[] = "IRremote_2927595"; //something random
const char  MQTTADD[] = "192.168.xxx.xxx"; //Broker IP address
const short MQTTPORT = 1883; //Broker port
const char  MQTTUSER[] = "xxxxxx";//Can be omitted if not needed
const char  MQTTPASS[] = "XXXXXX";//Can be omitted if not needed
const char  SUBTOPIC[] = "mqttthing/irOffice/set/#"; //subscribe
const char  PUBTOPIC[] = "mqttthing/irOffice/get"; //publish temp
const char  DEBUG[] = "mqttthing/irOffice/debug"; //for debug

//base values for the IR Remote state (Mitsubishi AC)
uint8_t coolState [kMitsubishiACStateLength] = { //28deg. cool, auto, Taikan
0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x58, 0x0C, 0x36, 0x40, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x18
};
uint8_t heatState [kMitsubishiACStateLength] = { //21deg. heat, auto, Taikan
0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x48, 0x05, 0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x18
};

void setSwing(bool swingmode){
  uint8_t *raw=mitsubishi.getRaw();
  if(swingmode) {
    raw[8] = (raw[8] & 0x0F) | 0xC0;
    raw[9] = (raw[9] & 0x3F) | 0x40;
  }else{
    raw[8] = (raw[8] & 0x0F) | 0x30;
    raw[9] = (raw[9] & 0x3F) | 0x80;
  }
}

void setSpeed(int speed){
  uint8_t *raw=mitsubishi.getRaw();
  raw[15] = raw[15] & 0xEF; //clear the powerfull bit(?)
  if     (speed < 20) raw[9] = (raw[9] & 0x3C) | 0x80; //auto
  else if(speed < 40) raw[9] = (raw[9] & 0x3C) | 0x41; //low
  else if(speed < 60) raw[9] = (raw[9] & 0x3C) | 0x42; //mid
  else if(speed < 80) raw[9] = (raw[9] & 0x3C) | 0x43; //high
  else { //powerfull
    raw[9] = (raw[9] & 0xC3) | 0x43;
    raw[15] = raw[15] | 0x10; //set the powerfull bit.
  }
}

void setup() {
  dht.begin(GPIO_SDA, GPIO_SCL); //DHT20
  mitsubishi.setRaw(coolState); //initialize the raw value
  mitsubishi.begin();
  client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT); 
  delay(1000);
}

void onMessageReceived(const String& topic, const String& message) { 
  String command = topic.substring(topic.lastIndexOf("/") + 1);

  if (command.equals("Active")) {
    if(message.equalsIgnoreCase("true")) mitsubishi.on();
    if(message.equalsIgnoreCase("false")) mitsubishi.off();
    mitsubishi.send(1);
  }else if(command.equals("TargetHeaterCoolerState")){
    if(message.equalsIgnoreCase("COOL")) {
      switchMode(kMitsubishiAcCool);
    }
    if(message.equalsIgnoreCase("HEAT")) {
      switchMode(kMitsubishiAcHeat);
    }
  }else if(command.equals("CoolingThresholdTemperature")){
    mitsubishi.setTemp(message.toFloat());
    mitsubishi.send(1);
  }else if(command.equals("HeatingThresholdTemperature")){
    mitsubishi.setTemp(message.toFloat());
    mitsubishi.send(1);
  }else if(command.equals("SwingMode")){
    if(message.equalsIgnoreCase("DISABLED")) setSwing(false);
    if(message.equalsIgnoreCase("ENABLED")) setSwing(true);
    mitsubishi.send(1);
  }else if(command.equals("RotationSpeed")){
    setSpeed(message.toInt());
  }
}

void onConnectionEstablished() {
  ArduinoOTA.setHostname("irXXXXXX");
  ArduinoOTA.setPasswordHash("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
  ArduinoOTA.begin();
  Serial.println("MQTT connection established.");
  client->subscribe(SUBTOPIC, onMessageReceived); //set callback function
  client->publish(DEBUG,"irXXXXXX started.");
}

//Backup and switch the operating mode.
void switchMode(const uint8_t targetmode){
  uint8_t currentmode;
  
  currentmode = mitsubishi.getMode();
  if(targetmode == currentmode) return; //no switch, do nothing

  const uint8_t *raw=mitsubishi.getRaw();
  
  if((currentmode == kMitsubishiAcHeat) && (targetmode == kMitsubishiAcCool)) {
    std::memcpy(heatState, raw, kMitsubishiACStateLength);
    mitsubishi.setRaw(coolState);
  }
  if((currentmode == kMitsubishiAcCool) && (targetmode == kMitsubishiAcHeat)) {
    std::memcpy(coolState, raw, kMitsubishiACStateLength);
    mitsubishi.setRaw(heatState);
  }
}

//IR Remo and MQTT: read DHT20 and publish results
void publishDHT() { 
  char buff[64];
  float humi, temp;
  if(DHT20_OK != dht.read()){
    client->publish(DEBUG,"DHT20 Read Error.");
  }else{
    humi=dht.getHumidity();
    temp=dht.getTemperature();
    sprintf(buff, "{\"temperature\":%.1f,\"humidity\":%.0f}", temp, humi);
    client->publish(PUBTOPIC,buff);
  }
}

void loop() {
  ArduinoOTA.handle();
  client->loop(); 
  if(millis() - dht.lastRead() >= 60000) publishDHT();
}

HomeKitから使用する

ここから先はRaspberry Piでの設定部分です。

 

 

Mqttthingプラグインを使い、Heater Coolerアクセサリを実装します。その設定で、

{
            "type": "heaterCooler",
            "name": "Aircon",
            "url": "mqtt://localhost:1883",
            "topics": {
                "setActive": "mqttthing/irOffice/set/Active",
                "setCoolingThresholdTemperature": "mqttthing/irOffice/set/CoolingThresholdTemperature",
                "getCurrentTemperature": "mqttthing/irOffice/get$.temperature",
                "setHeatingThresholdTemperature": "mqttthing/irOffice/set/HeatingThresholdTemperature",
                "setRotationSpeed": "mqttthing/irOffice/set/RotationSpeed",
                "setSwingMode": "mqttthing/irOffice/set/SwingMode",
                "setTargetHeaterCoolerState": "mqttthing/irOffice/set/TargetHeaterCoolerState"
            },
            "restrictHeaterCoolerState": [
                1,
                2
            ],
            "accessory": "mqttthing"
        },

のように設定しました。この結果、iPhoneMacのホームには、こんな形で現れます。

クリックすると、On/off、温度調整、動作モード切り替え(冷房・暖房・自動)を行うウィンドウが開きます。

また、このウィンドウの歯車アイコンをクリックすると、「ファンの速さ」が0から100までのスライダで調節でき、さらに「首振り」をスイッチでon/offできます。ファンの速さは、数値が低い方から自動・弱・中・強・最強にマッピングされてます。また首振りは、水平方向のスウィングに割り当ててあります。

まとめ

前回はパナソニックのエアコンを対象としましたが、今回は三菱のエアコンを対象にして、スマートリモコンをDIYしました。IRremoteESP8266ライブラリは多数のメーカの基本機能に正しく対応しています。しかし風量・風向機能で実際のリモコンと動作が違っていましたので調整しました。

製品世代の違いや販売地域の違いで、ライブラリの動作と実際のリモコンの差異が生じているようです。これは製品版のスマートリモコンでも同じことだと思います。そこをきっちりと合わせられるのがDIYの良いところだと思いました。

Sonoffの日本市場向けZigbeeスマートプラグをHomeKitで使う

こちらに移転しましたので自動転送します。

SonoffのZigbeeスマートプラグを使ってみました。Zigbee2MQTTでサポートされているので、Homebridgeのプラグインを使って、HomeKitからすぐに利用できました。技適PSEマークも付いていて日本市場に対応してます。

HomeKitで使えるスマートプラグ

日本の壁にあるコンセント(差し込み口)のことを英語ではアウトレットと呼びます。そこに挿すための差し込み器具がプラグです。スマートプラグはコンセントに挿す器具で、無線制御でon/offできる差し込み口(コンセント、アウトレット)を背面に備えています。

Sonoff (Zigbee) のプラグ

Sonoffが作っているZigbee方式スマートプラグがAliExpressで売られてます。

執筆時点で1,668円送料207円です。Zigbee2MQTTに対応しているので、HomeKitから使えます。日本の電波・安全規格に適合したHomeKit利用可能スマートプラグとしては最安値だと思います。

https://ja.aliexpress.com/item/1005004542320498.html

IKEA (Zigbee)

日本市場向けZigbeeスマートプラグは、IKEAにもあります。こちらは、2,499円税込・送料別です。少し高いですが、別途購入すると799円のZigbee方式スイッチが付属しています。また、IKEAのハブはHomeKitに対応しているので、ハブを購入すればホーム.appからすぐに使えます。Zigbee2MQTTの対応リストにも載ってるので、IKEAハブを購入しなくても、Homebridge経由でHomeKitから使えるはずです。ただアース端子のついた3極プラグの製品のみです。日本のコンセントに挿す場合は、アダプタが必要です。

www.ikea.com

Meross (WiFi)

スマートプラグは商用電源に接続するので消費電力の心配がありません。なので接続が簡単なWiFi方式の製品が多いです。中でもMerossの製品は、HomeKit対応製品としては妥当なお値段(2,180円税・送料込み)です。2個セットだと割安になります。HomebridgeもZigbee2MQTTも不要なので、誰にでも使いやすいです。

何個か使用していますが、コンセントから抜き差しすると、たまに応答がなくなる個体があります。

この場合、on/offボタンを押しても本体LEDが点灯せず、動作しません。on/offボタンを数秒長押しするとリセットされてLEDが2色交互点滅するはずなのですが、それも反応がありません。一見すると故障したように見えます。コンセントから外して10分程度放置すると直ります。WiFi方式のプラグではこのような不具合をよく聞きます。

SwitchBot (WiFi)

SwitchBotからもHomeKit対応スマートプラグが発売されています。Moressと同じくWiFi接続です。2個ほど購入して使ってますが、これもホーム.appからすぐに接続できます。Amazonには2個セットしかなく、2個で3,662円です。Merossより少し高いですが、今のところMerossのようにコンセント抜き差しで不調になる現象は経験していません。

Sonoffスマートプラグを開封

今回は、上記のサイトからSonoffのZigbee型スマートプラグを購入しましたので、その報告です。

https://ja.aliexpress.com/item/1005004542320498.html 

冒頭の写真にあるように、オレンジ色の綺麗な箱でAliExpressから届きました。本体には、技適PSEのマークが刻印されています。電圧・周波数も日本市場にピンポイントに合わせてあるようです。これだけ日本市場向けに作られているのに、なぜかAliExpressでしか売っていません。Amazonでも売れば良いのにと思います。

WiFi方式ならばご家庭のWiFiルータに接続できますが、Zigbee方式なのでブリッジが必須です。Sonoffのブリッジの他、Philipsなどのハブ、Zigbee機能を備えたAmazon Echoなどから使用できるようです。メーカー公式のHomeKitサポートはありません。HomeKitから使用するにはHomebridgeとZigbee2MQTTを使用する必要があります。という状況なので、一般の方には使いにくい製品なのかもしれません。でも、WiFi方式のプラグがたまに応答がなくなるのに対して、Zigbee方式は安定している印象があります。

Zigbee2MQTTに接続してHomeKitで使う

HomeKitで使うためには、Zigbee2MQTTサーバが稼働していて、MQTTブローカー (Mosquitto) が動き、Homebridgeが動いていて、さらにHomebridgeにZigbee2MQTTを使用するプラグインが入っている必要があります。手元の環境では、これらをRaspbetty Piで動かしてます。

これらの設定に関しては、以下をご覧ください。

diysmarthome.hatenablog.com

まずはSonoffのプラグを、Zigbee2MQTTのwebインタフェースからペアリングします。ペアリングモードがoffに設定してある場合は、webインタフェース右上のPermit join (All)のメニューをクリックしてペアリングを許可します。すると5分間、ペアリングが可能になります。

この状態でSonoffスマートプラグのon/offボタンを5秒程度長押しします。するとLEDが点滅してペアリングモードに入ります。ペアリングが終了すると、以下のようにプラグが表示されます。

ペアリングが終了すると、HomebridgeのZigbee2MQTTプラグインのおかげで、自動的にHomeKitに現れます。iPhoneMacのホーム.appアプリに、以下のようにスイッチとして表示され、on/off操作が可能になってます。

Zigbee2MQTTのMap機能で確認すると、Zigbeeルータとして機能しているようです。近くのZigbeeバイスが、Sonoffのスマートプラグ経由での接続方式に切り替わってました。

少し気になるのは、状態のMQTTメッセージを割と頻繁に出している点です。mosquitto_subでモニターしていると、電波強度とon/off状態を定期的にパブリッシュしているのですが、

zigbee2mqtt/0xAAAAAAAAAAAAAAAA {"linkquality":25,"state":"OFF"}

その頻度が10秒に1回です。もう少し少なくても良いように思うのですが、こんなものなのでしょうか。Zigbeeの温度センサが温度をパブリッシュする頻度は15分に1回程度です。ちなみに、

zigbee2mqtt/0xAAAAAAAAAAAAAAAA/set {"state":"ON"}

などとパブリッシュすれば、MQTT経由でon/offすることもできます。

まとめ

SonoffのZigbeeスマートプラグは、技適マーク・PSEマークがついていて、日本仕様になってました。価格も安価で確実に動作します。日本での販売にもっと力を入れて欲しいと思いました。

安価なZigbee温度湿度センサー (675円)をHomeKitで使う

こちらに移転しましたので自動転送します。

Zigbeeを使った安価な温度湿度センサを入手しました。AliExpressで送料込みで648円でした。Zigbee2MQTTで問題なく認識され、HomeKitから使用することができました。以前、安価な磁気接触センサを紹介しました。

diysmarthome.hatenablog.com

これと同じシリーズの製品です。同じく単4電池を2本使用しますので、ボタン電池製品と比べて少し大きいです。でも電池の持ちは期待できます。販売ページはこちらです。

https://ja.aliexpress.com/item/1005004745405648.html

開封して分解

箱から出すとこんな感じです。磁気開閉センサと同じ形・大きさですが、温度計・湿度計のマークがプリントされています。写真に見える点のような穴は、ペアリング状態を示すLEDの部分です。ここには写っていないヘリの部分に、リセットボタンの穴があり、そちらにはRESETと書いてあります。

 

 

本体裏蓋をスライドさせると電池ボックスが現れます。

 

 

中央にネジが見えます。これを外して、軽い噛み合わせを外すと、中が見えます。

 

 

磁気接触センサの内部 (下の写真) と比較すると、間違い探しレベルでそっくりです。

 

 

左にあるのがワンチップコンピュータで、ZTUという文字が見えます。TuyaのZigbeeチップです。写真を見比べると、中央ネジ穴の上にあった磁気スイッチ(磁気抵抗センサ)が無くなってます。

 

温度湿度センサ

磁気スイッチが無くなった代わりに、右端下部に6ピンの部品が増えています。これが温度湿度センサのようです。Zigbeeチップからできるだけ離して発熱(全く発熱しませんが)の影響を避けようと配慮されていると思われます。

 

 

この拡大写真を下に示します。

 

8305 BBCT3Aと刻印されています。番号で検索したところSensylinkという会社のI2C接続の温度湿度センサのようです。刻印されている四角にSのマークが会社のロゴで、型番が8305です。

www.sensylink.com

温度誤差は+/-0.5度、湿度は+/- 3.0%だそうで、ちゃんとしてます。前回使用したDHT20と同等の性能です。6個のピンは、電源、グラウンド、I2C信号、校正、警告のピンのようです。

Zigbee2MQTTに接続してHomeKitで使う

次にZigbee2MQTTにペアリングしてHomeKitで使えるようにします。このためには、Zigbee2MQTTサーバが稼働していて、MQTTブローカー (Mosquitto) が動き、Homebridgeが動いていて、さらにHomebridgeにZigbee2MQTTを使用するプラグインが入っている必要があります。これらをRaspbetty Piで動かしてます。

これらの設定に関しては、以下をご覧ください。

diysmarthome.hatenablog.com

まずは、Zigbee2MQTTのwebインタフェースからペアリングします。ペアリングモードがoffに設定してある場合は、webインタフェース右上のPermit join (All)のメニューをクリックしてペアリングを許可します。すると5分間、ペアリングが可能になります。

この状態で接触センサーの横にあるリセットボタンを5秒程度長押しします。するとLEDが点滅してペアリングモードに入ります。ペアリングが終了すると、以下のようにセンサを確認できました。TS0201というセンサとして認識されているようです。写真は液晶表示付きの高そうなセンサですが、これもおそらくは同じZTUチップを使用したセンサだと思われます。

 

mosquitto_subコマンドでモニターします。

% mosquitto_sub -h 192.168.xxx.xxx -t "zigbee2mqtt/#" -v  

しばらくすると

zigbee2mqtt/0x9999999999999999 {"battery":100,"humidity":66.05,"linkquality":163,"temperature":23.82,"voltage":3000}

のように温度、湿度のメッセージが現れました。以下のようにsubscribeするIDを限定すれば、

% mosquitto_sub -h 192.168.xxx.xxx -t "zigbee2mqtt/0x9999999999999999/#" -v 

このセンサからのみの情報が得られます。

HomebridgeにZigbee2MQTT用のプラグインが設定してあれば、あとは自動的に温度・湿度センサとして認識されて、iPhoneMacのホーム.appに表示されます。温度、湿度の一覧に加えられているので、クリックすると、

このように現在温度・湿度の測定結果を見ることができます。センサ名の初期値はIDでしたが、HomeKitで変更しました。

まとめ

Tuyaの安価な温度・湿度センサをHomeKitで使いました。送料込み675円は、HomeKitで使える温度・湿度センサとして最安と思います。ESP32でDIYしても、部品代だけでこの価格を上回ります。しかも電池駆動です。もう少し買い足しても良いかなと思いました。

赤外線リモコン受信モジュールでリモコン生データを得てHomeKitから使う

赤外線リモコン受信モジュール (110円) をRaspberry Piに取り付けて、赤外線パターンを取得・復号できるようにしました。天井照明リモコンの点滅パターンをこれで取得して、ESP32に組み込んで、HomeKitから点灯・消灯できました。

これまでのあらすじ

前回、HomeKitからパナソニックのエアコンをコントロールするスマート赤外線リモコンが完成しました。

diysmarthome.hatenablog.com

エアコンをコントロールするスマートリモコンとして、Nature Remoを使っていたのですが、これをDIYしたことでほぼ使わなくなってしまいました。唯一使用している機能が、赤外線リモコンの生データをローカルAPIで取得する機能です。

diysmarthome.hatenablog.com

Nature Remoにリモコンを向けてボタンを押すと、Nature Remoがパターンを取得してくれます。そのパターンはhttpアクセスで知ることができます。パターン取得機能のためだけにNature Remoを使うのは勿体無いので、今回はこれをDIYします。赤外線リモコン受信モジュールをRaspberry Piに取り付けて、赤外線パターンを取得することにしました。

赤外線リモコン受信モジュール

使用した赤外線リモコン受信モジュールは、秋月電子で110円で売られているPL-IRM0101-3という製品です。

akizukidenshi.com

これ1個を、3本のワイヤでRaspberry Piに取り付けるだけで、赤外線パターンが得られます。この部品のことは、赤外線リモコン自作ページでよく見かけていたので、以前から知ってました。発売日が2004年だそうですので、18年も続くロングセラーです。秋月電子のサイトには、他にも半額くらいで類似の製品がいくつかありますので、そちらでも良かったかもしれません。

サイトには電源が5Vと書いてありましたが、リンクされていたデータシートによると3Vにも対応しているようです。ただ古いロットの製品(Rev B/1)は5V限定だったようです。Raspberry PiのGPIOは3Vなので、現行のRev B/2が必要です。

https://akizukidenshi.com/download/ds/paralight/PL-IRM0101-3_20180307.pdf(https://akizukidenshi.com/)

Raspberry Piに取り付ける

PL-IRM0101-3のピンは3本で、左から、(1)信号、(2)GND、(3)Vccです。

これらの1, 2, 3番ピンをRaspberry PiのGPIOの 7, 9, 1番ピンに接続しました。

温度湿度センサの時と同様に、フラットケーブルになったジャンパ線を割いて使用しました。

そして受信モジュールを、Raspberry Piケースにテープで止めしました。

生データ取得プログラム

GPIOピンの7番はGPIO 4です。データシートによると、受信モジュールは通常は1で、赤外線信号があると0になるようです。下図の上段がモジュールが受け取る赤外線強度、下段がモジュールの出力です。

GPIOをreadしたところ、やはり通常は1で、赤外線が照射されると0になりました。そこで、

  1. GPIOが10秒間1のままならば終了する
  2. GPIOが1ms以上0になったら以下の測定を開始する
  3. GPIOが変化するたびにその間隔をマイクロ秒単位で記録する
  4. GPIOが20ms以上変化しなくなるまで3を繰り返す
  5. 結果を表示する

というプログラムを、Raspberry Pi上のPython3で作成しました。以下です。

#!/usr/bin/python3
import RPi.GPIO as GPIO
import time
import sys
PIN=4
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN,GPIO.IN)
raw=[]

print("Waiting for IR signal (for 10sec)...")

time_now=time.time()
time_start=time_now
while (time_now - time_start) < 10 :
  time_now=time.time()
  if GPIO.input(PIN)==0:
    time.sleep(0.001) #1ms delay
    if GPIO.input(PIN)==0: #still 0
      break

if (time_now - time_start) >=  10 :
  print("Time out.")
  sys.exit()

time_start=time_now
gpio_last=0
while (time_now - time_start) < 0.02 :
  time_now=time.time()
  gpio_now=GPIO.input(PIN)
  if(gpio_last != gpio_now):
    raw.append(round((time_now - time_start)*1000000))
    time_start=time_now
    gpio_last=gpio_now

print(raw)

天井照明リモコンパターンを取得する

簡単な例として、毎回登場している天井照明のリモコンを試します。これのon/offボタンのパターンを取得してみます。上記のプログラムを動かすと(irreceive.pyという名前をつけました)、

$ ./irreceive.py 
Waiting for IR signal (for 10sec)...
[6929, 4539, 371, 1316, 369, 494, 342, 1345, 344, 514, 368, 1319, 369, 492, 345, 1342, 345, 518, 367, 490, 366, 1324, 371, 485, 371, 490, 370, 1318, 345, 1343, 371, 1314, 372, 1323, 345]

という結果が得られました。グラフにすると以下のようです。横軸が時間で縦軸が赤外線出力です。開始のパルスは長めですし、0, 1に相当するパルス幅も広く、一般的なフォーマットとは少し違う特殊なリモコンのようです。

ESP32で作るDIYスマートリモコンに組み込む

ESP32で、エアコン用のスマートリモコンを作りましたが、

diysmarthome.hatenablog.com

今ここで得られた生データを送出する機能を、このESP32に追加して、天井照明もコントロールするように設定します。

 

 

エアコンのリモコンとして動作していたプログラムに、この天井照明のパターンを送出する部分を以下のように追加しました。赤外線点滅の生データを送出するためには、IRsendクラスのインスタンス(下記プログラムではirsend)を作って、これのsendRawというメソッドを使えば良いようです。このメソッドでon/offの生データを以下のように送ります。

#include <Arduino.h>
#include <ArduinoOTA.h>

//IR Remote
#include <IRremoteESP8266.h>
const uint16_t kIrLed = 4;  //GPIO for IR LED. Recommended: 4.
const char  SUBTOPIC[] = "mqttthing/irOffice/set/#";

(略)

IRsend irsend(kIrLed);  //for sending raw IR data
//ceiling light on/off pattern
const uint16_t kCeilingOnOff[35]= {
  6929, 4539, 371, 1316, 369, 494, 342, 1345, 344, 514, 
  368, 1319, 369, 492, 345, 1342, 345, 518, 367, 490, 
  366, 1324, 371, 485, 371, 490, 370, 1318, 345, 1343, 
  371, 1314, 372, 1323, 345
};

void setup() {
(略)
}

//MQTT: subtopic call back
void onMessageReceived(const String& topic, const String& message) { 
  String command = topic.substring(topic.lastIndexOf("/") + 1);

  if (command.equals("Active")) {

(略)

  }else if(command.equals("CeilingOnOff")){
    if(message.equalsIgnoreCase("true")) irsend.sendRaw(kCeilingOnOff, 35, 38);
  }
}

//MQTT: connection callback
void onConnectionEstablished() {
  ArduinoOTA.setHostname("irOffice");
  ArduinoOTA.setPasswordHash("xxxxxxxxxxxxxxx");
  ArduinoOTA.begin();
  client->subscribe(SUBTOPIC, onMessageReceived); //set callback function
}

(略)

void loop() {
  ArduinoOTA.handle();
  client->loop(); 
}

これで、mqttthing/irOffice/set/CeilingOnOffにtrueというメッセージが送られると、この生データを送出します。

一方、HomebridgeのMqttthingの設定部分には、以下のように追記して、このMQTTメッセージに対応するスイッチを作成しました。

{
    "type": "switch",
    "name": "Ceiling Toggle",
    "topics": {
        "setOn": "mqttthing/irOffice/set/CeilingOnOff"
    },
    "turnOffAfterms": 300,
    "accessory": "mqttthing"
}

この結果、iPhone, Macのホーム.appに、以下のように赤外線信号を送出するスイッチが追加されました。turnOffAfterms (turn off after ms) を300msに設定したので、通常はoffの状態で、

クリックすると0.3秒だけonの状態になり、またoffに戻ります。

これをクリックするたびに、上記の赤外線パターンが送出され、天井照明はon/off状態を交互に繰り返します。

デコード機能

前後しますが、上述のPythonプログラムに、赤外線パターンのデコード機能を追加しました。上記のプログラムの末尾に、以下を追加します。

T=400.0

isOn=False
evaluateNext=False
count=0
aByte=0

for x in raw:
  isOn = not isOn
  width=round(x/T)
  if(width > 3) and (isOn == False):
    count=0
    aByte=0
    print()
  if(width == 1) and (isOn == True):
    evaluateNext=True
  elif(evaluateNext):
    evaluateNext=False
    if(width == 1):
      aByte = aByte >> 1
      count += 1
    elif(width == 3):
      aByte = (aByte >> 1) + 0x80
      count += 1
    if(count >= 8):
      print(format(aByte,'02X'),end='')
      aByte=0
      count=0

print()

かなり手抜きです。Tで、短いパルス幅を指定しておきます。T幅の赤外線Onパルスが見つかったら、その次のOff幅を測定して、Tならば0、3Tならば1としています。また、それぞれのバイトのLSBから順番に送信されていると仮定してます。NECと家製協のフォーマットがこの方式なので、汎用性は高いです。

例えば、上記の天井照明リモコン (これは独自フォーマットですが0, 1 がほぼ1T, 3Tです) ならば、

$ ./irreceive.py 
55F2

となります(生データのprint文はコメントアウトしました)。エアコン用スマートリモコンで使用したパナソニックのエアコンならば、

$ ./irreceive.py 
0220E00400000006
0220E00400412C80AF0000066000008000068E

と表示されます。 (これは22度暖房のパターンです)

まとめ

秋月電子などで一般的に売られている赤外線リモコン受信モジュールを、Raspberry Piに取り付けて、赤外線を取得しデコードもできるようにしました。これを用いて、天井照明のリモコンのon/offパターンを測定し、ESP32から送出するようにして、HomeKitから使えるようにしました。この手順で、HomeKit対応スマート学習リモコンDIYできます。

Tuyaの安価なZigbee磁気接触センサ (648円)をHomeKitで使う

Zigbeeを使った安価な磁気接触センサ(開閉センサー)を使いました。送料込みで648円です。Zigbee2MQTTで問題なく認識され、HomeKitから使用することができました。単4電池を2本使用するので、ボタン電池を使用する製品に比べると少し大きいです。しかし安価ですし、電池も容易・安価に入手できるので、サイズが許容される場面では良い製品です。

色々使える接触センサ

リードスイッチセンサーは、磁石ユニットの近接を検知するセンサで、主にドアや窓に取り付けて防犯目的で使用されます。納戸や家具のドアに付けて中の照明を点灯させたり、玄関ドアに取り付けて電気錠でオートロックさせることもできます。このほか、近接状態を検出できる場面で色々と使用できます。例えば、エアコンの吹き出し口フラップに取り付ければ、エアコンの動作状態を検出することもできます。

さらには、センサ部分を加工すれば、安価な無線スイッチとして使えます。ここにプッシュスイッチを取り付ければ、無線呼び鈴やドアベルなどになります。また、JEM-A端子とか、雨量計などの1ビット状態を無線伝達するユニットとしても使えます。今回紹介するようなZigbee製品を流用すると、ESP32でDIYするより安価で省電力な無線スイッチが作れます。

HomeKitで使える磁気接触センサ

今まで何種類かの接触センサを試してきました。今回の製品(リストの最後)を加えると、以下です(価格はAliExpressでの送料込み現在価格)。

  1. TuyaのWiFiセンサ(単4電池x2、当時363円、現在は677円)
  2. TuyaのZigbeeセンサボタン電池、1,028円)
  3. AqaraのZigbeeセンサボタン電池、1,771円)
  4. TuyaのZigbeeセンサ(単4電池x2、648円)

このうち、1番と2番については以下の記事をご覧ください。

diysmarthome.hatenablog.com

diysmarthome.hatenablog.com

それぞれの製品の特徴をまとめた内容が以下です。

  1. TuyaのWiFiセンサ(単4電池x2、当時363円、現在は677円)時期によっては非常に安価ですが、応答が遅い(20秒程度)。TuyaがクラウドAPIを有料化したのでHomeKitからは事実上使用不可能になりました。Home AssistantのローカルTuyaを使えば使用可能なのかもしれません。
  2. TuyaのZigbeeセンサボタン電池、1,028円)Zigbee2MQTT/HomebridgeでHomeKitから使用可能。CR2032電池使用で小型。
  3. AqaraのZigbeeセンサボタン電池、1,771円)Zigbee2MQTT/HomebridgeでHomeKitから使用可能。CR1632電池使用で非常に小型。温度センサ内蔵でこれもHomeKitから使用可能。
  4. TuyaのZigbeeセンサ(単4電池x2、648円)Zigbee2MQTT/HomebridgeでHomeKitから使用可能。この記事で紹介します。

AqaraはXiaomi製品なので高級志向です。高機能なだけでなく、箱のパッケージにもアップル製品の雰囲気があります。2, 3, 4の製品は応答速度も良く安定して使用できるので、どれを選んでも良いです。予算とサイズで選んで良いと思います。

単4電池で長寿命

今回使用した磁気接触センサは、以下の製品です。

https://ja.aliexpress.com/item/1005004779586263.html

単4電池を2本使う製品です。ボタン電池を使うセンサに比べるとサイズが大きいですが、安価です。電池の入手が楽ですし、電池容量は大きいので交換の手間が少ないです。上記のサイトには、

  • スタンバイ電流10μA
  • 動作時電流15mA

との記載があります。パナソニックの電池情報のグラフを単純に外挿してみたら、単4アルカリ電池を10μAで使用する場合、20万時間の電池寿命になりました。23年くらいです。

上記のCR2032を使ったセンサの商品紹介では、1日5回検出される場合で電池寿命が3年と書いてあります。CR2032と単4電池は5倍くらい容量が違うので15年持続する計算になります。電池交換のことは忘れても良いくらいです。

開封して分解

箱から出すとこんな感じです。単4電池2本用を使うので、多少大きいです。ドアや窓なら、見た目を気にしなければ問題ない大きさです。写真に見える点のような穴は、LEDの部分です。ここには写っていないヘリの部分に、リセットボタンの穴があり、そちらにはRESETと書いてあります。

本体裏蓋をスライドさせると電池ボックスが現れます。

中央にネジが見えます。これを外して、軽い噛み合わせを外すと、中が見えます。

左にあるのがワンチップコンピュータです。ZTUという文字が見えます。以前ご紹介したTuyaのZigbeeセンサにも付いていたZigbeeチップです。

developer.tuya.com

磁気スイッチ

磁石が当たる部分には、小さな3ピンの部品がはんだ付けされています。表面には、2373S 2220と書いてあります。検索しても探せませんでしたが、村田製作所などが作っている磁気スイッチというチップにそっくりです。以下はPDF仕様書ページへのリンクです。

https://www.murata.com/en-us/products/productdata/8797583704094/MS201AE.pdf

3本のピンは、それぞれVcc, GND, OUT(出力の1または0)とのことです。実際に回路を追ってみたところ、Vccは電池の+側に、GNDは電池の-側に接続されていました。3Vで動いているようです。

OUTピンは、Zigbeeチップのピンに接続されていました。

このセンサは、磁気抵抗という現象を使って動作しているようです。磁場が加わると抵抗値が変化するセンサを使ったブリッジ抵抗回路を作り、その差異を増幅して出力としているようです。

磁気スイッチの代わりに押しボタンスイッチなどを取り付けたい場合は、この部品を取り外して、改造することになりそうです。ちょっと大変かもしれません。こちらのボタン電池バージョンは、単純なリードスイッチを使用していましたので、そちらの方が改造が楽かと思います。

Zigbee2MQTTに接続してHomeKitで使う

次にZigbee2MQTTにペアリングしてHomeKitで使えるようにします。このためには、Zigbee2MQTTサーバが稼働していて、MQTTブローカー (Mosquitto) が動き、Homebridgeが動いていて、さらにHomebridgeにZigbee2MQTTを使用するプラグインが入っている必要があります。これらをRaspbetty Piで動かしてます。

これらの設定に関しては、以下をご覧ください。

diysmarthome.hatenablog.com

まずは、Zigbee2MQTTのwebインタフェースからペアリングします。ペアリングモードがoffに設定してある場合は、webインタフェース右上のPermit join (All)のメニューをクリックしてペアリングを許可します。すると5分間、ペアリングが可能になります。

この状態で接触センサーの横にあるリセットボタンを5秒程度長押しします。するとLEDが点滅してペアリングモードに入ります。ペアリングが終了すると、以下のようにセンサを確認できました。TS0203というセンサとして認識されているようです。写真が違いますが、おそらくは同じZTUチップを使用したセンサだと思われます。

動作も以下のように確認できます。

Homebridgeには、Zigbee2MQTT用のプラグインが動作しています。なので、iPhoneMacのホーム.appにもすぐにセンサとして現れました。

 

応答も早いです。LED電球をon/offするオートメーションを作成してみましたが、0.2秒くらいで応答します。

まとめ

Tuyaの安価なZigbee接触センサをHomeKitで使いました。送料込み648円は、HomeKitで使える接触センサとして最安と思います。DIYしてもこの価格では作れないです。動作は安定していて、応答も早いです。もう少し買い足しても良いかなと思いました。

エアコンリモコンのHomeKitアクセサリをDIYする (後編:温度センサ他)

エアコンリモコンに相当するアクセサリを作る最終回です。今回のターゲットはパナソニックのエアコンですが、他のメーカーでも同様に作れます。下図のように、(右から)HomeKitに接続したHomebridgeサーバから、MQTT経由でコマンドを出し、ESP32で受け取って、赤外線LEDからリモコン信号を送り、エアコンをコントロールします。

 

前回のあらすじ

こちらの記事から続きます:

diysmarthome.hatenablog.com

diysmarthome.hatenablog.com

パナソニックのエアコンリモコン、A75C3777

と同等の赤外線パターンを送出する仕掛けを、ESP32で作りました。回路は以下です。

赤外線LEDの代わりに青色LEDを付けると以下のように光ります。

次に、HomebridgeのMqttthingプラグインを使って、MQTTメッセージで動作するHeater Coolerアクセサリを作りました。また、ESP32には、MQTTクライアント機能を追加しました。これで、HomeKitからエアコンが操作できるようになりました。

今回の目標〜室温表示と赤外線強化

前回までで、一応動作するようになりました。ただ、温度測定機能を作り込んでいませんでした。Heater Coolerアクセサリは、温度センサも兼ねてます。前回までの実装では、室温を取得していないので、例えばエアコンがoffの時に、0.0°Cと表示されてしまいます。

そこで、ESP32に温度センサ(DHT20)を取り付けて、正しい室温を送ることにします。こちらで作った仕組みです。

diysmarthome.hatenablog.com

また、赤外線LEDも改良しました。ということで、今回の内容は、温度センサの追加と赤外線の強化です。

温度センサを取り付ける

以前の記事で使用したセンサDHT20とESP32プログラムを使います。使用するセンサは、秋月で380円で売られている温度・湿度センサDHT20です。I2C接続なので、電源、グラウンド、信号線2本の、合計4本の配線を追加するだけです。回路図を再掲しておきます。

またDHT20ライブラリを使って温度・湿度情報を取得し、MQTTで送信します。前回のスマートリモコンプログラムに対して、追加する部分は以下です。

//...略...
#include "DHT20.h"
#define GPIO_SDA 21 //I2C for DHT20
#define GPIO_SCL 22 //I2C for DHT20
DHT20 dht; //instance of DHT20
//...略...
const char  PUBTOPIC[] = "mqttthing/irOffice/get"; //to publish temperature
const char  DEBUG[] = "mqttthing/irOffice/debug"; //topic for debug
//...略...
//IR Remo and MQTT: read DHT20 and publish results
void publishDHT() { 
  char buff[64];
  float humi, temp;
  if(DHT20_OK != dht.read()){
    client->publish(DEBUG,"DHT20 Read Error.");
  }else{
    humi=dht.getHumidity();
    temp=dht.getTemperature();
    sprintf(buff, "{\"temperature\":%.1f,\"humidity\":%.0f}", temp, humi);
    client->publish(PUBTOPIC,buff);
  }
} void loop() { client->loop(); if(millis() - dht.lastRead() >= 60000) publishDHT(); }

publishDHT()という関数を作って、ここでDHT20から温度・湿度を読んで、MQTTにパブリッシュしています。この結果、例えば、

{"temparature":21.5,"humidity":64}

というようなメッセージが、mqttthing/irOffice/getトピックに流れます。これを取り込むために、Mqttthingプラグインで作ったHeater Coolerアクセサリに、getCurrentTemperatureのトピックを読む部分を追加します。

{
  "type": "heaterCooler",
  "name": "Aircon",
略
  "topics": {
略
    "getCurrentTemperature": "mqttthing/irOffice/get$.temperature",
略
  },
  "accessory": "mqttthing"
}

.$temperatureは、json形式のtemperatureキーの内容を取得するという意味です。これでオフの時に現在室温が表示されるようになりました。

温度センサとしても表示されます。(別のセンサが23℃と反応しているので両方表示されてます)

DHT20には湿度センサもあるのですが、Heater Coolerアクセサリでは湿度は使われません。もったいないので、MqttthingでHumidity Sensorアクセサリを作って記述しておきます。

{
    "type": "humiditySensor",
    "name": "DHT20_humi",
    "topics": {
        "getCurrentRelativeHumidity": "mqttthing/irOffice/get$.humidity"
    },
    "accessory": "mqttthing"
}

これで湿度センサも表示されました。

赤外線LEDを強化する

今までに回路では、赤外線LEDを1個だけ使用し、これに35mA程度流していました。でもエアコンから離れると動作しないこともありました。またLEDの指向性が高い(半減するのが20度らしいです)ので、LEDを正しくエアコンに向ける必要がありました。なので、電流を上げて、複数のLEDを直列にして指向性を広げようと考えました。その回路を考えるためには、LEDとFETの特性を測りたいところです。

その測定には、こちらの他機能テスターという商品が便利でした。送料込みで2,198円でしたAmazonでも2,899円で買えます。

ROM焼き機のようなソケットに適当に部品を挿入すると、部品種類を自動判定して表示してくれます。抵抗、コンデンサ、コイル、ダイオード、FETなどを測定してくれます。まずは、今回使用している赤外線LEDを測定してみました。すると順方向電圧が1.15Vだとわかりました。3個くらいは直列でいけそうです。

同様に、今回使用したFETを測定しました。閾値電圧は2.5Vで回路図で目論んだ通りでした。ドレインソース間の起電力は0.68V, 抵抗は1.9Ωでした。

なので、LEDを3個直列にして、さらにFETに接続すると、全部の起電力が(1.15 x 3) + 0.67=4.12 Vになります。これを保護抵抗を介して5Vに接続すると、電圧差は 5 - 4.12 = 0.88 Vです。手元に10オームの抵抗がありました。これを保護抵抗として接続することを考えると、抵抗成分はFETの内部抵抗を加えて11.9オームになります。電流は、0.88 / 11.9 = 74mAになると計算できます。

ちょっと多い気もしますが、LEDの絶対定格が150mAで、FETが200mAなので、74mAなら大丈夫な気がします。リモコンなので点灯時間は一瞬ですし、38kHzのデューティ比は1/3らしいので、色々考えて10オームで良いかと思いました。それで以下のように接続しました。

電流を実測したところ大体75mA前後でした。テスターすごいです。ブレッドボードに組んだ様子は以下です。LEDが3個ついてます。また温度センサDHT20が左下に見えてます。

ユニバーサル基板で配線

動作確認できたのでユニバーサル基板

で配線しました。まずはFritzingを使って部品配置を考えます。

そして配線しました。

赤外線が出ていることを確認できるように、赤色LEDを追加しました。

裏側はこちらです。そのうちプリント基板も作りたいです。

まとめ

ESP32で作るエアコン用スマートリモコンに温度センサ機能を組み込みました。安価なZigbee接続温度湿度センサも売られているので、それを併用しても良かったかもしれません。また、赤外線を強化するために、赤外線LEDを複数個にして、電流を増やしました。LEDの足を長めに残しておくと、足を曲げてエアコンに向くように調整できます。

これで温度センサ内蔵のスマートリモコンが完成しました。スマートリモコンは棚の上など、赤外線が機器に届きやすい場所に設置することになります。デバッグするときにUSB接続するのが大変なので、OTAできるようにしました。

diysmarthome.hatenablog.com

エアコンリモコンのHomeKitアクセサリをDIYする (中編:MQTT対応)

エアコンリモコンに相当するアクセサリを作ろうと思いました。ターゲットはパナソニックのエアコンです(他のメーカーでも同様に作れると思います)。下図のように、(右から)HomeKitに接続したHomebridgeサーバから、MQTT経由でコマンドを出し、ESP32で受け取って、赤外線LEDからリモコン信号を送り、エアコンをコントロールします。

 

前編では、この計画のうちの左半分、ESP32から赤外線パターンを送出する機能を作りました。

diysmarthome.hatenablog.com

今回は中編として、右半分、MQTT対応部分を完成させます。

 

 

最初に、HomeKitのHeater Coolerアクセサリの機能を調べて、必要なMQTTメッセージの実装について書きます。次に、前編で作った赤外線リモコン機能にMQTTクライアントを組み込みます。

MQTTに関してはこちらをご覧ください。

diysmarthome.hatenablog.com

前回のあらすじ

パナソニックのエアコンリモコン、A75C3777の信号を取得し、ネット上の解析情報をもとに、ビット情報を把握しました。

次に、ESP32で赤外線を送出する回路を作成しました。

次に、赤外線リモコン用のライブラリIRremoteESP8266を使い、そこに用意されているパナソニックエアコンクラスを用いて、リモコンと同一の赤外線信号を出せるようにプログラムしました。

ダミーのエアコンアクセサリを作って調査する

では今回の本編です。まずはHomeKitのHeater Coolerアクセサリがどのように動いているかを調査しました。そのためにHomebridgeにインストールしたMqttthingプラグイン

github.com

でダミーのエアコンをデフォルト設定で作ってみました。アクセサリのタイプはHeater Coolerを選びます。iPhoneMacのホームには、こんな形で現れます。

気温の表示をクリックするとon/offします。それ以外の部分をクリックすると、On/off、温度調整、動作モード切り替え(冷房・暖房・自動)を行うウィンドウが開きます。

また、このウィンドウの歯車アイコンをクリックする(もしくは上にスクロールする)と、「ファンの速さ」が0から100までのスライダで調節でき、さらに「首振り」をスイッチでon/offできます。

運転切り替えにある「自動」は、日本のエアコンの「自動」に相当するようなモードかと思ったのですが、少し違ってました。以下のように、高低2種類の温度を設定して、それを上回る・下回る場合は、冷房・暖房を作動させるという機能でした。日本のエアコンには無い方法なので、「自動」は使わないことにします。

トピックとメッセージを調べる

次に、HomeKitのユーザ操作によって、MQTTのどのトピックにどのようなメッセージが流れるかを調べました。Mqttthingプラグインの説明によると、以下の項目に、トピック名もしくはjsonのキーを指定することになってます。

  • getRotationMode
  • setRotationMode
  • getActive
  • setActive
  • getCoolingThresholdTemperature
  • setCoolingThresholdTemperature
  • getCurrentHeaterCoolerState
  • getCurrentTemperature
  • getHeatingThresholdTemperature
  • setHeatingThresholdTemperature
  • getRotationSpeed
  • setRotationSpeed
  • getSwingMode
  • setSwingMode
  • getTargetHeaterCoolerState
  • setTargetHeaterCoolerState
  • getTemperatureDisplayUnits
  • setTemperatureDisplayUnits

そこで、これら全部の項目に別個のトピックを設定して、どういうメッセージが流れるのか調べてみました。ホームに現れたアクセサリを操作して、mosquitto_subコマンドで流れるメッセージをモニターしました。またgetなんとかというトピックは、ESP32の方からパブリッシュするタイプだと考えて、mosquitto_pubコマンドを使って色々なメッセージを流してみました。

その結果、全く使われないトピックも多くありました。まだ実装されていないのかもしれません。またgetのトピックにメッセージを流しても、表示に影響を与えないものも多かったです。

また、リモコン側からエアコンの状態を知ることはできません。なので、応答のしようのない項目もありました。例えば、getRotationSpeedは、単なるリモコンであるESP32からは、エアコンの風量設定をを知ることができないので、HomeKitに伝えることができません。それらを省略すると、結局は、以下の項目だけを使うことになりました。

  • setActive(エアコンをon/offする)
  • setCoolingThresholdTemperature(冷房温度を設定する)
  • getCurrentTemperature(現在の室温を伝える)
  • setHeatingThresholdTemperature(暖房温度を設定する)
  • setRotationSpeed(風量を設定する)
  • setTargetHeaterCoolerState(運転モードを設定する)
  • setSwingMode(手元のエアコンには風向スウィング機能は無いけど、風向変更にマップし直す)

その結果、Mqttthingプラグインを以下のように設定しました。

{
    "type": "heaterCooler",
    "name": "Aircon",
    "url": "mqtt://localhost:1883",
    "username": "xxxxxxxx",
    "password": "XXXXXXXX",
    "topics": {
        "setActive": "mqttthing/irOffice/set/Active",
        "setCoolingThresholdTemperature": "mqttthing/irOffice/set/CoolingThreasholdTemperature",
        "getCurrentTemperature": "mqttthing/irOffice/get.$temperature",
        "setHeatingThresholdTemperature": "mqttthing/irOffice/set/HeatingThresholdTemperature",
        "setRotationSpeed": "mqttthing/irOffice/set/otationSpeed",
        "setTargetHeaterCoolerState": "mqttthing/irOffice/set/TargetHeaterCoolerState",
        "SwingMode": "mqttthing/irOffice/set/SwingMode"
    },
    "restrictHeaterCoolerState": [1, 2],
    "accessory": "mqttthing"
},

restrictHeaterCoolerStateは、動作を限定するオプションで、[1,2]と指定することで、「自動」モード無しで、「暖房」「冷房」のみの動作モードになります。

ユーザ操作に対応するMQTTメッセージを確認する

プログラムで、MQTTメッセージにどのように対応すべきかを確認するために、mosquitto_subコマンドでモニターしながら、エアコンアクセサリを操作しました。すると以下のようにメッセージが流れました。

% mosquitto_sub -h localhost -u xxxx -P XXXX -t mqttthing/irOffice/# -v
オフにする
mqttthing/irOffice/set/Active false
冷房にする
mqttthing/irOffice/set/TargetHeaterCoolerState COOL
mqttthing/irOffice/set/Active true
暖房にする
mqttthing/irOffice/set/TargetHeaterCoolerState HEAT
mqttthing/irOffice/set/Active true
暖房の状態で設定温度を20度にする(ドラッグ中はメッセージが出ない)
mqttthing/irOffice/set/HeatingThresholdTemperature 20
冷房にする
mqttthing/irOffice/set/TargetHeaterCoolerState COOL
mqttthing/irOffice/set/Active true
冷房の状態で設定温度を28度にする(ドラッグ中はメッセージが出ない)
mqttthing/irOffice/set/CoolingThresholdTemperature 28
冷房の状態でファンのスライダを0%付近から50%付近にドラッグする
(ドラッグ中もメッセージが出る)
mqttthing/irOffice/set/RotationSpeed 26
mqttthing/irOffice/set/Active true
mqttthing/irOffice/set/RotationSpeed 35
mqttthing/irOffice/set/Active true
mqttthing/irOffice/set/RotationSpeed 49
mqttthing/irOffice/set/Active true
スウィングをonにする
mqttthing/irOffice/set/SwingMode ENABLED
スウィングをoffにする
mqttthing/irOffice/set/SwingMode DISABLED

こんな感じでした。

MQTTに対応するプログラムを考える

この動作を参考にして、ESP32のプログラムを以下のように考えました。

エアコン状態を保存する変数を用意する:状態は以下
{on/off、運転モード、冷房温度、暖房温度、風量、風向のスウィング}

setup(){
  エアコン状態を初期化する
  set/#のトピックに対処するコールバック関数onMessageReceived()割り当てる
  }
  
onMessageReceived(){ set/#が流れた時に呼び出される関数

  もしトピックがset/RotationSpeed()ならば
  {エアコン状態の風量を設定する。
  この後setActiveが来るので赤外線は出さない}
  
  もしトピックがset/CoolingThresholdTemperature()ならば
  {運転モードを冷房にして温度を設定する、赤外線を出す}
  
  もしトピックがset/HeatingThresholdTemperature()ならば
  {運転モードを暖房にして温度を設定する、赤外線を出す}
  
  もしトピックがset/SwingMode()ならば
  {エアコン状態の風向を設定する、赤外線を出す}

 もしトピックがset/Active()ならば
  {メッセージのtrue/falseに合わせて、エアコン状態をon/offにして
  赤外線を送出する}

setTargetHeaterCoolerState()
{メッセージに合わせて運転モードを設定する。
この後setActiveが来るので赤外線は出さない} } } loop(){ MQTTのloop }

プログラムは以下です。使用するトピックス名は、"mqttthing/irOffice/set/#"にしました。これに合致するトピックが来たらコールバック関数onMessageReceivedを呼び出して、その中で#の部分を切り出して判定する方式です。この関数が一番長くなってしまいました。

/* IRremoteESP8266 for Panasonic Aircon */
#include <Arduino.h>

//IR Remote
#include <IRremoteESP8266.h>
#include <ir_Panasonic.h>
const uint16_t kIrLed = 4;  //GPIO for IR LED. Recommended: 4.
IRPanasonicAc pana=IRPanasonicAc(kIrLed); //instance of Panasonic AC
//MQTT
#include <EspMQTTClient.h>
EspMQTTClient *client; //instance of MQTT

//WiFi & MQTT parameters
const char SSID[] = "XXXXXXXX"; //WiFi SSID
const char PASS[] = "xxxxxxxx"; //WiFi password
char CLIENTID[] = "IRremote_9792390784"; //something random
const char  MQTTADD[] = "192.168.xxx.xxx"; //Broker IP address
const short MQTTPORT = 1883; //Broker port
const char  MQTTUSER[] = "xxxx";//Can be omitted if not needed
const char  MQTTPASS[] = "XXXX";//Can be omitted if not needed
const char  SUBTOPIC[] = "mqttthing/irOffice/set/#"; //to subscribe commands
const char  PUBTOPIC[] = "mqttthing/irOffice/get"; //to publish temperature
const char  DEBUG[] = "mqttthing/irOffice/debug"; //topic for debug

//base value for the IR Remote state (Panasonic AC)
const uint8_t kPanasonicA75CState[kPanasonicAcStateLength] = {
    0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
    0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
    0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x00};

void setup() {
  dht.begin(GPIO_SDA, GPIO_SCL); //DHT20
  pana.begin();   //IR Remo
  pana.setRaw(kPanasonicA75CState);
  pana.setTemp(27); 
  pana.setMode(kPanasonicAcCool);
  client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT); 
  delay(1000);
}

//MQTT: subtopic call back
void onMessageReceived(const String& topic, const String& message) { 
  String command = topic.substring(topic.lastIndexOf("/") + 1);

  if (command.equals("Active")) {
    if(message.equalsIgnoreCase("true")) pana.on();
    if(message.equalsIgnoreCase("false")) pana.off();
    pana.send();
  }else if(command.equals("TargetHeaterCoolerState")){
    if(message.equalsIgnoreCase("COOL")) {
      switchMode(kPanasonicAcCool);
    }
    if(message.equalsIgnoreCase("HEAT")) {
      switchMode(kPanasonicAcHeat);
    }
  }else if(command.equals("CoolingThresholdTemperature")){
    switchMode(kPanasonicAcCool);//just in case
    pana.setTemp(message.toInt());
    pana.send();
  }else if(command.equals("HeatingThresholdTemperature")){
    switchMode(kPanasonicAcHeat);//just in case
    pana.setTemp(message.toInt());
    pana.send();
  }else if(command.equals("SwingMode")){
    if(message.equalsIgnoreCase("DISABLED")) pana.setSwingVertical(kPanasonicAcSwingVLowest);
    if(message.equalsIgnoreCase("ENABLED")) pana.setSwingVertical(kPanasonicAcSwingVAuto);
    pana.send();
  }else if(command.equals("RotationSpeed")){
    int speed=message.toInt();
    if(speed > 5) pana.setFan(kPanasonicAcFanAuto);
    else if(speed > 20) pana.setFan(kPanasonicAcFanMin);
    else if(speed > 40) pana.setFan(kPanasonicAcFanLow);
    else if(speed > 60) pana.setFan(kPanasonicAcFanMed);
    else if(speed > 80) pana.setFan(kPanasonicAcFanHigh);
    else pana.setFan(kPanasonicAcFanMax);
  }
}

//MQTT: connection callback
void onConnectionEstablished() {
  client->subscribe(SUBTOPIC, onMessageReceived); //set callback
}

//IR Remo: recover the temperature, fan, and swing on mode switch.
void switchMode(const uint8_t targetmode){
  static uint8_t cTemp=27, hTemp=20; 
  static uint8_t cFan=kPanasonicAcFanAuto, hFan=kPanasonicAcFanAuto;
  static uint8_t cSwingV=0xF, hSwingV=0xF;
  uint8_t currentmode;
  
  currentmode = pana.getMode();
  if(targetmode == currentmode) return; //do nothing
  
  if(targetmode == kPanasonicAcCool) {
    hTemp=pana.getTemp(); hFan=pana.getFan(); hSwingV=pana.getSwingVertical();
    pana.setMode(targetmode);
    pana.setTemp(cTemp);pana.setFan(cFan);pana.setSwingVertical(cSwingV);
  }
  if(targetmode == kPanasonicAcHeat) {
    cTemp=pana.getTemp(); cFan=pana.getFan(); cSwingV=pana.getSwingVertical();
    pana.setMode(targetmode);
    pana.setTemp(hTemp);pana.setFan(hFan);pana.setSwingVertical(hSwingV);
  }
}

void loop() {
  client->loop(); 
}

プログラムは長くなってしまいましたが、MQTTトピックとメッセージに従って、前回の記事で紹介したエアコンクラスのメソッドを呼び出しているだけです。使っているメソッドは、(インスタンス名をpanaにしているので)

  • pana.on();
  • pana.off();
  • pana.send();
  • pana.setTemp();
  • pana.setSwingVertical();
  • pana.setFan();
  • pana.setMode()

などです。また、運転モードを切り替える時に、設定温度などをバックアップしておく仕組みも前回の記事のまま使ってます。

次回の予定

MQTTトピックの、get/CurrentTemperatureは、リモコンが、現在の室温をHomeKitに知らせる時に使います。室温は、エアコンがオフの時に表示されます。今回はこれが未実装ですので、エアコンがオフの時に0度と表示されてしまいます。

次回は、ESP32に温度センサ(DHT20)をつけて、正しい室温を送ることにします。こちらで作った仕組みです。

diysmarthome.hatenablog.com

また、実際に使用してみたところ、赤外線が少し弱いようです。今は赤外線LEDに37mA程度流していますが、LEDにもFETにも余裕があるので、もう少し流しても良いです。また、赤外線LEDの指向性が強いため(半減角度が20度)、LEDがエアコン方向に向くように調整する必要があります。LEDの数を増やして、指向性を広げると良いと考えています。赤外線LED周りの改良も、次回の後編で予定してます。

まとめ

ESP32からエアコンリモコンするプログラムを作った前編に引き続き、本編ではMQTTメッセージを受けて赤外線信号を出すようにプログラムを追記しました。これで、iPhoneMacのホーム.appからHeater Coolerアクセサリとしてコントロールできるようになりました。これでエアコン操作はできましたので、一応の完成です。次回は、室温を伝える部分を作り、赤外線LEDの改良を行う予定です。

以下に続きます:

diysmarthome.hatenablog.com

エアコンリモコンのHomeKitアクセサリをDIYする (前編:赤外線送出)

エアコンリモコンに相当するアクセサリを作ろうと思いました。これでiPhone/Macの画面に上の図のようなコントローラが現れるはずです。最初のターゲットはパナソニックのエアコンです(他のメーカーでも同様に作れるはずです)。下図のように、(右から)

 

 

  1. HomeKitに接続したiPhone/MacからHomebridgeのアクセサリを操作すると、
  2. HomebridgeのMqttthingプラグインがMQTT経由でメッセージを出し、
  3. このMQTTメッセージをESP32が受け取って、
  4. ESP32が赤外線LEDからリモコン信号を送り、エアコンをコントロール

することを目指します。今回はこのステップの最後の部分、ESP32が赤外線LEDを点灯してエアコンをコントロールする部分を作ります。

 

 

残りの右半分の部分、つまり、ESP32がMQTTに接続して、HomeKitのエアコンアクセサリを構成する部分は、また後日進めます。

赤外線リモコン自作計画

今までは、Nature Remoのスマートリモコンを1個買って、エアコンをコントロールしてました。これで何の問題もなく動いていました。

diysmarthome.hatenablog.com

Natureは、日本の会社だけあって、国内のエアコンにきめ細かく対応しているようです。

ただ、赤外線リモコン代わりなので、他の部屋のエアコンなどをコントロールしようとすると、部屋ごとにNature Remoが必要です。Nature Remoは結構高価なので、エアコンの台数だけ買うのは辛いです。Tuyaの格安スマートリモコンも試しましたが、無料だったクラウドが有料(年額45万円くらい)になってしまいました。Nature Remoのweb APIも、開発チームのご厚意で使わせてもらっている状況と言えます。回数制限もありますし、いつ閉鎖されるかもしれません。頑張って自作すれば市販品を買わなくて済み、クラウドの変更に振り回される心配も無いと考えました。

前回、ESP32に赤外線LEDをつけて、あらかじめ取得した赤外線信号パターンをを送出して、シーリング照明をオン・オフさせるデバイスを自作しましました。そしてHomeKitの照明アクセサリとして動作させました。

diysmarthome.hatenablog.com

今回は、同じハードウェアで、今度はエアコンを操作します。

照明のon/offとかテレビのon/offのような短い固定パターンを送出するだけならば、Nature RemoにもローカルAPIがあり、クラウドに依存することなく利用できます。

diysmarthome.hatenablog.com

エアコンの場合は設定温度などの情報を含めた赤外線パターンを送出する必要があり、長くて複雑なので、ローカルAPIを使うとしても、ある程度のプログラミングが必要です。なのでESP32に任せることにします。

赤外線情報を確認する

エアコンに送る赤外線リモコン情報は、シーリング照明(2バイトでした)やテレビなどと違い長くて複雑で変動します。今回対象とするパナソニックのリモコンの場合、送出情報は27バイトあります。

一般に赤外線リモコンは、リモコンから本体への一方向通信です。双方向通信ではないため、リモコン側は、エアコンの設定状態を知ることができません。エアコンのリモコンには、運転モード、設定温度、風量風向などが液晶表示されますが、これはエアコン本体から得た情報ではありません。リモコンで勝手に表示している情報です。これとエアコン状態を無理やり一致させるために、毎回全ての設定項目を送出しいます。なのでソフトウェア上に「リモコンオブジェクト」というような仮想のリモコンを作って、それの温度や風量を設定して、最後に全設定を赤外線送出するプログラムを作ります。

今回対象とするエアコンのリモコンは下のものです。

撮影前に掃除にはしたのですが、古くて小汚くてすみません。型番はA75C3777です。検索すると互換リモコンもあります。普及している製品かと思います。

こちらのページで詳しく解析されていました。参考にさせていただきました。少し型番が違うけど、見た目は同じリモコンです。

github.com

このリモコンは、19バイトの信号を出しているようです。実際にNature Remoを使ってon/offパターンを取得してみました。

 

追記:赤外線リモコン受信モジュールを使ってパターンを取得できるようにしました。データも復号できます。これを使うと楽になりました。

diysmarthome.hatenablog.com

 

27度冷房、風量・風向共に自動の設定では、

{"format":"us","freq":37,"data":[3429,1782,388,481,387,1346,390,479,391,477,392,477,394,474,390,479,388,481,389,479,390,479,390,479,387,481,387,482,419,1311,393,477,390,480,391,476,390,479,390,479,387,481,388,481,390,1347,388,1345,392,1350,385,479,392,477,391,1346,389,479,390,479,388,481,389,479,390,479,390,478,420,444,393,480,387,481,386,478,392,477,392,477,391,477,390,479,389,479,390,479,389,479,390,479,387,482,387,481,389,479,388,481,389,479,388,481,387,482,387,481,386,478,392,477,392,477,391,477,391,1347,389,1348,387,481,390,479,389,479,387,482,387,481,388,10030,3428,1780,391,477,390,1347,392,477,390,479,389,479,422,442,425,443,395,477,389,479,390,479,390,478,390,479,387,482,387,1345,424,442,391,481,391,476,390,479,390,479,421,443,391,481,390,1347,420,1313,391,1346,391,477,392,477,390,1347,422,442,393,479,388,481,389,479,390,479,389,479,420,444,391,482,387,481,386,479,389,479,392,477,391,477,422,1313,391,480,389,479,388,481,387,1346,390,1348,389,480,390,478,390,479,389,1348,390,1347,388,481,387,1346,392,1345,392,477,389,479,390,479,389,479,390,479,388,481,389,479,390,479,387,482,419,1311,391,1348,392,1345,390,1347,390,1347,390,479,390,1348,386,482,387,1350,387,477,390,479,392,476,392,477,389,480,421,442,426,442,392,481,389,480,389,479,387,482,387,481,388,481,386,478,392,477,392,477,389,479,390,1348,389,1348,387,482,419,442,394,481,385,479,392,477,392,477,391,477,390,479,389,480,389,479,388,1350,387,1350,388,481,387,477,392,477,392,476,390,479,390,479,389,479,388,482,386,482,389,479,389,479,390,479,387,482,387,481,387,482,387,481,385,484,387,477,392,477,390,479,421,444,391,479,390,479,388,480,390,1348,389,479,390,479,389,479,388,481,387,482,386,482,385,479,392,477,392,477,389,1348,389,1348,390,479,389,479,388,481,388,481,387,481,386,483,388,476,392,477,392,1345,390,479,387,481,388,481,387,1350,395]}

という 結果が得られました。この情報の読み方は以下です。例えば最初の部分、

[3429,1782,388,481,387,1346,

は、

  1. 3429μs赤外線をonにして、1782μs赤外線をoffにして、(開始)
  2. 388μs赤外線をonにして、481μs赤外線をoffにして、(0)
  3. 387μs赤外線をonにして、1346μs赤外線をoffにする、(1)

という意味です。このデータは家電製品協会(AEHA)フォーマットと呼ばれる赤外線パターンです。425μsくらいが信号の基準長で、これをTとすると、{Tの点灯, Tの消灯}のペアが0、{T, 3T}のペアが1です。最初の長い{8T, 4T}のペアは、開始を表します。また{Tの点灯, 8ms以上の消灯}が終了を表します。またビット順は、バイトデータのLSB(最下位ビット)から先に送信されます。このことから、赤外線信号をバイト列に変換したところ、githubに書かれているA75C4269と全く同じ内容でした。

赤外線on/offパターンをグラフにすると以下のようになりました。横軸が時間、縦軸がon/offです。前半の短い信号と、後半の長い信号がセットになっていて、間に10msの空白時間が入ってます。

 

 

前半の短い信号は、8バイトで、毎回必ず同じ内容です。その内容は、

0x0220E004000000 06

です。最後のバイト(上の例では0x06)はチェックサム(その前7バイトの合計の下1バイト)です。後半の長い信号は19バイトです。その内容は、今回の場合は、

0x0220E00400 313680AF000006600000800006 88

でした。長い方の信号も、5バイトまでは同じパターンで始まってます。それに引き続く部分に、エアコンの運転モード(冷房、暖房、除湿、送風など)、設定温度、風速・風向などの情報が入っています。最後の1バイト(上では0x88)はチェックサムで、この前の18バイトの合計の下1バイトです。

この全部で27バイトの情報を適切に作成して、赤外線を送出できればエアコンを操作できます。

赤外線を出すハードウェア

これは前回の記事で作成したものと全く同じですので、そちらをご覧ください。回路図だけを再掲します。MOS FETで赤外線LEDをon/offします。

赤外線を出すプログラム例

これも前回の記事で紹介したIRremoteESP8266というライブラリを使いました。ESP8266 / ESP32用です。前回は、シーリング照明リモコンの単純なパターンをそのまま送出しました。今回は、家電製品ごとに用意されているクラスを利用します。パナソニックのクラスがありましたのでそれを使います。

ライブラリのgithubサイトは以下です。このソースは参考になりました。

github.com

このライブラリで、エアコンを赤外線コントロールする手順は以下です。

  1. エアコンリモコンクラスからインスタンスを作る
  2. そのインスタンスに運転モード、温度、風量、風向などを設定する
  3. そのインスタンスに発光コマンドを送る

物理的なリモコンに相当する存在(インスタンス)を作って、それを操作するというオブジェクト指向の概念です。簡単なプログラミング例で動作を確認してみましょう。

Arduinoにライブラリをインストールすると、ファイル・スケッチ例・IRremoteESP8266というメニュー項目ができています。その中に、TurnOnPanasonicACというサンプルプログラムがあります。これを動かしながら、動作を確認しました。これを元に、シンプルなプログラムを作りました。これは15秒ごとに、設定温度27度で冷房をonにするプログラムです。

/* IRremoteESP8266 for Panasonic Aircon */
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <ir_Panasonic.h>
const uint16_t kIrLed = 4; //GPIO pin to use.
IRPanasonicAc pana(kIrLed); //make an instance.

void setup() {
  pana.begin();
  Serial.begin(115200);
  while (!Serial); //wait for serial to connect.
}

void loop() {
  pana.on(); //power on the aircon.
  pana.setTemp(27);
  pana.setMode(kPanasonicAcCool); 
  pana.setFan(kPanasonicAcFanAuto);
  pana.setSwingVertical(kPanasonicAcSwingVAuto);
  pana.send(); //send IR signal.
  delay(15000); //15 sec delay.
}

最初に、IRPanasonicACクラスのインスタンスを作ります。この時に、赤外線LEDを接続したGPIOポート番号(4番が推奨)を書いておきます。

次にこのインスタンスの、on(), setTemp()メソッドを呼び出してそれぞれを設定します。またsetMode(), setFan(), setSwingVertical()メソッドを使って、運転モード、風量・風向を自動に設定しました。パラメータの値は、ir_Panasonic.hファイルの中に書いてあります。設定が終了したところで、send()メソッドで赤外線を送出します。

この結果、以下のバイト列が赤外線LEDから送出されました。

0x0220E004000000 06

0x0220E00400 313680AF00000EE00000810000 0B

赤外線パターンが実リモコンと一致しない

ESP32が出した赤外線パターンと、実リモコンのパターンは多少違ってました。27度冷房、風量・風向を自動にした場合の赤外線パターンを比較します。2つのパートでできているパターンを繋げて、27バイトの比較をします。最初の8バイト、次の5バイトは固定です。最後の1バイトはチェックサムです。それぞれのパートの最後の1バイトは、チェックサムです。

実リモコンのパターンは、以下でした。

0x0220E004000000 06 0220E00400313680AF000006600000800006 88

これに対してESP32が作ったパターンは、以下でした。

0x0220E004000000 06 0220E00400313680AF00000EE00000810000 0B

20, 21, 24, 26バイト目の合計4箇所で違いがあります。

最初に参照した解析ページによると、20, 21バイト目 (0660と0EE0) はon/offタイマーの値だそうです。実リモコンが出す値は0660で、これがon/offタイマーを使わない時の値だそうです。0EE0は実リモコンでは使われない値でした。一方、24バイト目 (80と81) の場所は実際の実リモコンでは80、ESP32では81、さらに26バイト目は、実リモコンで06、ESP32で00です。24, 26バイト目の内容は不明とのことです。

いずれも些細な違いなので、このままでも問題ないと思われますし、実際、エアコンもこれで反応します。このままでも良かったのですが、せっかくだからもう少し調整して、ESP32で作った赤外線パターンを、実リモコンと完全に一致させたいと思います。

setModel()で調整する

実リモコンには、色々なモデルがあって、多少ビット構成が違うようです。それを調整するためのメソッドに、setModel()がありました。最初はこれを使って調整できると思いました。Panasonicエアコンの細かいモデルの設定のようです。ソースコートを見ると、

  1. クラスのコンストラクタで、動作確認できている赤外線信号パターンを初期値として設定して、
  2. setModel()メソッドで、特定のバイトやビットを書き換えて、赤外線パターンの修正をしているようです。

パナソニック製品でビットパターンが違うところを修正しているのかと思います。ただ、ソースコードにあるすべてのモデルを試してみましたが、setModel()を行わない初期値が手元のリモコンに一番近い値(上の結果)でした。ということで、setModel()は省略してしまいました。

setRaw()で調整する

setModel()でパターンを一致させられなかったので、別の方法を考えました。最初は、RPanasonicACクラスのサブクラスを作って、調整するメソッドを追加しようかと思ったのですが、信号パターンの変数がprivate宣言されていてアクセスできませんでした。一方で、setRaw()メソッドが用意されていて、これで信号パターンを置き換えることができそうでした。そこで、A75Cリモコンに合わせた27バイトの信号パターンを定義して、インスタンス化した直後に、setRaw()メソッドで初期値として設定しておくことにしました。

以下のように先のプログラムを書き換えました。変更部分は太字の部分です。

/* IRremoteESP8266 for Panasonic Aircon */
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <ir_Panasonic.h>
const uint16_t kIrLed = 4; //GPIO pin to use.
IRPanasonicAc pana=IRPanasonicAc(kIrLed); //make an instance.
const uint8_t kPanasonicA75CState[kPanasonicAcStateLength] = {
    0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
    0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
    0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x00};

void setup() {
  pana.setRaw(kPanasonicA75CState);
  pana.begin();
  Serial.begin(115200);
  while (!Serial); //wait for serial to connect.
}

void loop() {
  pana.on(); //power on the aircon.
  pana.setTemp(27);
  pana.setMode(kPanasonicAcCool); 
  pana.setFan(kPanasonicAcFanAuto);
  pana.setSwingVertical(kPanasonicAcSwingVAuto);
  pana.send(); //send IR signal.
  delay(15000); //15 sec delay.
}

これで、生成される赤外線パターンは、

0x0220E004000000 06 0220E00400313680AF000006600000800006 88

となり、リモコンと一致しました。27度冷房、風量・風向共に自動という条件で、リアルリモコンと100%同一のビット列を作ることができました。

光パターンを確認する

今回も、青色LEDに差し替えて、動作確認しました。点灯している前後の2秒を切り出した動画です。

2バイトしか送っていなかったシーリング照明の時の点灯

https://cdn-ak.f.st-hatena.com/images/fotolife/d/diysmarthome/20221130/20221130190651.gif

と比べると、今回は27バイトなので長いです。

赤外線LEDに差し替えて、Nature Remoでパターンを取得して、点灯・消灯時間列の情報を取得しました。これを元にグラフにしました。リアルリモコンと全く同じビットパターンになってました。安心です。

冷房と暖房の設定を保存する

プログラム上でもう一つ細かい工夫をしました。例えば以下のようなシナリオを考えます。

  1. 冷房運転にする
  2. 室温を27度に設定する
  3. 暖房運転にする
  4. 室温を20度に設定する
  5. 冷房運転にする

これを単純にプログラムすると、

  1. pana.setMode(kPanasonicAcCool); on(); send(); //冷房運転にする
  2. pana.setTemp(27); send(); //室温を27度に設定する
  3. pana.setMode(kPanasonicAcHeat); on(); send(); //暖房運転にする
  4. pana.setTemp(20); send(); 室温を20度に設定する
  5. pana.setMode(kPanasonicAcCool); on(); send(); //冷房運転にする

となりますが、最後のステップ5で、冷房の設定温度が20度になってしまいます。実リモコンでは、冷房・暖房のそれぞれで設定温度を別に記憶しています。温度だけでなく、風量は風向などの設定も、運転モードごとに記憶してます。なので運転モードを切り替えると、そのモードで設定した値に戻してくれます。

これを実現するために、冷房用と暖房用のインスタンスをそれぞれ作成する方法を試しましたが、うまくいきませんでした。ということで、27バイトの状態変数を保存する方法にしました。先に、初期値として設定したのと同じような方法です。

まずは、冷房用と暖房用にそれぞれ27バイトの状態変数を用意します。このリモコンにはリセットボタンがついているので、リセット直後の冷房・暖房の設定での赤外線パターンをそれぞれの状態変数としました。

また、冷房・暖房の切り替えのために、switchMode()という関数を作って、そこで切り替えるようにしました。この関数の中で、冷房・暖房の27バイトの変数を保存し、次の切り替えの時に復帰させてます。

#include  //for std::memcpy method
(略)
IRPanasonicAc pana=IRPanasonicAc(kIrLed); //Remo.
//base value for the IR Remote state (Panasonic AC)
uint8_t coolState[kPanasonicAcStateLength] = { //Cool 28, auto 冷房用の初期データ
0x02,0x20,0xE0,0x04,0x00,0x00,0x00,0x06,
0x02,0x20,0xE0,0x04,0x00,0x31,0x38,0x80,0xAF,0x00,0x00,0x06,0x60,0x00,0x00,0x80,0x00,0x16,0x9A
};
uint8_t heatState[kPanasonicAcStateLength] = { //Heat 20, auto 暖房用の初期データ
0x02,0x20,0xE0,0x04,0x00,0x00,0x00,0x06,
0x02,0x20,0xE0,0x04,0x00,0x41,0x28,0x80,0xAF,0x00,0x00,0x06,0x60,0x00,0x00,0x80,0x00,0x06,0x8A
};

void setup() {
  pana.begin(); pana.setRaw(coolState);
(略) } //recover the temperature, fan, and swing on mode switch. void switchMode(const uint8_t targetmode){ uint8_t currentmode; currentmode = pana.getMode(); if(targetmode == currentmode) return; //no switch, do nothing const uint8_t *raw=pana.getRaw(); if((currentmode == kPanasonicAcHeat) && (targetmode == kPanasonicAcCool)) { std::memcpy(heatState, raw, kPanasonicAcStateLength); pana.setRaw(coolState); } if((currentmode == kPanasonicAcCool) && (targetmode == kPanasonicAcHeat)) { std::memcpy(coolState, raw, kPanasonicAcStateLength); pana.setRaw(heatState); } } void loop() { switchMode(kPanasonicAcCool); pana.on(); pana.setTemp(27); pana.send(); //冷房運転にする delay(180000); //3分間隔 switchMode(kPanasonicAcHeat); pana.on(); pana.setTemp(20); pana.send(); //暖房運転にする delay(180000); //3分間隔 switchMode(kPanasonicAcCool); pana.on(); pana.send(); //冷房運転にする delay(180000); //3分間隔 }

これで、温度、風量、風向(垂直)の状態は冷房・暖房それぞれで保持されます。これ以外にも、除湿とか送風での保存、水平方向の風向など、保持しても良い項目はあります。でも手元のエアコンには水平方向の調整はできず、HomeKitには除湿の項目が無いので、そこまでは作り込みしません。

まとめ

ESP32に赤外線LEDをつけて、エアコンをリモコンできるようにしました。次はこれをHomeKitに接続して、iPhoneMacからエアコンを設定したいと思います。

エアコンのリモコン操作では、温度や風量・風向などのデータも同時に送付するので、簡単な学習リモコン方式では実現できません。実リモコンを使って解析した情報がネット上にたくさんありました。ただ、リモコン操作で変化しないために、用途が不明なビットが多くあります。機種によって違ったり、後の新機能のために予約されているビットかと思います。そのためNature RemoやSwitch Botのサポートの人たちは大変だと思いました。スマートリモコンの設定がたまたま動いた状態であって、他の機能に干渉している可能性もあります。手持ちのリモコンの必要な機能を再現できているかどうか確認するためには、実際に信号を解析する必要があると思いました。

ハードウェアは前回の記事と同じで、安価にDIYできます。リモコンパターンの完璧さを求めることも可能なので、高いスマートリモコンを買ってくるよりは、DIYすることがおすすめだと思いました。

以下に続きます:

diysmarthome.hatenablog.com

diysmarthome.hatenablog.com

 

温度湿度センサDHT20をESP32に接続してHomeKitから使う

秋月電子で380円で販売されているI2C接続の温度湿度センサDHT20を、前回はRaspberry Piに接続しました。今回はこれのESP32版です。

diysmarthome.hatenablog.com

このセンサを、ESP32に接続します。そして取得した温度、湿度のデータを、今回もMQTTメッセージとして流し、HomeKitで温度、湿度センサとして表示させます。センサ情報は下図の左から右に流れます。MQTTブローカとHomebridgeがLAN内のRaspberry Pi 4で稼働していて、これがESP32からのMQTTメッセージを受けて、ソフトウェア的なセンサアクセサリを作り、iPhoneMacのあるHomeKit側に公開します。

 

 

DHT20を使うだけなら、前回のようにRaspberry Piに直結すれば簡単でした。でもESP32に接続できれば、安価に作れるので複数の場所の温度を知りたい場合に適してます。実はこの先、ESP32を使ったエアコンリモコンを作ろうと考えてます。それの温度センサ部分に使いたいと思い、準備のためにESP32に接続しました。

回路を作る

回路と言っても、Raspberry Piの時と同様に、ESP32にI2Cの2本の線で接続します。さらに電源(3.3V)とGNDを配線します。

GPIOは21と22を使用しました。他のピンでも可能なようですが、この2本を使う例がほとんどでした。ブレッドボードの実体配線図はこちらです。

配線した様子です。DHT20の足は弱いので、慎重にブレッドボードに挿します(曲げてしまいました)。

ライブラリを選択する

Arduino IDEのライブラリマネージャから、それらしいライブラリを検索しました。I2Cで検索すると多数現れました。いろいろ探しているうちに、なんとDHT20のためのライブラリもありそうでした。そこでdhtで検索したところこれも多数現れました。

DHT11, DHT12, DHT22などのセンサもあります。型番も形もは似てますがこれらはI2Cでは無い1線で通信するタイプです。DHT11は秋月電子でも売ってます。でもDHT20よりも値段が高くて精度が悪いので、DHT20が良いと思います。

そこで、DHT20だけに絞って検索しました。すると2個のライブラリが見つかりました。

便宜上、片方をDFRobot版、もう片方をRob版と呼ぶことにします。結局はどちらも試したのですが、どちらのライブラリも問題なく動作しました。更新はRob版の方が新しいですが、DFRobot版も昨年に更新されているので、放置されているわけでは無いです。調べたところ、使い方が簡単なのはDFRobot版、機能が充実しているのがRob版と言えます。

それぞれのバージョンで温度・湿度を読む手順は、DFRobota版が、

  1. getTemperature()で温度を読む
  2. getHumidity()で湿度を読む

ですが、Rob版の手順は

  1. read()コマンドを出す
  2. getTemperature()で温度を読む
  3. getHumidity()で湿度を読む

です。read()コマンドを出さずにgetすると、前回read()したタイミングでの値が、再びそのまま返ってきます。

Raspberry Piに接続したときは、I2C通信を生で使いましたが、その時の温度・湿度データ取得の手順は、

  1. DHT20にトリガーコマンドを送る
  2. 80ms待つ
  3. 温度と湿度のデータが一緒に得られる

でした。DFRobot版はAPIが簡単で分かりやすいすが、おそらくは温度、湿度を読むたびに、それぞれでトリガーコマンドを送って80ms待機する動作をしていると思われます。一方で、Rob版は、元々のセンサのI2Cプロトコルに忠実な無駄の無い実装だと思いました。DFRobotというところは、教育に重点を置きつつ、シングルボードコンピュータや電子部品を販売している会社のようです。日本だとスイッチサイエンスみたいな会社かな。プログラミング初心者が挫折しないよう、教育的な配慮がされているのかと想像しました。

室内の温度・湿度を取得するなら80ms程度の遅延はどうでも良いことですが、無駄な手順を踏むのは気持ちが落ち着かないので、Rob版を使うことにしました。Rob版のMore Infoをクリックした先のサイトはこちらです。

github.com

サンプルを動かす

ライブラリをインストールすると、ファイル・スケッチ例・DHT20のメニューの中にいくつかのサンプルファイルが見えるようになります。ここのDHT20というサンプルを動かしました。問題なくコンパイルできて動作しました。温度と湿度の値がシリアルモニターに表示されました。

ライブラリの基本的な使い方は:

  1. DHT20のクラスのインスタンスを作る(以下dht)。引数でGPIOを指定できるが省略すると21, 22
  2. setup()でdht.begin()をする
  3. dht.read()をする。戻り値が0ならばエラーなし。50msくらいブロックされる。
  4. float dht.getTemerature()すると戻り値に温度が得られる、
  5. float dht.getHumidity()すると戻り値に湿度が得られる
  6. 1秒程度以上待ってからステップ3に戻る

です。

read()して、getTemperature(), getHumidity()で温度湿度を得るのが基本的な順番です。getTemperature()とgetHumidity()は何回も続けて呼べるけど、次にread()するまで同じ値が返ってくるだけのようです。

read()では50msくらいブロックされるそうです。このサンプルのシリアルモニターに表示されているTimeの値は、ブロックされていた時間を表示しています。47msくらいでした。このブロック時間が問題になる場合には、非同期で動かす機能がライブラリにあるのでそちらを使うそうです。スマートホームの室温測定の目的なら50msは全く問題ないので、単純に待つことにします。

シンプルなプログラムを作る

スケッチ例を参考に、シンプルなプログラムを作りました。

#include "DHT20.h"
DHT20 dht = DHT20();

void setup(){
  dht.begin(); //default: 21,22
  Serial.begin(115200);
  while (!Serial); //wait for serial connected.
  delay(1000);
}

void loop(){
  if(millis() - dht.lastRead() >= 1000) {
    if(DHT20_OK == dht.read()){
      Serial.print("Humidity: ");
      Serial.print(dht.getHumidity(), 1);
      Serial.print("  Temerature: ");
      Serial.println(dht.getTemperature(), 1);
    }
  }
}

1秒おきに温度・湿度を読むプログラムです。lastRead()で前回read()した時刻が読めてタイミング調整に使えるようです。read()してエラーが返って来なかった時だけシリアル表示します。

MQTTにパブリッシュする

Raspberry Piの時と同じく、結果をMQTTブローカーパブリッシュする機能を追加しました。トピック名なども同じです。

#include <EspMQTTClient.h>
#include "DHT20.h"
EspMQTTClient *client;
DHT20 *dht = new DHT20();

//WiFi & MQTT
const char SSID[] = "XXXXXXXX"; //WiFi SSID
const char PASS[] = "xxxxxxxx"; //WiFi password
char CLIENTID[] = "DHT20_623575725"; //something random
const char  MQTTADD[] = "192.168.xxx.xxx"; //Broker IP address
const short MQTTPORT = 1883; //Broker port
const char  MQTTUSER[] = "xxxx";//Can be omitted if not needed
const char  MQTTPASS[] = "XXXX";//Can be omitted if not needed
const char  PUBTOPIC[] = "mqttthing/dht20/get"; //mqtt topic
const char  SUBTOPIC[] = "mqttthing/dht20/set"; //mqtt topic
const char  DEBUG[] = "mqttthing/dht20/debug"; //mqtt topic

void onConnectionEstablished() {
  Serial.println("MQTT connection established.");
  client->subscribe(SUBTOPIC, onMessageReceived); //callback
  publishDHT();
}

void onMessageReceived(const String& msg) { 
  publishDHT();
}

void publishDHT() {
  char buff[64];
  float humi, temp;
  if(DHT20_OK != dht->read()){
    client->publish(DEBUG,"DHT20 Read Error.");
  }else{
    humi=dht->getHumidity();
    temp=dht->getTemperature();
    sprintf(buff, "{\"temperature\":%.1f,\"humidity\":%.0f}", temp, humi);
    client->publish(PUBTOPIC,buff);
  }
}

void setup()
{
  dht->begin(GPIO_SDA, GPIO_SCL); 
  Serial.begin(115200);
  while (!Serial);  //wait for serial.
  client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT); 
  delay(1000);
}

void loop()
{
  client->loop();
  if(millis() - dht->lastRead() >= 180000) publishDHT();
}

更新頻度を3分にしました。別のターミナルウィンドウでmosquitto_subコマンドを動かし、サブスクライブしておきます。結果は以下のようになりました。 

mqttthing/dht20/get {"temperature":22.9,"humidity":61}
mqttthing/dht20/get {"temperature":22.9,"humidity":61}
mqttthing/dht20/get {"temperature":22.9,"humidity":60}

また、おまけの機能として、setというトピックに何かメッセージを送ると、次の3分を待たずにすぐに結果を返してくれるようにしてみました。Zigbeeセンサで見かけた機能です。

% mosquitto_pub -h 192.168.xxx.xxx -t "mqttthing/dht20/set" -m {}

などすると 、すぐに

mqttthing/dht20/get {"temperature":22.1,"humidity":62}

のように、結果を返します。測定結果がすぐに必要なアプリで役立つのではと思いました。

Homebridgeで受け取る

ここから先は、前回Raspberry PiにDHT20を取り付けた時と同じです。HomebridgeにはMqttthingプラグインを入れてあります。MQTTメッセージで動くアクセサリを実装できます。

diysmarthome.hatenablog.com

github.com

Mqttthinの設定から、tenmeratureSensorとhumiditySensorを使います。MQTTの設定は、プラグインの設定画面からGUIで入力できます。最終的に出来上がったconfigの部分は以下になりました。

{
    "type": "temperatureSensor",
    "name": "DHT20_temp",
    "username": "xxxxxxxx",
    "password": "XXXXXXXX",
    "topics": {
        "getCurrentTemperature": "mqttthing/dht20/get$.temperature"
    },
    "accessory": "mqttthing"
},
{
    "type": "humiditySensor",
    "name": "DHT20_humi",
    "username": "xxxxxxxx",
    "password": "XXXXXXXX",
    "topics": {
        "getCurrentRelativeHumidity": "mqttthing/dht20/get$.humidity"
    },
    "accessory": "mqttthing"
}

Mqttthingは、json形式のデータも扱えます。上の設定で、

mqttthing/dht20/get$.temperature

としたことで、mqttthing/dht20/getトピックに流れてくるjson形式のデータ:

{"temperature":23.0,"humidity":56}

のtemperatureキーの部分(23.0という値)を取得できます。humidityも同様です。

HomeKitから使ってみる

この結果、iPhoneMacのホーム.appの上に、

というような表示が現れ、これをクリックすると、

のように、今回取り付けたセンサーの値が表示されました。これをもとにオートメーションも作れます。室温が下がったらエアコンを動作させるとか、湿度が下がったら加湿器をonにするなどの自動化が可能です。例えば、上のDHT20_humiの表示をクリックして、「オートメーションを追加」のメニューを選択すると、数クリックの操作で、下のようなオートメーションが完成します。

まとめ

秋月で380円で買えるI2C温度・湿度センサDHT20をESP32に接続して、iPhoneMacのホーム.appから利用できるように設定しました。Raspberry Piの時と同様に、4本の線を配線するだけで温度・湿度センサが作れます。専用のライブラリも用意されていたので、プログラミングも簡単でした。結果をMQTTのメッセージで流すようにしたので、他のプログラムやスマートホームシステムからも利用できます。

ESP32に赤外線LEDを取り付けてエアコン用のスマートリモコンを作ろうと予定してます。HomeKItの空調アクセサリには、室温測定機能が備わっていることになってます。今回の仕組みで、自作スマートリモコンの室温測定機能を実現する予定です。