In this tutorial, we’ll look at how to set up a network of several ESP32s using the ESP-NOW protocol. The ESP32 is a Wifi-enabled development board. It can therefore connect and exchange data with devices connected to the same network.
Hardware
- Computer
- NodeMCU ESP32 or NodeMCU ESP8266 or Wemos x3
- USB A Male to Mini B Male cable x3
Description ESP-NOW
ESP-NOW is a communication protocol developed by Espressif, enabling wireless communication between several devices without the need for a specific network. It enables high-speed exchange of small data packets over 2.4GHz frequency bands, up to 200m away. It requires an initial pairing, but once this is done, communication is persistent and establishes itself on start-up. One of the great advantages, apart from the fact that it uses a dedicated network, is that one or more ESP32 or ESP8266 stations can connect to the Wifi in parallel.
Thanks to this protocol, it’s possible to create a network of NodeMCUs that communicate with each other.
Code
We’re going to set up bidirectional communication between several ESP32s, using the espnow.h library available when you install the board manager. To simplify the code, we’ll define and send an identical data structure for all the cards.
Master code
For the sending card to be able to communicate with another card, it needs its MAC address. You can retrieve this address when the receiving card starts up from messages sent using the setup() function (Serial.println(WiFi.macAddress());). We’re going to define a function that runs after a message has been sent to check that the transmission was successful.
Don’t forget to change the slave card addresses to match the cards you’re using.
#include <esp_now.h> #include <WiFi.h> const char nom[10]="Master"; uint8_t broadcastAddress[2][6] = { {0x2C, 0xF4, 0x32, 0x15, 0x52, 0x22}, //station0 {0xA0, 0x20, 0xA6, 0x08, 0x20, 0xD9} //station1 };// REPLACE WITH RECEIVER MAC ADDRESS // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message myData; struct_message dataRcv; unsigned long previousTime=0; // callbacks for sending and receiving data void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print(F("\r\nMaster packet sent:\t")); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&dataRcv, incomingData, sizeof(dataRcv)); Serial.print("\r\nBytes received: "); Serial.println(len); Serial.print("From slave: "); Serial.println(dataRcv.a); Serial.println(); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println(F("Error initializing ESP-NOW")); return; } Serial.print(F("Reciever initilized : ")); Serial.println(WiFi.macAddress()); // Define callback functions esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // Register peer esp_now_peer_info_t peerInfo; peerInfo.channel = 0; peerInfo.encrypt = false; memcpy(peerInfo.peer_addr, broadcastAddress[0], 6); if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } memcpy(peerInfo.peer_addr, broadcastAddress[1], 6); if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } } void loop() { if((millis() -previousTime)>5000){ // Set values to send strcpy(myData.a, nom); myData.b = random(1, 20); myData.c = 1.2; myData.e = false; // Send message via ESP-NOW myData.d = "Slave0"; esp_err_t result0 = esp_now_send(broadcastAddress[0], (uint8_t *) &myData, sizeof(myData)); myData.d = "Slave1"; esp_err_t result1 = esp_now_send(broadcastAddress[1], (uint8_t *) &myData, sizeof(myData)); previousTime=millis(); } }
Slave code
In the SLAVE code, we’ll create a function that runs when a message is received. This function is used to process the information received. In this example, we display the data contained in the structure.
You will need to modify the Slave code for each slave card so that the data and identifier are different.
#include <esp_now.h> #include <WiFi.h> const char nom[10]="Slave0"; uint8_t broadcastAddress[] = {0x3C, 0x61, 0x05, 0x30, 0x0A, 0x28};// REPLACE WITH MASTER MAC ADDRESS //{0xDC, 0x4F, 0x22, 0x58, 0xD2, 0xF5} //station0 // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message dataSent; struct_message dataRcv; unsigned long previousTime=0; // callbacks for sending and receiving data void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\n"+String(nom)+" packet sent:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&dataRcv, incomingData, sizeof(dataRcv)); Serial.print("\r\nBytes received: "); Serial.println(len); Serial.print("From: "); Serial.println(dataRcv.a); Serial.print("To: "); Serial.println(dataRcv.d); Serial.print("Sensor: "); Serial.println(dataRcv.b); Serial.print("Status: "); Serial.println(dataRcv.c); Serial.println(); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println(F("Error initializing ESP-NOW")); return; } Serial.print(F("Reciever initialized : ")); Serial.println(WiFi.macAddress()); // Define callback functions esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // Register peer esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; // Add peer if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println(F("Failed to add peer")); return; } } void loop() { if((millis() -previousTime)>1500){ // Set values to send strcpy(dataSent.a, nom); dataSent.b = random(100, 200); dataSent.c = false; // Send message via ESP-NOW esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &dataSent, sizeof(dataSent)); previousTime=millis(); } }
(Code for ESP8266 below)
When you upload the code for the first time, pay close attention to the setup message containing the receiver’s MAC address. You need to place it in the transmitter code to ensure successful pairing and communication.
Results
Once the MAC address of the receiving card has been defined and the codes uploaded to each card, communication is established and the structure is sent and decrypted correctly.
- Master
- Slave0
- Slave1
ATTENTION: during this tutorial, one of the cards seemed to be defective and was not sending messages to the master, even though it was receiving them. To be tested and validated with another card
Bonus: Communication between ESP32 and ESP8266
To integrate ESP8266 cards into your ESP-NOW network, simply modify a few lines of code.
I invite you to review the tutorial on ESP-NOW and ESP8266
To change from an ESP32 code to an ESP8266 code, you need to modify:
- Includes at the start of codeAdd an esp_now_set_self_role function with the correct role defined in each case
- Modify the arguments of the esp_now_add_peer function
- Modifier les types des arguments des fonctions OnDataRecv et OnDataSent
#include <espnow.h>//https://github.com/esp8266/Arduino/blob/master/tools/sdk/include/espnow.h #include <ESP8266WiFi.h> const char nom[10]="Slave0"; uint8_t broadcastAddress[] = {0x3C, 0x61, 0x05, 0x30, 0x0A, 0x28};// REPLACE WITH MASTER MAC ADDRESS //{0xDC, 0x4F, 0x22, 0x58, 0xD2, 0xF5} //station0 // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message dataSent; struct_message dataRcv; unsigned long previousTime=0; // callbacks for sending and receiving data void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\n"+String(nom)+" packet sent:\t"); Serial.println(status == 0 ? "Delivery Success" : "Delivery Fail"); } void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&dataRcv, incomingData, sizeof(dataRcv)); Serial.print("\r\nBytes received: "); Serial.println(len); Serial.print("From: "); Serial.println(dataRcv.a); Serial.print("To: "); Serial.println(dataRcv.d); Serial.print("Sensor: "); Serial.println(dataRcv.b); Serial.print("Status: "); Serial.println(dataRcv.c); Serial.println(); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != 0) { Serial.println(F("Error initializing ESP-NOW")); return; } Serial.print(F("Reciever initialized : ")); Serial.println(WiFi.macAddress()); // Define callback functions esp_now_set_self_role(ESP_NOW_ROLE_SLAVE); esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // Register peer esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_CONTROLLER, 1, NULL, 0); } void loop() { if((millis() -previousTime)>1500){ // Set values to send strcpy(dataSent.a, nom); dataSent.b = random(100, 200); dataSent.c = false; // Send message via ESP-NOW uint8_t result = esp_now_send(broadcastAddress, (uint8_t *) &dataSent, sizeof(dataSent)); previousTime=millis(); } }
Bonus: Display MAC address for copying
The MAC address of the Maestro or Slaves can be displayed in a format that allows it to be copied directly into broadcastAddress.
void getMacAdress(const uint8_t * mac){ /*for (int i=0; i<6; i++){ if (mac[i]<10) Serial.print(0,HEX),Serial.print(mac[i],HEX); // FF:FF:FF:FF:FF:FF else Serial.print(mac[i],HEX); if(i<5) Serial.print(","); } */ Serial.print("{"); for (int i=0; i<6; i++){ Serial.print("0x"); if (mac[i]<10) Serial.print(0,HEX),Serial.print(mac[i],HEX); // {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF} else Serial.print(mac[i],HEX); if(i<5) Serial.print(","); } Serial.print("}"); }