はじめに

この記事では、M5StickC PLUS のボタンを押すと ESP32 のLEDが点滅するデモを通じて、ESP-NOWによるデバイス間通信の実装方法を解説します。ESP-NOWはWiFiアクセスポイント不要・相手のMACアドレスだけで通信できる手軽な無線プロトコルで、BLE(Bluetooth Low Energy) と比べてもコードがシンプルです。M5StickC PLUSとESP32開発ボードがあれば、この記事のコードをそのまま動かせます。

ESP-NOWとは

ESP-NOWをもう少し詳しくみてみます。こちらにESP-NOWのドキュメントがありますので、目を通しておくと良いでしょう。

ドキュメントを翻訳すると、ESP-NOWは次のように説明されています。

ESP-NOWは、Espressifによって定義されたコネクションレス型Wi-Fi通信プロトコルです。アプリケーションデータはベンダー固有のアクションフレームにカプセル化され、接続なしで1つのWi-Fiデバイスから別のWi-Fiデバイスへ送信されます。CBC-MACプロトコル(CCMP)を使用したCTRでアクションフレームを暗号化してセキュリティを確保します。スマートライト・リモコン・センサなどで広く使われています。

TCPのようなハンドシェイクがないためデータ到達の保証はありませんが、高速かつ省電力な通信を実現できます。単純な送受信であれば使う関数はわずかで、すぐに動かせます。

プログラミング

ESP-NOWでLチカ

M5StickC PLUSを送信機、ESP32を受信機として実装します。送信するには受信機のMACアドレスが必要なので、まず受信側のプログラムを書き込んでMACアドレスを確認してください。

受信側のプログラム(ESP32)

Serial.println(WiFi.macAddress()); を実行することで、ESP32のMACアドレスをシリアルモニタへ表示します。表示されたMACアドレスをメモして下さい。

ESP-NOWを使ってデータを受信するには、esp_now_init() で初期化し、esp_now_register_recv_cb(onReceive) でデータ受信時のコールバックを登録します。WiFiモードは WIFI_STA(ステーションモード)を指定します。

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

const int LED_PIN = 13;
bool ledState = false;

void toggleLed() {
    if (ledState) {
        ledState = false;
        digitalWrite(LED_PIN, LOW);
    }
    else {
        ledState = true;
        digitalWrite(LED_PIN, HIGH);
    }
}

void onReceive(const uint8_t* mac_addr, const uint8_t* data, int data_len) {
    char macStr[18];
    snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
        mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
    Serial.println();
    Serial.printf("Last Packet Recv from: %s\n", macStr);
    Serial.printf("Last Packet Recv Data(%d): ", data_len);
    for (int i = 0; i < data_len; i++) {
        Serial.print(data[i]);
        Serial.print(" ");
        if (data[i] == 222) {
            toggleLed();
        }
    }

}

void setup() {

    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);
    Serial.println(WiFi.macAddress()); // このアドレスを送信側へ登録します

    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESP-Now Init Success");
    }
    esp_now_register_recv_cb(onReceive);
}

void loop() {
}

送信側のプログラム(M5StickC PLUS)

slaveAddress を、受信機のMACアドレスに書き換えてください。esp_now_peer_info_t で送信先の情報を登録し、esp_now_register_send_cb(onSend) で送信完了イベントを登録します(不要なら省略可)。データの送信は esp_now_send(slaveAddress, data, sizeof(data)) だけです。

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

uint8_t slaveAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; // 受信機のMACアドレスに書き換えます

void onSend(const uint8_t* mac_addr, esp_now_send_status_t status) {
    char macStr[18];
    snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
        mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(5, 10);
    M5.Lcd.println(macStr);
    M5.Lcd.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Failed");
}

void setup() {
    M5.begin();
    M5.Lcd.fillScreen(BLACK);

    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init Success");
    }

    esp_now_peer_info_t peerInfo;
    memcpy(peerInfo.peer_addr, slaveAddress, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;

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

    esp_now_register_send_cb(onSend);
}

void loop() {
    M5.update();

    if (M5.BtnA.wasPressed()) {
        uint8_t data[2] = { 111, 222 }; // 送信データ
        esp_err_t result = esp_now_send(slaveAddress, data, sizeof(data));

        Serial.print("Send Status: ");
        switch (result)
        {
        case ESP_OK:
            Serial.println("Success");
            break;
        case ESP_ERR_ESPNOW_NOT_INIT:
            Serial.println("ESPNOW not Init.");
            break;
        case ESP_ERR_ESPNOW_ARG:
            Serial.println("Invalid Argument");
            break;
        case ESP_ERR_ESPNOW_INTERNAL:
            Serial.println("Internal Error");
            break;
        case ESP_ERR_ESPNOW_NO_MEM:
            Serial.println("ESP_ERR_ESPNOW_NO_MEM");
            break;
        case ESP_ERR_ESPNOW_NOT_FOUND:
            Serial.println("Peer not found.");
            break;

        default:
            Serial.println("Not sure what happened");
            break;
        }

    }
    delay(1);
}

まとめ

ESP-NOWを使うと、ルーター不要・数行のコードだけでESP32デバイス間の無線通信が実現できます。ボタンを押したときのLED反応速度が有線と変わらないほど速く、IoTプロジェクトへの応用範囲は広いです。ESP32の無線通信をもっと活用したい方は、以下の記事も参考にしてみてください。

関連アイテム