9.18. MQTT, byte för byte

Vid det här laget har kameran alla delar den behöver för att kommunicera med en riktig tjänst på det öppna internet: en TCP-socket, TLS för att kapsla in den, DNS för att namnge motparten och asyncio för att låta samma skript göra annat arbete medan anslutningen är öppen. MQTT är det första trådprotokollet som drar samman allt detta till något som en driftsatt produkt faktiskt använder.

Den här sidan behandlar själva protokollet – formatet på tråden, de roller varje deltagare spelar och avvägningarna i dess utformning – ärligt nog för att den medföljande mqtt-klienten ska framstå som en uppenbar inpackning av det som redan är känt i stället för ett trossprång.

9.18.1. Pub/sub kontra request/response

HTTP – det protokoll de flesta kameraprojekt griper efter först – är request/response. En klient frågar en specifik server efter en specifik resurs; servern svarar. Varje utbyte är en-till-en, och båda ändarna känner varandras adress i förväg.

MQTT är publish/subscribe. Klienter ansluter till en tredje part i mitten som kallas broker. En publisher skickar ett meddelande till ett namngivet topic utan att veta eller bry sig om vem som lyssnar. En subscriber talar om för brokern vilka topics den vill ha och tar därefter emot varje meddelande som publiceras till de topics. Brokern är fan-outen: en publicering på yard-cam/motion når varje enhet som prenumererar på yard-cam/motion, oavsett om det finns noll, en eller femtio av dem.

Tre saker följer av detta byte av modell:

  • Frikoppling. Publishers behöver inte veta att subscribers existerar. Subscribers kan komma och gå utan att publishern märker det. Att lägga till en andra instrumentpanel är en kodrad på den nya instrumentpanelen; kameran ändras inte.

  • Fan-out. Brokern hanterar varje duplikat, så kameran skickar ett paket oavsett hur många enheter som läser det. Det är det användningsfall MQTT byggdes för.

  • Asymmetri. Brokern är nu en nödvändig infrastrukturdel – utan en sådan fungerar protokollet inte. För hemprojekt är detta vanligtvis en gratis publik broker (test.mosquitto.org, broker.hivemq.com) eller en liten som du kör själv.

En kamera som publicerar till ett yard-cam/motion-topic på en broker medan två webbläsarinstrumentpaneler och en molnarkiverare var och en tar emot samma meddelande.

9.18.2. Topics

Topics är snedstreckseparerade strängar. Konventionen är mest generell till vänster, mest specifik till höger:

yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3

Två jokertecken fungerar i prenumerationer (inte i publiceringar):

  • + matchar en enda nivå. +/motion prenumererar på varje kameras motion-topic; yard-cam/+ prenumererar på varje yard-cam-subtopic.

  • # matchar en eller flera efterföljande nivåer. yard-cam/# prenumererar på yard-cam/motion, yard-cam/temperature, yard-cam/temperature/sensor-3 och allt annat under yard-cam/. Det måste stå i slutet av prenumerationen.

Topic-strängar är skiftlägeskänsliga. Enligt specifikationen markerar ett inledande $ broker-interna topics ($SYS/...) som publishers inte bör skriva till.

9.18.3. Paketformatet

MQTT körs över TCP. Varje styrpaket inleds med ett en-byte fixed header följt av ett Remaining Length-fält av variabel längd, sedan ett pakettypspecifikt variable header och därefter payload. Samma yttre format täcker varje kommando – CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT och resten – vilket är anledningen till att en MQTT-klient kan skrivas på några hundra rader.

Byte-layouten för ett MQTT PUBLISH-paket som visar byten med fixed-header-typ och flaggor, fältet Remaining Length av variabel längd, topic-namnet, den valfria paketidentifieraren och payload-byten.

Det fasta huvudet är en byte:

  • Bit 7..4 är control packet type. 0x3 är PUBLISH (så den första byten börjar vanligtvis med 0x3?). 0x1 är CONNECT, 0x2 CONNACK, 0x8 SUBSCRIBE, 0xC PINGREQ, 0xE DISCONNECT och så vidare.

  • Bit 3..0 är pakettypspecifika flaggor. För PUBLISH kodar flaggorna DUP-återsändningsflaggan, QoS-nivån (2 bitar) och RETAIN-flaggan.

Remaining Length är ett heltal av variabel längd på 1 till 4 byte som räknar varje byte efter sig själv. Varje bytes översta bit är en fortsättningsmarkör – 1 betyder ”ytterligare en längdbyte följer”, 0 betyder ”detta är den sista”. En längd under 128 ryms i en byte; större payloads använder fler. Den maximala kodade längden är 256 MiB.

För en PUBLISH är variable header topic-namnet – en 2-byte-längd, sedan UTF-8-byte – följt av en 2-byte paketidentifierare som bara finns när QoS är 1 eller 2. De återstående byten är payloaden, som protokollet behandlar som ogenomskinliga byte.

En minimal QoS-0 PUBLISH av ok till a/b är:

30 07 00 03 'a' '/' 'b' 'o' 'k'
  • 30 – PUBLISH, alla flaggor noll.

  • 07 – 7 byte följer.

  • 00 03 – topic-längd 3.

  • 'a' '/' 'b' – topic.

  • 'o' 'k' – payload.

Nio byte på tråden och meddelandet landar hos varje subscriber till a/b på brokern.

9.18.4. QoS-nivåer

Quality-of-Service styr hur hårt brokern (och klienten) arbetar för att säkerställa leverans. De tre nivåerna:

QoS 0 – högst en gång. Skicka och glöm. PUBLISH-paketet skickas och bekräftas aldrig. Om TCP levererar vidarebefordrar brokern. Om anslutningen bryts mitt i sändningen är meddelandet borta. Det mesta av sensortelemetri klarar sig bra med QoS 0 – en enstaka missad temperaturavläsning i en ström som sänder var 30:e sekund spelar ingen roll.

QoS 1 – minst en gång. Publishern inkluderar en paketidentifierare och väntar på en PUBACK. Om ingen PUBACK kommer fram före en timeout sänder publishern om med DUP-flaggan satt. Brokern kan komma att leverera samma meddelande två gånger till en subscriber på samma nivå; subscribern måste vara beredd att hantera duplikat.

QoS 2 – exakt en gång. En fyrstegs handskakning (PUBREC / PUBREL / PUBCOMP) ser till att meddelandet landar exakt en gång, även över återanslutningar. Dyrt i tur-och-retur-resor och brokertillstånd. Få kameraappar behöver det.

Den medföljande mqtt-klienten implementerar QoS 0 och QoS 1; QoS 2 ger upphov till ett fel om du begär det. För en kamera som rapporterar sensoravläsningar är QoS 0 nästan alltid rätt svar.

9.18.5. Bevarade meddelanden och last will

Två funktioner är värda att känna till eftersom de ändrar vad brokern minns om ditt topic.

RETAIN. Om en PUBLISH har RETAIN-flaggan satt lagrar brokern meddelandet och vidarebefordrar det till varje framtida subscriber i samma ögonblick som de prenumererar. Det är så MQTT hanterar ”vad är det aktuella värdet?” – en sensor publicerar sin senaste avläsning bevarad, och en instrumentpanel som prenumererar tio minuter senare tar fortfarande emot det senaste värdet i stället för att vänta på nästa publicering. Att publicera om med samma topic skriver över det bevarade värdet; att publicera en tom payload rensar det.

Last will. När en klient ansluter kan den ge brokern ett ”last will and testament”: ett topic, en payload, ett QoS och en retain-flagga. Om den klienten kopplar från orent – TCP RESET, strömavbrott, nätverksbortfall utan något DISCONNECT-paket – publicerar brokern testamentet å klientens vägnar. Subscribers ser det som kamerans avisering om att den har gått offline. Kameran själv skickar aldrig testamentet; det gör brokern, eftersom kameran vid det laget är borta.

9.18.6. Keepalive och återanslutning

CONNECT bär ett keepalive-intervall i sekunder. Om klienten har varit tyst så länge betraktar brokern den som död. För att förhindra det skickar klienten med jämna mellanrum en PINGREQ (en byte: 0xC0) och får tillbaka en PINGRESP (0xD0) – det minsta, billigaste hjärtslag protokollet kan bära. De flesta kameraappar sätter keepalive till 30 eller 60 sekunder.

Om TCP-anslutningen bryts märker båda sidor det och återansluter från början. Prenumerationer som gjordes före brottet går förlorade om inte klienten använde en beständig session vid anslutning; för enkla kameraappar är mönstret med att prenumerera om vid återanslutning kortare och precis lika bra.

Detta räcker för att läsa MQTT-specifikationen eller handrulla en klient över en socket.socket. Den medföljande klienten i mqtt gör precis det, plus ett vettigt API för applikationskod.