ESP32: ESP-NOW, peer-to-peer messages without a router
Send messages between ESP32 boards directly, without Wi-Fi infrastructure. Up to 250 bytes per packet, up to 20 peers, range of 200m+ line of sight.
ESP-NOW is the Espressif protocol that lets ESP32 boards talk to each other directly, without a Wi-Fi router, without an access point, and without any infrastructure. Each board can send messages to up to 20 peers, and the range is 200+ meters line of sight (less through walls). Messages are limited to 250 bytes per packet.
This is the protocol I use for sensor networks, robot-to-controller links, and any project where adding a router feels like overkill.
ESP-NOW is Espressif-specific. The Arduino Uno, Pi, and Pico do not support it.
What you need
- Two or more ESP32 boards (the protocol needs at least 2 to do anything useful)
- A USB cable per board
The MAC address
Every ESP-NOW message is addressed by MAC address. Each ESP32 has a unique MAC burned into the chip. Get yours with this:
#include <WiFi.h>
void setup() {
Serial.begin(115200);
Serial.print("MAC: ");
Serial.println(WiFi.macAddress());
}
void loop() {}
Upload this to each ESP32, record the MAC address, and use it in your sender code.
The code: sender and receiver
Sender
#include <esp_now.h>
#include <WiFi.h>
uint8_t receiverMAC[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; // change this
typedef struct {
int sensorValue;
float temperature;
char label[20];
} SensorPacket;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA); // ESP-NOW requires station mode
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW init failed");
return;
}
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, receiverMAC, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("Failed to add peer");
return;
}
Serial.println("Sender ready");
}
unsigned long lastSend = 0;
void loop() {
if (millis() - lastSend > 1000) {
lastSend = millis();
SensorPacket packet;
packet.sensorValue = analogRead(34);
packet.temperature = 22.5;
strcpy(packet.label, "kitchen");
esp_err_t result = esp_now_send(receiverMAC, (uint8_t *)&packet, sizeof(packet));
if (result == ESP_OK) {
Serial.println("Sent");
} else {
Serial.print("Send failed: ");
Serial.println(result);
}
}
}
Receiver
#include <esp_now.h>
#include <WiFi.h>
typedef struct {
int sensorValue;
float temperature;
char label[20];
} SensorPacket;
void onReceive(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
if (len == sizeof(SensorPacket)) {
SensorPacket packet;
memcpy(&packet, data, sizeof(packet));
Serial.print(packet.label);
Serial.print(": T=");
Serial.print(packet.temperature);
Serial.print(" sensor=");
Serial.println(packet.sensorValue);
}
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW init failed");
return;
}
esp_now_register_recv_cb(onReceive);
Serial.println("Receiver ready");
}
void loop() {
}
Upload the receiver to one ESP32 and the sender to another. Open Serial Monitor on the receiver. After a few seconds, you should see the packets arriving.
The packet size limit
ESP-NOW limits packets to 250 bytes. For larger data, split into multiple packets or use a smaller payload. The struct above is 28 bytes (4 + 4 + 20), well within the limit.
For projects that need more than 250 bytes per message (e.g. a camera image), use Wi-Fi TCP or HTTP instead. ESP-NOW is for short messages.
The number of peers
Each ESP32 can have up to 20 encrypted peers in its peer list. The unencrypted limit is higher but rarely useful. For networks with more than 20 devices, use broadcast mode (no peer registration):
uint8_t broadcastMAC[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
void broadcast() {
SensorPacket packet;
// ... fill packet ...
esp_now_send(broadcastMAC, (uint8_t *)&packet, sizeof(packet));
}
Broadcast mode is unencrypted and any ESP32 within range receives the message.
Encryption
For encrypted ESP-NOW, set the LMK (Long-term Key) on both sender and receiver:
// On both sender and receiver
esp_now_set_pmk((uint8_t *)"1234567890123456"); // 16-byte key
Both sides must use the same key. Encrypted ESP-NOW is slower (about half the throughput) but private.
The range
ESP-NOW uses the same radio as Wi-Fi, so range is similar. Line of sight, expect 200+ meters. Through walls, 30-50 meters depending on construction. For longer range, use an external antenna ESP32 module.
Combining with Wi-Fi
ESP-NOW can coexist with Wi-Fi on the same chip. The pattern:
WiFi.mode(WIFI_AP_STA); // both AP and station
WiFi.begin(ssid, password);
esp_now_init();
// ... use both
The chip can be connected to a Wi-Fi network and sending ESP-NOW messages simultaneously. Useful for sensor nodes that publish to Wi-Fi-based MQTT and also communicate directly with each other.
Common projects
- Sensor networks. Many ESP32 sensors broadcast their readings; a central receiver collects them.
- Robot-to-controller. A handheld controller sends movement commands to a robot. No need for a router.
- Trigger signals. A motion sensor sends “motion detected” to a central ESP32 that controls lights and alarms.
- Mesh networks. Combine with ESP-MESH for self-healing multi-hop networks (see the ESP-MESH tutorial).
What you learned
- ESP-NOW is a peer-to-peer protocol built into every ESP32.
- Up to 20 encrypted peers, 250 bytes per packet.
- No router or access point required.
- Same range as Wi-Fi (200m+ line of sight).
When something breaks
- Sender says “Send failed”. Peer not added, or receiver MAC is wrong. Re-add the peer.
- Receiver gets nothing. WiFi.mode(WIFI_STA) not set on both sides, or the receiver callback is not registered.
- Range is much shorter than expected. Antenna is covered, or the boards are on different channels (set channel explicitly).
- Encryption fails. LMK key is wrong on one side.
What to build next
- The ESP-MESH tutorial combines multiple ESP-NOW links into a self-healing network.
- The ESP32 MQTT tutorial publishes ESP-NOW-received data to a broker.
- The book ESP32 Mesh Networks covers multi-hop ESP-NOW with routing.