Learn how to use ESP-NOW communication protocol with the ESP32 programmed with MicroPython. ESP-NOW is a connectionless communication protocol created by Espressif, designed for short packet transmission. It is one of the easiest ways to communicate between ESP32 boards wirelessly. Currently, the MicroPython firmware for the ESP32 includes two built-in modules, espnow and aioespnow, that provide classes and functions for working with ESP-NOW.

Using Arduino IDE? Follow this tutorial instead: Getting Started with ESP-NOW (ESP32 with Arduino IDE).
Prerequisites – MicroPython Firmware
To follow this tutorial, you need MicroPython firmware installed on your ESP32 boards. You also need an IDE to write and upload the code to your board. We suggest using Thonny IDE:
New to MicroPython? Check out our eBook: MicroPython Programming with ESP32 and ESP8266 eBook (2nd Edition)
Introducing ESP-NOW
ESP-NOW is a wireless communication protocol developed by Espressif that allows multiple ESP32 or ESP8266 boards to exchange small amounts of data without using Wi-Fi or Bluetooth. ESP-NOW does not require a full Wi-Fi connection (though the Wi-Fi controller must be turned on), making it ideal for low-power and low-latency applications like sensor networks, remote controls, or data exchange between boards.

ESP-NOW uses a connectionless communication model, meaning devices can send and receive data without connecting to a router or setting up an access point (unlike HTTP communication between boards). It supports unicast (sending data to a specific device using its MAC address) and broadcast (sending data to all nearby devices using a broadcast MAC address) messaging.
ESP-NOW supports the following features:
- Supports both encrypted and unencrypted unicast communication.
- Direct communication between up to 20 registered peers or 6 encrypted peers.
- Can communicate with a mix of encrypted and unencrypted devices at the same time.
- Allows sending data packets of up to 250 bytes.
ESP-NOW is very versatile and you can have one-way or two-way communication in different setups. For example:




- One ESP32 board sending data to another ESP32 board;
- ESP-NOW two-way communication between two boards;
- One ESP32 receiving data from multiple boards;
- One ESP32 board sending data to multiple boards;
- Multiple ESP32 boards sending and receiving data from multiple ESP32 boards to create something that looks like a network.
Basic Concepts in ESP-NOW
There are a few basic concepts you need to understand in ESP-NOW:
- Sender: the device that sends data using ESP-NOW.
- Receiver: the device that receives the data sent by the sender.
- MAC Address: a unique 6-byte hardware address assigned to each device’s Wi-Fi interface. ESP-NOW uses MAC addresses to identify devices. If you want to send data to a specific board, you need to know its MAC address.
- Broadcast MAC Address: a MAC address like this FF:FF:FF:FF:FF:FF is used to send a message to all nearby devices. Any ESP32 or ESP8266 in range can receive it.
- Peer: a specific device (identified by its MAC address) that you register on your ESP-NOW device so that it can send or receive data with that device.
- Packet: the data sent from one device to another. ESP-NOW packets can carry up to 250 bytes of data.
Requirements for ESP-NOW
Before using ESP-NOW, there are a few important requirements:
Same Wi-Fi channel: all devices communicating via ESP-NOW must be on the same Wi-Fi channel. If they’re not, they won’t be able to receive and send packets to one another. Usually, this is done automatically in the code, unless one of the devices is also connected to a Wi-Fi network.
MAC address: to send messages to a specific device, the sender must know the receiver’s MAC address in advance.
Registration of peers: you must register a peer before sending data to it, except when using the broadcast MAC address on ESP32 boards.
Wi-Fi: the Wi-Fi interface (station or access point mode) must be active, even if not connected.
ESP32: Getting Board MAC Address
To communicate via ESP-NOW, you need to know the MAC address of your boards. To get your board’s MAC Address, you can copy the following code to Thonny IDE and run it on your board.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp-now-esp32/
import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Get MAC address (returns bytes)
mac = wlan.config('mac')
# Convert to human-readable format
mac_address = ':'.join('%02x' % b for b in mac)
print("MAC Address:", mac_address)
After running the code, it should print the board’s MAC address on the shell.

We recommend that you add a label or sticker to each board so that you can clearly identify them.

ESP-NOW One-way Point to Point Communication (espnow module)
To get you started with ESP-NOW wireless communication, we’ll build a simple project that shows how to send a message from one ESP32 to another. One ESP32 will be the “sender” and the other ESP32 will be the “receiver”.

ESP32 Sender Code (ESP-NOW with MicroPython)
The following code sends a message every second to an ESP32 receiver board. Copy it to your Thonny IDE and then run it or upload it to the board.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp-now-esp32/
import network
import espnow
import time
# Stats tracking
last_stats_time = time.time()
stats_interval = 10 # Print stats every 10 seconds
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
#sta.config(channel=1) # Set channel explicitly if packets are not delivered
sta.disconnect()
# Initialize ESP-NOW
e = espnow.ESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize ESP-NOW:", err)
raise
# Receiver's MAC address
receiver_mac = b'\x30\xae\xa4\xf6\x7d\x4c'
#receiver_mac = b'\xff\xff\xff\xff\xff\xff' #broadcast
# Add peer
try:
e.add_peer(receiver_mac)
except OSError as err:
print("Failed to add peer:", err)
raise
def print_stats():
stats = e.stats()
print("\nESP-NOW Statistics:")
print(f" Packets Sent: {stats[0]}")
print(f" Packets Delivered: {stats[1]}")
print(f" Packets Dropped (TX): {stats[2]}")
print(f" Packets Received: {stats[3]}")
print(f" Packets Dropped (RX): {stats[4]}")
# Main loop to send messages
message_count = 0
while True:
try:
# Create a sample message with a counter
message = f"Hello! ESP-NOW message #{message_count}"
# Send the message with acknowledgment
try:
if e.send(receiver_mac, message, True):
print(f"Sent message: {message}")
else:
print("Failed to send message (send returned False)")
except OSError as err:
print(f"Failed to send message (OSError: {err})")
message_count += 1
# Print stats every 10 seconds
if time.time() - last_stats_time >= stats_interval:
print_stats()
last_stats_time = time.time()
time.sleep(1) # Send every 1 second
except OSError as err:
print("Error:", err)
time.sleep(5)
except KeyboardInterrupt:
print("Stopping sender...")
e.active(False)
sta.active(False)
break
You need to insert the receiver board’s MAC address or use the broadcast MAC address.
In my case, the receiver board MAC address is 30:AE:A4:F6:7D:4C. So, I need to convert it to bytes format as follows:
- 30:AE:A4:F6:7D:4C > b’\x30\xae\xa4\xf6\x7d\x4c’
Do the same for your receiver board’s MAC address.
# Receiver's MAC address
receiver_mac = b'\x30\xae\xa4\xf6\x7d\x4c'
How Does the Code Work?
Let’s take a quick look at how the code works and the most relevant ESP-NOW functions.
Importing Modules
First, import the required modules. For this example, we’re using the espnow module to use ESP-NOW functions and classes.
import network
import espnow
import time
Initialize the Wi-Fi Interface
Then, we need to initialize Wi-Fi (even if we don’t use it) to use ESP-NOW. We can use station (STA_IF) or access point mode (AP_IF).
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
#sta.config(channel=1) # Set channel explicitly if packets are not delivered
sta.disconnect()
Later, when testing if the packets are not being delivered, you may need to manually explicit which Wi-Fi channel to use. It must be the same on both the receiver and sender boards.
#sta.config(channel=1) # Set channel explicitly if packets are not delivered
Initialize ESP-NOW
Then, we can initialize ESP-NOW. First, create an espnow instance called e. Then activate it using the active() method and passing the True value as argument.
# Initialize ESP-NOW
e = espnow.ESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize ESP-NOW:", err)
raise
To deactivate ESP-NOW, pass False to the active() method. If you use the active() method without any arguments, it will return the current ESP-NOW state.
We activate ESP-NOW inside a try and except statements so that we can catch any errors if the initialization fails.
Add ESP-NOW Peer
Add the receiver MAC address (or use the broadcast MAC address to send the message to all devices within its range):
# Receiver's MAC address
receiver_mac = b'\x30\xae\xa4\xf6\x7d\x4c'
#receiver_mac = b'\xff\xff\xff\xff\xff\xff' #broadcast
Then, we can add the receiver’s MAC address as a peer using the add_peer() method.
# Add peer
try:
e.add_peer(receiver_mac)
except OSError as err:
print("Failed to add peer:", err)
raise
Print ESP-NOW Statistics
Then, we create a function to call later in the code, called print_stats() that prints current statistics about the ESP-NOW packets. To get the number of packets sent/received and lost, we can call the stats() method on the ESP-NOW e object.
This returns a 5-tuple containing the number of packets sent/received/lost:
(tx_pkts, tx_responses, tx_failures, rx_packets, rx_dropped_packets)
Then, we print each of those results:
print("\nESP-NOW Statistics:")
print(f" Packets Sent: {stats[0]}")
print(f" Packets Delivered: {stats[1]}")
print(f" Packets Dropped (TX): {stats[2]}")
print(f" Packets Received: {stats[3]}")
print(f" Packets Dropped (RX): {stats[4]}")
Sending ESP-NOW Message
We create a main loop to send a message with a counter (message_count) every second.
message_count = 0
while True:
try:
# Create a sample message with a counter
message = f"Hello! ESP-NOW message #{message_count}"
We send the message using the send() method on the ESP-NOW e object. This function accepts as arguments the receiver board’s MAC address, the message, and the last parameter is a boolean value (True to send a message to the peer and wait for a response; or False to return immediately).
try:
if e.send(receiver_mac, message, True):
print(f"Sent message: {message}")
else:
print("Failed to send message (send returned False)")
except OSError as err:
print(f"Failed to send message (OSError: {err})")
After sending the message, we increment the counter and check if it’s time to print new statistics.
# Print stats every 10 seconds
if time.time() - last_stats_time >= stats_interval:
print_stats()
last_stats_time = time.time()
time.sleep(1) # Send every 1 second
In summary…
In summary, these are the steps:
- Initialize the Wi-Fi interface;
- Initialize ESP-NOW;
- Add a Peer;
- Send a Message.
Running the Code
After connecting the board to your computer and establishing a communication with Thonny IDE, you can upload the code as main.py to the board or run it using the green Run button.

It will start printing messages in the Shell. It will fail to send the message because we haven’t prepared the receiver yet.

ESP32 Receiver Code (ESP-NOW with MicroPython)
The following code listens for incoming ESP-NOW packets from its peer (the sender board we programmed previously). Open another window of Thonny IDE (independent of the first one).
Enabling Two Instances of Thonny IDE
Go to Tools > Options and untick the option Allow only single Thonny instance.
Copy the code to your Thonny IDE and then run it or upload it to the board.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp-now-esp32/
import network
import espnow
import time
# Stats tracking
last_stats_time = time.time()
stats_interval = 10 # Print stats every 10 seconds
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1) # Set channel explicitly if packets are not received
sta.disconnect()
# Initialize ESP-NOW
e = espnow.ESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize ESP-NOW:", err)
raise
# Sender's MAC address
sender_mac = b'\x30\xae\xa4\x07\x0d\x64' # Sender MAC
# Add peer (sender) for unicast reliability
# You don't need to add peer for broadcast
#try:
# e.add_peer(sender_mac)
#except OSError as err:
# print("Failed to add peer:", err)
# raise
def print_stats():
stats = e.stats()
print("\nESP-NOW Statistics:")
print(f" Packets Sent: {stats[0]}")
print(f" Packets Delivered: {stats[1]}")
print(f" Packets Dropped (TX): {stats[2]}")
print(f" Packets Received: {stats[3]}")
print(f" Packets Dropped (RX): {stats[4]}")
print("Listening for ESP-NOW messages...")
while True:
try:
# Receive message (host MAC, message, timeout of 10 seconds)
host, msg = e.recv(10000)
# Print stats every 10 seconds
if time.time() - last_stats_time >= stats_interval:
print_stats()
last_stats_time = time.time()
except OSError as err:
print("Error:", err)
time.sleep(5)
except KeyboardInterrupt:
print("Stopping receiver...")
e.active(False)
sta.active(False)
break
How Does the Code Work?
The code is similar to the sender board. The only difference is the loop, where we’ll listen for incoming packets.
Inserting the Sender Board’s MAC address and add it as a peer is optional. However, it’s good to implement it for better reliability, especially if you’re not receiving the packets.
In my case, the sender board MAC address is 30:AE:A4:07:0D:64. So, I need to convert it to bytes format as follows:
- 30:AE:A4:07:0D:64 > b’\x30\xae\xa4\x07\x0d\x64′
Do the same for your sender board’s MAC address.
# Sender's MAC address
sender_mac = b'\x30\xae\xa4\x07\x0d\x64' # Sender MAC
If you want to add the sender as a peer, uncommend the following part of the code:
#try:
# e.add_peer(sender_mac)
#except OSError as err:
# print("Failed to add peer:", err)
# raise
Receiving ESP-NOW Messages
To receive messages, we can use the recv() method on the ESP-NOW e object.
host, msg = e.recv(10000)
This method waits for an incoming message and returns the mac address of the peer (saved in the host variable) and the message (saved in the msg variable). It is not necessary to register a peer (using add_peer()) to receive a message from that peer.
It accepts as arguments the timeout in milliseconds. The timeout refers to the amount of time the function will wait to receive an incoming message before giving up and returning. It can have the following values
- 0: No timeout. Return immediately if no data is available.
- > 0: specify a timeout value in milliseconds;
- < 0: do not timeout: wait forever for new messages;
- None: uses the default timeout value set with ESPNOW.config()
When we receive a new message, we print the host MAC address and the message.
if msg:
print(f"Received from {host.hex()}: {msg.decode()}")
Running the Code
With a new window on Thonny IDE open, establish a connection with this receiver board. Upload and/or run the receiver code on this new board.
It will start receiving the ESP-NOW packets from the other board.

At the same time, you’ll see that the sender board will print success messages now.

Every 10 seconds, the sender and receiver print the ESP-NOW statistics on the shell.
You can combine the functions used in both sketches and establish a two-way communication. You can also add more boards to your setup and create a network.
ESP-NOW One-way Point to Point Communication (aioespnow module)
There is another module that supports ESP-NOW asynchronously using the asyncio module—it is the aioespnow module. In this section, we’ll create the same example as the previous one, but using the aioespnow module, which supports asynchronous programming.
Before proceeding, we recommend learning about asynchronous programming with MicroPython: MicroPython: ESP32/ESP8266 Asynchronous Programming – Run Multiple Tasks.
ESP32 Sender Code (ESP-NOW with MicroPython)
The following code does the same as the one in the previous example, but it uses the aioespnow module if you want to use asynchronous programming instead.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp-now-esp32/
import network
import aioespnow
import asyncio
import time
# Stats tracking
last_stats_time = time.time()
stats_interval = 10 # Print stats every 10 seconds
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.disconnect()
# Initialize AIOESPNow
e = aioespnow.AIOESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize AIOESPNow:", err)
raise
# Receiver's MAC address (broadcast for testing)
#receiver_mac = b'\x30\x0e\xa4\xf6\x7d\x4c'
receiver_mac = b'\xff\xff\xff\xff\xff\xff'
# Add peer
try:
e.add_peer(receiver_mac)
except OSError as err:
print("Failed to add peer:", err)
raise
def print_stats():
stats = e.stats()
print("\nESP-NOW Statistics:")
print(f" Packets Sent: {stats[0]}")
print(f" Packets Delivered: {stats[1]}")
print(f" Packets Dropped (TX): {stats[2]}")
print(f" Packets Received: {stats[3]}")
print(f" Packets Dropped (RX): {stats[4]}")
# Async function to send messages
async def send_messages(e, peer):
message_count = 0
while True:
try:
message = f"Hello! AIOESPNow message #{message_count}"
if await e.asend(peer, message, sync=True):
print(f"Sent message: {message}")
else:
print("Failed to send message")
message_count += 1
# Print stats every 10 seconds
if time.time() - last_stats_time >= stats_interval:
print_stats()
await asyncio.sleep(1) # Send every 1 second
except OSError as err:
print("Error:", err)
await asyncio.sleep(5)
# Main async function
async def main(e, peer):
await send_messages(e, peer)
# Run the async program
try:
asyncio.run(main(e, receiver_mac))
except KeyboardInterrupt:
print("Stopping sender...")
e.active(False)
sta.active(False)
ESP32 Receiver Code (ESP-NOW with MicroPython)
Similarly, the following code receives incoming ESP-NOW packets from the sender board, but it uses the aioespnow module for asynchronous programming.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp-now-esp32/
import network
import aioespnow
import asyncio
import time
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1) # Set channel explicitly if packets are not received
sta.disconnect()
# Initialize AIOESPNow
e = aioespnow.AIOESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize AIOESPNow:", err)
raise
# Sender's MAC address
sender_mac = b'\x30\xae\xa4\x07\x0d\x64' # Sender MAC for unicast
# Add peer (sender) for unicast reliability
# You don't need to add peer for broadcast
#try:
# e.add_peer(sender_mac)
#except OSError as err:
# print("Failed to add peer:", err)
# raise
# Async function to receive messages
async def receive_messages(e):
while True:
try:
async for mac, msg in e:
print(f"Received from {mac.hex()}: {msg.decode()}")
except OSError as err:
print("Error:", err)
await asyncio.sleep(5)
# Async function to print stats periodically
async def print_stats(e):
global last_stats_time
last_stats_time = time.time()
stats_interval = 10 # Print stats every 10 seconds
while True:
if time.time() - last_stats_time >= stats_interval:
stats = e.stats()
print("\nESP-NOW Statistics:")
print(f" Packets Sent: {stats[0]}")
print(f" Packets Delivered: {stats[1]}")
print(f" Packets Dropped (TX): {stats[2]}")
print(f" Packets Received: {stats[3]}")
print(f" Packets Dropped (RX): {stats[4]}")
last_stats_time = time.time()
await asyncio.sleep(1) # Check every second
# Main async function
async def main(e):
# Run receive and stats tasks concurrently
await asyncio.gather(receive_messages(e), print_stats(e))
# Run the async program
try:
asyncio.run(main(e))
except KeyboardInterrupt:
print("Stopping receiver...")
e.active(False)
sta.active(False)
For more information about ESP-NOW modules, functions, and classes for MicroPython, check the documentation.
Wrapping Up
In this tutorial, we introduced you to the basics of ESP-NOW communication protocol with the ESP32 programmed with MicroPython.
ESP-NOW is a very versatile protocol that can be useful to transfer data wirelessly between boards, like sensor readings, or commands to control outputs. You can add multiple ESP32 boards to your setup and create a network of boards that exchange data between them.
If you’re interested in this topic, we can create more tutorials about ESP-NOW with MicroPython. Let us know in the comments section.
To learn more about MicroPython, you can check out our resources:
muito bom, obrigado !