Skip to content

11. Networking and communications

Group assignment:

  • Send a message between two projects

To see our group assignment click here

Individual assignment:

  • Design, build, and connect wired or wireless node(s) with network or bus addresses

Introduction to the project

The goal of this week’s project is to design a bidirectional wireless communication system between two ESP32 microcontrollers using the ESP-NOW protocol. The objective is to create a cross-interaction system where pressing a physical button on the first board instantly turns on an LED on the second board, and vice versa.

Unlike standard Wi-Fi communication, which requires an internet router or access point, ESP-NOW enables a direct, fast, and low-power connection between the ESP32 boards.

ESP-NOW Communication Protocol

ESP-NOW is a wireless communication protocol developed by Espressif that enables direct communication between multiple ESP32 or ESP8266 boards without requiring a central Wi-Fi router. It is a highly efficient solution for automation, IoT, and robotics projects.

Image


1. Main Features

ESP-NOW stands between Bluetooth and traditional Wi-Fi in terms of usage.

  • Router-Free Communication:
    Devices communicate directly in a Peer-to-Peer manner.

  • Low Latency:
    Communication is almost instantaneous because there is no complex Wi-Fi handshake process.

  • Low Power Consumption:
    Ideal for battery-powered nodes that need to wake up, send data, and return to sleep quickly.

  • Range:
    Similar to standard Wi-Fi, typically between 30 and 100 meters in open space depending on the antenna.

  • Capacity:
    One node can be paired with up to 20 registered devices.


2. Architecture and Addressing

Unlike traditional Wi-Fi, which uses IP addresses, ESP-NOW relies on the unique MAC address of each ESP32 or ESP8266 chip.

Possible Topologies

  1. One-to-One:
    One controller communicates with a single slave device.

  2. One-to-Many:
    One master sends commands to several nodes
    (e.g., one remote controller operating multiple motors).

  3. Many-to-One:
    Several sensors send their data to a central gateway
    (e.g., a multi-sensor weather station).


3. Communication Workflow

To establish communication, the program follows these critical steps:

1. Wi-Fi Initialization

WiFi.mode(WIFI_STA);

The ESP32 is configured in Station Mode.


2. MAC Address Retrieval

The receiver must provide its MAC address to the transmitter.


3. Peer Registration

esp_now_add_peer(...);

The transmitter adds the receiver’s MAC address to its peer list.


4. Callback Management

ESP-NOW uses callback functions for event handling.

Data Sent Confirmation

OnDataSent()

This function confirms whether the packet was successfully delivered.

Data Reception

OnDataRecv()

This function is automatically triggered when data is received.


Image

source Image

More informations

ESP-NOW Communication Setup

Setting up ESP-NOW follows a rigorous logic that transforms your ESP32 boards into real wireless network terminals. For the two boards to exchange the states of their buttons and LEDs, four key steps are required: preparation, addressing, data structuring, and event management through callbacks.


1. Preparation Phase (Station Mode)

Even though ESP-NOW does not require an internet connection, it still uses the ESP32’s Wi-Fi radio module. For this reason, the chip must be initialized in Station Mode (STA).

WiFi.mode(WIFI_STA);

This mode allows the ESP32 to: - Activate its MAC address - Listen for packets coming from other devices - Participate in ESP-NOW communication


2. Addressing: “Who Talks to Whom”

This is the most critical step.

In ESP-NOW, communication is not based on IP addresses, but on MAC addresses. Each ESP32 has a unique physical identifier made of 6 bytes.

Example:

08:3A:F2:1A:BC:DE

Peer Pairing

To establish communication: - The MAC address of Board B must be registered inside Board A - This registration process is called pairing (peer_info)

Without this step, the ESP32 will refuse to send data for security and optimization reasons.


3. Data Structure (The Message)

For both boards to understand each other, they must use the same data format.

This is achieved using a C++ structure (struct).

Example:

typedef struct struct_message {
    bool buttonState;
} struct_message;

In this project: - The structure contains a single boolean value - It indicates whether the button is pressed or released

Whenever the button state changes, the ESP32 places the value into the structure and sends it wirelessly.


4. Callback Functions (The Operator)

The ESP32 does not continuously wait for messages. Instead, it uses callback functions, which work like an operator reacting automatically to events.

Sending Callback

OnDataSent()

This function is automatically executed after sending data.

It confirms whether: - The message was successfully delivered - Or the transmission failed


Receiving Callback

OnDataRecv()

This is the most important callback for the project.

As soon as a packet arrives: - The ESP32 processor is immediately notified - The received value is read - The LED state is updated instantly

Example:

digitalWrite(LED_PIN, HIGH);


5. Communication Flow in the Project

The system works according to the following sequence:

  1. Action:
    You press the button on Board A

  2. Detection:
    The loop() function detects the state change

  3. Transmission:
    Board A sends the struct packet to the MAC address of Board B

  4. Reception:
    Board B receives the packet and activates OnDataRecv()

  5. Execution:
    Board B reads the received value and turns the LED ON or OFF


Here are our ESP32 boards :

Image


The assembly

I used the following image as inspiration for my wiring setup.
Of course, I modified the GPIO pin assignments according to the specific requirements of my project.

Image

Image

On the electronic board designed during Week 6, I integrated several LEDs and push buttons that will be used for this project.

Image

Once the wiring is complete on both boards, I'm ready to move on to uploading the code.


ESP-NOW Bidirectional Communication Implementation

For this project to work correctly, you must first retrieve the MAC address of each ESP32 board, then upload the main program while reversing the addresses between the two boards.

Step 1: Retrieve the MAC Addresses

Upload the following code to Board A, open the Serial Monitor at 115200 baud, and note the displayed MAC address. Repeat the same procedure for Board B.

#include "WiFi.h"

void setup(){
  Serial.begin(115200);
  delay(1000); // Laisse le temps au port série de s'initialiser

  // Initialisation explicite du Wi-Fi en mode Station
  WiFi.mode(WIFI_STA);
  WiFi.STA.begin(); 

  delay(1000); // Pause critique pour laisser la puce s'activer

  Serial.println("\n--- RECUPERATION ADRESSE MAC ---");
  String mac = WiFi.macAddress();

  if (mac == "00:00:00:00:00:00") {
    Serial.println("Erreur : L'adresse est encore nulle. Redémarrage...");
    ESP.restart(); // Redémarre la carte automatiquement si échec
  } else {
    Serial.print("SUCCESS ! L'adresse MAC est : ");
    Serial.println(mac);
  }
}

void loop(){}
Image

Image


Step 2: Main Communication Code

The following program is identical for both ESP32 boards.
The only difference is the value of:

uint8_t broadcastAddress[]

Code for Board A

In this code, replace the MAC address with the MAC address of Board B.

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

// --- CONFIGURATION ---
// REPLACE with the MAC address of BOARD B
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

const int ledPin = 2;
const int buttonPin = 4;

// Data structure
typedef struct struct_message {
    bool buttonPressed;
} struct_message;

struct_message myData;
struct_message incomingData;

// Callback function triggered when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *data, int len) {

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

  digitalWrite(ledPin, incomingData.buttonPressed ? HIGH : LOW);
}

void setup() {

  Serial.begin(115200);

  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);

  WiFi.mode(WIFI_STA);

  // Initialize ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW initialization error");
    return;
  }

  // Register receive callback
  esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));

  // Register peer device
  esp_now_peer_info_t peerInfo;

  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 button state (inverted because of INPUT_PULLUP)
  bool currentState = !digitalRead(buttonPin);

  // Send data only if state changes
  if (currentState != myData.buttonPressed) {

    myData.buttonPressed = currentState;

    esp_err_t result = esp_now_send(
      broadcastAddress,
      (uint8_t *) &myData,
      sizeof(myData)
    );

    if (result == ESP_OK) {
      Serial.println("Message sent successfully");
    } else {
      Serial.println("Transmission error");
    }
  }

  // Small debounce delay
  delay(50);
}

Code for Board B

Use exactly the same code as above, but replace the MAC address with the MAC address of Board A.

// REPLACE with the MAC address of BOARD A
uint8_t broadcastAddress[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

Explanation of Key Parts of the Code

1. INPUT_PULLUP Configuration

pinMode(buttonPin, INPUT_PULLUP);

The ESP32 uses its internal pull-up resistor.

  • The button is connected to GND
  • When the button is pressed:
    digitalRead(buttonPin)
    
    returns LOW

This is why the code uses:

!digitalRead(buttonPin)

to convert the pressed state into true.


2. Sending Data

esp_now_send(...)

This function sends the myData structure containing the button state.


3. Receiving Data

OnDataRecv(...)

This callback function acts as the reception “brain” of the system.

As soon as a packet arrives: - The received structure is copied - The LED state is updated instantly


4. State Change Detection

if (currentState != myData.buttonPressed)

This condition ensures that data is sent only when the button state changes.

This avoids: - Sending thousands of unnecessary packets - Wasting energy - Saturating the wireless channel


Final Result

Once both boards are flashed with their respective programs:

  • Pressing the button on Board A turns on the LED on Board B
  • Pressing the button on Board B turns on the LED on Board A

The communication occurs almost instantly thanks to the low latency of ESP-NOW.


Files

Download Code1 files

Download Code2 files