6.2 Upravljanje LED-icom preko WiFija
U 6.1 Povezivanje i dohvat podataka pločica je bila ta koja pita: pridružila se tvojoj Wi-Fi mreži, zatražila stranicu s web sjedišta i ispisala odgovor. Ovaj primjer to prevrće. Pločica je sada ona koju se pita. Postaje mali web server za sebe, a stranica koju otvoriš na mobitelu ili računalu pali i gasi pravu LED-icu.
Ništa oko same LED-ice nije novo. To je ista LED-ica, isti otpornik od 330 Ω i isti value() koje koristiš od 1.2 Blinkanje LED-ice. Novo je odakle uputa dolazi. Umjesto da je unaprijed upisana u skriptu, dolazi preko mreže onog trenutka kada netko pritisne tipku.
U ovoj dokumentaciji naučit ćeš:
- Razliku između klijenta i servera, i zašto je ovaj primjer obrnut od 6.1.
- Kako izgraditi web server iz sirovog socketa, jer MicroPython nema gotov.
- Kako iz zahtjeva pročitati traženu adresu i sam odraditi usmjeravanje (routing).
- Što su linije zaglavlja HTTP odgovora, i zašto ih mora slijediti prazna linija.
- Zašto te
SO_REUSEADDRspašava od greške svaki put kada ponovno pokreneš skriptu. - Kako nekoliko linija JavaScripta unutar stranice same drže liniju statusa svježom.
Potreban hardver:
- 1x Soldered NULA MINI pločica
- 1x Breadboard
- 1x LED-ica (bilo koje boje)
- 1x otpornik od 330 Ω
- 3x Jumper žice
- 1x USB-C kabel
- Wi-Fi mreža, i mobitel ili računalo pridruženo njoj
Spajanje komponenti
IO4, a to je pin koji ovom primjeru treba. Na a–e strani taj isti red 27 je IO19, potpuno drugi pin. Nisu spojeni međusobno. Broji svoje redove na f–j strani, onoj gdje silkscreen piše IO2 IO3 IO4 IO5 3V3 GND.1. Umetni NULA MINI pločicu na breadboard
Utisni pločicu u jedan kraj breadboarda tako da njezina dva reda pinova sjednu s obje strane središnjeg kanala, s čipom okrenutim prema dolje. Zauzet će redove 25 do 30.

2. Izvedi IO4 u slobodan red
IO4 je pin koji će pobuđivati LED-icu, a pločica prekriva cijeli red 27 osim krajnje rupice. Zato prva žica samo nosi taj pin nekamo gdje možeš raditi: jumper žica iz j27 duž do j19.

3. Dodaj otpornik od 330 Ω
Otpornik je sljedeći član lanca. Postavi ga tako da premosti red 19, red u koji je jumper žica upravo došla, i red 14, oba na f–j strani.

j19 a otpornik u i19, različite rupice u istom redu, pa su već spojeni. Odaberi onu rupicu do koje nožice tvoje komponente ugodno dosežu.4. Dodaj LED-icu
LED-ica nastavlja lanac: njezina duga nožica (anoda) u red 14, isti red u kojem otpornik završava, i njezina kratka nožica (katoda) u red 12.

5. Spoji LED-icu i pločicu na masu
Dvije jumper žice zaokružuju krug, i obje idu na plavu − vodilicu, koja djeluje kao dijeljena linija mase:
- Iz reda 12, gdje sjedi kratka nožica LED-ice, do plave − vodilice.
- Iz j30 (
GND) do te iste vodilice.

Krug je sada jedan neprekinut lanac: IO4 → red 19 → otpornik → red 14 → LED-ica → red 12 → − vodilica → GND. Struja može teći samo kada skripta dovede IO4 u visoko stanje, a to je točno ono što će tipka ON napraviti.
6. Spoji pločicu na računalo
Utisni USB-C kabel u pločicu i u računalo. PWR LED-ica na pločici odmah se upali, ali zelena LED-ica na breadboardu ostaje ugašena. Skripta je namjerno gasi pri pokretanju, tako da se stranica i hardver poklapaju od prvog trenutka.

Kako pločica postaje web server
U 6.1 pločica je bila klijent. Odabrala je web sjedište, zatražila stranicu i čekala odgovor. Sve je počelo od pločice.
Ovdje je server, što znači da radi obrnuto: sjedi i čeka, a razgovor pokreće nešto drugo. Kada u preglednik (engl. browser) upišeš adresu pločice, preglednik pošalje zahtjev a pločica odgovori. Pločica nikada ne odlučuje kada se to dogodi. Tvoj prst na tipki ON odlučuje.
Nema modula za web server, pa ga gradimo
To je najveća razlika u odnosu na Arduino verziju ovog primjera. Arduino skica uključi WebServer.h, stvori WebServer server(80), registrira funkciju obrađivača za svaku adresu pomoću server.on(), i pusti biblioteku da obavi ostalo.
MicroPython ne dolazi s takvom bibliotekom. Ono što ti umjesto toga daje je socket, sirovi mehanizam ispod svega, a web server izgrađen iz njega je oko dvadeset linija tvog vlastitog koda. To zvuči kao pogoršanje, i po udobnosti jest, ali znači da ništa nije skriveno. Svaka linija razgovora između preglednika i pločice je nešto što si ti napisao.
Otvaranje vrata
Serveru treba port, koji možeš zamisliti kao numerirana vrata na pločici. Port 80 su standardna vrata za web stranice, i zato možeš jednostavno upisati adresu a da nakon nje ništa ne dodaš. Preglednici po zadanom već pokušavaju vrata 80.
Ta vrata otvaraju četiri poziva:
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("", 80))
server.listen(2)
socket() ga stvara, bind() ga vezuje na port 80 na svakom mrežnom sučelju koje pločica ima, a listen(2) počinje prihvaćati veze, držeći do dvije na čekanju u redu.
setsockopt s SO_REUSEADDR je linija koja će ti nedostajati kada je nema. Port ostaje zauzet dokle god postoji socket koji ga je zauzeo. Uljudno zaustavljanje skripte izvršava except KeyboardInterrupt blok na dnu, koji poziva server.close() i vraća port, ali skripta koja je odsječena prije nego dođe do toga ostavlja svoj socket za sobom, još držeći port 80, i on ne istječe i ne pušta ga. Bez ove linije sljedeće pokretanje tada odmah padne s OSError: [Errno 112] EADDRINUSE, a čekanje ne pomaže; samo zatvaranje tog socketa ili resetiranje pločice oslobađa port. S ovom linijom bind() ipak uspije. Košta jednu liniju a poštedi te resetiranja gotovo svaki put kada urediš i ponovno pokreneš.Čekanje, pa čitanje zahtjeva
connection, address = server.accept()
request = connection.recv(1024).decode()
accept() čeka dok se preglednik ne poveže. Vraća dvije stvari: vezu preko koje se razgovara i adresu onoga koji se povezao. recv(1024) zatim čita do 1024 znaka onoga što su poslali, a decode() te sirove bajtove pretvara u tekst.
accept() blokira. Skripta se zaustavi na toj liniji i čeka, ponekad minutama, dok zahtjev ne dođe. Zato petlja izgleda toliko drugačije od Arduino verzije, gdje se server.handleClient() vraća odmah, bilo da je nešto čekalo ili ne. Ovdje je pločica stvarno parkirana na toj liniji, što je za ovaj primjer u redu, jer joj je odgovaranje na stranicu jedini posao.Sam odrađuješ usmjeravanje
Prva linija svakog HTTP zahtjeva izgleda ovako:
GET /led/on HTTP/1.1
Tri dijela odvojena razmacima: metoda, adresa i verzija protokola. Skripta izvlači srednji:
first_line = request.split("\r\n")[0]
parts = first_line.split(" ")
path = parts[1] if len(parts) > 1 else "/"
split("\r\n")[0] uzima prvu liniju zahtjeva, split(" ") je lomi na njezina tri dijela, a [1] je adresa. if len(parts) > 1 else "/" na kraju je sigurnosna mreža za neispravno oblikovan zahtjev koji nema drugi dio, a koji bi inače srušio server.
Lanac if i elif zatim odlučuje što svaka adresa znači. To se naziva usmjeravanje (routing), i to je ista lista koju Arduino obrađivači implementiraju, samo ispisana na otvorenom:
| Adresa | Što skripta radi |
|---|---|
/led/on | dovodi IO4 u visoko stanje i odgovara on |
/led/off | dovodi IO4 u nisko stanje i odgovara off |
/led/status | čita IO4 i prijavljuje njegovo trenutno stanje |
sve drugo, uključujući / | šalje samu web stranicu |
"on" if led.value() else "off" je uvjetni izraz: uzima prvu vrijednost kada je uvjet istinit i drugu kada je lažan, sve u jednoj liniji. I primijeti što čita: led.value() na izlaznom pinu čita natrag ono što pin trenutno piše, pa je status pravo mjerenje pravog pina, a ne varijabla za koju se skripta nada da je još točna.Ručno odgovaranje
HTTP odgovor nije samo sadržaj. Počinje s nekoliko linija koje ga opisuju, a skripta ih piše sama:
connection.send("HTTP/1.1 200 OK\r\n")
connection.send("Content-Type: " + content_type + "\r\n")
connection.send("Connection: close\r\n\r\n")
connection.sendall(body)
Prva linija nosi kod odgovora. 200 OK je "izvoli", isti onaj broj koji je 6.1 bio zadovoljan primiti. Content-Type govori pregledniku dobiva li web stranicu (text/html) ili čisti tekst (text/plain).
\r\n\r\n na kraju posljednjeg zaglavlja radi pravi posao. Prazna linija je ono što razdvaja zaglavlja od sadržaja, a preglednik nastavlja čitati zaglavlja dok ne nađe jednu. Pošalji tamo samo \r\n i preglednik početak tvog HTML-a tretira kao još zaglavlja, i stranica se nikada ne pojavi.Zatim:
connection.close()
Ovo završava taj jedan razgovor. Bez toga preglednik nastavlja čekati ostatak odgovora koji nikada ne dolazi, a pločici polako nestaje memorije.
Stranica živi unutar skripte
Cijela web stranica spremljena je u skripti kao jedan dugačak dio teksta, omotan u trostruke navodnike. To je Python koji dopušta da se string proteže kroz mnogo linija i sadrži pojedinačne navodnike bez znaka za izbjegavanje pred svakim. To je isti posao koji u Arduino skici radi R"rawliteral( ... )rawliteral".
Stranica je HTML, isti jezik koji nam je web sjedište u 6.1 poslalo natrag. Sada je pločica ta koja ga šalje.
Unutar stranice je kratak dio JavaScripta, koji se izvršava u pregledniku a ne na pločici. On od /led/status traži trenutno stanje i upisuje ga u liniju statusa, pa to ponavlja svake dvije sekunde:
updateStatus();
setInterval(updateStatus, 2000);
Zato linija statusa ostaje iskrena i ako netko drugi pritisne tipku na drugom mobitelu, a i zato može zaostajati: stranica može biti do dvije sekunde iza LED-ice.
nulamini.local umjesto IP adrese
try:
network.hostname(HOSTNAME)
except Exception:
print("Setting the hostname is not supported on this firmware, use the IP address instead.")
network.hostname() zauzima ime na tvojoj lokalnoj mreži, tako da pločica može odgovarati na http://nulamini.local/ umjesto na red brojeva. Omotano je u try i except jer tu funkciju ne nudi svaki MicroPython build, a funkcija koje nema ne bi trebala zaustaviti cijeli primjer.
nulamini.local ne otvori, koristi IP adresu. Razrješavanje imena na lokalnoj mreži nije podržano svugdje. Windows 10 i 11, macOS i iOS svi razrješavaju .local imena bez ikakvog namještanja, ali neke verzije Androida i neki routeri ne. Točno zato skripta ispisuje IP address:. Upiši te brojeve u svoj preglednik i doći ćeš na istu stranicu. Ime je udobnost, a ne zahtjev.Čisto zaustavljanje
Cijela petlja sjedi unutar try bloka koji hvata KeyboardInterrupt:
except KeyboardInterrupt:
print("Server stopped")
server.close()
KeyboardInterrupt je ono što pritisak na Stop u tvom editoru, ili Ctrl+C u terminalu, diže unutar skripte. Njegovo hvatanje daje skripti trenutak da zatvori socket, što oslobađa port za sljedeće pokretanje umjesto da ga ostavi zauzetim.
Postavljanje Wi-Fi podataka
Prije pokretanja skripte promijeni ove dvije linije da odgovaraju tvojoj mreži:
ssid = "your ssid"
password = "your password"
Kod
Ispod je cijeli kod za ovaj primjer:
# Modul network sadrži sve što je potrebno za pridruživanje Wi-Fi mreži.
import network
# Modul socket je najniža razina mrežne komunikacije: dopušta nam da slušamo druge uređaje koji nam žele
# razgovarati. MicroPython nema gotov web server, pa sami gradimo mali iz socketa.
import socket
# Pin upravlja LED-icom, a time nam daje pauze koje trebamo dok se povezujemo.
from machine import Pin
import time
# Ove dvije varijable drže ime tvoje Wi-Fi mreže (SSID) i njezinu lozinku. Tvoj mobitel ili računalo mora
# biti na istoj mreži kao pločica, inače neće moći doći do stranice.
ssid = "your ssid"
password = "your password"
# Ovo je varijabla kojoj predajemo broj pina na koji smo spojili LED-icu.
# Zapamti da LED-ici treba otpornik od 330 ohma u seriji s njom.
LED_PIN = 4
# Ovo je ime koje će pločica pokušati zauzeti na tvojoj mreži. Ako ga tvoja mreža podržava, onda u pregledniku možeš
# otvoriti http://nulamini.local/ umjesto da moraš pamtiti adresu sastavljenu od brojeva.
HOSTNAME = "nulamini"
# Ovo je sama web stranica, napisana u HTML-u i spremljena kao jedan dugačak dio teksta. Tri navodnika dopuštaju nam
# da napišemo mnogo linija teksta odjednom, uključujući pojedinačne navodnike, a da svaki od njih ne izbjegavamo.
# Stranica sadrži dvije tipke i mali dio JavaScripta, koda koji se izvršava unutar preglednika i koji od pločice traži
# trenutno stanje LED-ice svake dvije sekunde, pa prikazani status sam ostaje svjež.
HTML_PAGE = """<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NULA MINI LED Control</title>
<style>
body { font-family: Arial; text-align: center; margin-top: 50px; }
button { padding: 15px 30px; margin: 10px; font-size: 20px; }
.status { font-size: 24px; margin-top: 20px; }
</style>
</head>
<body>
<h1>NULA MINI LED Control</h1>
<button onclick="fetch('/led/on').then(()=>updateStatus())">ON</button>
<button onclick="fetch('/led/off').then(()=>updateStatus())">OFF</button>
<div class="status" id="status">Loading status...</div>
<script>
// Function that requests LED status from the NULA MINI and updates the page
async function updateStatus() {
let res = await fetch('/led/status');
let text = await res.text();
document.getElementById('status').innerHTML = 'LED is ' + text.toUpperCase();
}
// Run immediately after page load and update every 2 seconds
updateStatus();
setInterval(updateStatus, 2000);
</script>
</body>
</html>"""
# Ovdje stvaramo naš Pin objekt za LED-icu i odmah u njega pišemo 0, tako da LED-ica počne ugašena
# a status prikazan na stranici odgovara stvarnosti.
led = Pin(LED_PIN, Pin.OUT)
led.value(0)
# Ovdje stvaramo naš mrežni objekt i uključujemo Wi-Fi hardver. network.STA_IF znači da se pločica pridružuje tuđoj
# mreži, na isti način kao tvoj mobitel.
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Ovdje zauzimamo naše ime na mreži. Omotavamo to u try blok jer ne svaki build firmwarea nudi ovu
# funkciju, a funkcija koje nema ne bi trebala zaustaviti cijeli primjer.
try:
network.hostname(HOSTNAME)
except Exception:
print("Setting the hostname is not supported on this firmware, use the IP address instead.")
# connect() pokreće pokušaj povezivanja. Ne čeka da se povezivanje dovrši, pa na njega čekamo
# sami u petlji ispod. isconnected() nam kaže jesmo li već na mreži.
print("Connecting to WiFi", end="")
wlan.connect(ssid, password)
while not wlan.isconnected():
time.sleep(0.5)
print(".", end="")
# ifconfig() kao svoju prvu vrijednost vraća adresu koju je router dodijelio našoj pločici. Upisivanje te adrese u
# preglednik na istoj mreži otvara stranicu koju smo pripremili iznad.
print()
print("WiFi connected!")
print("IP address:", wlan.ifconfig()[0])
print("Access the board in your browser at: http://" + HOSTNAME + ".local/")
def send_response(connection, content_type, body):
# Ovo je funkcija koju smo napisali sami. Odgovara na jedan zahtjev.
# Svaki HTTP odgovor počinje s nekoliko linija koje ga opisuju prije samog sadržaja: kod odgovora, gdje
# 200 znači "izvoli", i tip sadržaja koji šaljemo. Prazna linija zatim razdvaja te linije
# od sadržaja.
connection.send("HTTP/1.1 200 OK\r\n")
connection.send("Content-Type: " + content_type + "\r\n")
connection.send("Connection: close\r\n\r\n")
connection.sendall(body)
# Ovdje otvaramo vrata i počinjemo slušati. socket() stvara socket, setsockopt() nam dopušta da ponovno koristimo isti
# port odmah kada ponovno pokrenemo program umjesto da čekamo da ga sustav otpusti, bind() nas vezuje
# na port 80, a listen() počinje prihvaćati veze.
# Port je kao broj vrata na pločici: port 80 su standardna vrata za web stranice, i zato ih preglednici
# pokušavaju po zadanom i zato ih ne moramo upisivati u adresnu traku.
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("", 80))
server.listen(2)
print("HTTP server started")
# Cijelu petlju omotavamo u try blok tako da zaustavljanje programa s Ctrl+C, ili tipkom za zaustavljanje tvog
# editora, propisno zatvori socket umjesto da ga ostavi zauzetim.
try:
while True:
# accept() čeka dok se preglednik ne poveže na nas. Vraća nam dvije stvari: vezu preko koje možemo razgovarati,
# i adresu onoga koji se povezao.
connection, address = server.accept()
# recv() čita ono što nam je preglednik poslao, do 1024 znaka toga. Prva linija svakog zahtjeva imenuje
# adresu koja se traži, a to nam treba da odlučimo kako odgovoriti.
request = connection.recv(1024).decode()
first_line = request.split("\r\n")[0]
# Linija zahtjeva izgleda kao "GET /led/on HTTP/1.1", pa nam njezino lomljenje po razmacima i uzimanje srednjeg
# dijela daje samu adresu.
parts = first_line.split(" ")
path = parts[1] if len(parts) > 1 else "/"
# Ovdje odlučujemo što raditi sa svakom adresom. To se naziva usmjeravanje: svaka adresa dobiva svoj odgovor.
# Adrese koje počinju s /led su one koje traže tipke i JavaScript u stranici, a one
# odgovaraju kratkim dijelom čistog teksta, a ne cijelom stranicom.
if path == "/led/on":
led.value(1)
print("LED ON")
send_response(connection, "text/plain", "on")
elif path == "/led/off":
led.value(0)
print("LED OFF")
send_response(connection, "text/plain", "off")
elif path == "/led/status":
# value() čita natrag ono što pin trenutno piše, što nam kaže je li LED-ica upaljena. if i
# else u jednoj liniji je kratak način odabira između dvije vrijednosti.
send_response(connection, "text/plain", "on" if led.value() else "off")
else:
# Svaka druga adresa, uključujući obični "/", dobiva samu web stranicu.
send_response(connection, "text/html", HTML_PAGE)
# close() završava taj jedan razgovor. To je važno: bez toga bi preglednik nastavio čekati a pločici
# bi polako nestalo memorije.
connection.close()
except KeyboardInterrupt:
# Zaustavljanje programa zatvara vrata za nama, pa je port slobodan sljedeći put kada startamo.
print("Server stopped")
server.close()
Što bi trebao vidjeti
Pritisni Run. Pločica prijavljuje cijeli svoj slijed pokretanja i onda utihne, čekajući preglednik:
Connecting to WiFi......
WiFi connected!
IP address: 192.168.75.75
Access the board in your browser at: http://nulamini.local/
HTTP server started
Čitajući to od vrha:
- Točke su pokazatelj napretka. Svaka je jedan prolaz petlje čekanja, a svaki prolaz traje 500 ms, pa je red točaka štoperica. Šest točaka ovdje znači oko tri sekunde do pridruživanja mreži. Očekuj negdje između četiri i osam; točan broj mijenja se od pokretanja do pokretanja, jer ovisi o tome kako brzo tvoj router odgovara.
IP address: 192.168.75.75je adresa koju je router dodijelio. Tvoja će biti drugačija. Zadrži je. To je tvoje rezervno rješenje ako senulamini.localne bude htio otvoriti.HTTP server startedje posljednja linija. Odavde je pločica parkirana naaccept(), slušajući.
isconnected() nikada ne postane True, pa pločica ispisuje točke unedogled i nikada ne dođe do toga da prijavi grešku. S imenom mreže koje ne postoji veselo će ispisati stotinu točaka i nastaviti. Budući da uspješno povezivanje traži manje od deset, red točaka koji još raste nakon dvadeset nije spora mreža. To je gotovo uvijek tipfeler u podacima. Primijeti da se ime mora točno poklapati s mrežom, uključujući razmake i velika slova.Otvaranje stranice
Kada pločica prijavi HTTP server started, otvori http://nulamini.local/ na bilo kojem mobitelu ili računalu na istoj mreži. Stranica je namjerno obična:
Linija statusa nije nagađanje. Prije nego se stranica do kraja učitala, JavaScript je već pitao /led/status, a pločica je pročitala IO4 i odgovorila. LED is OFF je pravo mjerenje pravog pina, a breadboard se slaže:

Sada pritisni ON:
I LED-ica na breadboardu se upali:

Između tih dviju fotografija na spajanju se ništa nije pomaknulo. Jedina razlika je napon na IO4, a on je došao od pritiska tipke negdje drugdje na tvojoj mreži.
Pritisak putuje dugačak put za nešto što izgleda tako jednostavno. Tvoj prst pokrene fetch('/led/on') u pregledniku, koji pošalje HTTP zahtjev preko tvog routera do pločice. accept() se vrati, recv() pročita liniju zahtjeva, lanac if se poklopi s /led/on, i led.value(1) se izvrši. Struja zatim teče iz IO4 kroz otpornik, kroz LED-icu i u − vodilicu. Pritisak na OFF radi isto putovanje obrnuto.
Konzola o tome također vodi zapis, jer svaka grana ispisuje kada se pokrene:
LED ON
LED OFF
LED ON
/led/status ne ispisuje ništa: konzola bi se time napunila.Ako ne radi
OSError: [Errno 112] EADDRINUSEpri drugom pokretanju. Prethodno pokretanje nije otpustilo port 80, i neće ga otpustiti samo od sebe. LinijaSO_REUSEADDRje tu da bind ipak uspije, pa ako vidiš ovu grešku, prvo provjeri je li ta linija još u tvojoj skripti. Ako to ne pomogne, resetiraj pločicu, što zasigurno očisti stari socket.- Stranica se ne otvara, a konzola piše
HTTP server started. Pločica sluša a preglednik ne može do nje doći, što znači da ta dva nisu na istoj mreži, ili mreža izolira svoje klijente. Probaj IP adresu prije.localimena, i probaj hotspot mobitela ako si na gostinskoj mreži. - Točke koje nikada ne prestaju. Pogrešni podaci, ili 5 GHz mreža. C6 nema 5 GHz radio.
Cijeli primjer
Pogledaj cijeli kod primjera na linku ispod:
6.2_Wi-Fi_LED_Control.py
Primjer koji pokazuje kako upravljati LED-icom na NULA MINI s web stranice, koju pločica sama servira iz sirovog socketa i koja je dostupna na nulamini.local.