ESP-NOW
Boards talking straight to each other by MAC address, with no router, no IP address and no handshake. A battery sensor can wake, send and be asleep again in 15 milliseconds - about two hundred times faster than joining Wi-Fi.
Fifteen milliseconds against three seconds
What you give up
- No internet. Nothing outside the radio's range exists.
- No phone. ESP-NOW is an Espressif protocol; phones do not speak it.
- 250 bytes a packet. Send a packed struct, not JSON.
- One channel for everybody. If any board is also on Wi-Fi, that board's router chooses the channel and everyone else must follow.
The shape most projects end up with
Battery sensors send ESP-NOW to one mains-powered board. That board is on Wi-Fi and forwards everything to MQTT or a web page. The sensors get years on a battery; the gateway gets a wall socket; nobody has to choose.
The code
Send a struct, not a string. Both ends compile the same definition, so there is no parsing and the whole reading fits in one packet with room to spare.
#include <WiFi.h>
#include <esp_now.h>
uint8_t peer[] = { 0x24, 0x6F, 0x28, 0x00, 0x00, 0x00 }; // the receiver
typedef struct { uint8_t id; float temp; uint16_t mv; } Reading;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
esp_now_init();
esp_now_peer_info_t info = {};
memcpy(info.peer_addr, peer, 6);
info.channel = 1; // both ends must agree
esp_now_add_peer(&info);
Reading r = { 3, 21.4, 3820 };
esp_err_t err = esp_now_send(peer, (uint8_t *)&r, sizeof(r));
Serial.println(err == ESP_OK ? "queued" : "failed");
}
void loop() {}The receiver's MAC address is printed by WiFi.macAddress() on that board. There is no discovery - you either hard-code it or broadcast to FF:FF:FF:FF:FF:FF.
The receiving side is a loop with a blocking read. Add the sender as a peer first, or its packets are ignored.
import network, espnow, struct
sta = network.WLAN(network.STA_IF)
sta.active(True)
print('my mac', sta.config('mac'))
e = espnow.ESPNow()
e.active(True)
while True:
mac, msg = e.recv()
if msg:
node, temp, mv = struct.unpack('<BfH', msg)
print(node, temp, mv)espnow.recv returns the sender's MAC alongside the data, which is how one receiver serves a dozen sensors without any addressing scheme of your own.
When it does not work
The two boards are on different channels. ESP-NOW has no channel negotiation. If one board is also joined to Wi-Fi, it is locked to the router's channel and the other must match it.
Same cause. A receiver that is also a Wi-Fi station follows the router. Read WiFi.channel() on that board and set the sender to match.
Success means the packet left the radio, not that anybody heard it. Add an acknowledgement of your own if delivery matters.
It should be similar. Check that neither board is in a metal case and that you are not sending from inside a deep-sleep wake-up before the radio has settled - give it a few milliseconds.
Peer to peer works to the edge of range. Past that, nodes have to relay for each other.
ESP-WIFI-MESH →Edit this page — content/esp32/esp-now.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.