ESP32

ESP32: ESP-NOW Web Server Sensor Dashboard (ESP-NOW + Wi-Fi)

In this project, you’ll learn how to host an ESP32 web server and use ESP-NOW communication protocol simultaneously. You can have several ESP32 boards sending sensor readings via ESP-NOW to one ESP32 receiver that displays all readings on a web server. The boards will be programmed using Arduino IDE.

ESP32: ESP-NOW Web Server Sensor Dashboard using Arduino IDE (ESP-NOW and Wi-Fi simultaneously)

We have other guides related to ESP-NOW that you might be interested in:

Watch the Video Tutorial

Note: we’ve updated this tutorial with a better method to use ESP-NOW and Wi-Fi simultaneously. The video doesn’t use this current method. You can still watch the video to see how everything works, but we recommend that you take a look at the written article.

Using ESP-NOW and Wi-Fi Simultaneously

Using ESP-NOW and Wi-Fi Simultaneously: ESP-NOW Receiver Web Server and ESP-NOW Sender boards

There are a few things you need to take into account if you want to use Wi-Fi to host a web server and use ESP-NOW simultaneously to receive sensor readings from other boards:

  • The ESP32 sender boards must use the same Wi-Fi channel of the receiver board.
  • The Wi-Fi channel of the receiver board is automatically assigned by your Wi-Fi router.
  • The Wi-Fi mode of the receiver board must be access point and station (WIFI_AP_STA).
  • You can set up the same Wi-Fi channel manually, or you can add a simple spinet of code on the sender to set its Wi-Fi channel to the same of the receiver board.

Project Overview

The following diagram shows a high-level overview of the project we’ll build.

ESP-NOW Receiver Web Server and ESP32 boards sending temperature humidity readings with ESP-NOW
  • There are two ESP32 sender boards that send DHT22 temperature and humidity readings via ESP-NOW to one ESP32 receiver board (ESP-NOW many to one configuration);
  • The ESP32 receiver board receives the packets and displays the readings on a web server;
  • The web server is updated automatically every time it receives a new reading using Server-Sent Events (SSE).

Prerequisites

Before proceeding with this project, make sure you check the following prerequisites.

Arduino IDE

We’ll program the ESP32 boards using Arduino IDE, so before proceeding with this tutorial, make sure you have the ESP32 board installed in your Arduino IDE.

Installing Libraries

For this project, you need to install the following libraries:

You can install the Libraries via the Arduino Library Manager. Go to Sketch Include Library > Manage Libraries and search for the libraries’ names to install them.

To learn more about the DHT11 or DHT22 temperature sensor, read our guide: ESP32 with DHT11/DHT22 Temperature and Humidity Sensor using Arduino IDE.

Parts Required

To follow this tutorial, you need multiple ESP32 boards. We’ll use three ESP32 boards. You also need:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

header-200.png?w=828&quality=100&strip=all&ssl=1

Getting the Receiver Board MAC Address

To send messages via ESP-NOW, you need to know the receiver board’s MAC address. Each board has a unique MAC address (learn how to Get and Change the ESP32 MAC Address).

Upload the following code to your ESP32 receiver board to get its MAC address.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.  
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#ifdef ESP32
  #include <WiFi.h>
  #include <esp_wifi.h>
#else
  #include <ESP8266WiFi.h>
#endif

void setup(){
  Serial.begin(115200);

  Serial.print("ESP Board MAC Address: ");
  #ifdef ESP32
    WiFi.mode(WIFI_STA);
    WiFi.STA.begin();
    uint8_t baseMac[6];
    esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
    if (ret == ESP_OK) {
      Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
                    baseMac[0], baseMac[1], baseMac[2],
                    baseMac[3], baseMac[4], baseMac[5]);
    } else {
      Serial.println("Failed to read MAC address");
    }
  #else
    Serial.println(WiFi.macAddress());
  #endif
}
 
void loop(){

}

View raw code

After uploading the code, press the RST/EN button, and the MAC address should be displayed on the Serial Monitor.

ESP32 board MAC Address with Arduino IDE Serial Monitor

ESP32 Receiver (ESP-NOW + Web Server)

The ESP32 receiver board receives the packets from the sender boards and hosts a web server to display the latest received readings.

Upload the following code to your receiver board – the code is prepared to receive readings from two different boards.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-esp-now-wi-fi-web-server/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.  
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <esp_now.h>
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#include <Arduino_JSON.h>

// Replace with your network credentials (STATION)
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
  int id;
  float temp;
  float hum;
  unsigned int readingId;
} struct_message;

struct_message incomingReadings;

JSONVar board;

AsyncWebServer server(80);
AsyncEventSource events("/events");

// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { 
  // Copies the sender mac address to a string
  char macStr[18];
  Serial.print("Packet received from: ");
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.println(macStr);
  memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
  
  board["id"] = incomingReadings.id;
  board["temperature"] = incomingReadings.temp;
  board["humidity"] = incomingReadings.hum;
  board["readingId"] = String(incomingReadings.readingId);
  String jsonString = JSON.stringify(board);
  events.send(jsonString.c_str(), "new_readings", millis());
  
  Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
  Serial.printf("t value: %4.2f \n", incomingReadings.temp);
  Serial.printf("h value: %4.2f \n", incomingReadings.hum);
  Serial.printf("readingID value: %d \n", incomingReadings.readingId);
  Serial.println();
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP-NOW DASHBOARD</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    p {  font-size: 1.2rem;}
    body {  margin: 0;}
    .topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 1.7rem; }
    .content { padding: 20px; }
    .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
    .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
    .reading { font-size: 2.8rem; }
    .packet { color: #bebebe; }
    .card.temperature { color: #fd7e14; }
    .card.humidity { color: #1b78e2; }
  </style>
</head>
<body>
  <div class="topnav">
    <h3>ESP-NOW DASHBOARD</h3>
  </div>
  <div class="content">
    <div class="cards">
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> BOARD #1 - TEMPERATURE</h4><p><span class="reading"><span id="t1"></span> &deg;C</span></p><p class="packet">Reading ID: <span id="rt1"></span></p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> BOARD #1 - HUMIDITY</h4><p><span class="reading"><span id="h1"></span> &percnt;</span></p><p class="packet">Reading ID: <span id="rh1"></span></p>
      </div>
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> BOARD #2 - TEMPERATURE</h4><p><span class="reading"><span id="t2"></span> &deg;C</span></p><p class="packet">Reading ID: <span id="rt2"></span></p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> BOARD #2 - HUMIDITY</h4><p><span class="reading"><span id="h2"></span> &percnt;</span></p><p class="packet">Reading ID: <span id="rh2"></span></p>
      </div>
    </div>
  </div>
<script>
if (!!window.EventSource) {
 var source = new EventSource('/events');
 
 source.addEventListener('open', function(e) {
  console.log("Events Connected");
 }, false);
 source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
 }, false);
 
 source.addEventListener('message', function(e) {
  console.log("message", e.data);
 }, false);
 
 source.addEventListener('new_readings', function(e) {
  console.log("new_readings", e.data);
  var obj = JSON.parse(e.data);
  document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
  document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
  document.getElementById("rt"+obj.id).innerHTML = obj.readingId;
  document.getElementById("rh"+obj.id).innerHTML = obj.readingId;
 }, false);
}
</script>
</body>
</html>)rawliteral";

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);

  // Set the device as a Station and Soft Access Point simultaneously
  WiFi.mode(WIFI_AP_STA);
  
  // Set device as a Wi-Fi Station
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Setting as a Wi-Fi Station..");
  }
  Serial.print("Station IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Wi-Fi Channel: ");
  Serial.println(WiFi.channel());

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info
  esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/html", index_html);
  });
   
  events.onConnect([](AsyncEventSourceClient *client){
    if(client->lastId()){
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // send event with message "hello!", id current millis
    // and set reconnect delay to 1 second
    client->send("hello!", NULL, millis(), 10000);
  });
  server.addHandler(&events);
  server.begin();
}
 
void loop() {
  static unsigned long lastEventTime = millis();
  static const unsigned long EVENT_INTERVAL_MS = 5000;
  if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
    events.send("ping",NULL,millis());
    lastEventTime = millis();
  }
}

View raw code

How the Code Works

First, include the necessary libraries.

#include <esp_now.h>
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#include <Arduino_JSON.h>

The Arduino_JSON library is needed because we’ll create a JSON variable with the data received from each board. This JSON variable will be used to send all the needed information to the web page as you’ll see later in this project.

Insert your network credentials on the following lines so that the ESP32 can connect to your local network.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Data Structure

Then, create a structure that contains the data we’ll receive. We called this structure struct_message and it contains the board ID, temperature and humidity readings, and the reading ID.

typedef struct struct_message {
    int id;
    float temp;
    float hum;
    int readingId;
} struct_message;

Create a new variable of type struct_message that is called incomingReadings that will store the variables’ values.

struct_message incomingReadings;

Create a JSON variable called board.

JSONVar board;

Create an Async Web Server on port 80.

AsyncWebServer server(80);

Create Event Source

To automatically display the information on the web server when a new reading arrives, we’ll use Server-Sent Events (SSE).

The following line creates a new event source on /events.

AsyncEventSource events("/events");

Server-Sent Events allow a web page (client) to get updates from a server. We’ll use this to automatically display new readings on the web server page when a new ESP-NOW packet arrives.

Important: Server-sent events are not supported on Internet Explorer.

OnDataRecv() function

The OnDataRecv() function will be executed when you receive a new ESP-NOW packet.

void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {

Inside that function, print the sender’s MAC address:

// Copies the sender mac address to a string
char macStr[18];
Serial.print("Packet received from: ");
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
         mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.println(macStr);

Copy the information in the incomingData variable into the incomingReadings structure variable.

memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));

Then, create a JSON String variable with the received information (jsonString variable):

board["id"] = incomingReadings.id;
board["temperature"] = incomingReadings.temp;
board["humidity"] = incomingReadings.hum;
board["readingId"] = String(incomingReadings.readingId);
String jsonString = JSON.stringify(board);

Here’s an example of how the jsonString variable may look like after receiving the readings:

board = {
  "id": "1",
  "temperature": "24.32",
  "humidity" = "65.85",
  "readingId" = "2"
}

After gathering all the received data on the jsonString variable, send that information to the browser as an event (“new_readings”).

events.send(jsonString.c_str(), "new_readings", millis());

Later, we’ll see how to handle these events on the client side.

Finally, print the received information on the Arduino IDE Serial Monitor for debugging purposes:

Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
Serial.printf("t value: %4.2f \n", incomingReadings.temp);
Serial.printf("h value: %4.2f \n", incomingReadings.hum);
Serial.printf("readingID value: %d \n", incomingReadings.readingId);
Serial.println();

Building the Web Page

The index_html variable contains all the HTML, CSS and JavaScript to build the web page. We won’t go into details on how the HTML and CSS works. We’ll just take a look at how to handle the events sent by the server.

Handle Events

Create a new EventSource object and specify the URL of the page sending the updates. In our case, it’s /events.

if (!!window.EventSource) {
 var source = new EventSource('/events');

Once you’ve instantiated an event source, you can start listening for messages from the server with addEventListener().

These are the default event listeners, as shown here in the AsyncWebServer documentation.

source.addEventListener('open', function(e) {
  console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
 if (e.target.readyState != EventSource.OPEN) {
   console.log("Events Disconnected");
 }
}, false);

source.addEventListener('message', function(e) {
 console.log("message", e.data);
}, false);

Then, add the event listener for “new_readings”.

source.addEventListener('new_readings', function(e) {

When the ESP32 receives a new packet, it sends a JSON string with the readings as an event (“new_readings”) to the client. The following lines handle what happens when the browser receives that event.

console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
document.getElementById("rt"+obj.id).innerHTML = obj.readingId;
document.getElementById("rh"+obj.id).innerHTML = obj.readingId;

Basically, print the new readings on the browser console, and put the received data into the elements with the corresponding id on the web page.

setup()

In the setup(), set the ESP32 receiver as an access point and Wi-Fi station:

WiFi.mode(WIFI_AP_STA);

The following lines connect the ESP32 to your local network and print the IP address and the Wi-Fi channel:

// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());

Initialize ESP-NOW.

if (esp_now_init() != ESP_OK) {
  Serial.println("Error initializing ESP-NOW");
  return;
}

Register for the OnDataRecv callback function, so that it is executed when a new ESP-NOW packet arrives.

esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));

Handle Requests

When you access the ESP32 IP address on the root / URL, send the text that is stored on the index_html variable to build the web page.

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html);
});

Server Event Source

Set up the event source on the server.

events.onConnect([](AsyncEventSourceClient *client){
  if(client->lastId()){
    Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
  }
  // send event with message "hello!", id current millis
  // and set reconnect delay to 1 second
  client->send("hello!", NULL, millis(), 10000);
 );
  server.addHandler(&events);

Finally, start the server.

server.begin();

loop()

In the loop(), send a ping every 5 seconds. This is used to check on the client side, if the server is still running.

static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
  events.send("ping",NULL,millis());
  lastEventTime = millis();
}

The following diagram summarizes how the Server-sent Events work on this project and how it updates the values without refreshing the web page.

ESP32 ESP-NOW Web Server Sensor Dashboard Project Overview

After uploading the code to the receiver board, press the on-board EN/RST button. The ESP32 IP address should be printed on the Serial Monitor as well as the Wi-Fi channel.

ESP-NOW get ESP32 IP address and Wi-Fi channel

ESP32 Sender Circuit

The ESP32 sender boards are connected to a DHT22 temperature and humidity sensor. The data pin is connected to GPIO 4. You can choose any other suitable GPIO (read ESP32 Pinout Guide). Follow the next schematic diagram to wire the circuit.

Wiring ESP32 to DHT22 temperature and humidity sensor

ESP32 Sender Code (ESP-NOW)

Each sender board will send a structure via ESP-NOW that contains the board ID (so that you can identify which board sent the readings), the temperature, the humidity, and the reading ID. The reading ID is an int number to know how many messages were sent.

ESP32 Sender Receiver Board with ESP-NOW using Arduino IDE

Upload the following code to each of your sender boards. Don’t forget to increment the id number for each sender board and insert your SSID in the WIFI_SSID variable.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-esp-now-wi-fi-web-server/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <esp_now.h>
#include <esp_wifi.h>
#include <WiFi.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>

// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 1

// Digital pin connected to the DHT sensor
#define DHTPIN 4  

// Uncomment the type of sensor in use:
//#define DHTTYPE    DHT11     // DHT 11
#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE);

//MAC Address of the receiver 
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

//Structure example to send data
//Must match the receiver structure
typedef struct struct_message {
    int id;
    float temp;
    float hum;
    int readingId;
} struct_message;

esp_now_peer_info_t peerInfo;

//Create a struct_message called myData
struct_message myData;

unsigned long previousMillis = 0;   // Stores last time temperature was published
const long interval = 10000;        // Interval at which to publish sensor readings

unsigned int readingId = 0;

// Insert your SSID
constexpr char WIFI_SSID[] = "REPLACE_WITH_YOUR_SSID";

int32_t getWiFiChannel(const char *ssid) {
  if (int32_t n = WiFi.scanNetworks()) {
      for (uint8_t i=0; i<n; i++) {
          if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
              return WiFi.channel(i);
          }
      }
  }
  return 0;
}

float readDHTTemperature() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  // Check if any reads failed and exit early (to try again).
  if (isnan(t)) {    
    Serial.println("Failed to read from DHT sensor!");
    return 0;
  }
  else {
    Serial.println(t);
    return t;
  }
}

float readDHTHumidity() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  if (isnan(h)) {
    Serial.println("Failed to read from DHT sensor!");
    return 0;
  }
  else {
    Serial.println(h);
    return h;
  }
}

// callback 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() {
  //Init Serial Monitor
  Serial.begin(115200);

  dht.begin();
 
  // Set device as a Wi-Fi Station and set channel
  WiFi.mode(WIFI_STA);

  int32_t channel = getWiFiChannel(WIFI_SSID);

  WiFi.printDiag(Serial); // Uncomment to verify channel number before
  esp_wifi_set_promiscuous(true);
  esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous(false);
  WiFi.printDiag(Serial); // Uncomment to verify channel change after

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of Trasnmitted packet
  esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent));
  
  // Register peer
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.encrypt = false;
  
  // Add peer        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
}
 
void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // Save the last time a new reading was published
    previousMillis = currentMillis;
    //Set values to send
    myData.id = BOARD_ID;
    myData.temp = readDHTTemperature();
    myData.hum = readDHTHumidity();
    myData.readingId = readingId++;
     
    //Send message via ESP-NOW
    esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
    if (result == ESP_OK) {
      Serial.println("Sent with success");
    }
    else {
      Serial.println("Error sending the data");
    }
  }
}

View raw code

How the Code Works

Start by importing the required libraries:

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

Set Board ID

Define the ESP32 sender board ID, for example set BOARD_ID 1 for ESP32 Sender #1, etc…

// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 1

DHT Sensor

Define the pin the DHT sensor is connected to. In our example, it is connected to GPIO 4.

#define DHTPIN 4

Select the DHT sensor type you’re using. We’re using the DHT22.

// Uncomment the type of sensor in use:
//#define DHTTYPE    DHT11     // DHT 11
#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

Create a DHT object on the pin and type defined earlier.

DHT dht(DHTPIN, DHTTYPE);

Receiver’s MAC Address

Insert the receiver’s MAC address on the next line (for example):

uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x15, 0xC7, 0xFC};

Data Structure

Then, create a structure that contains the data we want to send. The struct_message contains the board ID, temperature reading, humidity reading, and the reading ID.

typedef struct struct_message {
    int id;
    float temp;
    float hum;
    int readingId;
} struct_message;

Create a new variable of type struct_message that is called myData that stores the variables’ values.

struct_message myData;

Timer Interval

Create some auxiliary timer variables to publish the readings every 10 seconds. You can change the delay time on the interval variable.

unsigned long previousMillis = 0;  // Stores last time temperature was published
const long interval = 10000;       // Interval at which to publish sensor readings

Initialize the readingId variable – it keeps track of the number of readings sent.

unsigned int readingId = 0;

Changing Wi-Fi channel

Now, we’ll get the receiver’s Wi-Fi channel. This is useful because it allows us to automatically assign the same Wi-Fi channel to the sender board.

To do that, you must insert your SSID in the following line:

constexpr char WIFI_SSID[] = "REPLACE_WITH_YOUR_SSID";

Then, the getWiFiChannel() function scans for your network and gets its channel.

int32_t getWiFiChannel(const char *ssid) {
  if (int32_t n = WiFi.scanNetworks()) {
    for (uint8_t i=0; i<n; i++) {
      if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
        return WiFi.channel(i);
      }
    }
  }
  return 0;
}

This snippet of code was proposed by Stephane (one of our readers). You can see his complete example here.

Reading Temperature

The readDHTTemperature() function reads and returns the temperature from the DHT sensor. In case it is not able to get temperature readings, it returns 0.

float readDHTTemperature() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  // Check if any reads failed and exit early (to try again).
  if (isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    return 0;
  }
  else {
    Serial.println(t);
    return t;
  }
}

Reading Humidity

The readDHTHumidity() function reads and returns the humidity from the DHT sensor. In case it is not able to get humidity readings, it returns 0.

float readDHTHumidity() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  if (isnan(h)) {
    Serial.println("Failed to read from DHT sensor!");
    return 0;
  }
  else {
    Serial.println(h);
    return h;
  }
}

Note: to learn more about getting temperature and humidity from the DHT22 or DHT11 sensors, read: ESP32 with DHT11/DHT22 Temperature and Humidity Sensor using Arduino IDE.

OnDataSent Callback Function

The OnDataSent() callback function will be executed when a message is sent. In this case, this function prints if the message was successfully delivered or not.

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");
}

setup()

Initialize the Serial Monitor.

Serial.begin(115200);

Set the ESP32 as a Wi-Fi station.

WiFi.mode(WIFI_STA);

Set its channel to match the receiver’s Wi-Fi channel:

int32_t channel = getWiFiChannel(WIFI_SSID);

WiFi.printDiag(Serial); // Uncomment to verify channel number before
esp_wifi_set_promiscuous(true);
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
esp_wifi_set_promiscuous(false);
WiFi.printDiag(Serial); // Uncomment to verify channel change after

Initialize ESP-NOW.

if (esp_now_init() != ESP_OK) {
  Serial.println("Error initializing ESP-NOW");
  return;
}

After successfully initializing ESP-NOW, register the callback function that will be called when a message is sent. In this case, register for the OnDataSent() function created previously.

esp_now_register_send_cb(OnDataSent);

Add peer

To send data to another board (the receiver), you need to pair it as a peer. The following lines register and add the receiver as a peer.

// Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.encrypt = false;

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

loop()

In the loop(), check if it is time to get and send new readings.

unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
  // Save the last time a new reading was published
  previousMillis = currentMillis;

Send ESP-NOW Message

Finally, send the message structure via ESP-NOW.

// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
if (result == ESP_OK) {
  Serial.println("Sent with success");
}
else {
  Serial.println("Error sending the data");
}

Recommended reading: Getting Started with ESP-NOW (ESP32 with Arduino IDE)

Upload the code to your sender boards. You should notice that the boards change their Wi-Fi channel to the channel of the receiver board. In our case, the boards changed their Wi-Fi channel number to 6.

Demonstration

After uploading the code to all the boards and if everything is going as expected, the ESP32 receiver board should start receiving sensor readings from the other boards.

ESP32 ESP-NOW Web Server Sensor Dashboard ESP-NOW and Wi-Fi Demonstration Sensor Readings

Open a browser on your local network and type the ESP32 IP address.

ESP32 ESP-NOW Web Server Sensor Dashboard Web Browser

It should load the temperature, humidity, and reading IDs for each board. Upon receiving a new packet, your web page updates automatically without refreshing the web page.

ESP32 ESP-NOW Web Server Sensor Dashboard Mobile Responsive
Upcoming Course
Upcoming Course
Learn More
Instructor Tips
Instructor Tips
View Tips
Join Community
Join Community
Join Now