ESP-NOW

About

ESP-NOW is a communication protocol designed for low-power, low-latency, and high-throughput communication between ESP32 devices without the need for an access point (AP). It is ideal for scenarios where devices need to communicate directly with each other in a local network. ESP-NOW can be used for smart lights, remote control devices, sensors and many other applications.

This library provides an easy-to-use interface for setting up ESP-NOW communication, adding and removing peers, and sending and receiving data packets.

Arduino-ESP32 ESP-NOW API

ESP-NOW Class

The ESP_NOW_Class is the main class used for managing ESP-NOW communication.

begin

Initialize the ESP-NOW communication. This function must be called before using any other ESP-NOW functionalities.

bool begin(const uint8_t *pmk = NULL);
  • pmk: Optional. Pass the pairwise master key (PMK) if encryption is enabled.

Returns true if initialization is successful, false otherwise.

end

End the ESP-NOW communication. This function releases all resources used by the ESP-NOW library.

bool end();

Returns true if the operation is successful, false otherwise.

getTotalPeerCount

Get the total number of peers currently added.

int getTotalPeerCount();

Returns the total number of peers, or -1 if an error occurs.

getEncryptedPeerCount

Get the number of peers using encryption.

int getEncryptedPeerCount();

Returns the number of peers using encryption, or -1 if an error occurs.

onNewPeer

You can register a callback function to handle incoming data from new peers using the onNewPeer function.

void onNewPeer(void (*cb)(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg), void *arg);
  • cb: Pointer to the callback function.

  • arg: Optional. Pointer to user-defined argument to be passed to the callback function.

cb function signature:

void cb(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg);

info: Information about the received packet. data: Pointer to the received data. len: Length of the received data. arg: User-defined argument passed to the callback function.

ESP-NOW Peer Class

The ESP_NOW_Peer class represents a peer device in the ESP-NOW network. It is an abstract class that must be inherited by a child class that properly handles the peer connections and implements the _onReceive and _onSent methods.

Constructor

Create an instance of the ESP_NOW_Peer class.

ESP_NOW_Peer(const uint8_t *mac_addr, uint8_t channel, wifi_interface_t iface, const uint8_t *lmk);
  • mac_addr: MAC address of the peer device.

  • channel: Communication channel.

  • iface: WiFi interface.

  • lmk: Optional. Pass the local master key (LMK) if encryption is enabled.

add

Add the peer to the ESP-NOW network.

bool add();

Returns true if the peer is added successfully, false otherwise.

remove

Remove the peer from the ESP-NOW network.

bool remove();

Returns true if the peer is removed successfully, false otherwise.

send

Send data to the peer.

size_t send(const uint8_t *data, int len);
  • data: Pointer to the data to be sent.

  • len: Length of the data in bytes.

Returns the number of bytes sent, or 0 if an error occurs.

addr

Get the MAC address of the peer.

const uint8_t * addr() const;

Returns a pointer to the MAC address.

addr

Set the MAC address of the peer.

void addr(const uint8_t *mac_addr);
  • mac_addr: MAC address of the peer.

getChannel

Get the communication channel of the peer.

uint8_t getChannel() const;

Returns the communication channel.

setChannel

Set the communication channel of the peer.

void setChannel(uint8_t channel);
  • channel: Communication channel.

getInterface

Get the WiFi interface of the peer.

wifi_interface_t getInterface() const;

Returns the WiFi interface.

setInterface

Set the WiFi interface of the peer.

void setInterface(wifi_interface_t iface);
  • iface: WiFi interface.

isEncrypted

Check if the peer is using encryption.

bool isEncrypted() const;

Returns true if the peer is using encryption, false otherwise.

setKey

Set the local master key (LMK) for the peer.

void setKey(const uint8_t *lmk);
  • lmk: Local master key.

onReceive

Callback function to handle incoming data from the peer. This is a virtual method can be implemented by the upper class for custom handling.

void onReceive(const uint8_t *data, int len, bool broadcast);
  • data: Pointer to the received data.

  • len: Length of the received data.

  • broadcast: true if the data is broadcasted, false otherwise.

onSent

Callback function to handle the completion of sending data to the peer. This is a virtual method can be implemented by the upper class for custom handling.

void onSent(bool success);
  • success: true if the data is sent successfully, false otherwise.

Examples

Set of 2 examples of the ESP-NOW library to send and receive data using broadcast messages between multiple ESP32 devices (multiple masters, multiple slaves).

  1. ESP-NOW Broadcast Master Example:

/*
    ESP-NOW Broadcast Master
    Lucas Saavedra Vaz - 2024

    This sketch demonstrates how to broadcast messages to all devices within the ESP-NOW network.
    This example is intended to be used with the ESP-NOW Broadcast Slave example.

    The master device will broadcast a message every 5 seconds to all devices within the network.
    This will be done using by registering a peer object with the broadcast address.

    The slave devices will receive the broadcasted messages and print them to the Serial Monitor.
*/

#include "ESP32_NOW.h"
#include "WiFi.h"

#include <esp_mac.h>  // For the MAC2STR and MACSTR macros

/* Definitions */

#define ESPNOW_WIFI_CHANNEL 6

/* Classes */

// Creating a new class that inherits from the ESP_NOW_Peer class is required.

class ESP_NOW_Broadcast_Peer : public ESP_NOW_Peer {
public:
  // Constructor of the class using the broadcast address
  ESP_NOW_Broadcast_Peer(uint8_t channel, wifi_interface_t iface, const uint8_t *lmk)
    : ESP_NOW_Peer(ESP_NOW.BROADCAST_ADDR, channel, iface, lmk) {}

  // Destructor of the class
  ~ESP_NOW_Broadcast_Peer() {
    remove();
  }

  // Function to properly initialize the ESP-NOW and register the broadcast peer
  bool begin() {
    if (!ESP_NOW.begin() || !add()) {
      log_e("Failed to initialize ESP-NOW or register the broadcast peer");
      return false;
    }
    return true;
  }

  // Function to send a message to all devices within the network
  bool send_message(const uint8_t *data, size_t len) {
    if (!send(data, len)) {
      log_e("Failed to broadcast message");
      return false;
    }
    return true;
  }
};

/* Global Variables */

uint32_t msg_count = 0;

// Create a broadcast peer object
ESP_NOW_Broadcast_Peer broadcast_peer(ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);

/* Main */

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(10);

  // Initialize the Wi-Fi module
  WiFi.mode(WIFI_STA);
  WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
  while (!WiFi.STA.started()) delay(100);

  Serial.println("ESP-NOW Example - Broadcast Master");
  Serial.println("Wi-Fi parameters:");
  Serial.println("  Mode: STA");
  Serial.println("  MAC Address: " + WiFi.macAddress());
  Serial.printf("  Channel: %d\n", ESPNOW_WIFI_CHANNEL);

  // Register the broadcast peer
  if (!broadcast_peer.begin()) {
    Serial.println("Failed to initialize broadcast peer");
    Serial.println("Reebooting in 5 seconds...");
    delay(5000);
    ESP.restart();
  }

  Serial.println("Setup complete. Broadcasting messages every 5 seconds.");
}

void loop() {
  // Broadcast a message to all devices within the network
  char data[32];
  snprintf(data, sizeof(data), "Hello, World! #%lu", msg_count++);

  Serial.printf("Broadcasting message: %s\n", data);

  if (!broadcast_peer.send_message((uint8_t *)data, sizeof(data))) {
    Serial.println("Failed to broadcast message");
  }

  delay(5000);
}
  1. ESP-NOW Broadcast Slave Example:

/*
    ESP-NOW Broadcast Slave
    Lucas Saavedra Vaz - 2024

    This sketch demonstrates how to receive broadcast messages from a master device using the ESP-NOW protocol.

    The master device will broadcast a message every 5 seconds to all devices within the network.

    The slave devices will receive the broadcasted messages. If they are not from a known master, they will be registered as a new master
    using a callback function.
*/

#include "ESP32_NOW.h"
#include "WiFi.h"

#include <esp_mac.h>  // For the MAC2STR and MACSTR macros

#include <vector>

/* Definitions */

#define ESPNOW_WIFI_CHANNEL 6

/* Classes */

// Creating a new class that inherits from the ESP_NOW_Peer class is required.

class ESP_NOW_Peer_Class : public ESP_NOW_Peer {
public:
  // Constructor of the class
  ESP_NOW_Peer_Class(const uint8_t *mac_addr,
                     uint8_t channel,
                     wifi_interface_t iface,
                     const uint8_t *lmk)
    : ESP_NOW_Peer(mac_addr, channel, iface, lmk) {}

  // Destructor of the class
  ~ESP_NOW_Peer_Class() {}

  // Function to register the master peer
  bool add_peer() {
    if (!add()) {
      log_e("Failed to register the broadcast peer");
      return false;
    }
    return true;
  }

  // Function to print the received messages from the master
  void onReceive(const uint8_t *data, size_t len, bool broadcast) {
    Serial.printf("Received a message from master " MACSTR " (%s)\n", MAC2STR(addr()), broadcast ? "broadcast" : "unicast");
    Serial.printf("  Message: %s\n", (char *)data);
  }
};

/* Global Variables */

// List of all the masters. It will be populated when a new master is registered
std::vector<ESP_NOW_Peer_Class> masters;

/* Callbacks */

// Callback called when an unknown peer sends a message
void register_new_master(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
  if (memcmp(info->des_addr, ESP_NOW.BROADCAST_ADDR, 6) == 0) {
    Serial.printf("Unknown peer " MACSTR " sent a broadcast message\n", MAC2STR(info->src_addr));
    Serial.println("Registering the peer as a master");

    ESP_NOW_Peer_Class new_master(info->src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);

    masters.push_back(new_master);
    if (!masters.back().add_peer()) {
      Serial.println("Failed to register the new master");
      return;
    }
  } else {
    // The slave will only receive broadcast messages
    log_v("Received a unicast message from " MACSTR, MAC2STR(info->src_addr));
    log_v("Igorning the message");
  }
}

/* Main */

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(10);

  // Initialize the Wi-Fi module
  WiFi.mode(WIFI_STA);
  WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
  while (!WiFi.STA.started()) delay(100);

  Serial.println("ESP-NOW Example - Broadcast Slave");
  Serial.println("Wi-Fi parameters:");
  Serial.println("  Mode: STA");
  Serial.println("  MAC Address: " + WiFi.macAddress());
  Serial.printf("  Channel: %d\n", ESPNOW_WIFI_CHANNEL);

  // Initialize the ESP-NOW protocol
  if (!ESP_NOW.begin()) {
    Serial.println("Failed to initialize ESP-NOW");
    Serial.println("Reeboting in 5 seconds...");
    delay(5000);
    ESP.restart();
  }

  // Register the new peer callback
  ESP_NOW.onNewPeer(register_new_master, NULL);

  Serial.println("Setup complete. Waiting for a master to broadcast a message...");
}

void loop() {
  delay(1000);
}

Example of the ESP-NOW Serial library to send and receive data as a stream between 2 ESP32 devices using the serial monitor:

/*
    ESP-NOW Serial Example - Unicast transmission
    Lucas Saavedra Vaz - 2024
    Send data between two ESP32s using the ESP-NOW protocol in one-to-one (unicast) configuration.
    Note that different MAC addresses are used for different interfaces.
    The devices can be in different modes (AP or Station) and still communicate using ESP-NOW.
    The only requirement is that the devices are on the same Wi-Fi channel.
    Set the peer MAC address according to the device that will receive the data.

    Example setup:
    - Device 1: AP mode with MAC address F6:12:FA:42:B6:E8
                Peer MAC address set to the Station MAC address of Device 2 (F4:12:FA:40:64:4C)
    - Device 2: Station mode with MAC address F4:12:FA:40:64:4C
                Peer MAC address set to the AP MAC address of Device 1 (F6:12:FA:42:B6:E8)

    The device running this sketch will also receive and print data from any device that has its MAC address set as the peer MAC address.
    To properly visualize the data being sent, set the line ending in the Serial Monitor to "Both NL & CR".
*/

#include "ESP32_NOW_Serial.h"
#include "MacAddress.h"
#include "WiFi.h"

#include "esp_wifi.h"

// 0: AP mode, 1: Station mode
#define ESPNOW_WIFI_MODE_STATION 1

// Channel to be used by the ESP-NOW protocol
#define ESPNOW_WIFI_CHANNEL 1

#if ESPNOW_WIFI_MODE_STATION        // ESP-NOW using WiFi Station mode
#define ESPNOW_WIFI_MODE WIFI_STA   // WiFi Mode
#define ESPNOW_WIFI_IF WIFI_IF_STA  // WiFi Interface
#else                               // ESP-NOW using WiFi AP mode
#define ESPNOW_WIFI_MODE WIFI_AP    // WiFi Mode
#define ESPNOW_WIFI_IF WIFI_IF_AP   // WiFi Interface
#endif

// Set the MAC address of the device that will receive the data
// For example: F4:12:FA:40:64:4C
const MacAddress peer_mac({ 0xF4, 0x12, 0xFA, 0x40, 0x64, 0x4C });

ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, ESPNOW_WIFI_IF);

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

  Serial.print("WiFi Mode: ");
  Serial.println(ESPNOW_WIFI_MODE == WIFI_AP ? "AP" : "Station");
  WiFi.mode(ESPNOW_WIFI_MODE);

  Serial.print("Channel: ");
  Serial.println(ESPNOW_WIFI_CHANNEL);
  WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);

  while (!(WiFi.STA.started() || WiFi.AP.started())) delay(100);

  Serial.print("MAC Address: ");
  Serial.println(ESPNOW_WIFI_MODE == WIFI_AP ? WiFi.softAPmacAddress() : WiFi.macAddress());

  // Start the ESP-NOW communication
  Serial.println("ESP-NOW communication starting...");
  NowSerial.begin(115200);
  Serial.println("You can now send data to the peer device using the Serial Monitor.\n");
}

void loop() {
  while (NowSerial.available()) {
    Serial.write(NowSerial.read());
  }

  while (Serial.available() && NowSerial.availableForWrite()) {
    if (NowSerial.write(Serial.read()) <= 0) {
      Serial.println("Failed to send data");
      break;
    }
  }

  delay(1);
}