# IoT Lab · primer contacte amb MQTT

El protocol MQTT

MQTT és el "idioma" amb què es parlen la majoria de dispositius d'Internet de les Coses. Els clients no es parlen entre ells: publiquen missatges sobre un tema i un intermediari (el broker) els fa arribar a qui s'hi ha subscrit.

El sensor publica
mosquitto_pub -t casa/sala/temperatura -m 21.5
La pantalla s'hi ha subscrit
mosquitto_sub -v -t 'casa/#'
La pantalla rep
casa/sala/temperatura 21.5

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.

ConcepteQuè é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/+/temperatura agafa 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 amb casa/planta0 (el nivell "pare"), no només amb el que hi penja. I casa/# coincideix amb casa.
  • 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: /casa no és casa. (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) dona Error: Invalid subscription topic ….
  • A la shell, escriu '$SYS/#' entre cometes simples. Sense cometes, bash substitueix $SYS per 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.

NivellGarantiaPaquetsPot passar…
QoS 0at most once (com a màxim un cop): s'envia una sola vegadaPUBLISHque, si hi ha una fallada, algun missatge no arribi
QoS 1at least once (almenys un cop): s'envia fins que es confirma l'entregaPUBLISH + PUBACKque el subscriptor rebi algun missatge repetit
QoS 2exactly once (exactament un cop): cada missatge s'entrega, i només una vegadaPUBLISH + PUBREC + PUBREL + PUBCOMPres 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:

QoS 0
sending CONNECT
received CONNACK (0)
sending PUBLISH (d0, q0, r0, m1, 'casa/sala/temperatura', ... (4 bytes))
sending DISCONNECT
QoS 1
sending CONNECT
received CONNACK (0)
sending PUBLISH (d0, q1, r0, m1, 'casa/sala/temperatura', ... (4 bytes))
received PUBACK (Mid: 1, RC:0)
sending DISCONNECT
QoS 2
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.

CorreusMQTT
Oficina de correusel broker (el servidor)
Persona que envia o rep cartesun client
Contingut de la cartael missatge (payload)
Adreça del sobreel tema (topic)
Enviar una cartapublish
Rebre cartes d'un tema, o subtemasubscribe

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 / RESTMQTT
Modelpetició / resposta: el client preguntapublicació / subscripció: el broker reparteix quan hi ha dades
Pesmoltes capçaleres a cada peticióun paquet pot ocupar només 2 bytes (PINGREQ és C0 00)
Un a moltscal una acció POST per a cada destinatariel 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
Acoblamentel client ha de conèixer l'adreça del servidor que rebrà les dadesqui 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 fixa2 a 5 bytes · obligatòria
Capçalera variableopcional
Contingut (payload)opcional
  • 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

PaquetPrimer byteSentitPer a què
CONNECT0x10client → brokerobre la sessió (id del client, keep-alive…)
CONNACK0x20broker → clientaccepta (o rebutja) la connexió
PUBLISH0x30 + flagsambdósporta un missatge d'un tema
PUBACK0x40ambdósconfirma un PUBLISH de QoS 1
PUBREC0x50ambdósQoS 2: "rebut"
PUBREL0x62ambdósQoS 2: "pots alliberar-lo"
PUBCOMP0x70ambdósQoS 2: "completat"
SUBSCRIBE0x82client → brokerdemana subscriure's a un o més filtres
SUBACK0x90broker → clientconfirma la subscripció i el QoS concedit
UNSUBSCRIBE0xA2client → brokercancel·la una subscripció
UNSUBACK0xB0broker → clientconfirma la cancel·lació
PINGREQ0xC0client → broker"segueixo viu"
PINGRESP0xD0broker → clientresposta al ping
DISCONNECT0xE0client → brokerdesconnexió 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
000
1277F
12880 01
16 383FF 7F
16 38480 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 -i fix) la sessió és persistent: amb una subscripció de QoS 1, els missatges publicats mentre no hi eres t'esperen. Comprovat: amb -c el client va rebre mentre dorms en tornar; sense -c no va rebre res.
  • Els clients envien un PINGREQ dins l'interval de keep-alive (el log de Mosquitto mostra k60: 60 s per defecte als seus clients).

Ports, versions i seguretat

  • Port 1883 (text pla) i 8883 (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 4 al CONNECT). 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 TEMAtema 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|2QoS de la publicació o de la subscripció
-r(mosquitto_pub) publica amb el flag retain
-dmode debug: escriu cada paquet que envia o rep
-i ID · -cidentificador del client · sessió persistent
-h HOST · -p PORTbroker (per defecte localhost i 1883)
-W SEGONS(mosquitto_sub) surt passats aquests segons
--will-topic · --will-payloadtestament 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!


    

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.

Amb el broker, al terminal

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?

Necessites: Mosquitto
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/#.

Necessites: comodins
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.

Necessites: QoS
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.

Necessites: retain
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?

Necessites: 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.

Necessites: sessions
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 +).

Sense ordinador

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.

Necessites: bones pràctiques
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.