#はじめに
前の記事でESP8266からESP8266へのESP-Nowを使ったデータ転送を投稿しましたが、ESP32をスレーブにしようとしたところで、全然うまく行かず、つまづいてしまいましたので、まずは試しに、ESP32からESP32への転送で確認した後、ESP8266からの転送に再トライすることにしました。現状としては、ESP32からESP32への転送は非常にシンプル(関数も少ない)に実現できましたが、ESP8266から通信にはESP32がなかなか思うように受信してくれず、まだ道半ばです・・・・ だったのですが・・・・
#結論
何のことは無い、使っていたESP8266のモジュールが完全で無かったようです(どこかが一部壊れていたっぽい)。交換したら何事も無かったかのように正常に動作しました。(中途半端に壊れていたのか、ものすごく遅かったり、途中で止まったり・・が繰り返されたので試しに、別基板で実験したら、即OKでした。めでたしめでたし)
東京お気楽カメラ様のESP8266とESP32 温度測定にESP-NOWを使ってみる を大変参考にさせて頂きましたが、その記事の中でも述べられています通り、ESP8266のESP-NowのAPIと ESP32のESP-Nowでは、かなり違いが見られます。includeする headerファイルも違います。(ESP8266 → espnow.h、ESP32 → esp_now.h)。全般的に見て ESP8266の関数の方が充実していて、きめ細かい処理ができると思いますが、それに比べ ESP32のライブラリはシンプルです。roleを設定する関数もありません。また、call back関数の引数定義も微妙に違うため、最初はコンパイルエラーに戸惑いました。(ESP32は、constが必要とか・・・)
進め方として、前回 ESP8266→ESP8266は確認できたので、次に ESP32→ESP32を確認したあとで、ESP8266→ESP32を実験してみたいと思います。
#ESP32からESP32への通信
マスタ側もスレーブ側も非常にシンプルです。ESP32の場合には、スレーブ側をAPにする必要すらなく、STATIONのままでも通信が出来ます。文末に書きましたが、チャンネル設定をゼロにすると「現在のチャンネル」になるので、無難かと思い、そのように使ってみました。マスタ側では、Getting Started with ESP-NOW (ESP32 with Arduino IDE)
の記事にあったやり方で、構造体にセットしてからスレーブアドレスのpeer設定を行う形にしました。
//Register peer
esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
##マスタ側
コードは下記ですが、特に工夫はありません。前述のように、peer設定は構造体を使いました。文末に記載したとおり、チャンネルをゼロにしました。送るデータは、適当に0~100までを行ったり来たりする三角波を、5msec周期で送り続けます。受信側で シリアルプロッタで確認がしやすいかなと思っただけです。
最大250バイトしか送れないpayloadを有効に使うためには、文字列では無くバイナリで送るべきかと思いますが、今回スレーブ側は、単純になんでもいいから受信して、そのままシリアルでラズパイのホストに送り、内容の解析や、Node-REDでの活用はその先で行う方法を考えていますので、構造体を最初に決めてしまうよりは、ただの文字列の方が自由度が高いかなと考えたからです。マスタの種類や数を今後、色々増やしていくうえで、その都度、スレーブ側のコードを変更しなくても良いようにしたかったためです。思い通りに進むかどうかは、未知数ですが。
#include <esp_now.h>
#include <WiFi.h>
#define CHANNEL 0
uint8_t slaveMAC[] = {0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c};//peer mac address
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_send_cb(OnDataSent);
esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, slaveMAC, 6);
peerInfo.channel = CHANNEL;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("Failed to add peer");
return;
}
}
char buf[250];
int ctr = 0;
uint8_t bs[250];
boolean direction = 0;
void loop() {
sprintf(buf, "%d", ctr);
memcpy(bs, buf, strlen(buf));
Serial.print(buf);
Serial.print(" : ");
esp_now_send(slaveMAC, bs, strlen(buf));
if (direction == 0) {
if (ctr < 100) ctr++;
else {
direction = 1;
}
} else {
if (ctr >= 1) ctr--;
else {
direction = 0;
}
}
delay(5);
}
##スレーブ側(STATIONモード)
まずは、STATIONモードで確認しました。送られてきたデータ(文字列)をバッファに格納してシリアルに出力しているだけですが、これでシリアルプロッタを繋ぐと、5msec毎に送られた三角波が表示されるので、データが抜けたら少しは分かるかもという程度です。そもそも、ESP-Nowは、100%のデータ伝送保証はできない仕組みなので、非常に信頼性の高い通信が必要な場合には基本的には使えない仕組みです(と思います)。
下記のコードのように、STATIONモードでの受信は非常にシンプルです。
#include <esp_now.h>
#include <WiFi.h>
#define CHANNEL 0
char buf[250];
void OnDataRecv(const uint8_t * mac, const uint8_t *recvData, int len) {
memcpy(&buf[0], recvData, len);
buf[len]='\0'; //lenには文字列末のNULLが入らないので暫定的に。
Serial.println(buf);
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
}
##スレーブ側(APモード)
スレーブ側をAPモードにして、同様の通信を確認してみました。これまで、よく知らなかったのですがESP32とかESP8266のようなデバイスは、自分がSTATIONとなる時のMACアドレスと、APになる時のMACアドレスを別々に持つんですね。今回、初めて知りました。自分がAPになった時のMACアドレスは、WiFi.softAPmacAddress()関数で調べる事ができますので、そのアドレスを、マスタ側(Controller/Sender側)のコードに、peerアドレスとして設定する必要があります。当然ですが、STATIONモードの時に送っていたアドレスに送っても届きません。
マスタ側のコードの、peer設定するアドレスを、下記コードで表示されるsoftAPmacAddress()に変更して、通信状況をシリアルプロッタで確認すると同様の三角波が、正常に確認できました。
下記コードに於いて、WiFi.disconnect()は、入れても入れなくても、今回の実験には影響ありませんでしたが、多分、他の方が投稿されておられるように、消費電力に差が出るのかと思います。このあたりは別途、調べてみたいと思っています。
また、スレーブ側のCHANNELを0以外に変更してみましたが、マスタ側で 0 に設定しているため(文末に記述)だと思いますが、問題なく受信できました。(そもそも、このCHANNELが、どのようにWiFiに於いて作用しているのか勉強不足です)
#include <esp_now.h>
#include <WiFi.h>
#define CHANNEL 0
char buf[250];
void OnDataRecv(const uint8_t * mac, const uint8_t *recvData, int len) {
memcpy(&buf[0], recvData, len);
buf[len]='\0'; //lenには文字列末のNULLが入らないので暫定的に。
Serial.println(buf);
}
void setup() {
Serial.begin(115200);
//Serial.println(WiFi.macAddress());
WiFi.mode(WIFI_AP);
WiFi.softAP("ESP32-Slave", "ESP32-Slave-PW", CHANNEL, true);
Serial.println(WiFi.softAPmacAddress());
//WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
}
ここまでで、ESP32同士の通信実験は終わり。ここからは、ESP8266がマスタ、ESP32がスレーブとなる構成での実験です。。
#ESP8266からESP32への通信
基本的には、シンプルなコードで接続できます。ESP8266とESP32とで関数が違ったりするので、その差分さえ分かってしまえば、ストレートな書き方で大丈夫でした。(散々悩んだのに・・・ハードの故障で随分時間を取られてしまいました・・・)
##ESP8266のマスター側を色々試してみる
peerの足し方が悪いのかとも思い、ESP32と同様の方法を取ろうとしましたが、esp_now_peer_info_tの構造体が、ESP8266の環境では定義されていないので無理でした。やはり、esp_now_add_peer(slaveMAC, ESP_NOW_ROLE_SLAVE, WIFI_CHANNEL, NULL, 0)で足していくしかなさそうです。WIFI_CHANNELを双方ともゼロで合わせてみましたが、やはり通信できません。うぅ~ん・・・・。何が違うんだ。ESP8266からの送り方と ESP32からの送り方で何が違うんだ・・・・。 先人の実績があるから出来ないはずは無いし・・・・。行き詰まり・・・。
ちなみに、この状況でもESP8266からESP8266は問題なく通信できてます。マスター側に二つのPeerを設定して、送信していますが、ESP8266のスレーブは受け取ってくれますが、ESP32のスレーブが受け取ってくれません。
##マスター側
ESP8266は、espnow.hのincludeが必要です。通信相手先Peerとして登録するため、ESP32のAPモードのMACアドレスを、直接コードに書き込んでいます。これを初期化時に、Peerとして追加します。
ESP8266のマスターには、コントローラーとしてのROLEを定義し、ESP32のスレーブには、add_peer関数の中で、COMBOを定義しました。SLAVEでも良いとは思いますが、試していません。WIFI_CHANNELは 1を登録しましたが、要検討です。0の方が融通が利いて良いのではとも思いますが定かではありません。
send時に成功したかどうかは、call backで知る事が出来ますので、call back関数を登録して、send時のStatusを表示しています。
main loopでは、0~100までを inc/decして、三角波でデータの飛びが無いかどうかを確認するようなデータを 5msecの間隔で送り続けます。
#include <ESP8266WiFi.h>
#include <espnow.h>
#define WIFI_CHANNEL 1
uint8_t slaveMAC[] = {0x3c, 0x71, 0xff, 0xff, 0xff, 0xff};//COM16 ESP32 AP mode
void printMacAddress(uint8_t* mac) {
Serial.print("{");
for (int i = 0; i < 6; i++) {
Serial.print("0x");
Serial.print(mac[i], HEX);
if (i < 5) Serial.print(',');
}
Serial.print("}");
}
// call back for sending data
void send_cb(uint8_t* mac, uint8_t sendStatus) {
//printMacAddress(mac);
//Serial.print(" --> ");
Serial.println(sendStatus == 0 ? "OK" : "Failed");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
esp_now_add_peer(slaveMAC, ESP_NOW_ROLE_COMBO, WIFI_CHANNEL, NULL, 0);
esp_now_register_send_cb(send_cb);
}
char buf[200];
uint8_t bs[200];
int ctr = 0;
boolean direction = 0;
void loop() {
sprintf(buf, "%d", ctr);
memcpy(bs, buf, strlen(buf));
Serial.print(buf);
Serial.print(",");
esp_now_send(slaveMAC, bs, strlen(buf));
if (direction == 0) {
if (ctr < 100) ctr++;
else {
direction = 1;
}
} else {
if (ctr >= 1) ctr--;
else {
direction = 0;
}
}
delay(5);
}
##ESP32のスレーブ側
ESP32のスレーブ側は、esp_now.hのincludeが必要です。
APモードで設定しますが、アクセスポイントとしてのSSID設定等は使わないので設定しません。APモードのMacアドレスを取得して、確認用にシリアル出力します。この値を、マスター側でPeer先として登録する必要があります。
APモードに設定したあと、直ぐDisconnectしていますが、先人の情報によるとこの方が消費電力が減るとの事。ESP-Nowの動作としては、disconnectしてもしなくても同じです。
マスターからデータを受信したら、call back関数が呼ばれますので、そこで処理します。割り込み処理みたいなものなので、時間のかかる処理はこの中ではやるべきではありませんが、受け取った値と、そのまま Serial.printlnで出力し、USB シリアル経由で、ラズパイ等に送る予定なので、このくらいは大丈夫かなと思います。データを送る周期とのバランスかと思います。多くのデバイスから、非同期にデータが届きますから、取りこぼさないためには、少しでも早くこのルーチンは抜けるべきでしょう。
今回のテスト用のスレーブは、受信だけなので、メインループの中では、何もしていません。
5msec毎に、inc/decした値を、スレーブ側で受信して、シリアルプロッタで波形で大きな値の飛びが無い事を確認したのが下図です。
値を、Excelに落として各受信データ間の差分を確認しましたが1分程度の実験ですが、歯抜けはありませんでした。
#include <esp_now.h>
#include <WiFi.h>
#define WIFI_CHANNEL 1
char buf[200];
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_AP);
Serial.println( WiFi.softAPmacAddress() );
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-Now Init Failed....");
return;
}
esp_now_register_recv_cb(recv_cb);
}
// call back for receiving data
void recv_cb(const uint8_t * mac, const uint8_t *recvData, int len) {
memcpy(&buf[0], recvData, len);
buf[len]='\0'; //lenには文字列末のNULLが入らないので暫定的に。
Serial.println(buf);
}
void loop() {
}
という事で、色々悩みながらやって来ましたが、ハードを変えたらすぐ動いた・・という事で、やり方は間違っていなかったという事が分かりました。これからは、もう少し早めにハードを疑ってみる事にします。
次は、意味のあるデータを送る実験の予定です。
##HDC1080を使ったESP8266マスター(実験前)
参考までに、前回 HDC1080を繋いで実験した時の ESP8266のコードを載せておきます。交換した新しいESP8266モジュールでは、まだ実験できていませんが、問題なく動くでしょう。
#include <Wire.h>
#include "ClosedCube_HDC1080.h"
#include <ESP8266WiFi.h>
//extern "C" {
#include <espnow.h>
//}
#define WIFI_CHANNEL 0
uint8_t slaveMAC[][6] = {
{0x84, 0xf3, 0xeb, 0x86, 0xe0, 0xd4}, //0: ESP8266 COM5
{0x3c, 0x71, 0xbf, 0x9a, 0x60, 0xfc}, //1: ESP32 COM15
{0x3c, 0x71, 0xbf, 0x9a, 0x60, 0xfd}, //2: ESP32 COM15 AP
{0x3c, 0x71, 0xbf, 0x9a, 0x64, 0x84}, //3: ESP32 COM16
{0x3c, 0x71, 0xbf, 0x9a, 0x64, 0x85} //4: ESP32 COM16 AP
};
ClosedCube_HDC1080 hdc1080;
void printMacAddress(uint8_t* macaddr) {
Serial.print("{");
for (int i = 0; i < 6; i++) {
Serial.print("0x");
Serial.print(macaddr[i], HEX);
if (i < 5) Serial.print(',');
}
Serial.print("}");
}
void send_cb(uint8_t* mac, uint8_t sendStatus) {
printMacAddress(mac);
Serial.print(" --> ");
Serial.println(sendStatus == 0 ? "OK" : "Failed");
//Serial.printf(": %i\n", sendStatus);
}
void setup() {
Serial.begin(74880);
Serial.println();
hdc1080.begin(0x40); //default 14bit resolution
WiFi.mode(WIFI_STA);
if (esp_now_init() != 0) {
Serial.println("*** ESP-Now init failed");
}
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
Serial.println("add peer");
Serial.println(esp_now_add_peer(slaveMAC[0], ESP_NOW_ROLE_SLAVE, WIFI_CHANNEL, NULL, 0));
Serial.println(esp_now_add_peer(slaveMAC[4], ESP_NOW_ROLE_SLAVE, WIFI_CHANNEL, NULL, 0));
esp_now_register_send_cb(send_cb);
}
uint8_t bs[250];
char buf[250];
char s_temp[8];
char s_hum[8];
char* s_device = "8266-HDC1080";
void loop() {
dtostrf(hdc1080.readTemperature(), 0, 2, s_temp);
dtostrf(hdc1080.readHumidity(), 0, 2, s_hum);
sprintf(buf, "%s,%s,%s", s_device, s_temp, s_hum);
memcpy(bs, buf, strlen(buf));
Serial.println(buf);
esp_now_send(slaveMAC[0], bs, strlen(buf)); // NULL means send to all peers
esp_now_send(slaveMAC[4], bs, strlen(buf)); // NULL means send to all peers
delay(1000);
}
#他にも参考にさせて頂いたサイト
##peerの追加について
ESPRESSIFのESP32プログラミングガイドに以下の記述がありました。
"Call esp_now_add_peer() to add the device to the paired device list before you send data to this device. The maximum number of paired devices is twenty. If security is enabled, the LMK must be set. You can send ESP-NOW data via both the Station and the SoftAP interface. Make sure that the interface is enabled before sending ESP-NOW data. A device with a broadcast MAC address must be added before sending broadcast data. The range of the channel of paired devices is from 0 to 14. If the channel is set to 0, data will be sent on the current channel. Otherwise, the channel must be set as the channel that the local device is on."
これによると、送る相手はStationでもSoftAPでもどちらでも良いとあります。確かに、試した結果 ESP32同士の場合、スレーブはどちらでも通信できました。チャンネル設定は0~14だが、0にセットすると現在のチャンネルになるし、設定すればローカル側のチャンネルと一致させろという事かと思います。
ローカル側とは自分側を指すのか?と思いましたが、esp_now_peer_info構造体の説明を読むと、
"Wi-Fi channel that peer uses to send/receive ESPNOW data. If the value is 0, use the current channel which station or softap is on. Otherwise, it must be set as the channel that station or softap is on."
チャンネルを0以外にするときは、station or softapが動いているチャンネルにしろという意味かと思いますので、やはりPeer先のチャンネル番号を指定する事を意味していると思われます。 ゼロにしておくのが無難と思われます。peer情報構造体の情報はこちら