Què és MQTT
MQTT és una manera de comunicar dispositius d'Internet de les Coses entre ells. És un protocol lleuger i molt versàtil: es pot fer servir des d'un Arduino, una ESP32 o una Raspberry Pi, fins a un PC multinucli o els serveis d'Amazon Web Services.
Missatgeria amb intermediari
Quan cal comunicar dispositius per TCP/IP hi ha un gran ventall de protocols, i la clau és triar el més adequat; entendre com funciona el protocol ajuda a decidir. Hi ha altres opcions de missatgeria amb intermediari (message brokering), però la majoria de dispositius IoT fan servir MQTT.
Un sistema de missatgeria amb intermediari és un protocol de publicació / subscripció basat en el model hub and spoke: el model del transport aeri, equivalent als radis d'una roda de bicicleta, on tots els moviments dels radis passen pel centre. Aquest centre és el broker.
Cap client parla directament amb un altre: tots parlen amb el broker. El sensor no sap qui llegeix les seves dades, i la pantalla no sap qui les ha publicat. Aquest desacoblament és la gràcia de MQTT.
Paraules clau
Nou conceptes que sortiran a tot arreu. Els noms en anglès són els que veuràs a les eines i als logs.
| Concepte | Què és |
|---|---|
| Intermediari (broker) | Accepta els missatges dels clients i els envia als clients interessats. Cada missatge pertany a un tema. A vegades l'anomenen servidor (server). |
| Client (client) | Dispositiu o programa que publica un missatge sobre un tema, es subscriu a un tema, o fa les dues coses alhora. |
| Tema (topic) | El nom (o "lloc" al broker) d'un tipus de missatge. Els clients es subscriuen i publiquen a un tema. → temes i comodins |
| Publicació (publish) | Un client envia al broker un missatge indicant el nom del tema. |
| Subscripció (subscribe) | Un client indica al broker quins temes l'interessen. A partir d'aquí el broker li envia els missatges que s'hi publiquin. Un client es pot subscriure a diversos temes. |
| Cancel·lació de subscripció (unsubscribe) | Un client avisa el broker que vol deixar de rebre missatges d'un tema. |
| Qualitat del servei (QoS) | Com de robust és l'enviament d'un missatge davant de fallades (per exemple, de connectivitat). → QoS |
| Activitat (ping) | Per assegurar que la connexió segueix activa, el client envia de tant en tant un missatge PINGREQ i el broker respon amb un PINGRESP. |
| Desconnexió (disconnect) | El client es desconnecta enviant un missatge DISCONNECT. |
Temes (topics) i comodins
Els temes s'organitzen en nivells separats per barres, com les carpetes d'un disc: casa/planta0/sala/temperatura. Per subscriure's a molts temes alhora es fan servir els comodins.
Els tres símbols especials
+comodí d'un sol nivell: coincideix amb qualsevol nom en aquell nivell concret.casa/planta0/+/temperaturaagafa la temperatura de qualsevol habitació de la planta 0.#comodí de diversos nivells: coincideix amb tots els nivells que hi ha a partir d'aquell punt. Només pot anar al final del filtre:casa/planta0/#.$: els temes que comencen per$estan reservats per a les estadístiques internes del broker ($SYS/…). Els clients no hi poden publicar. De moment no hi ha cap estandardització oficial del seu contingut.
Els temes no cal "crear-los": existeixen en el moment en què algú hi publica. I els comodins només es poden fer servir per subscriure's; el tema d'una publicació ha de ser concret.
Prova un filtre
Escriu un filtre de subscripció i un tema publicat, i mira si coincideixen nivell per nivell.
Qui rep què
✔ vol dir que el missatge arriba. Aquesta taula s'ha comprovat contra un broker Mosquitto 2.0.18 real: subscrivint-se a cada filtre i publicant cada tema.
Cinc detalls que fan ensopegar
casa/planta0/#també coincideix ambcasa/planta0(el nivell "pare"), no només amb el que hi penja. Icasa/#coincideix ambcasa.- Un
#sol (mosquitto_sub -t '#') rep tot... menys els$SYS. Per veure'ls cal subscriure's explícitament a$SYS/#. - Un tema que comença amb
/té un primer nivell buit:/casano éscasa. (Per això s'aconsella no començar mai els temes amb barra.) - Els comodins no es poden publicar. El client de Mosquitto ho rebutja amb:
Error: Invalid publish topic 'a/+/b', does it contain '+' or '#'?. I un filtre mal format (a/#/b,casa/pl+/x) donaError: Invalid subscription topic …. - A la shell, escriu
'$SYS/#'entre cometes simples. Sense cometes, bash substitueix$SYSper res i acabes subscrit a/#.
Bones pràctiques per dissenyar els temes
- No comencis amb
/i evita espais, accents i majúscules: els temes distingeixen majúscules de minúscules. - Ordena de general a específic:
aula/p1/temperatura,aula/p1/humitat. - Posa a l'estructura qui és el dispositiu (
aula/p1/…) i deixa al missatge el valor. - Subscriu-te amb filtres precisos. Amb
#et ve tot el tràfic, i en un sistema real és molt.
Qualitat del servei (QoS)
MQTT té un mecanisme de qualitat del servei: la manera de gestionar la robustesa de l'enviament d'un missatge davant de fallades. Hi ha tres nivells.
| Nivell | Garantia | Paquets | Pot passar… |
|---|---|---|---|
| QoS 0 | at most once (com a màxim un cop): s'envia una sola vegada | PUBLISH | que, si hi ha una fallada, algun missatge no arribi |
| QoS 1 | at least once (almenys un cop): s'envia fins que es confirma l'entrega | PUBLISH + PUBACK | que el subscriptor rebi algun missatge repetit |
| QoS 2 | exactly once (exactament un cop): cada missatge s'entrega, i només una vegada | PUBLISH + PUBREC + PUBREL + PUBCOMP | res d'això, però és el més costós |
Un nivell més alt vol dir més missatges de verificació i, per tant, més càrrega. Quin triar depèn de la fiabilitat que necessita el sistema.
Els paquets d'un enviament, vistos des del client
Sortida real de mosquitto_pub -d (mode debug) per a cada nivell:
sending CONNECT received CONNACK (0) sending PUBLISH (d0, q0, r0, m1, 'casa/sala/temperatura', ... (4 bytes)) sending DISCONNECT
sending CONNECT received CONNACK (0) sending PUBLISH (d0, q1, r0, m1, 'casa/sala/temperatura', ... (4 bytes)) received PUBACK (Mid: 1, RC:0) sending DISCONNECT
sending CONNECT received CONNACK (0) sending PUBLISH (d0, q2, r0, m1, 'casa/sala/temperatura', ... (4 bytes)) received PUBREC (Mid: 1) sending PUBREL (m1) received PUBCOMP (Mid: 1, RC:0) sending DISCONNECT
(Abreujat: s'ha tret el prefix Client … de cada línia.) Els números d, q, r i m són DUP, QoS, RETAIN i l'identificador de missatge.
El QoS efectiu és el més baix dels dos
El QoS d'un missatge té dues cares: el del publicador i el de la subscripció. El broker entrega al subscriptor amb el més petit dels dos. Si es publica amb QoS 2 i l'abonat s'ha subscrit amb QoS 1, li arriba com a QoS 1; si s'hi ha subscrit amb QoS 0, li arriba sense confirmació, per molt que el publicador hagi pagat el cost del QoS 2.
Comprovat: publicant amb -q 2 i subscrit amb -q 1, el subscriptor rep PUBLISH (… q1 …) i respon amb un PUBACK.
Quin triar?
- QoS 0: lectures que es repeteixen sovint (una temperatura cada segon). Si se'n perd una, la següent la substitueix.
- QoS 1: ordres que es poden repetir sense conseqüències (posa el llum a "on").
- QoS 2: quan un duplicat fa mal (comptadors, esdeveniments que no es poden comptar dos cops).
Analogia amb el servei postal
Una oficina de correus envia i rep cartes. Els clients poden enviar una carta, rebre-la, o les dues coses alhora. Per enviar-la només cal posar un missatge dins un sobre, escriure'n l'adreça i lliurar-la al servei postal, que l'encamina al receptor previst. La clau és que les cartes passen per l'oficina, i l'oficina ha de conèixer els seus clients: és un sistema de missatgeria amb intermediari, amb model hub and spoke.
| Correus | MQTT |
|---|---|
| Oficina de correus | el broker (el servidor) |
| Persona que envia o rep cartes | un client |
| Contingut de la carta | el missatge (payload) |
| Adreça del sobre | el tema (topic) |
| Enviar una carta | publish |
| Rebre cartes d'un tema, o subtema | subscribe |
On falla l'analogia: una carta arriba a un sol destinatari, però un missatge MQTT arriba a tots els subscriptors del tema, i si no n'hi ha cap, el broker simplement l'oblida (a menys que sigui retained, vegeu més avall). Per això es diu d'un a molts.
MQTT vs HTTP (o REST)
No són rivals: serveixen a coses diferents. HTTP és molt bo per consultar i modificar recursos; MQTT, per repartir esdeveniments i telemetria.
| HTTP / REST | MQTT | |
|---|---|---|
| Model | petició / resposta: el client pregunta | publicació / subscripció: el broker reparteix quan hi ha dades |
| Pes | moltes capçaleres a cada petició | un paquet pot ocupar només 2 bytes (PINGREQ és C0 00) |
| Un a molts | cal una acció POST per a cada destinatari | el broker distribueix el missatge només als clients interessats |
| Connexió | sol ser petició a petició | una connexió TCP oberta que es manté viva amb PINGREQ |
| Acoblament | el client ha de conèixer l'adreça del servidor que rebrà les dades | qui publica i qui rep no es coneixen |
Estructura d'un missatge MQTT
Una de les bases de l'agilitat de MQTT és la definició senzilla dels seus missatges (paquets). Cada paquet té fins a tres parts.
- Capçalera fixa. Ocupa de 2 a 5 bytes. Porta un codi de control (tipus de paquet) i la longitud restant. La longitud es codifica en 1 a 4 bytes, dels quals s'usen els 7 primers bits i el vuitè és un bit de continuïtat.
- Capçalera variable. Informació addicional que només cal en alguns paquets (per exemple, l'identificador de paquet o el tema).
- Contingut (payload). El contingut real del missatge. Pot arribar a 256 MB, tot i que en implementacions reals el màxim sol ser molt menor (de 2 a 4 kB).
Mira els bytes (paquets reals)
Captura d'un client escrit a mà amb sockets contra un Mosquitto 2.0.18 (MQTT 3.1.1). Tria un paquet i passa el cursor per sobre dels bytes.
Tipus de paquet i codis de control
| Paquet | Primer byte | Sentit | Per a què |
|---|---|---|---|
CONNECT | 0x10 | client → broker | obre la sessió (id del client, keep-alive…) |
CONNACK | 0x20 | broker → client | accepta (o rebutja) la connexió |
PUBLISH | 0x30 + flags | ambdós | porta un missatge d'un tema |
PUBACK | 0x40 | ambdós | confirma un PUBLISH de QoS 1 |
PUBREC | 0x50 | ambdós | QoS 2: "rebut" |
PUBREL | 0x62 | ambdós | QoS 2: "pots alliberar-lo" |
PUBCOMP | 0x70 | ambdós | QoS 2: "completat" |
SUBSCRIBE | 0x82 | client → broker | demana subscriure's a un o més filtres |
SUBACK | 0x90 | broker → client | confirma la subscripció i el QoS concedit |
UNSUBSCRIBE | 0xA2 | client → broker | cancel·la una subscripció |
UNSUBACK | 0xB0 | broker → client | confirma la cancel·lació |
PINGREQ | 0xC0 | client → broker | "segueixo viu" |
PINGRESP | 0xD0 | broker → client | resposta al ping |
DISCONNECT | 0xE0 | client → broker | desconnexió neta |
El primer byte té dues meitats: els 4 bits alts són el tipus (1 = CONNECT… 14 = DISCONNECT) i els 4 baixos són flags. A PUBLISH els flags són DUP, QoS (2 bits) i RETAIN: per això 0x32 és un PUBLISH de QoS 1, 0x34 de QoS 2 i 0x31 un PUBLISH retained. A PUBREL, SUBSCRIBE i UNSUBSCRIBE els flags valen sempre 0010: per això l'especificació dona 0x62, 0x82 i 0xA2 (verificat a les captures de dalt). MQTT 5 hi afegeix AUTH (0xF0).
Com es codifica la longitud restant
| Longitud (bytes) | Bytes de la capçalera |
|---|---|
| 0 | 00 |
| 127 | 7F |
| 128 | 80 01 |
| 16 383 | FF 7F |
| 16 384 | 80 80 01 |
| 268 435 455 (el màxim: ≈ 256 MB) | FF FF FF 7F |
Cada byte aporta 7 bits de dades; si el bit alt val 1, vol dir "continua amb el byte següent". Els números van de menys a més significatiu. Exemple real: un PUBLISH de 212 bytes restants va com a 30 D4 01 (0xD4 = 84 amb el bit de continuació, 0x01 = 1 × 128; 84 + 128 = 212).
Coses que et trobaràs a la pràctica
No formen part del "mínim", però Node-RED i els clients MQTT te les posaran al davant ben aviat.
Missatges retinguts (retain)
Normalment un missatge s'entrega als subscriptors que hi són; si no n'hi ha cap, es perd. Si el publiques amb el flag retain, el broker se'n queda una còpia i l'entrega a qualsevol que s'hi subscrigui més tard. Per això és molt útil per a estats ("el llum és encès").
mosquitto_pub -r -t casa/sala/temperatura -m 21.5 # publica i retén mosquitto_pub -r -n -t casa/sala/temperatura # esborra el retingut (missatge buit)
Compte: el broker guarda l'últim missatge retained, no l'últim missatge. Comprovat: després d'un -r 21.5, un 22.0 sense -r arriba als subscriptors actuals, però un subscriptor nou rep 21.5, caducat. Per esborrar-lo cal publicar un missatge buit amb -r.
Testament (last will)
En connectar-se, un client pot deixar al broker un missatge "testament" i un tema. Si el client desapareix sense avisar (la connexió es talla sense DISCONNECT), el broker el publica per ell. Si se'n va amb un DISCONNECT net, no es publica.
mosquitto_sub -i sensor1 -t dummy --will-topic casa/sensor1/estat --will-payload offline
Comprovat: amb un kill -9 el monitor va rebre casa/sensor1/estat offline; amb un Ctrl-C (que envia DISCONNECT) no va rebre res.
Identificador de client i sessions
- Cada client es presenta al broker amb un identificador (
-i abonat). Ha de ser únic: si es connecta un segon client amb el mateix, el broker tanca el primer (log real:Client dup already connected, closing old connection.). - Per defecte la sessió és clean: en desconnectar-se, el broker oblida les subscripcions. Amb
-c(i un-ifix) la sessió és persistent: amb una subscripció de QoS 1, els missatges publicats mentre no hi eres t'esperen. Comprovat: amb-cel client va rebrementre dormsen tornar; sense-cno va rebre res. - Els clients envien un
PINGREQdins l'interval de keep-alive (el log de Mosquitto mostrak60: 60 s per defecte als seus clients).
Ports, versions i seguretat
- Port
1883(text pla) i8883(TLS) són els habituals. Existeix també MQTT sobre WebSockets, que fan servir els navegadors. - Les captures d'aquesta pàgina són MQTT 3.1.1 (nivell de protocol
4alCONNECT). MQTT 5 és la versió més nova, amb més característiques; els conceptes d'aquesta pàgina valen per a les dues. - Mosquitto 2.x, per defecte, només accepta connexions locals. Això mostra el seu log en arrencar sense configuració:
Starting in local only mode. Connections will only be possible from clients running on this machine.(vegeu l'apartat d'eines per obrir-lo a la xarxa de l'aula). - Un broker obert i sense contrasenya és un forat: qualsevol equip de la xarxa hi pot publicar i llegir. A classe, a la xarxa de l'aula, és acceptable; a internet, no.
Eines per provar-ho: Mosquitto
Un broker i dos clients de línia d'ordres són tot el que cal per entendre MQTT amb les mans. Totes les sortides d'aquesta pàgina són de Mosquitto 2.0.18.
Instal·lar-lo (Debian / Ubuntu)
sudo apt install mosquitto mosquitto-clients systemctl status mosquitto
mosquitto és el broker i mosquitto-clients porta mosquitto_pub i mosquitto_sub. Si el servei ja corre, els dos clients es connecten per defecte a localhost:1883.
Les opcions que farem servir
| Opció | Efecte |
|---|---|
-t TEMA | tema de publicació, o filtre de subscripció |
-m MSG | (mosquitto_pub) el missatge. -n envia un missatge buit |
-v | (mosquitto_sub) mostra també el tema abans de cada missatge |
-q 0|1|2 | QoS de la publicació o de la subscripció |
-r | (mosquitto_pub) publica amb el flag retain |
-d | mode debug: escriu cada paquet que envia o rep |
-i ID · -c | identificador del client · sessió persistent |
-h HOST · -p PORT | broker (per defecte localhost i 1883) |
-W SEGONS | (mosquitto_sub) surt passats aquests segons |
--will-topic · --will-payload | testament del client |
Obrir el broker a la xarxa de l'aula
Si volen provar entre dos ordinadors, el broker ha de tenir un listener a la xarxa. Posa això a un fitxer, per exemple /etc/mosquitto/conf.d/aula.conf, i reinicia el servei (sudo systemctl restart mosquitto):
listener 1883 allow_anonymous true
Comprovat: amb aquesta configuració el broker escolta a totes les adreces (0.0.0.0:1883) i accepta clients sense usuari. Només per a la xarxa de l'aula. Un broker obert així no s'ha d'exposar mai a internet.
Exemples en línia i referències
Exemple en línia
Una ESP32 programada en MicroPython que fa servir MQTT (Wokwi), interaccionant amb una ESP32 en Snap!
Per saber-ne més
Pràctica 2
Els exercicis 1 a 7 es fan al terminal amb Mosquitto: dos o tres terminals oberts alhora. Escriu primer el que esperes veure i comprova-ho després.
1. Un broker i dos clients
Instal·la Mosquitto i comprova que el broker corre. A un terminal, subscriu-te a aula/prova; a un altre, publica-hi un missatge. Després publica a aula/altre. Què passa?
Pista 1
Terminal 1: mosquitto_sub -v -t aula/prova. Terminal 2: mosquitto_pub -t aula/prova -m hola.
Pista 2
El missatge a aula/altre no arriba a ningú, i mosquitto_pub no dona cap error: qui publica no sap si algú l'escolta.
2. Prediu abans de provar
Subscriu-te a aula/+/temperatura. Abans de publicar, escriu quins d'aquests temes arribaran: aula/p1/temperatura, aula/p1/humitat, aula/p2/temperatura, aula/p1/laboratori/temperatura, aula/temperatura. Comprova-ho. Repeteix amb aula/#.
Pista 1
El + ocupa exactament un nivell: ni zero, ni dos.
Pista 2
Arriben aula/p1/temperatura i aula/p2/temperatura. Amb aula/# arriben els cinc.
3. Mira els paquets
Publica amb -d i QoS 0, 1 i 2. Apunta quins paquets surten a cada cas. Després subscriu-te amb -q 0, publica amb -q 2 i mira amb quin QoS arriba al subscriptor.
Pista 1
Fes servir mosquitto_pub -d -q 1 -t aula/prova -m hola i busca PUBACK.
Pista 2
Al subscriptor (mosquitto_sub -d -q 0 …) busca la línia received PUBLISH (d0, q0 …: arriba com a QoS 0.
4. Missatges retinguts
Publica -r un missatge a un tema abans que ningú s'hi subscrigui. Subscriu-te'n ara: què passa? Publica una nova lectura sense -r i subscriu-te des d'un terminal nou. Finalment, esborra el retingut.
Pista 1
El broker et dona el missatge retingut en el moment de subscriure't.
Pista 2
El subscriptor nou rep la lectura retained vella, no la nova. Per esborrar-la, mosquitto_pub -r -n -t el/teu/tema.
5. El testament
Fes dos "sensors" amb testament (mosquitto_sub amb --will-topic) i un monitor subscrit als estats. Mata un amb kill -9 i l'altre amb Ctrl-C. Quin dels dos fa publicar el testament?
Pista 1
kill -9 talla la connexió sense DISCONNECT; Ctrl-C n'envia un.
Pista 2
Només el que has matat amb kill -9. Mira també el log del broker: closed its connection per al que has matat, disconnected per a l'altre.
6. Sessions persistents
Subscriu-te amb -c -i alumne1 -q 1 a un tema i surt amb Ctrl-C. Publica un missatge amb -q 1 mentre hi ets fora. Torna a connectar-te amb les mateixes opcions. Repeteix sense -c.
Pista 1
Amb -c i -q 1 el missatge t'espera. Sense -c, el broker ha oblidat la subscripció en sortir.
Pista 2
Cal el mateix -i les dues vegades: és la clau amb què el broker et reconeix.
7. Què fa el broker per dins
Subscriu-te a '$SYS/#' i deixa-ho córrer uns segons. Després prova amb '#' a part: veus els $SYS? Per què?
Pista 1
Fes servir cometes simples. Sense elles, la shell esborra $SYS i et subscrius a /#.
Pista 2
Els temes que comencen per $ no coincideixen amb filtres que comencen per un comodí (# o +).
8. Dissenya la jerarquia de temes
Una aula té 3 zones. Cada zona té un sensor de temperatura, un de llum i un llum controlable. Dissenya els temes: un per a cada lectura, un per a cada ordre, i els filtres per (a) tota la temperatura de l'aula, (b) tot el que passa a una zona.
Pista 1
De general a específic: aula/zona1/temperatura, aula/zona1/llum…
Pista 2
Un filtre per (a): aula/+/temperatura; per (b): aula/zona1/#. Distingeix lectures d'ordres, per exemple aula/zona1/llum/ordre, perquè l'actuador no es respongui a si mateix.
9. Decodifica a mà un paquet
Aquest paquet és real. Digues el tipus, el QoS, si és retained, el tema, l'identificador i el contingut:
32 1D 00 15 63 61 73 61 2F 73 61 6C 61 2F 74 65 6D 70 65 72 61 74 75 72 61 00 0A 32 31 2E 35
Pista 1
Primer byte 0x32: tipus 3 (PUBLISH); flags 0010. Segon byte: longitud restant (0x1D). Després, dos bytes de longitud del tema.
Pista 2
QoS 1, sense retain, longitud restant 29, tema casa/sala/temperatura (21 bytes), identificador de paquet 10 (00 0A) i contingut 21.5. Hi ha identificador perquè el QoS és 1.
Entrena't
Quatre tipus d'exercici que surten diferents cada cop. Pensa la resposta, mostra la solució i compara.