Maker.io main logo

How To Get Started With ESP-NOW

2024-06-17 | By Maker.io Staff

ESP32 ESP8266

Read on to learn how to use ESP-NOW, a connectionless communication protocol developed by Espressif for transmitting packages between ESP32 boards without needing to establish a WiFi connection.

How To Get Started With ESP-NOW

What is ESP-NOW?

ESP-NOW is a peer-to-peer communication protocol that facilitates effortless data exchange between ESP-based development boards without a dedicated WiFi router. The protocol can coexist with WiFi and BLE and supports the popular ESP32 and ESP8266 platforms.

How To Get Started With ESP-NOW This figure summarizes the differences between the standard OSI layer architecture and the ESP-NOW model — image courtesy of Espressif.

The protocol is based on a slimmed-down version of the OSI layered model that cuts out several layers to reduce complexity and free up CPU and memory usage. It encapsulates payload data in action frames to facilitate communication without an intermediary, such as a WiFi router. However, this technique also limits the payload size of each transmitted package to 250 bytes. The protocol requires device-pairing and supports the ECDH and AES encryption algorithms to make data transmission more secure.

Communication can occur in one-to-many or many-to-many modes, depending on how many devices actively send data. In one-to-many mode, a single sender transmits packages to other devices. This setup is suitable for implementing remote controls, such as a garage door opener. In the second mode, more than one participant actively transmits data to other receiving devices, which can be used to implement smart-home-like systems, for example.

One of the protocol’s limitations is the relatively small number of participants that can exchange data. In Unicast mode, up to 20 devices can pair and simultaneously receive packages from a single sender. In encrypted mode, ESP-NOW supports up to six simultaneous receivers. Theoretically, an unlimited number of devices can be controlled if using broadcast packages.

Configuring the Arduino IDE for ESP-NOW

The Arduino IDE supports programming compatible ESP-32 or ESP-8266 development boards after executing a few setup steps outlined in this article.

Broadcast Data using ESP-NOW

Broadcasting is a convenient way to transmit data to all devices on the same channel without pairing. This approach also allows for unlimited receivers and is a good way of verifying ESP-NOW's basic functionality. Unfortunately, you must use different method calls depending on whether your development board utilizes an ESP32 or ESP8266. The transmission script for the ESP-32 contains the following include statements and variables:

Copy Code
// ESP32 Imports
#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h>

#define UPDATE_DELAY 250
#define CHANNEL 1

esp_now_peer_info_t peerInfo;
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
unsigned long lastUpdateMillis = 0UL;
uint8_t flagToSend = 0;
 

The ESP32 uses a custom structure to hold each peer's information, such as its MAC address and channel. The ESP8266 doesn't support using the structure and requires different include statements. So, its first few lines look as follows:

Copy Code
// ESP8266 Imports
#include <espnow.h>
#include <ESP8266WiFi.h>

#define BUTTON_PIN 2
#define CHANNEL 1

uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
unsigned long lastUpdateMillis = 0UL;
uint8_t flagToSend = 0;

The setup functions also slightly differ between the MCU types. The ESP32’s setup function looks as follows:

Copy Code
// ESP32
void setup() {
  Serial.begin(9600);
  WiFi.mode(WIFI_STA);
  esp_now_init();
  peerInfo.channel = CHANNEL;  
  peerInfo.encrypt = false;
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  esp_now_add_peer(&peerInfo);
}

The program opens a serial debugging connection before initializing ESP_NOW and copying the client’s info to the peerInfo struct. In this case, the transmitter only sends data to the broadcast address, meaning that all nearby ESP_NOW devices on the same channel will receive the data. Finally, the method adds a new peer using the struct. The ESP8266 setup accomplishes the same goal but without using the peerInfo structure:

Copy Code
// ESP8266
void setup() {
  Serial.begin(9600);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  esp_now_init();
  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
  esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, CHANNEL, NULL, 0);
}

The remaining functions are the same, irrespective of the platform. First, there are two additional helper methods for sending data and deleting peers:

Copy Code
void deletePeer(void) {
  uint8_t delStatus = esp_now_del_peer(broadcastAddress);
  if (delStatus != 0) {
    Serial.println("Could not delete peer");
  }
}

void sendData(void) {
  uint8_t result = esp_now_send(broadcastAddress, &flagToSend, sizeof(flagToSend));
  if (result != 0) {
    Serial.print("Error sending data!");
    deletePeer();
}
 

These call the respective ESP_NOW function and then check the return value to determine whether the operation succeeded. Each method prints an error message to the serial console in case of an error.

Finally, the loop method toggles a boolean flag every 250 milliseconds before verifying that the client exists. If it does, the program transmits the new value to the client:

Copy Code
void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - lastUpdateMillis >= UPDATE_DELAY) {
    flagToSend = !flagToSend; // Invert boolean value
    if (esp_now_is_peer_exist(broadcastAddress)) {
      sendData();
    }
    lastUpdateMillis = currentMillis;
  }
}

You can also implement data transmission to one or multiple known clients by using the client’s specific MAC address instead of the generic broadcast address. In that case, the program must repeat the esp_now_add_peer call for every additional ESP-NOW client. Alternatively, the sender can prefix each message with a short identifier specific to one of the clients. The recipients then extract the prefix, compare it to their own, and ignore messages meant for other clients. That way, you can send data to an unlimited number of clients.

Receive Data with ESP-NOW

Similar to the server code, the client’s include statement varies slightly, depending on the MCU type:

Copy Code
// ESP32 Imports
#include <esp_now.h>
#include <WiFi.h>

// ESP8266 Imports
#include <espnow.h>
#include <ESP8266WiFi.h>
 

However, the two boards now have the same variable definitions, which are very similar to the sender’s code:

Copy Code
#define CHANNEL 1
#define LED_PIN LED_BUILTIN
#define UPDATE_DELAY 50

bool receivedFlag = false;
unsigned long lastUpdateMillis = 0UL;

The setup function differs again, depending on the MCU. The ESP32’s setup looks as follows:

Copy Code
// ESP32
void setup() {
  Serial.begin(9600);
  WiFi.mode(WIFI_STA);
  esp_now_init();
  esp_now_register_recv_cb(onDataReceived);
}

The client’s setup also opens a serial connection for debugging and then puts the WiFi module into station mode before initializing ESP_NOW. The last line defines which function handles incoming data. The ESP8266’s setup function is very similar. However, it closes the WiFi connection and explicitly specifies that the device is a client:

Copy Code
// ESP8266
void setup() {
  Serial.begin(9600);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  esp_now_init();
  esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
  esp_now_register_recv_cb(onDataReceived);
}

Finally, the ESP8266's callback function requires slightly different parameter types:

Copy Code
// ESP32
void onDataReceived(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  receivedFlag = *data;
}

// ESP8266
void onDataReceived(uint8_t *mac_addr, uint8_t *data, uint8_t data_len) {
  receivedFlag = *data;
}

Both callback functions copy the received data into a local variable defined at the start of the sketch. The loop method handles everything else. Note that it’s good practice to keep callback functions as short as possible and do as much of the actual data processing outside, for example, in the loop method, which is the same for both ESP32 and ESP8266:

Copy Code
void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - lastUpdateMillis >= UPDATE_DELAY) {
    digitalWrite(LED_PIN, receivedFlag);
    lastUpdateMillis = currentMillis;
  }
}

The loop function waits for 50 milliseconds and then turns the built-in LED on or off based on the value transmitted by the sender.

Summary

ESP-NOW is a lightweight, connectionless peer-to-peer communication protocol developed by Espressif for their ESP32 and ESP8266 microcontrollers. The protocol is intended for transmitting small amounts of data with a payload size of up to 250 bytes per message. It uses MAC addresses to identify peers and supports encryption. A single sender can address up to twenty clients in unencrypted mode. However, broadcasting offers a less secure way to address unlimited clients.

Unfortunately, the code differs slightly between ESP32 and ESP8266-based devices, and makers must remember this fact when working with both MCUs. Other than that, there’s no limitation on which ESP-based boards can act as senders or receivers, and you can mix the two MCU types.

The basic idea is the same for the two types. The sender’s WiFi module operates in station mode and adds clients to its internal list of known peers via their MAC address. The server can then send data to these peers, add new ones, and remove existing ones, for example, in case of an error.

The client doesn’t keep a list of known senders or other clients. Instead, it defines a callback function that handles incoming messages. It’s good practice to keep the callback short and perform calculations within the loop method. In this example, the client receives a boolean flag from the server and toggles its onboard LED according to the value.

TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum