ESP-NOW Two-Way Communication Between ESP8266 NodeMCU Boards

Learn how to establish a two-way communication between two ESP8266 NodeMCU boards using ESP-NOW communication protocol. As an example, two ESP8266 boards will exchange DHT sensor readings. We’ll use Arduino IDE.

ESP-NOW Two-Way Communication Between ESP8266 NodeMCU Boards

We have other tutorials about ESP-NOW with the ESP8266:

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 ESP8266 boards.

ESP-NOW ESP8266 Two-Way Communication

Project Overview

The following diagram shows an overview of the project we’ll build:

ESP8266 ESP-NOW Two-Way Communication Exchange DHT Sensor Readings
  • In this project we’ll have two ESP8266 boards. Each board is connected to a DHT sensor;
  • Each board gets temperature and humidity readings from their corresponding sensors;
  • Each board sends its readings to the other board via ESP-NOW;
  • When a board receives the readings, it prints them on the Serial Monitor. You can connect an OLED display to visualize the readings, for example.
  • When a message is sent, a message is printed indicating if the packet was successfully delivered or not.
  • 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.

ESP8266 add-on Arduino IDE

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

Installing DHT Libraries

To read from the DHT sensor, we’ll use the DHT library from Adafruit. To use this library you also need to install the Adafruit Unified Sensor library. Follow the next steps to install those libraries.

1. Open your Arduino IDE and go to Sketch Include Library > Manage Libraries. The Library Manager should open.

2. Search for “DHT” on the Search box and install the DHT library from Adafruit.

Installing Adafruit DHT library

3. After installing the DHT library from Adafruit, type “Adafruit Unified Sensor” in the search box. Scroll all the way down to find the library and install it.

Installing Adafruit Unified Sensor driver library

After installing the libraries, restart your Arduino IDE.

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!

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.

// Complete Instructions to Get and Change ESP MAC Address: https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/

#include "WiFi.h"
 
void setup(){
  Serial.begin(115200);
  WiFi.mode(WIFI_MODE_STA);
  Serial.println(WiFi.macAddress());
}
 
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.

Get ESP8266 MAC Address Serial Monitor

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

ESP8266 NodeMCU board Get MAC Address

ESP8266 and DHT11/DHT22 Schematic Diagram

Before proceeding with the tutorial, wire the DHT11 or DHT22 temperature and humidity sensor to the ESP8266 as shown in the following schematic diagram.

ESP8266 DHT11 DHT22 Schematic Diagram Circuit

In this example, we’re wiring the DHT data pin to GPIO5 (D1), but you can use any other suitable GPIO. Read our ESP8266 GPIO Reference Guide to learn more about the ESP8266 GPIOs.

You might like: ESP8266 DHT11/DHT22 Temperature and Humidity Web Server with Arduino IDE

ESP8266 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
  Complete project details at https://RandomNerdTutorials.com/esp-now-two-way-communication-esp8266-nodemcu/
  
  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 <ESP8266WiFi.h>
#include <espnow.h>

#include <Adafruit_Sensor.h>
#include <DHT.h>

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

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

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

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

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

// Updates DHT readings every 10 seconds
const long interval = 10000; 
unsigned long previousMillis = 0;    // will store last time DHT was updated 

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

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

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

// Callback when data is sent
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  Serial.print("Last Packet Send Status: ");
  if (sendStatus == 0){
    Serial.println("Delivery success");
  }
  else{
    Serial.println("Delivery fail");
  }
}

// Callback when data is received
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {
  memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
  Serial.print("Bytes received: ");
  Serial.println(len);
  incomingTemp = incomingReadings.temp;
  incomingHum = incomingReadings.hum;
}

void getReadings(){
  // Read Temperature
  temperature = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  if (isnan(temperature)){
    Serial.println("Failed to read from DHT");
    temperature = 0.0;
  }
  humidity = dht.readHumidity();
  if (isnan(humidity)){
    Serial.println("Failed to read from DHT");
    humidity = 0.0;
  }
}

void printIncomingReadings(){
  // Display Readings in Serial Monitor
  Serial.println("INCOMING READINGS");
  Serial.print("Temperature: ");
  Serial.print(incomingTemp);
  Serial.println(" ºC");
  Serial.print("Humidity: ");
  Serial.print(incomingHum);
  Serial.println(" %");
}
 
void setup() {
  // Init Serial Monitor
  Serial.begin(115200);

  // Init DHT sensor
  dht.begin();
 
  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

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

  // Set ESP-NOW Role
  esp_now_set_self_role(ESP_NOW_ROLE_COMBO);

  // Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of Trasnmitted packet
  esp_now_register_send_cb(OnDataSent);
  
  // Register peer
  esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_COMBO, 1, NULL, 0);
  
  // Register for a callback function that will be called when data is received
  esp_now_register_recv_cb(OnDataRecv);
}
 
void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // save the last time you updated the DHT values
    previousMillis = currentMillis;

    //Get DHT readings
    getReadings();

    //Set values to send
    DHTReadings.temp = temperature;
    DHTReadings.hum = humidity;

    // Send message via ESP-NOW
    esp_now_send(broadcastAddress, (uint8_t *) &DHTReadings, sizeof(DHTReadings));

    // Print incoming readings
    printIncomingReadings();
  }
}

View raw code

How the Code Works

Keep reading to learn how the code works, or skip to the Demonstration section.

Import libraries

Start by importing the required libraries.

#include <ESP8266WiFi.h>
#include <espnow.h>

The espnow.h library comes installed by default when you install the ESP8266 board. When compiling the code, make sure you have an ESP8266 board selected in the Boards menu.

Include the DHT libraries to read from the DHT sensor.

#include <Adafruit_Sensor.h>
#include <DHT.h>

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

uint8_t broadcastAddress[] = {0x2C, 0x3A, 0xE8, 0x0E, 0xBB, 0xED};

Define the GPIO the DHT pin is connected to. In this case, it is connected to GPIO 5 (D1).

#define DHTPIN 5    

Then, select the DHT sensor type you’re using. In our example, we’re using the DHT22. If you’re using another type, you just need to uncomment your sensor and comment all the others.

//#define DHTTYPE    DHT11     // DHT 11
#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

Instantiate a DHT object with the type and pin defined earlier.

DHT dht(DHTPIN, DHTTYPE);

Define variables to store the DHT readings to be sent:

float temperature;
float humidity;

Define more two variables to store incoming readings:

float incomingTemp;
float incomingHum;

We’ll send DHT readings via ESP-NOW every 10 seconds. That period of time is defined in the interval variable. You can change that interval.

const long interval = 10000; 
unsigned long previousMillis = 0;    // will store last time DHT was updated 

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 temperature and humidity readings.

typedef struct struct_message {
    float temp;
    float hum;
} 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 DHTReadings will store the readings to be sent.

struct_message DHTReadings;

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

struct_message incomingReadings;

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”:

void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  Serial.print("Last Packet Send Status: ");
  if (sendStatus == 0){
    Serial.println("Delivery success");
  }

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

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

OnDataRecv() callback function

The OnDataRecv() function is executed when a new packet arrives.

void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t 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 incomingReadingsstructure, you just need to do call incomingReadings.temp as follows:

incomingTemp = incomingReadings.temp;

The same process is done for the humidity:

incomingHum = incomingReadings.hum;

getReadings()

The getReadings() function gets temperature and humidity from the DHT sensor. The following line gets the current temperature and saves it in the temperature variable:

temperature = dht.readTemperature();

This previous line returns the temperature in Celsius degrees. To get the temperature in Fahrenheit, comment the previous line and uncomment the following:

//float t = dht.readTemperature(true);

Sometimes, you’re not able to get readings from the sensor. When that happens the sensor returns nan. If that’s the case, we set the temperature to 0.0.

A similar process is done to get the humdity readings. The current humidity value is saved on the humidity variable:

humidity = dht.readHumidity();
if (isnan(humidity)){
  Serial.println("Failed to read from DHT");
  humidity = 0.0;
}

printIncomingReadings()

The printIncomingReadings() function simply prints the received readings on the Serial Monitor.

The received temperature in saved on the incomingTemp variable and the received humidity is saved on the incomingHum variable as we’ll see in a moment.

void printIncomingReadings(){
  // Display Readings in Serial Monitor
  Serial.println("INCOMING READINGS");
  Serial.print("Temperature: ");
  Serial.print(incomingTemp);
  Serial.println(" ºC");
  Serial.print("Humidity: ");
  Serial.print(incomingHum);
  Serial.println(" %");
}

setup()

In the setup(), initialize the Serial Monitor:

Serial.begin(115200);

Initialize the DHT sensor:

dht.begin();

Set the ESP8266 as a station:

WiFi.mode(WIFI_STA);
WiFi.disconnect();

Initialize ESP-NOW:

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

Set the ESP8266 ESP-NOW role. In this example, the ESP8266 receives and sends readings via ESP-NOW, so set the role to ESP_NOW_ROLE_COMBO.

esp_now_set_self_role(ESP_NOW_ROLE_COMBO);

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 line add a new peer.

esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_COMBO, 1, NULL, 0);

The esp_now_add_peer() function accepts the following arguments, in this order: mac address, peer role, wi-fi channel, key, and key length.

Finally, register for the OnDataRecv callback function.

esp_now_register_recv_cb(OnDataRecv);

loop()

In the loop(), check if it is time to get new readings:

unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
  // save the last time you updated the DHT values
  previousMillis = currentMillis;

If it is, call the getReadings function to read the current temperature and humidity.

getReadings();

Then, update the DHTReadings structure with the new temperature and humidity values:

DHTReadings.temp = temperature;
DHTReadings.hum = humidity;

Send the structure via ESP-NOW:

esp_now_send(broadcastAddress, (uint8_t *) &DHTReadings, sizeof(DHTReadings));

Finally, call the printIncomingReadings() function to print the incoming readings on the Serial Monitor.

printIncomingReadings();

Demonstration

After uploading the code to each board, open an Arduino IDE Serial Monitor window. Alternatively, you can open two different serial connections (using PuTTY) to see the two boards interacting simultaneously.

ESP-NOW ESP8266 NodeMCU Board Exchange Sensor Readings Arduino IDE

As you can see, it is working as expected. Each board is printing the other board’s readings.

ESP8266 NodeMCU board ESP-NOW Two-Way Communication exchange temperature humidity sensor readings

Wrapping Up

In this tutorial we’ve shown you how to establish a two-way communication with two ESP8266 board using ESP-NOW. This is a very versatile communication protocol that can be used to send packets with up to 250 bytes. ESP-NOW communication protocol can also be used with ESP32 boards: Getting Started with ESP-NOW (ESP32 with Arduino IDE).

As an example, we’ve shown you the interaction between two boards, but you can add many boards to your setup. You just need to know the MAC address of the board you’re sending data to. We’ll be publishing more tutorials about ESP-NOW, so stay tuned.

We hope you’ve found this tutorial useful. To learn more about the ESP8266 board, make sure you take a look at our resources:

Thanks for reading.



Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our weekly newsletter!

19 thoughts on “ESP-NOW Two-Way Communication Between ESP8266 NodeMCU Boards”

  1. Hello, Again a good project!
    I make this project, looks nice. Now i want to make some extra for testing, how can i turn on a led on the receiver with a button on the sender. I try to do this but i dont know exactly how to do the transmit and receive.
    Perhaps you have an example?
    Hope you can help
    Thanks
    regards Remon

    Reply
    • Well my extra push button to turn on a led at the receiver site works! ofcource i made a mistake! this kind of projects give me good inspiration to make other projects for example i make my own RC controller for my boat (i am building), now i want to control a servo, i think that is not a problem, i understand it better now!
      Remon

      Reply
  2. Working with many boards make You sometime lost. I found solution adding line in code with name/version of sketch and its version.
    Second thinks is question :Why i am getting different Mac adress of board during loading Node MCU and with Your sketch for getting this adress.
    Ihave lost much time forcing boards to communicate using this first one.

    Reply
  3. Hello Sara, hello Rui,
    I have recreated the project ESP-NOW Two-Way Communication Between ESP8266 NodeMCU Boards, but when compiling the error comes:

    /home/hans/Arduino/ESP8266_Two-Way_AMICA_ESP-NOW_Code/ESP8266_Two-Way_AMICA_ESP-NOW_Code.ino: In function ‘void setup ()’:
    ESP8266_Two-Way_AMICA_ESP-NOW_Code: 124: 25: error: ‘ESP_NOW_ROLE_COMBO’ was not declared in this scope
    esp_now_set_self_role (ESP_NOW_ROLE_COMBO);
    ^
    exit status 1
    ‘ESP_NOW_ROLE_COMBO’ was not declared in this scope

    I’ve tried different boards, always the same one
    Error message.
    What am I doing wrong?

    Kind regards
    Hans Herzig

    Reply
  4. Hey.
    Can one use this guide also for two wemos d1 mini?

    I have a wemos d1 mini which reads data from temperature etc. and would like to transfer data to another wemos d1 mini which then reads it on a display.
    can i do that.

    Reply
    • Hi.
      There isn’t a maximum range.
      It depends on your specific case. It depends on your surroundings, if there are many obstacles between the boards, where the boards are placed, etc..
      But the range is much better than wi-fi.
      Regards,
      Sara

      Reply
  5. Hello I already have connected two ESP8266 boards between them and communicate, now I want to add a third board to communicate with both, how should I do them?

    And another question could use an ESP8266 board as a bridge for receiving and sending data between two ESP8266 boards.

    Reply

Leave a Reply to Henrik Thide Cancel reply

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.