Lab #5: Komunikácia pomocou prokolu MQTT

organizácia tém, štruktúra správ, zmena konfigurácie zariadenia a jeho ovládanie na diaľku pomocou protokolu MQTT

Video pre cvičenie

Motivácia

Komunikačný protokol MQTT patrí medzi najčastejšie a najobľúbenejšie protokoly používané v oblasti IoT. Aj preto je hlavným komunikačným protokolom v architekúre projektu Smart Department, kde sa používa na komunikáciu medzi všetkými vrstami architektúry. Všetky správy, ktoré sa pomocou protokolu MQTT posielajú, sú vo formáte JSON.

Obr. 1: Smart Department: Architektúra

Na tomto cvičení zabezpečíme odosielanie nameraných údajov zo senzoru, ako aj príjem správ na zmenu konfigurácie zariadenia a vzdialenú správu.

Ciele

  1. naučiť sa ovládať chytré zariadenie pomocou protokolu MQTT

  2. naučiť sa prijímať správy cez MQTT protokol

  3. porozumieť významu príznaku retain pri odosielaní MQTT správ

Postup

Inštalácia knižnice umqtt.simple

V aktuálnom firmvéri jazyka MicroPython pre mikrokontroléry z rodiny RP2 sa nenachádza modul pre prácu s protokolom MQTT. V tomto kroku ju pridáme nainštalovaním balíka umqtt.simple.

Task

Nainštalujte na chytré zariadenie knižnicu umqtt.simple.

Knižnica umqtt.simple je minimalistický klient protokolu MQTT zo štandardnej knižnice jazyka MicroPython (micropython-lib). Je navrhnutá pre zariadenia s obmedzenými zdrojmi, takže je malá a podporuje len to najpodstatnejšie:

  • pripojenie k brokeru
  • podpora šifrovanej komunikácie pomocou TLS,
  • publikovanie správ s úrovňou kvality služby QoS 0 alebo 1 (QoS 2 nepodporuje),
  • prihlásenie na odber tém a spracovanie prijatých správ,
  • nastavenie poslednej vôle (z angl. last will).

Knižnica je blokujúca a spojenie s brokerom sama neudržiava. Pri dlhšom spojení je preto potrebné posielať brokeru správu PING a po výpadku sa pripojiť znova. Automatické opätovné pripojenie rieši nadstavba umqtt.robust.

Task

Sprístupnite zdrojový kód knižnice umqtt.simple vývojovému prostrediu PyCharm, aby rozpoznalo jej import a ponúkalo dopĺňanie kódu.

Balík mip ukladá knižnicu na zariadenie ako preložený súbor .mpy. Prostredie PyCharm ho nevie čítať a navyše nevidí do súborového systému mikrokontroléra, preto import from umqtt.simple import MQTTClient označí ako chybu. Pomôžeme mu tak, že zdrojový kód knižnice umiestnime priamo do projektu.

V koreňovom priečinku projektu (vedľa priečinka src/) vytvorte priečinok stubs/ a stiahnite doň súbor simple.py zo štandardnej knižnice jazyka MicroPython (micropython-lib). Zachovajte pritom štruktúru balíka, teda súbor uložte ako stubs/umqtt/simple.py.

Potom priečinok stubs/ označte v prostredí PyCharm ako zdrojový priečinok (pravé tlačidlo myši na priečinku → Mark Directory as → Sources Root). Na zariadenie sa nenahrá, pretože spúšťač, ktorý ste vytvorili v prvom cvičení, nahráva len obsah priečinka src/.

Ak ste postupovali správne, prostredie PyCharm import from umqtt.simple import MQTTClient rozpozná a pri písaní kódu vám bude ponúkať metódy triedy MQTTClient.

Používateľské nastavenia pre MQTT

Chytré zariadenie bude komunikovať pomocou protokolu MQTT. To, ku ktorému brokeru sa pripojí, aké prihlasovacie údaje použije a či bude komunikácia šifrovaná, však závisí od toho, kde bude zariadenie nasadené – iný broker použijete na cvičení, iný doma a iný bude mať zákazník. Tieto údaje preto nemôžu byť súčasťou statickej konfigurácie v module constants.py, ale musí ich byť možné zmeniť bez zásahu do kódu.

V tomto kroku rozšírime triedu Settings o novú skupinu nastavení pre pripojenie k MQTT brokeru.

Task

Rozšírte používateľské nastavenia o pripojenie k MQTT brokeru.

Nastavenia pripojenia budú v novej triede MQTT a v triede Settings budú dostupné v členskej premennej mqtt. Trieda MQTT bude vyzerať nasledovne:

from constants import DEVICE_TYPE

# ...

class MQTT(BaseModel):
    broker: str = Field(default=None, type=str, optional=True)
    port: int = 1883
    username: str = Field(default=None, type=str, optional=True)
    password: str = Field(default=None, type=str, optional=True)
    tls: bool = False
    topic_prefix: str = DEVICE_TYPE

Význam jednotlivých členských premenných je nasledovný:

  • broker – adresa MQTT brokera,
  • port – port, na ktorom broker počúva; štandardne 1883, pri šifrovanej komunikácii 8883,
  • username a password – prihlasovacie údaje k brokeru,
  • tls – príznak šifrovanej komunikácie (ak ho zapnete, nezabudnite zmeniť aj port),
  • topic_prefix – začiatok tém, do ktorých bude zariadenie publikovať; predvolenou hodnotou je typ zariadenia z konštanty DEVICE_TYPE ("thsensor"). V architektúre Smart Department má prefix tvar <department>/<room>/<device_type> (pozri dokument Komunikácia cez MQTT).

Task

Navrhnite a implementujte vhodné validátory pre overenie hodnôt v nastaveniach pripojenia k MQTT brokeru.

Zamyslite sa nad tým, ktoré hodnoty môže používateľ zadať nesprávne a čo by to spôsobilo pri pripájaní k brokeru alebo pri publikovaní správ. Môžete sa inšpirovať napríklad týmito validátormi:

  • check_port() – overí, či je číslo portu v rozsahu 1 až 65535,
  • check_host() – overí, či je adresa brokera platný doménový názov alebo IP adresa (napr. nesmie obsahovať medzery, schému ako mqtt:// ani číslo portu),
  • check_topic_prefix() – overí, či prefix tém neobsahuje zástupné znaky + a #, nezačína ani nekončí znakom / a neobsahuje prázdnu úroveň (//).

Ak hodnota nie je platná, validátor vyvolá výnimku ValueError s vhodnou správou.

Task

Vytvorte v triede Settings metódu base_topic(), ktorá vráti základ tém zariadenia (<base_topic>) v tvare <topic_prefix>/<device_id>, kde <device_id> je konštanta DEVICE_ID z modulu identity.py.

Vlastné správy zariadenia (napr. namerané údaje alebo stav zariadenia) sa budú posielať do tém, ktoré začínajú týmto spoločným základom, napr. thsensor/ab123cd/data. Vďaka metóde base_topic() sa základ tém skladá na jednom mieste. V ďalšom texte budeme témy zapisovať skrátene pomocou základu tém, napr. <base_topic>/data.

>>> settings.base_topic()
'thsensor/ab123cd'

Task

Doplňte do súboru s nastaveniami na zariadení nastavenia pre pripojenie k MQTT brokeru, ktorý budeme používať na cvičeniach.

Broker má tieto parametre:

  • adresa: mother.fei.tuke.sk
  • port: 8883
  • používateľské meno: student
  • heslo: dgRk9cQ8sU4fCO
  • šifrovaná komunikácia: áno

Namiesto zadávania jednotlivých hodnôt príkazom settings set tentokrát upravíte priamo súbor settings.json v priečinku DATA_FOLDER na zariadení. Otvoriť a rozšíriť o sekciu mqtt ho môžete priamo pomocou rozšírenia MicroPython Tools:

{
  "wifi": { ... },
  "units": "metric",
  "mqtt": {
    "broker": "mother.fei.tuke.sk",
    "port": 8883,
    "username": "student",
    "password": "dgRk9cQ8sU4fCO",
    "tls": true,
    "topic_prefix": "kpi/caprica/thsensor"
  }
}

Upravený súbor načítajte v servisnom termináli podpríkazom settings load. Ak súbor obsahuje chybu, podpríkaz ju ohlási a aktuálne nastavenia nezmení – chybu tak opravíte ešte pred reštartom zariadenia.

Ak ste postupovali správne, nastavenia sa načítajú a ich výpis bude vyzerať takto:

> settings load
Settings loaded.
> settings get mqtt
broker: "mother.fei.tuke.sk"
port: 8883
username: "student"
password: "dgRk9cQ8sU4fCO"
tls: true
topic_prefix: "kpi/caprica/thsensor"

Overte aj to, že nastavenia zostanú zachované po reštarte zariadenia.

Príkaz mqtt

V tomto kroku vytvoríme príkaz mqtt, pomocou ktorého bude možné pracovať s MQTT brokerom priamo zo servisného terminálu. Technik si tak bude môcť ručne overiť, či sú nastavenia pripojenia správne. Jednotlivé činnosti príkazu implementujeme ako operácie príkazu, aby ich neskôr mohli použiť aj stavy zariadenia.

Task

Rozšírte triedu Context o členskú premennú mqtt, ktorá bude uchovávať klienta pre pripojenie k MQTT brokeru.

Pripájanie k brokeru a odosielanie správ sa bude odohrávať na rôznych miestach: v príkaze mqtt aj v stavoch zariadenia. Klient preto musí byť uložený na mieste, ku ktorému majú prístup všetci, teda v kontexte. Premennú vytvorte v konštruktore s hodnotou None. Táto hodnota bude znamenať, že zariadenie nie je pripojené k brokeru.

Task

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

O tejto triede bude platiť:

  • bude potomkom triedy BaseCommand,
  • názov príkazu bude "mqtt",
  • opis príkazu bude "Manage MQTT client.",
  • prvým parametrom príkazu bude názov podpríkazu (connect, disconnect alebo pub) 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

Vytvorte podpríkaz connect na pripojenie k MQTT brokeru, ktorého činnosť implementujete ako operáciu príkazu .connect().

Operácia .connect() bude mať túto signatúru:

def connect(self) -> None:

Operácia nemá žiadne parametre a nič nevracia. Údaje potrebné na pripojenie získa z nastavení v kontexte.

Ak zariadenie nie je pripojené do WiFi siete, operácia vyvolá výnimku NetworkError z modulu exceptions.py so správou "Not connected to network.". Ak je zariadenie k brokeru už pripojené (členská premenná mqtt nemá hodnotu None), operácia vyvolá výnimku MQTTError so správou "Already connected to broker.".

Operácia .connect() vytvorí klienta triedy MQTTClient z knižnice umqtt.simple, pripojí sa ním k brokeru a uloží ho do členskej premennej kontextu mqtt. Pri vytváraní klienta použite:

  • identifikátor zariadenia DEVICE_ID ako identifikátor klienta,
  • adresu brokera, port a prihlasovacie údaje z nastavení mqtt,
  • konštantu MQTT_KEEPALIVE ako interval udržiavania spojenia,
  • šifrovanie podľa nastavenia tls.

Klienta uloží do kontextu až po úspešnom pripojení. Ak sa pripojenie nepodarí, členská premenná mqtt bude mať naďalej hodnotu None.

Pri pripájaní môžu vzniknúť dva typy výnimiek. Ak je broker nedostupný alebo má nesprávnu adresu, vznikne výnimka OSError. Ak broker pripojenie odmietne (napr. pre nesprávne meno alebo heslo), vznikne výnimka MQTTException s návratovým kódom brokera (napr. kód 5 znamená, že klient nie je autorizovaný). Operácia obe výnimky knižnice zachytí a namiesto nich vyvolá výnimku MQTTError s vhodnou správou. Stavy zariadenia tak nebudú závisieť od výnimiek použitej knižnice.

Podpríkaz vypíše:

  • "Connecting to <broker>..." na začiatku pripájania a "Connected." po úspešnom pripojení,
  • "Error: Not connected to network.", ak zariadenie nie je pripojené do WiFi siete,
  • "Error: Already connected to broker.", ak je zariadenie k brokeru už pripojené,
  • "Error: <text výnimky>", ak sa pripojenie nepodarí.

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

> mqtt connect
Error: Not connected to network.
> net connect iot-lab Secret.Password
...
> mqtt connect
Connecting to mother.fei.tuke.sk...
Connected.
> mqtt connect
Error: Already connected to broker.

Task

Vytvorte podpríkaz disconnect na odpojenie od MQTT brokera, ktorého činnosť implementujete ako operáciu príkazu .disconnect().

Operácia .disconnect() bude mať túto signatúru:

def disconnect(self) -> None:

Operácia nemá žiadne parametre a nič nevracia.

Operácia .disconnect() sa od brokera odpojí len vtedy, ak je zariadenie k nemu pripojené. V opačnom prípade vyvolá výnimku MQTTError z modulu exceptions.py so správou "Not connected to broker.". Po odpojení nastaví premennú mqtt v kontexte na hodnotu None, a to aj vtedy, keď odpojenie skončí chybou (napr. ak medzičasom spojenie zaniklo), aby v kontexte nezostal neplatný klient. Chybu knižnice pri odpájaní (výnimku OSError) operácia zachytí a namiesto nej vyvolá výnimku MQTTError.

Podpríkaz vypíše:

  • "Disconnected." po odpojení,
  • "Error: Not connected to broker.", ak zariadenie nie je pripojené k brokeru,
  • "Error: <text výnimky>", ak sa odpojenie nepodarí.

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

> mqtt connect
Connecting to mother.fei.tuke.sk...
Connected.
> mqtt disconnect
Disconnected.
> mqtt disconnect
Error: Not connected to broker.

Task

Vytvorte podpríkaz pub na odosielanie správ, ktorého činnosť implementujete ako operáciu príkazu .pub().

Operácia .pub() bude mať túto signatúru:

def pub(self, topic: str, message: dict, retain: bool = False, qos: int = 0) -> None:

Význam jednotlivých parametrov metódy je nasledovný:

  • topic – téma, do ktorej sa správa odošle,
  • message – odosielaná správa v podobe slovníka; operácia ju pred odoslaním serializuje do formátu JSON,
  • retain – príznak retain odosielanej správy,
  • qos – úroveň kvality služby.

Všetky správy, ktoré zariadenie posiela, sú vo formáte JSON. Operácia .pub() preto prijíma správu len ako slovník a serializáciu vykoná na jednom mieste.

Ak zariadenie nie je pripojené k brokeru, operácia vyvolá výnimku MQTTError z modulu exceptions.py so správou "Not connected to broker.". Ak sa odoslanie nepodarí (napr. ak medzičasom spojenie zaniklo a knižnica vyvolá výnimku OSError), operácia vyvolá výnimku MQTTError.

Podpríkaz pub dostane dva parametre: tému a správu vo formáte JSON. Keďže parser rozdelí riadok podľa medzier, správou bude celý zvyšok riadku za témou. Podpríkaz ju prevedie na slovník a odovzdá operácii .pub(). Správy posiela vždy s predvolenými hodnotami parametrov retain a qos. Podpríkaz vypíše:

  • "Published to <téma>." po odoslaní správy,
  • "Error: Not connected to broker.", ak zariadenie nie je pripojené k brokeru,
  • "Error: Message is not a valid JSON object.", ak správa nie je platný JSON objekt,
  • "Error: <text výnimky>", ak sa odoslanie nepodarí,
  • "Error: Wrong usage.", ak podpríkaz nedostane tému a správu.

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

> mqtt pub kpi/caprica/thsensor/ab123cd/data {"message": "Hello world!"}
Error: Not connected to broker.
> mqtt connect
Connecting to mother.fei.tuke.sk...
Connected.
> mqtt pub kpi/caprica/thsensor/ab123cd/data {"message": "Hello world!"}
Published to kpi/caprica/thsensor/ab123cd/data.
> mqtt pub kpi/caprica/thsensor/ab123cd/data Hello world!
Error: Message is not a valid JSON object.
> mqtt pub kpi/caprica/thsensor/ab123cd/data
Error: Wrong usage.

Ak ste postupovali správne, správa sa objaví v klientskej MQTT aplikácii (napr. MQTT Explorer), ktorá je pripojená k rovnakému brokeru a prihlásená na odber témy <base_topic>/#.

Pripojenie k a odpojenie od MQTT brokera

Pripojenie k MQTT brokeru bude úzko súvisieť so stavom ConnectNetwork. Najprv sa zariadenie pripojí do WiFi siete a hneď potom sa pripojí k MQTT brokeru.

Podobne to bude platiť aj pre stav DisconnectNetwork, ktorého súčasťou sa stane odpojenie od MQTT brokera.

Stavový diagram zariadenia sa v tomto kroku nemení. Stavy, ktorých sa krok týka, sa nachádzajú 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

Rozšírte funkcionalitu stavu ConnectNetwork o pripojenie k MQTT brokeru.

Stav sa po úspešnom pripojení do WiFi siete pripojí k MQTT brokeru. Na pripojenie použite operáciu .connect() príkazu mqtt, ktorú ste vytvorili v predchádzajúcom kroku.

Ak sa pripojenie k brokeru nepodarí (operácia vyvolá výnimku MQTTError), stav vypíše do konzoly príčinu a pokračuje stavom DisconnectNetwork, rovnako ako pri neúspešnom pripojení do WiFi siete. Po úspešnom pripojení pokračuje zariadenie tak ako doteraz – stavom DisconnectNetwork. Do stavu na odoslanie nameraných dát ho presmerujeme až v jednom z ďalších krokov.

Task

Rozšírte funkcionalitu stavu DisconnectNetwork o odpojenie od MQTT brokera.

Na odpojenie použite operáciu .disconnect() príkazu mqtt. Zariadenie sa musí najprv odpojiť od MQTT brokera a až potom od WiFi siete. Inak by sa broker o odpojení nedozvedel a spojenie by považoval za neočakávane prerušené.

Ak zariadenie k brokeru pripojené nie je (členská premenná kontextu mqtt má hodnotu None), nebude sa od brokera odpájať.

Pri odpájaní od brokera môže vzniknúť chyba (výnimka MQTTError), napríklad ak medzičasom spojenie zaniklo. V takom prípade stav vypíše príčinu do konzoly, ale v činnosti pokračuje – zariadenie sa musí odpojiť od WiFi siete v každom prípade.

Task

Overte správnosť svojej implementácie.

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

  1. Ak sú nastavenia WiFi siete aj MQTT brokera správne, tak sa zariadenie pripojí do siete a k brokeru, potom sa odpojí najprv od brokera a následne od siete a prejde do stavu Sleep.
  2. Ak sú prihlasovacie údaje k brokeru nesprávne, tak zariadenie vypíše príčinu, prejde do stavu DisconnectNetwork, ktorý odpojenie od brokera vynechá, a potom do stavu Sleep.
  3. Ak je broker nedostupný (napr. nesprávna adresa alebo port), tak sa zariadenie zachová rovnako ako v predchádzajúcom prípade.
  4. Ak sa zariadenie nepripojí do WiFi siete, tak sa o pripojenie k brokeru vôbec nepokúsi a pokračuje rovnako ako v cvičení 4.

Nesprávne hodnoty nastavte priamo v súbore settings.json a po overení nezabudnite vrátiť správne nastavenia.

Publikovanie nameraných dát

Namerané hodnoty bude zariadenie odosielať v novom stave PublishData. Zariadenie doň prejde zo stavu ConnectNetwork, ak sa podarilo pripojiť do siete aj k MQTT brokeru. Po odoslaní pokračuje stavom DisconnectNetwork, ktorý ho odpojí od brokera aj od siete. Ak sa pripojenie nepodarí, zariadenie stav PublishData vynechá a rovno sa odpojí.

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

---
# 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 -- "connected" --> publish("Publish<br>Data")
    connect -- "connection<br>failed" --> disconnect("Disconnect<br>Network")
    publish --> disconnect
    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,publish,disconnect,sleep state
    class terminal service
    class reset reset
    class start initial
    class sleepEnd,terminalEnd,resetEnd final

Obr. 3: Stavový diagram zariadenia so stavom PublishData

Zariadenie bude telemetriu publikovať do témy <base_topic>/data. Štruktúra správy, význam jej položiek, formát časových údajov aj zoznam názvov metrík a ich jednotiek sú opísané v dokumente Komunikácia cez MQTT, ktorý platí pre všetky zariadenia v architektúre Smart Department.

V kóde správu reprezentujú dva modely z kostry projektu, ktoré sa nachádzajú v balíku models:

  • Metric (modul models/metric.py) – jedna nameraná hodnota,
  • Telemetry (modul models/telemetry.py) – telemetria zariadenia, teda všetky metriky odoslané naraz v jednej správe.

Task

Vytvorte stav PublishData, ktorý odošle namerané hodnoty ako telemetriu zariadenia do témy <base_topic>/data.

Stav vytvorte v module publish_data.py balíka states. Z nameraných hodnôt vytvorte objekty triedy Metric, vložte ich do objektu triedy Telemetry a ten preveďte na slovník metódou .model_dump(). Na samotné odoslanie použite operáciu .pub() príkazu mqtt a tému zložte pomocou metódy base_topic().

Namerané hodnoty vznikajú v stave Measurement, ale odosielajú sa až v stave PublishData. Zabezpečte preto, aby sa k nim stav PublishData dostal, napr. prostredníctvom kontextu.

Ak sa odoslanie nepodarí, vypíšte do konzoly príčinu. Zariadenie bude v oboch prípadoch pokračovať stavom DisconnectNetwork.

Task

Upravte stav ConnectNetwork tak, aby po úspešnom pripojení do siete a k MQTT brokeru pokračoval stavom PublishData.

Ak sa pripojenie do siete alebo k brokeru nepodarí, zariadenie bude naďalej pokračovať stavom DisconnectNetwork.

Task

Overte, či zariadenie odosiela telemetriu.

Pomocou klientskej MQTT aplikácie (napr. MQTT Explorer) sa pripojte k rovnakému brokeru ako zariadenie a prihláste sa na odber témy <base_topic>/data. Potom nechajte zariadenie prejsť meraním a pripojením do siete.

Ak ste postupovali správne, po každom meraní sa v téme objaví nová správa so štruktúrou podľa dokumentu Komunikácia cez MQTT. Overte aj to, že zariadenie pri nedostupnom brokeri stav PublishData vynechá a pokračuje stavom DisconnectNetwork.

Publikovanie stavu zariadenia

Zariadenie sa po každom meraní pripojí k brokeru len na krátky čas a potom sa uspí. Ostatné časti systému by však mali vedieť, či je zariadenie dostupné, a to aj vtedy, keď sa na odber prihlásia až v čase, keď zariadenie spí. Preto bude zariadenie zverejňovať svoj stav do témy <base_topic>/status s príznakom retain. Broker si tak poslednú správu o stave zapamätá a doručí ju každému novému odberateľovi.

Zariadenie bude rozlišovať tieto stavy:

  • online – zariadenie je pripojené k brokeru,
  • sleeping – zariadenie sa korektne odpojilo a spí.

Štruktúra správy a význam jej položiek sú opísané v dokumente Komunikácia cez MQTT.

Task

Vytvorte v module models/status.py model Status, ktorý bude reprezentovať správu o stave zariadenia.

Model bude potomkom triedy BaseModel a jeho členské premenné budú zodpovedať položkám správy podľa dokumentu Komunikácia cez MQTT. Inšpirovať sa môžete modelmi Metric a Telemetry z kostry projektu. V tomto kroku bude čas dt povinnou položkou správy.

Hodnoty oboch položiek overte validátormi:

  • čas dt overte rovnako ako v modeloch Metric a Telemetry – funkciou iso8601() z modulu models/validators.py,
  • stav status môže mať len jednu z hodnôt "online", "sleeping" alebo "offline".

Model si môžete vyskúšať napríklad v REPL režime:

>>> from models.status import Status
>>> Status(dt='2026-10-31T07:05:00Z', status='online').model_dump()
{'dt': '2026-10-31T07:05:00Z', 'status': 'online'}
>>> Status(dt='2026-10-31T07:05:00Z', status='running')
Traceback (most recent call last):
...
ValueError: ...

Task

Upravte stav ConnectNetwork tak, aby po pripojení k brokeru zverejnil stav zariadenia online.

Stav zariadenia zverejnite hneď po úspešnom pripojení k brokeru, ešte pred prechodom do stavu PublishData. Správu vytvorte ako objekt triedy Status s aktuálnym časom a stavom "online" a odošlite ju do témy <base_topic>/status. Pri odoslaní nastavte parametre operácie podľa dokumentu Komunikácia cez MQTT.

Task

Upravte stav DisconnectNetwork tak, aby pred odpojením od brokera zverejnil stav zariadenia sleeping.

Stav zariadenia zverejnite tesne pred odpojením od brokera, ak je zariadenie k brokeru pripojené. Správu vytvorte ako objekt triedy Status s aktuálnym časom a stavom "sleeping" a odošlite ju do témy <base_topic>/status. Pri odoslaní nastavte parametre operácie podľa dokumentu Komunikácia cez MQTT.

Task

Overte, či zariadenie zverejňuje svoj stav.

Pomocou klientskej MQTT aplikácie (napr. MQTT Explorer) sa prihláste na odber témy <base_topic>/status a nechajte zariadenie prejsť meraním a pripojením do siete. Ak ste postupovali správne, v téme sa objaví stav online a pred uspaním zariadenia stav sleeping.

Odpojte klientsku aplikáciu od brokera a znova sa pripojte, kým zariadenie spí. Vďaka príznaku retain by ste mali ihneď dostať posledný známy stav sleeping.

Posledná vôľa

Ak sa spojenie so zariadením neočakávane preruší, napr. pri výpadku napájania, v téme zostane stav online, hoci zariadenie už nie je dostupné. Pre takéto situácie poskytuje protokol MQTT tzv. poslednú vôľu – správu, ktorú klient odovzdá brokeru už pri pripájaní a broker ju zverejní namiesto neho, ak spojenie s ním neočakávane zanikne.

Zariadenie bude mať ako poslednú vôľu nastavený stav offline, ktorý sa zverejní do rovnakej témy ako ostatné stavy zariadenia.

Task

Upravte model Status tak, aby bolo možné vytvoriť správu o stave zariadenia bez času.

Keďže k prerušeniu spojenia môže dôjsť kedykoľvek, pri nastavovaní poslednej vôle nevieme povedať, kedy to bude. Preto položka dt v modeli Status bude voliteľná. Správa bez času bude mať v položke dt hodnotu None.

Task

Rozšírte operáciu .connect() príkazu mqtt o nastavenie poslednej vôle so stavom zariadenia offline.

Poslednú vôľu nastavte metódou .set_last_will() nad objektom klienta, a to ešte pred pripojením k brokeru. Správu vytvorte ako objekt triedy Status so stavom "offline" bez času. Poslednú vôľu nastavte pre tému <base_topic>/status s parametrami podľa dokumentu Komunikácia cez MQTT.

Task

Overte, či broker po neočakávanom prerušení spojenia zverejní poslednú vôľu zariadenia.

Pri bežnej činnosti je zariadenie pripojené k brokeru len krátko, preto na overenie použite servisný terminál, v ktorom zostane spojenie otvorené. V servisnom termináli pripojte zariadenie k brokeru podpríkazom mqtt connect a potom zariadenie odpojte od napájania.

Broker zverejní stav offline až vtedy, keď zistí, že sa klient odmlčal, teda približne po 1,5-násobku intervalu MQTT_KEEPALIVE. Preto chvíľu počkajte.

Overte aj to, že ak sa zariadenie od brokera odpojí korektne podpríkazom mqtt disconnect, broker poslednú vôľu nezverejní.

Správanie overte sledovaním témy <base_topic>/status pomocou klientskej MQTT aplikácie, ako je napr. MQTT Explorer.

Pred odchodom z cvičenia…

Ďalšie úlohy

  1. Zverejnite po pripojení k brokeru aj informácie o zariadení do témy <base_topic>/info podľa dokumentu Komunikácia cez MQTT.

  2. Presuňte do modulu models/validators.py validátory, ktoré je možné opakovane používať.

    Modul models/validators.py je jediný súbor kostry, ktorý môžete meniť. Pri kopírovaní novej verzie kostry ho preto neprepíšte.

Ďalšie zdroje

  1. Smart Department: Komunikácia cez MQTT - organizácia tém, typy správ a ich formát

  2. MicroPython Lib: umqtt.simple - zdrojové kódy, ukážkové príklady a dokumentácia

  3. MicroPython Chapters: Inštalácia balíkov

  4. MicroPython Chapters: Práca s protokolom MQTT

Zoznam zmien

2026-11-05

  • Krok „Publikovanie nameraných dát“: položka units metriky obsahuje kód jednotky podľa štandardu SenML (napr. "Cel" pre teplotu a "%RH" pre relatívnu vlhkosť), nie značku jednotky ("°C", "%"). Teplotu zariadenie odosiela vždy v stupňoch Celzia bez ohľadu na nastavenie units (pozri dokument Komunikácia cez MQTT).

2026-11-01

  • Krok „Používateľské nastavenia pre MQTT“: nastavenie topic_prefix má hodnotu "kpi/caprica/thsensor", pretože broker prepúšťa len povolené témy.
  • Kroky „Používateľské nastavenia pre MQTT“ a „Príkaz mqtt“: identifikátor zariadenia je konštanta DEVICE_ID z modulu identity.py, nie nastavenie device_id (pozri cvičenie 4).
  • Krok „Príkaz mqtt“: ukážky podpríkazu pub používajú povolenú tému kpi/caprica/thsensor/ab123cd/data.

2026-10-31

  • Krok „Publikovanie stavu zariadenia a posledná vôľa“ rozdelený na kroky „Publikovanie stavu zariadenia“ (stavy online a sleeping, model Status) a „Posledná vôľa“ (stav offline); nová téma <topic_prefix>/<device_id>/status.
  • Ďalšie úlohy: dobrovoľná úloha na zverejnenie informácií o zariadení (info).
  • Ďalšie úlohy: dobrovoľná úloha na presun validátorov do modulu models/validators.py.

2026-10-30

  • Krok „Publikovanie nameraných dát“: položka units metriky obsahuje značku jednotky (napr. "°C"), nie sústavu jednotiek ("metric").

2026-10-29

  • Krok „Príkaz mqtt“: operácia .disconnect() nastaví členskú premennú kontextu mqtt na hodnotu None aj vtedy, keď odpojenie skončí chybou.
  • Krok „Príkaz mqtt“: operácia .disconnect() vyvolá výnimku MQTTError, ak zariadenie nie je pripojené k brokeru.
  • Krok „Príkaz mqtt“: podpríkaz connect hlási chýbajúce pripojenie do WiFi siete správou "Error: Not connected to network." a podpríkaz disconnect chýbajúce pripojenie k brokeru správou "Error: Not connected to broker.".
  • Krok „Príkaz mqtt“: operácia .connect() vyvolá výnimku NetworkError, ak zariadenie nie je pripojené do WiFi siete.
  • Krok „Príkaz mqtt“: podpríkaz pub hlási chýbajúce pripojenie k brokeru správou "Error: Not connected to broker.".
  • Krok „Príkaz mqtt“: operácia .pub() vyvolá výnimku MQTTError, ak zariadenie nie je pripojené k brokeru.
  • Krok „Príkaz mqtt“: operácia .connect() vyvolá výnimku MQTTError, ak je zariadenie k brokeru už pripojené; podpríkaz connect vtedy vypíše "Error: Already connected to broker.".
  • Krok „Príkaz mqtt“: operácie .connect(), .disconnect() a .pub() zachytia výnimky knižnice (OSError, MQTTException) a namiesto nich vyvolajú výnimku MQTTError; podpríkaz pub vypíše "Error: <text výnimky>", ak sa odoslanie nepodarí.
  • Krok „Pripojenie k a odpojenie od MQTT brokera“: stavy ConnectNetwork a DisconnectNetwork spracúvajú výnimku MQTTError.
  • Krok „Pripojenie k a odpojenie od MQTT brokera“: nová úloha na overenie implementácie.

2026-10-28

  • Krok „Príkaz mqtt“: nová úloha na rozšírenie triedy Context o členskú premennú mqtt.
  • Krok „Príkaz mqtt“: operácia .connect() uloží klienta do kontextu až po úspešnom pripojení.
  • Krok „Pripojenie k a odpojenie od MQTT brokera“: doplnený stavový diagram a opis úloh pre stavy ConnectNetwork a DisconnectNetwork.