Table of Contents
Today we will be working with ESP-NOW, a connectionless protocol developed by Espressif for the ESP32 and ESP08266 microcontrollers. With ESP-NOW we can build a private network without WiFi or a Router!
Introduction
The ESP32 and its cousin, the ESP8266, are undoubtedly remarkable microcontrollers. Aside from a high-speed 32-bit architecture, they also have built-in Bluetooth and WiFi.
The Bluetooth and WiFi capabilities on these devices are made possible by an integrated 2.4GHz radio transceiver module. And this module can also be used for other communications applications that use the unlicensed 2.4GHz band.
Espressif, the makers of the ESP8266 and ESP32, have developed a protocol that allows all these devices to create a private, wireless network using the 2.5GHz transceivers. This is a separate network from the WiFi network and can only be used by ESP-type microcontrollers.
The protocol is called ESP-NOW.
ESP-NOW
ESP-NOW allows simple packet communications between ESP devices, using the 2.4 GHz band. These transmissions operate a lot like those used by wireless mice and keypads and are limited to packets of 250 bytes or fewer.
Yes, I said 250 bytes! Not quite enough for voice and video perhaps, although there are ways of packetizing both of those, but quite sufficient to deliver remote control commands or data from sensors.
The data can be unidirectional or bidirectional, i.e. single-duplex or full-duplex. Most data types are supported.
Data can be encrypted or unencrypted, and no external source of WiFi or a router is required. Depending upon your configuration, you can have anywhere from 2 to 20 devices communicating between themselves.
The range can vary dramatically due to the environment, but under the right conditions (and with proper antennas) you can achieve over 400 meters. Just using the built-in antennas on the modules should still allow you to communicate through a medium-sized home without a problem.
ESP-NOW Networking Modes
You can place your ESP-NOW network in many configurations. You can mix and match ESP32 and ESP8266 devices within the same network.
Initiators and Responders
A device participating in an ESP-NOW network can be operated in one of two modes.
- Initiator – This device initiates the transmission. It will require the MAC address of the receiving device.
- Responder – This device receives the transmission.
In unidirectional (half-duplex) mode, the transmitting device is the Initiator and the receiving device is the Responder.
In a 2-way (full-duplex) communications mode, each device is both an Initiator and Responder.
One-Way Communication
The simplest communications topology is one-way, unidirectional communications.
In this arrangement, the Initiator ESP32 transmits data to the Responder ESP32. The Initiator can tell if the Responder received the message successfully.
This is a simple arrangement, but it has many uses in remote control applications.
One Initiator & Multiple Responders
This setup consists of one Initiator that is communicating with multiple responders.
The configuration can be used in two fashions:
- The Initiator communicates with each Responder individually.
- The Initiator initiates a broadcast message to communicate with all the Responders.
An alarm system might use this sort of configuration to activate remote sounders or communicate with remote monitors when an alarm has been triggered
One Responder & Multiple Initiators
This is the reverse of the previous ESP-NOW network configuration. In this arrangement, we have one Responder and multiple Initiators.
This arrangement is very common as it is used for remote sensor applications, with the Responder gathering data sent by the Initiator.
Two-Way Communications
The ESP-NOW protocol can also handle bidirectional, or full-duplex, communications.
This illustrates the simplest arrangement, with two devices communicating with one another.
In this, and all bidirectional communications configurations, the ESP-32 acts as both Initiator and Resolver.
Two-Way Networking
Expanding upon the previous configuration even further, we come up with this arrangement, four boards that have bidirectional communications established with one another.
Note that the boards can be a mix of different models of both ESP32 and ESP8266 devices.
MAC Addresses
When Initiators communicate with Responders, they need to know the Responder’s MAC Address.
A MAC, or Media Access Control, Address is a unique 6-digit hexadecimal number assigned to every device on a network. It is generally burned into the device by the manufacturer, although it is possible to manually set it.
Every ESP32 and ESP8266 has its own unique MAC address, and you’ll need to know that address to use it as a Responder in the experiments we will be doing today.
MAC Address Sketch
Here is a very simple sketch that you can run on an ESP32 to determine its unique MAC Address:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/* ESP32 MAC Address printout esp32-mac-address.ino Prints MAC Address to Serial Monitor DroneBot Workshop 2022 https://dronebotworkshop.com */ // Include WiFi Library #include "WiFi.h" void setup() { // Setup Serial Monitor Serial.begin(115200); // Put ESP32 into Station mode WiFi.mode(WIFI_MODE_STA); // Print MAC Address to Serial monitor Serial.print("MAC Address: "); Serial.println(WiFi.macAddress()); } void loop() { } |
All we are doing is including the WiFi Library, initializing the serial monitor, placing the ESP32 into Station mode, and then asking it for its MAC address. The result is printed on the serial monitor.
Running The MAC Sketch
The entire sketch runs in the Setup section, so after loading it to the ESP32, it will likely run before you get a chance to view it on the Serial monitor.
You can press the Reset key on your module to force it to run again.
The MAC Address will be at the bottom of the screen. Copy it to a safe location, so that you can use it later.
Coding for ESP-NOW
The ESP-NOW Library is included in your ESP device boards manager installation. It has a number of functions and methods to assist with coding for ESP-NOW.
In order to see how it works, you need to examine the sending and receiving of an ESP-NOW message packet.
Callbacks
A callback is a bit like an interrupt, it is generated every time a specific event has occurred.
In the ESP-NOW protocol, there are two callbacks of interest:
- Sending Data – The esp_now_register_send_cb() callback is called whenever data is sent.
- Receiving Data – The esp_now_register_rcv_cb() callback is called when data is received.
In your code, you will create a callback function that you will bind to either the sending or receiving callback using the functions listed above. Your function will run every time the event occurs.
The callback functions also return some useful data:
- The Sending callback returns the status of the sent data.
- The Receiving callback includes the received data packet.
Coding for ESP-NOW – Sending
If you are writing code to use the ESP32 or ESP8266 as the Initiator, then this is what you need to accomplish:
- You need to initialize the ESP-NOW library.
- Next, you’ll register your send callback function
- You need to add a peer device, which is the responder device. You add the peer by specifying its MAC address.
- Finally, you can packetize and send the message.
Coding for ESP-NOW – Receiving
To write code for the ESP-NOW responder, you’ll need to do the following:
- You need to initialize the ESP-NOW library.
- Next, you’ll register your receive callback function.
- In the receive callback, you’ll capture the incoming message data and pass it to a variable.
Let’s dig out some ESP boards and start experimenting with ESP-NOW.
Experimenting with ESP-NOW
I’ll be playing with the ESP32 today, but you should be able to run everything on an ESP8266 as well. If you do, you’ll need to change the WiFi library, as the ESP8266 uses a different one.
Gather a few modules, a mix of different manufacturers boards is fine, and run the MAC Address sketch on each of them and save the address to a text file or spreadsheet.
One-Way Data Test
Our first test is very simple and is a great way to learn the fundamentals of sending and receiving data using ESP-NOW.
We will need two ESP32 boards for this test. One board will be the Initiator (sender), and the other will be our Responder (receiver). We don’t require any additional hardware on the modules, as we’ll be monitoring the results with the serial monitor in the Arduino IDE.
As one board is Initiator and the other Responder, they will be running different sketches, which are shown below. You’ll need the MAC address of the Responder, as it goes into the Initiators code.
One-Way Initiator Sketch
The first sketch we will be running is the One-Way Initiator Sketch. This is run on the board that is transmitting the data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
/* ESP-NOW Demo - Transmit esp-now-demo-xmit.ino Sends data to Responder DroneBot Workshop 2022 https://dronebotworkshop.com */ // Include Libraries #include <esp_now.h> #include <WiFi.h> // Variables for test data int int_value; float float_value; bool bool_value = true; // MAC Address of responder - edit as required uint8_t broadcastAddress[] = {0x24, 0x6F, 0x28, 0x7A, 0xAE, 0x7C}; // Define a data structure typedef struct struct_message { char a[32]; int b; float c; bool d; } struct_message; // Create a structured object struct_message myData; // Peer info esp_now_peer_info_t peerInfo; // Callback function called when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nLast Packet Send Status:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void setup() { // Set up Serial Monitor Serial.begin(115200); // Set ESP32 as a Wi-Fi Station WiFi.mode(WIFI_STA); // Initilize ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } // Register the send callback esp_now_register_send_cb(OnDataSent); // Register peer memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; // Add peer if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } } void loop() { // Create test data // Generate a random integer int_value = random(1,20); // Use integer to make a new float float_value = 1.3 * int_value; // Invert the boolean value bool_value = !bool_value; // Format structured data strcpy(myData.a, "Welcome to the Workshop!"); myData.b = int_value; myData.c = float_value; myData.d = bool_value; // Send message via ESP-NOW esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); if (result == ESP_OK) { Serial.println("Sending confirmed"); } else { Serial.println("Sending error"); } delay(2000); } |
This is the simplest mode of communication with ESP-NOW, and it will show you the fundamentals of sending data using the ESP-NOW protocol.
We start by including both the WiFi and ESP-NOW libraries. Both were installed on your Arduino IDE when you set up the ESP32 Boards Manager, so there is no need to install them from the Library Manager.
We then set up a few variables we are going to transmit to the receiver. There is an integer, a float, and a boolean. We will also be sending some text in a character variable, which you’ll see shortly.
Now we need to enter the MAC address of the Responder board, which is the other ESP32 board. You will need to change the value in the sketch to match your board’s reading.
Now we define a data structure. This is the structure of the data message we want to send, and it should match the types of variables we are using. You’ll see that we start with a character array variable, followed by an integer, float, and boolean. Those data types match the types we just defined.
We then create a structured data object called myData to hold the data we will be transmitting.
Now we define our callback function, which we are calling OnDataSent. This will be called when data has been transmitted. In the function, we just print the status of the t transmission to the serial monitor.
Now we go to the Setup.
In Setup, we initialize our serial monitor and set the ESP32 temporarily as a WiFi Station.
Then we initialize ESP-Now
Assuming that it initializes, we register our callback function and then add information about the peer, which is our Responder.
Now to the Loop.
In the Loop we create some test data for the integer, float, and boolean. Then we add it to the myData object, along with the string “Welcome to the Workshop”.
Then we send the message. We print the result and then delay two seconds before starting at the top of the Loop.
One-Way Responder Sketch
Receiving has several similarities to transmitting.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
/* ESP-NOW Demo - Receive esp-now-demo-rcv.ino Reads data from Initiator DroneBot Workshop 2022 https://dronebotworkshop.com */ // Include Libraries #include <esp_now.h> #include <WiFi.h> // Define a data structure typedef struct struct_message { char a[32]; int b; float c; bool d; } struct_message; // Create a structured object struct_message myData; // Callback function executed when data is received void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&myData, incomingData, sizeof(myData)); Serial.print("Data received: "); Serial.println(len); Serial.print("Character Value: "); Serial.println(myData.a); Serial.print("Integer Value: "); Serial.println(myData.b); Serial.print("Float Value: "); Serial.println(myData.c); Serial.print("Boolean Value: "); Serial.println(myData.d); Serial.println(); } void setup() { // Set up Serial Monitor Serial.begin(115200); // Set ESP32 as a Wi-Fi Station WiFi.mode(WIFI_STA); // Initilize ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } // Register callback function esp_now_register_recv_cb(OnDataRecv); } void loop() { } |
We start by loading the same two libraries, and we then define a matching data structure and c a structured data object.
We then define our callback function, which in this case is called whenever data is received.
Remember, the receive callback returns the data in the message, so that is where all the “action” takes place! We grab the data and print it to the serial monitor.
We then go to the Setup, where we initialize the serial monitor and set up the ESP32 up as a WiFi Station.
Next, we attempt to initialize ESP-NOW. If we succeed, then we register our callback function.
And we are done. Nothing happens in the Loop, as everything is handled in the callback function.
Testing it Out
Load the sketches to their respective ESP32 boards.
Ideally, you’ll want to observe both of the boards’ output on a serial monitor. Instead of using two computers, you can just open two instances of the Arduino IDE on your computer and run them with different COM ports selected. That way, you can open up two serial monitors.
You should see the Initiator display the status of its sent messages. The Responders serial monitor will display the data that is received, and note how the variables change on each transmission.
Broadcast Mode
Our next demonstration will put the ESP32 in a 2-way Broadcast mode. You’ll need at least two ESP32 boards to do this experiment, but it is more fun if you have three or more!
We will have an LED and pushbutton attached to each of our ESP 32 boards.
All the LEDs will remain in the same state on every board. So if one is on, they are all on. The pushbuttons can toggle the LED states, any pushbutton will affect all the LEDs.
Broadcast Mode Hookup
Here is how we will hook up each of the ESP32 boards.
You can use any color of LED you like, and they don’t have to be the same color on each board. The resistor I used had a value of 150-ohms, but any value from 100 to 220 ohms will work fine.
The pushbutton is a standard momentary contact, normally-open switch.
Broadcast Mode Sketch
Here is the sketch that you will run on every board. As we are using broadcast mode, we don’t need to know the other boards’ MAC addresses, so every board can run identical code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
/* ESP-NOW Multi Unit Demo esp-now-multi.ino Broadcasts control messages to all devices in network Load script on multiple devices DroneBot Workshop 2022 https://dronebotworkshop.com */ // Include Libraries #include <WiFi.h> #include <esp_now.h> // Define LED and pushbutton state booleans bool buttonDown = false; bool ledOn = false; // Define LED and pushbutton pins #define STATUS_LED 15 #define STATUS_BUTTON 5 void formatMacAddress(const uint8_t *macAddr, char *buffer, int maxLength) // Formats MAC Address { snprintf(buffer, maxLength, "%02x:%02x:%02x:%02x:%02x:%02x", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]); } void receiveCallback(const uint8_t *macAddr, const uint8_t *data, int dataLen) // Called when data is received { // Only allow a maximum of 250 characters in the message + a null terminating byte char buffer[ESP_NOW_MAX_DATA_LEN + 1]; int msgLen = min(ESP_NOW_MAX_DATA_LEN, dataLen); strncpy(buffer, (const char *)data, msgLen); // Make sure we are null terminated buffer[msgLen] = 0; // Format the MAC address char macStr[18]; formatMacAddress(macAddr, macStr, 18); // Send Debug log message to the serial port Serial.printf("Received message from: %s - %s\n", macStr, buffer); // Check switch status if (strcmp("on", buffer) == 0) { ledOn = true; } else { ledOn = false; } digitalWrite(STATUS_LED, ledOn); } void sentCallback(const uint8_t *macAddr, esp_now_send_status_t status) // Called when data is sent { char macStr[18]; formatMacAddress(macAddr, macStr, 18); Serial.print("Last Packet Sent to: "); Serial.println(macStr); Serial.print("Last Packet Send Status: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void broadcast(const String &message) // Emulates a broadcast { // Broadcast a message to every device in range uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; esp_now_peer_info_t peerInfo = {}; memcpy(&peerInfo.peer_addr, broadcastAddress, 6); if (!esp_now_is_peer_exist(broadcastAddress)) { esp_now_add_peer(&peerInfo); } // Send message esp_err_t result = esp_now_send(broadcastAddress, (const uint8_t *)message.c_str(), message.length()); // Print results to serial monitor if (result == ESP_OK) { Serial.println("Broadcast message success"); } else if (result == ESP_ERR_ESPNOW_NOT_INIT) { Serial.println("ESP-NOW not Init."); } else if (result == ESP_ERR_ESPNOW_ARG) { Serial.println("Invalid Argument"); } else if (result == ESP_ERR_ESPNOW_INTERNAL) { Serial.println("Internal Error"); } else if (result == ESP_ERR_ESPNOW_NO_MEM) { Serial.println("ESP_ERR_ESPNOW_NO_MEM"); } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) { Serial.println("Peer not found."); } else { Serial.println("Unknown error"); } } void setup() { // Set up Serial Monitor Serial.begin(115200); delay(1000); // Set ESP32 in STA mode to begin with WiFi.mode(WIFI_STA); Serial.println("ESP-NOW Broadcast Demo"); // Print MAC address Serial.print("MAC Address: "); Serial.println(WiFi.macAddress()); // Disconnect from WiFi WiFi.disconnect(); // Initialize ESP-NOW if (esp_now_init() == ESP_OK) { Serial.println("ESP-NOW Init Success"); esp_now_register_recv_cb(receiveCallback); esp_now_register_send_cb(sentCallback); } else { Serial.println("ESP-NOW Init Failed"); delay(3000); ESP.restart(); } // Pushbutton uses built-in pullup resistor pinMode(STATUS_BUTTON, INPUT_PULLUP); // LED Output pinMode(STATUS_LED, OUTPUT); } void loop() { if (digitalRead(STATUS_BUTTON)) { // Detect the transition from low to high if (!buttonDown) { buttonDown = true; // Toggle the LED state ledOn = !ledOn; digitalWrite(STATUS_LED, ledOn); // Send a message to all devices if (ledOn) { broadcast("on"); } else { broadcast("off"); } } // Delay to avoid bouncing delay(500); } else { // Reset the button state buttonDown = false; } } |
We start with the two libraries, after that we define boolean variables to represent the state of the LED and pushbutton. We also define the pins these devices connect to.
There is also a function to correctly format the MAC addresses that are returned to us when we scan for recipients.
As we are both an Initiator and a Responder, we have two callbacks, one for send and one for receive.
The receive callback grabs the data and looks for the word “on”. If it gets it, then it turns on the LED, otherwise, it turns the LED off.
The send callback just prints up information about the sent packets on the serial monitor.
The broadcast function creates the broadcast message, by sending out the special MAC address of FF:FF:FF:FF:FF:FF. Each peer responds with its MAC address, which is used to send out the data.
In Setup, we do the usual – start the serial monitor, start the ESP32 in station mode before starting ESP-NOW.
We then register both callbacks.
Finally, we define the LED and pushbutton pins and an OUTPUT and INPUT, respectively. We use the internal pull-up for the pushbutton, which saves us the trouble of wiring up a resistor of our own.
In the Loop we examine the pushbutton status, if it is down then we toggle the state of the ledOn variable and send a broadcast message with a value of “on” or “off”, dependent upon its final value.
Testing Broadcast Mode
Load up the sketch onto each ESP32 module that you have wired up and power them all up. You should observe that the LED status on each board is identical and that you can change the status by pressing the pushbutton on any of the boards.
Remote Temperature & Humidity Sensor
Let’s put our ESP-NOW knowledge to work and build something practical – a remote temperature and humidity sensor.
We will have our old friend the DHT22 temperature and humidity sensor attached to the Initiator ESP32, broadcasting its values every two seconds. Our Responder will display the readings on the serial monitor.
This could be the basis for a practical project, with a display instated of a serial monitor.
Temperature Sensor Hookup
Here is how we will wire up our Initiator board.
Note that we are also using a 10K pull-up resistor on the data input line. This is actually optional, but you may find it more reliable with it.
The Responder (receiver) does not need any additional components wired to it. You will need to know its MAC address for all of this to work.
Initiator Sketch (Temperature Sender)
Here is the sketch that we will run on the Initiator board:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
/* ESP-NOW Remote Sensor - Transmitter esp-now-xmit.ino Sends Temperature & Humidity data to other ESP32 via ESP-NOW Uses DHT22 DroneBot Workshop 2022 https://dronebotworkshop.com */ // Include required libraries #include <WiFi.h> #include <esp_now.h> #include <DHT.h> // Define DHT22 parameters #define DHTPin 5 #define DHTType DHT22 // Create DHT Object DHT dht(DHTPin, DHTType); // Variables for temperature and humidity float temp; float humid; // Responder MAC Address (Replace with your responders MAC Address) uint8_t broadcastAddress[] = {0x24, 0x0A, 0xC4, 0x04, 0xF4, 0x40}; // Define data structure typedef struct struct_message { float a; float b; } struct_message; // Create structured data object struct_message myData; // Register peer esp_now_peer_info_t peerInfo; // Sent data callback function void OnDataSent(const uint8_t *macAddr, esp_now_send_status_t status) { Serial.print("Last Packet Send Status: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void setup() { // Setup Serial monitor Serial.begin(115200); delay(100); // Initiate DHT22 dht.begin(); // Set ESP32 WiFi mode to Station temporarly WiFi.mode(WIFI_STA); // Initialize ESP-NOW if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } // Define callback esp_now_register_send_cb(OnDataSent); memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Failed to add peer"); return; } } void loop() { // Read DHT22 module values temp = dht.readTemperature(); delay(10); humid = dht.readHumidity(); delay(10); Serial.print("Temp: "); Serial.println(temp); Serial.print("Humid: "); Serial.println(humid); // Add to structured data object myData.a = temp; myData.b = humid; // Send data esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); // Delay for DHT22 sensor delay(2000); } |
We include the two ESP libraries, plus the DHT library from Adafruit. If you don’t already have this library installed, you can find it in your Library Manager by searching for “DHT”. You want the library from Adafruit.
After setting up the DHT sensor parameters (which you can change if you want to use a DHT11 instead) we define a couple of floats for temperature and humidity.
We then have the MAC address of the responder. You will need to change this to the MAC address of your Responder ESP32 board.
We next define our data structure. As we are sending a couple of floating variables, we define exactly that – two floats. We then use this definition to create a structured data object.
Next is the sent callback function, which just prints some diagnostic data on the serial monitor.
In the Setup, we start the serial monitor and the DHT22. We do the usual and start the hESP32 in station mode before starting ESP-NOW.
We then register our callback and add our peer, as we saw in earlier sketches.
In the Loop, we query the DHT 22 and get the temperature and humidity values, which we assign to their respective variables. We also print those to the Serial Monitor.
We then add those values to the myData structured data object.
Then we send the data to the Responder.
After that we delay for a couple of seconds, this is for the benefit of the DHT22. Then we go back and start the Loop again.
Responder Sketch (Temperature Display)
As you have probably started to notice, the Responder sketches are much shorter than the Initiator ones. This is because we have been doing all of our work in our callback function, with no code in the Loop. There is also less to set up for a Responder, as it doesn’t need the Initiator’s MAC address.
Here is the sketch for our Responder, which receives and displays the temperature and humidity values sent by the Initiator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/* ESP-NOW Remote Sensor - Receiver esp-now-rcv.ino Receives Temperature & Humidity data from other ESP32 via ESP-NOW DroneBot Workshop 2022 https://dronebotworkshop.com */ // Include required libraries #include <WiFi.h> #include <esp_now.h> // Define data structure typedef struct struct_message { float a; float b; } struct_message; // Create structured data object struct_message myData; // Callback function void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { // Get incoming data memcpy(&myData, incomingData, sizeof(myData)); // Print to Serial Monitor Serial.print("Temp: "); Serial.println(myData.a); Serial.print("Humidity: "); Serial.println(myData.b); } void setup() { // Set up Serial Monitor Serial.begin(115200); // Start ESP32 in Station mode WiFi.mode(WIFI_STA); // Initalize ESP-NOW if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } // Register callback function esp_now_register_recv_cb(OnDataRecv); } void loop() { } |
We start with the usual two libraries, followed by a definition of a data structure with two floats, to match the transmitted one.
We then define our callback, which, like in other Responder sketches, does most of the work.
In this callback function, we grab the data and display it on the serial monitor.
The Setup is pretty straightforward, start everything and register the callback function. And there is nothing in the Loop, as the callback is what drives the serial monitor.
Testing the Remote Temperature Sensor.
Once again, you can use two serial monitors on the same machine if you wish, just open two instances of the Arduino IDE. Or you can choose to only monitor the Responder serial monitor, which will display the temperature and humidity values from the Initiator.
You should be able to move the initiator a fair distance from the Responder, so this can make a good remote sensor.
Multiple Sensors Modification
We can modify our Remote Temperature and Humidity Sensor project to use multiple Initiators. That way, we can monitor the temperature in more than one location.
We will just add one more board for this test, but you could expand it to several more. If you did expand it you’ll want to improve the Responder side to keep track of the data better, in this demonstration we are still using the serial monitor but an OLED would probably be more practical for a multi-sensor unit.
Of course, you will need an additional ESP32, along with a DHT22 and pull-up resistor for this experiment. It is wired identically to the first one.
We will also need to modify both of our sketches to make our multiple-sensor configuration more reliable. All we really need to add is a unique identifier for each Initiator so that it can be identified at the Responder end.
While we could have used the MAC address of the Initiator, it is a lot easier to define our own identity tag. In our case, we will just use an integer with a unique value for each Initiator.
Initiator Sketch Modifications
Here is the Initiator sketch, which will need to be modified with a unique identity value and then loaded onto both Temperature & Humidity sensor ESP32 boards.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
/* ESP-NOW Remote Sensor - Transmitter (Multiple Version) esp-now-xmit-multiple.ino Sends Temperature & Humidity data to other ESP32 via ESP-NOW Uses DHT22 Multiple Transmitter modification DroneBot Workshop 2022 https://dronebotworkshop.com */ // Include required libraries #include <WiFi.h> #include <esp_now.h> #include <DHT.h> // Define DHT22 parameters #define DHTPin 5 #define DHTType DHT22 // Create DHT Object DHT dht(DHTPin, DHTType); // Variables for temperature and humidity float temp; float humid; // Integer for identification (make unique for each transmitter) int ident = 2; // Responder MAC Address (Replace with your responders MAC Address) uint8_t broadcastAddress[] = {0x24, 0x0A, 0xC4, 0x04, 0xF4, 0x40}; // Define data structure typedef struct struct_message { float a; float b; int c; } struct_message; // Create structured data object struct_message myData; // Register peer esp_now_peer_info_t peerInfo; // Sent data callback function void OnDataSent(const uint8_t *macAddr, esp_now_send_status_t status) { Serial.print("Last Packet Send Status: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void setup() { // Setup Serial monitor Serial.begin(115200); delay(100); // Initiate DHT22 dht.begin(); // Set ESP32 WiFi mode to Station temporarly WiFi.mode(WIFI_STA); // Initialize ESP-NOW if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } // Define callback esp_now_register_send_cb(OnDataSent); memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Failed to add peer"); return; } } void loop() { // Read DHT22 module values temp = dht.readTemperature(); delay(10); humid = dht.readHumidity(); delay(10); Serial.print("Temp: "); Serial.println(temp); Serial.print("Humid: "); Serial.println(humid); // Add to structured data object myData.a = temp; myData.b = humid; myData.c = ident; // Send data esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); // Delay for DHT22 sensor delay(2000); } |
It’s the same initiator sketch, with a slight modification for the unique identity value. This value is sent along with the temperature and humidity information. This way, the sender can be identified on the Responder end.
There are only three modifications to make to our sketch.
First, we add an ident variable, an integer. This is the unique value, it needs to be set to a different value for each board.
The second modification is to the data structure, where we add a third data variable, an integer. This will be what holds our new identity integer value.
And finally, when we add the temperature and humidity to the structured data object, we also add the identity. So it gets transmitted along with the other information.
Responder Sketch Modifications
On the Responder side, we also make a few modifications to accommodate the new third variable, the integer with the Initiator identity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
/* ESP-NOW Remote Sensor - Receiver (Multiple Version) esp-now-rcv.ino Receives Temperature & Humidity data from other ESP32 via ESP-NOW DroneBot Workshop 2022 https://dronebotworkshop.com */ // Include required libraries #include <WiFi.h> #include <esp_now.h> // Define data structure typedef struct struct_message { float a; float b; int c; } struct_message; // Create structured data object struct_message myData; // Callback function void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { // Get incoming data memcpy(&myData, incomingData, sizeof(myData)); // Print to Serial Monitor Serial.print("Temp Sensor "); Serial.print(myData.c); Serial.print(": "); Serial.println(myData.a); Serial.print("Humidity Sensor "); Serial.print(myData.c); Serial.print(": "); Serial.println(myData.b); Serial.println(""); } void setup() { // Set up Serial Monitor Serial.begin(115200); // Start ESP32 in Station mode WiFi.mode(WIFI_STA); // Initalize ESP-NOW if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } // Register callback function esp_now_register_recv_cb(OnDataRecv); } void loop() { } |
The modification to the data structure is the same as it was for the transmitter, with the addition of an integer variable.
As most of our “action” in the Responder sketch is in the callback, this is where we use our new variable. We just incorporate it into the temperature and humidity reading, to identify the sender.
Running the Modified System
Load the modified sketches onto their respective boards, making sure to have changed the integer identity value for each Initiator.
Then turn it all on and observe the serial monitor on the Responder. You should see the temperature and humidity readings from both sensors.
If they are too fast, try increasing the delay on each board above the current 2 seconds.
This is more effective if the two transmitters are in separate environments, perhaps indoors and outdoors.
Conclusion
ESP-NOW is a very useful protocol that allows you to build even more advanced ESP32 projects.
For remote control and remote sensing applications, this has a lot of potential, and you’ll be seeing more ESP-NOW projects here in the workshop soon.
Parts List
Here are some components that you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase the cost to you and is a method of supporting this ad-free website.
COMING SOON!
Resources
ESP-NOW – Official documentation by Espressif
Code – All of the sketches used in this article, in a ZIP file.


Brilliant demo and elegant solution which saves the use of nRFxx or other transmitters which I use in similar meshed projects, at least within the shorter range coverage normally sufficient for home automation projects. I’m a keen follower and subscriber of your channel. Thank you.
Thank you very mych for this comprehensive tutorial on ESP communications. Others hsve tried to cover this topic and never came close to the clarity that you easily display here. Am glad I waited for it to come.
Can’t get this moving with esp8266. replaced the wifi.h with esp8266.h but now the error is ‘esp_now_peer_info_t’ does not name a type. Do I have to chuck out my 8266s and get esp32s to make this work?
I enjoyed your TUTORIAL
Love your Tutorials but HATE the circles on every page over important information as I usually print the tutorial out! is there a way to not have them cover information on every page?
Yet another clearly presented demonstration. I’m certainly going to be adding this capability to my ESP32 projects. I’ve learnt so much from you Bill. I’m a keen follower and subscriber since I found your channel earlier this year. I was struggling before then, but now I’m gaining confidence even more rapidly. Thank you so much and please keep these tips coming.
Well presented — no frills, no gimmicks. Well done!
You are amazing keep it up Sir
Well done with the explanation. I have to understand and get esp-now going quickly to replace the virtualwire link I usually use.
I have made a LED matrix scoreboard running DMD2, but the virtualwire for the remote control sharing of the timer clashes, so I am praying that I can run the DMD2 and esp-now ?
Always, wonderful presentation and teacher, thanks
This is a great ESP32 platform, but what would be the changes to make it ESP8266
environment?
I found this code works well with the ESP8266, and is almost the same as the DroneBot ESP32 tutorial:
https://randomnerdtutorials.com/esp-now-esp8266-nodemcu-arduino-ide/
I’m not sure if I’m allowed to show a link to another site, but I found I needed this information to get my ESP8266 system going.
This article and of course all of your previous ones are superb and excellent.
You are a row model on how tutorials must be presented to others.
Everything is so well explained, with plenty of preparation and wisely done.
What and how you do this tutorials are no obvious
A huge thank from me
Dany lipsker
Israel
Hi Great tutorials really enjoy your explinanations. I was wondering what happens if two esp now senders send at sams time ? Can both messages be received ?
Very good post which helped me a lot.
One coding error in the receiver code example line 27, there is a redundant blank space betweem the * and the variable mac which cause failure of delivery.
can we have library of it
Great Tutorial! Thought I was going to have to spend hours learning ESP-NOW. NOT! Using your tutorial and sample code I had things up-n-run’n with my own modifications in an afternoon. I previously tried “Arduino IOT Cloud” but found it a bit clunky and some of the concepts difficult, also, there is a fee. I needed to control a remote ESP32 with 3 commands. That led me here. Previously figured out how to use an old Ruku remote to talk an ESP, now the Initiator (in my home) which talks to the Responder out in the back 40. LOVE IT!… Read more »
Very Useful example and explaination
I would like to see some tutorials for three ESP32 to communicate with each other. Both ways to communicate. Each ESP32 will have two LEDs and two Buttons. I don’t know how to explain this. But I will try my best. First ESP32: 1st button (press to make the second ESP32 LED light on). 2nd button (press to make the third ESP32 LED Light on). Second ESP32: 1st button (press to make the first ESP32 LED light on). 2nd Button (press to make the third ESP32 LED light on). Third ESP32: 1st button (press to make the first ESP32 LED… Read more »
Votre exposé est très intéressant. Merci beaucoup…
Another great tutorial. I have the remote temperature Humidity sensor working great. Although I would like it to read in Fahrenheit. I have the formula just don’t know code to code it. Thanks.
Hi, thank you for articles, i’ve try all or your sketchs and that’s work fine !
my question is :
i want to transmit the signal of the I2c bus (not the value of the temp probe DS18B20 but the real signal)
i explain, i’ve a solar box with i2c bus and a DS18B20 but i need to extend it wirelessly, i can’t use câble.. so i wire a DS18B20 on the master and on the slave i want received not the value but same signal than the temperature probe of the master.. what can i do ?
best regards
Great material, but it all boils down to getting information from senders, not automating a process. Suppose there are several senders 200-300 meters from the receiver. Each sender is connected to a relay that turns something on or off based on readings from a sensor attached to each one. Is it possible for the receiver, receiving data from each sender, to return an instruction to each sender to turn a relay on or off, according to the sensor readings? All this based on the ESP-NOW protocol. Say through a LoRa Network.
Thank you for your Videos
please how can I change the Mac address for esp32