TL;DR
家の鍵の開け閉めををIFTTTに通知するデバイスをESP32で作ってみた。
ESP32は低電圧保護機能(Brownout reset)を無効にすれば、(非奨励だけど)2V付近でも動くので,単3電池2本とスイッチだけの単純な構成でIoT鍵監視装置が作れる。
はじめに
スマートロック製品いろんなところから出てますよね.
個人的には家の鍵をリモートで開け閉めしたり出来なくて良くて,「家の鍵を締めたか」さえわかれば良いなって思ってたのでESP32でシンプルな鍵モニタリングシステムを作ってみました.
イメージしたシステム構成は以下の通り.
ESP32が鍵の状態をIFTTTに通知,IFTTTがESP32からの通知をスマートフォンに仲介する流れです.
(ほんとは通知用のアプリとか作ってみたいんですが,IFTTTの手軽さが勝ってしまう・・・)
今回は,まずIFTTTでESP32からの通知をスマートフォンに中継する方法を説明し,その後ESP32でドアの鍵状態をIFTTTに通知する方法を説明します.
IFTTTによる通知中継
基本的には,IFTTTのWebhooksでESP32からのメッセージを受けて,スマートフォンのIFTTTアプリに転送する流れ.
Webhooksについての設定は,殆ど先人の知恵を丸パクリです.(先人の知恵:IFTTT(イフト)でWebhooksの利用)
URLを叩くことでトリガーが発火,JSONで引数を与えることも出来ます.
今回は,ESP32でURLを叩くときに,スマートフォンへ通知する文章を格納したJSONを付けておくことで,メッセージの内容等の設定をESP32のソースに集約して,IFTTTの機能を中継に限定しました.
また,叩くべきURLが何処にあるかが微妙にわかりづらいですが,Webhooksのページの右上"Documents"を押したページに表示されます.URLの確認やWebhooksの動作テストが出来るのでなかなか便利です.
出力には,スマートフォンに入れたIFTTTアプリに通知を飛ばせるので,これを使います.
特に設定は必要ないはず?
IFTTT内の設定はこんな感じになるはずです.(縦に長いのなんとかならないのか)
IFTTT内で設定を済ませたら,上手く通知が飛ぶかを先ほどのWebhooksテストページで試しておくと,
何かトラブルがあったときの問題の切り分けがラクです.
ESP32での鍵モニタリング
最近は素のM5St.+の方が人気がある気もしないでもないですが,シンプルさ優先でESP32-WROOM-32を使います.
WiFi使えるチップあるあるで電源回路泣かせなESP32ですが,どうやらNiMH電池2本(3V~2V)で動くようです(『ESP32 電池(NiMH×2)で動かすときは 40MHz でコンパイルせよ 』).
と,言うことでNiMH電池2本を直結すれば動くと期待して電池駆動を目指します.
当然消費電力を絞っていきたいので,通常時はSleep状態にして,鍵の状態が変化したときだけ起動してWiFi経由で通知を飛ばすことにします.
単純すぎて悲しくなってきますが,回路構成は以下の通り.電池と鍵のノブで押されるスイッチ,ESP32の3つだけです.
I/Oは何処のピンでも良いんですが,Sleepからの復帰のトリガーになってもらうので,RTC_GPIOピン(GPIO0, 2, 4, 12~15, 25~27, 32~35)&&特に他に役割がないピンである必要があります.今回はGPIO2を使ってます.
ちなみに,プログラムの開発自体はESP32-Devkit-Cでやりましたが,最終的には余計なチップの載っていないボードで運用しています.
書き込んだスケッチは長いので記事の最後に貼ってあります.
起きる度にスイッチの状態が変わったかを確認して,変わっていたら変更内容に応じたメッセージをJSONに格納してIFTTTに向かって投げます.
また,IFTTTに投げ終わったら,次の復帰条件をセットしてsleepモードに入ります.
IFTTTにJSONを投げる件はこちら(『M5Stick-CでJsonをPOSTする』)の記事を参考にしました.
また,ESP32のArduino開発環境だと,電源電圧が2.4V以下になるとリセットがかかるような初期設定になっています.
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detector
を実行してBrownout detecterによる低電圧保護を無効にしないと,電池容量の半分も使えなくなってしまいます.
あと前述のブログに書いてあるように,SPI Flashの動作周波数を40 MHzに切り替えるのを忘れずに.
スイッチを押したり離したりして,通知が飛ぶことを確認したらいよいよ設置です.
設置&まとめ

おまけ: ESP32の動作電圧
今回はNiMH2本で動かしていますが,実は一般にESP32と呼ばれてるESP32-WROOM-32はデータシートを見ると,
Operating voltage/Power supply: 3.0V ~ 3.6V
との記載があります.
NiMHが1本あたり満充電1.5Vで終端1Vぐらいなので,2本で3.0V~2.0V全然足りてないですね
ESP32-WROOM-32はESP32とSPI Flash,アンテナ等々を纏めたチップですが,ESP32単体のデータシートを見てみると以下のように書いてあります.
Recommended Operating Conditions: 2.3V ~ 3.6V
と,言うことでESP32自体は2.3Vでもイケるご様子.まぁ,趣味に使う分には多少無理させても良いでしょう・・・DC/DC高いもんね.
ちなみに,ESP32-WROOM-32のデータシートのRevision historyを見てみると元々もっと低い電圧で動く表記だったのが,だんだん引き上げられて現在の値になっているみたいです.
ESP32のソースコード
# include <ArduinoJson.h>
# include <HTTPClient.h>
# include <WiFiClientSecure.h>
# include "MyAPSettings.h" // Contain following 3 constants.
// const char *SSID_AP = "hogehoge";
// const char *PASSWORD_AP = "hogehoge";
// const char *IFTTT_URL = "http://maker.ifttt.com/trigger/...";
const gpio_num_t DOOR_LOCK_SW_PIN = GPIO_NUM_2;
enum DoorLockStatus { LOCKED, UNLOCKED, INVALID };
RTC_DATA_ATTR DoorLockStatus last_status =
INVALID; // Keep last status in RTC SLOW memory.
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detector
Serial.begin(115200);
pinMode(DOOR_LOCK_SW_PIN, INPUT_PULLUP);
}
void loop() {
Serial.printf("Last status: %s.\n",
DoorLockStatusToString(last_status).c_str());
DoorLockStatus current_status = GetDoorLockStatus();
Serial.printf("Current status: %s.\n",
DoorLockStatusToString(current_status).c_str());
if (last_status == current_status) {
Serial.println("Enter deep sleep mode.");
esp_sleep_enable_ext0_wakeup(
DOOR_LOCK_SW_PIN,
digitalRead(DOOR_LOCK_SW_PIN) == HIGH
? LOW
: HIGH); // Wake up by any change on DOOR_LOCK_SW_PIN;
esp_deep_sleep_start();
}
PostIFTTT(String("Door ") + DoorLockStatusToString(current_status));
last_status = current_status;
delay(1000);
}
String DoorLockStatusToString(DoorLockStatus status) {
switch (status) {
case LOCKED:
return "Locked";
case UNLOCKED:
return "Unlocked";
default:
return "Invalid";
}
}
DoorLockStatus GetDoorLockStatus() {
if (digitalRead(DOOR_LOCK_SW_PIN) == HIGH) {
return LOCKED;
}
return UNLOCKED;
}
void ConnectWiFi() {
if (WiFi.status() == WL_CONNECTED) {
return;
}
WiFi.mode(WIFI_STA);
WiFi.begin(SSID_AP, PASSWORD_AP);
for (int retry_count = 0; retry_count < 10; retry_count++) {
if (WiFi.status() == WL_CONNECTED) {
break;
}
delay(500);
Serial.print(".");
}
Serial.println("");
}
void PostIFTTT(String str) {
ConnectWiFi();
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
StaticJsonDocument<JSON_OBJECT_SIZE(1)> json_request;
json_request["value1"] = str.c_str();
char buffer[255];
serializeJson(json_request, buffer, sizeof(buffer));
Serial.println(buffer);
HTTPClient http;
http.begin(IFTTT_URL);
http.addHeader("Content-Type", "application/json");
http.POST((uint8_t *)buffer, strlen(buffer));
http.end();
}