ESP-NOW • Wolles Elektronikkiste (2024)

About this post

In my article WLAN with ESP8266 and ESP32, I showed how to let ESP boards communicate with each other via WiFi and how to control them via the browser of your PC or smartphone. However, if you only want to exchange data between two or more ESP boards, there is a simpler method called ESP-NOW.

I will explain step by step how to use your ESP32 or ESP8266 based boards with ESP-NOW as a receiver, transmitter or transceiver. I primarily use the ESP32 as a demonstration object. Finally, I will show you how to modify the code for ESP8266 boards.

This is the content:

  • Introduction / preparations (ESP32 and ESP8266)
    • Getting the MAC address
    • Changing the MAC address
  • Overview of the most important ESP-NOW functions (ESP32 and ESP8266)
  • One transmitter, one receiver – bare minimum (ESP32)
  • One transmitter, one receiver – advanced (ESP32)
  • Several transmitters, one receiver (ESP32)
  • One transmitter, multiple receivers (ESP32)
  • ESP boards as transceivers (ESP32)
  • “Translation” of the sketches for the ESP8266
  • Mixing ESP32 and ESP826 boards
  • Changing channels (ESP32 and ESP8266)
  • Range

Introduction / Preparations (ESP32 and ESP8266)

ESP-NOW is a protocol that allows you to exchange messages of up to 250 bytes between up to twenty ESP32 or ESP8266 boards. ESP-NOW is very flexible. It allows you to set up any board as a transmitter, receiver or transceiver and send messages to single or multiple members of your network. It is also possible to mix ESP32 and ESP8266 boards without any problems.

To use ESP-NOW, you do not need to install any additional software. The required libraries are part of the “standard equipment” of the ESP32 and ESP8266 packages.

Getting the MAC address

The members of a network must be clearly identifiable. ESP-NOW uses the MAC address of your ESP board for this purpose. You can determine the MAC address with the following little sketch:

#include <WiFi.h> // for ESP32 boards//#include <ESP8266WiFi.h> // for ESP8266 boardsvoid setup(){ Serial.begin(115200); // delay(1000); // uncomment if your serial monitor is empty Serial.print("MAC-Address: "); Serial.println(WiFi.macAddress());} void loop(){}

A few explanations about the sketch

To use the WiFi functions, you have to include WiFi.h for ESP32-based boards. For ESP8266-based boards, include ESP8266WiFi.h.

The MAC address consists of six bytes. Usually, the bytes are specified in the hexadecimal system, separated by colons. E.g. 94:3C:C6:33:68:98.

So, you have to find out the MAC address of every board that should participate in your ESP-NOW network and write it down. It is recommended to label the boards in some way to avoid confusion.

Note: If you load your sketches on ESP boards, then it may happen that the first Serial.print() instructions are “swallowed” during the first program run. A while(!Serial) after Serial.begin(), which is recommended for certain boards, does not eliminate the problem (if you should have it). That’s why you’ll find a commented out delay() in my sketches, which you can uncomment if necessary.

Changing the MAC address

If it is too tedious for you to read out all MAC addresses, then you can simply set a new one. The following two sketches show how to do this for the ESP32 and the ESP8266.

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(){}

A few explanations:

  • We use a function from the ESP API (Application Programming Interface) in this example. You can recognize these functions by the fact that they start with “esp_”. To access them, you need to include the appropriate libraries. Here, it is esp_wifi.h.
  • The MAC address is defined as an array (uint8_t).
  • An ESP card can either be integrated into a network (station mode) or serve as an access point itself (access point mode). We set the station mode for ESP-NOW using WiFi.mode(WIFI_STA).
  • With esp_wifi_set_mac() you set the MAC address. The function expects two arguments. The first one is the interface used. Since we are working in Station mode, we select the station interface ( = WIFI_IF_STA). The second argument is the new MAC address.
    • The documentation for the esp_wifi_set_mac() function can be found here.
  • Bit 0 of the first byte of the MAC address must be 0, or in simpler terms: the first byte must be an even number. Otherwise, as far as I know, there are no other restrictions on your choice.
  • The change of the MAC address by esp_wifi_set_mac() is not permanent, i.e. the ESP “forgets” the setting at a reset.

I will change the MAC address only in one of the example sketches. If you want to use this option in the other examples, you have to extend the sketches accordingly.

ESP8266

And this is how the counterpart for the ESP8266 looks like:

#include <ESP8266WiFi.h>uint8_t newMacAddress[] = {0x94, 0x3C, 0xC6, 0x33, 0x68, 0x01}; // customize as you wishvoid 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(){}

Overview of the most important ESP-NOW functions (ESP32 and ESP8266)

To let your boards communicate via ESP-NOW, you only need a few functions, which I have summarized here in an overview:

A complete list of functions can be found here in the API documentation for ESP-NOW.

One transmitter, one receiver – bare minimum (ESP32)

Since ESP-NOW can be a little confusing for beginners, we’ll start with a minimal example and then build on that. In this example, one board takes the role of the transmitter, the other serves as the receiver. The message consists of text only.

And again the hint: The code is ESP32 specific. I will come to the transfer to the ESP8266 at the end of the article.

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); //uncommentifyourserialmonitorisempty 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);}

Explanations:

  • With #include<esp_now.h> we integrate the ESP-NOW library.
  • peerInfo contains information about the module we want to communicate with. This is a structure of type esp_now_peer_info_t. The documentation can be found here. For us, the following elements of the structure are relevant:
    • peer_addr contains the MAC address of the receiver module.
      • An assignment peerInfo.peer_addr = receiverAddress is not possible, because arrays cannot be copied so easily. Therefore, the detour via memcpy().
    • channel is the WiFi channel. You can choose the channels 1 – 13. With “0” the default setting takes effect, namely “1”. I will describe how to change the WLAN channel of your module at the end of the article.
    • encrypt specifies whether you want to encrypt the message. I am not going into that.
  • esp_now_init() initializes ESP-NOW.
  • esp_now_add_peer(&peerInfo) adds the ESP module you defined before with peerInfo to the network of the current module.
  • esp_now_send() sends a message. You pass the MAC address of the recipient, the message and its length to the function. The message is of data type uint8_t and is passed as a pointer in esp_now_send(). Accordingly, message must still be explicitly converted using (uint8_t *).

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); //uncommentifyourserialmonitorisempty WiFi.mode(WIFI_STA); esp_now_init(); esp_now_register_recv_cb(messageReceived);} void loop(){}

Here are some explanations as well:

  • To receive data only, you do not need to add the sending module as a “peer”.
  • The only new function is esp_now_register_recv_cb(). With this, you register a function (here: messageReceived), which is called automatically when a message is received. The “cb” stands for “call back”. Since the function is called automatically, loop() can be left empty. You know the principle from interrupt service routines.
  • The parameters of the function to be called are the MAC address of the sender, the message itself and the length of the message.
  • Since we sent a text, but the received message is of data type uint8_t, we have to explicitly convert it back to char.

Output on the serial monitor

If everything went well, then the message of the transmitter should be displayed on the serial monitor of the receiver module every five seconds.

One transmitter, one receiver – advanced (ESP32)

The “bare minimum” sketches are not very comfortable yet. There are no error messages if something goes wrong, no send confirmation, and no display of the MAC address of the transmitter. Also, we have only sent plain text so far. In practice, it is more likely that users will want to transmit data, such as sensor readings. We will now eliminate these deficits.

Transmitter sketch

First, the transmitter sketch:

#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); //uncommentifyourserialmonitorisempty 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);}

What is different compared to the bare minimum sketch?

  • As format for our message we use a structure (message). This gives us a lot of flexibility in terms of the types of data that are included in the message. In this example, the structure consists of a character array, an integer, and a float value. For the character array, we have to specify the maximum expected length.
  • Structures like message are kind of stripped down classes. message myMessage; creates the object myMessage.
  • The return value of esp_now_init() tells us whether the operation was completed without errors.
  • In the same way, we use the return value of esp_now_add_peer(). Important: the function does not check whether the peer is really available or reachable. But, for example, you would get an error message if the maximum number of peers is exceeded. For more information, see the API documentation.
  • To copy the array textMsg to the array myMessage.text you have to use memcpy() for the reasons already mentioned before. You can assign the other elements directly.
  • With esp_now_register_send_cb(messageSent); we register the function messageSent, which is called when a message has been sent. Again, the parameters are predefined.
  • Checking status in the function messageSent and checking result as the return value of esp_now_send() may seem like unnecessary duplication, but different criteria are checked in each case. For details – guess what – see the API documentation.

Receiver sketch

The receiver sketch can also be made more comfortable:

#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); //uncommentifyourserialmonitorisempty 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(){}

What is different here compared to the bare minimum sketch?

  • We implement the same structure message as in the transmitter sketch.
  • We copy the incoming message to myMessage using memcpy() and then we can conveniently access the elements of the structure myMessage.
  • We display the MAC address of the transmitter. The formatting (hex numbers, colons) is provided by the printf() function.

Output of receiver_basic.ino

Several transmitters, one receiver (ESP32)

In practice, one often uses several transmitters and one receiver. A typical application example would be a weather station with sensors at different locations that transmit their data to a central station.

You do not need any additional ESP-NOW functions for this configuration. The only challenge is the assignment of the incoming data to the transmitters.The obvious solution would be to match the transmitter MAC address with a list stored in the receiver sketch. However, an identity check of arrays is quite computationally intensive. Alternatively, the message from the transmitter could contain an identifier.

I decided to use a different method. All ESP modules participating in the network get a new MAC address. The MAC addresses differ only in the last byte, which also serves as numbering. In my example, three transmitters are used, and I have assigned the following MAC addresses for them:

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

Transmitter sketch example

Here is the sketch for the 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);}

As data we send the humidity and the temperature, which we could have determined with a DHT22, for example. The data is “packed” in the structure data.

Receiver

On the receiver side, we first copy the incoming messages to the “auxiliary structure” stationMsg and from there we transfer them to the array weatherStation[]. The counter for the elements of the array is simply the last digit of the MAC address, i.e. 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); }

Here is the output:

One transmitter, multiple receivers (ESP32)

It is just as easy to network one transmitter and multiple receivers. This configuration could be used in smart home applications, for example. The sender must know all the MAC addresses of the peers, i.e. the receivers, and connect to all of them.

For simplicity and better overview, in my example all receivers get sent the same data types, so we only need to define one structure. If necessary, this could be easily changed.

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); //uncommentifyourserialmonitorisempty 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);}

I don’t think the code needs any further explanation, do you?

You could also simplify this sketch a bit by changing the MAC addresses of the receivers so that they only differ in the last byte. You then only need a one-dimensional array for the receiverAddress, and if you want to address receiver no. i, then you only have to set the address with receiverAddress[5] = 0x0i.

Maybe also useful: If you replace the receiver address in the function esp_now_send() by NULL (written exactly like this, not “0”), the message will be sent to all registered peers.

Output

This is what the output looks like on the transmitter side:

Receiver

The sketch for the receivers should also be understandable without further explanation:

#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); //uncommentifyourserialmonitorisempty 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(){}

Output

And here still exemplarily the output of Receiver 1:

ESP as transceiver (ESP32)

One setup is still missing, namely the use of ESP boards as transceivers, i.e. as combined transmitters and receivers. The good news is that you don’t need any new features to do this. You also don’t have to switch between receive and transmit mode or similar. Just take one of the transmitter sketches and add the elements needed for a receiver (or the other way around).

In my example of this, one transceiver (the “lead”) sends data to another transceiver (the “follower”) at certain intervals, which thanks the lead transceiver for doing so and reports back its runtime in seconds. Admittedly, it makes little sense, but it is only meant to illustrate the principle!

I named the lead transceiver because it sets the pace in this specific example. Just as well, the two transceivers could send their messages to each other on an equal and event-based basis, e.g. at certain time intervals, when a certain sensor value limit is reached, when a button is pressed, or whatever comes to your mind.

Sketch for the “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); //uncommentifyourserialmonitorisempty 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);}

As you can see – it’s just a combination of transmitter and receiver sketches.

Sketch for the “Following Transceiver”

Here, on the follower side, the messages are received and acknowledged with a reply.

#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); //uncommentifyourserialmonitorisempty 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(){}

Outputs:

Here is the output for the lead:

And here is the output for the follower.

I will not show an example of networking multiple transceivers. You should be able to deduce this easily from the previous examples.

“Translation” of the sketches for the ESP8266

The ESP8266 implementation of ESP-NOW is not very different from the implementation on the ESP32. However, the difference is large enough that unified sketches (via annotations or #ifdef ESP32...#else constructions) would have become confusing.

Here are the main differences:

  • The names of the libraries to be included.
  • The parameters of the callback functions.
  • For the ESP82866, its role as receiver, transmitter or transceiver must be explicitly defined with esp_now_set_self_role().
  • Return values of some functions.

As an example I have “translated” the transceiver sketches for the ESP8266.

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); //uncommentifyourserialmonitorisempty 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);}

Followers

#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); //uncommentifyourserialmonitorisempty 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(){}

Mixing ESP32 and ESP826 boards

One of the advantages of ESP-NOW is that you can easily mix ESP8266 and ESP32 boards in your network. You can test this by loading, for example, the lead transceiver sketch for the ESP32 to an ESP32 board and the follower transceiver sketch for the ESP8266 to an ESP8266 board. Only the MAC addresses have to be adjusted, of course.

Changing channels (ESP32 and ESP8266)

If you have range problems due to interaction with other WiFi networks, then switching to a different channel might help. The following two sketches show how to change the channel for an ESP32 and an ESP8266 board. All participants in a network must of course be set to the same channel.

ESP32

#include <esp_now.h>#include <esp_wifi.h>#include <WiFi.h>#define CHANNEL 13void 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 13void setup(){ Serial.begin(115200); // delay(1000); //uncommentifyourserialmonitorisempty 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(){}

Range

If you google for range tests for ESP-NOW, you will come across maximum values between 100 and 250 meters, as long as the tests were conducted outdoors, with a free line of sight and without external antennas. How far you get in your apartment or house depends mainly on the building fabric.

In my own range tests, two Wemos D1 Mini Boards and two ESP32-WROOM-32 based development boards were used. Both pairs achieved identical ranges. In the next room, i.e. through one wall, I did not lose any messages. One room further, i.e. through two walls, I had good connection only in some areas. This is roughly comparable to the range of my Fritz!Box 7590.

If the range is not sufficient for you, you could place a module as a repeater between the modules to be connected. Or you can use modules that allow you to connect external antennas.

Acknowledgement

I have S K on Pixabay to thank for the note with the “NOW” on the post image.

ESP-NOW • Wolles Elektronikkiste (2024)

FAQs

Is ESP-now secure? ›

In ESP-NOW, application data is encapsulated in a vendor-specific action frame and then transmitted from one Wi-Fi device to another without connection. CTR with CBC-MAC Protocol(CCMP) is used to protect the action frame for security.

What is the range of ESP32 now? ›

ESP-NOW has a limited signal range, typically around 220 meters under ideal conditions. The actual range can vary depending on factors like environmental interference, antenna design, and obstacles in the communication path.

What is the data rate of ESP-now? ›

The application for ESP-NOW does not require any special procedures. For technical documentation, please refer to ESP-NOW User Guide. You can use examples in ESP-NOW SDK for testing. The default bit rate of ESP-NOW is 1 Mbps.

What is the latency of ESP-now? ›

This protocol is a low-latency and fast unicast communication technology with a maximum payload of 250 Bytes, achieving a latency of nearly 1 ms for 1-byte transmission and less than 2 ms for 100 Bytes [19] .

Why is ESP so cheap? ›

The short answer is that it's cheap to manufacture. In particular the RF engineers have done a bunch of very clever things on the Wi-Fi side. You will also notice that in a lot of ways ESP32's design is not like other common microcontrollers. This is generally not by accident, it's to keep the overall cost down.

Is ESP32 a security risk? ›

Security researchers have recently described a fault injection attack on ESP32, which may result in some security compromise and unintended disclosure of information. However, ESP32's security design remains safe for the vast majority of our products. Here's how.

What is the lifespan of ESP32? ›

In theory, there's nothing that wears down in the ESP32 itself. However, the flash chip in a module can lose its information over time. For instance, the GD25Q32 we use in our modules guarantees a flash lifetime of 20 years, minimum. The other thing that can wear down the flash is repeated erase cycles.

How long can ESP32 stay on? ›

Power consumption of 1µA is negligible in most situations. Neglecting voltage effects, a small 3.7V, 850mAh rechargeable battery would be able to power an ESP32 in modem-sleep for approximately 12.5 hours. Light-sleep could run for over 1000 hours.

Is ESP32 real time? ›

The esp 32 is not going to keep accurate time for long periods of time. You either have to synchronize with an internet time source periodically or use something like an RTC to get accurate timing.

Is ESP now a mesh network? ›

5, the ESP-NOW two-way communication methodology is suitable for creating a mesh network in which many ESP32 boards can transfer data to each other.

What is ESP frequency? ›

Usually ESP operates at its normal frequency usually 60 Hz.

What is sample rate in ESP32? ›

ESP32 supports up to 2 MHz of sampling rate theoretically.

Is 139 latency bad? ›

What is considered high ping? Generally, a ping of below 20 ms (milliseconds) is considered excellent, between around 40 to 50 ms is great, around 80 to 120 ms is fairly good, and 150 ms and up is considered high. Most people would consider a ping of above 250 ms to be unplayable, especially in competitive games.

Is 4 ms latency bad? ›

For most people, MOS ratings of 4 or higher are considered “good,” while anything below 2.5 is considered unacceptable.

What latency is playable? ›

Latency is measured in milliseconds, and indicates the quality of your connection within your network. Anything at 100ms or less is considered acceptable for gaming. However, 20-40ms is optimal.

Why use ESP-now? ›

ESP-NOW provides a flexible and low-power data transmission to all interconnected devices. It can also be used as an independent protocol that helps with device provisioning, debugging, and firmware upgrades.

Is ESP-now a mesh network? ›

5, the ESP-NOW two-way communication methodology is suitable for creating a mesh network in which many ESP32 boards can transfer data to each other.

What is the maximum number of slaves in ESP-now? ›

In the ESP-NOW protocol, used primarily with ESP32 and ESP8266 modules, the number of slaves that can be connected to a master in a one-to-many communication setup depends on the specific hardware and software constraints. For the ESP32, typically, the limit is around 20 slaves connected to a single master.

Is ESP header encrypted? ›

ESP uses AES-GMAC (Galios Message Authentication Code) to provide authentication, but not encryption.

Top Articles
Latest Posts
Article information

Author: Tyson Zemlak

Last Updated:

Views: 6475

Rating: 4.2 / 5 (63 voted)

Reviews: 86% of readers found this page helpful

Author information

Name: Tyson Zemlak

Birthday: 1992-03-17

Address: Apt. 662 96191 Quigley Dam, Kubview, MA 42013

Phone: +441678032891

Job: Community-Services Orchestrator

Hobby: Coffee roasting, Calligraphy, Metalworking, Fashion, Vehicle restoration, Shopping, Photography

Introduction: My name is Tyson Zemlak, I am a excited, light, sparkling, super, open, fair, magnificent person who loves writing and wants to share my knowledge and understanding with you.