ESP32 ESP-NOW Tutorial: Build a Low-Latency Sensor Network Without WiFi (2026 Guide)
If you've ever tried to get two ESP32 boards talking to each other and immediately hit a wall of router credentials, IP addresses, and MQTT broker setup just to send a single number, you already know the problem. WiFi is overkill for a lot of what we actually build. Sometimes you just need Board A to tell Board B "the temperature is 24.6°C" — instantly, without a network, without pairing screens, and without draining a battery in two days.
That's exactly what ESP-NOW was built for. It's Espressif's own connectionless protocol that sits directly on top of the WiFi radio but skips the entire WiFi stack — no router, no DHCP, no handshake delay. In my own tests, packets land in under 2 milliseconds at close range, and a battery-powered sensor node sleeping between sends can realistically run for 6–12 months on a single 18650 cell.
This guide walks through what ESP-NOW actually is, when to use it instead of WiFi or Bluetooth, and how to wire up a working sender-and-receiver pair with real code you can paste into the Arduino IDE right now.
What Is ESP-NOW, Really?
ESP-NOW is a proprietary wireless communication protocol developed by Espressif for its ESP8266, ESP32, and newer ESP32-C/S series chips. It uses the 2.4GHz radio already built into the chip, but instead of routing traffic through an access point, devices talk directly to each other using MAC addresses — similar in spirit to Bluetooth Low Energy's connectionless advertising, but running on the WiFi radio and capable of higher throughput.
The practical result: two ESP32 boards can exchange small data packets (up to 250 bytes per message) without ever joining a network. There's no SSID, no password, no IP address. You register a peer's MAC address, call one send function, and the data arrives.
ESP-NOW vs WiFi vs Bluetooth vs LoRa: When to Actually Use It
This is the part most tutorials skip, and it's the part that actually saves you time.
| Protocol | Setup Complexity | Latency | Range (open air) | Power Use | Best For |
|---|---|---|---|---|---|
| ESP-NOW | Very Low | ~1-2ms | 100-220m | Very Low | Sensor swarms, remotes, quick device-to-device links |
| WiFi (MQTT) | High | 20-100ms | Router-dependent | High | Cloud dashboards, home assistant integration |
| Bluetooth BLE | Medium | 20-50ms | 10-30m | Low | Phone pairing, wearables |
| LoRa | High | 100ms+ | 1-10km | Very Low | Long-range, low-bandwidth telemetry |
If your project needs a phone app or a cloud dashboard, ESP-NOW alone won't get you there — you'll still want WiFi/MQTT for that final hop. But for board-to-board communication, remote controls, robot swarms, or a cluster of battery sensors reporting to one hub, ESP-NOW is almost always faster to build and cheaper to run than the alternatives.
What You'll Need
- 2x ESP32 boards (any variant — DevKit, ESP32-C3, ESP32-S3 all support ESP-NOW)
- Arduino IDE with the ESP32 board package installed
- USB cables for both boards
- (Optional) A DHT22 or similar sensor for the real-world example at the end
Step 1: Find Each Board's MAC Address
ESP-NOW pairs devices by MAC address, so you need to know the receiver's address before the sender can talk to it. Upload this to your receiver board first:
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
Serial.print("Receiver MAC Address: ");
Serial.println(WiFi.macAddress());
}
void loop() {}Open the Serial Monitor, copy the printed MAC address — you'll need it in Step 3.
Step 2: Set Up the Receiver
#include <esp_now.h>
#include <WiFi.h>
typedef struct SensorMessage {
float temperature;
int deviceId;
} SensorMessage;
SensorMessage incomingData;
void onDataReceive(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
memcpy(&incomingData, data, sizeof(incomingData));
Serial.print("Device ");
Serial.print(incomingData.deviceId);
Serial.print(" reports: ");
Serial.print(incomingData.temperature);
Serial.println(" °C");
}
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(onDataReceive);
}
void loop() {}Step 3: Set Up the Sender
Replace the MAC address below with the one you copied in Step 1.
#include <esp_now.h>
#include <WiFi.h>
uint8_t receiverAddress[] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX}; // <-- paste receiver MAC here
typedef struct SensorMessage {
float temperature;
int deviceId;
} SensorMessage;
SensorMessage outgoingData;
esp_now_peer_info_t peerInfo;
void onDataSent(const uint8_t *mac, esp_now_send_status_t status) {
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivered" : "Delivery failed");
}
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_send_cb(onDataSent);
memcpy(peerInfo.peer_addr, receiverAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("Failed to add peer");
return;
}
}
void loop() {
outgoingData.temperature = random(200, 300) / 10.0; // swap for real sensor reading
outgoingData.deviceId = 1;
esp_now_send(receiverAddress, (uint8_t *) &outgoingData, sizeof(outgoingData));
delay(2000);
}Upload the sender code to your second board, open its Serial Monitor, and you should immediately see "Delivered" messages — while the receiver's monitor prints the incoming temperature readings in real time.
Turning This Into a Real Battery-Powered Sensor Node
Swap the random() line for an actual DHT22 or BMP280 reading, then wrap the loop() body with ESP32 deep sleep so the board wakes, reads, sends, and sleeps again:
esp_sleep_enable_timer_wakeup(600 * 1000000); // sleep 10 minutes
esp_deep_sleep_start();Sending a single ESP-NOW packet and going back to sleep draws power for only a few milliseconds instead of holding a WiFi connection open. This is the combination that gets you months of battery life on a coin cell or small LiPo — the same principle covered in more depth in our ESP32 sleep modes guide.
Common ESP-NOW Problems and Fixes
- "esp_now_add_peer failed" — Usually means the sender and receiver are on different WiFi channels. Set both to channel 0 or explicitly match them with
WiFi.channel(). - No data received — Double-check the MAC address was copied exactly, including all six byte pairs.
- Works at close range, fails farther away — ESP-NOW range drops fast through walls. Antenna orientation and 2.4GHz interference from routers matter more than raw distance.
- Want more than 2 devices? — ESP-NOW supports up to 20 registered peers per board, making it genuinely usable for small sensor swarms, not just a single pair.
Frequently Asked Questions
Can ESP-NOW and WiFi run at the same time on one ESP32? Yes. You can keep a device connected to your home WiFi for cloud reporting while it simultaneously uses ESP-NOW to collect data from nearby sensor nodes — this is a common "gateway" pattern.
Is ESP-NOW secure?
It supports optional encryption (peerInfo.encrypt = true with a shared key), though it's designed for local, trusted device clusters rather than internet-facing traffic.
What's the real-world range? Expect roughly 100-220 meters in open line-of-sight, dropping significantly indoors — plan for one wall between nodes as a practical working range in most homes.
Wrapping Up
ESP-NOW is one of those tools that quietly solves a problem most people don't realize they have until they've already wasted an afternoon setting up MQTT for something that should've taken ten minutes. Once you've got two boards exchanging data without a router in sight, the next natural step is combining this with deep sleep for genuinely long-lived, battery-powered sensor networks.
If you're exploring ways to turn side projects like this into income beyond ad revenue — from affiliate content to niche monetization strategies — petlifehacks.page (sponsored/related resource) has practical breakdowns worth a look.
