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
Parameter ESP32 ESP8266 Notes Max Range ~200m ~200m Line of sight, optimal conditions Max Peers 20 20 Total paired devices Data Rate Up to 1 Mbps Up to 1 Mbps Shared WiFi channel bandwidth Latency <5 ms <5 ms Typical Message Size 250 bytes 250 bytes Maximum payload Encryption Optional Optional AES encryption support Power ~100 mA TX ~80 mA TX Depends 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[] = { 0x 24 , 0x 6F , 0x 28 , 0x 12 , 0x 34 , 0x 56 };
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 0x 01
#define CMD_LED_OFF 0x 02
#define CMD_SET_RELAY 0x 03
#define CMD_REQUEST_STATUS 0x 04
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
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
Devices Not Communicating
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
Intermittent Communication
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[] = { 0x 01 , 0x 02 , 0x 03 , 0x 04 , 0x 05 , 0x 06 , 0x 07 , 0x 08 ,
0x 09 , 0x 0A , 0x 0B , 0x 0C , 0x 0D , 0x 0E , 0x 0F , 0x 10 };
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
Feature ESP-NOW WiFi LoRa Range ~200m ~100m 10+ km Latency <5ms 10-50ms 100-1000ms Setup No router Requires router No infrastructure Power Medium High Low Data Rate 1 Mbps 150+ Mbps 0.3-50 kbps Use Case Local mesh Internet access Long range IoT
LoRa Communication Long-range alternative to ESP-NOW
WiFi Modules Internet connectivity for ESP devices