Lab #4: Používateľské nastavenia a pripojenie do internetu

o pripojení do internetu, vytvorení vlastného hotspot-u, synchronizácii času, externom module reálneho času, a stiahnutí rozširujúcich modulov pre MicroPython, dataclasses

Motivácia

  • o pouzivatelskych nastaveniach
  • o wifi
  • o instalacii rozsirujucich balikov

Ciele

  1. naučiť sa pripojiť mikrokontrolér k WiFi v jazyku MicroPython

  2. naučiť sa synchronizovať čas zariadenia pomocou protokolu NTP

  3. naučiť sa nainštalovať rozširujúce moduly na MicroPython zariadenie

Postup

Používateľské nastavenia

Používateľ musí mať možnosť zmeniť správanie chytrého zariadenia. Môže ísť napríklad o nastavenie pripojenia k WiFi, jednotiek teploty alebo intervalu merania. Na rozdiel od statickej konfigurácie v module constants.py môže používateľské nastavenia meniť používateľ za behu zariadenia.

Na reprezentáciu nastavení vytvoríme dátovú triedu s názvom Settings (pozri Dátové triedy). Všetky dátové triedy sa budú nachádzať v balíku models, kde je aj trieda BaseModel – predok každej dátovej triedy. Nastavenia budú uložené v súbore settings.json a pri každom zapnutí zariadenia sa z neho načítajú.

Task

Vytvorte triedu Settings, ktorá bude reprezentovať model pre prácu s používateľskými nastaveniami.

Triedu vytvorte v module settings.py balíka models. Momentálne nás bude v nastaveniach zaujímať:

  • nastavenie pripojenia k WiFi (názov siete, heslo a časový limit pripojenia v sekundách), a
  • jednotka merania teploty.

Trieda Settings bude zatiaľ vyzerať takto:

from hw.mixins.sensors.temperature import TemperatureUnit
from models.base import BaseModel, Field


class WiFi(BaseModel):
    ssid: str = Field(default=None, type=str, optional=True)
    password: str = Field(default=None, type=str, optional=True)
    timeout: int = 10


class Settings(BaseModel):
    wifi: WiFi = Field(default_factory=WiFi, type=WiFi)
    units: str = TemperatureUnit.METRIC

Význam parametrov funkcie Field() (napr. prečo premenná wifi používa parameter default_factory) nájdete v kapitole Dátové triedy.

Task

Overte funkčnosť vytvorenej triedy Settings.

Triedu môžete vyskúšať v REPL jazyka MicroPython. Vytvorte predvolené nastavenia, zmeňte jednotku teploty a vytvorte nastavenia aj zo slovníka, ako keby boli načítané zo súboru settings.json:

>>> from models.settings import Settings
>>> settings = Settings()
>>> settings
Settings(wifi=WiFi(ssid=None,password=None,timeout=10),units='metric')
>>> settings.units = 'imperial'
>>> settings.units
'imperial'
>>> settings = Settings(**{'wifi': {'ssid': 'my-network', 'password': 'secret'}})
>>> settings.wifi.ssid
'my-network'

Ak ste postupovali správne, predvolené nastavenia budú mať prázdne nastavenie WiFi a jednotky "metric" a slovník s nastavením WiFi sa prevedie na objekt triedy WiFi.

Task

Vytvorte validátor s názvom check_units() pre členskú premennú units, ktorý overí, či je zadávaná jednotka teploty platná.

Validátor je metóda, ktorá sa spustí pri priraďovaní hodnoty do členskej premennej. Predtým, ako sa hodnota priradí, sa najprv overí. Ak nie je platná, validátor vyvolá výnimku. V opačnom prípade sa hodnota priradí.

Validátor nemusíte volať sami – trieda BaseModel ho spustí automaticky pri každom priradení hodnoty do danej členskej premennej, aj pri vytváraní objektu.

Validátor na overenie hodnoty členskej premennej units môže vyzerať nasledovne:

from models.base import validator

# ...

class Settings(BaseModel):
    # ...

    @validator('units')
    def check_units(self, value):
        if value not in (
            TemperatureUnit.METRIC,
            TemperatureUnit.IMPERIAL,
            TemperatureUnit.STANDARD
        ):
            raise ValueError(f'Unknown temperature unit "{value}".')

Task

Overte vytvorený validátor.

Validátor vyskúšajte v REPL jazyka MicroPython tak, že do premennej units priradíte neplatnú jednotku teploty:

>>> from models.settings import Settings
>>> settings = Settings()
>>> settings.units = 'kelvin'
Traceback (most recent call last):
  ...
ValueError: Unknown temperature unit "kelvin".
>>> settings.units
'metric'

Ak ste postupovali správne, validátor odmietne neplatnú jednotku teploty a premenná units si ponechá pôvodnú hodnotu.

Task

Podobným spôsobom vytvorte validátor positive() pre členskú premennú timeout triedy WiFi, ktorý overí, či je jej hodnota kladná.

Ak hodnota nie je kladná, validátor vyvolá výnimku ValueError so správou "Timeout must be positive.".

Ak ste postupovali správne, validátor odmietne nulovú aj zápornú hodnotu a premenná timeout si ponechá pôvodnú hodnotu:

>>> from models.settings import Settings
>>> settings = Settings()
>>> settings.wifi.timeout = 0
Traceback (most recent call last):
  ...
ValueError: Timeout must be positive.
>>> settings.wifi.timeout
10

Prvé spustenie a obnova výrobných nastavení

Používateľské nastavenia sú pre fungovanie zariadenia veľmi dôležité. Nastavenia budú na zariadení uložené v súbore, čím sa zabezpečí ich perzistencia. Cesta k súboru s používateľskými nastaveniami sa nachádza v konštante SETTINGS_FILE.

Súbor s nastaveniami bude uložený v priečinku DATA_FOLDER. Do neho bude chytré zariadenie ukladať všetky údaje, ktoré vzniknú počas jeho práce, napr. používateľské nastavenia, merania či záznamy (logy).

V tomto kroku vyriešime tieto dôležité situácie:

  • prvé spustenie zariadenia, keď súbor s používateľskými nastaveniami ešte neexistuje,
  • poškodený súbor s nastaveniami, ktorý nie je možné načítať, a
  • ručné vyvolanie obnovy výrobných nastavení.

Task

Vytvorte nový stav s názvom FactoryReset, ktorý vytvorí predvolené nastavenia.

Stav FactoryReset vytvorte v module factory_reset.py balíka states.

V tomto stave vytvoríte predvolené nastavenia a uložíte ich do súboru settings.json v priečinku DATA_FOLDER. Celá cesta k tomuto súboru sa nachádza v konštante SETTINGS_FILE.

Predvolené nastavenia vytvoríte serializáciou inštancie triedy Settings. Ako na to, nájdete v kapitole Dátové triedy.

V tomto stave teda zabezpečte nasledovné:

  1. Ak neexistuje priečinok DATA_FOLDER, tak ho vytvorte.
  2. V súbore s cestou SETTINGS_FILE vytvorte predvolené nastavenia.
  3. Natvrdo reštartujte zariadenie.

Reštart zabezpečí, že sa zariadenie spustí s novými nastaveniami a prejde bežným štartom cez stav Init, ktorý nastavenia zo súboru načíta.

Task

Overte správnosť implementácie stavu FactoryReset.

Stav ešte nie je prepojený so stavom Init, preto ho spustite priamo z REPL jazyka MicroPython. Využijete pri tom to, že kontext dostáva počiatočný stav ako parameter:

>>> from context import Context
>>> from main import register_devices
>>> from states.factory_reset import FactoryReset
>>> Context(register_devices(), FactoryReset).run()

Zariadenie sa po vytvorení nastavení reštartuje, takže sa k nemu bude potrebné znova pripojiť. Potom overte, či súbor s nastaveniami vznikol a čo obsahuje:

>>> from constants import SETTINGS_FILE
>>> print(open(SETTINGS_FILE).read())
{"wifi": {"ssid": null, "password": null, "timeout": 10}, "units": "metric"}

Ak ste postupovali správne, v priečinku DATA_FOLDER vznikne súbor s predvolenými nastaveniami vo formáte JSON. Poradie kľúčov v súbore sa môže od ukážky líšiť.

Task

Zabezpečte prechod zo stavu Init do stavu FactoryReset na základe podmienok prechodu.

Zariadenie prejde do stavu FactoryReset, ak:

  • neexistuje súbor s nastaveniami,
  • sa súbor s nastaveniami nepodarí načítať z ľubovoľného dôvodu, alebo
  • je pri štarte ovládacie tlačidlo stlačené aspoň tak dlho, ako určuje konštanta LONG_PRESS_DURATION (v milisekundách).

V opačnom prípade bude pokračovať stavom Measurement.

Stavový diagram zariadenia s novým stavom sa nachádza na obrázku 1.

---
# spolocna konfiguracia stavovych diagramov THSensor; vklada sa ako prvy
# riadok bloku ```mermaid, za nim nasleduje "flowchart LR" a samotny diagram
config:
  flowchart:
    curve: basis
    nodeSpacing: 40
    rankSpacing: 60
  themeVariables:
    fontSize: 20px
  themeCSS: ".edgeLabel, .edgeLabel p, .labelBkg { background-color: var(--bs-body-bg) !important; }"
---

flowchart LR
    start@{ shape: f-circ } --> init(Init)
    init --> measurement(Measurement)
    init -- "<code>SVC_PIN</code><br>grounded" --> terminal("Service<br>Terminal")
    init -- "no/faulty<br><code>settings.json</code> /<br>long button press" --> reset("Factory<br>Reset")
    measurement --> sleep(Sleep)
    sleep --> sleepEnd@{ shape: fr-circ }
    terminal --> terminalEnd@{ shape: fr-circ }
    reset --> resetEnd@{ shape: fr-circ }

    %% spolocne styly stavovych diagramov THSensor (farby podla
    %% images/thsensor-state.diagram.complete.png); vklada sa na koniec diagramu
    %% a stavy sa k nim priradia cez "class <stavy> <styl>"
    classDef state fill:#cfe2ff,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef service fill:#cbc3a3,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef reset fill:#ffd6f5,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef config fill:#fddba7,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef error fill:#ffd7d9,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef ota fill:#c3f5c8,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef initial fill:#000,stroke:#fff,stroke-width:2px
    classDef final fill:#fff,stroke:#000,stroke-width:2px

    class init,measurement,sleep state
    class terminal service
    class reset reset
    class start initial
    class sleepEnd,terminalEnd,resetEnd final

Obr. 1: Stavový diagram zariadenia so stavom FactoryReset

Ovládacie tlačidlo je v správcovi zariadení dostupné pod aliasom ALIAS_CONTROL_BTN. Ak sa toto tlačidlo vo vašom správcovi zariadení nenachádza, zaregistrujte ho (je pripojené na pin BTN_PIN).

Diagnostická LED dióda svieti pri zapnutí nazeleno. Ak bude používateľ pri zapnutí držať ovládacie tlačidlo stlačené dlhšie, ako určuje konštanta LONG_PRESS_DURATION, zmeňte jej farbu na fialovú (konštanta Color.PURPLE). Vďaka tomu bude používateľ vedieť, že keď tlačidlo uvoľní, naozaj sa spustí obnova výrobných nastavení.

Nastavenia zo súboru deserializujte a výsledný objekt typu Settings uložte do členskej premennej kontextu settings. Vďaka tomu budú nastavenia dostupné v celom zariadení prostredníctvom kontextu. Členskú premennú settings vytvorte už v konštruktore kontextu s hodnotou None, aby existovala aj pred načítaním nastavení.

Pri implementácii nezabudnite, že kontrola prechodu do stavu FactoryReset sa musí vykonať až po kontrole servisného tlačidla, aby sa do servisného terminálu dalo dostať aj bez platných nastavení.

Task

Overte vytvorenú implementáciu.

Pri overovaní vyskúšajte všetky situácie, ku ktorým môže pri štarte zariadenia dôjsť:

  1. súbor s nastaveniami neexistuje (zmažte ho) – zariadenie prejde do stavu FactoryReset, vytvorí predvolené nastavenia a po reštarte pokračuje stavom Measurement,
  2. súbor s nastaveniami je poškodený (napr. doň zapíšte neplatný JSON alebo neznámy kľúč) – zariadenie prejde do stavu FactoryReset,
  3. súbor s nastaveniami je platný – zariadenie pokračuje stavom Measurement,
  4. ovládacie tlačidlo je pri štarte stlačené dlhšie ako LONG_PRESS_DURATION – diagnostická LED dióda sa rozsvieti nafialovo a po uvoľnení tlačidla zariadenie prejde do stavu FactoryReset,
  5. servisný pin SVC_PIN je uzemnený a súbor s nastaveniami neexistuje – zariadenie prejde do servisného terminálu.

Príkaz settings

K používateľským nastaveniam bude potrebné pristupovať aj zo servisného terminálu. Technik musí mať možnosť hodnoty nastavení zobraziť a zmeniť, zmenené nastavenia uložiť do súboru a v prípade potreby ich zo súboru znova načítať.

V tomto kroku preto vytvoríme príkaz settings s niekoľkými podpríkazmi. Zmeny vykonané v servisnom termináli sa prejavia v nastaveniach zariadenia hneď, do súboru sa však uložia až podpríkazom na uloženie. Technik tak môže zmeniť viacero hodnôt naraz, alebo neuložené zmeny zahodiť opätovným načítaním nastavení zo súboru.

Task

V module commands/settings.py vytvorte triedu Settings, ktorá bude reprezentovať príkaz settings.

O tejto triede bude platiť:

  • bude potomkom triedy BaseCommand,
  • názov príkazu bude "settings",
  • opis príkazu bude "Manage user settings.",
  • prvým parametrom príkazu bude názov podpríkazu (show, get, set, save alebo load) a ďalšie parametre dostane vybraný podpríkaz,
  • ak príkaz nedostane žiadny parameter alebo dostane neznámy podpríkaz, vypíše správu "Error: Wrong usage."

Príkaz zaregistrujte rovnako ako ostatné príkazy.

Task

Rozšírte príkaz settings o podpríkaz show, ktorý zobrazí aktuálnu podobu používateľských nastavení.

Výstup príkazu môže vyzerať nasledovne:

> settings show
wifi:
  ssid: "iot-lab"
  password: "Secret.Password"
  timeout: 10
units: "metric"

Task

Rozšírte príkaz settings o podpríkaz get, ktorý vráti aktuálnu hodnotu nastavenia na základe jeho kľúča.

Kľúč bude reprezentovaný ako cesta k požadovanej hodnote v JSON štruktúre nastavení, pričom jednotlivé úrovne sa oddeľujú bodkou (napr. wifi.ssid). Ak daný kľúč neexistuje, príkaz vypíše chybovú správu "Error: Key \"<kľúč>\" not found.". Ak podpríkaz nedostane práve jeden parameter, vypíše správu "Error: Wrong usage.".

Príklad použitia príkazu môže vyzerať nasledovne:

> settings get units
metric
> settings get wifi.ssid
iot-lab
> settings get wifi
ssid: "iot-lab"
password: "Secret.Password"
timeout: 10
> settings get wifi.port
Error: Key "wifi.port" not found.
> settings get
Error: Wrong usage.

Task

Rozšírte príkaz settings o podpríkaz set, pomocou ktorého bude možné nastaviť novú hodnotu pre zadaný kľúč.

Podpríkaz dostane dva parametre: kľúč v rovnakom tvare ako pri podpríkaze get a novú hodnotu. Keďže hodnota zadaná v termináli je vždy reťazec, pred priradením ju preveďte na typ aktuálnej hodnoty nastavenia (napr. na int). Podpríkaz vypíše chybovú správu:

  • "Error: Wrong usage.", ak nedostane práve dva parametre,
  • "Error: Key \"<kľúč>\" not found.", ak daný kľúč neexistuje,
  • "Error: \"<kľúč>\" is a section, not a value.", ak kľúč neukazuje na hodnotu, ale na celú sekciu nastavení,
  • text výnimky, ak nová hodnota nie je platná (napr. ju odmietne validátor).

Zmena sa prejaví len v nastaveniach v pamäti zariadenia. Do súboru sa uloží až podpríkazom save.

Príklad jeho použitia môže vyzerať nasledovne:

> settings set wifi.ssid iot-lab
wifi.ssid = iot-lab
> settings set units imperial
units = imperial
> settings set units kelvin
Error: Unknown temperature unit "kelvin".
> settings set wifi iot-lab
Error: "wifi" is a section, not a value.
> settings set wifi.port 80
Error: Key "wifi.port" not found.

Task

Rozšírte príkaz settings o podpríkaz load, ktorý načíta používateľské nastavenia zo súboru vo formáte JSON.

Podpríkaz načíta nastavenia zo súboru SETTINGS_FILE a nahradí nimi aktuálne nastavenia v kontexte. Všetky zmeny, ktoré ešte neboli uložené podpríkazom save, sa tým zahodia. Podpríkaz vypíše:

  • "Settings loaded.", ak sa nastavenia podarilo načítať,
  • "Error: Settings file not found.", ak súbor s nastaveniami neexistuje,
  • text výnimky, ak súbor s nastaveniami nie je možné načítať (napr. je poškodený); aktuálne nastavenia v kontexte sa v takom prípade nezmenia.

Príklad použitia podpríkazu môže vyzerať nasledovne:

> settings set units imperial
units = imperial
> settings load
Settings loaded.
> settings get units
metric

Task

Rozšírte príkaz settings o podpríkaz save, ktorý aktuálnu verziu používateľských nastavení uloží do súboru vo formáte JSON.

Podpríkaz serializuje aktuálne nastavenia z kontextu a uloží ich do súboru SETTINGS_FILE, čím sa prepíše jeho pôvodný obsah. Po úspešnom uložení vypíše správu "Settings saved.". Uložené nastavenia sa použijú pri ďalšom zapnutí zariadenia, prípadne po zavolaní podpríkazu load.

Príklad použitia podpríkazu môže vyzerať nasledovne:

> settings set wifi.ssid iot-lab
wifi.ssid = iot-lab
> settings save
Settings saved.
> settings load
Settings loaded.
> settings get wifi.ssid
iot-lab

Ak ste postupovali správne, zmena hodnoty zostane zachovaná aj po opätovnom načítaní nastavení zo súboru, prípadne po reštarte zariadenia.

Nakoniec si do identifikátora zariadenia nastavte svoje študentské prihlasovacie meno a nastavenia uložte. Vďaka tomu bude vaše zariadenie odlíšiteľné od zariadení ostatných študentov.

WiFi modul

Vývojová doska RPi Pico 2 WH obsahuje aj WiFi modul s čipom CYW43439. Práve pomocou neho budeme komunikovať so svetom a pripojíme chytré zariadenie do internetu.

V jazyku MicroPython sa s WiFi modulom pracuje pomocou triedy network.WLAN. V tomto kroku ju zabalíme do samostatnej triedy, aby sme z WiFi modulu spravili perifériu nášho projektu. Jej schopnosti opisujú mixiny NetworkMixin a WiFiMixin, ktoré sú súčasťou kostry projektu.

Task

Vytvorte triedu CYW43439, ktorá zapúzdri prístup k inštancii triedy network.WLAN na prácu s WiFi modulom.

O tejto triede bude platiť nasledovné:

  • jej implementácia sa bude nachádzať v module cyw43439.py,
  • názov zariadenia bude "CYW43439",
  • opis zariadenia bude "Built-in WiFi module (Raspberry Pi Pico 2 W)",
  • bude implementovať mixiny NetworkMixin a WiFiMixin a bude potomkom triedy BaseDevice,
  • konštruktor bude mať len voliteľný parameter alias – WiFi modul je na doske zapojený napevno, preto mu piny pri vytváraní neodovzdávame,
  • metódy implementovaných mixinov prenechajú volanie inštancii triedy network.WLAN.

Metódy, ktoré treba implementovať, predpisujú mixiny NetworkMixin a WiFiMixin. Väčšina z nich len odovzdá volanie rovnomennej metóde inštancie triedy network.WLAN.

Task

Vytvorené zariadenie zaregistrujte v správcovi zariadení a otestujte ho.

Pri registrácii použite alias ALIAS_WIFI. Zariadenie môžete otestovať priamo z prostredia REPL napr. takto:

>>> from constants import ALIAS_WIFI
>>> from main import register_devices
>>> wifi = register_devices().get(ALIAS_WIFI)
>>> wifi.capabilities()
['NetworkMixin', 'WiFiMixin']
>>> wifi.active(True)
>>> wifi.active()
True
>>> wifi.isconnected()
False
>>> for network in wifi.scan():
...     print(network[0], network[3])
...
b'iot-lab' -52
b'eduroam' -67

Ak ste postupovali správne, WiFi modul sa aktivuje a metóda .scan() vráti zoznam WiFi sietí v okolí. Každá sieť je n-tica, ktorej prvou položkou je názov siete (SSID) a štvrtou sila signálu (RSSI) v dBm.

Príkaz net

Na prácu so sieťovým rozhraním vytvoríme v servisnom termináli samostatný príkaz net. Technikovi umožní zistiť, či je zariadenie pripojené do siete a s akou adresou, vyhľadať dostupné WiFi siete alebo sa do niektorej z nich ručne pripojiť – napríklad aby vyskúšal prihlasovacie údaje skôr, ako ich uloží do nastavení.

Príkaz bude mať niekoľko podpríkazov, pomocou ktorých bude možné sieťové rozhranie ovládať a zistiť jeho stav.

Task

V module commands/net.py vytvorte triedu Net, ktorá bude reprezentovať príkaz net.

O tejto triede bude platiť:

  • bude potomkom triedy BaseCommand,
  • názov príkazu bude "net",
  • opis príkazu bude "Manage network interface.",
  • prvým parametrom príkazu bude názov podpríkazu (scan, connect, disconnect, deactivate alebo stat) a ďalšie parametre dostane vybraný podpríkaz,
  • ak príkaz nedostane žiadny parameter alebo dostane neznámy podpríkaz, vypíše správu "Error: Wrong usage.",
  • sieťové rozhranie si vyžiada od správcu zariadení pomocou aliasu ALIAS_WIFI.

Jednotlivé podpríkazy implementujte ako operácie príkazu .scan(), .connect(), .disconnect(), .deactivate() a .stat(), pretože ich bude potrebné použiť na viacerých miestach.

Príkaz zaregistrujte rovnako ako ostatné príkazy.

Task

Vytvorte podpríkaz scan, ktorý vyhľadá WiFi siete v okolí.

Podpríkaz najprv aktivuje sieťové rozhranie (ak ešte nie je aktívne) a potom pomocou metódy .scan() zistí dostupné siete. Pre každú nájdenú sieť vypíše:

  • jej názov (SSID); namiesto prázdneho názvu skrytej siete vypíše (hidden),
  • číslo kanála,
  • silu signálu (RSSI) v dBm,
  • spôsob zabezpečenia: open, WEP, WPA, WPA2 alebo WPA/WPA2.

Význam jednotlivých položiek, ktoré metóda .scan() pre každú sieť vráti, nájdete v kapitole Práca s WiFi modulom. Pozor – spôsob zabezpečenia nie je jednoduchý číselný kód, ale bitová maska.

Príklad použitia podpríkazu môže vyzerať nasledovne:

> net scan
SSID                               CH   RSSI  SECURITY
--------------------------------  ---  -----  ---------
iot-lab                             6    -52  WPA2
eduroam                             1    -67  WPA2
TUKE-guest                         11    -74  open
(hidden)                           11    -81  WPA/WPA2

Task

Vytvorte podpríkaz connect, pomocou ktorého sa zariadenie pripojí k zadanej WiFi sieti.

Podpríkaz dostane dva parametre: názov siete (SSID) a heslo. Zadané údaje sa do nastavení neukladajú. Technik si tak môže overiť, či sú prihlasovacie údaje správne.

Metóda .connect() triedy network.WLAN na pripojenie nečaká – len ho spustí a hneď sa vráti. Podpríkaz preto musí počkať, kým sa zariadenie pripojí, alebo kým neuplynie časový limit v sekundách, ktorý je uložený v nastaveniach (wifi.timeout). Priebeh pripájania môžete sledovať metódou .status(), ktorá vráti stav pripojenia (napr. network.STAT_GOT_IP, network.STAT_WRONG_PASSWORD alebo network.STAT_NO_AP_FOUND).

Podpríkaz vypíše:

  • "Connecting to <ssid>..." na začiatku pripájania,
  • po úspešnom pripojení názov siete, IP adresu, masku siete, adresu brány a DNS servera,
  • "Error: Wrong password.", ak je heslo nesprávne,
  • "Error: Network not found.", ak sieť so zadaným názvom nie je v dosahu,
  • "Error: Connection timed out.", ak sa zariadenie nepripojí v časovom limite,
  • "Error: Wrong usage.", ak podpríkaz nedostane práve dva parametre.

Príklad použitia podpríkazu môže vyzerať nasledovne:

> net connect iot-lab Secret.Password
Connecting to iot-lab...
SSID      : iot-lab
IP        : 192.168.1.42
Mask      : 255.255.255.0
Gateway   : 192.168.1.1
DNS       : 192.168.1.1
> net connect iot-lab wrong-password
Connecting to iot-lab...
Error: Wrong password.
> net connect no-such-network secret
Connecting to no-such-network...
Error: Network not found.

Task

Rozšírte príkaz net o podpríkazy disconnect a deactivate.

O týchto podpríkazoch bude platiť:

  • disconnect – odpojí zariadenie od WiFi siete a vypíše správu "Disconnecting from network..."; ak zariadenie nie je pripojené, vypíše správu "Error: Not connected.",
  • deactivate – vypne sieťové rozhranie a vypíše správu "Deactivating interface.".

Príklad použitia podpríkazov môže vyzerať nasledovne:

> net disconnect
Disconnecting from network...
> net disconnect
Error: Not connected.
> net deactivate
Deactivating interface.

Task

Vytvorte podpríkaz stat, ktorý zobrazí aktuálny stav sieťového rozhrania.

Podpríkaz vypíše:

  • či je sieťové rozhranie aktívne,
  • stav pripojenia, napr. idle, connecting, connected, wrong password alebo no AP found (číselné hodnoty stavov zodpovedajú konštantám network.STAT_*),
  • MAC adresu rozhrania.

Ak je zariadenie pripojené do siete, vypíše navyše:

  • názov siete (SSID), číslo kanála a silu signálu (RSSI),
  • IP adresu, masku siete, adresu brány a DNS servera.

Príklad použitia podpríkazu môže vyzerať nasledovne:

> net stat
Active    : no
Status    : idle
MAC       : 28:cd:c1:0f:3a:52
> net connect iot-lab Secret.Password
...
> net stat
Active    : yes
Status    : connected
MAC       : 28:cd:c1:0f:3a:52
SSID      : iot-lab
Channel   : 6
RSSI      : -52 dBm
IP        : 192.168.1.42
Mask      : 255.255.255.0
Gateway   : 192.168.1.1
DNS       : 192.168.1.1
> net disconnect
Disconnecting from network...
> net stat
Active    : yes
Status    : idle
MAC       : 28:cd:c1:0f:3a:52
> net deactivate
Deactivating interface.
> net stat
Active    : no
Status    : idle
MAC       : 28:cd:c1:0f:3a:52

Pripojenie do WiFi siete a odpojenie od nej

Chytré zariadenie nepotrebuje byť pripojené do siete neustále. Pripojí sa len vtedy, keď potrebuje odoslať namerané hodnoty, a hneď potom sa od siete odpojí a WiFi modul vypne. Zapnutý WiFi modul totiž patrí medzi najväčších spotrebiteľov energie v zariadení.

Pripojenie a odpojenie preto vytvoríme ako dva samostatné stavy:

  • ConnectNetwork – pripojí zariadenie do WiFi siete podľa používateľských nastavení,
  • DisconnectNetwork – odpojí zariadenie od siete a vypne WiFi modul.

Do stavu DisconnectNetwork zariadenie prejde aj vtedy, keď sa pripojenie nepodarí. Vďaka tomu bude WiFi modul vypnutý vždy, keď zariadenie prejde do stavu Sleep. Na samotné pripojenie a odpojenie použijeme operácie príkazu net, ktoré sme vytvorili v predchádzajúcom kroku.

Stavový diagram zariadenia s novými stavmi ConnectNetwork a DisconnectNetwork sa nachádza na obrázku 2.

---
# spolocna konfiguracia stavovych diagramov THSensor; vklada sa ako prvy
# riadok bloku ```mermaid, za nim nasleduje "flowchart LR" a samotny diagram
config:
  flowchart:
    curve: basis
    nodeSpacing: 40
    rankSpacing: 60
  themeVariables:
    fontSize: 20px
  themeCSS: ".edgeLabel, .edgeLabel p, .labelBkg { background-color: var(--bs-body-bg) !important; }"
---

flowchart LR
    start@{ shape: f-circ } --> init(Init)
    init --> measurement(Measurement)
    init -- "<code>SVC_PIN</code><br>grounded" --> terminal("Service<br>Terminal")
    init -- "no/faulty<br><code>settings.json</code> /<br>long button press" --> reset("Factory<br>Reset")
    measurement --> connect("Connect<br>Network")
    connect --> disconnect("Disconnect<br>Network")
    disconnect --> sleep(Sleep)
    sleep --> sleepEnd@{ shape: fr-circ }
    terminal --> terminalEnd@{ shape: fr-circ }
    reset --> resetEnd@{ shape: fr-circ }

    %% spolocne styly stavovych diagramov THSensor (farby podla
    %% images/thsensor-state.diagram.complete.png); vklada sa na koniec diagramu
    %% a stavy sa k nim priradia cez "class <stavy> <styl>"
    classDef state fill:#cfe2ff,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef service fill:#cbc3a3,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef reset fill:#ffd6f5,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef config fill:#fddba7,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef error fill:#ffd7d9,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef ota fill:#c3f5c8,stroke:#9aa3ad,stroke-width:3px,color:#343a40,font-weight:bold
    classDef initial fill:#000,stroke:#fff,stroke-width:2px
    classDef final fill:#fff,stroke:#000,stroke-width:2px

    class init,measurement,connect,disconnect,sleep state
    class terminal service
    class reset reset
    class start initial
    class sleepEnd,terminalEnd,resetEnd final

Obr. 2: Stavový diagram zariadenia so stavmi ConnectNetwork a DisconnectNetwork

Task

Vytvorte stav DisconnectNetwork, ktorý odpojí zariadenie od WiFi siete a vypne WiFi modul.

Stav vytvorte v module disconnect_network.py balíka states. Na samotné odpojenie použite operáciu .disconnect() a na vypnutie WiFi modulu operáciu .deactivate() príkazu net.

Zariadenie sa do tohto stavu dostane zo stavu ConnectNetwork, ktorý vytvoríte v nasledujúcej úlohe, a po odpojení bude pokračovať stavom Sleep.

Task

Vytvorte stav ConnectNetwork, ktorý pripojí zariadenie do WiFi siete.

Stav vytvorte v module connect_network.py balíka states. Názov WiFi siete a heslo získate z používateľských nastavení. Na samotné pripojenie použite operáciu .connect() príkazu net. Ak sa pripojenie nepodarí, vypíšte do konzoly príčinu.

Upravte stav Measurement tak, aby po meraní pokračoval stavom ConnectNetwork. Po pripojení, ale aj po neúspešnom pokuse o pripojenie, bude zariadenie pokračovať stavom DisconnectNetwork.

Task

Overte správnosť svojej implementácie.

Pri overovaní vyskúšajte tieto situácie:

  1. nastavenia WiFi siete sú správne – zariadenie sa pripojí, odpojí a prejde do stavu Sleep,
  2. heslo k WiFi sieti je nesprávne – zariadenie vypíše príčinu, prejde do stavu DisconnectNetwork a potom do stavu Sleep,
  3. WiFi sieť so zadaným názvom nie je v dosahu – zariadenie sa zachová rovnako ako v predchádzajúcom prípade,
  4. WiFi sieť nie je nastavená (napr. po obnove výrobných nastavení) – zariadenie vypíše príčinu a pokračuje rovnako.

Nastavenia WiFi siete pre jednotlivé situácie zmeňte príkazom settings v servisnom termináli a nezabudnite ich uložiť. Vo všetkých prípadoch musí byť WiFi modul v stave Sleep vypnutý.

Názov zariadenia v sieti

Keď sa zariadenie pripojí do siete, bude ho možné okrem IP adresy identifikovať aj pomocou názvu hostiteľa (z angl. hostname). Na doskách RPi Pico 2 WH je tento názov predvolene nastavený na hodnotu Pico2W. Ak sa teda v jednej sieti bude nachádzať viacero rovnakých zariadení, nebude ich možné podľa názvu od seba odlíšiť.

V tomto kroku preto každému chytrému zariadeniu nastavíme jedinečný názov hostiteľa, podľa ktorého ho bude možné v sieti odlíšiť od ostatných zariadení. Názov vznikne spojením typu zariadenia a jeho identifikátora.

Task

Pomocou funkcie network.hostname() nastavte zariadeniu názov v sieti v tvare <DEVICE_TYPE>-<DEVICE_ID>.

Toto nastavenie zabezpečte v module boot.py, ktorý vytvorte v priečinku src/ vedľa modulu main.py. Ak sa súbor boot.py na mikrokontroléri nachádza, MicroPython ho spustí pri každom spustení zariadenia ešte pred súborom main.py.

Názov musí byť nastavený ešte predtým, ako sa zariadenie pripojí do siete. Po tvrdom reštarte a po vypnutí zariadenia sa však stratí, preto ho treba nastaviť pri každom spustení zariadenia.

Task

Overte, že je zariadenie v sieti dostupné podľa jeho názvu.

Pripojte zariadenie do WiFi siete pomocou príkazu net connect v servisnom termináli. Potom z počítača pripojeného do tej istej siete overte jeho dostupnosť príkazom ping:

$ ping thsensor-ab123cd.local

Ak ste postupovali správne, zariadenie bude na príkaz ping odpovedať.

Čas zariadenia

Mikrokontroléry z rodiny RP2 majú interný hodinový modul. Ten však po zapnutí nepozná skutočný čas a začína od dátumu 2021-01-01. Keďže správy, ktoré bude zariadenie neskôr odosielať, budú obsahovať čas, musí ho poznať už pri meraní, teda ešte pred pripojením do siete.

Správny čas preto bude uchovávať externý hodinový modul, ktorý si ho udrží aj po vypnutí zariadenia. Po štarte zariadenia z neho čas prevezmú interné hodiny a po každom pripojení do siete sa modul doladí podľa servera NTP.

Prácu s časom zabalíme do príkazu rtc. Jeho operácie bude možné použiť v stavoch zariadenia aj v servisnom termináli.

Task

Rozšírte používateľské nastavenia o adresu servera NTP.

Adresa bude v novej triede NTP v členskej premennej server s predvolenou hodnotou "ntp.tuke.sk". V triede Settings bude trieda dostupná v členskej premennej ntp.

Task

V module commands/rtc.py vytvorte triedu RTC, ktorá bude reprezentovať príkaz rtc.

O tejto triede bude platiť:

  • bude potomkom triedy BaseCommand,
  • názov príkazu bude "rtc",
  • opis príkazu bude "Manage real-time clock.",
  • prvým parametrom príkazu bude názov podpríkazu (now, set alebo sync) a ďalšie parametre dostane vybraný podpríkaz,
  • ak príkaz nedostane žiadny parameter alebo dostane neznámy podpríkaz, vypíše správu "Error: Wrong usage.",
  • hodinový modul si vyžiada od správcu zariadení pomocou aliasu ALIAS_RTC.

Príkaz zaregistrujte rovnako ako ostatné príkazy.

Task

Vytvorte operáciu .restore(), ktorá nastaví interné hodiny podľa hodinového modulu, a použite ju v stave Init.

Interné hodiny nastavíte metódou .datetime() triedy machine.RTC, ktorá používa rovnaký tvar n-tice ako trieda DS3231. Čas nastavte hneď na začiatku stavu Init, aby ho mali k dispozícii všetky ďalšie stavy, a to aj servisný terminál.

Task

Vytvorte podpríkaz now, ktorý vypíše čas hodinového modulu, a jeho činnosť implementujte ako operáciu .now().

Operácia vráti čas ako reťazec vo formáte YYYY-MM-DDTHH:MM:SSZ v časovom pásme UTC.

Task

Vytvorte podpríkaz set, ktorý nastaví čas zadaný používateľom, a jeho činnosť implementujte ako operáciu .set().

Operácia dostane čas ako reťazec vo formáte YYYY-MM-DDTHH:MM:SSZ. Nastaví ho v hodinovom module aj v interných hodinách. Ak má reťazec nesprávny formát, vyvolá výnimku ValueError, ktorú podpríkaz vypíše ako "Error: <text výnimky>".

Task

Vytvorte podpríkaz sync, ktorý synchronizuje čas pomocou protokolu NTP, a jeho činnosť implementujte ako operáciu .sync().

Na to slúži funkcia settime() z modulu ntptime, ktorý je súčasťou firmvéru. Tá nastaví interné hodiny na aktuálny čas v časovom pásme UTC. Adresu servera nastavte pred jej zavolaním do premennej ntptime.host. Operácia potom čas z interných hodín zapíše do hodinového modulu.

Ak zariadenie nie je pripojené do siete, operácia vyvolá výnimku NetworkError so správou "Not connected to network.". Ak server neodpovie, funkcia settime() vyvolá výnimku OSError, ktorú operácia zachytí a namiesto nej vyvolá výnimku NetworkError.

Príklad použitia príkazu môže vyzerať nasledovne:

> rtc now
2026-11-01T13:13:52Z
> rtc sync
Error: Not connected to network.
> rtc set 2026-13-01T00:00:00Z
Error: Invalid date and time "2026-13-01T00:00:00Z", expected YYYY-MM-DDTHH:MM:SSZ.
> rtc set 2030-06-15T08:30:00Z
2030-06-15T08:30:00Z

Task

Rozšírte stav ConnectNetwork o synchronizáciu času.

Čas synchronizujte operáciou .sync() hneď po úspešnom pripojení do WiFi siete. Ak operácia vyvolá výnimku NetworkError, vypíšte do konzoly príčinu a v činnosti pokračujte, pretože zariadenie môže pracovať aj bez presného času.

Task

Overte správnosť svojej implementácie.

Nastavte v servisnom termináli nesprávny čas, pripojte zariadenie do siete a podpríkazom rtc sync synchronizujte čas. Odpojte zariadenie od napájania, znova ho zapnite a podpríkazom rtc now overte, že má stále správny čas. Interné hodiny overíte funkciou time.gmtime():

>>> import time
>>> time.gmtime()
(2026, 11, 1, 13, 13, 57, 6, 305)

Vytvorenie príkazu pip

Podobne ako jazyk Python je možné aj jazyk MicroPython rozširovať pomocou balíkov, ktoré je možné doinštalovať. V jazyku Python na to slúži nástroj príkazového riadka pip. V prostredí jazyka MicroPython máme niekoľko spôsobov, ako na mikrokontroléri inštalovať nové balíky. Nás v tomto kroku bude zaujímať modul mip, ktorý sa používa podobne ako nástroj pip. Keďže balíky sťahuje z internetu, zariadenie musí byť pri inštalácii pripojené do siete.

V tomto kroku vytvoríme v servisnom termináli samostatný príkaz pip, pomocou ktorého bude možné rozšíriť aktuálne schopnosti mikrokontroléra o nové balíky.

Viac informácií o inštalovaní nových balíkov v prostredí jazyka MicroPython sa dozviete v kapitole Inštalácia balíkov.

Task

V module pip.py vytvorte triedu Pip, ktorá bude reprezentovať príkaz pip na inštaláciu nových balíkov.

O tejto triede bude platiť:

  • bude potomkom triedy BaseCommand,
  • názov príkazu bude "pip",
  • opis príkazu bude "Install packages.",
  • podpríkaz install <balík> nainštaluje zadaný balík pomocou funkcie mip.install(),
  • pred inštaláciou overí, či je zariadenie pripojené do siete (WiFi modul s aliasom ALIAS_WIFI); ak nie je, vypíše správu "Error: Not connected." a inštaláciu nespustí,
  • ak príkaz nedostane podpríkaz install s práve jedným názvom balíka, vypíše správu "Error: Wrong usage."

Príkaz zaregistrujte rovnako ako ostatné príkazy.

Task

Overte funkčnosť príkazu pip.

Najprv vyskúšajte inštaláciu bez pripojenia do siete a potom sa pripojte a nainštalujte balík pathlib zo štandardnej knižnice jazyka MicroPython (micropython-lib):

> pip install pathlib
Error: Not connected.
> net connect iot-lab Secret.Password
Connecting to iot-lab...
SSID      : iot-lab
IP        : 192.168.1.42
Mask      : 255.255.255.0
Gateway   : 192.168.1.1
DNS       : 192.168.1.1
> pip install pathlib
Installing pathlib...
Done.

Nainštalované balíky sa uložia do priečinka /lib na mikrokontroléri, odkiaľ ich je možné importovať rovnako ako vstavané moduly. Či sa balík naozaj nainštaloval, môžete overiť priamo z prehliadača súborov vášho IDE alebo z prostredia REPL jazyka MicroPython:

>>> import os
>>> os.listdir('/lib')
['pathlib.mpy']
>>> from pathlib import Path

Ak ste postupovali správne, priečinok /lib bude obsahovať nainštalovaný balík a jeho import prebehne bez chyby.

Ďalšie úlohy

  1. Rozšírte príkaz reset o podpríkaz factory, ktorý obnoví výrobné nastavenia zariadenia.

Ďalšie zdroje

  1. Práca s WiFi modulom – scan WiFi sietí, pripojenie k WiFi, vytvorenie prístupového bodu

  2. Názov zariadenia v sieti – nastavenie názvu hostiteľa pomocou funkcie network.hostname()

  3. Súborový systém – špeciálne súbory boot.py a main.py

  4. Inštalácia balíkov – inštalácia balíkov do mikrokontroléra v jazyku MicroPython

  5. Dátové triedy – ako na dátové triedy pomocou triedy BaseModel

Zoznam zmien

2026-11-04

  • Nový krok „Názov zariadenia v sieti“: nový modul boot.py, ktorý nastaví názov hostiteľa v tvare <DEVICE_TYPE>-<DEVICE_ID>, a overenie dostupnosti zariadenia podľa názvu príkazom ping.

2026-11-01

  • Krok „Používateľské nastavenia“: identifikátor zariadenia device_id a jeho validátor check_device_id() sú odstránené z triedy Settings, zariadenie používa konštantu DEVICE_ID z modulu identity.py (pozri cvičenie 2). Do 9. 11. nahraďte v kóde settings.device_id za DEVICE_ID, odstráňte premennú a validátor z triedy Settings a kľúč device_id zo súboru settings.json. Bez zmazania kľúča sa súbor nenačíta a zariadenie prejde do stavu FactoryReset.
  • Nový krok „Čas zariadenia“: nastavenie ntp.server, príkaz rtc s podpríkazmi now, set a sync, prevzatie času z hodinového modulu v stave Init a synchronizácia času v stave ConnectNetwork.