techrmc’s blog

ICT大好きな中小企業診断士のブログです

ESP-WROOM-02を用いた自作のWiFiドアセンサ

自作のWiFiドアセンサを紹介します。ドアセンサと名付けていますが、郵便箱に取り付けて、郵便箱の扉の開閉検知に利用しています。元々は、新聞や郵便物が投函されたか、頻繁に見に行くのが面倒だと感じて作りました。郵便箱の開閉を外出中でも把握できるので、結構、便利です。郵便箱の扉の開閉以外に、ドアの開閉を検知したり、工具が定位置から持ち出されたことを検知したり、様々な用途に使えます。

郵便箱

全体概要

扉の開閉はリードスイッチと磁石で検知しています。リードスイッチを入れるケースは、100円ショップで購入した防犯センサのケースを流用しました。磁石も防犯センサの部品の流用です。リードスイッチは、扉が閉まっているときは磁石によって接点が閉じてクローズド状態となり、一方、扉が開くと接点が離れてオープン状態となります。

検知回路の心臓部はESP-WROOM-02というマイコンを利用しました。このマイコンは1個当たり数百円程度と安価で、WiFiを内蔵しているので、IoT用途にはもってこいです。もちろん技適マークも付いているので安心です。

リードスイッチがクローズド状態の間は、ESP-WROOM-02を低消費電力のディープスリープモードにしておきます。そして、リードスイッチがオープン状態になると、ESP-WROOM-02が眠りから覚めて起動し、建物内のWiFiアクセスポイント経由でインターネット上のIFTTTへメッセージを送り、IFTTTからメールが送られます。

郵便箱は屋外に設置してありそのままでは検知回路が雨ざらしとなってしまうため、検知回路をウォールボックスに格納することで防水性を高めています。また、検知回路からリードスィッチまでの配線も、金魚店で購入したチューブに通すことで、雨水や虫から保護しています。

ウォールボックスに格納した検知回路

検知回路

次に、検知回路について説明します。KiCADで描いた回路図を下記に示します。電源3.3Vは、単三乾電池3本(すなわち4.5V)から3端子レギュレータで降圧・安定化していますが、回路図からは省略しています。

回路図

リードスイッチSW1は、扉が閉まっている間は磁石によって通電状態となっています。それによって、トランジスタQ1のベース電位がゼロに近づくため、コレクタからエミッタへの電流は流れず、ENとIO5はそれぞれR1とR2でプルアップされたHigh状態となっています。

ここで扉が開くとSW1がオープン状態となってQ1のベースがHighになり、コレクタからエミッタへ電流が流れ始めます。すると、IO5がLowになるとともに、同電位になっているコンデンサC1の両足がLowに落ち、それに伴い、R1でプルアップされていたENがHighからLowに落ちます。そして、C1がR1経由で蓄電されるにつれ、ENの電位はLowから徐々にHighに戻っていきます。ESP-WROOM-02は、ENがHighの時にアクティブとなるため、ENがHighとなるタイミングでディープスリープから復帰します。ディープスリープから復帰した後のプログラムの動作は、後ほどプログラムのパートで説明します。

再び扉が閉まると、SW1が閉じてコレクタからエミッタへの電流が停止し、R2でプルアップされたコレクタの電位がHighになります。この時C1は蓄電状態になっているためC1の両足には電位差があり、このままではEN側の電位が3.3V以上となってESP-WROOM-02の耐電圧を超えてしまいます。そこでダイオードD1によってC1に溜まった電荷を逃し、ENに3.3V以上の電圧がかかることを防ぎます。

ディープスリープからの復帰の肝となるのは、C1です。C1の容量が小さすぎると、ENが十分にLowに下がり切らないために復帰しません。実験したところ、0.1μFでは容量不足で、1μFで動作しました。

また、SW1が閉じている間、R3には常に電流が流れ続けるので、乾電池を長持ちさせるためにはできるだけ抵抗値を大きくして電流を絞りたいところです。R3の抵抗値を変えながら実験した結果、1MΩ以上では動作しなかったため、R3を470kΩとしました。計算上、470kΩとした場合、R3での消費電流は7μAとなります。ディープスリープ中のESP-WROOM-02の消費電流は20μAであるため、合計27μAが常に消費される計算です。実機で長時間運転していますが、単三アルカリ乾電池3本でざっくり1年程度は連続運用できています。

回路図のその他の部品についても説明します。

SW2は回路動作をプログラムで切り替えるためのスイッチです。プログラムのモード切り替えに使っています。

SW3はESP-WROOM-02をリセットするスイッチで、スイッチを入れてRESETをLOWに落とすと、ESP-WROOM-02がリセットされます。

D2は動作状態を可視化するためのLEDで、プログラムで点灯制御します。

回路図には描いていませんが、電源3.3Vは、3端子レギュレータによって単三乾電池3本から作り出しています。ESP-WROOM-02は、WiFi接続の際に200mA近くの電流を消費するため、3端子レギュレータの選定も重要です。ここでは、出力電流150mAのXC6202P332THを使いましたが、現在はディスコンとなっているので、今後は他の3端子レギュレータを探す必要があります。

ユニバーサル基板への実装結果は下記の写真の通りです。

SW2は手持ちの4連DIP-SWを使いましたが、4つのSWのうち1つのみ結線しています。

また、右側中ほどの2連ターミナルブロックにはリードスイッチまでのシールド線を接続します。

ユニバーサル基板に実装した検知回路

プログラム

ESP-WROOM-02に書き込むプログラムを説明します。ソフトウェア開発環境は、使い慣れているArduinoを用いました。

#include <ESP8266WiFi.h>                    // ESP8266用ライブラリ
extern "C" {
#include "user_interface.h"                 // ESP8266用の拡張IFライブラリ
}
#define PIN_SW 5                            // connect a reed SW to IO5
#define PIN_LED 4                           // connect an LED to IO4
#define PIN_TEST 12                         // connect a test SW to IO12, LOW at test-mode
#define SLEEP_P 0                           // sleep forever 
#define REED_ON 1                           // door closed
#define REED_OFF 0                          // door opened

// WiFi config
const char ssid[] = "xxxxxxxx";             // xxxxxxxxはWiFiアクセスポイントのSSIDに差し替え
const char pass[] = "xxxxxxxx";             // xxxxxxxxはWiFiアクセスポイントのパスワードに差し替え

// IFTTT
const char* host = "maker.ifttt.com";
const int httpPort = 80;
String url_message = "/trigger/xxxxxxxx/with/key/xxxxxxxx"; // xxxxxxxxは各自のイベント名及びアクセスキーに差し替え

// Use WiFiClient class to create TCP connections
WiFiClient client;

void setup(){
    int reed;                                       // status of the reed switch. REED_ON or REED_OFF
    int test;                                       // status of the test switch. LOW at test-mode
    int waiting=0;                                  // アクセスポイント接続待ち用カウンタ
    int i;
    pinMode(PIN_SW,INPUT);                          // INPUT_PULLUP did not work. Should be INPUT. 
    pinMode(PIN_TEST,INPUT_PULLUP);
    pinMode(PIN_LED,OUTPUT);
    delay(250);
    reed=digitalRead(PIN_SW);
    test=digitalRead(PIN_TEST);
    delay(250);

    if (test==LOW) {                                // test mode
        if (reed == REED_OFF) {                     // REED_OFF
            WiFi.mode(WIFI_STA);                    // STA mode
            WiFi.begin(ssid, pass);                 
            while(WiFi.status() != WL_CONNECTED){   // wait until WiFi connected
                delay(100);                         // wait 100msec
                waiting++;                          // counter increment
                if(waiting > 300) {                 // max waiting counter. approx.300*100 msec= 30sec
                    digitalWrite(PIN_LED,LOW);
                    for(i=0;i<5;i++) longLight();   // long alarm in case of WiFi connection error
                    sleep();
                }
                digitalWrite(PIN_LED,waiting%2);    // short interval lightning during connection
            }
            digitalWrite(PIN_LED,LOW);
            send2IFTTT();
        } else {                                    // REED_ON
            longLight();
        } 
        sleep();
    } else {                                        // not test mode
        if (reed == REED_OFF) {                     // REED_OFF
            WiFi.mode(WIFI_STA);                    // STA mode
            WiFi.begin(ssid, pass);                 
            while(WiFi.status() != WL_CONNECTED){   // wait until WiFi connected
                delay(100);                         // wait 100msec
                waiting++;                          // counter increment
                if(waiting > 300) {                 // max waiting counter. approx.300*100 msec= 30sec
                    sleep();
                }
            }
            send2IFTTT();
        }
        sleep();
    }  
}

void loop(){  // dummy
    sleep();
}

void longLight(){
    digitalWrite(PIN_LED,HIGH);
    delay(1000);  // light on for 1 sec
    digitalWrite(PIN_LED,LOW);
    delay(100);  // light off for 100 msec
}

void send2IFTTT(){  
    if (!client.connect(host, httpPort)) {
        return;
    }
    
    // This will send the request to the server
    client.print(String("GET ") + url_message 
                   + "?value1=postbox&value2=sense&value3=opened"
                   + " HTTP/1.1\r\n" 
                   + "Host: " + host + "\r\n" 
                   + "Connection: close\r\n\r\n");    
    unsigned long timeout = millis();
    while (client.available() == 0) {
        if (millis() - timeout > 5000) {
            client.stop();
            return;
        }
    }
  
    // Read all the lines of the reply from server
    while(client.available()){
        String line = client.readStringUntil('\r');
    }
}

void sleep(){
    ESP.deepSleep(SLEEP_P,WAKE_RF_DEFAULT);  // スリープモードへ移行する
    delay(1000);                             // スリープモードに入るのに時間を要すためダミーのdelayを入れる
}

動作モードはSW2で切り替えます。SW2を閉じてIO12をLowにするとテストモードになり、SW2を解放すると低消費電力モードになります。テストモードではLEDによって動作状況をモニタできます。一方、低消費電力モードではLEDを点灯させずに消費電力を抑えます。

扉が開いてリードスイッチSW1がオープン状態になると、検知回路で説明したように、ENが一旦HighからLowに落ち、C1の働きでENがLowからHighに戻る際にESP-WROOM-02がアクティブ状態になります。アクティブ状態になると、まずsetup()が走ります。setup()の冒頭はESP-WROOM-02の初期設定です。その後は、テストモードと低消費電力モードに分岐しますが、LED点灯制御が異なる以外は基本的に次の流れで処理が進みます。

  1. reedに読み取ったIO5(PIN_SW)をチェック。reed=1(すなわちIO5=High)の場合は、扉が開いていないのにノイズ等の影響でディープスリープから復帰した誤作動、あるいは扉が閉まった状態でリセットボタンSW3が押されたと判定して再度ディープスリープ。reed=0(すなわちIO5=Low)の場合は、扉が開いていることを示すので、2へ進む。
  2. WiFiアクセスポイントへ接続確立。
  3. 接続に成功したらsend2IFTTT()を呼ぶ。接続に失敗した場合はsleep()を呼んでディープスリープ。
  4. send2IFTTT()がIFTTTのサーバへHTTPのGETでメッセージを送信。
  5. sleep()を呼んでディープスリープ。

sleep()の中でESP.deepSleep()を呼んでいますが、この第一引数をゼロとすることで、永続的にディープスリープ状態に入ります。なお、通常のArduinoのプログラムでは、初期化時にsetup()が呼ばれた後、loop()が無限に呼ばれますが、上記のプログラムでは、setup()の中でディープスリープに入るため、loop()が呼ばれることはありません。
次に、検知回路からHTTPのGETメソッドで送られてくるメッセージを処理するIFTTTアプレットを説明します。

IFTTTアプレット

IFTTTのアプレットは、WebHookとe-mailで構成されます。検知回路がHTTP GETメソッドでWebHookをキックすると、e-mailが発火して自分のメールアドレスにメールが送られてくるという流れです。IFTTTの設定は、IFTTTにログインした状態で、IFTTTのサイト上で作業します。

IFTTTアプレットの構成

WebHookのイベント名(Event Name)には、プログラムの文字列定数url_messageに、アクセスキーとともに埋め込んだ文字列を設定します。(下記の設定画面ではイベント名は伏せています。)

WebHookの設定画面

e-mailでは、発火した時に自分宛に送付するメールのsubjectとメール本文を設定します。下記の設定画面では、メールのsubjectは「”イベント名”通知」と設定しています。また、メール本文は、イベント名、発火時刻、3つの文字列パラメータ(Value1、Value2、Value3、いずれもプログラムのsend2IFTTT()で指定しています)を埋める設定としています。

e-mailの設定画面

下記は、扉が開いた時に、自分のメールアドレスへ送られてきた実際のメール画面です。イベント名や、Vaue1, Value2, Value3には、プログラムの中で設定した文字列が埋め込まれています。

実際のメール画面