ESP Now Data Transmission between ESP32/ESP8266

ESP-NOW is a wireless communication protocol for Espressif microcontrollers (ESP32, ESP8266, ...) without the need of a router. It is fast and supports a mesh infrastructure. Each ESP NOW enabled ESP can act as client (send data) and server (receive data). Addressing is done based on the MAC address.

On this page I will cover following topics:

  • Overview on ESP NOW
  • Introduction of the Use Case: How to synchronize a LED animation on several ESPs
  • Implementation of an ESP NOW sender (Client) and receiver (Server)

On the end of the page you will find the download of the example Sketch for ESP32.

Bascis about ESP NOW

ESP NOW Transmission

ESP NOW uses WiFi (2.4G) or Bluetooth BL to transport data. Transmission is not IP based but by MAC Addresses. You can send either

  • unicast (one to one): the client sends to one specified MAC address.
  • broadcast (one to all): the clients broadcasts to 0xFF:0xFF:0xFF:0xFF:0xFF:0xFF

In case of the ESP NOW broadcast the data is transmitted into the network. Any other ESP server listening to the broadcast can receive the message with the payload data.

Payload formatting: Transmit Binary Data

As ESP NOW is a proprietary transmission and limited to ESP microcontrollers anyway, the transmission of binary data is acceptable. For example you could define a structure for your payload data as following:

// the structure defines what data should be send/received
struct Message {
  uint16_t id = 0;      // you might send an identifier for the board or an indicator for the mode of operation
  uint16_t index = 0;   // current index of pattern (actual pattern)
};
Message messageData;

In this case we have a total payload of 4 bytes. A maximum of 250 bytes is possible.

Timing: How fast is ESP NOW?

I have measured the transmission time with a two channel oscilloscope. The trigger was the change of the output pin on the client side, around 4 milliseconds later, the server has changed its status also.

Usecase: Synchronize LED Pattern over several ESP

Let's assume following usecase with several microcontrollers: Each ESP controls several LEDs and runs a "LED show". The ESP has a workflow/sequence which LED to switch on/off at which time. We want to synchronize the LED show of several ESPs with a wireless connection. Think of a carnaval corso with several wagons or a group of artists wearing LED costumes on a stage. Each wagon/costume should be in synch with the others. However - if the connection gets lost for any reason, each ESP will continue "offline" with the pattern. If the connection gets established again, all ESP should get in synchronization again.

The sketch is splitted in 3 parts and structured in several tabs:

  • the LED animation
  • a ESP NOW client sending a "clock signal" to all other ESPs
  • a ESP NOW server receiving the "clock signal"

The LED Animation

The LED animation is based on one of my tutorials about non blocking programming. Basically it uses a large array to store the show. Each entry indicates which LED is on for how long.

// each line of the pattern consists of these components
struct Pattern {
  const uint16_t interval;      // the milliseconds how long the defined LEDs should be shown
  const uint16_t led;           // indicate which of the 16 LEDs to be activated
};

// the LED pattern to be displayed, each bit defines one LED state, "paint" your pattern/sequence
const Pattern pattern[] = {
       // 5432109876543210
  {100, 0b0000000000000000},    // all off
  {500, 0b0000001111111111},    // all on
  {500, 0b0000000000000000},
  {100, 0b0000010101010101},    // several LEDs are on
  {100, 0b0000001010101010},
  {100, 0b0000010101010101}
  // and several more
}

It is important to use non blocking code without delay(). The loop must call the run function over and over again

void loop() {
  runPattern();
}

Each ESP can start and run the animation on it's own - even in offline situations. If you would let run the animation on several ESPs for half a day, wouldl see how the animations will get of synch. Therefore we also have to implement an client (master) and server (slave).

ESP NOW Client

The client (sender, master) transmits its actual pattern (current index within the animation) as broadcast into the network.

Most of the client code is in a separate tab. The clientTransmit() transmits data as broadcast:

// the send function to transmit the payload
void clientTransmit() {
  //messageData.id = 0;      // ready for future usage (rfu)
  messageData.index = actualPattern;
  esp_err_t result = esp_now_send(serverAddress, (uint8_t *) &messageData, sizeof(messageData));
  if (result != ESP_OK) {
    Serial.println("Sending error");
  }
}

The clientOnDataSent() is a callback function. It gets called after the data was sent. This function is optional and could be removed.

clientSetup() needs to be called once in the setup().

In the main tab the runPattern() will need an additional line on the client to trigger the transmission. Therefore you find a pre-compiler #if. In the current implementation I decided to let the client only send each 10th update, hence the modulo division. This might differ if you have a slow animation.

// run animation
void runPattern() {
  if (millis() - lastMillis >= actualInterval) {
    actualPattern++;
    if (actualPattern >= totalNoPattern) actualPattern = 0;
#ifdef CLIENT
    // on client side (sender): trigger partner. Here: only each 10th iteration
    if (actualPattern % 10 == 0) clientTransmit();
#endif
    set(actualPattern);
    actualInterval = pattern[actualPattern].interval;
    lastMillis = millis();
  }
}

ESP NOW Server

The servers (receivers, slaves) listens for broadcasts in the network. When the server has received a broadcast, the message will be splitted into its part and the actual pattern will be set according to the broadcast. There is one if to sanitize against malformed messages.

// callback when receiver has received data
void serverOnData(const uint8_t *macAddress, const uint8_t *incomingData, int dataLength) {
  memcpy(&messageData, incomingData, sizeof(messageData));
  actualPattern = messageData.index;
  if (actualPattern >= totalNoPattern) actualPattern = 0;  // don't trust blind others data to much
  set(actualPattern);
  actualInterval = pattern[actualPattern].interval;
  lastMillis = millis();
  Serial.print("received actualPattern:"); Serial.println(actualPattern);
}

Configuration/Tabs

As the client and the servers should run the same animation there is only one common code.

When you want to compile and flash a client (sender) you must activate this line in the main tab

/* ***************************
Configuration/Konfiguraton
* ************************* */

#define CLIENT 1 // comment/deactivate this line if you want to compile the SERVER

When you want to compile and flash a server (receiver) you must deactivate this line in the main tab

/* ***************************
Configuration/Konfiguraton
* ************************* */

//#define CLIENT 1 // comment/deactivate this line if you want to compile the SERVER

ESP NOW mixture of ESP32 and ESP8266

An ESP NOW transmission between ESP32 and ESP8266 devices will work. When you write an universal code, you need to pay attention on the different APIs. First you will need to include different libraries for each architecture:

#ifdef ARDUINO_ARCH_ESP32    // libraries if you compile for a ESP32
#include <WiFi.h>
#include <esp_now.h>
#endif
#ifdef ARDUINO_ARCH_ESP8266  // libraries if you compile for a ESP8266
#include <ESP8266WiFi.h>
#include <espnow.h>          // https://github.com/esp8266/Arduino/blob/master/tools/sdk/include/espnow.h
#endif

Also some function signatures differ.

So if you want to write one common code you will need proper pre-compiler defines to make a code either compile for the ESP32 or the ESP8266.

Summary

ESP NOW can be used for a fast communication between ESP microcontrollers. The example/usecase showes how to syncronize data between several ESPs.

(*) Disclosure: Some of the links above are affiliate links, meaning, at no additional cost to you I will earn a (little) comission if you click through and make a purchase. I only recommend products I own myself and I'm convinced they are useful for other makers.

History

First upload: 2024-03-19 | Version: 2024-03-22