ESP32: Načtení JSON dat z internetu
Pokud jsme zvládli v MicroPyhonu vytvořit program, který nám modul ESP32 proměnil v jednoduchého webového klienta, který načetl zvolený textový soubor umístěný na internetu, můžeme tento kód povýšit na „užitečnější“ úroveň.
V dnešní digitální době je načítání dat z internetu klíčovou součástí mnoha aplikací a webových služeb. Ať už se jedná o zobrazování aktuálních zpráv, informací o počasí nebo údajů z databází. Jedním z nejpoužívanějších formátů pro přenos těchto dat je tzv. JSON (JavaScript Object Notation). Tento formát je lehký, snadno čitelný a široce podporovaný napříč různými programovacími jazyky.
Struktura souboru JSON je jednoduchá a čitelná jak pro lidi, tak pro stroje, což z něj činí oblíbený formát pro přenos dat přes internet. JSON používá dvojice klíč-hodnota, přičemž data mohou být uspořádána do objektů (označených složenými závorkami {}
) nebo polí (hranaté závorky []
). Díky své univerzálnosti je JSON podporován většinou moderních programovacích jazyků a často se používá v API pro komunikaci mezi webovými službami a aplikacemi.
Příklad jednoduchého JSON souboru obsahujícího informace o uživateli:
{
"jmeno": "Jan Novák",
"vek": 30,
"email": "jan.novak@example.com",
"adresa": {
"ulice": "Hlavní 123",
"mesto": "Praha",
"psc": "11000"
},
"telefonni_cisla": ["+420123456789", "+420987654321"]
}
V Pythonu lze pracovat s JSON daty pomocí modulu json
. Pokud jsou data uložena jako řetězec nebo načtena z API, můžeme je snadno převést na slovník a přistupovat k jednotlivým hodnotám. Jako ukázku si můžeme kód, který z textu obsahující JSON strukturu (proměnná json_data
) získá informace do použitelné pythonovské struktury, se kterou se dá nadále
pracovat.
import json # modul pro klasický Python
# import ujson as json # modul pro MicroPython
# JSON data jako řetězec
json_data = '''
{
"jmeno": "Jan Novák",
"vek": 30,
"email": "jan.novak@example.com",
"adresa": {
"ulice": "Hlavní 123",
"mesto": "Praha",
"psc": "11000"
},
"telefonni_cisla": ["+420123456789", "+420987654321"]
}
'''
# Parsování JSON do slovníku
uzivatel = json.loads(json_data)
# Získání základních informací
print("Jméno:", uzivatel["jmeno"])
print("Věk:", uzivatel["vek"])
print("E-mail:", uzivatel["email"])
print("Město:", uzivatel["adresa"]["mesto"])
print("První telefonní číslo:", uzivatel["telefonni_cisla"][0])
Výše uvedený kód je pro použití v klasickém Pythonu, chceme-li jej použít na modulu ESP32, kde je nainstalován MicroPython, vypustíme připojení modulu json
na první řádce a naopak zrušíme komentář na řádce druhé.
import ujson as json
MicroPython používá odlehčenou verzi standardního Python modulu json
, která je dostupná pod názvem ujson
. Zde zvolený alias json
umožní, aby byl kód přenosnější mezi MicroPythonem a běžným Pythonem, kde se používá standardní json
modul. Podobnou techniku jsme kupříkladu užili v minulém článku u knihovny socket
a usocket
. Tento přístup je užitečný, pokud chcete psát kód, který bude fungovat v obou prostředích s minimálními úpravami.
Python ve svém modulu json
, respektive Micropython v knihovně ujson
, nabízí metodu loads()
, která nám umožní „JSONovský“ text, za určitých podmínek (!), převést na Pythonem zpracovatelnou podobu. Tyto jednoduché přístupy umožňují snadno extrahovat potřebná (vhodná) data a dále s nimi pracovat v aplikacích.
Načítání JSON dat z API – příklad s 'open-meteo.com'
Nyní si na praktickém příkladu načtení JSON dat z webové služby ukážeme, jak pracovat s JSON v MicroPythonu na modulu ESP32. Při práci s IoT zařízeními často potřebujeme získávat data z internetu, například o počasí, cenách kryptoměn nebo jiných senzorických hodnotách. Pro tuto ukázku využijeme veřejné meteorologické API (Application Programming Interface čili aplikační programové rozhraní) webu open-meteo.com
, které nevyžaduje žádnou registraci ani API klíč, což z něj činí ideální volbu pro naše experimentování!
Open-Meteo poskytuje bezplatná data o počasí, která lze snadno získat prostřednictvím jednoduchých HTTP požadavků. API vrací data ve formátu JSON, což znamená, že je můžeme zpracovat pomocí vestavěného MicroPython modulu ujson
. V našem příkladu se připojíme k open-meteo API, získáme aktuální teplotu pro konkrétní souřadnice a zobrazíme ji na výstupu. Tento přístup lze
snadno rozšířit o další meteorologické údaje, jako je vlhkost vzduchu, rychlost větru nebo předpověď na několik dní dopředu.
V minulém článku jsme si vytvořili jednoduchý webový klient, který nyní zkusíme využít. Pro odeslání požadavku na server open-meteo.com
budeme potřebovat několik informací. Jaké konkrétní informace musíme API dodat, je naštěstí uvedeno v dokumentaci přímo na webu https://
- Host: Adresu webu (serveru) poskytující službu navracející JSON data (zde:
api.open-meteo.com
) - Cesta k souboru: Cesta ke zdroji, který nám navrátí požadovaná data (zde:
/v1/forecast
). Jelikož voláme službu metodou GET, musí být zde zadána formou parametrů ještě určitá sada doplňujících informací:- Doplňující parametry zadáváme formou parametrů v URL.
Musíme minimálně zadat GPS souřadnice místa a specifikovat požadovanou sadu JSON dat, konkrétně:- latitude: zeměpisná šířka místa, pro které požadujeme informace
- longitude: zeměpisná délka tamtéž
- název konkrétní služby (zde current – aktuální stav), hodnota pro teplotu je
temperature_2m
)
- Doplňující parametry zadáváme formou parametrů v URL.
Bude-li nás zajímat aktuální počasí pro Prahu, konkrétně pro GPS souřadnice baziliky sv. Ludmily, budeme se dotazovat následující URL:
https://
Pokud si tuto adresu nyní zadáme do webového prohlížeče, získáme následující výsledek:

open-meteo.com
do webového prohlížečeVidíme, že po zaškrtnutí naformátovaného výstupu jde o docela „sympatickou“ strukturu dat. Můžeme z těchto dat na první pohled vyčíst mnoho informací. Například: GPS souřadnice meteorologické stanice, která byla vyhodnocena jako nejbližší, tedy její data jsou pro naše místo prezentována – zde se (prý) jedná o nějakou stanici kdesi na Hlavním nádraží. Dále vidíme, že hodnoty teploty jsou zaslány ve stupních Celsia, že čas měření je uveden v GMT (ale to bychom mohli změnit zadáním parametru při dotazu). Taktéž zde vidíme, že hodnoty jsou aktualizovány jednou za 900 vteřin. Ale především vidíme, že aktuální teplota je –1,7 °C.
Pokud toto dokážeme vyčíst my, pojďme to naučit náš modul ESP32! Začneme tím, že program (podobně jako právě použitý webový prohlížeč) data načte a zobrazí. Pak se uvidí:
# pripojeni knihoven
import network
import time
import sys
try:
import ujson as json
except:
import json
try:
import usocket as socket
except:
import socket
# Nastavení Wi-Fi
ssid = "nazev-WiFi-site"
pwd = "Heslo-k-WiFi-siti"
# GPS souřadnice
latitude = 50.0754669 # Např. Bazilika sv. Ludmily (Praha)
longitude = 14.4371797
# Požadavek na API Open-Meteo
host = 'api.open-meteo.com'
path = f'/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m'
# --------------------------------------------------------
def exit_program():
print("Ukoncuji program...")
sys.exit(0)
connect_status = {
network.STAT_IDLE: "zadna aktivita", # STAT_IDLE - žádné připojení a žádná aktivita
network.STAT_CONNECTING: "pripojeni probiha", # STAT_CONNECTING - probíhá připojení
network.STAT_WRONG_PASSWORD: "chybne heslo", # STAT_WRONG_PASSWORD - selhalo z důvodu nesprávného hesla
network.STAT_NO_AP_FOUND: "AP nenalezen", # STAT_NO_AP_FOUND - selhalo, nebyl nalezen přístupový bod
network.STAT_GOT_IP: "Uspesne pripojeni" # STAT_GOT_IP - připojení se podařilo
}
# pripojeni k Wi-Fi
sta_if = network.WLAN(network.STA_IF) # nastaveni rezimu STA
sta_if.active(True) # zapnuti sitoveho pripojeni
if not sta_if.isconnected(): # neni-li ESP32 pripojen, zacne se pripojovat
sta_if.connect(ssid, pwd) # autorizace pristupu
print("Pripojuji se k Wi-Fi")
n = 0
while (not sta_if.isconnected()) and (n < 10): # cekaci smycka
n += 1 # timeout
print(".", end="")
time.sleep(1)
sta_ifstatus = sta_if.status()
print("\nVysledek pripojeni:", connect_status[sta_ifstatus])
if (sta_ifstatus == network.STAT_GOT_IP):
STAconf = sta_if.ifconfig() # nacteni informaci pripojeni
print("\nSTA-IP:\t\t", STAconf[0], "\nSTA-NETMASK:\t", STAconf[1], \
"\nSTA-GATEWAY:\t", STAconf[2], "\nSTA-DNS:\t", STAconf[3], sep="")
print("---")
else:
print("KONEC pripojovani!")
sta_if.active(False)
exit_program()
# --------------------------------------------------------
# klient, nacteni stranky z URL
url = f"http://{host}{path}"
print("Stahuji data z:", url)
# Vytvoření soketu a připojení k serveru
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, 80))
# Sestavení HTTP GET požadavku
request = f"GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
# Odeslání požadavku na server
sock.send(request.encode()) # převod do bytového řetězce
# Příjem odpovědi
response = b""
while True:
data = sock.recv(1024)
if not data:
break
response += data
# Uzavření soketu
sock.close()
sta_if.active(False) # ESP32 vypne Wi-Fi adaptér
# Rozdělení odpovědi na hlavičky a tělo
header, body = response.split(b'\r\n\r\n', 1)
body = body.decode('utf-8')
print(body) # vypis tělo zprávy, ať je vidět, co se bude zpracovávat
Připojení k Wi-Fi (už zase je něco jinak?)
První část programu již tradičně nastavuje modul ESP do režimu STA a připojuji jej Wi-Fi síti. Asi není třeba připomínat, že je třeba do programu zadat přístupové údaje do proměnných ssid
a pwd
. Abychom (asi již tradičně) i v této neustále se opakující části udělali vždy nějakou změnu, která nám ukáže něco nového z MicroPythonu, opět jsme tuto část kódu trochu změnili. V minulé verzi kódu připojování k Wi-Fi jsme v případě neúspěchu zastavili program nekonečnou smyčkou. Dnes jsme se rozhodli zastavit program definitivně. Pro tento účel jsme si definovali proceduru exit_program()
, která
využívá metody exit()
z knihovny sys
. Její zavolání s argumentem 0
ukončí celý program. Jen kvůli tomuto účelu je do programu přidána knihovna sys
.
Pozorný čtenář minulých článků by jistě věděl, jak z této knihovny importovat jen metody exit
samotnou. Že by zase nějaký domácí úkol? 😉
Mluvíme-li o knihovnách (nebo též, jak by se v Pythonu asi mělo spíše říkat: „modulech“) podívejte se na připojení knihoven ujson
a usocket
. Jak je to s některými knihovnami v Pythonu a MicroPythonu jsme si již
říkali, dokonce jsme si ukazovali přealiasování knihoven MicroPythonu, aby bylo možné kód psát pro Python a fungoval v MicroPythonu. Zatím jsme před nahráním programu do zařízení museli měnit tento import. Zde prezentovaný způsob je takový automatický způsob. Využili jsme pro něj konstrukci try
/except
. Jak to funguje? Pokusíme se v sekci try
aliasovat knihovnu MicroPythonu. Pokud program běží v MicroPythonu, dojde k přiřazení aliasu a knihovna běží pod pythonovským jménem. Je-li program spuštěn v klasickém Pythonu, dojde k chybě, takže se spustí sekce except
, ve které se importuje standardní pythonovská knihovna.
Klient připojení k API meteoserveru
Mnohem důležitější je druhá část programu, která odešle požadavek na meteoweb a zatím jen vypíše navrácenou odpověď. Pro vytvoření dotazu slouží následující řádky kódu:
# GPS souřadnice
latitude = 50.0754669 # Např. Bazilika sv. Ludmily (Praha)
longitude = 14.4371797
# Požadavek na API Open-Meteo
host = 'api.open-meteo.com'
path = f'/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m'
Proměnné latitude
a longitude
určují místo, na kterém budeme chtít určit teplotu. Tyto údaje budeme muset vložit do dotazovacího řetězce. Dále víme, že pro odeslání dotazu potřebujeme adresu serveru, zde uloženo v proměnné host
, a zbytek dotazu, který vytváříme v proměnné path
. U vytvoření řetězce dotazu v proměnné path
se trochu zastavíme. Asi nás trochu zarazí znak f
před úvodním apostrofem, stejně tak výrazy ve složených závorkách v řetězci. Zápis s f
před řetězcem je další z možností, jak složit řetězec z textů a proměnných. Ve své podstatě to říká, že výsledný řetězec bude složen z textu, ve kterém na místě složených závorek bude vložena hodnota proměnné, která je v těchto závorkách uzavřena. Např. je-li zde {latitude}
znamená to, že na toto místo bude vložena hodnota 50.0754669
. Výsledný řetězec tedy bude vypadat tak, jak potřebujeme: /v1/
atd.
Další část programu je v podstatě stejný webový klient, jako jsme používali v minulém článku.
Jsou zde ale dvě docela důležité změny:
- V naší cestě k univerzálnosti zde netušíme, jak dlouhá může být odpověď. U minulého načtení textového souboru by vlastně mohlo být jedno, kdybychom jej „usekli“ po 1024 znacích. Zde by to mohlo znamenat zásah do JSON kódu. Následné rozebrání useknutého JSON kódu by vyvolalo syntaktickou chybu. To nemluvě o tom, že naše požadovaná hodnota teploty je až na samém konci, takže bychom se o ni mohli tímto způsobem pěkně připravit. Je tedy třeba načíst odpověď celou.
Odpověď načítáme do proměnnéresponse
, která je na začátku definována jako prázdný bytový řetězec. Dílčí část odpovědi serveru načítáme po 1024 znacích do pomocné proměnné odpovědidata
, kterou pak připojujeme k proměnnéresponse
. Jakmile již není co načíst do proměnnédata
, tato proměnná získá hodnotuFalse
, čímž se cykluswhile
ukončí (příkazbreak
).
# Příjem odpovědi
response = b""
while True:
data = sock.recv(1024)
if not data:
break
response += data
- Přidání řádky
Connection: close
do odeslaného požadavku metodousend()
request = f"GET {path} HTTP/1.1\r\n
Host: {host} \r\nConnection: close \r\n\r\n"
Tato hlavička souvisí s tím, jak HTTP protokol zachází s připojeními a jak servery obvykle zacházejí s požadavky, které nemají explicitně uvedený hlavičkový řádekConnection: close
. Když v požadavku explicitně uvedeme hlavičkuConnection: close
, říkáme serveru, že po zpracování požadavku by měl server zavřít připojení. Server ví, že po odpovědi na tento požadavek může okamžitě ukončit spojení, čímž se uvolní prostředky a ušetří čas čekání na další data nebo žádosti. Pokud tuto hlavičku neuvedeme, server si může myslet, že máme v úmyslu udržet spojení otevřené pro více požadavků (což je v případě HTTP/1.1 běžná praxe). To znamená, že server si ponechá připojení otevřené pro další požadavky, které mohou následovat. Některé servery tak mohou zůstat „v očekávání“ dalšího požadavku (i když žádný už nepřijde) a nezavřou spojení, což může způsobit prodloužený čas čekání. Tento čas může být nečekaně dlouhý (několik sekund), což výrazně zpomalí celou odpověď.
Jakmile máme v proměnné response
načtenou celou odpověď serveru, vše běží, jak jsme zvyklí. Dojde k rozdělení odpovědi na hlavičky a na tělo. Ještě předtím, než část body vypíšeme na výstup, musíme ji převést na text. Na to jsme používali metodu decode()
. Tu použijeme i dnes, jen ji ještě doplníme o argument udávající požadovanou znakovou sadu.
body = body.decode('utf-8')
Nyní by v proměnné body
měl být textový řetězec v kódování UTF-8, který odpovídá textové podobě odpovědi zaslané API daného meteoserveru.
Pojďme se podívat, jak to bude vypadat. Obrázek č. 2 nám znázorňuje vypsání odpovědi serveru ve výstupu prostředí Thonny:

open-meteo.com
načtené modulem ESP32 (REPL prostředí Thonny)To, co nás asi zarazí, je hodnota 142 před strukturou JSON a 0 na konci. Bohužel přítomnost těchto hodnot totálně zničí JSON formát a jakýkoliv pokus o dekódování takového výstupu v tuto chvíli skončí chybou. Nejdříve jsme si mysleli, že je to nějaký problém, který vznikl při použití funkce decode
a převodu na UTF-8. Ale pokud si vypíšete kompletní odpověď serveru, tedy proměnnou response
, uvidíme ve výstupu, že hodnota (tentokráte!) 143 a závěrečná 0 je již tam přítomna (viz obrázek č. 3).

Ale, co teď s tím?
Nezbývá než obsah proměnné response
(resp. body
) trochu „očistit“ do podoby formátu JSON.
- AKTUALIZACE: (20. 2. 2025)
- Nejdříve jsme vůbec nechápali, kde se v odpovědi meteoserveru objevila na začátku hodnota 142 a na konci 0. Pak jsme se dozvěděli, že za to může hlavička
Transfer-Encoding: chunked
. - Pokud server nezná předem velikost těla odpovědi (například při generování dynamického obsahu, což je přesně tento případ), může použít metodu „chunked transfer encoding“, což znamená, že tělo odpovědi bude posíláno po částech ve formě částí (tzv. „chunků“). První číslo (142) pak značí délku prvního „chunku“, 0 pak označuje konec těla přenosu.
- Knihovna
usocket
v MicroPythonu je relativně nízkoúrovňová a neobsahuje přímou podporu pro dekódováníTransfer-Encoding: chunked
, jak to dělají vyšší knihovny (napříkladurequests
– viz dále). Jakmile tedy server odpoví sTransfer-Encoding: chunked
, musíme dekódovat odpověď ručně. - Poznámka:
- V případě, že chcete zajistit (spíše „poprosit“), aby server odeslal odpověď bez použití
Transfer-Encoding: chunked
, místo toho použilContent-Length:
(tělo odpovědi přeneseno jedním blokem), můžete to provést zasláním hlavičkyConnection: close
. Tato hlavička naznačuje serveru, že po odeslání odpovědi by měl uzavřít spojení. Některé (!) servery pak (prý) nepoužijíTransfer-Encoding: chunked
. - ALE: Zde jsme
Connection: close
v requestu použili, a vidíme, že nám to stejně nebylo nic platné! 😡
Získání „čistého“ JSON formátu.
Díky této patálii musíme k našemu kódu doplnit následující část kódu. Vysvětlíme si jej až za ukázkou kódu:
# Očistíme odpověď: odstraníme čísla na začátku a na konci
# 1. Najdeme první výskyt "{" a poslední výskyt "}"
start_index = body.find("{")
end_index = body.rfind("}")
# Pokud nalezneme validní JSON, extrahujeme ho
if ((start_index != -1) and (end_index != -1)):
json_str = body[start_index:end_index+1]
else:
print("Chyba: Není možné najít platný JSON.")
json_str = ""
Možná nám už komentáře v kódu naznačily, co se zde děje. Je třeba v textovém řetězci najít první výskyt znaku {
, který určuje začátek JSON formátu. K tomu využíváme řetězcové metody find()
s argumentem hledaného znaku.
start_index = body.find("{")
Obdobně potřebujeme najít poslední výskyt znaku }
, který naopak strukturu JSON uzavírá. Také najdeme první výskyt tohoto znaku, ale tentokráte od konce. Použijeme tedy metodu rfind()
, která hledá v řetězci zprava.
Následuje vyříznutí správného úseku textu. Na to použijeme následující příkaz:
json_str = body[start_index:end_index+1]
Zápis „vyřezávacího intervalu“ nás svou podobou asi trochu přenáší do Excelu, kde se také dvojtečkou zadává nějaký rozsah. Tento zápis tedy říká: „Vyřízni z proměnné body
vše od prvního výskytu {
do posledního výskytu }
. A přidej jeden znak, ať se tam vejde i ten znak }
“.
Celé to je ovšem ještě vnořeno do podmínky, která testuje, zda vůbec taková operace může proběhnout.
if ((start_index != -1) and (end_index != -1)):
Pokud by se nepodařilo ve výchozím textu najít některý ze znaků {}
, byla by do proměnných jejich umístění (start_index
, end_index
) dosazena návratová hodnota –1 a je jasné, že z textu validní JSON nedostaneme. V tomto případě je výstupní proměnná json_str
prázdná, jinak obsahuje právě získaný JSON kód.
Jakmile máme v proměnné json_str
čistý JSON formát, můžeme jej převést na pythonovsky zpracovatelný formát. Následující část kódu ukazuje jak na to:
# Hledání teploty v těle odpovědi
if json_str:
try:
data = json.loads(json_str)
if (("current" in data) and ("temperature_2m" in data["current"])):
temp = data["current"]["temperature_2m"]
print(f"\nAktuální teplota: {temp} °C")
else:
print("Chyba: Nepodařilo se získat teplotu.")
except Exception as e:
print("Chyba při stahování dat:", e)
Převod z JSON probíhá pochopitelně, jen pokud máme v proměnné json_str
„cosi“ uzavřeného ve složených závorkách. Proto je celá část vnořena do podmínky testující neprázdnost této proměnné.
Zajímavá je asi jen podoba podmínky: (pochopitelně by bylo možné to zapsat mnohem konvenčnějším způsobem)
if json_str:
Nás však zajímá metoda loads()
knihovny json
(resp. ujson
):
data = json.loads(json_str)
Tímto příkazem jsem dosáhli svého – máme kompletní strukturu JSON převedenou do struktury proměnné data
. K jednotlivým položkám můžeme přistupovat jako k položkám pole, kde místo indexů budeme používat klíčová slova struktury JSON.
Abychom trochu ošetřili případný neúspěch při hledání teploty ve struktuře JSON, je zde podmínka, která testuje dvě základní věci:
("current" in data)
– zde ve struktuředata
existuje klíčcurrent
.("temperature_2m" in data["current"])
– zda v podstruktuřecurrent
existuje klíčtemperature_2m
Tato podmínka slouží k určitému ověření integrity dat, která jsme načetli z meteowebu, než se pokusíme přistupovat k hodnotě teploty.
- Bez této kontroly by došlo k chybě, pokud by server vrátil nějaké neočekávané nebo chybějící údaje.
- Takto můžeme bezpečně ověřit, že data, která používáme, jsou kompletní a správná.
Například pokud by server vrátil data bez teploty, kód by se vyhnul chybě typu KeyError
a místo toho vypíše zprávu, že teplota není k dispozici. Tímto způsobem můžeme elegantně ošetřit možné problémy s neúplnými nebo chybějícími daty.
Přítomnost neznámých znaků před strukturou JSON nás možná zbytečně donutila k tomu, abychom byli tak trochu ve střehu. Samotný převod z JSON je pro jistotu vsazen do struktury try
/except
. Takže kdyby se cokoliv při převodu pokazilo, měli bychom na to umět zareagovat aspoň touto obecnou výjimkou. Protože zrovna v případě čísla 142 (či nuly na konci) by byl okamžikem, který samotná předchozí podmínka podchytit nedokázala a převod z JSON by se nedařil.
Všimněme si ale sekce except
:
except Exception as e:
print("Chyba při stahování dat:", e)
Výraz Exception as e
přiřadí do proměnné e
chybu, která blok except
vyvolala. Následné vypsání proměnné e
nám pak naznačí, k jakému problému došlo. To je vcelku zajímavé rozšíření bloku except
, že?
Pokud máme tedy vše ošetřené a vyřešené, lze přistoupit k načtení teploty:
temp = data["current"]["temperature_2m"]
K vypsání právě získané hodnoty teploty opět použijeme dnešní trik se znakem f
před řetězcem:
print(f"\nAktuální teplota: {temp} °C")
Pojďme se podívat, jak bude funkčnost výsledná funkčnost programu vypadat:

open-meteo.com
Jen poznamenejme, že pokud z programu odstraníme vypsání proměnné body
, zmizí z výstupu to vypsání JSON struktury i se „záhadnými“ čísly 142 a 0. 😊
Struktura programu
Ještě než si zde uvedeme celý kód programu, shrneme jeho základní strukturu:
- Wi-Fi připojení:
- Tato část kódu zůstává relativně nezměněná vůči předchozím programům zabývajícími se Wi-Fi, protože připojení k Wi-Fi zůstává stále stejné.
- Socketové připojení:
- Používáme
socket
pro vytvoření TCP socketu, který se připojí k serveruapi.open-meteo.com
na portu 80 (HTTP). - Sestavujeme HTTP GET požadavek jako textovou zprávu.
- Odesíláme požadavek přes socket, následně přijímáme odpověď po kouskách (1024 bytů) a tvoříme celou odpověď.
- Používáme
- Zpracování odpovědi:
- Odpověď je dekódována z bytového řetězce do textu.
- Po získání odpovědi hledáme v textu hlavičku a tělo odpovědi oddělené sekvencí
\r\n\r\n
. - Z odpovědi je vyříznuta čistá JSON struktura.
- Získaný text s JSON zápisem je následně zpracován jako JSON pomocí
loads()
a teplota je extrahována z příslušné části JSON objektu.
- Chybové zpracování:
- Pokud se nepodaří získat teplotu nebo dojde k chybě při zpracování odpovědi, bude vypsána chybová zpráva.
Kód programu:
# pripojeni knihoven
import network
import time
import sys
try:
import ujson as json
except:
import json
try:
import usocket as socket
except:
import socket
# Nastavení Wi-Fi
ssid = "nazev-WiFi-site"
pwd = "Heslo-k-WiFi-siti"
# GPS souřadnice
latitude = 50.0754669 # Např. Bazilika sv. Ludmily (Praha)
longitude = 14.4371797
# Požadavek na API Open-Meteo
host = 'api.open-meteo.com'
path = f'/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m'
# --------------------------------------------------------
def exit_program():
print("Ukoncuji program...")
sys.exit(0)
connect_status = {
network.STAT_IDLE: "zadna aktivita", # STAT_IDLE - žádné připojení a žádná aktivita
network.STAT_CONNECTING: "pripojeni probiha", # STAT_CONNECTING - probíhá připojení
network.STAT_WRONG_PASSWORD: "chybne heslo", # STAT_WRONG_PASSWORD - selhalo z důvodu nesprávného hesla
network.STAT_NO_AP_FOUND: "AP nenalezen", # STAT_NO_AP_FOUND - selhalo, nebyl nalezen přístupový bod
network.STAT_GOT_IP: "Uspesne pripojeni" # STAT_GOT_IP - připojení se podařilo
}
# pripojeni k Wi-Fi
sta_if = network.WLAN(network.STA_IF) # nastaveni rezimu STA
sta_if.active(True) # zapnuti sitoveho pripojeni
if not sta_if.isconnected(): # neni-li ESP32 pripojen, zacne se pripojovat
sta_if.connect(ssid, pwd) # autorizace pristupu
print("Pripojuji se k Wi-Fi")
n = 0
while (not sta_if.isconnected()) and (n < 10): # cekaci smycka
n += 1 # timeout
print(".", end="")
time.sleep(1)
sta_ifstatus = sta_if.status()
print("\nVysledek pripojeni:", connect_status[sta_ifstatus])
if (sta_ifstatus == network.STAT_GOT_IP):
STAconf = sta_if.ifconfig() # nacteni informaci pripojeni
print("\nSTA-IP:\t\t", STAconf[0], "\nSTA-NETMASK:\t", STAconf[1], \
"\nSTA-GATEWAY:\t", STAconf[2], "\nSTA-DNS:\t", STAconf[3], sep="")
print("---")
else:
print("KONEC pripojovani!")
sta_if.active(False)
exit_program()
# --------------------------------------------------------
# klient, nacteni stranky z URL
url = f"http://{host}{path}"
print("Stahuji data z:", url)
# Vytvoření soketu a připojení k serveru
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, 80))
# Sestavení HTTP GET požadavku
request = f"GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
# Odeslání požadavku na server
sock.send(request.encode()) # převod do bytového řetězce
# Příjem odpovědi
response = b""
while True:
data = sock.recv(1024)
if not data:
break
response += data
# Uzavření soketu
sock.close()
sta_if.active(False) # ESP32 vypne Wi-Fi adaptér
# Rozdělení odpovědi na hlavičky a tělo
header, body = response.split(b'\r\n\r\n', 1)
body = body.decode('utf-8')
print(body) # vypis tělo zprávy, ať je vidět, co se bude zpracovávat
# Očistíme odpověď: odstraníme čísla na začátku a na konci
# 1. Najdeme první výskyt "{" a poslední výskyt "}"
start_index = body.find("{")
end_index = body.rfind("}")
# Pokud nalezneme validní JSON, extrahujeme ho
if ((start_index != -1) and (end_index != -1)):
json_str = body[start_index:end_index+1]
else:
print("Chyba: Není možné najít platný JSON.")
json_str = ""
# Hledání teploty v těle odpovědi
if json_str:
try:
data = json.loads(json_str)
if (("current" in data) and ("temperature_2m" in data["current"])):
temp = data["current"]["temperature_2m"]
print(f"\nAktuální teplota: {temp} °C")
else:
print("Chyba: Nepodařilo se získat teplotu.")
except Exception as e:
print("Chyba při stahování dat:", e)
Jiná verze programu (použití modulu 'urequests')
Na samém konci minulého článku jsme naznačili, že by šlo webového klienta vytvořit i trochu jednodušeji. Získání určité „syrové“ odpovědi od serveru ve formě textu, její rozdělení na hlavičky a tělo… to vše je takové nízko úrovňové. Copak neexistuje způsob, který by fungoval trochu jako webový prohlížeč – zadal adresu a vrátila by se odpověď obsahu stránky. Jak vůbec ošetřit případné problémy při nezdaru připojení? A tak dále…
Naštěstí ano! Je tady knihovna requests
(resp. v MicroPythonu urequests
). Tato knihovna výrazným
způsobem zjednodušuje práci s webovou komunikací. Kupříkladu za nás navazuje sockety nebo, když budeme chtít, vrací jen textový obsah těla zprávy. Zajímavé je pro nás i to, že tento textový obsah těla zprávy již bude zbaven záhadných hodnot. Kód našeho klienta dotazujícího se na API meteowebu open-meteo.com
bude rázem výrazně kratší.
Uvedeme si zde jen kód našeho meteoklienta s užitím requests
(resp. urequests
). Připojení k Wi-Fi síti je stejné jako v předchozím příkladu:
# pripojeni knihoven
import network
import time
import sys
try:
import ujson as json
except:
import json
try:
import urequests as requests
except:
import requests
# Nastavení Wi-Fi
ssid = "nazev-WiFi-site"
pwd = "Heslo-k-WiFi-siti"
# GPS souřadnice
latitude = 50.0754669 # Např. Bazilika sv. Ludmily (Praha)
longitude = 14.4371797
# Požadavek na API Open-Meteo
url = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m"
# --------------------------------------------------------
def exit_program():
print("Ukoncuji program...")
sys.exit(0)
connect_status = {
network.STAT_IDLE: "zadna aktivita", # STAT_IDLE - žádné připojení a žádná aktivita
network.STAT_CONNECTING: "pripojeni probiha", # STAT_CONNECTING - probíhá připojení
network.STAT_WRONG_PASSWORD: "chybne heslo", # STAT_WRONG_PASSWORD - selhalo z důvodu nesprávného hesla
network.STAT_NO_AP_FOUND: "AP nenalezen", # STAT_NO_AP_FOUND - selhalo, nebyl nalezen přístupový bod
network.STAT_GOT_IP: "Uspesne pripojeni" # STAT_GOT_IP - připojení se podařilo
}
# pripojeni k Wi-Fi
sta_if = network.WLAN(network.STA_IF) # nastaveni rezimu STA
sta_if.active(True) # zapnuti sitoveho pripojeni
if not sta_if.isconnected(): # neni-li ESP32 pripojen, zacne se pripojovat
sta_if.connect(ssid, pwd) # autorizace pristupu
print("Pripojuji se k Wi-Fi")
n = 0
while (not sta_if.isconnected()) and (n < 10): # cekaci smycka
n += 1 # timeout
print(".", end="")
time.sleep(1)
sta_ifstatus = sta_if.status()
print("\nVysledek pripojeni:", connect_status[sta_ifstatus])
if (sta_ifstatus == network.STAT_GOT_IP):
STAconf = sta_if.ifconfig() # nacteni informaci pripojeni
print("\nSTA-IP:\t\t", STAconf[0], "\nSTA-NETMASK:\t", STAconf[1], \
"\nSTA-GATEWAY:\t", STAconf[2], "\nSTA-DNS:\t", STAconf[3], sep="")
print("---")
else:
print("KONEC pripojovani!")
sta_if.active(False)
exit_program()
# --------------------------------------------------------
# Požadavek na API Open-Meteo
print("Stahuji data z:", url)
try:
response = requests.get(url) # Odeslání GET požadavku na server Open-Meteo
print(response.text) # jen aby bylo možné vidět, co se bude dále zpracovávat
data = json.loads(response.text) # Převod z formátu JSON do Python objektu
response.close() # Uzavření odpovědi, aby se uvolnily prostředky
sta_if.active(False) # ESP32 vypne Wi-Fi adaptér
if (("current" in data) and ("temperature_2m" in data["current"])): # Kontrola existence klíčů
temp = data["current"]["temperature_2m"] # extrahujeme hodnotu teploty
print(f"\nAktuální teplota: {temp} °C") # Vytiskneme aktuální teplotu
else:
print("Chyba: Nepodařilo se získat teplotu.") # nepodařilo se získat teplotu
except Exception as e:
print("Chyba při stahování dat:", e) # došlo k jakékoliv chybě (např. připojením nebo dekódováním dat)
Část kódu týkající se klienta je okomentována, ale i přesto se na jednotlivé části podíváme.
První změnou je, že nyní potřebujeme znát celou URL našeho požadavku, tedy se to celé bude tak trochu chovat jako webový prohlížeč – zadáme adresu a hotovo! Žádné host
ani path
!
url = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m"
Vytvoření URL dotazu pomocí zápisu s počátečním f
pro vložení proměnných latitude
a longitude
už známe.
Následuje odeslání GET požadavku:
response = requests.get(url)
Návratový objekt response
obsahuje všechno možné, ale nás v tuto chvíli zajímá jen text těla odpovědi.
Protože v tuto chvíli je odpovědí čistý JSON kód, můžeme jej hned převést z JSON do pythonovské struktury:
data = json.loads(response.text)
Předpokládáme-li, že vše proběhlo bez problémů (Ano, problémy mohou nastat vždy, tak jsme to celé opět vnořit do struktury try
/except
!), můžeme otestovat existenci klíčů a vypsat teplotu:
if (("current" in data) and ("temperature_2m" in data["current"])):
temp = data["current"]["temperature_2m"]
print(f"\nAktuální teplota: {temp} °C")
else:
print("Chyba: Nepodařilo se získat teplotu.")
A je hotovo!
Jo… a všimněte si, že knihovna urequest
se umí vypořádat i s Transfer-Encoding: chunked
!

urequests
Nakonec nejdelší částí celého programu je to úvodní připojení k Wi-Fi. Možná bychom se do budoucna mohli zamyslet nad tím, zda bychom to přihlašování k Wi-Fi nemohli „zaparkovat“ do nějaké jiné (samostatné?) části projektu. Tak trochu z tohoto vytvořit knihovnu/modul, abychom se v hlavní části programu mohli zabývat tím důležitým.
Závěrem
Úplně nevím, zda závěrečné použití vcelku efektivní (a efektní!) knihovny urequests
příliš nerozběsnilo čtenáře, který nyní do obrazovky ječí, proč jsme to rovnou neřekli na samém začátku. Ano, možná jsme si mohli ušetřit povídání o socketech,
postupném načítání odpovědi a její rozdělování na hlavičky a tělo. O očišťování JSON kódu z textu odpovědi zamořeném „podivnými“ čísly ani nemluvě! Ale upřímně, co bychom se v takovém případě naučili? 😜 (Tady máte knihovnu a hrajte si!)
No, snad nám tento „podlý výukový trik“ do příště odpustíte.
Bohužel, v současné době bůhví, kdy zase „to příště“ bude!