ESP32

ESP-NOW Two-Way Communication Between ESP32 Boards

In this guide, we’ll show you how to establish a two-way communication between two ESP32 boards using ESP-NOW communication protocol. As an example, two ESP32 boards will exchange sensor readings (with a range in open field up to 220 meters ~ 722 feet).

ESP-NOW Two-Way Communication Between ESP32 Boards

Watch the Video Introduction

For an introduction to ESP-NOW protocol, you can watch the following video:

If you want to learn more about ESP-NOW, you can read this guide: Getting Started with ESP-NOW (ESP32 with Arduino IDE).

Introducing ESP-NOW

ESP-NOW is a connectionless communication protocol developed by Espressif that features short packet transmission. This protocol enables multiple devices to talk to each other without using Wi-Fi.

ESP-NOW - ESP32 Logo

This is a fast communication protocol that can be used to exchange small messages (up to 250 bytes) between ESP32 boards. ESP-NOW is very versatile and you can have one-way or two-way communication in different arrangements.

In this tutorial, we’ll show you how to establish a two-way communication between two ESP32 boards.

ESP-NOW ESP32 Two-Way Communication Protocol

Note: read our ESP-NOW Getting Started Guide for a complete introduction to ESP-NOW protocol with ESP32.

Project Overview

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

ESP-NOW Two-Way Communication - Send Sensor Readings Between Boards
  • In this project we’ll have two ESP32 boards. Each board is connected to an OLED display and a BME280 sensor;
  • Each board gets temperature, humidity and pressure readings from their corresponding sensors;
  • Each board sends its readings to the other board via ESP-NOW;
  • When a board receives the readings, it displays them on the OLED display;
  • After sending the readings, the board displays on the OLED if the message was successfully delivered;
  • Each board needs to know the other board MAC address in order to send the message.

In this example, we’re using a two-way communication between two boards, but you can add more boards to this setup, and having all boards communicating with each other.

Prerequisites

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

ESP32 add-on Arduino IDE

We’ll program the ESP32 using Arduino IDE, so before proceeding with this tutorial you should have the ESP32 add-on installed in your Arduino IDE. Follow the next guide:

Install libraries

Install the following libraries in your Arduino IDE. These libraries can be installed through the Arduino Library Manager. Go to Sketch Include LibraryManage Libraries and search for the library name.

Parts Required

For this tutorial you need the following parts:

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 Boards MAC Address

To send messages between each board, we need to know their MAC address. Each board has a unique MAC address (learn how to Get and Change the ESP32 MAC Address).

Upload the following code to each of your boards to get their 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.
*/
#include <WiFi.h>
#include <esp_wifi.h>

void readMacAddress(){
  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");
  }
}

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

  WiFi.mode(WIFI_STA);
  WiFi.STA.begin();

  Serial.print("[DEFAULT] ESP32 Board MAC Address: ");
  readMacAddress();
}
 
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

Write down the MAC address of each board to clearly identify them.

ESP32 board MAC Address label

Schematic Diagram

Wire an OLED display and a BME280 sensor to each ESP32 board. Follow the next schematic diagram.

ESP32 wiring schematic diagram to BME280 sensor and OLED display

You can use the following table as a reference when wiring the BME280 sensor.

BME280ESP32
VIN3.3V
GNDGND
SCLGPIO 22
SDAGPIO 21

You can also follow the next table to wire the OLED display to the ESP32.

OLED DisplayESP32
GNDGND
VCCVIN
SCLGPIO 22
SDAGPIO 21

Learn more about interfacing multiple I2C peripherals with the ESP32.

ESP32 Two-Way Communication ESP-NOW Code

Upload the following code to each of your boards. Before uploading the code, you need to enter the MAC address of the other board (the board you’re sending data to).

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp-now-two-way-communication-esp32/
  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 <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

Adafruit_BME280 bme;

// REPLACE WITH THE MAC Address of your receiver 
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// Define variables to store BME280 readings to be sent
float temperature;
float humidity;
float pressure;

// Define variables to store incoming readings
float incomingTemp;
float incomingHum;
float incomingPres;

// Variable to store if sending data was successful
String success;

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

// Create a struct_message called BME280Readings to hold sensor readings
struct_message BME280Readings;

// Create a struct_message to hold incoming sensor readings
struct_message incomingReadings;

esp_now_peer_info_t peerInfo;

// 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");
  if (status ==0){
    success = "Delivery Success :)";
  }
  else{
    success = "Delivery Fail :(";
  }
}

// Callback when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
  Serial.print("Bytes received: ");
  Serial.println(len);
  incomingTemp = incomingReadings.temp;
  incomingHum = incomingReadings.hum;
  incomingPres = incomingReadings.pres;
}
 
void setup() {
  // Init Serial Monitor
  Serial.begin(115200);

  // Init BME280 sensor
  bool status = bme.begin(0x76);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  // Init OLED display
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
 
  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // 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.channel = 0;  
  peerInfo.encrypt = false;
  
  // Add peer        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
  // Register for a callback function that will be called when data is received
  esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
}
 
void loop() {
  getReadings();
 
  // Set values to send
  BME280Readings.temp = temperature;
  BME280Readings.hum = humidity;
  BME280Readings.pres = pressure;

  // Send message via ESP-NOW
  esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &BME280Readings, sizeof(BME280Readings));
   
  if (result == ESP_OK) {
    Serial.println("Sent with success");
  }
  else {
    Serial.println("Error sending the data");
  }
  updateDisplay();
  delay(10000);
}
void getReadings(){
  temperature = bme.readTemperature();
  humidity = bme.readHumidity();
  pressure = (bme.readPressure() / 100.0F);
}

void updateDisplay(){
  // Display Readings on OLED Display
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("INCOMING READINGS");
  display.setCursor(0, 15);
  display.print("Temperature: ");
  display.print(incomingTemp);
  display.cp437(true);
  display.write(248);
  display.print("C");
  display.setCursor(0, 25);
  display.print("Humidity: ");
  display.print(incomingHum);
  display.print("%");
  display.setCursor(0, 35);
  display.print("Pressure: ");
  display.print(incomingPres);
  display.print("hPa");
  display.setCursor(0, 56);
  display.print(success);
  display.display();
  
  // Display Readings in Serial Monitor
  Serial.println("INCOMING READINGS");
  Serial.print("Temperature: ");
  Serial.print(incomingReadings.temp);
  Serial.println(" ºC");
  Serial.print("Humidity: ");
  Serial.print(incomingReadings.hum);
  Serial.println(" %");
  Serial.print("Pressure: ");
  Serial.print(incomingReadings.pres);
  Serial.println(" hPa");
  Serial.println();
}

View raw code

How the code works

We’ve covered in great detail how to interact with the OLED display and with the BME280 sensor in previous tutorials. Here, we’ll just take a look at the relevant parts when it comes to ESP-NOW.

The code is well commented so that you understand what each line of code does.

To use ESP-NOW, you need to include the next libraries.

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

In the next line, insert the MAC address of the receiver board:

uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64};

Create variables to store temperature, humidity and pressure readings from the BME280 sensor. These readings will be sent to the other board:

// Define variables to store BME280 readings to be sent
float temperature;
float humidity;
float pressure;

Create variables to store the sensor readings coming from the other board:

// Define variables to store incoming readings
float incomingTemp;
float incomingHum;
float incomingPres;

The following variable will store a success message if the readings are delivered successfully to the other board.

// Variable to store if sending data was successful
String success;

Create a structure that stores humidity, temperature and pressure readings.

typedef struct struct_message {
  float temp;
  float hum;
  float pres;
} struct_message;

Then, you need to create two instances of that structure. One to receive the readings and another to store the readings to be sent.

The BME280Readings will store the readings to be sent.

// Create a struct_message called BME280Readings to hold sensor readings
struct_message BME280Readings;

The incomingReadings will store the data coming from the other board.

// Create a struct_message to hold incoming sensor readings
struct_message incomingReadings;

Create a variable of type esp_now_peer_info_t to store information about the peer.

esp_now_peer_info_t peerInfo;

Then, we need to create two callback functions. One will be called when data is sent, and another will be called when data is received.

OnDataSent() callback function

The OnDataSent() function will be called when new data is sent. This function simply prints if the message was successfully delivered or not. If the message is delivered successfully, the status variable returns 0, so we can set our success message to “Delivery Success”:

Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
if (status ==0) {
  success = "Delivery Success :)";
}

If the success message returns 1, it means the delivery failed:

else {
  success = "Delivery Fail :(";
}

OnDataRecv() callback function

The OnDataRecv() function will be called when a new packet arrives.

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

We save the new packet in the incomingReadings structure we’ve created previously:

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

We print the message length on the serial monitor. You can only send 250 bytes in each packet.

Serial.print("Bytes received: ");
Serial.println(len);

Then, store the incoming readings in their corresponding variables. To access the temperature variable inside incomingReadings structure, you just need to do call incomingReadings.temp as follows:

incomingTemp = incomingReadings.temp;

The same process is done for the other variables:

incomingHum = incomingReadings.hum;
incomingPres = incomingReadings.pres;

setup()

In the setup(), initialize ESP-NOW.

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

Then, register for the OnDataSent callback function.

esp_now_register_send_cb(OnDataSent);

In order to send data to another board, you need to pair it as a peer. The following lines register and add a new peer.

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

Register for the OnDataRecv callback function.

 esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));

loop()

In the loop(), we call the getReadings() function that is responsible for getting new temperature readings from the sensor. That function is created after the loop().

After getting new temperature, humidity and pressure readings, we update our BME280Reading structure with those new values:

BME280Readings.temp = temperature;
BME280Readings.hum = humidity;
BME280Readings.pres = pressure;

Then, we can send the BME280Readings structure via ESP-NOW:

// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &BME280Readings, sizeof(BME280Readings));

if (result == ESP_OK) {
  Serial.println("Sent with success");
}
else {
  Serial.println("Error sending the data");
}

Finally, call the updateDisplay() function that will update the OLED display with the readings coming from the other ESP32 board.

updateDisplay();

The loop() is executed every 10 seconds.

That’s pretty much how the code works. You should upload the code to both of your boards. You just need to modify the code with the MAC address of the board you’re sending data to.

Recommended reading: Guide for OLED Display with ESP32 and Guide for BME280 Sensor with ESP32.

Demonstration

After uploading the code to both boards, you should see the OLED displaying the sensor readings from the other board, as well as a success delivery message.

ESP32 ESP-NOW Protocol Exchange Data Demonstration

As you can see, it’s working as expected:

ESP32 ESP-NOW Protocol Exchange Data Demonstration

We tested the communication range between the two boards, and we are able to get a stable communication up to 220 meters (approximately 722 feet) in open field. In this experiment both ESP32 on-board antennas were pointing at each other.

ESP-NOW communication range test with ESP32 boards
Upcoming Course
Upcoming Course
Learn More
Instructor Tips
Instructor Tips
View Tips
Join Community
Join Community
Join Now