Fyzikální kabinet FyzKAB
TechHobby ESP32 + MicroPython ESP32: Wi-Fi v MicroPythonu ESP32: Jednoduchý server v MicroPythonu

Jednoduchý server na ESP32 v MicroPythonu

V předchozích článcích jsme se věnovali základům programování modulu ESP32 v jazyce MicroPython. Především jsme se v těch posledních naučili, jak modul ESP32 připojit k Wi-Fi. Kromě možnosti získání data z internetu, například z meteorologických webů ve formátu JSON bychom mohli začít zkoušet modul ESP32 tak trochu ovládat. V prvních článcích jsme přece zvládli práci s jeho GPIO piny! Což to takhle zkusit nějak spojit a tím začít tak trochu s tím dnes tak často skloňovaným IoT.

Dnes se tedy posuneme dále a vytvoříme jednoduchý server, který nám umožní ovládat vestavěnou LED modulu ESP32 přes internet nebo lokální síť.

Jasně… blikat LED asi není nic moc, to jsme taky rovnou mohli vytvořit web, který akorát napíše: „Hello World!“. Na druhou stranu nějak začít musíme, ne?

Zopakování síťové komunikace

Než se pustíme do samotného programování, je dobré si připomenout, jak funguje síťová komunikace. Klasický webový server přijímá HTTP požadavky od klientů (například webového prohlížeče v mobilu nebo PC) a odpovídá na ně zpravidla ve formátu HTTP.

Každý požadavek obsahuje tři hlavní části:

  1. Řádek požadavku – např. GET /rozsvit HTTP/1.1, který určuje metodu (GET), cestu (/rozsvit) a verzi protokolu (HTTP/1.1).
  2. Hlavičky – metadata požadavku, jako jsou Host, User-Agent, Origin aj.
  3. Tělo požadavku – u metody POST (nebo PUT) může obsahovat data, která jsou na server odeslána.

Odpověď serveru má také tři části:

  1. Status kód – např. HTTP/1.1 200 OK, což znamená úspěch.
  2. Hlavičky odpovědi – např. Content-Type: text/html.
  3. Tělo odpovědi – obsah HTML stránky nebo jiná data, která se odesílají.

Dnešní článek ukáže, jak tuto komunikaci zrealizovat na modulu ESP32 v MicroPythonu, konkrétně na úrovni modulu usocket.

Modul ESP32 jako server v režimu stanice (STA)

První ukázkový program bude spouštět server na modulu ESP32, který bude nastaven v režimu stanice (STA), tedy bude se připojovat k existující Wi-Fi síti. To jsme zde již řešili minule i předminule. Stále a stále se nám tu opakovala část kódu, která řešila připojení k Wi-Fi. Článek od článku jsme stále tuto část trochu modifikovali, aby bylo v této části vždy aspoň něco nového. Ale ve své podstatě to byl pořád ten samý kód. A jeho úkol byl jasný – připojit modul ESP32 k existující Wi-Fi.

Dnes v tomto kódu nebudeme měnit nic na programové úrovni, dokonce ho zde uvedeme v té „nejholejší“ podobě, ale zkusíme udělat strukturální zásah. A to do celého našeho projektu.

Rozdělíme program, na dvě části (každá bude uložená v jiném specifickém souboru MicroPythonu). V MicroPythonu jsou soubory boot.py a main.py součástí procesu spouštění. Tyto soubory mají specifickou roli, a jejich obsah má určitá omezení:

  1. boot.py:
    • Účel: Tento soubor je spouštěn jako první při startu zařízení.
    • Použití: Slouží k nastavení základních konfigurací zařízení, jako je inicializace periferie (např. sériová komunikace), připojení k Wi-Fi, nastavení hodin reálného času (RTC), nebo konfigurace pinů.
    • Omezení: V boot.py by neměl být vykonáván žádný kód, který závisí na konkrétních knihovnách nebo hardwaru, který ještě není inicializován. Například pokus o práci s Wi-Fi nebo specifickými GPIO piny, které nejsou připraveny, by mohl vést k chybám.
  2. main.py:
    • Účel: Tento soubor je spouštěn po dokončení inicializace z boot.py.
    • Použití: Je určen pro hlavní aplikaci, která by měla obsahovat kód, který využívá všechna zařízení a knihovny, které jsou nastaveny v boot.py. Zde lze spouštět logiku programu, například číst senzory, posílat data, nebo vykonávat hlavní funkce zařízení.
    • Omezení: V main.py bychom měli být opatrní při volání funkcí, které jsou přímo závislé na konfiguracích provedených v boot.py. Také zde je vhodné udržovat kód efektivní a rychlý, protože jakýkoli problém zde může ovlivnit funkčnost celého zařízení.

Budeme si tedy pamatovat, že soubor boot.py, kterému jsme se zatím vyhýbali, je v MicroPythonu trochu specifický. Často se uvádí, že kód v boot.py by měl být co nejjednodušší, aby nebrzdil start zařízení. Například někdy se uvádí, že by zde neměly být pokusy o připojení k Wi-Fi. Jinde pak ale najdeme kód, kde se právě připojení k Wi-Fi naopak používá. Čert aby se v tom vyznal! My zde tento soubor použijeme právě pro připojení modulu ESP32 k Wi-Fi a později si ukážeme, jak změnou kódu v boot.py lze lehce změnit server fungující v režimu STA na režim AP.

Ale pojďme na to postupně, tedy pěkně od boot.py.

V souboru boot.py, jak už jsme zmínili, nastavíme připojení k Wi-Fi:

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")

Rychlý rozbor kódu:

  • network.WLAN(network.STA_IF) – Vytváří síťové rozhraní v režimu stanice (pro připojení k existující Wi-Fi).
  • wlan.active(True) – Aktivuje síťový adaptér.
  • wlan.connect(ssid, password) – Pokusí se připojit k síti se zadaným SSID a heslem.
  • wlan.isconnected() – Kontroluje stav připojení.
  • wlan.ifconfig()[0] – Vrací IP adresu přidělenou ESP32.
  • Připojení je řešeno pomocí funkce connect_wifi s dvěma vstupními parametry (síť a heslo)
  • Pokus o připojení je realizován cyklem s pevným počtem kroků, ale v případě úspěchu dojde k ukončení cyklu i celé funkce (příkaz return)

V souboru main.py vytvoříme jednoduchý server pomocí modulu usocket:

import usocket as socket
from machine import Pin

led = Pin(2, Pin.OUT)   # Definice LED na pinu 2

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # Vytvoření socketu
s.bind(('0.0.0.0', 80))     # Připojení socketu na port 80
s.listen(5)                 # Server naslouchá příchozím připojením (max. počet klientů 5)

while True:
    conn, addr = s.accept()     # Přijímá nové připojení
    request = conn.recv(1024)   # Přijme data od klienta (max. 1024 bytů)
    request = str(request)

    if '/rozsvit' in request:    # Test GET dotazu
        led.value(1)             # Rozsvítí LED
    elif '/zhasni' in request:   # Test GET dotazu
        led.value(0)             # Zhasne LED

    response = "HTTP/1.1 200 OK\nContent-Type: text/html\n\nLED stav aktualizován"
    conn.send(response)   # Odeslání odpovědi
    conn.close()          # Uzavření spojení

Tento kód implementuje jednoduchý webový server běžící na modulu ESP32 s použitím MicroPythonu. Server umožňuje kontrolovat stav LED připojené k pinu 2 pomocí HTTP požadavků.

První část kódu obsahuje tradiční import knihoven:

import usocket as socket
from machine import Pin
  • usocket: Moduly usocket poskytují funkce pro práci se sockety (síťová komunikace). V tomto případě je importována zkrácená verze pro MicroPython aliasovaná jako plná verze pro Python.
  • machine: Tento modul umožňuje přístup k hardwarovým pinům a dalším funkcím na modulu (v tomto případě si importujeme jen část Pin pro ovládání LED).

Následuje nastavení vestavěné LED, ta je u modulu ESP32 na pinu číslo 2.

led = Pin(2, Pin.OUT)
  • Pin(2, Pin.OUT): Vytvoří objekt pro pin číslo 2, který bude použit pro ovládání LED. Tento pin bude v režimu výstupu (Pin.OUT), což znamená, že na něm bude možné měnit stav (vysoký nebo nízký).
  • Tato LED přístupná přes objekt led, může být zapnuta (logická 1) nebo vypnuta (logická 0).

Pro připojení a komunikaci serveru s okolím je třeba nastavit socket.

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 80))
s.listen(5)
  • socket.socket(socket.AF_INET, socket.SOCK_STREAM): Vytvoří nový socket pro síťovou komunikaci. Používá protokol IPv4 (AF_INET) a protokol pro streamy (SOCK_STREAM), což je typické pro TCP komunikaci.
  • s.bind(('0.0.0.0', 80)): Tento příkaz „připojí“ socket na IP adresu 0.0.0.0 a port 80. IP adresa 0.0.0.0 znamená, že server bude naslouchat na všech dostupných síťových rozhraních zařízení (všechny IP adresy). Tento zápis je speciální způsob, jak vyjádřit, že server není omezen na jednu konkrétní síťovou adresu, ale je otevřený a přijímá připojení na všech dostupných síťových rozhraních (ethernet, WiFi, apod.). Pokud bychom místo '0.0.0.0' použili konkrétní IP adresu zařízení (např. '192.168.1.100'), server by naslouchal pouze na této konkrétní IP adrese. To by znamenalo, že server by přijal připojení pouze z tohoto rozhraní (nebo pouze z této sítě). To se také občas může hodit, ale nyní je situace jiná.
  • s.listen(5): Tímto se server připraví naslouchat na příchozí připojení, a to s maximálním počtem 5 klientů, kteří mohou být připojeni najednou.

Máme-li server připravený, můžeme v hlavní nekonečné smyčce čekat na připojení klienta.

while True:
    conn, addr = s.accept()
    request = conn.recv(1024)
    request = str(request)
  • s.accept(): Čeká na připojení od klienta. Když se někdo připojí, vrátí socket conn pro komunikaci s tímto klientem a addr, což je adresa (IP) klienta.
  • conn.recv(1024): Tento příkaz čeká na data od klienta. Získá až 1024 bytů dat (což dostačuje velikosti jednoho HTTP požadavku). Data jsou přijata jako bytový řetězec, který je následně převeden na textový řetězec (str(request)).

Co má server dělat, mu naznačíme zadáním správné URL. Kupříkladu pro rozvícení LED se budemem dožadovat souboru /rozvit. Pokud se tento specifický výraz objeví v textu požadavku, provedeme akci. (Zatím hledáme v celém požadavku výskyt specifického výrazu, do budoucna by to asi chtělo „vyšetřit“ jen řádku GET v požadavku nebo celý požadavek korektně zpracovat)

if '/rozsvit' in request:
    led.value(1)
elif '/zhasni' in request:
    led.value(0)
  • Pokud řetězec request obsahuje /rozsvit, znamená to, že klient požaduje rozsvícení LED. To se provede nastavením pinu na vysokou hodnotu (led.value(1)).
  • Pokud proměnná request obsahuje /zhasni, znamená to, že klient požaduje zhasnutí LED. To se provede nastavením pinu na nízkou hodnotu (led.value(0)).

Klient by se po dotazu na server měl dočkat nějaké odpovědi. Tu mu připraví a odešlou následující řádky:

response = "HTTP/1.1 200 OK\nContent-Type: text/html\n\nLED stav aktualizován"
conn.send(response)
conn.close()
  • response: Definuje odpověď, kterou server odešle klientovi. Odpověď je: první řádek je stavový status (200 OK), což znamená, že požadavek byl úspěšně zpracován. Pak následuje stanovení formátu odpovědi (Content-Type: text/html – určuje formát HTTP). Po prázdné řádce následuje tělo odpovědi. Odpovědí je text: "LED stav aktualizován".
  • conn.send(response): Odesílá připravenou odpověď klientovi.
  • conn.close(): Uzavře po odeslání odpovědi socketové spojení s klientem.

Teď, když je ESP32 spuštěné a webový server běží, můžeme vyzkoušet ovládání LED přes webový prohlížeč. Abychom se mohli připojit k serveru, potřebujeme znát IP adresu svého ESP32. Jakmile je ESP32 připojeno k Wi-Fi síti, IP adresa bude přidělena routerem. My se ji dozvíme tak, že ji modul při svém připojení (kód v souboru boot.py) zobrazí na výstupu prostředí Thonny, kterým jsme vytvořili a nahráli program.

Druhou možností, jak získat IP adresu je získat tuto adresu v administraci routeru nebo použít nástroje pro skenování sítě. Asi se zatím budeme držet toho, že si ji přečteme ve výstupu prostředí Thonny.

Po získání IP adresy (např. 192.168.x.x), otevřeme webový prohlížeč na počítači nebo mobilním telefonu, který je připojený ke stejné síti (!), zadáme do prohlížeče specifickou URL adresu našeho požadavku. Například pro zapnutí LED zadáme:

http://192.168.x.x/rozsvit

(Pochopitelně, zde 192.168.x.x nahradíme skutečnou IP adresou ESP32!)

Pro vypnutí LED zadáme do prohlížeče:

http://192.168.x.x/zhasni

Pokud vše funguje správně, po zadání těchto adres by měla LED na ESP32 reagovat – zapnout se, nebo vypnout – přesně podle toho, co zadáme za povel. Každý požadavek by měl nejen způsobit změnu stavu LED na ESP32, ale i webový prohlížeč by měl navrátit odpověď „LED stav aktualizován“. (POZOR: s tím dlouhým A tu je problém, protože jsme v odpovědi nenastavili kódování výstupu – viz obrázek)

dotaz na server

Získaný výsledek asi není špatný, ale rozhodně není moc komfortní. Můžeme tedy zkusit upravit kód, aby modul ESP32 nebyl jen serverem přijímajícím HTTP požadavky, také mohl posílal HTML stránku s nějakými řídicími odkazy (nebo ještě lépe přímo s tlačítky pro ovládání vestavěné LED). To znamená, že server nebude pouze čekat na požadavky, ale také odpoví na požadavek klienta (prohlížeče) HTML stránkou, která umožní ovládání LED pomocí klikání na tlačítka nebo odkazy.

Pojďme se podívat na níže uvedený kód, který by měl vracet HTML stránku s tlačítky pro ovládání LED (A asi něco uděláme i s tím kódováním češtiny!):

import usocket as socket
import machine

led = machine.Pin(2, machine.Pin.OUT)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 80))
s.listen(5)

while True:
    conn, addr = s.accept()
    request = conn.recv(1024)
    request = str(request)

    if '/rozsvit' in request:
       led.value(1)
    elif '/zhasni' in request:
       led.value(0)

    response = """HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

<!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>
"""

    conn.send(response)
    conn.close()

V čem se tento kód změnil?

  • Byla přidána proměnná response, která obsahuje text v trojitých uvozovkách. V Pythonu se text zadaný v trojitých uvozovkách ("""text""" nebo '''text''') nazývá víceřádkový řetězec (nebo také multiline string). Tento typ řetězce se používá pro text, který může být na více řádcích. Zde jej využíváme pro text odpovědi – první část jsou hlavičky, po prázdném řádku je tělo odpovědi.
  • Všimněte si, že tento víceřádkový text můžeme i doplňovat podle aktuální situace, tedy stavem LED – spojení pomocí + s podmíněným textem v závorce (už jsme také někde dříve potkali)
  • Do hlavičky odpovědi jsme u Content-Type: text/html za středník přidali charset=utf-8, což nastavuje použitou znakovou sadu výstupu serveru. Takže bychom se mohli dočkat pořádné prezentace české diaktitiky.

Program jinak funguje úplně stejně jako předchozí, jen po každém požadavku server vrátí HTML stránku (proměnná response), která obsahuje: kromě nadpisu ("ESP32 LED Control"), textu s aktuálním stavem LED (rozsvícená nebo vypnutá) také dvě tlačítka (<button>) s odkazy "Rozsvítit LED" (odkaz na /rozsvit) a "Zhasnout LED" (odkaz na /zhasni). HTML stránka se zobrazí v prohlížeči, uživatel může kliknutím na odkazy ovládat LED.

Po zadání dané IP adresy modulu ESP32 by se měl zobrazit jednoduchý web, kde je možné kliknutím na odkazy ovládat LED na ESP32.

dotaz na server s tlacitky

Pokud se nám nelíbí vzhled tlačítek, která obsahují odkaz (modrý potržený text), můžeme využít v HTML kódu JavaScriptu a přidat tlačítkům událost pro click (metoda onclick) vyvolání daného odkazu (změna vlastnosti location.href hlavního prvku window).

<button onclick="window.location.href='/rozsvit'">Rozsvítit LED</button>
<button onclick="window.location.href='/zhasni'">Zhasnout LED</button>

Vzhled stránka pak bude vypadat takto:

dotaz na server s lepsimi tlacitky

Pochopitelně se další vývojářově kreativitě meze nekladou! Zde prezentovaný ukázkový kód je maximálně zjednodušen. Některé části na základě předešlých článků již jistě dokážeme udělat lépe. Nyní však jde především o základní ukázku serveru.

Modul ESP32 jako server v režimu přístupového bodu (AP)

Máme-li fungující program serveru, je vlastně jedno, zda nyní bude fungovat na modulu nastaveném jako stanice (STA), nebo jako přístupový bod (AP). Pokud tedy změníme výchozí nastavení modulu ESP32 v souboru boot.py, nemusíme v souboru main.py ani hnout prstem!

Chcete mít server na modulu ESP32 při nastavení AP? Změňte v něm soubor boot.py za následující:

import network

ssid = 'ESP32-AP-test'
password = 'heslo-pro-pristup'

ipinfo = ('192.168.8.103', '255.255.255.0', '192.168.8.103', '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

Po spuštění kódu v modulu ESP32 se vytvoří v modulu přístupový bod, v tomto případě dokonce s pevnou IP adresou a můžeme se k němu přihlásit kupříkladu svým mobilním telefonem.

Jakmile se přihlásíme k Wi-Fi síti „ESP32-AP-test“ a zadáme do URL řádky webového prohlížeče adresu: 192.168.8.103 měli bychom skončit na úvodní stránce, kde si zvolíme, co s vestavěnou LED potřebujeme provést. Klikneme-li na tlačítko rozsvítit, skončíme na stránce /rozsvit a ESP32 svou vestavěnou LED rozsvítí. V případě druhého tlačítka skončíme se zhasnutou LED na stránce /zhasni.

Následující obrázek celou situaci zobrazuje, pokud bychom použili lehce upravený náš dnešní zcela první program:

ESP32-server na mobilu

Pokud do souboru main.py použijeme poslední verzi našeho dnešního programu, mohla by být situace podstatně zajímavější.

Závěrem

Jako úvod do serveru by to dnes asi stačilo. K tématu serveru na modulu ESP32 se ještě vrátíme v příštím článku, kde bychom opět zkusili opustit nízko úrovňový modul usocket a podívali bychom se, zda by nám MicroPython pro ESP32 neposkytl něco trochu programátorsky komfortnějšího (podobně jako jsme viděli v případě webového klienta a modulu urequest).

Zatím jsme se dnes naučili:

  • Jak rozdělit program na boot.py a main.py.
  • Jak napsat jednoduchý server s usocket.
  • Jak vytvořit program serveru použitelný pro režim stanice (STA) i přístupového bodu (AP).

Pro počáteční hraní si s modulem ESP32 tohoto (v kontextu předešlých článků) vůbec neumíme málo. Takže konec studia teorie a běžte si hrát! Protože v případě modulu ESP32 platí pořekadlo: „Kdo si hraje, nezlobí!“ obzvláště!

UPOZORNĚNÍ:
Nesouhlasíme s vyřazením Newtonových zákonů, Ohmova zákona a zákona zachování energie z učiva fyziky základních škol v České republice!