ESP32[17] ESP-NOW

ESP-NOWを使ってみた。1対1の通信なら、WiFiでも特に問題なく使用できるし、BTSerialでも比較的簡単にできる。しかし、複数のESP32を使用しようと思った場合、ルータを中心にスター型の接続はそれほど難しくはないけど、1次元に並べられたところを渡りながら移動していくような使い方、たとえば、鉄道模型などを考えると結構、この切り替えていくのが難しいとわかってきた。前のブログにも書いたけど、WiFiでもBTSerialでも切り替えできなくはない。しかし、切り替えに時間がかかったり切り替えがなかなか安定しない。そんなとき、この ESP-NOWを見つけた。いや~~、ネットサーフィンってすごく有益だよね。先人たちが挙げてくれている情報がなかったら何にもできないです。

ESP-NOWはEspressifが開発した通信方式で、Wi-Fiルーターを経由せず直接機器同士で通信ができます。

MACアドレスを指定しての1対1の通信やブロードキャストでの1対多の一斉送信も行えます。欠点もあります。TCPのようなセッションは張れずに、UDPのような使い方しかできないようです。ESP32ということから、ほとんど趣味で使用されるということと、大きなデータは扱わないでしょうからUDPで十分かと思います。それよりも使い方が簡単でこれはいいものを見つけたという感じです。

ドキュメントを見ると、PMK is used to encrypt LMK with the AES-128 algorithm. Call esp_now_set_pmk() to set PMK. If PMK is not set, a default PMK will be used.と書いてあるので、デフォルトでも暗号化しているようです。

使いかたはちょっと変わっていて、受信があると勝手にコールバックされるので受信部分のコードを書く必要がありません。ライブラリは最初から入っているようでした。スケッチ例⇒ESP32の下にESPNowとあればライブラリも入っていると思います。

私はブロードキャストで投げて、受け側で自分が必用かどうかを判断するやり方にしたので、ここではブロードキャストのみの例を書いておきます。多分、IPアドレスを指定すれば、P2Pになるとは思いますが。

#include <WiFi.h>  // WiFI.h も必要らしいです。

#include <esp_now.h>

esp_now_peer_info_t slave;

// 送信コールバック 関数です。ここでは、MACアドレスのみ設定して、メッセージは loop 内で書きます。
void OnDataSent(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]); // デフォルト FFみたい。
}

// 受信コールバック //ここで、受信したデータを処理します。
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[18];
  char msg[1];
  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]);



  sscanf((char*) data, "%d %d %d %d %d %d %d %f %d %d %d %d", // 送信データに合わせて取り出します。
         &sender, &tmt_cnt, &gps_time_hour, &gps_time_minute, &gps_time_second, &kirotei, &dist_to_xross, &gps_speed_kmph,
         &st0, &st1, &st2, &st3);

}

setup() {
  // ESP-NOW初期化
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  } else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }

  // ESP-NOWコールバック登録
  esp_now_register_send_cb(OnDataSent);
  esp_now_register_recv_cb(OnDataRecv);

  // マルチキャスト用Slave登録
  memset(&slave, 0, sizeof(slave));
  for (int i = 0; i < 6; ++i) {
    slave.peer_addr[i] = (uint8_t)0xff;
  }

  esp_err_t addStatus = esp_now_add_peer(&slave);
  if (addStatus == ESP_OK) {
    // Pair success
    Serial.println("Pair success");

}

loop(){
char buf[128];
uint8_t bs[250];

sprintf(buf, "%d %d %d %d %d %d %d %3.1f %d %d %d %d\0", // 一旦 char buf[] に貯めます。
            ME, tmt_cnt, gps_time_hour, gps_time_minute, gps_time_second, kirotei, dist_to_xross, gps_speed_kmph,
            st0, st1, st2, st3);


    memcpy(bs, buf, strlen(buf)); // 型変換が必用なのでメモリコピーします。

  
    esp_err_t result = esp_now_send(slave.peer_addr, bs, strlen(buf));

    //  Serial.print("Send Status: ");
    if (result == ESP_OK) {
      //    Serial.println("Success");
    } else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
      Serial.println("ESPNOW not Init.");
    } else if (result == ESP_ERR_ESPNOW_ARG) {
      Serial.println("Invalid Argument");
    } else if (result == ESP_ERR_ESPNOW_INTERNAL) {
      Serial.println("Internal Error");
    } else if (result == ESP_ERR_ESPNOW_NO_MEM) {
      Serial.println("ESP_ERR_ESPNOW_NO_MEM");
    } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
      Serial.println("Peer not found.");
    } else {
      Serial.println("Not sure what happened");
    }

}

今まで、いろいろやってきましたが、これが一番簡単な気がします。 いつも、uint8_t の型変換で苦労します。なぜ、普通に char とか int とかを使ってくれないんだろう。