ESP32: server na knihovně vyšší úrovně
Podobně jako v článku věnovanému načítání JSON informací z meteowebu, jistě nás napadne, zda použití nízko úrovňového
modul usocket
je jediná volba. Dříve jsme viděli, že modul urequests
nám docela zjednodušil kód klienta. Dobrá zpráva je, že určitě lze využít některé z vyšších knihoven. Špatné zprávy máme dvě: zaprvé rozhodně není možné použít naší „starou známou“ knihovny urequest
(ta je jen pro tvorbu klienta) a zadruhé budeme muset některou z těch vyšších knihoven ručně doinstalovat, neboť není standardní součástí jazyka MicroPython pro modul ESP32.
Ale pozitivně smýšlející čtenář již jistě jásá: „Hurá zase se naučím něco nového!“
Modul Microdot
Pro zjednodušení tvorby serveru můžeme použít modul microdot
, který usnadňuje implementaci webových serverů v MicroPythonu. Modul Microdot
je minimalistický framework pro vytváření webových serverů v MicroPythonu, a je
optimalizován pro použití především na malých zařízeních, jako je modul ESP32.
Instalace modul Microdot
- Upozornění:
- Na Internetu lze narazit na několik návodů, jak v prostředí Thonny postupovat při instalaci modulu
microdot
do zařízení s MicroPythonem. Zpravidla jde o postup využívající příkazuupip
, což je správce balíčků pro MicroPython. - Podle tohoto postupu by mělo stačit v Shellu prostředí Thonny při připojeném zařízení s MicroPythonem spustit následující příkazy:
import upip
upip.install( 'micropython- microdot') - Tyto příkazy by měly použít
upip
pro stažení a instalaci balíčkumicropython-microdot
přímo z příslušného repozitáře MicroPythonu. - Problém je ale v případě modulu ESP32, který ve svém MicroPythonu správce balíčků
upip
nemá! - Takže následující postup skončí chybou:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: no module named 'upip'- Zkrátka tento postup se nám ESPákům nehodí, neboť ho lze použít jen pro zcela jiné micropythonovská zařízení!
Modul Microdot
tedy musíme nainstalovat do modulu ESP32 ručně!
Budeme postupovat dle následujících kroků:
- Stažení
Microdot
- Otevřeme stránku Microdot
na GitHubu (https://
github. com/ miguelgrinberg/ microdot). Stáhneme aktuální verzi modulu jako ZIP soubor s celým repozitářem. - Rozbalíme stažený ZIP soubor někde v počítači a najdeme v této struktuře soubor
microdot.py
. Měl by být někde ve složce:src/microdot
. Soubormicrodot.py
budeme potřebovat nahrát do ESP32.
- Otevřeme stránku Microdot
na GitHubu (https://
- Nahrání
Microdot
do modulu ESP32- Otevřeme prostředí Thonny a připojíme modul ESP32 k počítači.
- V prostředí Thonny otevřeme v menu volbu Soubor → Otevřít… a načteme soubor
microdot.py
z rozbaleného repozitáře (tam, kde jsme jej našli). - Pokud nemáme v prostředí nastaveno zobrazení souborů (menu: Zobrazení → Soubory), tak si toto zobrazení zapneme. V levém okně vidíme jak soubory v PC (nahoře), tak soubory v modulu ESP32 (dolní část okna souborů).
- Vytvoříme v modulu ESP32 složku
lib
(pokud již neexistuje!). Kliknout pravým tlačítkem, zvolit Nový adresář…, a nazvat holib
. - Nově vytvořenou složku na modulu ESP32 poklikáním otevřeme.
- Přetáhneme soubor
microdot.py
z PC do adresářelib
na ESP32, nebo načtený soubor z PC dáme Uložit jako… do zařízení ESP32 a zvolíme složkulib
.

Ověření instalace
Po úspěšném nahrání souboru bychom měli být schopni do kódu importovat Microdot
přímo ve svém skriptu. Můžeme to též ověřit přímo v Shellu:
from microdot import Microdot
Pokud se nezobrazí žádná chybová hláška (jako na následujícím obrázku), máme vyhráno.

Použití Microdot
Nyní již můžeme začít používat modul Microdot
ve svých skriptech. Následující jednoduchý příklad ukazuje, jak (opět) vytvořit základní webový server, který na ESP32 rozsvítí nebo zhasne vestavěnou LED:
# Importuje modulu
from microdot import Microdot
from machine import Pin
# Vytvoření instance aplikace Microdot
app = Microdot()
# Inicializace pinu 2 na desce (např. ESP32 nebo ESP8266) pro ovládání LED jako výstup
led = Pin(2, Pin.OUT)
# Definuje route (URL endpoint) pro rozsvícení LED
@app.route('/rozsvit')
def rozsvit(request):
# Nastaví hodnotu pinu na 1, čímž rozsvítí LED
led.value(1)
# Vrátí odpověď, že LED byla rozsvícena
return "LED rozsvícena"
# Definuje route (URL endpoint) pro zhasnutí LED
@app.route('/zhasni')
def zhasni(request):
# Nastaví hodnotu pinu na 0, čímž zhasne LED
led.value(0)
# Vrátí odpověď, že LED byla zhasnuta
return "LED zhasnuta"
# Spustí webovou aplikaci
app.run()
Podívejme se na důležité části kódu. První část by nám již měla být jasná:
from microdot import Microdot
from machine import Pin
from microdot import Microdot
: Importuje tříduMicrodot
z knihovnyMicrodot
, což je mikro-webový framework pro MicroPython. Umožňuje vytvoření jednoduchých webových aplikací.from machine import Pin
: Importuje z modulumachine
objektPin
, který poskytuje metody pro práci s GPIO piny na desce.
Nás nyní především zajímá následující vytvoření instance aplikace modulu Microdot
:
app = Microdot()
Temto příkaz vytvoří novou instance třídy Microdot
, která reprezentuje celý webový server. Tato instance je základním objektem, který umožňuje definovat a spravovat webové endpointy (cesty) a reagovat na HTTP požadavky.
Při volání app = Microdot()
se:
- Inicializuje webový server: Vytváří se základní serverová infrastruktura, která naslouchá na portu a čeká na požadavky.
- Nastaví se databáze endpointů:
Microdot
si uchovává seznam všech definovaných URL cest a funkcí, které je obsluhují. Tento seznam se vytváří pomocí tzv. dekorátorů@app.
, které přiřazují konkrétní funkce k URL endpointům (viz dále).route() - Konfigurace serveru:
Microdot
také může mít nějaké nastavení pro server, jako je port (standardně 80), zpracování chyby 404 (stránka nenalezena), nebo logování požadavků.
Po vytvoření instance Microdot
ještě server neběží, aplikace se spustí až pomocí metody run()
, která
naslouchá na určitém portu a čeká na příchozí HTTP požadavky. Přesto si nyní představme, že server běží a ukážeme si, jak jsou v totmto případě zpracovávány požadavky klientů.
Co se děje při požadavku?
Když uživatel (nebo klient) pošle HTTP požadavek na nějaký URL endpoint definovaný v aplikaci (například URL /zhasni
), Microdot
:
- Zjistí, která funkce je přiřazena k dané URL (pomocí dekorátoru
@app.
nastavíme, co se v programu má spustit nebo jaký kód vykonat).route() - Zavolá tuto funkci a předá ji objekt
request
, který obsahuje informace o požadavku (např. parametry URL, data těla požadavku, hlavičky apod.). - Funkce pak vrátí odpověď, která bude odeslána zpět klientovi.
Ukázkový příklad:
Podívejme se na jednoduchý příklad, jak to funguje:
from microdot import Microdot
# Vytvoření instance aplikace
app = Microdot()
# Definování první route - /hello
@app.route('/hello')
def hello(request):
return "Hello, world!"
# Definování druhé route - /goodbye
@app.route('/goodbye')
def goodbye(request):
return "Goodbye, world!"
# Spuštění aplikace
app.run()
V tomto případě @app.
definuje endpoint /hello
, který spustí funkci hello()
. Když uživatel přistoupí na tuto URL, server odpoví "Hello, world!". Podobně @app.
definuje endpoint /goodbye
, který spustí funkci goodbye()
a odpoví "Goodbye, world!".
Příkaz app.run()
na konci programu spustí server, který začne naslouchat požadavkům (ale to trochu předbíháme).
Vypadá to složitě? Ani ne, ale je třeba si trochu zvyknout na trochu jiný styl práce.
Pokud jsme na předchozím příkladu aspoň trochu pochopili filozofii použití modulu Microdot
, zkusíme se vrátiti k původnímu programu s rozsvěcením vestavěné LED modulu ESP32.
Následující řádek nastavuje pin 2 (pin, na kterém je vestavěná LED) do režimu digitálního výstupu:
led = Pin(2, Pin.OUT)
Jak v MicroPythonu pracovat s vstupně výstupními piny modulu ESP32 jsme si vysvětlovali hned v jednom z prvních článků. Takže jen rychle připomeneme:
Pin(2)
označuje pin číslo 2 na desce (obvykle GPIO pin), kde bude LED připojena.Pin.OUT
označuje, že pin bude použit jako výstup (pro zapínání a vypínání LED).led
je pak objektem, pomocí jehož metod a atributů můžeme s daným pinem pracovat.
Definování HTTP endpointů
Důležitou částí našeho serveru bude nastavení funkcí, které bude třeba volat při zadání dané URL (tzv. nastavení endpointů):
# Definování route pro /rozsvit
@app.route('/rozsvit')
def rozsvit(request):
led.value(1)
return "LED rozsvícena"
@app.
, čili tzv. dekorátor (kdo tohle pojmenování vymýšlel!), určuje, že funkce rozsvit
bude obsluhovat HTTP požadavek na URL /rozsvit
. Tedy, že když uživatel navštíví tuto URL, zavolá se funkce rozsvit
.
def rozsvit(request):
: Tato funkce je definována pro obsluhu požadavku na endpointu/rozsvit
.led.value(1)
: Tato metoda nastaví hodnotu pinu na úroveň logické 1, což znamenávysoký stav (HIGH)
, tedy zapnutí LED.return "LED rozsvícena"
: Po vykonání této akce (rozsvícení LED) funkce vrátí odpověď, která bude odeslána zpět klientovi (např. webovému prohlížeči) ve formě textového řetězce"LED rozsvícena"
.
Obdobně bude řešena i část zabývající se vypnutím LED:
# Definování route pro /zhasni
@app.route('/zhasni')
def zhasni(request):
led.value(0)
return "LED zhasnuta"
Poslední částí celého programu je spuštění serveru (Což jsme ale už před chvílí trochu předčasně prozradili.)
app.run()
Příkaz app.run()
spustí samotný webový server, který začne naslouchat na portu (obvykle port 80) pro příchozí HTTP požadavky. Kdo by chtěl tento příkaz doplnit upřesňujícími parametry, může jej zapsat kupříkladu následujícím způsobem:
app.run(host='0.0.0.0', port=80)
Aplikace běží ve smyčce a čeká na požadavky (Není ji tedy potřeba vnořovat do nějaké nekonečné smyčky). Je to jednoduchý webový server, který pomocí HTTP umožňuje ovládání hardwaru na desce (v tomto případě LED). Takto vzniklá webová aplikace se spustí na modulu ESP32 a je přístupná přes webový prohlížeč na IP adrese stejně jako předchozí programy. Konkrétně: Když server dostane požadavek na /rozsvit
, zavolá funkci rozsvit
, která rozsvítí LED. Když server dostane požadavek na /zhasni
, zavolá funkci zhasni
, která zhasne LED.
Máme hotovo… (?)
Cože, že Vám něco chybí?
No jistě! Předchozí programy přece ještě měly nějaké webové rozhraní, kterým se dala vestavěná LED řídit. Potřebujeme tedy nějakou stránku, která se načte na kořenové stránce a bude zasílat „ty správné“ požadavky.
Jak tento problém vyřešíme?
Zkrátka přidáme další „routu“ – konkrétně pro @app.
@app.route('/')
def index(request):
response = """
<!DOCTYPE html>
<html>
<head>
<title>ESP32 LED Control</title>
</head>
<body>
<h1>ESP32 LED Control</h1>
<p>Stav LED: """ + ("Rozsvícena" if led.value() == 1 else "Vypnuta") + """</p>
<button><a href="/rozsvit">Rozsvítit LED</a></button>
<button><a href="/zhasni">Zhasnout LED</a></button>
</body>
</html>
"""
V proměnné response
máme několika řádkový text naší odpovědi (stejné jako v předchozích programech). Na náš více řádkový text jsme aplikovali funkci Response()
a takto vzniklé odpovědi (proměnná res
) jsme nastavili kódování UTF-8 a content-type odpovídající HTML kódu. Jinak by nám webový prohlížeč na straně klienta prezentoval výstup jako text.
Co dělá Response()
?
Asi nás zarazila funkce Response
, pojďme se na ni podívat podorbněji. Response()
je objekt, který reprezentuje HTTP odpověď a umožňuje nám nastavit různé atributy odpovědi, jako je:
- Tělo odpovědi (content): Co bude klient dostávat (HTML, JSON, text atd.).
- Hlavičky odpovědi: K tomu patří například
Content-Type
, který říká, jaký typ dat klient očekává (napříkladtext/html
pro HTML neboapplication/
pro JSON). Například našejson Content-Type: text/html; charset=utf-8
říká prohlížeči, že obsah odpovědi je HTML a má být kódován v UTF-8. - Status kód: Kód odpovědi, například 200 (OK), 404 (Not Found), 500 (Internal Server Error), atd.
- Další vlastnosti: Můžeme přidat různé HTTP hlavičky nebo informace pro cache, cookies, atd.
V praxi tedy Response()
vytváří celkovou odpověď, kterou server pošle zpět klientovi, a to nejen s obsahem, ale i s odpovídajícími hlavičkami, které definují, jak bude obsah interpretován.
Pokud bychom pouze poslali proměnnou response
(například ve formě textu), nebyly by nastaveny potřebné hlavičky (například Content-Type
), prohlížeč by nemusel správně interpretovat obsah odpovědi. Tímto způsobem
bychom například mohli poslat HTML obsah, ale prohlížeč by nemusel vědět, že se jedná o HTML, a místo toho by mohl tento obsah interpretovat jako prostý text. To už jsme naznačovali.
Budeme si tedy pamatovat, že funkce Response()
umožňuje nastavit nejen obsah odpovědi, ale i nastavit správné hlavičky, což je klíčové pro správné zobrazení obsahu (například Content-Type
), případně přidat status kód odpovědi a další metadata. Bez Response()
by nám chyběly tyto klíčové komponenty, což by vedlo k nesprávné interpretaci odpovědi na straně klienta.
A když už jsme se tak pěkně rozjeli v tvorbě serveru pro rozsvěcení LED, doplníme kód ještě o jednu routu:
# Chyba 404 - stránka nenalezena
@app.errorhandler(404)
def page_not_found(request):
res = Response("""
<!DOCTYPE html>
<html>
<head>
<title>Chyba 404</title>
</head>
<body>
<h1>Stránka nenalezena (404)</h1>
<p>Omlouváme se, požadovaná stránka nebyla nalezena.</p>
<a href="/">Zpět na hlavní stránku</a>
</body>
</html>
""" )
res.headers['Content-Type'] = 'text/html; charset=utf-8'
return res
Copak asi tato část kódu dělá?
Ale na naší nekonečné cestě k dokonalosti ještě nejme hotovi! Co nyní program dělá?
Otevřeme IP modulu ESP32, načte se úvodní stránka s tlačítky. Klikneme na jakékoliv tlačítko a skončíme textem, který nás upozorňuje, že LED je rozsvícena/zhasnuta. A dál? A dál právě nic!
Pojďme to upravit tak, aby se při zavolání URL /rozsvit
(případně /zhasni
) stalo to, co má, ale aby pak server uživatele opět nasměroval na úvodní stránku s popisem stavu LED a s přepínacími tlačítky.
Jak to uděláme? Následující upravená routa nám to ukáže:
@app.route('/rozsvit')
def rozsvit(request):
led.value(1) # Rozsvícení LED
return redirect('/') # Přesměrování zpět na hlavní stránku
Jak je vidět, zcela jsme zrušili textovou odpověď a místo toho vrátili pomocí funkce redict()
požadavek o přesměrování na úvodní URL ('/'
).
Tak, teď už snad můžeme kód prohlásit za hotový!
from microdot import Microdot, Response, redirect
from machine import Pin
app = Microdot()
# Nastavení LED na pinu 2
led = Pin(2, Pin.OUT)
@app.route('/')
def index(request):
# Dynamické generování HTML s aktuálním stavem LED
response = """
<!DOCTYPE html>
<html>
<head>
<title>ESP32 LED Control</title>
</head>
<body>
<h1>ESP32 LED Control</h1>
<p>Stav LED: """ + ("Rozsvícena" if led.value() == 1 else "Vypnuta") + """</p>
<button><a href="/rozsvit">Rozsvítit LED</a></button>
<button><a href="/zhasni">Zhasnout LED</a></button>
</body>
</html>
"""
res = Response(response)
res.headers['Content-Type'] = "text/html; charset=utf-8"
return res
@app.route('/rozsvit')
def rozsvit(request):
led.value(1) # Rozsvícení LED
return redirect("/") # Přesměrování zpět na hlavní stránku
@app.route('/zhasni')
def zhasni(request):
led.value(0) # Zhasnutí LED
return redirect("/") # Přesměrování zpět na hlavní stránku
# Chyba 404 - stránka nenalezena
@app.errorhandler(404)
def page_not_found(request):
res = Response("""
<!DOCTYPE html>
<html>
<head>
<title>Chyba 404</title>
</head>
<body>
<h1>Stránka nenalezena (404)</h1>
<p>Omlouváme se, požadovaná stránka nebyla nalezena.</p>
<a href="/">Zpět na hlavní stránku</a>
</body>
</html>
""")
res.headers['Content-Type'] = "text/html; charset=utf-8"
return res
app.run(host="0.0.0.0", port=80)
Server v režimu AP nebo STA?
Jak jsme si již v minulém článku ukázali, tak nyní vlastně hotovo nemáme, neboť program serveru nám nebude fungovat, dokud modul ESP32 nebude připojen k síti Wi-Fi. Pokud i dnes zvolíme strategii dvou souborů, tedy hlavní část programu (program serveru) uložíme do zařízení do souboru main.py
, můžeme vhodnou volbou souboru boot.py
zvolit nastavení modulu ESP32 jako přístupového bodu (AP) nebo stanice (STA).
Režim STA
Chceme-li tedy použít právě naprogramovaný server na modulu, který se přihlásí do existující sítě Wi-Fi, použijeme následující soubor boot.py
:
import network
import time
def connect_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF) # Vytvoří instanci síťového adaptéru v režimu stanice
wlan.active(True) # Aktivuje síťový adaptér
wlan.connect(ssid, password) # Připojí se k dané Wi-Fi síti
for _ in range(10): # Pokusíme se připojit během 10 pokusů
if wlan.isconnected(): # Ověříme, zda je připojení úspěšné
print("\nConnected to WiFi")
print("IP address:", wlan.ifconfig()[0]) # Výpis přidělené IP adresy
return
print("." , end="")
time.sleep(1) # Počkáme 1 sekundu mezi pokusy
print("Connection failed") # Pokud se nepřipojíme, vypíšeme chybovou hlášku
connect_wifi("NAZEV_WIFI", "HESLO_WIFI")
Režim AP
Pokud se vydáme cestou AP, tedy situace, kdy modul ESP32 bude vytvářet svou vlastní Wi-Fi, uložíme do souboru boot.py
následující kód:
import network
ssid = 'ESP32-AP-test'
password = 'heslo-pro-pristup'
ipinfo = ('192.168.8.102', '255.255.255.0', '192.168.8.102', '8.8.8.8')
mynet = network.WLAN(network.AP_IF)
mynet.active(True)
mynet.ifconfig(ipinfo)
mynet.config(essid=ssid, password=password, authmode=network.AUTH_WPA_WPA2_PSK, hidden=0)
# mynet.config(essid=ssid, password=password)
while not mynet.active():
pass
V obou případech pak ovládání vestavěné LED na modulu ESP32 bude mít ve webovém prohlížeči následující podobu:

Závěrem:
Dnes jsme si představili jednu z pythonovských knihoven pro implementaci serveru do modulu ESP32. Modul Microdot
pochopitelně není jediný, takže se při svém vyhledávání dalších relevantních informací jistě potkáte s vývojáři, kteří používají modul jiný (např. uHTTPd
nebo Aiohttp
, ale i další…). Možná si v některém z dalších článků ještě nějakou podobnou knihovnu představíme, protože serverů v MicroPythonu na modulu ESP32 nikdy není dost!
Zároveň však myslíme, že pokud chceme využívat modul ESP32 pro nějakou tu domácí automatizaci, čili to honosně znějící „IoT“, měli bychom se začít také začít zabývat tím, jak lze k modulu ESP32 v MicroPythonu připojit nějaká čidla.
Uvidíme! Třeba to příště zkusíme spojit a představíme si nějakou další knihovnu pro tvorbu serveru a jako ukázku zkusíme načíst data z nějakého čidla. Co třeba: Bylo by libo teplotu a vlhkost vzduch?