ESP-WROOM-02とMQTT + openHABでホームオートメーションに挑戦 (3)

前回までで門柱灯(gatepost lamp)のリモート操作ができるようになりましたので、いよいよ自動化の設定に入ります。

とりあえず次のような動作を考えました。

  1. 昼行灯(ひるあんどん)は良くないので明るいときは無条件にOFF
  2. 日没後、暗くなったらON
  3. PM9:00にOFF

EEスイッチを置き換えたので他の項目は実現できなくても1の項目だけは何としてもクリアしたい所です。

前回までに光センサーの値はESP-WROOM-02側からMQTT Brokerの /home/gatepost/sensor/stateへパブリッシュされるようになっていますので、今回はOpenHAB側のconfigを一部追加して表示させるようにします。本来光センサーの値をリアルタイムに表示してもあまり意味をなさないのですが、今回は自動化の設定値を求めるために一時的に行います。(シリアルモニタで見るより簡単・手間いらずです)

■itemsの編集

$ sudo vi /etc/openhab/configurations/items/my_home.items
Group All

Switch lamp1 "gatepost Lamp" (all){mqtt=">[broker:/home/gatepost/lamp/com:command:on:ON],>[broker:/home/gatepost/lamp/com:command:off:OFF],<[broker:/home/gatepost/lamp/state:state:default]"}

/* 以下を追記します */
Number light_sensor "光センサー (Light Sensor) [%d ]"  {mqtt="<[broker:/home/gatepost/sensor/state:state:default]"}

■sitemapの編集

$ sudo vi /etc/openhab/configurations/sitemaps/my_home.sitemap
sitemap my_home label="Main Menu"
{
   Frame label="MQTT" {
   Switch item=lamp1 label="Gatepost Lamp"
   /* 以下を追記します */
   Text item=light_sensor
  }  
}

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

openhab03-1

光センサーの値がスイッチの下の行に表示されるようになりました。さて、さらにこれを進めて一日の推移をグラフにして表示してみます。

 

 

 

 

先日紹介した https://openhardwarecoza.wordpress.com/2015/03/30/openhab-mqtt-arduino-and-esp8266-part-5-1-graphing-sensor-data/ こちらのサイトを参考に設定を行います。

■rrd4j add-onをインストール

$ sudo apt-get install openhab-addon-persistence-rrd4j

上記はdemoサイトが設定されていれば追加されるみたいです。そのときは不要です。

■persistenceファイルの編集

$ sudo vi /etc/openhab/configurations/persistence/rrd4j.persist
Strategies {
        // for rrd charts, we need a cron strategy
        everyMinute : "0 * * * * ?"
}

Items {

light_sensor : strategy = everyMinute

}

■sitemapの編集

$ sudo vi /etc/openhab/configurations/sitemaps/my_home.sitemap
sitemap my_home label="Main Menu"
{
   Frame label="MQTT" {
   Switch item=lamp1 label="Gatepost Lamp"
   Text item=light_sensor
   
   /* 以下を追記します */
   Frame label="フォトトランジスタの出力をAD変換した値" {
        Chart label="light_sensor" item=light_sensor period=d refresh=30000
  }
 }
}

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

openhab03-2

再起動後しばらくするとグラフが描かれ始めます。period=d としたので24時間のグラフになりました。このようにOpenHABではとても簡単にグラフを描くことができます。

こちら に書かれていますがデータをグループで指定して複数のグラフを書くこともできるようです。ただ残念ながら現在のところ複数のY軸の設定は出来ないようなので異なるカテゴリーの値を一つのグラフに表示させることは難しいようです。また、設定が悪いのかバグなのかは分かりませんが、私のiOSアプリではなぜかグラフが表示されません。(サファリなどのブラウザからは表示されます)

観察の結果、だいたいセンサーの値が25を下回ったら電灯をONにすれば良さそうです。電灯をOFFにする条件は、センサーの値の変動を考慮して50を超えた値で無条件にOFFとしましょう。先ほども申しましたが光センサーの値のグラフはあまり意味をなさないので、将来的には気温のグラフを表示させようと思っています。

余談ですが、当初は光センサーのフォトトランジスタの向きが良くなかったようで、暗いときの感度が低くNGでした。センサーを外付けにして調整したころ改善しました。

(失敗) 間接的に外の光を計測していたので感度が不足してしまった

(失敗) 間接的に外の光を計測していたので感度が不足してしまった

さて、ようやく準備ができました。いよいよルールを設定する番です。OpenHABではJAVA言語を発展させたXtendという言語でルールを記述するようです。JAVAすら初めてな私にとっては、ハードルが極めて高そうですね。でもそこは、いつもどおり「見よう見まね」で切り抜けていきましょう。

https://github.com/openhab/openhab/wiki/Rules やhttps://github.com/openhab/openhab/wiki/Samples-Rules を参考にして、だいたい次の事がわかりました。

● rules ファイルはテキストで記述され、

[インポート:Imports]   [変数宣言:Variable Declarations]   [ルール:Rules]

3セクション構成となっている

rules file 例 (https://github.com/openhab/openhab/wiki/Samples-Rules#how-to-turn-on-light-when-motion-detected-and-is-dark 記載)

    var Number counter = 0
    var Number lastCheck = 0

    rule "corLightOn"
    when   
            Item corMotion changed from OFF to ON or
            Item corFrontDoor changed from CLOSED to OPEN
    then   
            counter = counter + 1
            if(corBright.state < 40) {
                    sendCommand(corLightCeil, ON)
            }
    end

    rule "corLightOff"
    when   
            Time cron "0 * * * * ?"
    then   
            if(lastCheck == counter) {
                    counter = 0
                    lastCheck = -1;
                    sendCommand(corLightCeil, OFF)
                    sendCommand(corMotion, OFF)
            } else {
                    lastCheck = counter
            }
    end  

上記の通りルールは一つのファイルに複数記入しても良いみたいですね。正直言って [インポート:Imports] は全く理解不能ですが、この例のように簡単なルールでは必要ないみたいです。恐らくArduino IDEで言えばライブラリみたいなものでしょう。まぁ、動作しなかった時に考えましょう。何か読み込ませばきっと動くでしょう。たぶん。

● ルールが起動するトリガは次の3つ

  1. Itemベーストリガ —– コマンドが発行されたりステータスが変更になったときに起動
  2. Timeベーストリガ —– 時間指定による起動
  3. Systemベーストリガ—- システムの開始時などに起動

Itemベーストリガ 
    Item  received command []
    Item  received update []
    Item  changed [from ] [to ]

Timeベーストリガ
    Time is midnight
    Time is noon
    Time cron ""

TimeベーストリガではLinuxのcronライクな表現で指定できるのですね。ただ こちら を見るとlinux の cronにくらべ機能が拡張されているようです。左から順に

秒 分 時 日 月 曜日 年(オプション)

秒や年が指定できるようです。persistenceファイルの編集の時にも出てきました。ワイルドカードの*はともかくとして、?マークは見慣れないですね。どうやら日と曜日はどちらかを指定して指定しない方に?をつけるようです(たぶん)。という訳で16時台から20時台の15分毎に起動させたければ “0 0/15 16-20 * * ?” と指定すれば良いですね。

以上をふまえて、当初計画した自動化ルールを作成します。

  1. 光センサー(Item:light_sensor)の値を調べ、50より大きいときは門柱灯(Item:lamp1)へOFFのコマンドを送る。[1分おきに実行]
  2. 光センサーの値が25より小さい時はONのコマンドを送る。[16時から20時45分まで15分おきに実行]
  3. 門柱灯へOFFのコマンドを送る。[21時0分0秒に実行]

サンプルを見ると日没時間等を計算式で求める事も可能なようですね。まったくもってアメージングです。でも、あまり欲をかいてしまうと失敗してしまいますので、身の丈にあった設定ファイルにしておきます。

■gatepost.rulesの作成

$ sudo vi /etc/openhab/configurations/rules/gatepost.rules
rule "LightOFF_in_daylight"
    when
                Time cron "0 * * * * ?"
    then
                if (light_sensor.state > 50){
                   sendCommand(lamp1, OFF)
                }

rule "LightON_after_sunset"
    when
                Time cron "0 0/15 16-20 * * ?"
    then
                if (lamp1.state == OFF && light_sensor.state < 25){
                   sendCommand(lamp1, ON)
                }
                

rule "LightOff_at_21"
    when
                Time cron "0 0 21 * * ?"
    then
                    sendCommand(lamp1, OFF)
    end

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

11月22日 東京での日の入り時間は16時31分でした。 16時45分に門柱灯が自動的に点灯しましたので、どうやらうまく行ったようです。ログを見てみます。

$ sudo cat /var/log/openhab/events.log

....
2015-11-22 16:28:55 - light_sensor state updated to 54
2015-11-22 16:29:00 - lamp1 received command OFF
2015-11-22 16:29:00 - lamp1 state updated to OFF
2015-11-22 16:29:00 - light_sensor state updated to 53
....
2015-11-22 16:29:55 - light_sensor state updated to 50
2015-11-22 16:30:00 - light_sensor state updated to 51
2015-11-22 16:30:05 - light_sensor state updated to 46
....
2015-11-22 16:44:56 - light_sensor state updated to 24
2015-11-22 16:45:00 - lamp1 received command ON
2015-11-22 16:45:00 - lamp1 state updated to ON
2015-11-22 16:45:01 - light_sensor state updated to 24
2015-11-22 16:45:06 - light_sensor state updated to 24
2015-11-22 16:45:11 - light_sensor state updated to 25
....

光センサーの値が5秒間隔で記録されています。必要以上に間隔が短すぎるかもしれませんね、後で調整することにします。

光センサーを外付けにしました。

光センサーを外付けにしました

ESP-WROOM-02とMQTT + openHABでホームオートメーションに挑戦 (2)

とある日 工作をしている私に家人から質問がありました

家人:「一体その品物に、いくら掛かっているのですか?」私:「2,000~3,000円ぐらいかな」家人:「また、また、尾戯れを」私:「本当ですってば」

しかし嘘ではありません。一番高価な部品はesp-wroom-02モージュールで、これが約1000円。AC100VーDC5V電源とケースが各400円程。SSRキットが250円。+その他部品ですから大体3,000円以内で収まっていると思いますよ。

(しかしながら、原因不明で電源が壊れてしまい廃棄処分になった1号機の部品代や将来使うかもしれないと思い購入してしまったドップラーセンサーモジュール、スケッチ書き込みのための超小型USBシリアル変換モジュール、予備の部品、etcを合計すると、それなりの金額になっているのは間違いない事実ではあります。)

回路図

回路図

回路図は基本的に前回と同じですが次の変更を加えました。AC100V-DC5V電源にe-bayから仕入れた Ultra-compact power module HLK-PM01(出力5v 0.6A)を使い、電灯線制御にソリッドリレー。 さらに将来の自動化を目的にフォトトランジスタによる照度センサー。そしてローカルで電灯を制御するためにSW3を新設しました。

【デプロイした2号機】USBケーブルの先端にUSBシリアル変換モジュール。

【デプロイした2号機】USBケーブルの先端にUSBシリアル変換モジュール。スケッチを書き込むために仮にAC100Vへ配線して通電しています。 スケッチの書き込み時は100Vに通電せずUSB側から給電するようにします。(100vに通電して書き込みを行うと最悪の場合PCを破損する可能性があります。)

技術もないのに省スペースを追求しすぎて1号機は失敗してしまいました。教訓を活かして2号機は秋月電子の 【ユニバーサル基板 Bタイプ(95x72mm)】とケースにホームセンターで仕入れた【PVKボックス(プールボックス) 未来工業】に集積度を上げず余裕を持って作ることにしました。

既存の電灯配線に制御回路を繋げなくてはいけませんが、これが結構大変です。制御回路を配置する場所が問題となります。通常、電灯は図の様に配線されているので設置候補として図中A,B,Cの場所があります。Bは一番良い場所なのですが、大抵見えない屋根裏とかに隠蔽されているので設置や保守が大変です。

Cからは何とか回路を引き出すことが出来ますが2芯ケーブルの線間電圧が100VではないのでESP-WROOM-02のための電源が別途必要になります。

残るはAの電灯直下ですが、こちらは雨風にさられることになりますので、そちらの対策が必要になります。

配線01

たまたま私の家の門柱灯はEE-Switch【暗くなると自動的にONになる】が露出配線で繋がっていて、この場所にEE-Switchと入れ替える形で制御回路を設置することができました。

(ちなみにこの配線工事は自宅であっても法律上、電気工事士の有資格者が行う必要があるので注意が必要です。)

家庭などのAC配線では線間電圧(100V)という概念のほかに対地電圧という概念が重要になります。アースされているのが接地側で感電の危険性が高いのが非接地側となります。

電気器具もなるべく感電しないようになっていて、白熱電球の人が触れやすいネジの部分の電極は接地側になるよう配線されています。同じ理由で差し込むときガイドになるコンセントの左側が右側より大きく、接地側に接続されています。

また、配線の色は原則として接地側が白又は緑で、非接地側には他の色を使う決まりになっています。(場合により例外もあります)
EE_switch
テスターや未来工業の「検電くん」を使い調査した結果、自宅のEE-スイッチに来ている3芯ケーブルも白が接地側、黒が非接地側、赤が電灯に繋がるように配線されていました。

ESP-WROOM-02のArduinoスケッチは前回のものを少し手直しいたしました。変更点は以下の通りです。

●アナログインターフェースPIN 【TOUT】に繋がっている照度センサのフォトトランジスタの値を analogread(A0) 命令で0Vから1Vを0~1024の値に変換して読み込みます。

手持ちの部品、回路では全く光が入らない状態でも0にならず、大体20ぐらいの値が読み込まれました。この値はMQTT Broker /home/gatepost/sensor/stateへパブリッシュしてOpenhabに伝えます。

【失敗してしまった1号機】

失敗してしまった1号機

失敗した1号機はこの値が不安定だったのでいろいろ弄っている内に、なぜか電源が壊れてしまったのでした。

●DHCPからスタティックIPアドレスに変更しました。DHCPで受け取ったアドレスにはリース期間が設定されているので、その期間が過ぎると一旦接続が切れてしまいます。

●手元で電灯を操作するために 【IO4】にSWを新設しました。当初、割り込みを使ってスケッチを記述しようとしたのですが、読み込んでいるライブラリと相性が悪いのかうまく動作しませんでした。

今回はOPENHARDWARE.CO.ZAさんのコードを使わせていただきました。ただ、このスケッチではMQTTサーバに繋がっていないと動作しません。改善の余地があります。

●ESP-WROOM-02がバックグラウンドでTCP等の処理をする時間を確保するためにloop()の最後に delay(10) を挿入しました。

●SSRをスイッチするためにトランジスタを1段入れましたのでON/OFFの極性が逆になりました。またloop()中 mills()がオーバーフローしたとき(起動してから5ヶ月後)挙動がわからないのでabs()で絶対値の差で比較しました。

/*
 Basic ESP8266 MQTT example

 This sketch demonstrates the capabilities of the pubsub library in combination
 with the ESP8266 board/library.

  2015/10/12 改変
  - subscribes to the topic "/home/gatepost/lamp/com", printing out any messages
   - If the characters of the topic "/home/gatepost/lamp/com" are "ON", switch ON the ESP Led, "OFF" switch off
 
  2015/11/08 改変
  - phototransistor (照度センサー)  analogRead(A0) 
  - Static IP
  - a physical button on IO4 (turns the device on/off)
    --> https://openhardwarecoza.wordpress.com/2015/04/05/openhab-mqtt-arduino-and-esp8266-part-5-2-combined-switch-and-sensor-module-based-on-arduino/
*/ 


#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.

const char* ssid = "your_SSID";
const char* password = "your_Password";
const char* mqtt_server = "192.168.xx.xx";  // mqttサーバのアドレスを設定します。
IPAddress ip(192,168,xx,xx);
IPAddress gateway(192,168,xx,xx);
IPAddress subnet(255,255,255,0);
 
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
int buttonPin = 4;
boolean buttonState = LOW; //stroage for current button state
boolean lastState = LOW;//storage for last button state
boolean OutputPinState = LOW;
int analog_val;
char illumination[5];

void setup() {
  pinMode(5, OUTPUT);     // Initialize the 5 pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  //set static ip part
  WiFi.config(ip,gateway,subnet);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

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

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


  // check whether ON or OFF
  if( payload[0] == 'O' && payload[1] == 'N' ) {
    digitalWrite( 5, HIGH);
    OutputPinState = HIGH;
    client.publish("/home/gatepost/lamp/state", "ON"); 
  }
  else if( payload[0] == 'O' && payload[1] == 'F' && payload[2] == 'F' ) {
    digitalWrite( 5, LOW );
    OutputPinState = LOW;
    client.publish("/home/gatepost/lamp/state", "OFF"); 
  }
  else {
    Serial.print( "Unknown command : " );
    // Serial.println( (char*)payload );
  }
  Serial.println();
}

void reconnect() {
  // Loop until we're reconnected 
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    
    
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("/home/gatepost/lamp/com");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

 unsigned long now = millis();
  if (abs(now - lastMsg) > 5000) {
    lastMsg = now;
    analog_val = analogRead(A0);                 // read photoTr
    itoa(analog_val,illumination,10);
    client.publish("/home/gatepost/sensor/state",illumination);    
    ++value;
    snprintf (msg, sizeof(msg), "hello world from Gatepost. ILLUMINATION=%u #%ld  ",analog_val, value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);

  }
  buttonState = digitalRead(buttonPin);
  if (buttonState == LOW && lastState == HIGH){   //if button has just been pressed
    Serial.println("pressed");
    delay(300);                                   //prevent from chattering
    
    //toggle the state of the LED
    if (OutputPinState == HIGH){
      digitalWrite(5, LOW);
      OutputPinState = LOW;
      client.publish("/home/gatepost/lamp/state", "OFF");

    } else {
      digitalWrite(5, HIGH);
      OutputPinState = HIGH;
       client.publish("/home/gatepost/lamp/state", "ON");
    }
  }
  lastState = buttonState;
  delay(10);
}

デプロイ後、スマートフォンから電灯のON/OFFが出来るようになりました。めでたしめでたしです。でもこれで終了ではありません。まだまだ続きます。

リモート操作する門柱灯です

リモート操作する門柱灯です