Skip to main content
ESP-NOW is a wireless communication protocol developed by Espressif that enables direct, low-latency communication between ESP32 and ESP8266 devices without requiring a WiFi access point.
Platform Requirement: ESP-NOW is only available on ESP32 and ESP8266 platforms. It is not compatible with Arduino AVR boards.

ESP-NOW Overview

Peer-to-Peer

Direct device communication without WiFi router

Low Latency

Communication latency under 5 milliseconds

Mesh Network

Support for up to 20 paired devices (ESP32)

Low Power

More efficient than traditional WiFi for short messages

Specifications

ParameterESP32ESP8266Notes
Max Range~200m~200mLine of sight, optimal conditions
Max Peers2020Total paired devices
Data RateUp to 1 MbpsUp to 1 MbpsShared WiFi channel bandwidth
Latency<5 ms<5 msTypical
Message Size250 bytes250 bytesMaximum payload
EncryptionOptionalOptionalAES encryption support
Power~100 mA TX~80 mA TXDepends on TX power

Module Architecture

The Kinematrix ESP-NOW module provides MAC address management and communication utilities:
lib/modules/communication/wireless/now/esp-now.h
class ESPNow {
public:
    ESPNow();
    ~ESPNow();
    
    bool begin(esp_now_send_cb_t sendCb, esp_now_recv_cb_t recvCb);
    
    void setThisDeviceIndex(uint8_t index);
    int getThisDeviceIndex();
    int getThisDeviceIndexByMacAddress();
    
    void addMacAddress(String macStr, int master = -1);
    void debugMacAddressList();
    
    esp_err_t sendData(const uint8_t *peer_addr, const uint8_t *data, size_t len);
    bool broadcastData(const uint8_t *data, size_t len, void (*err_callback)() = nullptr);
    
    String macAddressToString(uint8_t *macArray);
    void macStringToUint8(const char *macStr, uint8_t *macArray);
    
    static const int MASTER = 1;
    static void defaultSendCallback(const uint8_t *mac_addr, esp_now_send_status_t status);
    static void defaultReceiveCallback(const uint8_t *mac, const uint8_t *incomingData, int len);
};

Message Structure

typedef struct {
    String msg;          // String message
    char buffer[250];    // Raw buffer for structured data
} ESPNowMessage;

Basic Setup

Simple Point-to-Point Communication

#if defined(ESP32) || defined(ESP8266)

#define ENABLE_MODULE_ESP_NOW
#include "Kinematrix.h"

ESPNow espnow;

// Peer device MAC address
uint8_t peerMAC[] = {0x24, 0x6F, 0x28, 0x12, 0x34, 0x56};

void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("Send Status: ");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail");
}

void onDataReceived(const uint8_t *mac, const uint8_t *incomingData, int len) {
  char buffer[len + 1];
  memcpy(buffer, incomingData, len);
  buffer[len] = '\0';
  
  Serial.print("Received: ");
  Serial.println(buffer);
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  
  // Initialize ESP-NOW
  if (!espnow.begin(onDataSent, onDataReceived)) {
    Serial.println("ESP-NOW init failed");
    return;
  }
  
  // Add peer
  espnow.addMacAddress("24:6F:28:12:34:56");
  
  Serial.println("ESP-NOW initialized");
  Serial.print("MAC Address: ");
  Serial.println(WiFi.macAddress());
}

void loop() {
  String message = "Temperature: " + String(25.5);
  
  esp_err_t result = espnow.sendData(
    peerMAC,
    (uint8_t *)message.c_str(),
    message.length()
  );
  
  if (result == ESP_OK) {
    Serial.println("Sent successfully");
  } else {
    Serial.println("Send failed");
  }
  
  delay(5000);
}

#endif

Getting Device MAC Address

void printMacAddress() {
  uint8_t mac[6];
  WiFi.macAddress(mac);
  
  Serial.print("MAC Address: ");
  for (int i = 0; i < 6; i++) {
    if (mac[i] < 16) Serial.print("0");
    Serial.print(mac[i], HEX);
    if (i < 5) Serial.print(":");
  }
  Serial.println();
}

Broadcast Communication

Broadcasting to All Peers

ESPNow espnow;

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  
  espnow.begin(onDataSent, onDataReceived);
  
  // Add multiple peers
  espnow.addMacAddress("24:6F:28:12:34:56");
  espnow.addMacAddress("24:6F:28:12:34:57");
  espnow.addMacAddress("24:6F:28:12:34:58");
  
  Serial.println("Broadcast mode ready");
}

void loop() {
  String message = "Broadcast: " + String(millis());
  
  bool success = espnow.broadcastData(
    (uint8_t *)message.c_str(),
    message.length(),
    onBroadcastError
  );
  
  if (success) {
    Serial.println("Broadcast sent to all peers");
  }
  
  delay(10000);
}

void onBroadcastError() {
  Serial.println("Broadcast error occurred");
}

Structured Data Communication

Sending Sensor Data

struct SensorData {
  float temperature;
  float humidity;
  uint32_t timestamp;
  uint8_t batteryLevel;
  char deviceId[16];
};

void sendSensorData() {
  SensorData data;
  data.temperature = 25.5;
  data.humidity = 60.2;
  data.timestamp = millis();
  data.batteryLevel = 85;
  strcpy(data.deviceId, "SENSOR_001");
  
  esp_err_t result = espnow.sendData(
    peerMAC,
    (uint8_t *)&data,
    sizeof(SensorData)
  );
  
  if (result == ESP_OK) {
    Serial.println("Sensor data sent");
  }
}

void onDataReceived(const uint8_t *mac, const uint8_t *incomingData, int len) {
  if (len == sizeof(SensorData)) {
    SensorData data;
    memcpy(&data, incomingData, sizeof(SensorData));
    
    Serial.print("Device: ");
    Serial.println(data.deviceId);
    Serial.print("Temperature: ");
    Serial.println(data.temperature);
    Serial.print("Humidity: ");
    Serial.println(data.humidity);
    Serial.print("Battery: ");
    Serial.print(data.batteryLevel);
    Serial.println("%");
  }
}

Command and Control

struct CommandMessage {
  uint8_t command;    // Command type
  uint8_t value;      // Command value
  uint32_t timestamp;
};

#define CMD_LED_ON      0x01
#define CMD_LED_OFF     0x02
#define CMD_SET_RELAY   0x03
#define CMD_REQUEST_STATUS 0x04

void sendCommand(uint8_t cmd, uint8_t val) {
  CommandMessage msg;
  msg.command = cmd;
  msg.value = val;
  msg.timestamp = millis();
  
  espnow.sendData(peerMAC, (uint8_t *)&msg, sizeof(CommandMessage));
}

void onDataReceived(const uint8_t *mac, const uint8_t *incomingData, int len) {
  if (len == sizeof(CommandMessage)) {
    CommandMessage msg;
    memcpy(&msg, incomingData, sizeof(CommandMessage));
    
    switch (msg.command) {
      case CMD_LED_ON:
        digitalWrite(LED_PIN, HIGH);
        Serial.println("LED ON");
        break;
        
      case CMD_LED_OFF:
        digitalWrite(LED_PIN, LOW);
        Serial.println("LED OFF");
        break;
        
      case CMD_SET_RELAY:
        digitalWrite(RELAY_PIN, msg.value);
        Serial.print("Relay: ");
        Serial.println(msg.value);
        break;
        
      case CMD_REQUEST_STATUS:
        sendStatusResponse();
        break;
    }
  }
}

Multi-Device Network

Hub-and-Spoke Topology

// Hub/Master device
ESPNow espnow;

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  
  espnow.begin(onDataSent, onDataReceived);
  
  // Set as master
  espnow.setThisDeviceIndex(0);
  
  // Add all sensor nodes
  espnow.addMacAddress("24:6F:28:12:34:56", ESPNow::MASTER);  // This is master
  espnow.addMacAddress("24:6F:28:78:9A:BC");  // Sensor 1
  espnow.addMacAddress("24:6F:28:78:9A:BD");  // Sensor 2
  espnow.addMacAddress("24:6F:28:78:9A:BE");  // Sensor 3
  
  espnow.debugMacAddressList();
  
  Serial.println("Hub ready - waiting for sensor data");
}

void onDataReceived(const uint8_t *mac, const uint8_t *incomingData, int len) {
  // Get sender index
  int senderIndex = espnow.getMacIndex(mac);
  
  Serial.print("Data from sensor ");
  Serial.print(senderIndex);
  Serial.print(": ");
  
  // Process data
  char buffer[len + 1];
  memcpy(buffer, incomingData, len);
  buffer[len] = '\0';
  Serial.println(buffer);
}

Mesh Network

// Each node can communicate with any other node
ESPNow espnow;

#define NODE_ID 1  // Change for each device

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  
  espnow.begin(onDataSent, onDataReceived);
  espnow.setThisDeviceIndex(NODE_ID);
  
  // Add all other nodes in the mesh
  if (NODE_ID != 1) espnow.addMacAddress("24:6F:28:12:34:56");  // Node 1
  if (NODE_ID != 2) espnow.addMacAddress("24:6F:28:78:9A:BC");  // Node 2
  if (NODE_ID != 3) espnow.addMacAddress("24:6F:28:78:9A:BD");  // Node 3
  
  Serial.print("Mesh node ");
  Serial.print(NODE_ID);
  Serial.println(" ready");
}

void sendToNode(uint8_t targetNode, String message) {
  // Send to specific node based on index
  // Implementation depends on your node organization
}

MAC Address Management

Converting MAC Formats

ESPNow espnow;

void setup() {
  // String to uint8_t array
  uint8_t mac[6];
  espnow.macStringToUint8("24:6F:28:12:34:56", mac);
  
  // uint8_t array to String
  String macStr = espnow.macAddressToString(mac);
  Serial.println(macStr);  // "24:6F:28:12:34:56"
}

Identifying Devices

void onDataReceived(const uint8_t *mac, const uint8_t *incomingData, int len) {
  // Check if message is from this device (loopback)
  uint8_t thisMac[6];
  WiFi.macAddress(thisMac);
  
  if (espnow.isThisDevice((uint8_t *)mac, thisMac)) {
    Serial.println("Loopback message - ignoring");
    return;
  }
  
  // Get sender index in peer list
  int senderIndex = espnow.getMacIndex(mac);
  Serial.print("Message from peer index: ");
  Serial.println(senderIndex);
}

Advanced Features

Error Handling

void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  if (status == ESP_NOW_SEND_SUCCESS) {
    Serial.println("Delivery success");
  } else {
    Serial.println("Delivery failed");
    
    // Retry logic
    retryCount++;
    if (retryCount < MAX_RETRIES) {
      delay(100);
      resendLastMessage();
    }
  }
}

Rate Limiting

unsigned long lastSendTime = 0;
const unsigned long SEND_INTERVAL = 100;  // 100ms minimum

void loop() {
  if (millis() - lastSendTime >= SEND_INTERVAL) {
    lastSendTime = millis();
    
    // Send data
    sendData();
  }
}

Channel Selection

void setup() {
  WiFi.mode(WIFI_STA);
  
  // Set specific WiFi channel (1-13)
  WiFi.channel(1);
  
  // Initialize ESP-NOW
  espnow.begin(onDataSent, onDataReceived);
  
  Serial.print("Operating on channel: ");
  Serial.println(WiFi.channel());
}

Power Optimization

Deep Sleep Integration

#define SLEEP_DURATION 60  // seconds

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  
  espnow.begin(onDataSent, onDataReceived);
  espnow.addMacAddress("24:6F:28:12:34:56");
  
  // Send data
  sendSensorData();
  
  // Wait for transmission
  delay(100);
  
  // Go to sleep
  Serial.println("Entering deep sleep...");
  ESP.deepSleep(SLEEP_DURATION * 1000000);
}

void loop() {
  // Never reached due to deep sleep
}

Light Sleep Mode

void loop() {
  // Send data
  sendSensorData();
  
  // Light sleep for 30 seconds
  WiFi.mode(WIFI_OFF);
  delay(30000);
  WiFi.mode(WIFI_STA);
  
  // Reinitialize ESP-NOW if needed
}

Troubleshooting

Check:
  • Both devices in station mode (WIFI_STA)
  • MAC addresses added correctly to peer list
  • Same WiFi channel on both devices
  • Devices within range (~200m max)
  • ESP-NOW initialized successfully
Debug:
Serial.println(WiFi.macAddress());  // Print MAC
Serial.println(WiFi.channel());     // Print channel
espnow.debugMacAddressList();       // Show peers
Possible causes:
  • WiFi interference (crowded 2.4 GHz band)
  • Distance too far
  • Obstacles blocking signal
  • Power supply issues
Solutions:
  • Change WiFi channel to less crowded one
  • Reduce distance or add relay nodes
  • Ensure stable 3.3V power supply
  • Add external antenna (ESP32)
Check send callback status:
void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  if (status != ESP_NOW_SEND_SUCCESS) {
    Serial.println("Send failed - possible causes:");
    Serial.println("- Peer not in range");
    Serial.println("- Peer not added to list");
    Serial.println("- Channel mismatch");
    Serial.println("- Message too large (>250 bytes)");
  }
}

Best Practices

Message Size Limit: Maximum payload is 250 bytes. Split larger messages into multiple packets.
Channel Coordination: All devices in an ESP-NOW network must be on the same WiFi channel. Set this explicitly in your code.
Peer Limit: ESP32 supports up to 20 paired peers (10 encrypted + 10 unencrypted). Plan your network topology accordingly.

Security Considerations

// For encrypted communication, add peers with encryption key
esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, peerMAC, 6);
peerInfo.channel = 0;
peerInfo.encrypt = true;

// Set encryption key (16 bytes)
uint8_t key[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
memcpy(peerInfo.lmk, key, 16);

esp_now_add_peer(&peerInfo);

Application Examples

Home Automation

Control lights, sensors, and appliances without WiFi router

Sensor Networks

Collect data from multiple sensors in real-time

Remote Control

Build custom wireless controllers for robotics

Emergency Systems

Peer-to-peer communication when infrastructure fails

ESP-NOW vs WiFi vs LoRa

FeatureESP-NOWWiFiLoRa
Range~200m~100m10+ km
Latency<5ms10-50ms100-1000ms
SetupNo routerRequires routerNo infrastructure
PowerMediumHighLow
Data Rate1 Mbps150+ Mbps0.3-50 kbps
Use CaseLocal meshInternet accessLong range IoT

LoRa Communication

Long-range alternative to ESP-NOW

WiFi Modules

Internet connectivity for ESP devices

Build docs developers (and LLMs) love