ESP
DIYer
32
LAB

Moderní mikrokontrolér v rukou „bastlíře“

Komunikace ESP-NOW v MicroPythonu

Přestože nás seriál o MicroPythonu na modulu ESP32 již skončil, ve chvíli, kdy se jeho jednotlivé články staly základem stránek ESP32 DIYerLAB, nám dává smysl pokračovat v dalším hraní si s modulem ESP32 a z toho pak publikovat další články. Dnešní článek je tedy věnován komunikaci ESP-NOW.

ESP-NOW je bezdrátový komunikační protokol vyvinutý společností Espressif pro čipy ESP32 a ESP8266. Na první pohled připomíná Wi-Fi, ale funguje úplně jinak. Umožňuje posílat krátké datové zprávy přímo mezi moduly bez nutnosti Wi-Fi routeru, připojení k síti nebo používání TCP/IP komunikace. Právě díky této jednoduchosti je ESP-NOW velmi oblíbený mezi bastlíři a tvůrci IoT zařízení. Pokud potřebujete bezdrátově přenést stav tlačítka, hodnotu senzoru nebo řídicí příkaz z jednoho ESP32 do druhého, bývá ESP-NOW často rychlejší, úspornější a jednodušší než klasická Wi-Fi komunikace. Na rozdíl od HTTP nebo WebSocketů pracuje ESP-NOW přímo na linkové vrstvě. Zařízení si proto neposílají data pomocí IP adres, ale komunikují přímo prostřednictvím svých MAC adres. Díky tomu odpadá navazování síťových spojení a přenos může proběhnout během několika milisekund.

komunikace esp32-esp32

ESP-NOW se hodí všude tam, kde potřebujete rychle a spolehlivě přenést menší množství dat:

  • bezdrátová čidla teploty, vlhkosti nebo pohybu,
  • ovládání relé a akčních členů,
  • tlačítkové ovladače,
  • meteostanice s bezdrátovými čidly,
  • komunikace mezi více ESP32 v jedné domácnosti,
  • sběr dat do centrálního uzlu (gateway).

Proč používat ESP-NOW?

Mezi hlavní výhody ESP-NOW patří:

  • Nízká latence – odeslání zprávy trvá jen několik milisekund, takže je protokol vhodný pro ovládání, alarmy nebo přenos dat v reálném čase.
  • Nízká spotřeba energie – zařízení nemusí udržovat spojení s přístupovým bodem, což výrazně prodlužuje výdrž bateriových aplikací.
  • Není potřeba Wi-Fi router – komunikace probíhá přímo mezi jednotlivými moduly.
  • Současný provoz s Wi-Fi – ESP-NOW může běžet vedle běžné Wi-Fi komunikace, takže zařízení může například přijímat data od senzorů přes ESP-NOW a současně je odesílat na server.
  • Podpora unicastu i broadcastu – lze komunikovat s konkrétním zařízením nebo jednou zprávou oslovit všechna zařízení v dosahu.

Limity ESP-NOW, které je dobré znát

ESP-NOW není náhradou za plnohodnotnou počítačovou síť. Je navržen především pro rychlý přenos menších objemů dat mezi zařízeními ESP. Při návrhu projektu je dobré počítat zejména s těmito limity:

  • jedno zařízení může mít registrováno maximálně 20 partnerů (peerů),
  • při použití šifrování se počet šifrovaných peerů výrazně snižuje,
  • velikost jedné zprávy je omezená (u základní verze V1 to je 250 bajtů),
  • všechna komunikující zařízení musí používat stejný Wi-Fi kanál.

Ve většině hobby i profesionálních IoT projektů však tato omezení nepředstavují problém. Naopak vedou k jednoduchému a velmi efektivnímu řešení.

Reálný dosah závisí na použitém modulu, anténě, překážkách i rušení okolního prostředí. V otevřeném prostoru lze bez problémů dosáhnout desítek až stovek metrů. Praktické testy často ukazují vzdálenosti přes 200 metrů bez použití speciálních antén. V interiéru bývá dosah menší kvůli stěnám, kovovým konstrukcím a dalším zdrojům útlumu, přesto obvykle bez potíží pokryje rodinný dům, dílnu nebo zahradu. To je pro běžného bastlíře doslova splněný sen! 😊

MAC adresa

Pokud chceme pomocí ESP-NOW komunikovat s konkrétním zařízením, musíme znát jeho MAC adresu, která nahrazuje klasické IP adresování známé z běžných počítačových sítí. V prostředí ESP-NOW totiž nepracujeme s IP adresami jako u běžné Wi-Fi komunikace. Každé zařízení je identifikováno přímo svou MAC adresou, která slouží jako jeho jedinečný identifikátor v síti.

MAC (Media Access Control) adresa je 6bajtové číslo uložené přímo v čipu. Výrobce ji přiřazuje každému modulu již během výroby, takže by měla být unikátní. Obvykle se zapisuje jako šest dvojic šestnáctkových číslic oddělených dvojtečkami – např. AC:67:B2:AF:B8:FC. Tuto adresu budete používat při registraci partnerů (peerů) pro adresnou komunikaci typu unicast. Bez znalosti MAC adresy cílového zařízení mu nelze zprávu odeslat.

Poznámka:
Každý modul ESP32 má ve skutečnosti několik MAC adres odvozených od jedné základní tovární adresy. Pro ESP-NOW nás obvykle zajímá MAC adresa Wi-Fi rozhraní STA (Station).

Jak zjistit MAC adresu v MicroPythonu

Nejjednodušší způsob je přímo z prostředí MicroPython pomocí modulu network. Wi-Fi rozhraní musí být nejprve aktivní, jinak nebude možné jeho konfiguraci číst. Není však přitom nutné být připojen k nějaké Wi-Fi síti.

Následující kód nám po spuštění v modulu ESP32 s firmwarem MicroPythonu vypíše jeho MAC adresu:

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

mac = wlan.config("mac")
print(mac)

Výsledkem bude šestibajtová hodnota typu bytes, například: b'\x84\xcc\xa8\x00\x00\x00'

Pro zápis do dokumentace nebo k poznamenání na kovové pouzdro daného modulu ESP32 je ale praktičtější převést adresu do běžného textového formátu:

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

mac = wlan.config("mac")
mac_text = ":".join("{:02X}".format(b) for b in mac)
print("MAC:", mac_text)

Výstupem pak bude obvyklý tvar s dvojtečkami – jako například na obrázku č. 1

get MAC
Obrázek č. 1 – Získaná MAC adresa vypsaná jako výsledek micropythonovského programu.

Takto zjištěnou adresu je dobré si někam zapsat. Pokud pracujeme s větším počtem modulů, velmi se osvědčilo nalepit na každý modul malý štítek s MAC adresou nebo ji zapsat permanentním fixem přímo na pouzdro modulu ESP32. Po několika týdnech vývoje, věřte, že to ušetří spoustu času. 😒

Zjištění MAC adresy pomocí esptool

MAC adresu lze ale také zjistit přímo v počítači s Pythonem (prostředím Thonny) bez jediného řádku MicroPython kódu. K tomu nám poslouží nástroj esptool, který je součástí plnohodnotného Pythonu, který jsme získali současně s prostředím Thonny IDE. Je však nutné skutečně použít Python, který je nainstalovaný v počítači, nikoliv firmware MicroPythonu, který je v modulu ESP32. Následující postup tedy nemůžeme aplikovat přímo v okně REPLu, kde bychom se od MicroPythonu nástroje esptool nedočkali.

Pro přímé získaní MAC adresy modulu ESP32 můžeme použít následující postup:

  1. Otevřeme systémový terminál prostředí Thonny IDE:
    • V horním menu Thonny klikneme na Nástroje.
    • Vybereme možnost Otevřít systémový shell.
    • Otevře se černé okno příkazové řádky.

V několika návodech je uvedeno, že okno je přednastavené tak, aby v tu chvíli znalo cestu k Pythonu a jeho nástrojům, které Thonny používá. To asi platí při plnohodnotné instalaci prostředí Thonny, pokud však používáme přenosnou instalaci (tzv. portablovou – pouze rozbalený ZIP soubor), otevře se okno nejspíše v pracovní složce našich pythonovských projektů. Jelikož se zde jedná o běžný příkazový řádek, je možné se pomocí MS-DOSovského příkazu cd dostat až do složky s Thonny a Pythonem (do složky, ve které je nainstalované prostředí Thonny) – například: d:\portable\Thonny\.

Jakmile jsme ve složce s Pythonem, můžeme pokračovat.

  1. Připojíme modul ESP32 k počítači:
    • Chvíli počkáme, než se stabilizuje připojení modulu k USB portu.
  2. Spustíme příkaz pro zjištění pythonu k modulu ESP32 a vypsání jeho údajů – včetně MAC adresy:
    • Pokud máme prostředí Thonny plnohodnotně nainstalované (včetně nastavených cest a registrů) patrně stačí do okna terminálu vepsat příkaz: esptool read-mac
    • Pokud by tento příkaz nefungoval, tedy patrně máme prostředí Thonny nainstalované portablově, musíme použít variantu: python -m esptool read-mac

V některých návodech se uvádí, že příkaz esptool se má spustit s parametrem read_mac. Tato „podtržítková“ verze je starší dosud platná verze, ale raději si již zvykejme na novější „pomlčkovou“ verzi read-mac.

  1. Připojení esptool k modulu ESP32:
    • Nástroj esptool se pokusí detekovat váš připojený čip (např. ESP32 nebo ESP32-S3). Pokud by se připojení nezdařilo, nepanikařte! Zkuste to znova jen se stisknutým tlačítkem BOOT modulu ESP32.
  2. Získání MAC
    • Jakmile se připojení k modulu zdaří, vypíší se do okna informace o připojeném modulu. Je mezi nimi i řádek začínající slovem MAC: a obsahující šestici dvojic znaků (např. MAC: 84:cc:a8:00:00:00). To je naše hledaná MAC adresa právě připojeného modulu ESP32 – situaci ilustruje obrázek č. 2.
esptool - zjisteni MAC
Obrázek č. 2 – Získaná MAC adresa vypsaná v systémovém shellu.

Přestože tato metoda asi zprvu vypadá složitě, je vcelku výhodná, neboť ji lze aplikovat na jakýkoliv modul a to bez ohledu na to, zda je či není v něm nahraný firmware MicroPythonu. Tuto metodu jistě oceníme i při přípravě většího množství modulů.

Lze MAC adresu změnit?

Odpověď zní: Ano, ale ne v současném MicroPythonu – zkrátka záleží na použitém prostředí.

Samotný čip ESP32 umožňuje MAC adresu změnit softwarově za běhu programu. Tato změna však není trvalá. Po restartu zařízení nebo po probuzení z hlubokého spánku (deep sleep) se znovu použije původní tovární (hardwarová) MAC adresa uložená v čipu.

Je důležité rozlišovat mezi možnostmi hardwaru ESP32 a možnostmi firmwaru MicroPythonu.

MicroPython
V oficiálním MicroPythonu pro ESP32 nelze MAC adresu Wi-Fi rozhraní změnit. Parametr mac v metodě WLAN.config() je pouze pro čtení. Pokus o nastavení nové adresy skončí chybou.
Toto omezení není způsobeno modulem ESP32, ale tím, že oficiální firmware MicroPythonu neobsahuje podporu pro volání funkce esp_wifi_set_mac() z ESP-IDF.
Arduino IDE
V prostředí Arduino IDE lze MAC adresu změnit bez problémů, protože Arduino používá přímo knihovny ESP-IDF, které tuto funkci podporují. Ukázku jak lze v prostředí Arduino IDE změnit MAC adresu najdete v článku: Základy komunikace ESP-NOW.

Pro většinu projektů je nejlepší používat původní tovární MAC adresy a pouze si je dobře evidovat. Ušetříte si tím řadu obtížně dohledatelných problémů při ladění komunikace.

Praktická příprava: Jak testovat na jednom počítači

Než se pustíme do následujících experimentů, je dobré si ještě trochu upravit prostředí Thonny IDE. Pro náš vývoj, a především ladění projektů s ESP-NOW, budeme typicky potřebovat alespoň dva mikrokontroléry – jeden jako vysílač a druhý jako přijímač.

Aby bylo testování co nejpohodlnější, můžeme obě desky buď připojit k jednomu počítači, nebo každou k jinému stroji (např. notebook a desktop). Pokud budeme používat jen jeden počítač, bylo by fajn si na pracovní ploše otevřít dvě okna prostředí Thonny IDE a v každém řešit připojení vysílače a vedle přijímače. Standardní nastavení prostředí Thonny IDE nám nabízí ale výchozí nastavení takové, že nám umožní mít otevřené pouze jedno okno. Takže je třeba tuto volbu změnit, abychom nadále mohli vést efektivní práci, při které budeme sledovat výstupy z obou desek současně v oddělených konzolích (Shell).

Nastavení Thonny pro více instancí:

  1. V horním menu přejděte na NástrojeMožnosti.
  2. Na kartě Obecné zrušte zaškrtnutí volby: Povolit pouze jednu instanci Thonny – viz obrázek č. 3
  3. Po potvrzení (OK) můžete prostředí Thonny spustit znovu poklikáním na jeho ikonu.
  4. Nyní se vám otevře druhé nezávislé okno.
esptool - zjisteni MAC
Obrázek č. 3 – Vypnutí spuštění jen jedné instance prostředí Thonny IDE.

Díky tomuto nastavení můžete v každém okně vybrat jiný COM port (vpravo dole v Thonny IDE), a tím pádem ovládat vysílač i přijímač najednou. To nám umožní okamžitě vidět, zda zpráva odeslaná z jednoho okna v pořádku dorazila do druhého.

První ESP-NOW program: příjem zpráv

A ještě jedno: „Než začneme…

Než začneme odesílat data konkrétním zařízením, vyzkoušíme si nejjednodušší možný scénář: ESP32 bude nejprve pouze naslouchat a vypisovat všechny přijaté zprávy. Takový program je ideální pro první testy, protože není potřeba znát MAC adresu vysílače ani registrovat žádné peery.

Z pohledu přijímače je ESP-NOW překvapivě jednoduchý. Stačí:

  1. aktivovat Wi-Fi rozhraní,
  2. inicializovat ESP-NOW,
  3. čekat na příchozí zprávy.

Každá přijatá zpráva protokolem ESP-NOW obsahuje dvě části:

  • MAC adresu odesílatele,
  • vlastní přenášená data (payload).

Tyto části načteme do dvojice proměnných a můžeme s nimi dále nakládat, jak uvidíme v programu jednoduchého přijímače.

Jednoduchý přijímač

Následující program vypisuje všechny přijaté zprávy do konzole.

import network
import espnow

# Aktivace Wi-Fi rozhraní
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1)

# Pro jistotu odpojíme případné Wi-Fi připojení
sta.disconnect()

# Inicializace ESP-NOW
e = espnow.ESPNow()
e.active(True)

print("Čekám na ESP-NOW zprávy...")

while True:
   host, msg = e.recv()

   if host and msg:
      print(host, msg)

Na začátku programu vidíme použití modulů network, který slouží pro obecnou obsluhu vysílání, a modulu espnow, který má na starosti komunikaci protokolem ESP-NOW.

První část je dokonce velmi podobná komunikaci přes Wi-Fi. Dochází k nastavení režimu Stanice (STA). U běžného Wi-Fi by asi následovalo připojení pomocí ssid a hesla sítě. To zde není třeba.

Důležitá poznámka k Wi-Fi kanálu

Přestože ESP-NOW nepotřebuje Wi-Fi router ani připojení k síti, stále využívá Wi-Fi hardware uvnitř čipu. Všechna komunikující zařízení proto musí pracovat na stejném Wi-Fi kanálu. Pokud bude vysílač vysílat například na kanálu 1 a přijímač poslouchat na kanálu 6, žádná komunikace neproběhne.

Pro naše první pokusy je proto rozumné nastavit všude pevný kanál:

sta.config(channel=1)

Stejnou hodnotu pak použijeme i na vysílači.

Sotva jsme zapnuli vysílací část modulu ESP32, nastavili režim a komunikační kanál, je zde trochu překvapivý příkaz:

sta.disconnect()

Toto vypnutí případného Wi-Fi připojení je zde tak trochu pro jistotu, abychom měli jistotu, že modul ESP32 nyní bude komunikovat jen v režimu vysílání ESP-NOW.

Konečně se dostáváme ke komunikaci ESP-NOW, pro jejíž obsluhu si vytvoříme objekt modulu espnow:

e = espnow.ESPNow()
e.active(True)

Od tohoto okamžiku program čeká na první příchozí paket. Jakmile nějaký dorazí, uloží se dvě její části do dvojice proměnných host, msg. První obsahuje MAC adresu odesílatele, druhá obsah zaslané zprávy.

host, msg = e.recv()

Pokud jsou obě zprávy neprázdné, vypíše MAC adresu odesílatele a obsah zprávy:

if host and msg:
    print(host, msg)

Typický výstup může vypadat například takto: b'\xac\x67\xb2\x00\x00\x00' b'AHOJ'

Takový výpis sice obsahuje všechny potřebné informace, ale ve formátu bytes není příliš přehledný. Asi by bylo vhodnější MAC adresu převést do běžného textového tvaru a pokusit se přijatá data dekódovat jako text.

Podmínku tedy upravíme následujícím způsobem:

if host and msg:
    # Převedení na formát AA:BB:CC:DD:EE:FF a velká písmena
    mac_hex = ":".join(f"{b:02x}" for b in host)

    try:
        message = msg.decode('utf-8')
    except UnicodeDecodeError:
        message = msg

    print(f"Přijato od [{mac_hex}]: {message}")

Podobně jako v kódu programu pro zobrazení MAC adresy i zde je proměnná host „proběhnuta“ přes svých 6 bytů určující MAC adresu a hodnoty budou (pomocí formátovacích znaků) převedeny do hexadecimální podoby oddělené dvojtečkami.

V případě proměnné msg se pokusíme o převod jejího obsahu na text formátu utf-8. V případě neúspěchu (vyvolání výjimky) necháme obsah proměnné takový, jaký je.

Získaný výstup nyní bude výrazně přehlednější – viz obrázek č. 4:

ESP-NOW - prijem
Obrázek č. 4 – Přijetí textové zprávy prostřednictvím ESP-NOW.

Takový výpis je při ladění velmi užitečný, protože okamžitě vidíte, kdo zprávu odeslal a jaký obsah dorazil.

Funkce metody recv()

Trochu se vrátíme ke klíčové funkci celé předchozí ukázky:

host, msg = e.recv()

Jak bylo zmíněno, metoda recv() vrací dvojici hodnot:

  • host – MAC adresa odesílatele
  • msg –Přijatá data (bytes)

Pokud zavoláte recv() bez parametrů, program bude čekat na zprávu neomezeně dlouho. To je vhodné pro jednoduché ukázky, ale v reálných projektech bývá praktičtější nastavit nějaký timeout.

Příklad:

host, msg = e.recv(5000)

V tomto případě bude program čekat maximálně 5 sekund.

Pokud během této doby žádná zpráva nedorazí, vrátí metoda prázdný výsledek a program může pokračovat dál. To je užitečné například tehdy, když současně měříme senzory, obsluhujeme displej nebo jen blikáte stavovou LED.

Všesměrové vysílání (Broadcast)

Je sice hezké, že umíme zachytit zprávu, ale zatím nemáme co přijímat. Je tedy třeba také začít něco vysílat. K tomu použijeme druhý modul ESP32 a nastavíme ho jako jednoduchý vysílač zpráv ve formátu ESP-NOW.

Nejjednodušším způsobem odesílání dat v ESP-NOW je tzv. broadcast, tedy všesměrové vysílání. Zařízení v tomto režimu neposílá zprávu konkrétnímu příjemci, ale všem ESP modulům, které se nacházejí v dosahu a poslouchají na stejném Wi-Fi kanálu.

ESP-NOW broadcast
Zdroj obrázku: https://zaitronics.com.au/blogs/esp32-iot-systems-pathway/esp32-esp-now-tutorial-arduino-ide

Broadcast je ideální pro naše první pokusy s ESP-NOW, protože není nutné zjišťovat MAC adresu cílového zařízení. Stačí spustit přijímač z předchozí kapitoly a můžeme začít vysílat.

Typické použití:

  • pravidelné vysílání hodnot senzorům,
  • automatické vyhledávání zařízení v síti,
  • rozesílání stavových informací více přijímačům současně,
  • jednoduchá komunikace typu „jsem tady“.

Pro všesměrové vysílání se používá speciální MAC adresa: FF:FF:FF:FF:FF:FF

V MicroPythonu se MAC adresa zapisuje jako pole bajtů, takže by to mohlo vypadat takto: b'\xff\xff\xff\xff\xff\xff'. (Pozor na ta malá písmena!) Tato adresa představuje všechna zařízení v dosahu. Každý paket odeslaný na tuto adresu zachytí všechna aktivní ESP32 poslouchající na stejném kanálu.

Při použití broadcastu v MicroPythonu se v některých kódech můžeme setkat s tím, že se žádná speciální MAC adresa pro toto všesměrové vysílání nepoužívá – dokonce se v kódu neobjevuje vůbec žádná MAC adresa. Současné vysílání ESP-NOW je v MicroPythonu pojato tak, že pokud není žádná adresa uvedena, automaticky se použije výchozí MAC pro broadcast. My zde tento trik používat nebudeme a adresu pěkně zaregistrujme. Má to tu výhodu, že pouhou změnou MAC adresy za konkrétní čip ESP32 dokážeme přeměnit všesměrové vysílání (broadcast) na vysílání jeden na jednoho (unicast).

Broadcast vysílač

Následující příklad každých pět sekund odešle textovou zprávu všem zařízením v dosahu.

import network
import espnow
import time

sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1) 
sta.disconnect()

# Inicializace ESP-NOW
e = espnow.ESPNow()
e.active(True)

# Cílová MAC adresa (broadcast)
receiver_mac = b'\xff\xff\xff\xff\xff\xff'
# receiver_mac = b'\x24\x62\xa8\x00\x00\x00'

# Bezpečné přidání peeru (pokud už existuje, vyvolá to výjimku, kterou ošetříme)
try:
    e.add_peer(receiver_mac)
except OSError as err:
    print("Peer již možná existuje nebo nelze přidat:", err)

message_count = 1

# Hlavní smyčka pro odesílání
while True:
    message = f"AHOJ, Jsem tady - zprava c. {message_count}"
    try:
        # Odeslání zprávy (pouze dva argumenty)
        e.send(receiver_mac, message)
        print(f"Odeslana zprava: {message}")
    except OSError:
        print("Failed to send message")

    message_count += 1
    time.sleep(5)

Po spuštění by se v konzoli vysílače měl objevit například výstup jako na obrázku č. 5.

ESP-NOW - broadcast
Obrázek č. 5 – Odeslání zprávy přes všesměrové vysílání (Broadcast)

Současně by měl přijímač z předchozí kapitoly zobrazovat příchozí zprávy – což jsme již viděli na obrázku č. 4.

Registrace příjemce

Adresu příjemce máme uloženou v proměnné receiver_mac.

Pro všesměrový broadcast to je:

receiver_mac = b'\xff\xff\xff\xff\xff\xff'

Zároveň v zakomentované podobě vidíme MAC konkrétního příjemce.

Registraci příjemce (peera) provedeme pomocí metody .add_peer():

e.add_peer(receiver_mac)

Jelikož se (obecně) může stát, že by již tento příjemce mohl být již zaregistrovaný – například v případech automatické odpovědi (viz dále), je tento kód vložen do struktury výjimky (try – except)

Samotné odeslání zprávy zajišťuje jediný řádek:

e.send(receiver_mac, zprava)

První parametr určuje cílovou MAC adresu, druhý obsah zprávy.

Metodou lze obecně přenášet:

  • textové řetězce,
  • objekty typu bytes,
  • binární struktury vytvořené například pomocí modulu struct.

Objekt struct

V předchozí části jsme si ukázali odesílání textových zpráv. To je skvělé pro testování, ale v reálných projektech většinou neposíláme věty nebo řetězce znaků. Mnohem častěji potřebujeme přenést konkrétní data, například: ID senzoru, teplotu, vlhkost, stav relé nebo třeba napětí baterie.

Typickou chybou začátečníků je převádět všechna data na text:

"SENZOR=1;TEPLOTA=23.5"

Takové řešení funguje, ale není příliš efektivní. Zabírá více místa, vytváří zbytečné řetězce a na přijímači je potřeba text znovu rozebírat. U ESP-NOW to sice nemusí být problém u jedné zprávy, ale při častém odesílání nebo větším počtu zařízení je lepší používat binární data.

Binární přenos je rychlejší, úspornější a lépe škáluje při větším množství zařízení. Je tedy dobré potřebná data do tohoto formátu nějak převést. K tomu nám dobře poslouží použití:

struct.pack(...)

Modul struct umožňuje převádět proměnné do kompaktní binární podoby, která je pro ESP-NOW přirozenější než textové řetězce. Vysílač data pomocí .pack() zabalí, příjemce je pomocí metody .unpack() rozbalí a získá původní hodnoty bez nutnosti parsování textu. Díky tomu lze efektivně přenášet naměřené hodnoty, stavové informace i složitější datové struktury.

Představme si, že chceme odeslat:

  • ID senzoru = 1
  • teplota = 23.5

Textová varianta by vypadala:

"1,23.5"

Nyní zkusíme vytvořit binární variantu:

struct.pack("if", 1, 23.5)

V obou případech přenášíme stejnou informaci, ale binární forma zabírá méně místa a příjemce ji nemusí složitě parsovat. Další výhodou je jednoznačnost. Přijímač přesně ví, že první čtyři bajty představují celé číslo a další čtyři bajty desetinné číslo.

Jak vidíme na ukázce použití strukturovaných dat, obsahuje metoda pack kromě odesílaných dat i určitý „formátovací řetězec“. Právě použití těchto krátkých značek nám umožňuje metoda podat informaci o formátu přenášených dat.

Nejběžnější jsou:
iinteger (4 bajty)
ffloat (4 bajty)
hshort integer (2 bajty)
Bunsigned byte (1 bajt)

Námi zvolený formát: "if" tedy znamená: integer + float a výsledná zpráva tedy bude mít: 4 + 4 = 8 bajtů.

A to vždy – bez ohledu na konkrétní hodnoty. Lze to říci i o přenosu dat v textové podobě? Ne!

Vysílání adresné komunikace (unicast)

Broadcast je výborný pro první testy a jednoduché senzory, ale má jednu zásadní nevýhodu: Nevíte, kdo zprávu přijal. Metoda .send() sice vrátí úspěch, ale pouze potvrzuje, že paket byl předán Wi-Fi hardwaru. Neznamená to, že jej skutečně zachytil nějaký příjemce.

Pokud potřebujeme:

  • komunikovat s konkrétním zařízením,
  • získat potvrzení o doručení,
  • používat šifrování,
  • vytvářet obousměrnou komunikaci,

je vhodnější použít unicast, tedy komunikaci pomocí konkrétní MAC adresy.

ESP-NOW one-to-one
Zdroj obrázku: https://zaitronics.com.au/blogs/esp32-iot-systems-pathway/esp32-esp-now-tutorial-arduino-ide

Ukážeme si tedy, jak poslat zprávu konkrétnímu ESP32 pomocí jeho MAC adresy. A protože jsme si právě ukázali, jak odesílat data zabalené modulem struct, ukážeme si adresné odeslání (a příjem) dat v této podobě, což je nejčastěji používaný v reálných projektech.

Vysílač

Následující program pravidelně odesílá ID senzoru a teplotu.

import network
import espnow
import struct
import time

# Inicializace Wi-Fi
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1)
sta.disconnect()

# Inicializace ESP-NOW
vysilac = espnow.ESPNow()
vysilac.active(True)

# Cílová MAC adresa (broadcast, nebo specifická adresa přijímače)
receiver_mac = b'\xac\x67\xb2\x00\x00\x00'

try:
    vysilac.add_peer(receiver_mac)
except OSError:
    pass

sensor_id = int(1)          # Explicitně jako celé číslo
temperature = float(23.5)   # Explicitně jako reálné číslo

print("Odesílám strukturovaná data...")

while True:
    # Zabalení dat do binární podoby:
    # 'i' = integer (4 bajty), 'f' = float (4 bajty)
    # Celá zpráva bude dlouhá 8 bajtů
    zprava = struct.pack('if', sensor_id, temperature)

    try:
        vysilac.send(receiver_mac, zprava)
        print(f"Odesláno -> ID senzoru: {sensor_id}, Teplota: {temperature} °C")
    except OSError:
        print("Chyba při odesílání")

    temperature += 0.1  # Simulace změny teploty

    time.sleep(5)

V kódu vidíme, že nyní byla před odesláním zaregistrována konkrétní MAC adresa příjemce. A tím máme vyřešen celý unicast. (jak prosté! 😉).

Zároveň v kódu vidíme, že objekt pro manipulaci s komunikací ESP-NOW již neoznačuje klasickým „tutoriálským“ označením e, ale pojmenovali jsme ho trochu více výstižněji: vysilac.

V neposlední řadě vidíme v kódu, jak se před odesláním obě proměnné zabalí do osmibajtové binární zprávy, která putuje přes ESP-NOW k příjemci.

zprava = struct.pack('if', sensor_id, temperature)

To jsme si ale již ukazovali ve zmínce o strukturovaných datech.

Obrázek č. 6 ukazuje výstup v Shellu při úspěšném odesílání.

ESP-NOW - odesilani structurovanych dat
Obrázek č. 6 – Odeslání strukturovaných dat prostřednictvím ESP-NOW

Data jsou odeslána a nyní se musíme podívat na stranu příjemce a pokusit se odeslaná data nejen zachytit, ale opět z binární struktury „rozbalit“ na původně odesílané data.

Příjemce

Na straně příjemce musíme použít stejný formát dat, jaký byl použit na straně vysílače. V našem případě jde o dvojici celého a reálného čísla. Je třeba i dodržet pořadí!

Jestliže jsme na straně vysílače použili:

struct.pack("if", ...)

Musí na straně příjemce použít:

struct.unpack("if", ...)

A to je celá věda! Následující kód programu zachycuje příjemce strukturovaných dat odeslaných v předešlé ukázce:

import network
import espnow
import struct

# Inicializace Wi-Fi
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1)
sta.disconnect()
 
# Inicializace ESP-NOW
prijimac = espnow.ESPNow()
prijimac.active(True)
 
print("Poslouchám strukturovaná data...")
 
while True:
    host, msg = prijimac.recv()
   
    if host and msg:
        mac_hex = ":".join(f"{b:02x}" for b in host)
       
        # Ověříme, že zpráva má správnou délku (4 bajty pro 'i' + 4 bajty pro 'f' = 8 bajtů)
        if len(msg) == 8:
            # Rozbalení binárních dat zpět na proměnné
            sensor_id, temperature = struct.unpack('if', msg)
            print(f"Přijato od [{mac_hex}] -> ID senzoru: {sensor_id}, Teplota: {temperature:.2f} °C")
        else:
            print(f"Přijata data neznámého formátu (délka {len(msg)} bajtů)")

Obrázek č. 7 ukazuje výstup tohoto programu:

ESP-NOW - prijeti structurovanych dat
Obrázek č. 7 – Přijetí strukturovaných dat prostřednictvím ESP-NOW

Kontrola délky zprávy

Ve výše uvedené ukázce si můžete všimnout podmínky, která zatím v kódu obecného příjemce nebyla:

if len(msg) == 8:

Tato podmínka není povinná, ale je v našem případě velmi užitečná. Přijímač tím nejprve ověří, že skutečně dorazila zpráva odpovídající formátu "if". Jak jsme říkali, příchozí zpráva by měla mít 8 bajtů.

Pokud dorazí jiný typ zprávy, program se nepokusí data rozbalit a tím nedojde k případné chybě.

Zabezpečení a šifrování

Ve všech předchozích příkladech jsme odesílali data bez jakéhokoliv zabezpečení. To je pro testování nebo domácí projekty obvykle naprosto dostačující. Pokud však přenášíte citlivější informace nebo chcete zabránit odposlechu komunikace, nabízí ESP-NOW vestavěné hardwarové šifrování pomocí algoritmu AES.

ESP-NOW obsahuje hardwarově podporované AES šifrování, které lze aktivovat pro komunikaci mezi konkrétními zařízeními. Stačí nastavit PMK, při registraci peeru (v metodě add_peer()) přidat LMK a zapnout parametr encrypt=True. Samotný způsob odesílání a příjmu dat se tím prakticky nemění, protože šifrování i dešifrování probíhá transparentně na úrovni Wi-Fi hardwaru.

Velkou výhodou je, že šifrování probíhá přímo ve Wi-Fi hardwaru ESP32. Programátor tedy nemusí data před odesláním šifrovat ručně ani je po přijetí dešifrovat. Pokud jsou klíče na obou stranách nastaveny správně, aplikace pracuje se zprávami úplně stejně jako v případě nešifrované komunikace.

Než začneme s konfigurací, je dobré znát několik důležitých omezení:

  • šifrování funguje pouze pro komunikaci unicast,
  • broadcast zprávy šifrovat nelze,
  • obě zařízení musí používat stejné šifrovací klíče,
  • používat šifrovanou komunikaci může současně maximálně 6 registrovaných peerů.
Budeme si tedy pamatovat:
Pokud používáte broadcast, šifrování neřešíte.
Pokud komunikujete mezi konkrétními zařízeními pomocí jejich MAC adres, můžete komunikaci zabezpečit.

Šifrovací klíče

ESP-NOW pro funkci šifrování zpráv používá dva typy klíčů – PMK a LMK.

  • PMK (Primary Master Key) – Klíč PMK lze chápat jako hlavní šifrovací klíč systému. Slouží k ochraně ostatních klíčů používaných uvnitř ESP-NOW. Má délku 16 bajtů.
  • LMK (Local Master Key) – LMK je klíč použitý pro samotnou komunikaci mezi dvěma konkrétními zařízeními. Také musí mít délku přesně 16 bajtů.

Ve většině projektů nastavíte jeden PMK a pro komunikující dvojici zařízení jeden LMK.

Klíče jsou zadávány jako objekt typu bytes.

Například:

PMK_KEY = b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x00'

LMK_KEY = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10'

Jak bylo řečeno, oba musí mít délku přesně: 16 bajtů = 128 bitů

Šifrovaný vysílač

Následující příklad vychází z předchozí kapitoly o přenosu strukturovaných dat. Do komunikace pouze přidáme nastavení klíčů a zapnutí šifrování.

import network
import espnow
import struct
import time

# 1. Inicializace Wi-Fi
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1)
sta.disconnect()

# 2. Inicializace ESP-NOW
vysilac = espnow.ESPNow()
vysilac.active(True)

# 3. Nastavení šifrovacích klíčů (musí mít přesně 16 znaků/bajtů!)
PMK_KEY = b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x00'
LMK_KEY = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10'

# Nastavení hlavního PMK klíče
vysilac.set_pmk(PMK_KEY)

# Cílová MAC adresa přijímače (šifrovat lze pouze unicast, ne broadcast!)
receiver_mac = b'\xac\x67\xb2\x00\x00\x00'

# Přidání peera se zapnutým šifrováním (encrypt=True) a LMK klíčem
try:
    vysilac.add_peer(receiver_mac, lmk=LMK_KEY, encrypt=True)
    print("Peer úspěšně přidán se šifrováním.")
except OSError as e:
    print("Chyba při přidávání peera:", e)

sensor_id = int(1)          # Celé číslo
temperature = float(23.5)   # Reálné číslo

print("Odesílám šifrovaná strukturovaná data...")

while True:
    zprava = struct.pack('if', sensor_id, temperature)

    try:
        vysilac.send(receiver_mac, zprava)
        print(f"Odesláno (šifrovaně) -> ID senzoru: {sensor_id}, Teplota: {temperature} °C")
    except OSError:
        print("Chyba při odesílání")

    temperature += 0.1  # Simulace změny teploty

    time.sleep(5)

V předchozím kódu vidíme, že oproti nešifrované verzi přibyla definice klíčů:

PMK_KEY = b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x00'

LMK_KEY = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10'

Následovalo nastavení hlavního PMK klíče:

vysilac.set_pmk(PMK_KEY)

A pochopitelně lokálního klíče (LMK) odpovídajícího dvojici vysílač-příjemce:

vysilac.add_peer(receiver_mac, lmk=LMK_KEY, encrypt=True)

A to je vše! Jak vidíme na obrázku č. 8, v chování programu vlastně není poznat, že jde o šifrovanou komunikaci.

ESP-NOW - odeslani sifrovanych structurovanych dat
Obrázek č. 8 – Odeslání strukturovaných dat prostřednictvím šifrovaného ESP-NOW

Šifrovaný příjemce

Příjemce musí používat stejné klíče a musí mít vysílač zaregistrovaný jako peer. To je zásadní rozdíl oproti nešifrovanému provozu, kde jsme mohli přijímat zprávy od libovolného zařízení v dosahu.

import network
import espnow
import struct

# 1. Inicializace Wi-Fi
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1)
sta.disconnect()

# 2. Inicializace ESP-NOW
prijimac = espnow.ESPNow()
prijimac.active(True)

# 3. Nastavení stejných šifrovacích klíčů jako na vysílači
PMK_KEY = b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x00'
LMK_KEY = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10'

prijimac.set_pmk(PMK_KEY)

# MAC adresa odesílatele, od kterého chceme data přijímat a dešifrovat
vysilac_mac = b'\x24\x62\xa8\x00\x00\x00'

# Registrace vysílače jako peera se šifrováním na straně přijímače
try:
    prijimac.add_peer(vysilac_mac, lmk=LMK_KEY, encrypt=True)
    print("Vysílač registrován pro příjem šifrovaných zpráv.")
except OSError as e:
    print("Chyba při přidávání peera:", e)

print("Poslouchám šifrovaná strukturovaná data...")

while True:
    host, msg = prijimac.recv()

    if host and msg:
        mac_hex = ":".join(f"{b:02x}" for b in host)

        # Kontrola, zda zpráva přišla od našeho povoleného vysílače
        if host == vysilac_mac:
            if len(msg) == 8:
                sensor_id, temperature = struct.unpack('if', msg)
                print(f"Přijato (dešifrováno) od [{mac_hex}] -> ID: {sensor_id}, Teplota: {temperature:.2f} °C")
            else:
                print(f"Přijata data neznámého formátu (délka {len(msg)} bajtů)")

I v případě šifrovaného příjemce se kód příliš nezměnil, pochopitelně muselo dojít k registraci vysílacího peera – to je při šifrování nutnost. Pokud jsou oba klíče správně nakonfigurovány, funguje pak příjem úplně stejně jako v nešifrovaném příkladu – což vidíme i na obrázku č. 9. Hardware data automaticky dešifroval ještě předtím, než se vůbec dostala do programu.

ESP-NOW - prijeti sifrovanych structurovanych dat
Obrázek č. 9 – Příjem strukturovaných dat prostřednictvím šifrovaného ESP-NOW

Obecně lze říci, že šifrování se vyplatí zejména tehdy, pokud přenášíme:

  • přístupové údaje,
  • stav zabezpečovacího systému,
  • řídicí povely pro důležitá zařízení,
  • data z komerčních nebo průmyslových aplikací.

Naopak u jednoduché meteostanice, teploměru nebo přenosu hodnot z pokojového senzoru bývá šifrování spíše otázkou osobních preferencí než skutečné potřeby.

Oboustranná komunikace (Two-way communication)

V dosavadních příkladech jedno zařízení vždy pouze vysílalo a druhé pouze přijímalo. V reálných projektech ale často potřebujeme, aby si zařízení informace vzájemně vyměňovala.

Typické příklady:

  • senzor odešle naměřenou hodnotu a čeká na potvrzení přijetí,
  • ovladač odešle příkaz a očekává odpověď o aktuálním stavu zařízení,
  • centrální jednotka si vyžádá data od vzdáleného uzlu,
  • zařízení si vzájemně synchronizují stav nebo konfiguraci.

ESP-NOW pochopitelně umožňuje, aby každé ESP32 fungovalo současně jako vysílač i přijímač. Zařízení odešle zprávu, přepne se do režimu naslouchání a čeká na odpověď druhé strany.

ESP-NOW both ways
Zdroj obrázku: https://zaitronics.com.au/blogs/esp32-iot-systems-pathway/esp32-esp-now-tutorial-arduino-ide

Princip komunikace „Request-Response“

Náš následující čistě ukázkový příklad používá jednoduchý model vzájemné oboustranné komunikace:

  1. Modul ESP32 #1 pošle teplotu ve °C na modul ESP32 #2
  2. Modul ESP32 #2 data přijme, přepočítá je na °F a vrátí (odešle) výsledek na modul ESP32 #1
  3. Oba moduly zobrazují, co odesílají a co přijaly

ESP32 č. #1 – Master

Master pravidelně odesílá naměřenou teplotu a čeká na odpověď druhého zařízení. Master po každém odeslání čeká maximálně dvě sekundy na odpověď. Pokud nic nepřijde, pokračuje dalším cyklem.

import network
import espnow
import struct
import time

# Wi-Fi
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1)
sta.disconnect()

# ESP-NOW
master_vysilani = espnow.ESPNow()
master_vysilani.active(True)

# MAC adresa druhého zařízení
slave_mac = b'\xac\x67\xb2\x00\x00\x00'

try:
    master_vysilani.add_peer(slave_mac)
except OSError:
    pass

sensor_id = 1
temp_c = 22.0

while True:
    print(
        f"\nOdesilam: "
        f"ID={sensor_id}, "
        f"Teplota={temp_c:.1f} °C"
        )

    # Zabalení dat
    msg_out = struct.pack("if", sensor_id, temp_c)

    # Odeslání
    master_vysilani.send(slave_mac, msg_out)

    # Čekání na odpověď
    host, msg_in = master_vysilani.recv(2000)

    if host and msg_in and len(msg_in) == 4:
        temp_f = struct.unpack("f", msg_in)[0]
        print(
            f"Odpoved: "
            f"{temp_f:.2f} °F"
        )
    else:
        print("Odpoved nedorazila.")

    temp_c += 0.5

    time.sleep(5)

Na obrázku č. 9 vidíme chování modulu ESP32 #1 (master) při odeslání dotazu a přijetí odpovědi.

ESP-NOW - oboustanna komunikace - dotaz
Obrázek č. 9 – Odeslání dotazu (teplota v °C) a přijetí odpovědi (teplota v °F)

ESP32 č. #2 – Slave

Druhé zařízení neustále čeká na příchozí zprávy. Po přijetí zprávy rozbalí data, vypočítá teplotu ve °F a odešle výsledek zpět odesílateli.

import network
import espnow
import struct

# Wi-Fi
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1)
sta.disconnect()

# ESP-NOW
slave_vysilani = espnow.ESPNow()
slave_vysilani.active(True)

print("Cekam na data...")

while True:
    host, msg = slave_vysilani.recv()

    if host and msg and len(msg) == 8:
        sensor_id, temp_c = struct.unpack("if", msg)
        print(
            f"Prijato od {host.hex()}: "
            f"ID={sensor_id}, "
            f"{temp_c:.1f} °C"
        )

        # Registrace odesílatele pro odpověď
        try:
            slave_vysilani.add_peer(host)
        except OSError:
            pass

        temp_f = (temp_c * 1.8) + 32

        msg_back = struct.pack("f", temp_f)
        slave_vysilani.send(host, msg_back)

        print(
            f"Odesilam: "
            f"{temp_f:.2f} °F"
        )

Na obrázku č. 10 vidíme chování modulu ESP32 #2 (slave) při přijetí dotazu a odeslání odpovědi.

ESP-NOW - oboustanna komunikace - odpoved
Obrázek č. 10 – Přijetí dotazu (teplota v °C) a odeslání odpovědi (teplota v °F)

Možná si pozorný čtenář všiml, že v celém kódu nikde není zadána MAC adresa modulu ESP32 #1 (master), na který má modul ESP32#2 (slave) odpovídat. Jak tedy tento modul může vrátit teplotu v °F?

Zajímavostí tohoto příkladu je následující část, která peera pro odpověď přidává dynamicky. Každý, kdo zašle dotaz (nezávisle na MAC) tak dostane svou odpověď – díky tomu je vždy odpovězeno na příchozí dotaz.

Tím, že kód:

try:
    slave_vysilani.add_peer(host)
except OSError:
    pass

je zde vložen až do větve po přijetí zprávy, může být jako peer zaregistrována MAC až z příchozí komunikace.

Takže tento přijímač dokud nedostane zprávu, předem neví, kdo mu zprávu pošle. Jakmile ale přijme první paket, získá MAC adresu odesílatele v proměnné host. Tu následně zaregistruje jako peer, aby mohl odpovědět.

Pochopitelně, kdo chce, aby přijímač odpovídal na jednu konkrétní MAC, může ji do programu zadat a registrovat ještě před začátkem komunikace – obdobně předchozímu kódu vysílače.

Oboustranná komunikace ukazuje skutečný potenciál ESP-NOW. Každé zařízení může současně vysílat i přijímat data, reagovat na požadavky ostatních uzlů a vytvářet jednoduché protokoly typu dotaz–odpověď. Díky nízké latenci a minimálním režijním nákladům je tento přístup vhodný pro senzory, ovládací prvky i rozsáhlejší distribuované systémy založené na ESP32.

Závěr:

ESP-NOW představuje jeden z nejzajímavějších komunikačních nástrojů, který mají vývojáři na platformách ESP32 a ESP8266 k dispozici. Přestože bývá často ve stínu klasické Wi-Fi komunikace, v mnoha projektech nabízí jednodušší, rychlejší a energeticky úspornější řešení.

V průběhu článku jsme si ukázali, že komunikace mezi zařízeními nemusí vyžadovat Wi-Fi router, IP adresy ani složitou síťovou infrastrukturu. Stačí aktivní Wi-Fi rozhraní, znalost MAC adresy cílového zařízení a několik řádků kódu.

Postupně jsme prošli:

  • Jak zjistit a pracovat s MAC adresami.
  • Jak vytvořit jednoduchý textový přijímač.
  • Ukázali jsme si jak všesměrového vysílání (broadcast), tak i adresnou komunikaci mezi zařízeními (unicast).
  • K reálnému použití ESP-NOW jsme se přiblížili přenosem strukturovaných dat pomocí modulu struct.
  • Seznámili jsme se s hardwarově podporovaným AES šifrováním komunikace ESP-NOW.
  • Na samotný závěr jsme si ukázali i oboustrannou komunikaci typu dotaz–odpověď.

Právě kombinace nízké latence, malá spotřeba a především jednoduchá implementace dělá z ESP-NOW skvělou volbu pro různé bezdrátové senzory, dálková ovládání, meteostanice, domácí automatizaci ale i průmyslové aplikace. Místo budování celé síťové infrastruktury se tak můžete soustředit na samotnou funkčnost projektu.

Za zmínku stojí také fakt, že ESP-NOW a klasickou Wi-Fi komunikaci lze kombinovat. Jedno ESP32 tak může například sbírat data z okolních senzorů přes ESP‑NOW a současně je odesílat do databáze nebo cloudové služby prostřednictvím běžného Wi-Fi připojení.

Pokud jste dočetli až sem, máte k dispozici všechny základní stavební kameny potřebné k vytvoření vlastních ESP-NOW projektů v MicroPythonu. Ať už půjde o jednoduchý bezdrátový teploměr se dvěma moduly, nebo o rozsáhlejší síť senzorů, principy budou stále stejné – správně nastavený kanál, známé MAC adresy a dobře navržený formát přenášených dat.

Přejeme hodně zábavy při Vašem samostatném experimentování s komunikací ESP-NOW v MicroPythonu, protože BASTLENÍ MÁ především BAVIT!

Pošli signál autorovi: