Práca s protokolom MQTT
pripojenie sa, publikovanie správ, prihlásenie sa na odber správ, nastavenie poslednej vôle
Komunikačný protokol MQTT patrí medzi najpopulárnejšie a najpoužívanejšie protokoly v IoT riešeniach. To je dané jeho jednoduchosťou a výrazne nižším množstvom prenášaných dát v porovnaní s inými protokolmi.
V tomto návode sa pozrieme na to, ako ho použiť v jazyku MicroPython.
Podpora protokolu v jazyku MicroPython
Modul pre prácu s MQTT sa nenachádza v každom firmvéri s jazykom MicroPython. Nájdete ho napr. vo firmvéri pre mikrokontrolér ESP32, ale nenájdete ho vo firmvéri pre mikrokontroléry z rodiny RP2 (napr. RP2350 na doske RPi Pico 2 W). To, či ho máte alebo nemáte k dispozícii, si môžete overiť vypísaním zoznamu všetkých dostupných modulov príkazom:
>>> help('modules')Ak ho nemáte, môžete ho nainštalovať priamo z prostredia REPL jazyka MicroPython týmito príkazmi:
>>> import mip
>>> mip.install('umqtt.simple')Poznámka
Aby inštalácia prebehla úspešne, musíte byť s mikrokontrolérom pripojení do internetu.
Pripojenie k brokeru
Aby sme sa mohli pripojiť na MQTT broker, potrebujeme
vytvoriť objekt typu MQTTClient a pri vytváraní mu dodať
potrebné informácie pre pripojenie. Trieda MQTTClient sa
nachádza v balíku umqtt.simple:
from umqtt.simple import MQTTClientVytvorenie MQTT klienta vyzerá nasledovne:
client = MQTTClient(
client_id,
server,
port=0,
user=None,
password=None,
keepalive=0,
ssl=None
)Význam jednotlivých parametrov je nasledovný:
client_id- Jednoznačný identifikátor klienta, ktorým sa odlišuje od ostatných klientov pripojených k rovnakému MQTT brokeru. Ide o povinný parameter.server- Doménové meno alebo IP adresa MQTT brokera (bez schémy, ako napr.mqtt://). Ide o povinný parameter.port- Port, na ktorom broker počúva. Ak ho nezadáme, použije sa port1883, čo je štandardný port pre komunikáciu pomocou protokolu MQTT alebo sa použije port8883, ak je nastavený parameterssl.userapassword- V prípade, že pripojenie k MQTT brokeru si vyžaduje prihlasovacie meno a heslo, zadáte ich pomocou týchto dvoch parametrov.keepalive- Maximálny čas v sekundách medzi dvoma správami od klienta. Ak broker v tomto intervale (presnejšie v 1,5-násobku tohto intervalu) nedostane žiadnu správu, považuje klienta za nedostupného a odošle jeho poslednú vôľu. Hodnota0túto kontrolu vypína. Knižnicaumqtt.simplesprávuPINGneposiela sama – ak je spojenie otvorené dlhšie ako tento interval a klient nič neodosiela, je potrebné pravidelne volať metódu.ping(), inak ho broker odpojí.ssl- Objekt triedyssl.SSLContext, pomocou ktorého bude spojenie šifrované protokolom TLS (napr.ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)). Staršia podobassl=True(spolu s parametromssl_params) je v knižnici ponechaná len kvôli spätnej kompatibilite. Pri nej sa však brokeru neposiela meno servera (SNI), a preto ho niektoré brokery môžu odmietnuť.
Pre vygenerovanie jedinečného identifikátora klienta môžete využiť napr. tento fragment kódu:
import binascii
import machine
client_id = binascii.hexlify(machine.unique_id())Pripojenie sa k brokeru
Po vytvorení klienta sa môžete pokúsiť pripojiť pomocou volania
metódy .connect(). Signatúra metódy je nasledovná:
.connect(self, clean_session=True, timeout=None)Význam jednotlivých parametrov metódy je nasledovný:
clean_session- Ak má hodnotuTrue, broker po pripojení začne novú reláciu a zahodí všetko, čo si o klientovi pamätal z predchádzajúceho pripojenia (napr. odbery tém a nedoručené správy s QoS 1). Ak má hodnotuFalse, broker v predchádzajúcej relácii pokračuje.timeout- Časový limit v sekundách pre operácie so soketom, napr. pre samotné pripojenie. Predvolená hodnotaNoneznamená, že sa bude čakať bez obmedzenia.
client = MQTTClient(CLIENT_ID, server)
client.connect()
# tu príde komunikáciaAk sa pripojiť nepodarí, dôjde k vyvolaniu výnimky
MQTTException z balíka umqtt.simple s kódom
chyby (kód -1 znamená, že broker neposlal platnú odpoveď).
Ak sa pripojenie podarí, metóda vráti príznak session present:
hodnotu 1, ak broker pokračuje v predchádzajúcej relácii
klienta, inak hodnotu 0. Pri parametri
clean_session=True je to teda vždy hodnota
0.
V nasledujúcej tabuľke sa nachádzajú kódy chýb podľa MQTT špecifikácie:
| Kód | Názov | Význam |
|---|---|---|
0 |
Connection Accepted | pripojenie úspešné |
1 |
Unacceptable Protocol Version | Klient používa nepodporovanú verziu MQTT |
2 |
Identifier Rejected | Client ID neakceptovaný (prázdny, duplicitný, neplatný) |
3 |
Server Unavailable | Broker je dostupný, ale neprijíma nové pripojenia |
4 |
Bad Username or Password | Chyba v autentifikácii |
5 |
Not Authorized | Klient nie je autorizovaný – broker ho odmietol kvôli prístupovým právam |
Poznámka
Operácia pripojenia môže však skončiť s výnimkou:
OSError: [Errno 12] ENOMEMVýnimka ENOMEM znamená, že zariadeniu došla pamäť (RAM).
To môže mať viacero dôvodov:
- vyrovnávacia pamäť pre správu je príliš veľká,
- prijímate veľké správy,
- máte málo voľnej pamäte RAM (napr. kvôli jej fragmentácii),
- šifrovanie pomocou TLS spotrebuje veľa pamäte (najmä na mikrokontroléroch s menšou pamäťou),
- v pamäti je naraz príliš veľa objektov (napr. reťazcov, štruktúr JSON alebo názvov tém).
Odpojenie sa od brokera
Odpojiť sa od brokera sa môžete zavolaním metódy
.disconnect() nad objektom klienta. Táto metóda nemá žiadny
parameter.
client = MQTTClient(CLIENT_ID, server)
client.connect()
# tu príde komunikácia
client.disconnect()Ak sa klient ešte nepripojil, dôjde k vyvolaniu výnimky
AttributeError, pretože klient ešte nemá vytvorený soket.
Ak už bol klient odpojený, dôjde k vyvolaniu výnimky
OSError.
Nastavenie poslednej vôle
Posledná vôľa (z angl. Last Will and Testament) je špeciálna správa, ktorú MQTT broker automaticky odošle za klienta, ak sa klient neočakávane odpojí (napr. kvôli výpadku napájania, signálu alebo pádu programu).
Poslednú vôľu odovzdá klient brokeru pri pripájaní – je súčasťou
správy, ktorou sa klient k brokeru pripája. Nastaviť ju je možné
zavolaním metódy .set_last_will() nad objektom klienta, a
to ešte pred zavolaním metódy .connect().
Ak ju nastavíte až po pripojení, broker sa o nej nedozvie. Signatúra
metódy je nasledovná:
.set_last_will(self, topic, msg, retain=False, qos=0)Význam jednotlivých parametrov metódy je nasledovný:
topic- Názov témy, do ktorej bude posledná vôľa odoslaná.msg- Samotná správa, ktorá reprezentuje poslednú vôľu.retain- Nastavenie príznakuretainpre správu poslednej vôle.qos- Úroveň QoS.
Správa, ktorá sa posiela ako posledná vôľa, je obyčajne veľmi jednoduchá. Obsahuje totiž len informáciu o tom, že je zariadenie vypnuté. Ak sa posiela vo formáte JSON, môže vyzerať napríklad takto:
{
"status": "offline"
}Odoslanie dát na MQTT broker
Pre odoslanie správy (tzv. publikovanie) použijeme metódu nad
MQTT klientom s názvom .publish(). Signatúra tejto
metódy vyzerá nasledovne:
.publish(self, topic, msg, retain=False, qos=0)Význam jednotlivých parametrov metódy je nasledovný:
topic- Názov témy, do ktorej bude správa poslaná. Musí byť serializovateľný do poľa bytov.msg- Samotná správa. Musí byť serializovateľná do poľa bytov.retain- Označenie správy príznakomretain. Predvolene správa nie je s týmto príznakom odoslaná.qos- Úroveň QoS, ktorá môže byť0alebo1. Predvolená hodnota je0. Úroveň2knižnicaumqtt.simplenepodporuje.
Je dôležité, aby parametre topic, ako aj
msg boli serializovateľné do poľa bytov. Ak by ste sa
napríklad pokúsili odoslať priamo slovník, došlo by k vyvolaniu výnimky
TypeError. Slovník preto pred odoslaním prevedte na reťazec
pomocou balíka json.
Príklad použitia môže vyzerať takto:
topic = 'kpi/kronos/humidity/ab123cd'
message = {
'dt': '2024-10-30T16:55:06Z',
'metrics': [
{
'dt': '2024-10-30T16:50:00Z',
'name': 'humidity',
'unit': 'percent',
'value': 77
}
]
}
client.publish(topic, json.dumps(message))Poznámka
Správa s príznakom retain zostane na brokeri uložená,
kým ju nenahradí iná správa s týmto príznakom. Ak chcete uloženú správu
z témy odstrániť, odošlite do tejto témy prázdnu správu s príznakom
retain:
client.publish(topic, b'', retain=True)Z príkazového riadka to isté urobíte nástrojom
mosquitto_pub s prepínačmi -r (príznak
retain) a -n (prázdna správa):
$ mosquitto_pub -h <broker> -t <téma> -r -nPrijímanie správ z MQTT brokera
Pokiaľ chceme správy z MQTT brokera prijímať, musíme sa prihlásiť na ich odber z konkrétnej témy. Samozrejme naraz je možné byť prihlásený na odber správ z viacerých tém.
Na zabezpečenie prijímania správ je potrebné spraviť nasledovné:
- vytvoriť tzv. callback funkciu, ktorá sa zavolá vtedy, keď zariadenie dostane správu
- prihlásiť sa na odber správ do príslušnej témy
- skontrolovať dostupnosť správ na brokeri
Vytvorenie callback funkcie
Callback funkcia je normálna funkcia, ktorá však musí mať dva parametre:
topic- názov témy, v ktorej došlo k prijatiu správy, amessage- samotná prijatá správa.
Oba parametre sú reprezentované postupnosťou bytov. Preto ich netreba zabudnúť konvertovať do požadovaných údajových typov.
Funkcia nič nevracia.
Jednoduchá podoba callback funkcie, v ktorej je téma vo formáte reťazec a správa je prenášaná vo formáte JSON, môže vyzerať nasledovne:
def on_message(topic: bytes, message: bytes):
topic = topic.decode('utf-8')
message = json.loads( message )
print(f'>>> {topic}: {message}')Callback funkciu je potrebné zaregistrovať u klienta ešte
predtým, ako dôjde k pripojeniu k MQTT brokeru, resp. predtým,
ako dôjde k prihláseniu sa na odber správ z konkrétnej témy. To je možné
zabezpečiť volaním metódy .set_callback() nad objektom
klienta. Táto metóda má jediný parameter, ktorým je adresa
callback funkcie.
Zaregistrovanie vytvorenej callback funkcie
on_message() bude vyzerať takto:
client = MQTTClient(CLIENT_ID, server)
client.set_callback(on_message)
client.connect()Prihlásenie sa na odber správ
Prihlásiť sa na odber tém je možné pomocou metódy
.subscribe() nad objektom klienta. Táto metóda má dva
parametre:
topic- názov témy, do ktorej sa chceme prihlásiť na odber správ, aqos- úroveň QoS, ktorá môže byť0alebo1(úroveň2knižnicaumqtt.simplenepodporuje).
Metóda nič nevracia.
Prihlásiť sa na odber je možné až potom, keď je klient pripojený k MQTT brokeru. Okrem toho musí mať klient zadefinovanú callback funkciu. Ak to tak nie je, prihlásenie na odber skončí s výnimkou.
Príklad prihlásenia sa na odber správ z témy
kpi/hyperion/switch/cd456ef bude vyzerať takto:
client = MQTTClient(CLIENT_ID, server)
client.set_callback(on_message)
client.connect()
client.subscribe('kpi/hyperion/switch/cd456ef')Kontrola doručených správ
Overiť dostupnosť správ je však potrebné vykonať ručne. To je možné vykonať dvoma metódami nad objektom MQTT klienta:
.check_msg()- Neblokujúca metóda. Skontroluje, či klientovi neprišla od brokera správa. Ak neprišla, metóda hneď skončí. Ak prišla, spracuje ju pomocou callback funkcie a skončí..wait_msg()- Blokujúca metóda. Zastaví vykonávanie programu a bude čakať na prijatie jednej správy. Tú následne spracuje pomocou callback funkcie a skončí.
Metódu .wait_msg() je možné s výhodou využiť v
prípadoch, keď funkcionalita zariadenia je vyslovene závislá od správ,
ktoré prijme zo servera. V tomto prípade môžeme hovoriť o okamžitom
spracovaní prijatých správ.
Príklad jej použitia môže vyzerať nasledovne:
while True:
print('Waiting for message...')
client.wait_msg() # blocking call
print('Message was processed')Naopak metóda .check_msg() je výhodná vtedy, ak je
potrebné len rýchlo overiť a spracovať prijatú správu a následne napr.
zaspať. Broker však správy nedrží, kým si ich klient nevyzdvihne –
posiela ich len klientom, ktorí sú práve pripojení a prihlásení na
odber. Zariadenie, ktoré väčšinu času spí, by tak správy odoslané počas
spánku nedostalo. Preto je v tomto prípade veľmi dôležité, aby správa
mala v danej téme príznak retain. Broker si ju potom uloží
a pošle ju každému klientovi hneď, ako sa na odber témy prihlási – teda
aj zariadeniu po jeho prebudení.
Príklad jej použitia môže vyzerať nasledovne:
client.check_msg()
machine.deepsleep(10 * 1000)Poznámka
Môže sa stať, že pri overovaní dostupnosti správ pomocou volania
metódy .check_msg() nedôjde k prijatiu správy aj napriek
tomu, že je v téme uložená správa s príznakom retain.
Broker ju totiž pošle až chvíľu po tom, ako potvrdí prihlásenie na
odber, a okamžité volanie metódy .check_msg() tak môže
prísť príliš skoro. V tom prípade si pred jej použitím na chvíľu
“zdriemnite” zavolaním funkcie sleep() alebo metódu
zavolajte dvakrát po sebe:
sleep(0.1)
client.check_msg()
machine.deepsleep(10 * 1000)Ďalšie zdroje
MicroPython: umqtt.simple - GitHub repozitár s implementáciou modulu
umqtt.simpleMicroPython: umqtt.robust - GitHub repozitár s implementáciou modulu
umqtt.robust