概要
Arduino3系の調査とESP-IDF5系の調査をしつつ、最新のESP-NOW仕様を調査して、Arduino用の自動ピアリング対応のライブラリを作ってみました。
送信上限
まずESP-NOWといえば、250バイトまでの送信だったのですが、ESP-IDF 5.4 以降は1470バイトまで拡張されています。既存との互換性もありますので、古いバージョンやESP8266に送信した場合には先頭から250バイトまで受信する仕様となっております。
Wi-Fiチャンネル
Wi-Fiは1から13までのチャンネルがあり、チャンネルが異なると通信することができません。ESP-NOWで未指定の場合にはチャンネル1を使って通信するので、混信する可能性が高いです。イベント会場などで通信をする場合には他のチャンネルに変更することも検討してみてください。
ただ、ごく近距離の場合には隣の隣のチェンネルへの通信を受信する場合があったので、チャンネルをずらすことで13ピアが混信せずに通信できるわけではありません。チャンネルを変更することで混信を低減することはできますが、過信しないことが重要そうです。
通信速度
昔のバージョンでは上記のブログなどで通信速度を変更して通信をしています。
esp_wifi_config_espnow_rate(WIFI_IF_STA, WIFI_PHY_RATE_54M);
上記のようにかなりシンプルに指定できたのですが、この関数が非推奨になり新しい関数が増えています。
bool EspNowBus::applyPeerRate(const uint8_t mac[6])
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
if (!mac)
return false;
esp_now_rate_config_t rateCfg{};
rateCfg.rate = config_.phyRate;
rateCfg.ersu = false;
rateCfg.dcm = false;
if (config_.phyRate < WIFI_PHY_RATE_48M)
{
rateCfg.phymode = WIFI_PHY_MODE_11B;
}
else if (config_.phyRate < WIFI_PHY_RATE_MCS0_LGI)
{
rateCfg.phymode = WIFI_PHY_MODE_11G;
}
else if (config_.phyRate < WIFI_PHY_RATE_LORA_250K)
{
rateCfg.phymode = WIFI_PHY_MODE_HT20;
}
else
{
// default to 1M if unsupported
ESP_LOGW(TAG, "unsupported phyRate=%d, defaulting to 1M", static_cast<int>(config_.phyRate));
rateCfg.rate = WIFI_PHY_RATE_1M_L;
rateCfg.phymode = WIFI_PHY_MODE_11B;
}
esp_err_t err = esp_now_set_peer_rate_config(mac, &rateCfg);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "set peer rate failed rate=%d err=%d", static_cast<int>(config_.phyRate), static_cast<int>(err));
return false;
}
return true;
#else
(void)mac;
return false;
#endif
}
作成したライブラリの速度設定をしているコードですが、一気に面倒になりました。まず全体設定ではなく、ピア単位での速度設定に変更されています。また、phymodeを指定する必要がありますが、お手軽に設定する方法はないので自分で対応表を持つ必要があります。
速度について
| 規格定義 | 速度定義 |
|---|---|
| WIFI_PHY_MODE_11B | WIFI_PHY_RATE_1M_L |
| WIFI_PHY_MODE_11B | WIFI_PHY_RATE_2M_L |
| WIFI_PHY_MODE_11B | WIFI_PHY_RATE_5M_L |
| WIFI_PHY_MODE_11B | WIFI_PHY_RATE_11M_L |
| WIFI_PHY_MODE_11B | WIFI_PHY_RATE_2M_S |
| WIFI_PHY_MODE_11B | WIFI_PHY_RATE_5M_S |
| WIFI_PHY_MODE_11B | WIFI_PHY_RATE_11M_S |
| WIFI_PHY_MODE_11G | WIFI_PHY_RATE_48M |
| WIFI_PHY_MODE_11G | WIFI_PHY_RATE_24M |
| WIFI_PHY_MODE_11G | WIFI_PHY_RATE_12M |
| WIFI_PHY_MODE_11G | WIFI_PHY_RATE_6M |
| WIFI_PHY_MODE_11G | WIFI_PHY_RATE_54M |
| WIFI_PHY_MODE_11G | WIFI_PHY_RATE_36M |
| WIFI_PHY_MODE_11G | WIFI_PHY_RATE_18M |
| WIFI_PHY_MODE_11G | WIFI_PHY_RATE_9M |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS0_LGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS1_LGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS2_LGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS3_LGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS4_LGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS5_LGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS6_LGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS7_LGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS0_SGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS1_SGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS2_SGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS3_SGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS4_SGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS5_SGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS6_SGI |
| WIFI_PHY_MODE_HT20 | WIFI_PHY_RATE_MCS7_SGI |
ざっくりとこの対応になります。速度にLかSがついているのがIEEE 802.11bになり、速度は遅いですが遠距離に強い無線LAN規格です。LとSがさらにプリアンブル長の長さでLの方が古い端末向けで、Sの方がスループットが高くなります。
54MなどMがつくのが11gになります。11bより遠距離に弱いですが、速度がでます。
MCS0などの表記になったものは11nになります。後ろの数値が多いほど速度が早くなります。実際にはHT20、HT40などチャネル幅の指定も必要です。HT20だと20 MHzになります。
ここからが面倒なのですが、ESP32シリーズにより、利用できる速度が変わります。特にMCS7が最速なのですが、ESP32-S3/C3以外だと不安定になるようです。無印ESP32やC系のシリーズの場合にはもう少し速度を下げないと安定しないとの情報がありました。
WIFI_PHY_RATE_1M_L(802.11b): 遅いが遠距離でも安定。WIFI_PHY_RATE_11M_L(802.11b): 中距離まで安定し、そこそこ高速。WIFI_PHY_RATE_24M(802.11g): 近距離で高速かつ汎用性あり。WIFI_PHY_RATE_MCS4_LGI(802.11n, 約39 Mbps): 無印 ESP32 で現実的な安定上限。WIFI_PHY_RATE_MCS7_LGI(802.11n, 約65 Mbps): 最速だが ESP32-S3/C3 以外では不安定になりがち。
ざっくりした目安ですが、上記をまずは参考にしてみてください。利用する環境によっても変わりますので、実験してみてから速度を設定するのがおすすめです。とくに人が少ないきれいな環境とオフィスや展示会などの人が多い環境ではかなりの差がでるはずです。
デフォルトはWIFI_PHY_RATE_1M_Lの一番安定している通信になります。
暗号化
ESP-NOWではあまり暗号化を使っていない気がします。暗号化した場合にはペア数の上限があり6ピアまでと結構低いです。通常はこれぐらいのピア数で問題ないとは思いますが、数を増やしたい場合には暗号化は諦める必要があります。
自作ライブラリ
ここまでの調査を踏まえて、Arduino用のESP-NOWライブラリを作成してみました。上記のEspNowBusになります。
基本方針
まずESP-NOWはブロードキャストで周りに通信を撒き散らすことがあるので、手軽な方法で混信しないライブラリを目指してみました。デフォルトのArduinoライブラリはいまいちな作り名ので使わないのが推奨です。使うにしてもESP-IDFの関数を直接呼び出すのがおすすめです。
混信しない仕組みとして、事前暗号キー方式を採用しました。
EspNowBus::Config cfg;
cfg.groupName = "espnow-demo_" __FILE__;
初期化前にグループ名を指定しています。上記の場合にはグループ名の他にフルパスのファイル名も追加しているので、ほぼ被ることがないと思います。このグループ名を暗号キーとして利用して混信を防ぐ仕組みになっています。
暗号キーを利用して、ブロードキャストの署名とユニキャストの暗号化を行っています。ブロードキャストでは全端末にパケットが届いてしまうので、暗号キーを利用した署名を実施することで、同じグループ名かの判定が可能です。
ユニキャストの場合にはパケット自体の暗号化にこの暗号キーから生成したキーを利用しているので、別グループ名の端末には届かない仕組みになります。
欠点として、署名や暗号化を実施している関係で送信できるバイト数が減っています。最長1470バイトからブロードキャストの場合には36バイトも減ってしまいます。
自動ピアリング
ライブラリの肝として、ピアリングは自動化してあります。同じグループ名のピアをブロードキャストで募集することで、受信した端末のライブラリ側で自動ピアリングを実施しています。
自動募集はOFFにもでき、インターネットに接続しているマスターノードのみが募集をして、センサーノードは自動ピアリング機能を利用することで、センサーノード同士がピアリングしないスター型のネットワークが構築可能です。この場合には全ピアにユニキャストする関数がありますので、マスターノードが複数いる場合にデータ登録をかんたんに冗長化することができます。
6台までのESP-NOWを利用したゲームなどでは自動募集をONにしておくと、全ノードが自動的にピアリングする動作になります。
ピアリングしなくても署名付きのブロードキャストを使うことで、同じグループ名同士で通信が可能になりますが、ピアリングをすることで通信内容が暗号化されてより混信しなくなります。
ただし、自動ピアリングがあるので、ピア同士のハートビート確認などをライブラリ側で実施して、一定時間通信が無くなったらピアリングの依頼をしたり、タイムアウトでパージしてピアリングから外す処理などが組み込まれています。
受信確認と自動再送
ブロードキャストの場合には相手に届いているかわからないのですが、ユニキャストの場合には受信確認パケットをやりとりする仕様となっています。ただし、デフォルトの受信確認は物理的に届いているかの確認で、暗号化が失敗していた場合などにもOKが返ってきます。そこでライブラリ側で暗号キーを利用した受信確認と、失敗時の再送処理を実装しました。
また、ESP-NOWは送信中の判定ができず、あやまって送信中にさらに送信をすることが可能であり、その結果送信が失敗するケースがあります。ライブラリでは送信用のキューを実装し、順番に自動送信する仕組みになっています。
チャンネル
少し乱暴な仕様なのですが、グループ名から1から13のチャンネルを自動選択する機能がデフォルトになっています。同じグループ名の場合には特定のチャンネルになるので混信しにくくなります。ただしグループ名によっては混雑しているチャンネル1などに割り当てられることがあるので、環境によっては手動で上書き設定する必要があります。
速度
デフォルトを一番遠距離に強い802.11bのWIFI_PHY_RATE_1M_LからWIFI_PHY_RATE_11M_Lに変更してあります。もう少し早い速度設定も可能ですが、数メートル程度であれば安定している11Mをとりあえずのデフォルトにしました。
まとめ
ESP-NOWの送信サイズが250バイトから拡張されていて嬉しいです。UDP的に高速通信したい場合にはESP-IDFの関数を直接利用するのが好ましいです。
多少のオーバーヘッドがあってもお手軽に通信したい場合には作成した作成したEspNowBusライブラリが便利だと思います。



コメント