Skip to content

Communication ESP NOW

pschatzmann edited this page Oct 22, 2026 · 1 revision

ESP-NOW is Espressif's connectionless WiFi protocol: ESP32 devices send packets directly to each other by their MAC address, without an access point, a router or a login. It is easy to set up and has a low latency - a good choice for a wireless microphone or a link between two ESP32 boards. See Communication for the general considerations.

The ESPNowStream class

ESPNowStream (AudioTools/Communication/ESPNowStream.h) makes ESP-NOW an Arduino Stream: what you write is sent to the peers, what is received can be read. It is supported on the ESP32.

Each device needs to know the MAC addresses of its peers. You can define the device's own MAC address in the configuration, which makes this easy: give the sender and the receiver fixed addresses and add each other as peer.

Method Description
begin(cfg) starts ESP-NOW with the configuration (defaultConfig())
addPeer(mac), addPeers(array) adds the receivers (MAC as "A8:48:FA:0B:93:01" string or bytes)
addBroadcastPeer() sends to all ESP-NOW devices in range
setChannel(ch) WiFi channel; all devices must use the same one
write(), readBytes(), available() send and receive

The most important configuration fields (ESPNowStreamConfig):

Field Description
mac_address the device's own MAC address (nullptr: the default one)
use_send_ack default true: each write waits until the receiver has confirmed the previous packet - no buffer overflow on the receiver, but slower. Broadcast does not support it.
write_retry_count, delay_after_failed_write_ms what to do if a packet was not confirmed
buffer_size, buffer_count receive buffers (one packet is at most 250 bytes, 1470 with ESP-NOW v2 in newer ESP-IDF versions)
start_read_threshold_percent the receiver starts reading only when its buffer is filled to this level - absorbs jitter
channel WiFi channel (0: automatic)
ssid, password connect to WiFi at the same time (then the channel is the one of the access point)
use_long_range Espressif's long range mode: more range, less throughput
rate, phymode WiFi bit rate and PHY mode (default 6 Mbit/s, 802.11g)
primary_master_key, local_master_key encryption

Sending and receiving

From the pcm examples. The sender:

#include "AudioTools.h"
#include "AudioTools/Communication/ESPNowStream.h"

AudioInfo info(8000, 1, 16);
SineGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> sound(sineWave);
ESPNowStream now;
StreamCopy copier(now, sound);
const char *peers[] = {"A8:48:FA:0B:93:01"};  // the receiver

void setup() {
  auto cfg = now.defaultConfig();
  cfg.mac_address = "A8:48:FA:0B:93:02";  // the sender
  now.begin(cfg);
  now.addPeers(peers);
  copier.setRetry(0);
  sineWave.begin(info, N_B4);
}

void loop() { copier.copy(); }

With use_send_ack (the default) the sender is slowed down to the receiver's speed, so the generator needs no Throttle. The receiver:

AudioInfo info(8000, 1, 16);
ESPNowStream now;
I2SStream out;
StreamCopy copier(out, now);
const char *peers[] = {"A8:48:FA:0B:93:02"};  // the sender

void setup() {
  auto cfg = now.defaultConfig();
  cfg.mac_address = "A8:48:FA:0B:93:01";  // the receiver
  now.begin(cfg);
  now.addPeers(peers);
  auto config = out.defaultConfig(TX_MODE);
  config.copyFrom(info);
  out.begin(config);
}

void loop() { copier.copy(); }

Broadcast

To reach several receivers, send to the broadcast address - without acknowledgments, so the sender must be throttled to the audio rate, and the receivers should buffer a bit. From the broadcast examples:

// sender
ESPNowStream now;
Throttle throttle(now);
StreamCopy copier(throttle, sound);
...
auto cfg = now.defaultConfig();
cfg.use_send_ack = false;  // broadcast does not support acknowledgments
now.begin(cfg);
now.addBroadcastPeer();

// receiver
auto cfg = now.defaultConfig();
cfg.start_read_threshold_percent = 50;  // start playing when the buffer is half full

Bandwidth and codecs

ESP-NOW is fast enough for low sample rates in PCM (8 kHz, 16 bit mono = 128 kbit/s), but the throughput drops with distance and interference - measure it with the speed-test examples, or the *_measure receivers. For better quality use a codec: the codec examples send SBC:

SBCEncoder sbc;
EncodedAudioStream encoder(&now, &sbc);                  // sender: encode and send
EncodedAudioStream decoder(&out, new SBCDecoder(256));   // receiver: a packet has at most 256 bytes

Things to consider

  • All devices on the same WiFi channel: if one device is also connected to an access point, it uses the access point's channel - set the same channel on the others.
  • Clock drift: sender and receiver clocks differ slightly - see Communication.
  • Multiple receivers with acknowledgments: only the first receiver that acknowledges is used as coordinator - use broadcast for several receivers.

Clone this wiki locally