ESP-NOW

Über den Beitrag

Update 19. Juli 2024: Das Boardpaket für den ESP32 hat ab Version 3.0.0 eine neue Version der ESP-NOW Bibliothek, die nicht abwärtskompatibel ist. Ich habe die Sketche entsprechend angepasst. Die ESP8266-Sketche weiter unten im Beitrag funktionieren noch so, wie sie sind.

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 Sketch auslesen und formatieren könnt:

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

void setup(){
    Serial.begin(115200);
    delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA); 
    while (!(WiFi.STA.started())) { // comment the while loop for ESP8266
        delay(10);
    }
    // delay(1000); // uncomment for ESP8266
    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(){}

Die explizite Einstellung des Wi-Fi Modus (Zeile 7) ist ab Boardpaketversion 3.0.0 erforderlich, um die MAC-Adresse zu ermitteln.

Und hier die Ausgabe:

Ausgabe get_mac_address.ino
Ausgabe get_mac_address.ino

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[] =  {0xC8, 0xC9, 0xA3, 0xC6, 0xFE, 0x54}; // customize as you wish  
uint8_t requestedNewMacAddress[6] = {0,0,0,0,0,0};

void setup(){
    Serial.begin(115200);
    delay(1000); 
    WiFi.mode(WIFI_STA);
    while (!(WiFi.STA.started())) {
        delay(100);
    }
    Serial.print("Default ESP Board MAC Address: ");
    Serial.println(WiFi.macAddress());
       
    esp_wifi_set_mac(WIFI_IF_STA, newMacAddress);
    esp_wifi_get_mac(WIFI_IF_STA, requestedNewMacAddress);
    
    Serial.print("New ESP Board MAC Address:     ");
    esp_wifi_get_mac(WIFI_IF_STA, requestedNewMacAddress);
    for (int i=0; i<6; i++) {
        Serial.print(requestedNewMacAddress[i],HEX);
        if (i<5) {
            Serial.print(":");
        }
    }   
}
 
void loop(){}

Und hier die Ausgabe:

Ausgabe change_mac_address.ino
Ausgabe change_mac_address.ino

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.
  • Seit dem Update auf das Paket 3.x liefert WiFi.macAddress() immer die Hardware MAC-Adresse zurück. Zur Überprüfung deshalb hier der unbequeme Umweg über esp_wifi_get_mac().

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

Die Funktion esp_now_register_recv_cb() ist der Hauptgrund für die Nicht-Abwärtskompatibilität des Boardpaketes der Version 3.x. Genauer gesagt ist es die Änderung des ersten Funktionsparameters von uint8_t * macAddr zu const esp_now_recv_info* info. esp_now_recv_info ist eine Struktur mit den folgenden öffentlichen Elementen:

  • uint8_t *src_addr, d. h. die MAC Adresse des Senders
  • uint8_t *des_addr, d. h. die MAC Adresse des Empfängers
  • wifi_pkt_rx_ctrl_t *rx_ctrl, das sind RX Kontrollinformationen

Eine 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)-1); // -1 to not send the NULL terminator
    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.

Broadcasting

Wenn ihr keine Lust habt, MAC Adressen zu ermitteln, dann könntet ihr anstelle individueller Adressen die Broadcast MAC Adresse FF:FF:FF:FF:FF:FF verwenden. Damit erhalten alle Empfänger die Nachricht, sofern sie auf denselben Kanal eingestellt sind.

Receiver

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

void messageReceived(const esp_now_recv_info *info, 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 bislang 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 esp_now_recv_info *info, const uint8_t* incomingData, int len){
    memcpy(&myMessage, incomingData, sizeof(myMessage));
    Serial.printf("Transmitter MAC Address: %02X:%02X:%02X:%02X:%02X:%02X \n\r", 
            info->src_addr[0], info->src_addr[1], info->src_addr[2], info->src_addr[3], info->src_addr[4], info->src_addr[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.  
    • Die MAC-Adresse (src_addr) ist ein Element von info. Da info als Zeiger an messageReceived() übergeben wurde, müssen wir den Pfeil- anstelle des Punktoperators verwenden, um auf src_addr zuzugreifen.

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 info->src_addr[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, 0};

void messageReceived(const esp_now_recv_info *info, const uint8_t* incomingData, int len){
    memcpy(&stationMsg, incomingData, sizeof(stationMsg));
    weatherStation[info->src_addr[5]].humidity = stationMsg.humidity;
    weatherStation[info->src_addr[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
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 Smarthome 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] =  {{0xC8, 0xC9, 0xA3, 0xCA, 0x22, 0x70},
                                  {0xC8, 0xC9, 0xA3, 0xC6, 0xFE, 0x54},
                                  {0x94, 0xE6, 0x86, 0x0D, 0x7B, 0x80}};
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

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 esp_now_recv_info *info, 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[] =  {0xC8, 0xC9, 0xA3, 0xCA, 0x22, 0x70};
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 esp_now_recv_info *info, const uint8_t* incomingData, int len){
    memcpy(&myReceivedMessage, incomingData, sizeof(myReceivedMessage));
    Serial.printf("Incoming Message from: %02X:%02X:%02X:%02X:%02X:%02X \n\r", 
            info->src_addr[0], info->src_addr[1], info->src_addr[2], info->src_addr[3], info->src_addr[4], info->src_addr[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[] = {0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4};
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 esp_now_recv_info *info, const uint8_t* incomingData, int len){
    memcpy(&myReceivedMessage, incomingData, sizeof(myReceivedMessage));
    Serial.printf("Incoming Message from: %02X:%02X:%02X:%02X:%02X:%02X \n\r", 
                info->src_addr[0], info->src_addr[1], info->src_addr[2], info->src_addr[3], info->src_addr[4], info->src_addr[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

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

Neue Funktionen ab Boardpaketversion 3.0.0

Die ESP-NOW Bibliothek ab ESP32 Boardpaketversion 3.0.0 hat einige sehr komfortable Funktionen erhalten. Probiert dazu einmal die Beispielsketche ESP_NOW_Broadcast_Master, ESP_NOW_Broadcast_Slave und ESP_NOW_Networking an (Datei → Beispiele → ESP_NOW). Mithilfe dieser Sketche setzt ihr ESP-NOW-Netzwerke auf, in denen die Teilnehmer automatisch angemeldet werden, ohne dass ihr MAC Adressen auslesen müsst. Allerdings sind diese Sketche für Anfänger nicht unbedingt einfach zu verstehen. Vielleicht schreibe ich dazu noch einmal einen eigenen Beitrag.

Eine weitere Neuerung ist ESP-NOW Serial. Dieses Feature ist nicht so komfortabel, besticht aber durch seine überaus einfache Bedienung. Auch dazu gibt es einen Beispielsketch. Ich werde über ESP-NOW Serial in einem meiner nächsten Beiträge berichten.

Ü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.
  • Der erste Parameter der mit esp_now_register_recv_cb() angemeldeten Funktion (hier: messageReceived()) ist uint8_t* macAddr und nicht const esp_now_recv_info* info.

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

Durch die Änderungen in der ESP-NOW Bibliothek im Rahmen der Einführung des ESP32 Boardpaketes 3.x ist eine Kommunikation zwischen ESP8266- und ESP32 Boards über ESP-NOW nicht mehr möglich. Zumindest ist mir das nicht gelungen. Vorschläge, wie es gehen könnte, werden gerne entgegengenommen!

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.

76 thoughts on “ESP-NOW

  1. Hallo Wolfgang,
    durch das neue Boardpaket ESP32 3.0 funktioniert nun mein ESP32 im Display nicht mehr. Er hadert mit der Arduino_GFX. Es ist ein altes Projekt, aber ich will es erweitern und realisiere das mit ESPnow und dem alten Boardpaket. Jetzt fehlen mir aber deine hilfreichen Beispiele mit der alten Version. Wie kann ich darauf noch zugreifen?
    Gruß
    Pius

    1. Hallo Pius,

      das ist ja blöd. Keine Chance, die Arduino_GFX auf das neue Paket anzupassen, anstelle mit ESP_NOW zur alten Version zurückzukehren?

      Leider habe ich die alten Versionen nicht mehr. Aber es ist nicht viel an Änderungen notwendig. Die Transmittersketche sollten funktionieren, wie sie sind. Wenn ich mich recht erinnere, dann musst du lediglich bei Receiver- oder Transceiversketchen

      void messageReceived(const esp_now_recv_info *info, const uint8_t* incomingData, int len)

      ersetzen durch:

      void messageReceived(const uint8_t* macAddr, const uint8_t* incomingData, int len)

      Wenn du auf der Receiverseite auf die MAC Adresse des Senders zugreifen möchtest, dann machst du das nicht über info->src_addr[], sondern über macAddr[]. Ich glaube, das war es schon. Willst du das mal probieren?

      VG, Wolfgang

  2. Hallo Wolfgang,
    hab hier ein ESP32-S3-DevKit-Lipo und ein AllnetESP8266-IoT-Relais .. hab ein wenig gebastelt, will so ein paar Sachen via Tastatur so für 1h bis 3h ausschalten und Licht auf Terrasse und sowas ..
    Deine Routinen für ESP32 und ESP8266 übernommen bzw. angepasst ..
    .. UND GEHT AUF ANHIEB, Reichweitentest, im Raum, Innenwand, Außenwand, Gegenseite mit USB-Powerbank aufs Tor gelegt, ca. 20m gesamt … GEHT !!

    DANKE DANKE DANKE

  3. Hallo,

    ich versuche die Daten meines GPS über ESP-NOW an einen anderen ESP zu senden, das GPS-Programm SEPERAT ( damals mit Bluetooth-Datenweiterleitung ) hat problemlos funktioniert, die ESP-NOW-Verbindung läuft auch, es kommen die Variableninhalte durch…

    ABER in Kombination GPS/ESP-NOW scheint es keine brauchbaren Daten zu geben – ich bekomme nach einigem Anlauf die Meldung „Signal verfügbar“ und ganz selten „Daten unbrauchbar“…

    „Beisst“ sich hier das GPS (Tiny bzw. Softwareserial“ mit dem ESP-NOW ?

    Ich bin kein „gelernter“ Programmierer -eher so ein „LEGO“-Typ – zusammensuchen und zusammenbauen…

    Trotzdem anbei Code….eventuell kannst Du mir ja helfen..

    !!! Code habe ich herausgenommen – s.u. Grüße, Wolfgang !!!

    1. Hi Stephan,

      wie du siehst, gehen hier die Einrückungen und die includes verloren. Kannst du mir das Programm mal per E-mail senden (wolfgang.ewald@wolles-elektronikkiste.de)? Ich bin so frei und nehme den Code mal heraus aus deinem Kommentar.

      Grüße, Wolfgang

  4. Hallo Wolfgang,
    echt stark was du hier so in den letzten Monaten und Jahre so erarbeitet und allen an Wissen zu Verfügung stellst.
    Dafür ein riesen großes Dankeschön!!!

    Kleine Frage von mir, lässt sich ESP NOW und BT zusammen nutzen? Also die Daten vom ESP NOW per BT auf ein Smartphone etc. schieben?

    Grüße und Danke
    Jörg

    1. Hi Jörg,
      erst einmal vielen Dank fürs Lob! Und ja, ESP NOW und BT vertragen sich. Ich habe gerade mal den Sketch esp_now_serial_receiver.ino aus dem Beitrag dahingehend erweitert, dass die empfangenen Nachrichten mit BT weitergesendet werden:

      #include "ESP32_NOW_Serial.h"
      #include "MacAddress.h"
      #include "WiFi.h"
      #include "esp_wifi.h"
      #include "BluetoothSerial.h" 
      
      #define ESPNOW_WIFI_CHANNEL 1
      
      BluetoothSerial espBT; 
      
      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);
          espBT.begin("ESP32 Bluetooth Example");
      }
      
      void loop() {
          if (NowSerial.available()) {
              Serial.print("Sender message: ");
              String messageIn = NowSerial.readStringUntil('\n');
              Serial.println(messageIn);
              NowSerial.println("Thank you!");
              espBT.println(messageIn);
          }
          delay(1);
      }
      

      Auf dem Smartphone konnte ich die Nachrichten dann mit meiner App „Bluetooth Terminal“ lesen. Nur ein kleiner Eingriff ist noch notwendig, denn BT Serial verbraucht unglaublich viel Speicher, sodass der resultierende Sketch zu groß für die Standardeinstellungen wurde. Du kannst den Programmspeicher des ESP32 aber vergrößern. Dazu wählst du unter Werkzeuge -> Partition Scheme -> die Option „Huge APP(3MB No OTA/1MB SPIFFS)“.

      VG, Wolfgang

      1. Wow gleich mit Beispiel.
        Nochmal vielen Dank.
        Das Programm wird eigentlich gar nicht so viel größer. Das Problem ist das automatisch mit BT der OTA Speicher reserviert wird.

        Bitte und Danke sind leider in dieser Welt viel zu selten geworden. Es wird alles als Selbstverständlich hin genommen.

  5. Hallo zusammen,

    ich habe den Beitrag im Hinblick auf die Änderungen ab ESP32 Boardpaketversion 3.0.0 angepasst und die Sketche „repariert“. Entsprechend funktionieren die Sketche mit Boardpaketen der Versionen <3.x nicht mehr.

    Abgesehen von der nervigen Inkompatibilität hat ESP-NOW ab ESP32 Boardpaketversion 3.0.0 einige nützliche zusätzliche Funktionen. Dazu wird es einen separaten Beitrag geben.

    VG, Wolfgang

  6. Hallo Wolfgang,
    ich habe ein neues Projekt begonnen, bei dem Daten von verschiedenen ESP8266 gesammelt und an einen ESP32 geschickt werden. Das Versenden und Empfangen hat letzte Woche noch funktioniert, heute gibt es bei allen Beispielen – auch von anderen Autoren im Netz – einen Compilerfehler beim Receiver:
    esp_now_register_recv_cb(OnDataRecv);

    Compilation error: invalid conversion from ‚void ()(const uint8_t, const uint8_t*, int)‘ {aka ‚void ()(const unsigned char, const unsigned char*, int)‘} to ‚esp_now_recv_cb_t‘ {aka ‚void ()(const esp_now_recv_info, const unsigned char*, int)‘} [-fpermissive]

    Arduino IDE hat verschiedene Bibliotheken upgedated, welche weiß ich nicht mehr. Kannst Du mal testen, ob deine Empfänger-Sketche bei Dir noch laufen?
    Danke!
    Gruß
    Pius

    1. Hallo Pius,

      vielen Dank für den Kommentar, denn das Problem war mir noch nicht aufgefallen. Es hängt mit der Umstellung des ESP32 Boardpaketes von 2.0.17 auf 3.0.0 zusammen. Genauer gesagt hängt es mit der ESP-NOW Bibliothek zusammen, die Teil des Paketes 3.0.0 ist. Ich bin mal auf 2.0.17 zurückgegangen und das Problem war weg. Wieder zurück auf 3.0.0 und es war wieder da. Im Moment kann ich als Lösung nur anbieten, auf 2.0.17 zurückzugehen.

      Das Ganze scheint auch kein „Version.0“ Problem zu sein, sondern Absicht, wie man auf Github, wo das Paket verwaltet wird, lesen kann:
      https://github.com/espressif/arduino-esp32/issues/9737

      Wie ich das hasse (und das Wort nehme ich nicht leichtfertig in den Mund), wenn es keine Rückwärtskompatibilität gibt. Dann sollen sie die alte Bibliothek doch drin lassen und eine neue (ESP-NOW_2 o .ä.) parallel einführen oder die alte umbenennen. Aber nein, lieber lässt man alle User davon kalt erwischen.

      VG, Wolfgang

      Ich muss mir das in Ruhe anschauen. Erst dann kann ich abschätzen, ob ich alle Sketche komplett umschreiben muss oder ob es mit ein bisschen Kosmetik getan ist.

      1. Hallo Wolfgang,
        wie geht man auf die alte Version 2.0.17 zurück?
        Danke!
        Gruß
        Pius

        1. Hallo Pius,

          Das machst du über den Board Manager in der Arduino IDE. In der Version 2.x erreichst du den Manager über eines der Symbole auf der linken Seite. Dann gibst du dort esp32 ein und solltest dann den richtigen Eintrag sehen. Da steht dann die Versionsnummer. Wenn du da drauf gehst siehst du die älteren Versionen. 2.0.17 ist die, die noch funktioniert. Zumindest kompiliert alles erfolgreich.

          Ich habe gestern noch angefangen mir die Beispielsketche der neuen ESP-NOW Bibliothek anzuschauen. Das sieht schon sehr anders aus und ist nicht mal so so eben gemacht. Selbst die Ermittlung der Mac-Adresse wurde anscheinend geändert. Ich bekomme nur noch 00:00:00:00:00:00 angezeigt.
          VG, Wolfgang

          1. Hallo Wolfgang,
            danke, hab’s gefunden. mein Fehler war, dass ich nach der ESP-now Bibliothek gesucht habe. Jetzt geht es wieder wie vorher. Ich werde das Projekt mit der alten Version fertigstellen und – wenn denn wieder mal eine Anwedung mit ESP-now kommt – die neue Version nehmen.
            Übrigens: Tolle Seite! Ist bei mir fest verlinkt.
            Gruß
            Pius

            1. Der folgende Receiver Sketch funktioniert mit der aktuellen Version vom Boardpaket:

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

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

              const uint8_t newMacAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x01};

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

              void loop(){}

              1. Hallo, die Kommentarfelder werden als HTML interpretiert. Deswegen wird alles in spitzen Klammern verschluckt. Entweder du nimmst Anführungszeichen oder die HTML Anweisungen für eckige Klammern: &lt; bzw. &gt;.
                Ich kann die eimzubindenden Headerdateien für dich einfügen, wenn du sie nennst.

                Ok, nun zum eigentlichen Thema: vielen Dank für den Hinweis! Heißt „funktioniert“, dass er kompiliert oder dass du damit auch tatsächlich empfängst? Hast du auch das Gegenstück dazu, also einen Transmittersketch?

                Ich werde den Beitrag für ESP32 wohl tatsächlich neu schreiben (und diesen Beitrag auf ESP8266 umschreiben). Es sind zu viele Änderungen bzw. Neues für ESP32. Durchaus auch sehr Nützliches wie z.b. das supereinfache ESP-NOW Serial und Funktionen, die neue Peers automatisch mit in das Netzwerk aufnehmen, ohne dass man erst großartig MAC Adressen ermitteln muss. Wird aber noch ein wenig dauern.

                VG, Wolfgang

                1. Hi Wolfgang,

                  die Header sind:
                  #include „esp_now.h“
                  #include „WiFi.h“
                  #include „esp_wifi.h“

                  Funktioniert heißt in dem Fall, dass es kompiliert und ich die Message aus dem Sendersketch empfangen kann.

                  Der Sketch, der die Mac Adresse ausliest hat 0x00 0x00 0x00 0x00 ausgespuckt und wenn ich die Adresse dem Sender gegeben habe, hat das Übertragen nicht funktioniert. Ich musste dem Empfäner erst manuell eine Mac Adresse zuweisen (s.o)

                  VG
                  Clemens

                  1. Nochmal Danke! Bei der MAC-Adressenerkennung hat sich etwas geändert. Ich war auch über das Problem gestolpert. Die Lösung ist, explizit in den STA Modus zugehen. D.h. du fügst noch die folgenden Zeilen ein:

                      WiFi.mode(ESPNOW_WIFI_MODE);
                      while (!WiFi.STA.started()) {
                        delay(100);
                      }
                    

                    Es geht auch mit dem AP-Modus das ergibt aber eine andere MAC-Adresse.

                    Es sind diese vielen kleinen Änderungen, die nicht explizit erklärt werden, die den Umstieg auf das neue Paket etwas nervig machen.

                    VG, Wolfgang

                    1. Gerne!

                      Weißt du zufällig wo die Implementierung von esp_err_t esp_now_send(const uint8_t *peer_addr, const uint8_t *data, size_t len); liegt?

                      In esp_now.h ist nur die Deklarierung.

                      VG
                      Clemens

  7. Hallo Wolfgang,
    ich bin mit meinem Projekt schon deutlich weiter, aber jetzt häng ich wieder ziemlich fest.
    Ich will jetzt alles vor Ort auf einen Chip bekommen, das Lesen des Shelly EM3, das Steuern der Module, und das Senden der Aktionen an meinen Schreibtisch.
    Alles klappt schon ganz ordentlich nur das Senden per ESPNOW nicht.
    Ist da ein ein Fehler im Skript, oder kollidiert da ESPNOW-send mit der HTTP-Abfrage?
    Jedenfalls kommt : Delivery fail und es kommt auch beim Empfänger nix an.
    ne Idee?
    Danke,
    Helmut

    ich kopier mal den Sketch hier rein,:
    /* Generic ESP8266 Modul

    sketch_mar27c.ino

    aus BasicHTTPClient.ino
    Created on: 27.03.2024
    liest den Status aus Shelly 3EM, filtert nach „POWER“,ermittelt den Strom eines Moduls,
    schaltet die notwendige/zulässige Anzahl Module zu oder ab,
    und funkt das Ergebnis an einen ESP01 zur Anzeige am Monitor.
    */

    #include
    #include
    #include
    #include
    #include

    ESP8266WiFiMulti WiFiMulti;

    // REPLACE WITH RECEIVER MAC Address (ESP01:E1)
    //uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
    //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 broadcastAddress[] = { 0xC8, 0xC9, 0xA3, 0x56, 0x46, 0xCB }; //A
    //uint8_t broadcastAddress[] = { 0xDC, 0x4F, 0x22, 0x26, 0x02, 0xF4 };//E3

    //********
    //01 11 21 22
    String Verteil[11] = { „0000“, „1000“, „0100“, „0010“, „0001“, „0110“, „1110“, „0011“, „1011“, „0111“, „1111“ };
    // 00 10 11 21 22 23 33 34 44 54 55
    // 0 1 1 2 2 3 3 4 4 5 5
    //********

    typedef struct struct_message
    {
    int Last;
    float Amp;
    int Sum;
    int Phas1;
    int Phas2;
    int Phas3;
    } struct_message;
    struct_message myData;

    unsigned long lastTime = 0;
    unsigned long timerDelay = 1000; // send readings timer

    String payload = „“;
    String x1;
    String Ausgabe;
    int Strom;
    int Phase[3];
    int Summe;
    int Schwelle;
    int Laststufe = 0;
    int Limit;
    float Ampere;
    int s;
    int inputString;
    char Puffer[60];

    WiFiClient client;
    HTTPClient http;

    //*************** Setup ****************

    void setup()
    {
    pinMode(12, OUTPUT);
    pinMode(13, OUTPUT);
    pinMode(14, OUTPUT);
    pinMode(16, OUTPUT);
    WiFi.mode(WIFI_STA);
    WiFiMulti.addAP(„shellyem3-3494547B8D61“, „“);

    Serial.begin(115200); // Init Serial Monitor
    Serial.println(„“);
    Serial.println(WiFi.macAddress());
    if (esp_now_init() != 0) // Init ESP-NOW
    {
    Serial.println(„Error initializing ESP-NOW“);
    return;
    }
    esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
    uint8_t result = esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_CONTROLLER, 0, NULL, 0);
    if (result != 0)
    {
    Serial.println(„Failed to add peer“);
    }
    else
    {
    Serial.println(„Peer ok“);
    }
    esp_now_register_send_cb(OnDataSent); // we will register for Send CB
    }

    //************** Loop *****************

    void loop()
    {
    if ((millis() – lastTime) > timerDelay)
    {
    lastTime = millis();
    LeseShelly();
    Auswertung();
    Senden();
    }
    }

    //***************** LeseShelly ********************

    void LeseShelly()
    {
    if ((WiFiMulti.run() == WL_CONNECTED))
    {
    if (http.begin(client, „http://192.168.33.1/status“))
    {
    int httpCode = http.GET(); // start connection and send HTTP header
    if (httpCode > 0)
    {
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
    {
    String payload = http.getString();
    Summe = 0;
    int i = 0;
    Ausgabe = „“;
    for (s = 400; s < 750; s++) // zwischen Stelle 400 und 750 stehen die Werte für die 3 Phasen
    {
    String A = payload.substring(s, s + 5);
    if (A == "power")
    {
    s = s + 7;
    //**************************
    x1 = payload.substring(s, s + 10);
    Phase[i] = x1.toInt();
    i++;
    s = s + 10;
    //**************************
    }
    }
    }
    }
    }
    }
    Summe = Phase[0] + Phase[1] + Phase[2];
    }

    //**************** Auswertung ***********************

    void Auswertung()
    {
    Strom = analogRead(A0);
    Ampere = ((Strom – 512) / 512.000 * 60); // ADC-Ausgabe umgerechnet in Ampere
    if (Ampere 10) { Limit = 10; }
    Schwelle = (Schwelle * 3 + Summe) / 4;
    if (Schwelle 0) { Laststufe -= 1; }
    if (Schwelle >= 200 and Laststufe Limit) { Laststufe = Limit; }

    //Schalten: (LOW: MODUL abgeklemmt, HIGH: MODUL zugeschaltet)
    if (Verteil[Laststufe].substring(0, 1) == „0“) { digitalWrite(12, LOW); }
    if (Verteil[Laststufe].substring(1, 2) == „0“) { digitalWrite(13, LOW); }
    if (Verteil[Laststufe].substring(2, 3) == „0“) { digitalWrite(14, LOW); }
    if (Verteil[Laststufe].substring(3, 4) == „0“) { digitalWrite(16, LOW); }

    if (Verteil[Laststufe].substring(0, 1) != „0“) { digitalWrite(12, HIGH); }
    if (Verteil[Laststufe].substring(1, 2) != „0“) { digitalWrite(13, HIGH); }
    if (Verteil[Laststufe].substring(2, 3) != „0“) { digitalWrite(14, HIGH); }
    if (Verteil[Laststufe].substring(3, 4) != „0“) { digitalWrite(16, HIGH); }
    }

    //***************** Senden ********************

    void Senden()
    {
    //Ausgabe = Verteil[Laststufe] + “ “ + Strom + “ “ + Ampere + “ “ + Limit + “ “ + Summe + “ “ + Schwelle + Ausgabe + “ „;
    //Strom.toCharArray(Puffer, sizeof(Puffer));
    myData.Last = Laststufe;
    myData.Amp = Ampere;
    myData.Sum = Summe;
    myData.Phas1 = Phase[0];
    myData.Phas2 = Phase[1];
    myData.Phas3 = Phase[2];
    memcpy(&Puffer, &myData, sizeof(myData));
    esp_now_send(broadcastAddress, (uint8_t *)&Puffer, sizeof(Puffer));
    Serial.print(myData.Last + “ „);
    Serial.print(myData.Amp + “ „);
    Serial.print(myData.Sum + “ „);
    Serial.print(myData.Phas1 + “ „);
    Serial.print(myData.Phas2 + “ „);
    Serial.println(myData.Phas3);
    }

    //***************** OnDataSent ********************

    void OnDataSent(uint8_t *broadcastAddress, uint8_t sendStatus) // Callback when data is sent
    {
    if (sendStatus == 0)
    {
    Serial.println(„Last Packet Send Status: Delivery success“);
    }
    else
    {
    Serial.println(„Last Packet Send Status: Delivery fail“);
    }
    }

      1. Manchmal braucht’s den Tritt in den Hintern.
        Ja, wenn ich die HTTP-Abfrage rausnehme, dann sendet der ESP12 auch.
        Jetzt brauch ich jedenfalls nicht mehr nach einem Syntaxfehler suchen.
        Vll. kann ich ja den Shelly 3EM auch mit ESPNOW abfragen?
        Danke dir,
        Gruß
        Helmut

        1. In den Hintern treten wollte ich dir nicht!!! Mit dem Shelly 3EM habe ich keine Erfahrung. Aber mein Gefühl sagt mir, dass es nicht funktionieren wird, weil ESPNOW schon recht speziell ist und nicht wie bei WiFI Anwendungen einem allgemein üblichen Protokoll folgt. Ich lasse mich allerdings auch immer gerne vom Gegenteil überzeugen.
          VG, WOlfgang

  8. Hallo Wolfgang,

    Lob für Dein Tutorial zu ESÜ-Now.
    Ich würde gerne zur Qualitätsüberwachung und Antennen-Ausrichtung auf beiden Seiten eine Anzeige der Empfangsfeldstärke RSSI realisieren, habe aber nirgendwo eine Lösung dazu gefunden.
    Ich hoffe, Du kannst mir weiterhelfen und es ist alles „ganz einfach“.
    Gruß
    Uwe

    1. Hi, ich denke, man kann RSSI über int rssi = WiFi.RSSI(); ermitteln. Und den Wert kann man ja einfach als Teil der Nachricht in einem struct übermitteln.
      Hoffe, das geht.
      VG, Wolfgang

      1. Hallo Wolfgang,
        ich habe diese Anweisung in beiden messageReceived-Callback-Funktionen eingefügt:
        Es wird mir aber immer nur 0 ausgegeben…

          1. Schade…falls Dir irgendwann noch eine Idee kommt…ich bin aufnahmebereit.
            Trotzdem erstmal Dnake für Deine Bemühungen.

    1. Hallo, aus dem Tiefschlaf lässt sich der ESP8266 nur mittels Timer (also periodisches Aufwachen) oder per LOW Signal am Resetpin wecken. Also keine Chance per ESP-NOW. Wenn nur einer der beiden ESP8266 schlafen gehen muss, dann könnte man ihn periodisch aufwachen und nachfragen lassen, „ob es Neuigkeiten gibt“, sprich ob er noch eine Nachricht empfangen soll, bevor er sich wieder schlafen legt.

  9. Hallo Herr Ewald,

    ich finde Ihre Seite große Klasse. Hat mir son einiges gebracht. Seit ein paar Wochen beschäftigt mich eine Sache, die Sie mir vielleicht schnell mit Ja oder Nein beantworten können.

    Gibt es eine Möglichkeit, einen ESP8266, der sich im Tiefschlaf befindet, mittels eines anderen ESP8266 aufzuwecken um ihn dann nach Daten zu fragen?

  10. „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.

  11. 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

  12. Hallo Wolfgang

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

    Super

    Danke

  13. 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

  14. 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

  15. 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

  16. 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

  17. 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 !!

  18. 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