MicroPython: ESP-NOW с ESP32 (начало работы)
Узнайте, как использовать протокол связи ESP-NOW с ESP32, запрограммированной на MicroPython. ESP-NOW — это протокол связи без установления соединения, созданный Espressif и предназначенный для передачи коротких пакетов данных. Это один из самых простых способов беспроводной связи между платами ESP32. В настоящее время прошивка MicroPython для ESP32 включает два встроенных модуля — espnow и aioespnow — которые предоставляют классы и функции для работы с ESP-NOW.
Используете Arduino IDE? Следуйте этому руководству: Начало работы с ESP-NOW (ESP32 с Arduino IDE).
Предварительные требования — прошивка MicroPython
Для выполнения этого руководства вам нужна прошивка MicroPython, установленная на ваших платах ESP32. Вам также понадобится IDE для написания и загрузки кода на плату. Мы рекомендуем использовать Thonny IDE:
Новичок в MicroPython? Ознакомьтесь с книгой: MicroPython Programming with ESP32 and ESP8266 eBook (2nd Edition)
Знакомство с ESP-NOW
ESP-NOW — это протокол беспроводной связи, разработанный Espressif, который позволяет нескольким платам ESP32 или ESP8266 обмениваться небольшими объёмами данных без использования Wi-Fi или Bluetooth. ESP-NOW не требует полного Wi-Fi-соединения (хотя контроллер Wi-Fi должен быть включён), что делает его идеальным для приложений с низким энергопотреблением и низкой задержкой, таких как сенсорные сети, пульты дистанционного управления или обмен данными между платами.
ESP-NOW использует модель связи без установления соединения, что означает, что устройства могут отправлять и получать данные без подключения к маршрутизатору или настройки точки доступа (в отличие от HTTP-связи между платами). Он поддерживает одноадресную (unicast — отправка данных на конкретное устройство по его MAC-адресу) и широковещательную (broadcast — отправка данных всем ближайшим устройствам с использованием широковещательного MAC-адреса) передачу сообщений.
ESP-NOW поддерживает следующие функции:
Поддержка как зашифрованной, так и незашифрованной одноадресной связи.
Прямая связь между 20 зарегистрированными пирами или 6 зашифрованными пирами.
Возможность одновременной связи с зашифрованными и незашифрованными устройствами.
Отправка пакетов данных размером до 250 байт.
ESP-NOW очень универсален, и вы можете организовать одностороннюю или двустороннюю связь в различных конфигурациях. Например:
Одна плата ESP32 отправляет данные другой плате ESP32;
Двусторонняя связь ESP-NOW между двумя платами;
Одна ESP32 принимает данные от нескольких плат;
Одна плата ESP32 отправляет данные нескольким платам;
Несколько плат ESP32 отправляют и принимают данные друг от друга, создавая подобие сети.
Основные понятия ESP-NOW
Есть несколько базовых понятий, которые нужно понимать при работе с ESP-NOW:
Отправитель (Sender): устройство, которое отправляет данные с помощью ESP-NOW.
Приёмник (Receiver): устройство, которое получает данные, отправленные отправителем.
MAC-адрес: уникальный 6-байтовый аппаратный адрес, назначенный Wi-Fi-интерфейсу каждого устройства. ESP-NOW использует MAC-адреса для идентификации устройств. Если вы хотите отправить данные на конкретную плату, вам нужно знать её MAC-адрес.
Широковещательный MAC-адрес: MAC-адрес вида FF:FF:FF:FF:FF:FF используется для отправки сообщения всем ближайшим устройствам. Любой ESP32 или ESP8266 в зоне действия может его получить.
Пир (Peer): конкретное устройство (идентифицируемое по его MAC-адресу), которое вы регистрируете на своём ESP-NOW-устройстве, чтобы оно могло отправлять или получать данные от этого устройства.
Пакет (Packet): данные, передаваемые от одного устройства к другому. Пакеты ESP-NOW могут нести до 250 байт данных.
Требования для ESP-NOW
Перед использованием ESP-NOW необходимо учитывать несколько важных требований:
Один и тот же Wi-Fi-канал: все устройства, взаимодействующие через ESP-NOW, должны находиться на одном Wi-Fi-канале. Если это не так, они не смогут отправлять и получать пакеты друг от друга. Обычно это делается автоматически в коде, если только одно из устройств также не подключено к Wi-Fi-сети.
MAC-адрес: для отправки сообщений конкретному устройству отправитель должен заранее знать MAC-адрес приёмника.
Регистрация пиров: вы должны зарегистрировать пира перед отправкой ему данных, за исключением использования широковещательного MAC-адреса на платах ESP32.
Wi-Fi: Wi-Fi-интерфейс (режим станции или точки доступа) должен быть активен, даже если он не подключён к сети.
ESP32: Получение MAC-адреса платы
Для связи через ESP-NOW вам нужно знать MAC-адрес ваших плат. Чтобы получить MAC-адрес платы, скопируйте следующий код в Thonny IDE и запустите его на вашей плате.
# 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)
После запуска кода MAC-адрес платы должен отобразиться в терминале (Shell).
Рекомендуем наклеить метку или стикер на каждую плату, чтобы можно было чётко их различать.
ESP-NOW: Односторонняя связь «точка-точка» (модуль espnow)
Для начала работы с беспроводной связью ESP-NOW мы создадим простой проект, который показывает, как отправить сообщение с одной ESP32 на другую. Одна ESP32 будет отправителем, а другая ESP32 — приёмником.
Код отправителя ESP32 (ESP-NOW с MicroPython)
Следующий код отправляет сообщение каждую секунду на плату-приёмник ESP32. Скопируйте его в Thonny IDE, затем запустите или загрузите на плату.
# 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
Вам нужно вставить MAC-адрес платы-приёмника или использовать широковещательный MAC-адрес.
В моём случае MAC-адрес платы-приёмника — 30:AE:A4:F6:7D:4C. Его нужно преобразовать в формат bytes следующим образом:
30:AE:A4:F6:7D:4C >
b'\x30\xae\xa4\xf6\x7d\x4c'
Сделайте то же самое для MAC-адреса вашей платы-приёмника.
# Receiver's MAC address
receiver_mac = b'\x30\xae\xa4\xf6\x7d\x4c'
Как работает код?
Давайте кратко разберём, как работает код и наиболее важные функции ESP-NOW.
Импорт модулей
Сначала импортируем необходимые модули. В этом примере мы используем модуль espnow для работы с функциями и классами ESP-NOW.
import network
import espnow
import time
Инициализация Wi-Fi-интерфейса
Затем нужно инициализировать Wi-Fi (даже если мы его не используем) для работы ESP-NOW. Можно использовать режим станции (STA_IF) или точки доступа (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()
Позже, если при тестировании пакеты не доставляются, возможно, потребуется вручную указать Wi-Fi-канал. Он должен быть одинаковым на платах отправителя и приёмника.
#sta.config(channel=1) # Set channel explicitly if packets are not delivered
Инициализация ESP-NOW
Затем можно инициализировать ESP-NOW. Сначала создайте экземпляр espnow с именем e. Затем активируйте его с помощью метода active(), передав True в качестве аргумента.
# Initialize ESP-NOW
e = espnow.ESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize ESP-NOW:", err)
raise
Чтобы деактивировать ESP-NOW, передайте False в метод active(). Если вызвать метод active() без аргументов, он вернёт текущее состояние ESP-NOW.
Мы активируем ESP-NOW внутри конструкции try / except, чтобы перехватить ошибки при неудачной инициализации.
Добавление пира ESP-NOW
Добавьте MAC-адрес приёмника (или используйте широковещательный MAC-адрес для отправки сообщения всем устройствам в зоне действия):
# Receiver's MAC address
receiver_mac = b'\x30\xae\xa4\xf6\x7d\x4c'
#receiver_mac = b'\xff\xff\xff\xff\xff\xff' #broadcast
Затем добавляем MAC-адрес приёмника в качестве пира с помощью метода add_peer().
# Add peer
try:
e.add_peer(receiver_mac)
except OSError as err:
print("Failed to add peer:", err)
raise
Вывод статистики ESP-NOW
Затем создаём функцию print_stats(), которую будем вызывать позже в коде. Она выводит текущую статистику пакетов ESP-NOW. Для получения количества отправленных/полученных/потерянных пакетов вызываем метод stats() объекта ESP-NOW e.
Он возвращает 5-кортеж, содержащий количество отправленных/полученных/потерянных пакетов:
(tx_pkts, tx_responses, tx_failures, rx_packets, rx_dropped_packets)
Затем выводим каждый из этих результатов:
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]}")
Отправка сообщения ESP-NOW
Создаём основной цикл, который отправляет сообщение со счётчиком (message_count) каждую секунду.
message_count = 0
while True:
try:
# Create a sample message with a counter
message = f"Hello! ESP-NOW message #{message_count}"
Отправляем сообщение с помощью метода send() объекта ESP-NOW e. Эта функция принимает в качестве аргументов MAC-адрес платы-приёмника, сообщение и последний параметр — логическое значение (True — отправить сообщение пиру и ждать ответа; False — вернуться немедленно).
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})")
После отправки сообщения увеличиваем счётчик и проверяем, не пора ли вывести новую статистику.
# 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
Подведём итог…
Подведём итог — вот основные шаги:
Инициализировать Wi-Fi-интерфейс;
Инициализировать ESP-NOW;
Добавить пира;
Отправить сообщение.
Запуск кода
После подключения платы к компьютеру и установления соединения с Thonny IDE вы можете загрузить код как main.py на плату или запустить его, нажав зелёную кнопку Run.
Он начнёт выводить сообщения в терминал (Shell). Отправка сообщений будет неудачной, потому что мы ещё не подготовили приёмник.
Код приёмника ESP32 (ESP-NOW с MicroPython)
Следующий код прослушивает входящие пакеты ESP-NOW от пира (платы-отправителя, которую мы запрограммировали ранее). Откройте ещё одно окно Thonny IDE (независимо от первого).
Включение двух экземпляров Thonny IDE
Перейдите в Tools > Options и снимите галочку с опции Allow only single Thonny instance.
Скопируйте код в Thonny IDE, затем запустите или загрузите его на плату.
# 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
Как работает код?
Код аналогичен коду платы-отправителя. Единственное отличие — в цикле, где мы прослушиваем входящие пакеты.
Указание MAC-адреса платы-отправителя и добавление его в качестве пира является необязательным. Однако это рекомендуется для лучшей надёжности, особенно если пакеты не принимаются.
В моём случае MAC-адрес платы-отправителя — 30:AE:A4:07:0D:64. Его нужно преобразовать в формат bytes следующим образом:
30:AE:A4:07:0D:64 >
b'\x30\xae\xa4\x07\x0d\x64'
Сделайте то же самое для MAC-адреса вашей платы-отправителя.
# Sender's MAC address
sender_mac = b'\x30\xae\xa4\x07\x0d\x64' # Sender MAC
Если вы хотите добавить отправителя в качестве пира, раскомментируйте следующую часть кода:
#try:
# e.add_peer(sender_mac)
#except OSError as err:
# print("Failed to add peer:", err)
# raise
Приём сообщений ESP-NOW
Для приёма сообщений используем метод recv() объекта ESP-NOW e.
host, msg = e.recv(10000)
Этот метод ожидает входящее сообщение и возвращает MAC-адрес пира (сохраняется в переменной host) и сообщение (сохраняется в переменной msg). Для приёма сообщения от пира не обязательно регистрировать его (с помощью add_peer()).
В качестве аргумента он принимает таймаут в миллисекундах. Таймаут — это время, в течение которого функция будет ожидать входящее сообщение, прежде чем сдаться и вернуть управление. Он может иметь следующие значения:
0: Без таймаута. Немедленный возврат, если данных нет.> 0: указать значение таймаута в миллисекундах;< 0: без таймаута: ожидать новых сообщений бесконечно;None: использовать значение таймаута по умолчанию, установленное вESPNOW.config().
Когда мы получаем новое сообщение, выводим MAC-адрес хоста и само сообщение.
if msg:
print(f"Received from {host.hex()}: {msg.decode()}")
Запуск кода
Откройте новое окно Thonny IDE и установите соединение с платой-приёмником. Загрузите и/или запустите код приёмника на этой новой плате.
Она начнёт принимать пакеты ESP-NOW от другой платы.
В это же время вы увидите, что плата-отправитель теперь выводит сообщения об успешной отправке.
Каждые 10 секунд отправитель и приёмник выводят статистику ESP-NOW в терминал.
Вы можете объединить функции, используемые в обоих скетчах, и установить двустороннюю связь. Также можно добавить больше плат в вашу систему и создать сеть.
ESP-NOW: Односторонняя связь «точка-точка» (модуль aioespnow)
Существует ещё один модуль, который поддерживает ESP-NOW асинхронно с использованием модуля asyncio — это модуль aioespnow. В этом разделе мы создадим тот же пример, что и в предыдущем, но с использованием модуля aioespnow, который поддерживает асинхронное программирование.
Перед продолжением рекомендуем ознакомиться с асинхронным программированием на MicroPython: MicroPython: ESP32/ESP8266 — Асинхронное программирование — Запуск нескольких задач.
Код отправителя ESP32 (ESP-NOW с MicroPython)
Следующий код делает то же самое, что и в предыдущем примере, но использует модуль aioespnow для асинхронного программирования.
# 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 (ESP-NOW с MicroPython)
Аналогично, следующий код принимает входящие пакеты ESP-NOW от платы-отправителя, но использует модуль aioespnow для асинхронного программирования.
# 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)
Для получения дополнительной информации о модулях, функциях и классах ESP-NOW для MicroPython, ознакомьтесь с документацией.
Заключение
В этом руководстве мы познакомили вас с основами протокола связи ESP-NOW с ESP32, запрограммированной на MicroPython.
ESP-NOW — очень универсальный протокол, который может быть полезен для беспроводной передачи данных между платами, таких как показания датчиков или команды для управления выходами. Вы можете добавить несколько плат ESP32 в вашу систему и создать сеть плат, обменивающихся данными друг с другом.
Если вас интересует эта тема, мы можем создать больше руководств по ESP-NOW с MicroPython. Сообщите нам в комментариях.
Для дальнейшего изучения MicroPython ознакомьтесь с нашими ресурсами: