Über den Beitrag
ESP-NOW Serial wurde mit dem Update des ESP32 Boardpaketes für Arduino auf Version 3.x eingeführt. Es handelt sich dabei um ein ESP-NOW Feature, das durch seine überaus einfache Bedienung besticht. Über ESP-NOW hatte ich einen separaten Beitrag verfasst, den ich übrigens mittlerweile hinsichtlich der Änderungen des Boardpaketes 3.x angepasst habe.
ESP-NOW bietet zwar wesentlich mehr Möglichkeiten als ESP-NOW Serial, allerdings ist ESP-NOW dafür komplexer, was insbesondere ab Boardpaketversion 3.x gilt. Wer einfach nur ein paar Daten zwischen zwei oder mehreren ESP32 Boards austauschen möchte, für den könnte ESP-NOW Serial genau das Richtige sein.
Folgendes kommt auf euch zu:
- Die MAC-Adresse ermitteln
- ESP-NOW Serial Minimum Beispiel
- Erweiterter Sketch
- Mehr als zwei ESP-NOW Serial Netzwerkteilnehmer
- Strukturen mit ESP-NOW Serial senden
Die MAC-Adresse ermitteln
Bevor es mit ESP-NOW Serial losgeht, will ich noch zeigen, wie ihr die MAC-Adressen eurer ESP32-Boards ermittelt, weil ihr diese für die Kommunikation benötigt. Zur Erinnerung: die MAC-Adresse eines Gerätes ist eine spezifische Kennung, bestehend aus sechs Bytes, mit denen ihr ein Gerät in einem Netzwerk eindeutig identifizieren könnt.
Der ESP32 hat im Station-Modus (STA) und im Access-Point-Modus (AP) unterschiedliche MAC-Adressen. In den Beispielsketchen nutze ich nur den STA-Modus, ihr könntet aber auch den AP-Modus verwenden oder auch gemischte Netzwerke mit Teilnehmern im STA- und im AP-Modus aufsetzen.
Station-Modus
#include "WiFi.h" void setup(){ Serial.begin(115200); delay(1000); WiFi.mode(WIFI_STA); while (!(WiFi.STA.started())) { delay(10); } Serial.print("MAC-Address: "); String mac = WiFi.macAddress(); Serial.println(mac); Serial.print("Formated: "); Serial.print("{"); int index = 0; for (int i=0; i<6; i++) { Serial.print("0x"); Serial.print(mac.substring(index, index+2)); if(i<5){ Serial.print(", "); } index += 3; } Serial.println("}"); } void loop(){}
Hier ein Ausgabebeispiel:
Access-Point-Modus
#include <WiFi.h> void setup(){ Serial.begin(115200); delay(1000); WiFi.mode(WIFI_AP); while (!(WiFi.AP.started())) { delay(10); } Serial.print("MAC-Address: "); String mac = WiFi.softAPmacAddress(); Serial.println(mac); Serial.print("Formated: "); Serial.print("{"); int index = 0; for (int i=0; i<6; i++) { Serial.print("0x"); Serial.print(mac.substring(index, index+2)); if (i<5) { Serial.print(", "); } index += 3; } Serial.println("}"); } void loop(){}
Und hier das Ausgabebeispiel:
Alternative: die MAC-Adresse ändern
Alternativ könnt ihr die hardwareseitig vorgegebene MAC-Adresse ändern. Der Vorteil ist, dass ihr die MAC-Adressen nicht mühselig für jedes Board ermitteln müsst. Wie das geht, habe ich hier beschrieben. Die Änderung ist nicht permanent.
ESP-NOW Serial Minimum Beispiel
Zum Einstieg nehmt ihr den folgenden Sketch und ladet ihn auf zwei ESP32-Boards. Zuvor müsst ihr den Sketch hinsichtlich der MAC-Adressen des Kommunikationspartners („Peer“) anpassen und unter unterschiedlichen Namen abspeichern.
#include "ESP32_NOW_Serial.h" #include "MacAddress.h" #include "WiFi.h" #include "esp_wifi.h" #define ESPNOW_WIFI_CHANNEL 1 const MacAddress peer_mac({0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4}); // counterpart MAC address ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE); while (!(WiFi.STA.started())) { delay(100); } NowSerial.begin(115200); Serial.println("You can now send data to the peer using the Serial Monitor.\n"); } void loop() { while (NowSerial.available()) { Serial.write(NowSerial.read()); } while (Serial.available()) { NowSerial.write(Serial.read()); } delay(1); }
Jetzt könnt ihr für beide Sketche jeweils einen seriellen Monitor öffnen und darüber Nachrichten von einem Board zum anderen schicken. Für die bessere Lesbarkeit solltet ihr auf beiden Seiten „Sowohl NL als auch CR“ einstellen.
Erklärungen zum Sketch
- Mit
#define ESPNOW_WIFI_CHANNEL 1
legt ihr den Kanal fest (1 bis 14). Die Boards müssen auf denselben Kanal eingestellt sein.- Jeder Kanal hat eine eigene Frequenz (2412 bis 2484 MHz). Nicht jeder Kanal darf in jeder Region benutzt werden, wobei die Kanäle 1 – 11 in den meisten Ländern erlaubt sein sollten. Mehr Details findet ihr hier.
const MacAddress peer_mac({....})
ist die Adresse eures „Peers“.ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);
erzeugt euer ESP-NOW Serial Objekt. Ihr übergebt ihm die Peer MAC Adresse, den Kanal und den Modus (hier WIFI_IF_STA = WiFi Interface Station).- Mit
WIFI.Mode()
legt ihr den Modus fest, mitWIFI.setChannel()
stellt ihr den Kanal ein und mitWIFI.STA.started()
prüft ihr, ob der Modus gestartet ist. NowSerial.begin()
startet euer ESP-NOW Serial Objekt.
Die Hauptschleife loop()
funktioniert so, wie ihr es von der Serial- oder SoftwareSerial-Kommunikation her gewohnt seid. NowSerial.available()
prüft, ob Daten empfangen wurden. NowSerial.read()
liest das nächste Zeichen im Empfangs-Pufferspeicher als Byte und NowSerial.write()
schreibt ein Zeichen als Byte in den Sende-Pufferspeicher.
Erweiterter Sketch
Stolperfalle: Wenn das andere Board nicht bereit ist
Trennt einmal eines der Boards vom PC und versucht ihm mit dem anderen Board eine Nachricht zu senden. Das geht natürlich nicht. Verbindet das Board wieder und sendet ihm erneut eine Nachricht. Es funktioniert weiterhin nicht! Erst, wenn ihr das sendende Board neu startet, kommen die Nachrichten wieder an. Ich weiß nicht, ob das gewollt oder ein Bug ist, der vielleicht noch behoben wird. In den folgenden Sketchen fangen wir den Fehler ab, indem das sendende Board bei missglücktem Senden neu gestartet wird.
Außerdem möchte ich mit den nächsten beiden Sketchen zeigen, dass auch andere von Serial und SoftwareSerial her gewohnte Funktionen mit ESP-NOW Serial funktionieren. Dazu habe ich einen Sender- und einen Empfängersketch geschrieben.
Sender Sketch
#include "ESP32_NOW_Serial.h" #include "MacAddress.h" #include "WiFi.h" #include "esp_wifi.h" #define ESPNOW_WIFI_CHANNEL 1 #define SEND_PERIOD 4000 const MacAddress peer_mac({0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4}); ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE); while (!(WiFi.STA.started())) { delay(100); } NowSerial.begin(115200); } void loop() { static unsigned long lastSent = 0; if (millis() - lastSent > SEND_PERIOD) { int success = NowSerial.println("Hi Receiver, greetings from your peer!"); Serial.println("Message sent"); lastSent = millis(); if (!success) { Serial.println("Connection issue - rebooting in 3 seconds"); delay(3000); ESP.restart(); } } if (NowSerial.available()) { String messageIn = NowSerial.readStringUntil('\n'); Serial.print("Receiver feedback: "); Serial.println(messageIn); Serial.println(); } delay(1); }
Der Rückgabewerte von NowSerial.println()
ist die Anzahl der in den Puffer geschriebenen Bytes. Wenn dieser Wert Null ist, wird der ESP32 mit ESP.restart()
neu gestartet. Ich denke, der Sketch sollte ansonsten weitgehend selbsterklärend sein.
Receiver Sketch
#include "ESP32_NOW_Serial.h" #include "MacAddress.h" #include "WiFi.h" #include "esp_wifi.h" #define ESPNOW_WIFI_CHANNEL 1 const MacAddress peer_mac({0xC8, 0xC9, 0xA3, 0xCA, 0x22, 0x70}); ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE); while (!(WiFi.STA.started())) { delay(100); } NowSerial.begin(115200); } void loop() { if (NowSerial.available()) { Serial.print("Sender message: "); String messageIn = NowSerial.readStringUntil('\n'); Serial.println(messageIn); NowSerial.println("Thank you!"); } delay(1); }
Hier noch die Ausgaben:
Ihr könnt ja mal den Empfänger von der Stromversorgung trennen. Ihr werdet sehen, dass das Senderboard automatisch neu startet. Allerdings passiert das immer erst nach der zweiten erfolglos gesendeten Nachricht. Bei der ersten Nachricht fällt der Fehler noch nicht auf.
readStringUntil('\n');
sorgt übrigens dafür, dass der String sofort ausgewertet wird, wenn der Code beim Einlesen auf den Zeilenumbruch trifft. Wenn ihr stattdessen die Funktion readString()
verwendet, dann wartet der Code bis zum Timeout (Voreinstellung: 1000 Millisekunden), ob noch weitere Zeichen empfangen werden.
Mehr als zwei ESP-NOW Serial Netzwerkteilnehmer
Ihr wollt mehr als zwei ESP32 Boards miteinander kommunizieren lassen? Kein Problem. Alles, was ihr tun müsst, ist für jeden Empfänger einen eigenes ESP-NOW Serial Objekt erzeugen. Und wenn ihr Nachrichten an alle Netzteilnehmer versenden wollt, dann könnt ihr das mithilfe der Broadcast-Adresse FF:FF:FF:FF:FF:FF tun.
Die folgenden Sketche verdeutlichen anhand von drei Boards das Prinzip. In diesem speziellen Beispiel gibt es einen Sender und zwei Empfänger. Der Sender sendet im Abstand von drei Sekunden Nachrichten an Empfänger 1, dann Empfänger 2 und schließlich Empfänger 1 und 2.
Sender
Der Sendersketch sorgt für das Versenden der Nachrichten und wartet auf Empfangsbestätigungen. Sollte das Versenden von Nachrichten scheitern, gibt es einen Neustart. Um zu steuern, wer die nächste Nachricht bekommt, habe ich die Variable sendToReceiver eingeführt.
#include "ESP32_NOW_Serial.h" #include "MacAddress.h" #include "WiFi.h" #include "esp_wifi.h" #define ESPNOW_WIFI_CHANNEL 1 #define SEND_PERIOD 3000 const MacAddress peer_mac_1({0xC8, 0xC9, 0xA3, 0xCA, 0x22, 0x70}); // peer 1 const MacAddress peer_mac_2({0xC8, 0xC9, 0xA3, 0xC6, 0xFE, 0x54}); // peer 2 const MacAddress all_peers_mac({0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); // broadcast ESP_NOW_Serial_Class NowSerial1(peer_mac_1, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); ESP_NOW_Serial_Class NowSerial2(peer_mac_2, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); ESP_NOW_Serial_Class NowSerial_all(all_peers_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); while (!(WiFi.STA.started())) { delay(10); } NowSerial1.begin(115200); NowSerial2.begin(115200); NowSerial_all.begin(115200); } void loop() { static unsigned long lastSent = millis(); static int sendToReceiver = 1; if (millis() - lastSent > SEND_PERIOD && sendToReceiver == 1) { int success = NowSerial1.println("Hello Receiver 1, greetings from your peer!"); lastSent = millis(); sendToReceiver = 2; checkSuccess(success); } if (millis() - lastSent > SEND_PERIOD && sendToReceiver == 2) { int success = NowSerial2.println("Hello Receiver 2, greetings from your peer!"); lastSent = millis(); sendToReceiver = 3; checkSuccess(success); } if (millis() - lastSent > SEND_PERIOD && sendToReceiver == 3) { NowSerial_all.println("Hi all, greetings from your peer!"); lastSent = millis(); sendToReceiver = 1; } if (NowSerial1.available()) { Serial.print("Receiver 1 feedback: "); String messageIn = NowSerial1.readStringUntil('\n'); Serial.println(messageIn); } if (NowSerial2.available()) { Serial.print("Receiver 2 feedback: "); String messageIn = NowSerial2.readStringUntil('\n'); Serial.println(messageIn); } delay(1); } void checkSuccess(int success) { if (!success) { Serial.println("Connection issue - rebooting in 3 seconds"); delay(3000); ESP.restart(); } }
Receiver
Die Sketche für die beiden Receiver sind identisch.
#include "ESP32_NOW_Serial.h" #include "MacAddress.h" #include "WiFi.h" #include "esp_wifi.h" #define ESPNOW_WIFI_CHANNEL 1 const MacAddress peer_mac({0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4}); ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE); while (!WiFi.STA.started()) { delay(10); } NowSerial.begin(115200); } void loop() { if (NowSerial.available()) { String messageIn = NowSerial.readStringUntil('\n'); Serial.println(messageIn); NowSerial.println("Message received"); } delay(1); }
Hier noch die Ausgaben:
Für komplexere Netzwerke mit vielen und / oder wechselnden Teilnehmern ist ESP-NOW Serial nur begrenzt geeignet. In solchen Fällen solltet ihr auf ESP-NOW zurückgreifen. Schaut dazu einmal in die Beispielsketche des ESP32 Boardpaketes (Datei → Beispiele → ESP_NOW). Insbesondere der Sketch ESP_NOW_Network ist sehr komfortabel, wenn auch etwas schwer verdaulich für den Durchschnitts-User.
Strukturen mit ESP-NOW Serial senden
Zum Abschluss möchte ich noch zeigen, wie ihr mit ESP-NOW Serial Strukturen versendet. Strukturen bieten sich als „Datencontainer“ an, wenn ihr verschiedene Datentypen in einer Nachricht versenden wollt. Dafür bemühe ich – mal wieder – das Beispiel einer Wetterstation, die die Luftfeuchte, die Temperatur und den Regenstatus versendet.
Transmitter
#include "ESP32_NOW_Serial.h" #include "MacAddress.h" #include "WiFi.h" #include "esp_wifi.h" #define ESPNOW_WIFI_CHANNEL 1 #define SEND_PERIOD 4000 const MacAddress peer_mac({0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4}); ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); struct weatherData { int humidity; float temperature; bool rain; }; weatherData currentWeather = { 32, 25.0, false }; void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE); while (!(WiFi.STA.started())) { delay(100); } NowSerial.begin(115200); } void loop() { static unsigned long lastSent = 0; if (millis() - lastSent > SEND_PERIOD) { int success = NowSerial.write((byte*)¤tWeather, sizeof(currentWeather)); if (success) { Serial.println("Message sent"); } else { Serial.println("Connection issue - rebooting in 3 seconds"); delay(3000); ESP.restart(); } lastSent = millis(); } if (NowSerial.available()) { String messageIn = NowSerial.readStringUntil('\n'); Serial.print("Receiver feedback: "); Serial.println(messageIn); Serial.println(); } delay(1); }
Receiver
#include "ESP32_NOW_Serial.h" #include "MacAddress.h" #include "WiFi.h" #include "esp_wifi.h" #define ESPNOW_WIFI_CHANNEL 1 const MacAddress peer_mac({0xC8, 0xC9, 0xA3, 0xCA, 0x22, 0x70}); ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); struct weatherData { int humidity; float temperature; bool rain; }; weatherData currentWeather = { 0, 0.0, false }; void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE); while (!(WiFi.STA.started())) { delay(100); } NowSerial.begin(115200); } void loop() { if (NowSerial.available()) { NowSerial.readBytes((byte*)¤tWeather, sizeof(currentWeather)); Serial.print("Humidity [%] : "); Serial.println(currentWeather.humidity); Serial.print("Temperature [°C]: "); Serial.println(currentWeather.temperature, 1); if (currentWeather.rain) { Serial.println("It's raining.\n"); } else { Serial.println("It doesn't rain.\n"); NowSerial.println("Thank you!"); } } delay(1); }
Hier noch die Ausgaben:
Danksagung
Den Zettel mit dem „NOW“ auf dem Beitragsbild habe ich S K auf Pixabay zu verdanken.
Moin, ist denn über diesen Weg esp8266 -> ESP32 wieder möglich?
Mir ist es – wie hier erwähnt: https://wolles-elektronikkiste.de/esp-now#mix_esp32_esp8266 – nicht gelungen.
Hallo Wolfgang
Vielen Dank für deine Unterstützung.
Das hat mir jetzt keine Ruhe gelassen. Den selben Versuch habe ich heute auch durchgeführt. Und siehe: ohne Probleme.?? Dann habe ich den andern( älteren) ESP mit deinem Programm getestet. Nach spätestens 5 bis 10 resrart blieb er hängen. Der ältere ESP 32 ist ca.6 Jahre alt. Der der immer funktioniert maximal 1 Monat. Zumindest habe ich ihn vor 1 Monat gekauft.
Ich denke das hier im Betriebssystem änderungen vorgenommen wurden. Beide wurden mit der selben Arduino Version programmiert. Auffällig ist das ein Upload zum ESP bei dem älteren viel langsamer ist. Ich musste die Upload Geschwindigkeit beim wechseln der ESP jedesmal ändern. 115200 512000 512000 ist bei mir normal.
VG, Veit
Hallo Veit, interessantes Phänomen, das mir noch nicht über den Weg gelaufen ist. Interessieren würde mich noch, ob der Neustart mit ESP.deepSleep(0) mit den alten ESP32 besser funktioniert als ESP.restart(). Ich würde es selber ausprobieren, aber ich habe keinen ESP32, der dieses Phänomen zeigt. Wäre ggf. nützlich für Leute, die dasselbe Problem haben.
VG, Wolfgang
Hallo Wofgang
Mein Test hat doch etwas länger gebraucht.
Ich habe wie von dir angesprochen den alten ESP mit deepSleep(0) probiert. Ich habe ihn ca 5 Std laufen gelassen. Ohne Hänger. Dann habe ich noch einen zweiten „alten“ ESP aus ein vorhandenes Projekt ausgebaut und den selben Test unterzogen. Einmal mit deepSleep und einmal mit restart. Auch hier blieb der alte ESP mit restart nach 5-10 mal hängen. Und mit deepSleep nicht. Aber 2 Dinge passieren. Nach einer Zeit, ich war nicht zu Haus ist einer doch hängen geblieben. Mindestens hat er 8 Stunden durchgehalten. Ich hatte alle gleichzeitig jeweils dem selben Test ausführen lassen. Ein alter ESP ist doch mit deepSleep stehen geblieben.?? Der zweite Punkt ist das beide alten ESP recht warm (Heiß) geworden sind mit den kleinen Programm. Mit Infrarot gemessene 70 Grad am Blech.
Der neue überhaupt nicht. Vielleicht liegt hier die Antwort.
Ich werde den Alten wie gehabt über ein GPIO resetten lassen. Das ist mir sicherer. Der andere Alte braucht so etwas nicht. Zukünftig wenn ein restart benötigt wird werde ich bei einem neuen ESP mit deepSleep arbeiten. Vielen Dank für deine Unterstützung.
VG. Veit
Vielen Dank für das Update, interessant!
Hallo Wofgang
Wie Immer ein verständlicher, guter Beitrag. Ich habe damit jetzt einen seriellen Datentransfer mit einer Wetterstation 🙂 zum laufen gebracht.
Meine Frage dazu ist der Befehl „ESP.restart“ .In einem anderen Projekt hole ich nach einem restart von einem NTP Server die aktuelle Zeit ab. Der restart Befehl funktionierte 5 – 10 mal. danach blieb der ESP 32 hängen und es musste ein Hardware Reset manuell gemacht werden. Ein „Selbst Reset“ von einem Ausgang war dann die Lösung.
Ist dir auch diese Problematik bekannt.
Hallo Veit,
ich habe eben diesen kurzen Sketch hunderte Male durchlaufen lassen:
Er tut zuverlässig, was er soll. Allerdings gibt es bei ESP.restart() eine Stolperfalle, und zwar, dass kein vollständiger Reset durchgeführt wird. Du kannst das sichtbar machen, indem du die Zeile digitalWrite(18, LOW) auskommentierst. In dem Fall bleibt die LED dauerhaft an, sprich der GPIO-Zustand bleibt während des Neustarts erhalten. Es könnte deshalb dazu kommen, dass du mit Bedingungen neu startest, die für andere Bauteile, die du ggf. verwendest, unverträglich sind. Vielleicht ist ein solches Problem bei dir gewesen?
Eine Alternative zu ESP.restart() ist ESP.deepSleep(0). Also: gehe für 0 Sekunden in den Tiefschlaf und führe dann einen Reset durch. Wenn du ESP.restart() und digitalWrite(18, LOW) auskommentierst und stattdessen ESP.deepSleep(0) entkommentierst, dann wirst du sehen, dass die LED kurz ausgeht. Der Reset ist alaso sauberer. Vielleicht wäre das eine Alternative zum Reset mithilfe eines GPIOs.
VG, Wolfgang