Why ESP-NOW?
ESP-NOW operates at the 802.11 MAC layer — there is no TCP handshake, no IP address, no DHCP, no router. One ESP32 sends a 250-byte packet directly to another ESP32’s MAC address and it arrives in under 2 milliseconds. For remote sensors, wireless remotes, and mesh networks where a router is impractical, ESP-NOW is the right tool.
Comparison: ESP-NOW vs Wi-Fi MQTT vs BLE
| Feature | ESP-NOW | Wi-Fi + MQTT | BLE |
|---|---|---|---|
| Latency | < 2 ms | 50–300 ms | 10–50 ms |
| Router needed | No | Yes | No |
| Max payload | 250 bytes | 256 MB | 512 bytes (MTU) |
| Peer limit | 20 encrypted | Unlimited | 9 (NimBLE) |
| Wake from sleep | ~50 ms | ~2000 ms | ~500 ms |
Point-to-Point Sender
#include <WiFi.h>
#include <esp_now.h>
// Replace with the MAC address printed by the RECEIVER sketch
uint8_t receiverMAC[] = {0x24, 0x6F, 0x28, 0xAB, 0xCD, 0xEF};
typedef struct {
float temperature;
float humidity;
uint32_t counter;
} SensorPacket;
SensorPacket packet;
void onDataSent(const uint8_t* mac, esp_now_send_status_t status) {
Serial.printf("Send to %02X:%02X:%02X:%02X:%02X:%02X — %s\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
status == ESP_NOW_SEND_SUCCESS ? "OK" : "FAIL");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW init failed");
return;
}
esp_now_register_send_cb(onDataSent);
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, receiverMAC, 6);
peer.channel = 0; // 0 = current channel
peer.encrypt = false;
esp_now_add_peer(&peer);
Serial.println("Sender ready. My MAC: " + WiFi.macAddress());
}
void loop() {
packet.temperature = 22.5 + (random(-20, 20) / 10.0);
packet.humidity = 55.0 + (random(-50, 50) / 10.0);
packet.counter++;
esp_err_t result = esp_now_send(receiverMAC,
(uint8_t*)&packet, sizeof(packet));
Serial.printf("Sent #%lu: %.1f°C %.1f%% — %s\n",
packet.counter, packet.temperature, packet.humidity,
result == ESP_OK ? "queued" : "error");
delay(2000);
}
Receiver
#include <WiFi.h>
#include <esp_now.h>
typedef struct {
float temperature;
float humidity;
uint32_t counter;
} SensorPacket;
void onDataRecv(const esp_now_recv_info_t* info,
const uint8_t* data, int len) {
SensorPacket* p = (SensorPacket*)data;
Serial.printf("From %02X:%02X:%02X:%02X:%02X:%02X — ",
info->src_addr[0], info->src_addr[1], info->src_addr[2],
info->src_addr[3], info->src_addr[4], info->src_addr[5]);
Serial.printf("#%lu %.1f°C %.1f%%\n",
p->counter, p->temperature, p->humidity);
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW init failed");
return;
}
esp_now_register_recv_cb(onDataRecv);
Serial.println("Receiver ready. My MAC: " + WiFi.macAddress());
Serial.println("Give this MAC to the sender sketch.");
}
void loop() {}
Broadcast to All Devices
uint8_t broadcastMAC[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
esp_now_peer_info_t bcastPeer = {};
memcpy(bcastPeer.peer_addr, broadcastMAC, 6);
bcastPeer.channel = 0;
bcastPeer.encrypt = false;
esp_now_add_peer(&bcastPeer);
// Send:
esp_now_send(broadcastMAC, (uint8_t*)&packet, sizeof(packet));
Deep Sleep Sender (Battery Sensor Node)
#include <WiFi.h>
#include <esp_now.h>
#include <esp_sleep.h>
#define SLEEP_SECONDS 60
uint8_t receiverMAC[] = {0x24, 0x6F, 0x28, 0xAB, 0xCD, 0xEF};
volatile bool sent = false;
typedef struct { float temp; uint32_t bootCount; } Pkt;
RTC_DATA_ATTR uint32_t bootCount = 0;
void onSent(const uint8_t* mac, esp_now_send_status_t s) {
Serial.printf("Sent: %s\n", s == ESP_NOW_SEND_SUCCESS ? "OK" : "FAIL");
sent = true;
}
void setup() {
Serial.begin(115200);
bootCount++;
WiFi.mode(WIFI_STA);
WiFi.disconnect();
esp_now_init();
esp_now_register_send_cb(onSent);
esp_now_peer_info_t p = {};
memcpy(p.peer_addr, receiverMAC, 6);
esp_now_add_peer(&p);
Pkt pkt = { 23.5, bootCount };
esp_now_send(receiverMAC, (uint8_t*)&pkt, sizeof(pkt));
// Wait for send callback (max 200 ms)
unsigned long t = millis();
while (!sent && millis() - t < 200) delay(10);
Serial.printf("Sleeping %d s (boot #%lu)\n", SLEEP_SECONDS, bootCount);
esp_deep_sleep(SLEEP_SECONDS * 1000000ULL);
}
void loop() {}