🏠 ESP32で実現するスマートドアベル:ESP-NOWで実現する超高速ワイヤレス通知システム

【DIY】ホームセーフティ実践

「ドアが開いた瞬間に、離れた部屋のブザーが鳴る」—そんなシンプルなIoTシステムを、 ESP32とESP-NOW(エスピー・ナウ) という強力なコンビで実現する方法を紹介します。

このシステムは、Wi-Fiルーターを経由せず、デバイス間で直接通信を行うため、超低遅延省電力、しかも初期設定が非常に簡単なのが最大の魅力です。

この記事では、具体的な配線と、子機(ドアセンサー)と親機(ブザー)それぞれの完全なプログラムコードを提供します。


💡 プロジェクトの概要とESP-NOWのメリット

プロジェクトの目的:シンプルなワイヤレスドアベル

本プロジェクトは、以下の2つのESP32モジュールで構成されます。

  1. 子機(送信側):ドアに設置し、開閉状態を検知して親機へデータを送信します。
  2. 親機(受信側):ブザーを接続し、子機からのドア開信号を受け取ったらブザーを鳴らします。

ESP-NOWを選ぶ理由

なぜ通常のWi-Fi通信(MQTTやHTTP)ではなくESP-NOWを選ぶのでしょうか?

特徴ESP-NOW一般的なWi-Fi通信 (MQTT/HTTP)
遅延数ミリ秒(超高速)数百ミリ秒~数秒
ルーター不要(デバイス間直接通信)必要
消費電力
設定相手のMACアドレスを登録するだけWi-Fi SSID、パスワード、サーバーアドレスなどが必要

ESP-NOWは、非常に高速な応答が必要な「ドア開閉通知」や「ボタン入力」といった用途に最適なのです。


🛠 準備するものリストと配線図

必須コンポーネント

部品名数量備考
ESP32 開発ボード2個ESP-WROOM-32などが一般的です。
ブザーモジュール1個アクティブブザー(電源を入れると鳴るタイプ)が簡単です。
リードスイッチ/磁気センサー1個ドアの開閉検知に使用します。
ジャンパー線数本配線用。
ブレッドボード2枚部品の仮固定と配線に使います。
USB ケーブル2本ESP32への書き込みと電源供給用。

🔌 配線ガイド

1. 親機(受信側/ブザー)の配線

ESP32ピン接続先役割
GPIO 27ブザーの信号ピンブザーを鳴らす制御信号。
GNDブザーの$\text{GND}$
3.3V または 5Vブザーの$\text{VCC}$(アクティブブザーの場合)

2. 子機(送信側/ドアセンサー)の配線

今回は、シンプルなデジタル入力として INPUT_PULLUP を使用します。これにより、外付けの抵抗が不要になります。

ESP32ピン接続先役割
GPIO 4リードスイッチの片側信号入力ピン。
GNDリードスイッチのもう片側

🔑 センサーの動作原理 $\text{INPUT\_PULLUP}$を設定した$\text{GPIO}\ 4$は、センサーが開いている(磁石が離れている)ときは内部抵抗により$\text{HIGH}$(ドア閉)を示します。センサーが閉じている(磁石が近づいている)と、$\text{GND}$に接続され$\text{LOW}$(ドア開)を示します。この「$\text{LOW}$になったら開いた」という信号をプログラムで処理します。


💻 プログラム実装:親機の MAC アドレス取得から始める

ESP-NOW通信を行うには、子機が親機の MAC アドレスを知っている必要があります。

Step 1: 親機の MAC アドレスを確認

まずは親機側のESP32に以下のコードを書き込み、シリアルモニタで$\text{MAC}$アドレスを取得します。

C++

#include <WiFi.h>

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA); // ステーションモードに設定
    
    Serial.print("==============================\n");
    Serial.print("Receiver MAC Address: ");
    Serial.println(WiFi.macAddress()); // MACアドレスを表示
    Serial.print("==============================\n");
}

void loop() {
    // 一度表示したらOK
}

表示された MAC アドレス(例:AA:BB:CC:DD:EE:FF)をメモしておいてください 。これは後の子機プログラムで必要になります。


3. 親機(受信側)の完全プログラム

親機は、子機から送信されるデータ構造体を共有し、データを受信するためのコールバック関数を定義します。

C++

#include <esp_now.h>
#include <WiFi.h>

// ブザーを接続するGPIOピン(例: GPIO 27)
const int BUZZER_PIN = 27; 

// ★★★ 共有するデータ構造体 ★★★
// 送信側と受信側で完全に一致させる必要があります。
typedef struct struct_message {
    bool door_open; // ドアが開いているかどうかのフラグ (true=開 / false=閉)
} struct_message;

struct_message myData; // 受信データ格納用の変数

// データを受信したときに呼ばれるコールバック関数
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
    // 受信したデータを myData 構造体にコピー
    memcpy(&myData, incomingData, sizeof(myData));

    // 受信データの表示(デバッグ用)
    Serial.print("Data received from MAC: ");
    for (int i = 0; i < 6; i++) {
        Serial.printf("%02X", mac_addr[i]);
        if (i < 5) Serial.print(":");
    }
    Serial.println();
    Serial.print("Door Open Status: ");
    Serial.println(myData.door_open ? "OPEN (Signal ON)" : "CLOSED (Signal OFF)");

    // ドアが開いた信号を受信したらブザーを鳴らす
    if (myData.door_open) {
        Serial.println(">>> ALERT: Door Open Signal Received! Beeping...");
        // tone(ピン番号, 周波数, 鳴らす時間[ms])
        tone(BUZZER_PIN, 1500, 500); // 1.5kHzの音を0.5秒間鳴らす
    } else {
        // ドアが閉じた信号
        Serial.println("Door Closed/State Reset.");
    }
}

void setup() {
    Serial.begin(115200);

    // Wi-Fiをステーションモードに設定(ESP-NOWの前提条件)
    WiFi.mode(WIFI_STA); 

    // ブザーピンを出力に設定
    pinMode(BUZZER_PIN, OUTPUT);
    digitalWrite(BUZZER_PIN, LOW); // 初期状態ではブザーをオフ

    // ESP-NOWの初期化
    if (esp_now_init() != ESP_OK) {
        Serial.println("FATAL ERROR: Failed to initialize ESP-NOW");
        return;
    }

    // データ受信時のコールバック関数を登録
    esp_now_register_recv_cb(OnDataRecv);

    Serial.println("ESP-NOW Receiver (Parent) Initialized.");
}

void loop() {
    // ブザーが鳴っている間も他の処理をブロックしないため、
    // loop()では特別な処理は不要です。
    delay(10);
}

4. 子機(送信側)の完全プログラム

子機は、センサーの状態を監視し、状態が変化したときだけ親機へデータを送信します(省電力の工夫)。

C++

#include <esp_now.h>
#include <WiFi.h>

// ★★★ ここに Step 1 で取得した親機(受信側)の MAC アドレスを設定 ★★★
// 例: AA:BB:CC:DD:EE:FF
uint8_t broadcastAddress[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; 

// ドアセンサーを接続するGPIOピン(例: GPIO 4)
const int DOOR_SENSOR_PIN = 4;
// センサー入力モード: 内部プルアップ抵抗を使用
const int SENSOR_MODE = INPUT_PULLUP; 

// ★★★ 共有するデータ構造体 ★★★
// 受信側と完全に一致させる必要があります。
typedef struct struct_message {
    bool door_open; 
} struct_message;

struct_message myData;

// 状態変化を検出するための変数
bool lastDoorState = false; // true=開 / false=閉

// データ送信後に呼ばれるコールバック関数(送信成功/失敗を確認)
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
    Serial.print("Last Packet Send Status: ");
    if (status == ESP_NOW_SEND_SUCCESS) {
        Serial.println("Delivery Success");
    } else {
        Serial.println("Delivery Fail - Retrying...");
        // 失敗時はここで再送信ロジックを追加できます
    }
}

void setup() {
    Serial.begin(115200);

    // Wi-Fiをステーションモードに設定
    WiFi.mode(WIFI_STA);

    // ドアセンサーピンを入力に設定(プルアップ有効)
    pinMode(DOOR_SENSOR_PIN, SENSOR_MODE);

    // ESP-NOWの初期化
    if (esp_now_init() != ESP_OK) {
        Serial.println("FATAL ERROR: Failed to initialize ESP-NOW");
        return;
    }

    // データ送信後のコールバック関数を登録
    esp_now_register_send_cb(OnDataSent);

    // 親機をピア(通信相手)として登録
    esp_now_peer_info_t peerInfo;
    peerInfo.channel = 0; 
    peerInfo.encrypt = false; 
    memcpy(peerInfo.peer_addr, broadcastAddress, 6); // MACアドレスをコピー

    if (esp_now_add_peer(&peerInfo) != ESP_OK){
        Serial.println("ERROR: Failed to add peer (Check MAC Address!)");
        return;
    }

    Serial.println("ESP-NOW Sender (Child) Initialized.");
    Serial.print("Monitoring Door Sensor on GPIO ");
    Serial.println(DOOR_SENSOR_PIN);
}

void loop() {
    // センサーの状態を読み取る
    // INPUT_PULLUPの場合: LOW = 磁石接近(GND接続) = ドア開
    bool currentDoorState = (digitalRead(DOOR_SENSOR_PIN) == LOW); 

    // ★★★ 状態変化検出ロジック ★★★
    // 状態が前回と異なる場合にのみ通信を実行する
    if (currentDoorState != lastDoorState) {
        lastDoorState = currentDoorState; // 状態を更新
        
        // 送信データを設定
        myData.door_open = currentDoorState; 

        // データの送信
        esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
       
        if (result == ESP_OK) {
            Serial.print("State Change Detected. Sending: Door Open ");
            Serial.println(myData.door_open ? "YES" : "NO");
        } else {
            Serial.println("WARNING: Sending Error Occurred.");
        }
    
        // 連続送信(チャタリング)を防ぐためのディレイ
        delay(500); 
    }

    // センサーの監視間隔
    delay(50);
}

❓ トラブルシューティング Q&A

Q1: ブザーが鳴りっぱなしになってしまう。

A1: アクティブブザー(電源を入れると鳴り続けるタイプ)の場合、親機側のプログラムでtone()関数を使っています。この関数は、指定した時間(例:500ms)が経過すると自動的に音が止まります。もし止まらない場合は、配線が間違っているか、または$\text{GPIO}ピンが意図せず\text{HIGH}$のままになっている可能性があります。tone()ではなくdigitalWrite(BUZZER_PIN, HIGH);を使っている場合は、必ずdelay()後にdigitalWrite(BUZZER_PIN, LOW);で止める処理が必要です。

Q2: 子機で「Failed to add peer」と表示される。

A2: 親機の MAC アドレスが正しくありません。以下の点を確認してください。

  1. 子機プログラムのbroadcastAddress[]配列に、親機のMACアドレスが正しく$\text{0x}$形式で入力されているか。
  2. 親機側が一度でも実行され、$\text{MAC}$アドレスが表示されたか。

Q3: 子機は「Delivery Success」なのに親機が反応しない。

A3: 最も多い原因は、データ構造体の不一致です。

  • 親機と子機のプログラムで定義しているstruct_messageの中身(変数名、変数の型、変数の順序)が完全に一致しているか確認してください。
  • 親機と子機が同じ Wi-Fi チャンネル(通常はchannel = 0)を使用しているか確認してください。

Q4: 通信距離を伸ばしたい。

A4: ESP-NOWは比較的遠くまで届きますが、通信が不安定な場合は以下の対策を試してください。

  • ESP32の$\text{Wi-Fi}$出力を最大にするコードを追加する。
  • 外部アンテナ接続用のESP32モジュール($\text{U.FL/IPEX}$コネクタ付き)を使用する。
  • 可能であれば、親機と子機の間に遮蔽物(特に金属や水)を置かないように配置を工夫する。

✨ 応用例:システムをさらにスマートに

このシンプルなESP-NOWシステムを拡張すれば、より高度なスマートホームシステムを構築できます。

応用例必要な追加コンポーネント拡張のポイント
バッテリー駆動のドアセンサーLi-Po バッテリー、充電回路子機プログラムに$\text{Deep Sleep}を追加し、状態変化時のみ起動するように変更する(\text{Wake-up Source}は外部\text{Interrupt}$)。
静かな通知システムLED や OLED ディスプレイ親機側でブザーの代わりに、LED を点滅させるか、ディスプレイに「ドアが開きました」と表示させる。
セキュリティ強化RTC(リアルタイムクロック)子機に時刻情報を追加で送信させ、親機で「夜間の開閉のみ」通知するようにロジックを組む。

ESP-NOWは、その手軽さと信頼性から、センサーネットワーク構築の基盤として非常に優秀です。ぜひ、この記事のプログラムをベースに、あなたのアイデアを形にしてみてください!

タイトルとURLをコピーしました