In this guide, you’ll learn how to use ESP-NOW to control multiple ESP32 boards from a single ESP32 main controller (one-to-many setup). Think of it like a remote control that sends commands to several devices. We’ll show you how to set up one ESP32 board to wirelessly control three other boards using the ESP-NOW communication protocol—perfect for home automation, sensor networks, or projects involving multiple IoT devices.

Using Arduino IDE? Follow this tutorial instead: ESP-NOW with ESP32: Send Data to Multiple Boards (one-to-many).
Prerequisites – MicroPython Firmware
To follow this tutorial, you need MicroPython firmware installed on your ESP32 or ESP8266 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.
New to ESP-NOW? Read our getting started guide: MicroPython: ESP-NOW with ESP32 (Getting Started).
Project Overview
This tutorial shows how to set up an ESP32 board as an ESP-NOW main controller that sends commands to multiple ESP32 boards.

Here’s an overview of how the project works:
- One ESP32 board will act as a controller board that sends commands to other ESP32 boards.
- The controller will be connected to three pushbuttons. Each button controls an LED of one of the receiver boards.
- The ESP32 controller board sends messages to a specific board by referring to its MAC address.
- When you press the pushbutton for a particular board, it will toggle the current LED state.
- The ESP32 receiver boards will be listening for ESP-NOW packages. When they receive a message from the controller, they will control their corresponding LED accordingly.
Parts Required
To follow the project in this tutorial, you’ll need the following parts:
- 4x (at least three) ESP32 boards
- 3x Pushbuttons
- 3x LEDs
- 3x 220 Ohm resistors (or similar values)
- Breadboard
- Jumper wires
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
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.

Get the MAC address for all of your boards.
For example, in my case, I get:
- Sender board: 24:0A:C4:31:38:5C
- Receiver board 1: DC:B8:15:81:2B:64
- Receiver board 2: 30:AE:A4:07:0D:64
- Receiver board 3: 30:AE:A4:F6:7D:4C
Preparing the ESP32 Sender Board
The sender board will be connected to three pushbuttons. Each pushbutton will control one LED of a different ESP32.

Wiring the Circuit
Start by wiring three pushbuttons to the ESP32 sender board. We’ll connect the pushbuttons to GPIOs 21, 22, and 23. You can use any other suitable GPIOs (don’t forget to check out the ESP32 board pinout).
We’ll use the ESP32 internal pull-down resistors. So, you don’t need to add resistors to the circuit.

ESP32 ESP-NOW Controller – The Sender Code
Copy the following code to Thonny IDE.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp-now-esp32-one-to-many/
import network
import aioespnow
import asyncio
from machine import Pin
# 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
# Define pins for pushbuttons
button1_pin = Pin(21, Pin.IN, Pin.PULL_UP) # Button for Board 1
button2_pin = Pin(22, Pin.IN, Pin.PULL_UP) # Button for Board 2
button3_pin = Pin(23, Pin.IN, Pin.PULL_UP) # Button for Board 3
# Define MAC addresses for receiving boards
board1_mac = b'\xff\xff\xff\xff\xff\xff' # MAC address for Board 1
board2_mac = b'\xff\xff\xff\xff\xff\xff' # MAC address for Board 2
board3_mac = b'\xff\xff\xff\xff\xff\xff' # MAC address for Board 3
# Build dictionary with pin and MAC variables
boards = {
1: {"pin": button1_pin, "mac": board1_mac},
2: {"pin": button2_pin, "mac": board2_mac},
3: {"pin": button3_pin, "mac": board3_mac}
}
# Add peers
for board_num, info in boards.items():
try:
e.add_peer(info["mac"])
print(f"Added Board {board_num} peer: {info['mac']}")
except OSError as err:
print(f"Failed to add Board {board_num} peer: {err}")
raise
# Async function to monitor buttons and send toggle commands
async def monitor_buttons(e):
# Track LED states (True = ON, False = OFF)
led_states = {1: False, 2: False, 3: False}
while True:
for board_num, info in boards.items():
button = info["pin"]
if button.value() == 0: # Button pressed (active low)
led_states[board_num] = not led_states[board_num] # Toggle LED
command = "LED_ON" if led_states[board_num] else "LED_OFF"
if await e.asend(info["mac"], command, sync=True):
print(f"Board {board_num}: {command}")
else:
print(f"Board {board_num}: Failed to send")
await asyncio.sleep(0.3) # Debounce delay
await asyncio.sleep(0.1) # Short loop delay
# Main async function
async def main(e):
await monitor_buttons(e)
# Run the async program
try:
asyncio.run(main(e))
except KeyboardInterrupt:
print("Stopping...")
e.active(False)
sta.active(False)
Make sure to modify the code with your receivers’ boards’ MAC addresses.
How Does the Code Work?
Let’s take a quick look at how the code works. Alternatively, you can skip to the next section.
Importing Libraries
Start by importing the required libraries. We’re using the aioespnow library for ESP-NOW, which is an asynchronous version of the espnow library.
import network
import aioespnow
import asyncio
from machine import Pin
If you’re new to asynchronous programming, we recommend following this guide to better understand how it works: MicroPython ESP32/ESP8266 Asynchronous Programming – Run Multiple Tasks.
Initialize the Wi-Fi Interface and ESP-NOW
To use ESP-NOW, you first need to initialize the Wi-Fi interface.
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.disconnect()
Then, you can initialize ESP-NOW communication protocol
# Initialize AIOESPNow
e = aioespnow.AIOESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize AIOESPNow:", err)
raise
Defining the Pushbuttons
Then, define the pins for the pushbuttons. If you’re using different GPIOs for the pushbuttons, change accordingly.
# Define pins for pushbuttons
button1_pin = Pin(21, Pin.IN, Pin.PULL_UP) # Button for Board 1
button2_pin = Pin(22, Pin.IN, Pin.PULL_UP) # Button for Board 2
button3_pin = Pin(23, Pin.IN, Pin.PULL_UP) # Button for Board 3
Receiver Boards’ MAC Addresses
Add the MAC address of the receiver boards in the following lines. The MAC address should be in bytes format. For example:
- 30:AE:A4:F6:7D:4C turns into b’\x30\xae\xa4\xf6\x7d\x4c’
# Define MAC addresses for receiving boards
board1_mac = b'\x30\xae\xa4\x07\x0d\x64' # MAC address for Board 1
board2_mac = b'\x0c\xb8\x15\x81\x2b\x64' # MAC address for Board 2
board3_mac = b'\x30\xae\xa4\xf6\x7d\x4c' # MAC address for Board 3
Boards Dictionary
We create a dictionary to associate each board number with its pushbutton and MAC address.
# Build dictionary with pin and MAC variables
boards = {
1: {"pin": button1_pin, "mac": board1_mac},
2: {"pin": button2_pin, "mac": board2_mac},
3: {"pin": button3_pin, "mac": board3_mac}
}
Add Peers
Then, we add the receiver boards as peers.
# Add peers
for board_num, info in boards.items():
try:
e.add_peer(info["mac"])
print(f"Added Board {board_num} peer: {info['mac']}")
except OSError as err:
print(f"Failed to add Board {board_num} peer: {err}")
raise
Monitor Buttons and Send Messages
The monitor_buttons function will check the state of each pushbutton and send a message to the corresponding board.
async def monitor_buttons(e):
First, we create another dictionary to hold the state of the LED for each board.
led_states = {1: False, 2: False, 3: False}
Then, we check whether a pushbutton was pressed
for board_num, info in boards.items():
button = info["pin"]
if button.value() == 0: # Button pressed (active low)
led_states[board_num] = not led_states[board_num] # Toggle LED
command = "LED_ON" if led_states[board_num] else "LED_OFF"
if await e.asend(info["mac"], command, sync=True):
Since we’re using the buttons in an active-low state, when the value of the pushbutton is 0, it means it was pressed.
if button.value() == 0: # Button pressed (active low)
When that happens, we toggle the LED state for the board whose button was pressed (in the led_states dictionary) and prepare the command to send via ESP-NOW.
if button.value() == 0: # Button pressed (active low)
led_states[board_num] = not led_states[board_num] # Toggle LED
command = "LED_ON" if led_states[board_num] else "LED_OFF"
Finally, we call the asend() method on the espnow e object to send the message to the corresponding board by referring to its MAC address.
if await e.asend(info["mac"], command, sync=True):
print(f"Board {board_num}: {command}")
else:
print(f"Board {board_num}: Failed to send")
await asyncio.sleep(0.3) # Debounce delay
Create and Run the Asynchronous Task
Finally, we create the asynchronous main function and run the asynchronous task.
# Main async function
async def main(e):
await monitor_buttons(e)
# Run the async program
try:
asyncio.run(main(e))
except KeyboardInterrupt:
print("Stopping...")
e.active(False)
sta.active(False)
Uploading the Code to the Sender Board
After modifying the code with the MAC address of each receiver board, you can upload it to your board.
In Thonny IDE, with a connection established with your board, go to File > Save as > MicroPython device.

Save the code with the name main.py, otherwise it will not work. When you name a file main.py, it will automatically run when the ESP32 restarts or resets.

After uploading the code to your board, reset the board by pressing the RST button or by unplugging and plugging power. It will start running the code.
At the moment, the code will not work because we haven’t prepared the receiver boards yet.
Preparing the Receiver Boards
Follow this procedure for each of your ESP32 receiver boards. In this example, we’re using three ESP32 receiver boards, but you can use only two (or even just one), or more than three.

Wiring the Circuit
Wire one LED to each of your receiver boards. We’re connecting the LED to GPIO2, but you can connect to any other GPIO.

ESP-NOW Receiver Code
Copy the following code to Thonny IDE and upload it to each of your receiver boards. This code will listen for ESP-NOW packets and turn the LED on or off accordingly.
Important: don’t forget to modify the code with the MAC address of the sender board.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp-now-esp32-one-to-many/
import network
import aioespnow
import asyncio
from machine import Pin
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1) # Match sender's channel
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'\xff\xff\xff\xff\xff\xff' # You need to replace with actual sender MAC address
# Add sender as peer for more reliability
try:
e.add_peer(sender_mac)
print(f"Added sender peer: {sender_mac}")
except OSError as err:
print(f"Failed to add sender peer: {err}")
raise
# Initialize LED
led = Pin(2, Pin.OUT)
led.value(0)
# Async function to receive messages and control LED
async def receive_messages(e):
while True:
try:
async for mac, msg in e:
message = msg.decode()
print(f"Received from {mac.hex()}: {message}")
if message == "LED_ON":
led.value(1) # Turn LED on
print("LED turned ON")
elif message == "LED_OFF":
led.value(0) # Turn LED off
print("LED turned OFF")
except OSError as err:
print("Error:", err)
await asyncio.sleep(5)
# Main async function
async def main(e):
await receive_messages(e)
# Run the async program
try:
asyncio.run(main(e))
except KeyboardInterrupt:
print("Stopping receiver...")
led.value(0) # Turn off LED on exit
e.active(False)
sta.active(False)
How Does the Code Work?
Continue reading to learn how the code works, or skip to the demonstration section.
Importing Libraries
Start by importing the required libraries.
import network
import aioespnow
import asyncio
from machine import Pin
Initialize the Wi-Fi Interface and ESP-NOW
To use ESP-NOW, you first need to initialize the Wi-Fi interface.
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1) # Match sender's channel
sta.disconnect()
Then, you can initialize ESP-NOW communication protocol.
# Initialize AIOESPNow
e = aioespnow.AIOESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize AIOESPNow:", err)
raise
Add the Sender as a Peer
Add the sender board as a peer. Don’t forget to replace with your actual sender MAC address.
# Sender's MAC address
sender_mac = b'\x24\x0A\xC4\x31\x38\x5C' # Replace with actual sender MAC
# Add sender as peer for more reliability
try:
e.add_peer(sender_mac)
print(f"Added sender peer: {sender_mac}")
except OSError as err:
print(f"Failed to add sender peer: {err}")
raise
Initialize the LED
Initialize the LED as an output on GPIO 2 and set its initial state to LOW.
# Initialize LED
led = Pin(2, Pin.OUT)
led.value(0)
Receive ESP-NOW Messages and Control the LED
The receive_messages() asynchronous function will be listening for ESP-NOW messages.
async def receive_messages(e):
while True:
try:
async for mac, msg in e:
message = msg.decode()
print(f"Received from {mac.hex()}: {message}")
Then, it will turn the LED on or off according to the received message.
if message == "LED_ON":
led.value(1) # Turn LED on
print("LED turned ON")
elif message == "LED_OFF":
led.value(0) # Turn LED off
print("LED turned OFF")
Create and Run the Asynchronous Task
Finally, we create the asynchronous main function and run the asynchronous task.
# Main async function
async def main(e):
await receive_messages(e)
# Run the async program
try:
asyncio.run(main(e))
except KeyboardInterrupt:
print("Stopping receiver...")
led.value(0) # Turn off LED on exit
e.active(False)
sta.active(False)
Uploading the Code to the Receiver Boards
After modifying the code with the MAC address of the sender board, you can upload the code provided to each of your receiver boards.
In Thonny IDE, with a connection established with your board, go to File > Save as > MicroPython device.

Save the code with the name main.py, otherwise it will not work. When you name a file main.py, it will automatically run when the ESP32 restarts or resets.

After uploading the code to your board, reset the board by pressing the RST button or by unplugging and plugging in power. It will start running the code.
Demonstration
Press the pushbuttons on the sender board. Each button will toggle the LED state for a specific board. The ESP32 sender works like a remote control to send individual commands to each board.

You can take a look at the following video to see this project working.
Wrapping Up
In this tutorial, you learned how to send ESP-NOW messages from one ESP32 sender to multiple boards. This is just one of the many examples of how you can use ESP-NOW in your IoT and automation projects.
We hope you’ve found this guide and project useful. We have more ESP-NOW guides that you may like:
- MicroPython: ESP-NOW with ESP32 (Getting Started)
- MicroPython: ESP32 ESP-NOW Two-Way Communication
- MicroPython: ESP-NOW with ESP32—Receive Data from Multiple Boards (many-to-one)
If you want to learn more about programming the ESP32 using MicroPython firmware, check out our resources:




