9.18. MQTT, byte voor byte¶
Op dit punt heeft de cam alles wat nodig is om met een echte dienst op het open internet te communiceren: een TCP-socket, TLS om die in te pakken, DNS om de peer een naam te geven, en asyncio om hetzelfde script ander werk te laten doen terwijl de verbinding openstaat. MQTT is het eerste wire-protocol dat dit alles samenbrengt tot iets wat een ingezet product daadwerkelijk gebruikt.
Deze pagina behandelt het protocol zelf – het on-the-wire-formaat, de rollen die elke deelnemer speelt, en de afwegingen in het ontwerp ervan – eerlijk genoeg zodat de meegeleverde mqtt-client een voor de hand liggende verpakking van het reeds bekende lijkt in plaats van een sprong in het diepe.
9.18.1. Pub/sub versus request/response¶
HTTP – het protocol waar de meeste cam-projecten als eerste naar grijpen – is request/response. Een client vraagt een specifieke server om een specifieke resource; de server antwoordt. Elke uitwisseling is een-op-een, en beide kanten kennen elkaars adres vooraf.
MQTT is publish/subscribe. Clients verbinden met een derde partij in het midden, de broker genaamd. Een publisher stuurt een bericht naar een benoemd topic zonder te weten of er zich iets van aan te trekken wie er luistert. Een subscriber vertelt de broker welke topics hij wil en ontvangt daarna elk bericht dat naar die topics wordt gepubliceerd. De broker is de fan-out: één publicatie op yard-cam/motion bereikt elk apparaat dat geabonneerd is op yard-cam/motion, ongeacht of dat er nul, één of vijftig zijn.
Uit die verandering van model volgen drie dingen:
Ontkoppeling. Publishers hoeven niet te weten dat subscribers bestaan. Subscribers kunnen komen en gaan zonder dat de publisher het merkt. Een tweede dashboard toevoegen is één regel code op het nieuwe dashboard; de cam verandert niet.
Fan-out. De broker handelt elk duplicaat af, dus de cam stuurt één pakket ongeacht hoeveel apparaten het lezen. Dat is de use case waarvoor MQTT is gebouwd.
Asymmetrie. De broker is nu een vereist stuk infrastructuur – zonder broker werkt het protocol niet. Voor thuisprojecten is dit meestal een gratis publieke broker (
test.mosquitto.org,broker.hivemq.com) of een kleine die je zelf draait.
9.18.2. Topics¶
Topics zijn met slashes gescheiden strings. De conventie is het meest algemene links, het meest specifieke rechts:
yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3
Twee wildcards werken in subscriptions (niet in publicaties):
+matcht één enkel niveau.+/motionabonneert op het motion-topic van elke cam;yard-cam/+abonneert op elk yard-cam-subtopic.#matcht een of meer afsluitende niveaus.yard-cam/#abonneert opyard-cam/motion,yard-cam/temperature,yard-cam/temperature/sensor-3en alles anders onderyard-cam/. Het moet aan het einde van de subscription staan.
Topic-strings zijn hoofdlettergevoelig. Volgens de specificatie markeert een voorafgaande $ broker-interne topics ($SYS/...) waar publishers niet naartoe zouden moeten schrijven.
9.18.3. Het pakketformaat¶
MQTT draait over TCP. Elk control-pakket begint met een fixed header van één byte, gevolgd door een Remaining Length-veld van variabele lengte, vervolgens een pakkettype-specifieke variable header en daarna de payload. Hetzelfde buitenste formaat dekt elk commando – CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT en de rest – en dat is waarom een MQTT-client in een paar honderd regels geschreven kan worden.
De fixed header is één byte:
Bits 7..4 vormen het control packet type.
0x3is PUBLISH (dus de eerste byte begint meestal met0x3?).0x1is CONNECT,0x2CONNACK,0x8SUBSCRIBE,0xCPINGREQ,0xEDISCONNECT, enzovoort.Bits 3..0 zijn pakkettype-specifieke flags. Voor PUBLISH coderen de flags de DUP-retransmit-flag, het QoS-niveau (2 bits) en de RETAIN-flag.
De Remaining Length is een integer van variabele lengte van 1 tot 4 bytes die elke byte erna telt. De bovenste bit van elke byte is een continuation-markering – 1 betekent “er volgt nog een length-byte”, 0 betekent “dit is de laatste”. Een lengte onder 128 past in één byte; grotere payloads gebruiken meer. De maximaal gecodeerde lengte is 256 MiB.
Voor een PUBLISH is de variable header de topic-naam – een lengte van 2 bytes, dan UTF-8-bytes – gevolgd door een packet identifier van 2 bytes die alleen bestaat wanneer QoS 1 of 2 is. De resterende bytes vormen de payload, die door het protocol als opake bytes wordt behandeld.
Een minimale QoS-0 PUBLISH van ok naar a/b is:
30 07 00 03 'a' '/' 'b' 'o' 'k'
30– PUBLISH, alle flags nul.07– er volgen 7 bytes.00 03– topic-lengte 3.'a' '/' 'b'– topic.'o' 'k'– payload.
Negen bytes op de lijn en het bericht komt aan bij elke subscriber op a/b op de broker.
9.18.4. QoS-niveaus¶
Quality-of-Service bepaalt hoe hard de broker (en client) werken om aflevering te garanderen. De drie niveaus:
QoS 0 – hoogstens één keer. Fire and forget. Het PUBLISH-pakket wordt verzonden en nooit bevestigd. Als TCP aflevert, stuurt de broker het door. Als de verbinding halverwege wegvalt, is het bericht verloren. De meeste sensortelemetrie is prima op QoS 0 – één gemiste temperatuurmeting in een stroom die om de 30 seconden uitzendt maakt niet uit.
QoS 1 – ten minste één keer. De publisher voegt een packet identifier toe en wacht op een PUBACK. Als er geen PUBACK aankomt voor een timeout, verzendt de publisher opnieuw met de DUP-flag gezet. De broker kan uiteindelijk hetzelfde bericht twee keer afleveren aan een subscriber op hetzelfde niveau; de subscriber moet bereid zijn duplicaten af te handelen.
QoS 2 – precies één keer. Een handshake in vier stappen (PUBREC / PUBREL / PUBCOMP) zorgt ervoor dat het bericht precies één keer aankomt, zelfs over reconnects heen. Duur in round-trips en broker-state. Weinig cam-apps hebben het nodig.
De meegeleverde mqtt-client implementeert QoS 0 en QoS 1; QoS 2 werpt een foutmelding als je erom vraagt. Voor een cam die sensormetingen rapporteert is QoS 0 vrijwel altijd het juiste antwoord.
9.18.5. Retained-berichten en last will¶
Twee functies zijn de moeite waard om te kennen omdat ze veranderen wat de broker onthoudt over je topic.
RETAIN. Als een PUBLISH de RETAIN-flag gezet heeft, slaat de broker het bericht op en stuurt het naar elke toekomstige subscriber op het moment dat die zich abonneert. Zo handelt MQTT “wat is de huidige waarde?” af – een sensor publiceert zijn laatste meting als retained, en een dashboard dat zich tien minuten later abonneert ontvangt nog steeds de meest recente waarde in plaats van te wachten op de volgende publicatie. Opnieuw publiceren met hetzelfde topic overschrijft de retained-waarde; een lege payload publiceren wist hem.
Last will. Wanneer een client verbinding maakt, kan hij de broker een “last will and testament” geven: een topic, een payload, een QoS en een retain-flag. Als die client onrein de verbinding verbreekt – TCP RESET, stroomuitval, netwerkonderbreking zonder DISCONNECT-pakket – publiceert de broker de will namens de client. Subscribers zien het als de melding van de cam dat hij offline is gegaan. De cam zelf stuurt de will nooit; de broker doet dat, want tegen die tijd is de cam weg.
9.18.6. Keepalive en reconnect¶
CONNECT draagt een keepalive-interval in seconden. Als de client zo lang stil is geweest, beschouwt de broker hem als dood. Om dat te voorkomen, stuurt de client periodiek een PINGREQ (één byte: 0xC0) en krijgt een PINGRESP (0xD0) terug – de kleinste, goedkoopste heartbeat die het protocol kan dragen. De meeste cam-apps zetten keepalive op 30 of 60 seconden.
Als de TCP-verbinding wegvalt, merken beide kanten het en maken ze opnieuw verbinding vanaf nul. Subscriptions die vóór de onderbreking zijn gemaakt, zijn verloren tenzij de client bij het verbinden een persistente sessie gebruikte; voor eenvoudige cam-apps is het patroon van opnieuw abonneren bij reconnect korter en net zo goed.
Dit is genoeg om de MQTT-specificatie te lezen of zelf een client over een socket.socket te bouwen. De meegeleverde client in mqtt doet precies dat, plus een verstandige API voor applicatiecode.