MicroPython: ESP-NOW with ESP32 – Control Multiple Boards (One to Many)

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.

MicroPython ESP-NOW with ESP32 Control Multiple Boards One to Many

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 - ESP32 Logo

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.

ESP32 Controller Sends Data to Multiple Boards via ESP-NOW Communication Protocol

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:

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)

View raw code

After running the code, it should print the board’s MAC address on the shell.

Thonny IDE - Get ESP32 Board MAC Address - MicroPython

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.

ESP32 ESP-NOW Controller with Three Pushbuttons

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 connected to three pushbuttons - circuit diagram

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)

View raw code

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.

Thonny IDE Save to 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.

Save main.py to micropython device

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.

Three ESP32 boards with ESP-NOW code to receive data

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.

ESP32 with an LED connected to GPIO 2

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)

View raw code

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.

Thonny IDE Save to 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.

Save main.py to micropython device

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.

One ESP32 board controls three ESP32 boards via ESP-NOW by pressing a pushbutton for 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:

If you want to learn more about programming the ESP32 using MicroPython firmware, check out our resources:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.