ESP-NOW

Über den Beitrag

In meinem Beitrag WLAN mit ESP8266 und ESP32 hatte ich gezeigt, wie ihr ESP-Boards über WLAN miteinander kommunizieren lasst und wie ihr sie über den Browser eures PCs oder Smartphones steuert. Wenn ihr jedoch einfach nur Daten zwischen zwei oder mehreren ESP-Boards austauschen wollt, dann gibt es eine einfachere Methode und die heißt ESP-NOW.

Ich werde schrittweise erklären, wie ihr eure ESP32- oder ESP8266-basierten Boards mit ESP-NOW als Receiver, Transmitter oder Transceiver einsetzt. Dabei verwende ich primär den ESP32 als Anschauungsobjekt. Zum Schluss zeige ich aber auch, wie ihr den Code für ESP8266-Boards modifiziert.

Folgendes kommt auf euch zu:

Einführung / Vorbereitungen (ESP32 und ESP8266)

ESP-NOW ist ein Protokoll, mit dem ihr Nachrichten von bis zu 250 Bytes zwischen bis zu zwanzig ESP32- oder ESP8266-Boards austauschen könnt. ESP-NOW ist sehr flexibel. Es erlaubt euch, jedes Board als Transmitter, Receiver oder Transceiver einzurichten und Nachrichten an einzelne oder mehrere Mitglieder eures Netzes zu senden. Dabei ist es auch problemlos möglich, ESP32- und ESP8266-Boards zu mischen.

Um ESP-NOW nutzen zu können, müsst ihr nichts zusätzlich installieren. Die benötigten Bibliotheken sind Teil der „Standardausstattung“ der ESP32- und ESP8266-Pakete.  

MAC-Adresse ermitteln

Die Mitglieder eines Netzwerkes müssen eindeutig identifizierbar sein. Bei ESP-NOW wird dazu die MAC-Adresse eurer ESP-Boards verwendet, die ihr mit dem folgenden kleinen Sketch auslesen könnt:

#include <WiFi.h> // for ESP32 boards
//#include <ESP8266WiFi.h> // for ESP8266 boards

void setup(){
  Serial.begin(115200);
  // delay(1000); // uncomment if your serial monitor is empty
  Serial.print("MAC-Address: ");
  Serial.println(WiFi.macAddress());
}
 
void loop(){}

Ein paar Erklärungen zum Sketch

Um die WiFi-Funktionen nutzen zu können, müsst ihr für ESP32-basierte Boards WiFi.h einbinden. Für ESP8266-basierte Boards bindet ihr ESP8266WiFi.h ein.

Die MAC-Adresse besteht aus sechs Bytes. Üblicherweise werden die Bytes im Hexadezimalsystem angegeben und durch Doppelpunkte getrennt, also z.B. 94:3C:C6:33:68:98.

Ihr müsst also von jedem Board, das an eurem ESP-NOW Netzwerk teilnehmen soll, die MAC-Adresse ermitteln und aufschreiben. Es empfiehlt sich, die Boards in irgendeiner Weise zu kennzeichnen, um nicht durcheinanderzukommen.

Hinweis: Wenn ihr eure Sketche auf ESP-Boards ladet, dann kann es passieren, dass die ersten Serial.print() Anweisungen beim ersten Programmdurchlauf „verschluckt“ werden. Ein while(!Serial) nach Serial.begin(), das für bestimmte Boards empfohlen wird, beseitigt das Problem (sofern ihr es habt) nicht. Deswegen findet ihr in meinen Sketchen ein auskommentiertes delay(), das ihr ggf. entkommentieren könnt. 

MAC-Adresse ändern

Falls es euch zu mühsam ist, alle MAC-Adressen auszulesen, dann könnt auch einfach eine neue festlegen. Die folgenden beiden Sketche zeigen, wie ihr das für den ESP32 und den ESP8266 bewerkstelligt.

ESP32

#include <WiFi.h>
#include <esp_wifi.h>

const uint8_t newMacAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x01}; // customize as you wish  

void setup(){
      Serial.begin(115200);
      //delay(1000); // uncomment if your serial monitor is empty
      Serial.print("Default ESP Board MAC Address: ");
      Serial.println(WiFi.macAddress());
      
      WiFi.mode(WIFI_STA);
      esp_wifi_set_mac(WIFI_IF_STA, newMacAddress);
      
      Serial.print("New ESP Board MAC Address:     ");
      Serial.println(WiFi.macAddress());     
}
 
void loop(){}

Ein paar Erklärungen:

  • Wir verwenden in diesem Beispiel eine Funktion aus der ESP-API (Application Programming Interface). Diese Funktionen erkennt ihr daran, dass sie mit „esp_“ beginnen. Um Zugriff darauf zu haben, müsst ihr die entsprechenden Bibliotheken einbinden. Hier ist das esp_wifi.h.
  • Die MAC-Adresse wird als Array (uint8_t) definiert.
  • Ein ESP-Board kann sich entweder in ein Netzwerk einklinken (Station Modus) oder selbst als Zugangspunkt dienen (Access Point Modus). Wir stellen für ESP-NOW mit WiFi.mode(WIFI_STA) den Station Modus ein.
  • Mit esp_wifi_set_mac() legt ihr die MAC-Adresse fest. Die Funktion erwartet zwei Argumente. Das erste ist die verwendete Schnittstelle (Interface). Da wir im Station Modus arbeiten, wählen wir das Station Interface ( = WIFI_IF_STA). Das zweite Argument ist die neue MAC-Adresse.
    • Die Dokumentation zur esp_wifi_set_mac() Funktion findet ihr hier.
  • Das Bit 0 des ersten Byte der MAC-Adresse muss 0 sein, oder einfacher ausgedrückt: Das erste Byte muss eine gerade Zahl sein. Sonst gibt es diesbezüglich meines Wissens keine weitere Einschränkung.
  • Die Änderung der MAC-Adresse durch esp_wifi_set_mac() ist nicht permanenter Natur, d.h. der ESP „vergisst“ die Einstellung bei einem Reset.

Ich ändere die MAC-Adresse nur in einem der Beispielsketche. Wenn ihr die Option in den anderen Beispielen nutzen wollt, müsst ihr die Sketche entsprechend erweitern.

ESP8266

Und so sieht das Gegenstück für den ESP8266 aus:

#include <ESP8266WiFi.h>

uint8_t newMacAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x01}; // customize as you wish

void setup(){
      Serial.begin(115200);
      // delay(1000); // uncomment if your serial monitor is empty
      Serial.print("Default ESP Board MAC Address: ");
      Serial.println(WiFi.macAddress());
      
      WiFi.mode(WIFI_STA);
      wifi_set_macaddr(STATION_IF, newMacAddress);
      
      Serial.print("New ESP Board MAC Address:     ");
      Serial.println(WiFi.macAddress());     
}
 
void loop(){}

Übersicht der wichtigsten ESP-NOW Funktionen (ESP32 und ESP8266)

Um eure Boards per ESP-NOW kommunizieren zu lassen, benötigt ihr nur wenige Funktionen, die ich hier in einer Übersicht zusammengefasst habe:

Die wichtigsten ESP-NOW Funktionen im Überblick
Die wichtigsten ESP-NOW Funktionen im Überblick

Ein vollständige Auflistung der Funktionen findet ihr hier in der API-Doku für ESP-NOW.

Ein Transmitter, ein Receiver – Bare Minimum (ESP32)

Da ESP-NOW für Einsteiger ein wenig verwirrend sein kann, starten wir mit einem Minimalbeispiel, auf dem wir dann weiter aufbauen. In diesem Beispiel übernimmt ein Board die Rolle des Transmitters, das andere dient als Receiver. Die Nachricht besteht nur aus Text.

Und noch einmal der Hinweis: Der Code ist ESP32-spezifisch. Zur Übertragung auf den ESP8266 komme ich am Ende des Beitrages.

Transmitter

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

uint8_t receiverAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x98};
esp_now_peer_info_t peerInfo;

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    esp_now_init();
    
    memcpy(peerInfo.peer_addr, receiverAddress, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;

    esp_now_add_peer(&peerInfo);
}
 
void loop(){
    char message[] = "Hi, this is a message from the transmitting ESP";
    esp_now_send(receiverAddress, (uint8_t *) message, sizeof(message));
    delay(5000);
}

Erklärungen:

  • Mit #include<esp_now.h> binden wir die ESP-NOW Bibliothek ein.
  • peerInfo enthält Informationen über das Modul, mit dem wir kommunizieren wollen. Dabei handelt es sich um eine Struktur vom Typ esp_now_peer_info_t. Die Dokumentation findet ihr hier. Für uns sind die folgenden Elemente der Struktur relevant:
    • peer_addr enthält die MAC-Adresse des Empfängermoduls.
      • Eine Zuordnung peerInfo.peer_addr = receiverAddress ist nicht möglich, da man Arrays nicht so ohne Weiteres kopieren kann. Deswegen der Umweg über memcpy().
    • channel ist der WLAN-Kanal. Ihr könnt die Kanäle 1 – 13 wählen. Bei „0“ greift die Voreinstellung, nämlich „1“. Wie ihr den WLAN-Kanal eures Moduls ändert, beschreibe ich am Ende des Beitrages.
    • encrypt gibt an, ob ihr die Nachricht verschlüsseln wollt. Darauf gehe ich nicht ein.
  • esp_now_init() initialisiert ESP-NOW.
  • esp_now_add_peer(&peerInfo) fügt das ESP-Modul, das ihr zuvor mit peerInfo definiert habt, zum Netzwerk des aktuellen Moduls hinzu. 
  • esp_now_send() sendet eine Nachricht. Ihr übergebt der Funktion die MAC-Adresse des Empfängers, die Nachricht und ihre Länge. Die Nachricht ist vom Datentyp uint8_t und wird in esp_now_send() als Zeiger übergeben. Entsprechend muss message noch explizit mittels (uint8_t *) umgewandelt werden.

Receiver

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

void messageReceived(const uint8_t* macAddr, const uint8_t* incomingData, int len){
    for(int i=0; i<len; i++){
        Serial.print((char)incomingData[i]);
    }
    Serial.println();
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    esp_now_init();
    esp_now_register_recv_cb(messageReceived);
}
 
void loop(){}

Auch hierzu einige Erklärungen:

  • Um Nachrichten nur zu empfangen, müsst ihr das sendende Modul nicht als „Peer“ hinzufügen.
  • Die einzige neue Funktion ist esp_now_register_recv_cb(). Mit ihr meldet ihr eine Funktion an (hier: messageReceived), die automatisch aufgerufen wird, wenn eine Nachricht eingeht. Das „cb“ seht dabei für „call back“. Da die Funktion automatisch aufgerufen wird, kann loop() leer bleiben. Das Prinzip kennt ihr von Interrupt Service Routinen.
  • Die Parameter der aufzurufenden Funktion sind die MAC-Adresse des Absenders, die Nachricht selbst und die Länge der Nachricht.
  • Da wir einen Text verschickt haben, die empfangene Nachricht aber vom Datentyp uint8_t ist, müssen wir sie explizit in char zurück umwandeln. 

Ausgabe auf dem seriellen Monitor

Wenn alles gut gegangen ist, dann sollte die Nachricht des Transmitters alle fünf Sekunden erneut auf dem seriellen Monitor des Empfängermoduls angezeigt werden.

Ausgabe von receiver_bare_minimum.ino

Ein Transmitter, ein Receiver – Advanced (ESP32)

Die „Bare Minimum“ Sketche sind noch nicht besonders komfortabel. Es gibt keine Fehlermeldungen, wenn etwas schiefläuft, keine Sendebestätigung und keine Anzeige der MAC-Adresse des Senders. Außerdem haben wir bisher nur einfachen Text gesendet. In der Praxis wird man eher Daten, wie beispielsweise Sensorwerte, übermitteln wollen. Diese Defizite werden wir jetzt beseitigen.

Transmittersketch

Zunächst zum Transmitter:

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

uint8_t receiverAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x98};
esp_now_peer_info_t peerInfo;

typedef struct message {
    char text[64];
    int intVal;
    float floatVal;
} message;

message myMessage; 

void messageSent(const uint8_t *macAddr, esp_now_send_status_t status) {
    Serial.print("Send status: ");
    if(status == ESP_NOW_SEND_SUCCESS){
        Serial.println("Success");
    }
    else{
        Serial.println("Error");
    }
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }
    
    esp_now_register_send_cb(messageSent);   

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

    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        Serial.println("Failed to add peer");
        return;
    }
}
 
void loop(){
    char textMsg[] = "Hi Receiver, here's my data for you: ";
    memcpy(&myMessage.text, textMsg, sizeof(textMsg));
    myMessage.intVal = 4242;
    myMessage.floatVal = 42.42;
    esp_err_t result = esp_now_send(receiverAddress, (uint8_t *) &myMessage, sizeof(myMessage));
    if (result != ESP_OK) {
        Serial.println("Sending error");
    }
    delay(5000);
}

 

Was ist anders im Vergleich zum „Bare Minimum“?

  • Als Format für unsere Nachricht verwenden wir eine Struktur (message). Damit sind wir sehr flexibel hinsichtlich der in der Nachricht enthaltenen Datentypen. In diesem Beispiel besteht die Struktur aus einem Character Array, einem Integer und einem Float. Für das Character Arrays müssen wir die maximal zu erwartende Länge spezifizieren.
  • Strukturen wie message sind abgespeckte Klassen. Mit message myMessage; erzeugt ihr daraus das Objekt myMessage.
  • Der Rückgabewert von esp_now_init() verrät uns, ob der Vorgang fehlerfrei abgeschlossen wurde. 
  • In gleicher Weise nutzen wir den Rückgabewert von esp_now_add_peer(). Wichtig: die Funktion prüft nicht, ob der Peer wirklich vorhanden bzw. erreichbar ist. Eine Fehlermeldung würdet ihr beispielsweise bekommen, wenn die maximale Anzahl Peers überschritten ist. Weitere Informationen findet ihr in der API-Doku.
  • Um das Array textMsg in das Array myMessage.text zu kopieren, müsst ihr aus den schon zuvor genannten Gründen memcpy() nutzen. Die anderen Elemente könnt ihr direkt zuordnen. 
  • Mit esp_now_register_send_cb(messageSent); registrieren wir die Funktion messageSent, die aufgerufen wird, wenn eine Nachricht versendet wurde. Auch hier sind die Parameter wieder vorgegeben.
  • Die Prüfung von status in der Funktion messageSent und die Prüfung von result als Rückgabewert von esp_now_send() mag als unnötige Verdopplung erscheinen, allerdings werden jeweils andere Kriterien geprüft. Für Details schaut in die API-Doku.

Receiversketch

Auch der Receiversketch lässt sich komfortabler gestalten:

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

typedef struct message {
    char text[64];
    int intVal;
    float floatVal;
} message;

message myMessage;

void messageReceived(const uint8_t* macAddr, const uint8_t* incomingData, int len){
    memcpy(&myMessage, incomingData, sizeof(myMessage));
    Serial.printf("Transmitter MAC Address: %02X:%02X:%02X:%02X:%02X:%02X \n\r", 
            macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);    
    Serial.print("Message: ");
    Serial.println(myMessage.text);
    Serial.print("Integer Value: ");
    Serial.println(myMessage.intVal);
    Serial.print("Float Value: ");
    Serial.println(myMessage.floatVal);
    Serial.println();
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }

    esp_now_register_recv_cb(messageReceived);
}
 
void loop(){}

 

Was ist hier anders im Vergleich zum „Bare Minimum“Sketch?

  • Wir implementieren dieselbe Struktur message wie im Transmittersketch.
  • Die eingehende Nachricht kopieren wir mit memcpy() in myMessage und können dann bequem auf die Elemente der Struktur myMessage zugreifen.
  • Wir lassen uns die MAC-Adresse des Transmitters anzeigen. Für die Formatierung (Hex-Zahlen, Doppelpunkte) sorgt die printf() Funktion.  

Ausgabe receiver_basic.ino

Ausgabe von receiver_basic.ino
Ausgabe von receiver_basic.ino

Mehrere Transmitter, ein Receiver (ESP32)

In der Praxis setzt man häufig mehrere Transmitter und einen Receiver ein. Ein typisches Anwendungsbeispiel wäre eine Wetterstation mit Sensoren an unterschiedlichen Orten, die ihre Daten an eine zentrale Station übermitteln.

Für diese Konfiguration benötigt ihr keine zusätzlichen ESP-NOW Funktionen. Die einzige Herausforderung ist die Zuordnung der eingehenden Daten zu den Transmittern. Naheliegend wäre ein Abgleich der Transmitter MAC-Adresse mit einer im Receiver-Sketch hinterlegten Liste. Allerdings ist eine Identitätsprüfung von Arrays recht rechenintensiv. Alternativ könnte die Nachricht des Transmitters einen Identifier enthalten.  

Ich habe mich für eine andere Methode entschieden. Alle am Netzwerk beteiligten ESP-Module bekommen eine neue MAC-Adresse. Dabei unterscheiden sich die MAC-Adressen lediglich im letzten Byte, das zugleich als Nummerierung dient. In meinem Beispiel kommen drei Transmitter zum Einsatz und dafür habe ich die folgenden MAC-Adressen vergeben:

  • Transmitter 0 Adresse = 94:3C:C6:33:68:00,
  • Transmitter 1 Adresse = 94:3C:C6:33:68:01,
  • Transmitter 2 Adresse = 94:3C:C6:33:68:02,
  • Receiver Adresse = 94:3C:C6:33:68:05

Transmitter Example

Hier der Sketch für den Transmitter 0:

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

uint8_t receiverAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x05};
uint8_t myAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x00};
esp_now_peer_info_t peerInfo;

typedef struct data {
    int humidity;
    float temperature;
} data;

data myMessage; 

void messageSent(const uint8_t *macAddr, esp_now_send_status_t status) {
    Serial.print("Send status: ");
    if(status == ESP_NOW_SEND_SUCCESS){
        Serial.println("Success");
    }
    else{
        Serial.println("Error");
    }
}

void setup(){
    Serial.begin(115200);
    delay(1000);
    WiFi.mode(WIFI_STA);
    esp_wifi_set_mac(WIFI_IF_STA, myAddress);
    Serial.print("New ESP Board MAC Address:  ");
    Serial.println(WiFi.macAddress());  
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }
    
    esp_now_register_send_cb(messageSent);   

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

    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        Serial.println("Failed to add peer");
        return;
    }
}
 
void loop(){
    myMessage.humidity = 42;
    myMessage.temperature = 16.9;
    esp_err_t result = esp_now_send(receiverAddress, (uint8_t *) &myMessage, sizeof(myMessage));
    if (result != ESP_OK) {
        Serial.println("Sending error");
    }
    delay(3000);
}

 

Als Daten versenden wir die Luftfeuchte und die Temperatur, die wir beispielsweise mit einem DHT22 ermittelt haben könnten. Die Daten werden in der Struktur data „verpackt“.

Receiver

Auf der Receiverseite kopieren wir die eingehenden Nachrichten erst in die „Hilfsstruktur“ stationMsg und übernehmen sie von da in das Array weatherStation[]. Der Zähler für die Elemente des Arrays ist dabei einfach die letzte Stelle der MAC-Adresse, also macAddr[5]:

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

uint8_t myAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x05};

typedef struct data {
    int humidity;
    float temperature;
} data;

data stationMsg;
data weatherStation[3] = {0};

void messageReceived(const uint8_t* macAddr, const uint8_t* incomingData, int len){
    memcpy(&stationMsg, incomingData, sizeof(stationMsg));
    weatherStation[macAddr[5]].humidity = stationMsg.humidity;
    weatherStation[macAddr[5]].temperature = stationMsg.temperature;
}

void setup(){
    Serial.begin(115200);
    delay(1000);
    WiFi.mode(WIFI_STA);
    esp_wifi_set_mac(WIFI_IF_STA, myAddress);
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }

    esp_now_register_recv_cb(messageReceived);
}
 
void loop(){
    for(int i=0; i<3; i++){
        Serial.print("Weather Station ");
        Serial.print(i);
        Serial.println(":");
        Serial.print("Humidity [%]    : ");
        Serial.println(weatherStation[i].humidity);
        Serial.print("Temperature [°C]: ");
        Serial.println(weatherStation[i].temperature,1);
        Serial.println();
    }
    Serial.println();
    delay(5000);   
}

 

Hier die Ausgabe:

Ausgabe von multi_transm_one_recv_receiver.ino

Ein Transmitter, mehrere Receiver (ESP32)

Genauso einfach ist es, einen Transmitter und mehrere Receiver zu vernetzen. Diese Konfiguration könnte beispielsweise bei Smart Home Anwendungen zum Einsatz kommen. Der Transmitter muss dabei alle MAC-Adressen der Peers, also der Receiver, vorhalten und sich mit allen verbinden.

Der Einfachheit und besseren Übersicht halber bekommen in meinem Beispiel alle Receiver dieselben Datentypen zugesendet, sodass wir nur eine Struktur definieren müssen. Bei Bedarf ließe sich das einfach ändern.

Transmitter

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

uint8_t receiverAddress[3][6] = {{0x94, 0x3C, 0xC6, 0x33, 0x68, 0x98},
                                 {0x78, 0x21, 0x84, 0xDE, 0x2E, 0x3C},
                                 {0x30, 0xAE, 0xA4, 0x7B, 0x79, 0x90}};
esp_now_peer_info_t peerInfo[3];

typedef struct message {
    char text[32];
    int intVal;
    float floatVal;
} message;

message myMessage[3]; 

void messageSent(const uint8_t *macAddr, esp_now_send_status_t status) {
    Serial.printf("Send status to receiver %02X:%02X:%02X:%02X:%02X:%02X : ", 
            macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);        
    if(status == ESP_NOW_SEND_SUCCESS){
        Serial.println("Success");
    }
    else{
        Serial.println("Error");
    }
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }
    
    esp_now_register_send_cb(messageSent);   

    for(int i=0; i<3; i++){
        memcpy(peerInfo[i].peer_addr, receiverAddress[i], 6);
        peerInfo[i].channel = 0;
        peerInfo[i].encrypt = false;

        if (esp_now_add_peer(&peerInfo[i]) != ESP_OK) {
            Serial.println("Failed to add peer");
            return;
        }
    }
}
 
void loop(){
    char textMsg0[] = "Hi Receiver 0!";
    memcpy(&myMessage[0].text, textMsg0, sizeof(textMsg0));
    myMessage[0].intVal = 4242;
    myMessage[0].floatVal = 42.42;
    
    char textMsg1[] = "Ciao Receiver 1";
    memcpy(&myMessage[1].text, textMsg1, sizeof(textMsg1));
    myMessage[1].intVal = 1234;
    myMessage[1].floatVal = 12.34;
    
    char textMsg2[] = "Hola Receiver 2";
    memcpy(&myMessage[2].text, textMsg2, sizeof(textMsg2));
    myMessage[2].intVal = 4711;
    myMessage[2].floatVal = 47.11;
    
    for(int i=0; i<3; i++){
        esp_err_t result = esp_now_send(receiverAddress[i], (uint8_t *) &myMessage[i], sizeof(myMessage[i]));
        if (result != ESP_OK) {
            Serial.print("Sending error module ");
            Serial.println(i);
        }
    }
     delay(10000);
}

 

Ich denke, der Code braucht keine weiteren Erläuterungen, oder?

Auch diesen Sketch könntet ihr etwas vereinfachen, indem ihr die MAC-Adressen der Receiver so ändert, dass sie sich nur im letzten Byte unterscheiden. Ihr braucht dann nur ein eindimensionales Array für die receiverAddress, und wenn ihr Receiver Nr. i ansprechen wollt, dann müsst ihr lediglich die Adresse mit receiverAddress[5] = 0x0i einstellen.

Vielleicht noch ganz nützlich: Wenn ihr in der Funktion esp_now_send() die Receiver-Adresse durch NULL (so ausgeschrieben, also nicht „0“) ersetzt, dann geht die Nachricht an alle angemeldeten Peers.

Ausgabe

So sieht die Ausgabe auf der Transmitterseite aus:

Ausgabe von one_transm_multi_recv_transmitter.ino
Ausgabe von one_transm_multi_recv_transmitter.ino

Receiver

Auch der Sketch für die Receiver sollte ohne weitere Erläuterungen verständlich sein:

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

typedef struct message {
    char text[32];
    int intVal;
    float floatVal;
} message;

message myMessage;

void messageReceived(const uint8_t* macAddr, const uint8_t* incomingData, int len){
    memcpy(&myMessage, incomingData, sizeof(myMessage));
    Serial.print("Message: ");
    Serial.println(myMessage.text);
    Serial.print("Integer Value: ");
    Serial.println(myMessage.intVal);
    Serial.print("Float Value: ");
    Serial.println(myMessage.floatVal);
    Serial.println();
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }

    esp_now_register_recv_cb(messageReceived);
}
 
void loop(){}

 

Ausgabe

Und hier noch beispielhaft die Ausgabe von Receiver 1:

Ausgabe von one_transm_multi_recv_receiver.ino
Ausgabe von one_transm_multi_recv_receiver.ino

ESP als Transceiver (ESP32)

Eine Konstellation fehlt noch, nämlich der Einsatz von ESP-Boards als Transceiver, also als kombinierten Sender und Empfänger. Die gute Nachricht ist, dass ihr dazu keine neuen Funktionen braucht. Ihr müsst auch nicht zwischen Empfangs- und Sendemodus umschalten o. ä. Nehmt einfach einen der Transmitter-Sketche und fügt die für einen Receiver benötigten Elemente hinzu (oder andersherum).

In meinem Beispiel dazu sendet ein Transceiver (der „Lead“) in bestimmten Abständen Daten an einen anderen Transceiver (der „Follower“), der sich dafür beim Lead-Transceiver bedankt und seine Laufzeit in Sekunden zurückmeldet. Macht zugegeben wenig Sinn, aber es soll ja auch nur das Prinzip veranschaulichen.

Den Lead-Transceiver habe ich so genannt, weil er in dieser Konstellation den Takt vorgibt. Genauso gut könnten sich die beiden Transceiver ihre Nachrichten gleichberechtigt und ereignisbasiert zusenden, z. B. in bestimmten Zeitabständen, bei einem bestimmten Sensorwert, beim Drücken eines Tasters oder was euch auch immer in den Sinn kommt.

Sketch für den „Lead-Transceiver“

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

uint8_t receiverAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x98};
esp_now_peer_info_t peerInfo;

typedef struct messageToBeSent {
    char text[64];
    int intVal;
    float floatVal;
} messageToBeSent;

typedef struct receivedMessage {
    char text[64];
    long runTime;
} receivedMessage;

messageToBeSent myMessageToBeSent; 
receivedMessage myReceivedMessage; 

void messageSent(const uint8_t *macAddr, esp_now_send_status_t status) {
    Serial.print("Send status: ");
    if(status == ESP_NOW_SEND_SUCCESS){
        Serial.println("Success");
    }
    else{
        Serial.println("Error");
    }
}

void messageReceived(const uint8_t* macAddr, const uint8_t* incomingData, int len){
    memcpy(&myReceivedMessage, incomingData, sizeof(myReceivedMessage));
    Serial.printf("Incoming Message from: %02X:%02X:%02X:%02X:%02X:%02X \n\r", 
            macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
    Serial.print("Message: ");
    Serial.println(myReceivedMessage.text);
    Serial.print("RunTime [s]: ");
    Serial.println(myReceivedMessage.runTime);
    Serial.println();
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }
    
    esp_now_register_send_cb(messageSent);  
    esp_now_register_recv_cb(messageReceived); 

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

    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        Serial.println("Failed to add peer");
        return;
    }
}
 
void loop(){
    char textMsg[] = "Hi, here's my data for you: ";
    memcpy(&myMessageToBeSent.text, textMsg, sizeof(textMsg));
    myMessageToBeSent.intVal = 4242;
    myMessageToBeSent.floatVal = 42.42;
    esp_err_t result = esp_now_send(receiverAddress, (uint8_t *) &myMessageToBeSent, sizeof(myMessageToBeSent));
    if (result != ESP_OK) {
        Serial.println("Sending error");
    }
    delay(5000);
}

 

Wie ihr seht – es ist einfach nur eine Kombi aus Transmitter- und Receiversketch.

Sketch für den „Following Transceiver“

Hier, auf der Follower-Seite, werden die Nachrichten entgegengenommen und mit einer Antwort quittiert.

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

uint8_t receiverAddress[] = {0x30, 0xAE, 0xA4, 0x7B, 0x79, 0x90};
esp_now_peer_info_t peerInfo;

typedef struct messageToBeSent{
    char text[64];
    long runTime;
} messageToBeSent;

typedef struct receivedMessage {
    char text[64];
    int intVal;
    float floatVal;
} receivedMessage;

messageToBeSent myMessageToBeSent; 
receivedMessage myReceivedMessage; 

void messageReceived(const uint8_t* macAddr, const uint8_t* incomingData, int len){
    memcpy(&myReceivedMessage, incomingData, sizeof(myReceivedMessage));
    Serial.printf("Incoming Message from: %02X:%02X:%02X:%02X:%02X:%02X \n\r", 
                macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
    Serial.print("Message: ");
    Serial.println(myReceivedMessage.text);
    Serial.print("Integer Value: ");
    Serial.println(myReceivedMessage.intVal);
    Serial.print("Float Value: ");
    Serial.println(myReceivedMessage.floatVal);
    Serial.println();
    Serial.println("Sending answer...");
    Serial.println(); 

    char textMsg[] = "Thanks for the data!";
    memcpy(&myMessageToBeSent.text, textMsg, sizeof(textMsg));
    myMessageToBeSent.runTime = millis()/1000;
    esp_err_t result = esp_now_send(receiverAddress, (uint8_t *) &myMessageToBeSent, sizeof(myMessageToBeSent));
    if (result != ESP_OK) {
        Serial.println("Sending error");
    }    
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }

    esp_now_register_recv_cb(messageReceived);
    
    memcpy(peerInfo.peer_addr, receiverAddress, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;

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

 

Ausgaben

Hier die Ausgabe für den Lead:

Ausgabe von transceiver_lead.ino

Und hier die Ausgabe für den Follower.

Ausgabe von transceiver_follower.ino
Ausgabe von transceiver_follower.ino

Ich spare mir ein Beispiel für das Vernetzen mehrerer Transceiver. Das solltet ihr problemlos aus den vorherigen Beispielen aufbauen können.

Übertragung der Sketche auf den ESP8266

Die ESP8266 Implementierung von ESP-NOW unterscheidet sich nicht gravierend von der Implementierung auf dem ESP32. Der Unterschied ist aber groß genug, dass vereinheitlichte Sketche (über Kommentierungen oder #ifdef ESP32...#else Konstruktionen) recht unübersichtlich geworden wären. 

Hier die wesentlichen Unterschiede:

  • Die Namen der einzubindenden Bibliotheken.
  • Die Parameter der Callback Funktionen. 
  • Für den ESP82866 muss seine Rolle als Receiver, Transmitter oder Transceiver mit esp_now_set_self_role() ausdrücklich festgelegt werden.
  • Rückgabewerte einiger Funktionen.

Als Beispiel habe ich die Transceiver-Sketche für den ESP8266 „übersetzt“.

Lead

#include <espnow.h>
#include "ESP8266WiFi.h"

uint8_t receiverAddress[] = {0xA4, 0xCF, 0x12, 0xDF, 0x5D, 0x89};

typedef struct messageToBeSent {
    char text[64];
    int intVal;
    float floatVal;
} messageToBeSent;

typedef struct receivedMessage {
    char text[64];
    long runTime;
} receivedMessage;

messageToBeSent myMessageToBeSent; 
receivedMessage myReceivedMessage; 

void messageSent(uint8_t *macAddr, uint8_t status) {
    Serial.print("Send status: ");
    if(status == 0){
        Serial.println("Success");
    }
    else{
        Serial.println("Error");
    }
}

void messageReceived(uint8_t* macAddr, uint8_t* incomingData, uint8_t len){
    memcpy(&myReceivedMessage, incomingData, sizeof(myReceivedMessage));
    Serial.printf("Incoming Message from: %02X:%02X:%02X:%02X:%02X:%02X \n\r", 
            macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);    
    Serial.print("Message: ");
    Serial.println(myReceivedMessage.text);
    Serial.print("RunTime [s]: ");
    Serial.println(myReceivedMessage.runTime);
    Serial.println();
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == 0) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }

    esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
    uint8_t result = esp_now_add_peer(receiverAddress, ESP_NOW_ROLE_COMBO, 0, NULL, 0);
    if(result != 0){
        Serial.println("Failed to add peer");
    }
    
    esp_now_register_send_cb(messageSent);  
    esp_now_register_recv_cb(messageReceived); 

   
}
 
void loop(){
    char textMsg[] = "Hi, here's my data for you: ";
    memcpy(&myMessageToBeSent.text, textMsg, sizeof(textMsg));
    myMessageToBeSent.intVal = 4242;
    myMessageToBeSent.floatVal = 42.42;
    esp_now_send(receiverAddress, (uint8_t *) &myMessageToBeSent, sizeof(myMessageToBeSent));
    delay(5000);
}

 

Follower

#include <espnow.h>
#include "ESP8266WiFi.h"

uint8_t receiverAddress[] = {0x44, 0x17, 0x93, 0x0E, 0x2E, 0xED};

typedef struct messageToBeSent{
    char text[64];
    long runTime;
} messageToBeSent;

typedef struct receivedMessage {
    char text[64];
    int intVal;
    float floatVal;
} receivedMessage;

messageToBeSent myMessageToBeSent; 
receivedMessage myReceivedMessage; 

void messageReceived(uint8_t* macAddr, uint8_t* incomingData, uint8_t len){
    memcpy(&myReceivedMessage, incomingData, sizeof(myReceivedMessage));
    Serial.printf("Incoming Message from: %02X:%02X:%02X:%02X:%02X:%02X \n\r", 
            macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
    Serial.print("Message: ");
    Serial.println(myReceivedMessage.text);
    Serial.print("Integer Value: ");
    Serial.println(myReceivedMessage.intVal);
    Serial.print("Float Value: ");
    Serial.println(myReceivedMessage.floatVal);
    Serial.println();
    Serial.println("Sending answer...");
    Serial.println(); 

    char textMsg[] = "Thanks for the data!";
    memcpy(&myMessageToBeSent.text, textMsg, sizeof(textMsg));
    myMessageToBeSent.runTime = millis()/1000;
    esp_now_send(receiverAddress, (uint8_t *) &myMessageToBeSent, sizeof(myMessageToBeSent));    
}    

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == 0) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }

    esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
    uint8_t result = esp_now_add_peer(receiverAddress, ESP_NOW_ROLE_COMBO, 0, NULL, 0);
    if(result != 0){
        Serial.println("Failed to add peer");
    }
    
    esp_now_register_recv_cb(messageReceived);
}
 
void loop(){}

 

ESP8266 und ESP32 mischen

Einer der Vorzüge von ESP-NOW ist, dass ihr in eurem Netzwerk ESP8266- und ESP32-Boards problemlos mischen könnt. Ihr könnt das testen, indem ihr beispielsweise den Lead-Transceiver Sketch für den ESP32 auf ein ESP32-Board ladet und den Follower-Transceiver Sketch für den ESP8266 auf ein ESP8266-Board. Lediglich die MAC-Adressen sind natürlich anzupassen.

Kanal wechseln (ESP32 und ESP8266)

Falls Ihr Reichweitenprobleme aufgrund der Wechselwirkung mit anderen WLAN Netzwerken habt, dann könnte ein Wechsel auf einen anderen Kanal helfen. Die folgenden zwei Sketche zeigen, wie ihr den Kanal für ein ESP32- und ein ESP8266-Board wechselt. Alle Teilnehmer in einem Netz müssen natürlich auf denselben Kanal eingestellt werden.

ESP32

#include <esp_now.h>
#include <esp_wifi.h>
#include <WiFi.h>
#define CHANNEL 13

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    Serial.print("WiFi-Channel Default: ");
    Serial.println(WiFi.channel());
    
    esp_wifi_set_promiscuous(true);
    esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE);
    esp_wifi_set_promiscuous(false);
    
    Serial.print("WiFi-Channel Update : ");
    Serial.println(WiFi.channel());
}
     
void loop(){}

ESP8266

#include <espnow.h>
#include "ESP8266WiFi.h"
#define CHANNEL 13

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    Serial.print("WiFi-Channel Default: ");
    Serial.println(WiFi.channel());
    
    wifi_promiscuous_enable(true);
    wifi_set_channel(CHANNEL);
    wifi_promiscuous_enable(false); 

    Serial.print("WiFi Channel Update : ");
    Serial.println(WiFi.channel());
}
 
void loop(){}

Reichweite

Wenn ihr nach Reichweitentests für ESP-NOW googelt, werdet ihr auf Maximalwerte zwischen 100 und 250 Meter stoßen, sofern die Versuche im Freien, ohne Hindernisse und ohne externe Antennen durchgeführt wurden. Wie weit ihr in eurer Wohnung oder eurem Haus kommt, hängt vor allem von der Bausubstanz ab.

Bei meinen eigenen Reichweitentests kamen jeweils zwei Wemos D1 Mini Boards und zwei ESP32-WROOM-32 basierte Development Boards zum Einsatz. Beide Paarungen erzielten identische Reichweiten. Im Nachbarzimmer, also durch eine Wand, gingen mir keine Nachrichten verloren. Ein Zimmer weiter, also durch zwei Wände, hatte ich nur noch in einigen Bereichen Empfang. Das ist ungefähr vergleichbar mit der Reichweite meiner Fritz!Box 7590.

Falls euch die Reichweite nicht ausreichen sollte, könntet ihr ein Modul als Repeater zwischen die zu verbindenden Module setzen. Oder ihr greift zu Modulen, die es erlauben, externe Antennen anzuschließen.

Danksagung

Den Zettel mit dem „NOW“ auf dem Beitragsbild habe ich S K auf Pixabay zu verdanken.

42 thoughts on “ESP-NOW

  1. „bis zu zwanzig ESP32- oder ESP8266-Boards“
    https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/kconfig.html#config-esp-wifi-espnow-max-encrypt-num

    Spricht prinzipiell irgendetwas dagegen, mehrere ESPs mit der gleichen MAC-Adresse auszustatten und statt dessen zusätzlich ein ID-Byte mitzusenden, um die Anzahl möglicher ESPs im Netz zu erhöhen?

    Es erfordert natürlich noch eine zusätzliche Identifikationsroutine auf beiden Seiten aber damit ließen sich dann ESPs in entsprechend vielen Gruppen ansprechen, bzw. abfragen (z.B. bei Sensoren/Aktoren).

    1. Hallo, ich weiß nicht, ob das funktioniert. Insbesondere beim „add peer“ Prozess könnte es schiefgehen. An deiner Stelle würde ich es einfach mal mit drei Teilnehmern, wobei zwei dieselbe Adresse haben, ausprobieren. Das Ergebnis würde mich interessieren. Viel Erfolg!

        1. Ich schaue es mir bis zum Wochenedende mal und schreib dann das Ergebnis hier rein. Vorerst danke 😉

          1. „If broadcast packets are used, theoretically there is no limitation in the number of devices that can be controlled.“

            Ja, man kann an FF:FF:FF:FF:FF:FF senden und dann empfangen alle ESPs das Paket. Ein Beispiel, um Heruaszufinden, wie man den WiFi-Kanal abgleicht, ist hier zu finden:
            https://randomnerdtutorials.com/esp-now-auto-pairing-esp32-esp8266/

            Prinzipiell ist dies ja genau das, nur eben mit einer Adresse die alle ESPs kennen. Ob es auch mit einer anderen Adresse geht, die nur einer Gruppe von ESPs bekannt ist, teste ich.

  2. Hallo Wolfgang,
    ich habe deine Minimalversion angepasst an ESP8266.
    Wozu:
    mein Projekt wird eine Solarmodulverwaltung:
    Am Zähler im Keller ist ein Shelly 3EM. Der funkt die Daten der 3 Phasen
    an einen ESP12. Der befindet sich unter dem Dach. Der mißt auch den Strom von den Solarmodulen zu 2 Invertern.
    Er entscheidet und schaltet dann über 6 Mosfets entsprechend viele Solarmodule
    zu oder ab. Damit ich sehe, wie er funzt, will ich, daß er sein Ergebnis über seriell einem ESP-01 liefert. Dieser soll es an einen weiteren ESP-01 an meinem Schreibtisch funken, der es dann über seriell an den Monitor weitergibt.
    Also kurz: Ich will ein serielles Kabel einsparen und durch 2 ESP-01 ersetzen.
    Alles soweit ok, außer:
    Es gelingt mir nicht, den seriell eingelesenen String in esp_now_send-gerechten Zustand zu bringen.
    Mein Sketch:

    /*
    Liest seriell den kompletten DatensatzString vom ESP12
    und sendet den über WLAN an einen ESP01, der ihn
    seriell an den Monitor weitergibt.
    //***********************************************
    */

    // Generic ESP8266 Modul

    #include
    #include

    // REPLACE WITH RECEIVER MAC Address (ESP01:E1)
    //uint8_t broadcastAddress[] = { 0xDC, 0x4F, 0x22, 0x26, 0x28, 0xA1 };//E2
    //uint8_t broadcastAddress[] = { 0xC8, 0x2B, 0x96, 0x20, 0x85, 0x33 };//E5
    // uint8_t broadcastAddress[] = { 0x48, 0x3F, 0xDA, 0x4A, 0x27, 0x27 };//Eb

    uint8_t receiverAddress[] = {0x48, 0x3F, 0xDA, 0x4A, 0x27, 0x27};//Eb

    char myData [100];
    String inputString ;
    String message ;
    //char message [100];
    //*************************************

    void setup()
    {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);

    esp_now_init();
    esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
    esp_now_add_peer(receiverAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
    }

    //*************************************

    void loop()
    {
    readData();
    // char message[100] ;

    memcpy(&myData, &message, sizeof( message));

    // char message[] = „Hi, this is a message from the transmitting ESP“;
    esp_now_send(receiverAddress, (uint8_t *) myData, sizeof(myData));
    //Serial.println (message);
    delay(1000);
    }

    //*********************************

    void readData()
    {
    while (Serial.available())
    {
    char inChar = (char)Serial.read();
    if (inChar == ‚\n‘)
    {
    message = inputString;
    inputString = „“;
    }
    else
    {
    inputString += inChar;
    }
    }
    }

    Was mach ich falsch?
    mit viel Repekt vor deiner SITE,
    Helmut

    1. Hallo Helmut,
      das roblem, das ich sehe ist, dass du der memcpy() Funktion einen String übergibst bzw. die Adresse des Strings. Strings sind aber recht eigen. An ihren Adressen (im Stack) stehen nicht die Zeichenketten, sondern nur ein Verweis auf die Zeichenkette, die sich im Heap befinden. Siehe auch hier: https://wolles-elektronikkiste.de/sram-management#variable_storage_in_sram.

      Du müsstest deinen String also in eine Character Array umwandeln. Und da du nur die seriell erhaltenen Daten weiterleiten willst, brauchgst du auch den Weg über memcpy() nicht (siehe Beispiel transmitter_bare_minimum.ino).

      Vielleicht geht es so:

      String meinString = "";
      ........
      if(Serial.available()){
          meinString = Serial.readString();
          Serial.println(meinString); // nur zur Überprüfung
          esp_now_send(receiverAddress, (uint8_t *) meinString.c_str(), sizeof(message)+1);
        }
      

      Ich kann das so im Moment nicht ausprobieren. Dafür müsste ich zu viel aufbauen.

      VG, Wolfgang

      1. Danke, das funzt weitgehend.
        Das eben ist für mich noch das Problem. Ich kenne die Eigenschaften der verschiedenen Datenformate (noch) nicht. In meinem Datensatz gibt es eine Gleitkommazahl mit einem Punkt.
        Genau dort bricht der Datensatz jetzt ab.
        Aber ich bin Dank deiner Hilfe jetzt soweit, daß der Anfang des Datensatzes schon mal lesbar ankommt. Ich bin zuversichtlich!!

        Du hast mir sehr geholfen, vielen Dank,
        Helmut

        1. Hat nix mit dem Punkt zu tun!
          Sind einfach nur die ersten 13 Zeichen meines Strings, krieg ich aber!
          Gruß
          Helmut

      2. Hab das so eingepflegt:
        … esp_now_send(receiverAddress, (uint8_t *) meinString.c_str(), sizeof(meinString)+1);
        Serial.println(meinString); // nur zur Überprüfung
        Serial.println(sizeof(meinString)); ……

        aber wenn ich den String und dessen Länge ausdrucke, kommt der String komplett, aber die Länge nur mit 12 !??

        Wie kommt sowas?
        (.. was war BASIC doch so einfach 🙁 ..)
        Gruß
        Helmut

        010100 520 0.94 10 444 336 111 67 266
        12

        1. Sorry, mein Fehler: sizeof(meinString) war Blödsinn, meinString.length() gibt die Länge aus. Aber das „+1“ muss noch mit rein, weil das übergebene Character Array noch den Null-Terminator enthält. Dass sizeof für einen String auf einem ESP32 den Wert 12 liefert, ist, dass das tatsächlich die Größe des eigentlichen String Objektes ist. Das String-Objekt liegt im Stack und enthält einen Verweis zur eigentlichen Zeichenkette, die im Heap liegt. Das gilt wiederum nur für Strings mit einer Länge >10.
          Strings sind etwas eigentümliche Gebilde und C-Experten raten vom Gebrauch von Strings ab. Stattdessen sollte man mit C-Strings bzw. Character Arrays arbeiten. Allerdings sind Character Arrays nicht so komfortabel. Deswegen greife auch ich gerne auf sie zurück.

      3. Hallo Wolfgang,
        jetzt läuft das alles, aber mit einem Schönheitsfehler.
        Weil ich einen ganzen String einlese, fummelt der Sketch an dem Datensatz herum mit „Null“,CR,LF…
        Wär doch schlauer, nur ein Byte einzulesen und das sofort weiterzufunken. Die CR und LF würden beim Empfänger nur ankommen, wenn sie auch im ursprünglichen „Strang“ erhalten sind.
        Spricht was dagegen ? / Timing?…
        Gruß
        Helmut

        1. Hallo Helmut, im Prinzip sollte es auch möglich sein, jedes Zeichen einzeln zu versenden. Dauert nur länger. Klingt mehr nach einer Notlösung. Ich kann nicht wirklich nachvollziehen, warum das notwendig ist, bzw. warum auf dem Wege vom seriellen Einlesen zum Versenden Formatierungszeichen hinzukommen. Wenn man ESP-Now ein Character Array zum Versenden gibt, dann dichtet es eigentlich auch nichts hinzu. Ich würde eher vermuten, dass noch irgendetwas auf dem Wege zwischen Einlesen der seriellen Daten und der Erstellung des Character Arrays falsch läuft.

          VG, Wolfgang

        2. Hallo Helmut, ich habe jetzt noch einmal selbst herumprobiert. Dazu habe ich den Sketch receiver_bare_minimum, so gelassen, wie er ist und dem transmitter_bare_minium habe ich eine eine Erweiterung spendiert, die N:achrichten über den seriellen Monitor einliest und an den Receiver weiterleitet.

          So geht es bei mir, ohne dass Formatierungszeichen durchkommen:

          #include <esp_now.h>
          #include <WiFi.h>
          
          uint8_t receiverAddress[] = {0xC8, 0xC9, 0xA3, 0xC6, 0xFE, 0x54};
          esp_now_peer_info_t peerInfo;
          
          void setup(){
              Serial.begin(115200);
              delay(1000); // uncomment if your serial monitor is empty
              WiFi.mode(WIFI_STA);
              esp_now_init();
              memcpy(peerInfo.peer_addr, receiverAddress, 6);
              peerInfo.channel = 0;
              peerInfo.encrypt = false;
              esp_now_add_peer(&peerInfo);
          }
           
          void loop(){
              if(Serial.available()){
                  String meinString = Serial.readString();
                  Serial.println(meinString); // nur zur Überprüfung
                  esp_now_send(receiverAddress, (uint8_t *) meinString.c_str(), meinString.length());
              }
          }
          

          Alternativ, und ein wenig effektiver, da ohne String-Objekte, ist die folgende loop() Schleife (Rest gleich).

          void loop(){
              if(Serial.available()){
                  char message[50];
                  int messageSize = Serial.readBytesUntil('\n', message, 50); // setzt voraus, dass ein Zeilenumbruch mit geliefert wird. 
                  Serial.println(message);
                  Serial.println(messageSize);
                  esp_now_send(receiverAddress, (uint8_t *) message, messageSize);
              }
          }
          

          Vielleicht hilft das ja.

          VG, Wolfgang

  3. Hallo Wolfgang

    Bin an den kleinen feinen Unterschieden vor einem Jahr gescheitert.
    Jetzt die Lösung……

    Super

    Danke

  4. Hallo Wolfgang, hallo an alle Wissenden und Fragenden,

    wie ist das mit ESP-NOW auf Empfängerseite ? Wird bei jedem Empfang ein Interrupt ausgelöst bzw. das Empfangen (Aufruf der Callback Funktion) per Interrupts realisiert ? Wenn dem so ist, frage ich mich, wie ich den Empfänger noch zum normalen „Arbeiten“ bringen kann, wenn der Sender zyklisch am Stück Daten sendet, z.B. irgendwelche Messdaten, die kontinuierlich anstehen. Ich habe mal auf Empfängerseite ein Monitoring gestartet. Hierbei hat sich gezeigt, dass die Callbackfunktion zeitweise zig Mal in Folge aufgerufen wird, ohne dass Coding in der Arduino Loop durchlaufen wird, und manchmal wird die Loop auf Empfangsseite durchlaufen, dann hat die Callback Funktion irgendwie eine Pause… verstehe aber den Zusammenhang nicht, weil der Sender kontinuierlich bei dem Monitoring gesendet hat.

    1. Hallo Harald,
      die Callbunktionen, die bei Senden und Empfangen ausgelöst werden, sind interruptgesteuert. Aber ich kann das Problem nicht nachvollziehen. Normalerweise sollte die Callbackfunktion einmal pro emfangener bzw. gesendeter Nachricht aufgerufen werden. Bei dir wird die Callbackfunktion „einfach so“ aufgerufen? Mit meinen Beispielsketchen hat das bei mir problemlos geklappt. Hast du die denn mal unverändert ausprobiert? Wenn man den Empfänger mit Nachrichten in hoher Frequenz bombardieren würde, könnte es unter Umständen Probleme geben.
      VG, Wolfgang

      1. Hallo Wolfgang,
        vielen Dank zunächst einmal für die schnelle Antwort. „Einfach so“ wird die Callback Funktion bei mir im Empfänger nicht aufgerufen. Ich habe einen Sender (ESP8266) und einen Empfänger (ESP8266), Coding analog zu deinem Beispielcode oben. Der Sender sendet Temperaturwerte (2-3 Werte pro Sekunde), und das kontinuierlich, ohne Unterbrechung, immer der gleiche Zyklus auf Senderseite. Auf Empfangsseite würde ich nun erwarten, dass die Callbackfunktion auch in gleichen Zeitabständen aufgerufen wird, weil ja der Sender mit gleichen Zyklen sendet (Frequenz etwa 2 Hz). Mein Monitoring (habe in der Callbackfunktion und in der Loop jeweils einen Serial.print() gesetzt ) zeigt allerdings, dass dem nicht so ist. Manchmal wird der „Serial.print()“ innerhalb der Callback zig mal hintereinander aufgerufen ohne dass der „Serial.print()“ in der Loop anschlägt und manchmal wird auch mal die Loop ausgeführt.
        Jetzt weiss ich nicht, was du unter hoher Frequenz verstehst „Wenn man den Empfänger mit Nachrichten in hoher Frequenz bombardieren würde, könnte es unter Umständen Probleme geben.“. Sind 2-3Hz Senderseitig hoch ? Hat der Empfänger irgendwie einen Puffer für die Daten ? Wenn der Empfänger bei JEDEM Empfang den Interrupt auslöst und in die Callbackfunktion springt, würde ich
        auf Empfangsseite (analog zum Sender) gleiche Zyklen , d.h. gleiche zeitliche Abstände zwischen den einzelnen Aufrufen der Callbackfunktion erwarten. Dies ist ganz und gar nicht der Fall. Aber warum ?

        Vielen Dank
        Harald

        1. Hallo Harald,
          wenn ich mehr Zeit hätte, würde ich da tiefer einsteigen und selbst probieren. Ich verzettele mich aber gerade ohnehin schon. Deswegen kann ich nur ein wenig spekulieren. Da ist zunächst die Serial.print() Funktion. Sie ist relativ langsam und in Interruptroutinen kann sie zu merkwürdigem Verhalten führen. Vor allem läuft Serial.print() nicht sequentiell mit den anderen Anweisungen. Was ich damit meine: da es für die serielle Übertragung einen Puffer gibt, wird schon mit dem Programm fortgefahren, bevor die Übertragung abgeschlossen ist. Wenn du jetzt schnell zwischen loop() und Callbackfunktion hin- und herspringst, kommt er vielleicht nicht klar. Um erst fortzufahren, wenn die laufende serielle Übertragung abgeschlossen ist, kannst du ein Serial.flush() an den entsprechenden Stellen einfügen. Wie gesagt: Spekulation. Vielleicht probierst du zum Monitoring auch mal versuchsweise einen Zähler ein, den du nur alle x Male per Serial.print() ausgibst.
          Die variablen Zeitabstände können auch damit zusammenhängen, dass ESP-NOW unter der Haube recht komplex ist und mit freeRTOS mit verschiedenen Tasks arbeitet.
          Und ich würde die Frequenz, in der du Nachrichten versendest, zumindest versuchsweise senken (alle paar Sekunden), schauen, ob es geht und dann langsam erhöhen, um die Grenze auszutesten.
          VG, Wolfgang

        2. Hallo Harald,
          vergiss, was ich über Interrupts geschrieben habe. Es funktioniert anders. Was allerdings genau um Hintergrund passiert, wenn einne Nachricht eingeht, kann ich dir nicht sagen. Die esp-now Bibliothek ziemlich groß und komplex. Du kannst dir ja mal selbst einen Eindruck verschaffen:
          https://github.com/espressif/esp-now
          Allerdings ändert das nichts an meinen Anregungen bzgl. Serial.print() und dem Spielen mit der Frequenz des Verschickens von Nachrichten. Wenn du noch etwas herausgefunden solltest, wäre ich dankbar wenn du es teilst. Ich würde gerne selbst herumprobieren, aber ich komme leider im Moment nicht dazu.
          VG, Wolfgang

  5. Hallo Wolfgang,

    ich habe extreme Probleme den code Many to one umzusetzen. Ich habe einen ESP8266 und habe auch schon versucht alles umzustricken jedoch bekomme ich es selbst nach 4 h nicht hin. Immer wieder Fehlermeldungen.
    Kannst du mir Weiterhelfen?

    LG
    Christian

    1. Hallo Christian, ich will es zumindest versuchen! Kommen die Fehlermeldungen schon beim Kompilieren oder erst während der Laufzeit? Am besten schickst du mir mal deine Sketche (wolfgang.ewald@wolles-elektronikkiste.de) und teilst mir auch mit, welches Board du in der Arduino IDE ausgewählt hast.
      VG, Wolfgang

  6. Hallo,
    das ist ja mächtig gewaltig, würde Egon Olsen sagen. Ich habe die Quellen benutzt, vielen Dank. Aber wie so oft, funktioniert nicht alles so, wie ich will.
    Eine aus meiner Sicht notwendige Funktion ist die Gestaltung eines WLAN-Gateways. Ich habe das begonnen, komme aber nun nicht vom Fleck.
    Das Problem: Ein ESP32 (WLAN_ESP) soll als Gateway funktionieren. Ein ESP8266 ist ein ausschließlich ESP-Now nutzender Rechner. Vom WLAN_ESP zum ESP-Now funktioniert senden und empfangen nur ohne WLAN. Starte ich im WLAN_ESP „Wifi.begin“ dann nimmt der WLAN_ESP nichts mehr vom ESP-Now auf. Die WLAN-Verbindung steht und macht das übliche. Aber ESPNow-Senden geht nur noch in die Richtung „zum“ ESP-Now.

    Nun gibt es die verschiedensten Vorschläge z.B. in
    https://randomnerdtutorials.com/esp32-esp-now-wi-fi-web-server/
    aber niemand nutzt die Datenübertragung in beide Richtungen (sowohl im WLAN als auch via ESP-Now).

    Nun meine Frage: liege ich da irgendwie ganz daneben, das meine „duplex“-Verbindung überhaupt funktionieren kann oder muss ich auf irgendwas serielles als Gateway zurückgreifen, was mir nicht gerade helfen würde.

    Oder kennst du ein Beispielprojekt, wo sowas schon eine Lösung hat?

    Viele Grüße
    Thomas

    1. Hallo, ich weiß nicht, ob ich da wirklich helfen kann. Die Kombination von ESP-NOW und WiFi Server habe ich nicht probiert. Mit „reinem“ ESP-NOW ist die Kommunikation in beiden Richtungen kein Problem. Aber auch die Kommunikation der W-LAN (Server-Client) geht in beide Richtungen, Ein Rollentausch ist möglich:
      https://wolles-elektronikkiste.de/wlan-mit-esp8266-und-esp32
      Wie man das Kommunikation in beide Richtungen unter Verwendung von ESP-NOW und WiFi realisiert, das kann ich nicht sagen. Ich schätze, da müsste ich ein paar Stunden investieren, dazu werde ich aber sicherlich nicht so schnell kommen (auch wenn es mich reizt, mir das anzuschauen. Vielleicht kannst du bei randomnerdstutorials mal fragen? VG, Wolfgang

      1. Hallo Wolfgang,
        Vielen Dank für Deine Antwort und ja, ich habe weiter geforscht und WLAN und ESPNow zumindestens so zum laufen gebracht, das WLAN und ESP-Now auf einem ESP32-Server laufen und ein ESP12 als Sender mit ESP-Now zum ESP32-Server Daten liefert.
        Das Ganze habe ich aus „https://randomnerdtutorials.com/esp-now-auto-pairing-esp32-esp8266/“
        Der Server generiert auch eine kleine Webseite auf dem ESP32-Server mit den Daten vom ESP12. Funktioniert soweit.

        Einen ESP32 als Sender habe ich heute nicht mehr ranbekommen. Aber das wird noch…
        Viele Grüße
        Thomas

  7. Wow! Ich bin erst vor kurzem auf diese Seite gestoßen und bin schlichtweg begeistert von der Fülle und vor allem Qualität der Infos.

    Ich löte/bastle/baue selber seit >20 Jahren aber wer kennt es nicht: man kann einfach nicht alles wissen, also googelt man nach gewissen Dingen. In meinem Fall nach ESP deep sleep modes.

    Fantastisch!

    Viele Grüße, Jan

  8. Wieder mal eine super Anleitung, Danke dafür.
    In Englisch gibt es von randomnerdtutorials dot com auch sowas, aber in Deutsch und so gut auf gearbeitet, respekt !!

  9. Hallo Wolfgang,
    bin ein blutiger Anfänger und stolpere unter Anderem über das Thema „Reichweiten“.
    Wo bekommt man Module mit „externen“ Antennen oder wie liesen sich welche anschließen?
    Vielen Dank.

    1. Hallo Reinhard, wenn du einfach mal „esp32 mit externer antenne“ googlest, dann wirst du auf Module mit einem IPEX-Antennenanschluss stoßen. Das ist ein kleiner runder Stecker von wenigen Millimeter Durchmesser und kleinem Pin in der Mitte. Oder wenn du ein ESP8266 Modul bevorzugst, dann könntest du ein Wemos D1 Mini Pro Board nehmen. Für die Antenne google einach „2.4 GHz Antenne“. Diese Antennen habe für gewöhnlich einen SMA-Anaschluss. Dafür gibt es Adapter. Einfach nach „IPEX SAM Adpater“ suchen. Das alles gibt es zum Beispiel auf Amazon oder eBay. VG, Wolfgang

    1. Hallo Wolfgang,

      vielleicht kannst Du mir auf die Sprünge helfen, ich bekomme die Fehlermeldung :C:\Myfiles_Toshiba\Arduino\Test\Test.ino:1:10: fatal error: esp_now.h: No such file or directory
      1 | #include
      | ^~~~~~~~~~~
      compilation terminated.

      exit status 1

      Compilation error: esp_now.h: No such file or directory

      Irgendwie finde ich nicht die passende Library…..

      Vielen Dank für den „richtigen“ Hinweis…

      Gruß

      Karsten

      1. Hi Karsten,
        welchen ESP verwendest du? Wenn es ein ESP8266 ist (z.B. ein Wemos D1 Mini Board), dann heißt die einzubindende Bibliothek espnow.h und nicht esp_now.h. Das wäre der naheliegendste Fehler.
        VG, Wolfgang

        1. Hallo Wolfgang,

          der naheliegeste Fehler war es, 😉 peinlich, peinlich.

          Vielen Dank für die Unterstützung, jetzt funktioniert es.
          Wer lesen kann ist nartürlich klar im Vorteil!

          Gruß

          Karsten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert