9.18. MQTT, tavu tavulta

Tässä vaiheessa kameralla on kaikki tarvittavat palaset todellisen palvelun kanssa keskustelemiseen avoimessa internetissä: TCP-soketti, sen ympärille kiedottava TLS, DNS vastapuolen nimeämiseen ja asyncio, jotta sama skripti voi tehdä muita töitä yhteyden ollessa auki. MQTT on ensimmäinen siirtoprotokolla, joka kokoaa kaikki nämä yhteen joksikin, mitä käyttöön otetut tuotteet tosiasiassa käyttävät.

Tämä sivu käsittelee itse protokollaa – siirtotason muotoa, kunkin osapuolen rooleja ja sen suunnitteluratkaisujen kompromisseja – riittävän rehellisesti, jotta mukana toimitettu mqtt-asiakas näyttää ilmeiseltä kääreeltä jo tunnetun ympärille eikä uskonhypyltä.

9.18.1. Pub/sub vs. pyyntö/vastaus

HTTP – protokolla, johon useimmat kameraprojektit ensin tarttuvat – on pyyntö/vastaus -tyyppinen. Asiakas pyytää tietyltä palvelimelta tiettyä resurssia; palvelin vastaa. Jokainen vaihto on yksi yhteen, ja molemmat päät tuntevat toistensa osoitteen ennalta.

MQTT on julkaisu/tilaus -tyyppinen. Asiakkaat yhdistävät keskellä olevaan kolmanteen osapuoleen, jota kutsutaan nimellä broker. Julkaisija lähettää viestin nimettyyn aiheeseen (topic) tietämättä tai välittämättä siitä, kuka kuuntelee. Tilaaja kertoo brokerille, mitä aiheita se haluaa, ja vastaanottaa jokaisen näihin aiheisiin tämän jälkeen julkaistun viestin. Broker hoitaa hajautuksen: yksi julkaisu aiheeseen yard-cam/motion tavoittaa jokaisen aihetta yard-cam/motion tilaavan laitteen, oli niitä sitten nolla, yksi tai viisikymmentä.

Tästä mallin muutoksesta seuraa kolme asiaa:

  • Irrottaminen. Julkaisijoiden ei tarvitse tietää tilaajien olemassaolosta. Tilaajat voivat tulla ja mennä ilman, että julkaisija huomaa. Toisen koontinäytön lisääminen on yksi koodirivi uudessa koontinäytössä; kamera ei muutu.

  • Hajautus. Broker hoitaa jokaisen kopion, joten kamera lähettää yhden paketin riippumatta siitä, kuinka monta laitetta sen lukee. Juuri tähän käyttötapaukseen MQTT on rakennettu.

  • Epäsymmetria. Broker on nyt välttämätön infrastruktuurin osa – ilman sitä protokolla ei toimi. Kotiprojekteissa tämä on yleensä ilmainen julkinen broker (test.mosquitto.org, broker.hivemq.com) tai pieni itse ylläpidetty.

Yksi kamera julkaisee aiheeseen yard-cam/motion brokerilla samalla kun kaksi selainkoontinäyttöä ja yksi pilviarkistoija kukin vastaanottavat saman viestin.

9.18.2. Aiheet (topics)

Aiheet ovat kauttaviivalla erotettuja merkkijonoja. Sopimuksen mukaan yleisin on vasemmalla ja yksityiskohtaisin oikealla:

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

Tilauksissa (ei julkaisuissa) toimii kaksi jokerimerkkiä:

  • + täsmää yhteen tasoon. +/motion tilaa jokaisen kameran motion-aiheen; yard-cam/+ tilaa jokaisen yard-cam-alaaiheen.

  • # täsmää yhteen tai useampaan loppupään tasoon. yard-cam/# tilaa aiheet yard-cam/motion, yard-cam/temperature, yard-cam/temperature/sensor-3 ja kaiken muunkin polun yard-cam/ alta. Sen on oltava tilauksen lopussa.

Aihemerkkijonot erottelevat isot ja pienet kirjaimet. Määrityksen mukaan etuliite $ merkitsee brokerin sisäisiä aiheita ($SYS/...), joihin julkaisijoiden ei pitäisi kirjoittaa.

9.18.3. Paketin muoto

MQTT toimii TCP:n päällä. Jokainen ohjauspaketti alkaa yhden tavun kiinteällä otsikolla, jota seuraa vaihtuvanmittainen Remaining Length -kenttä, sitten pakettityyppikohtainen muuttuva otsikko ja lopuksi hyötykuorma. Sama ulompi muoto kattaa jokaisen komennon – CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT ja loput – minkä vuoksi MQTT-asiakas voidaan kirjoittaa muutamassa sadassa rivissä.

MQTT PUBLISH -paketin tavurakenne, joka näyttää kiinteän otsikon tyyppi- ja lipputavun, vaihtuvanmittaisen Remaining Length -kentän, aiheen nimen, valinnaisen paketin tunnisteen ja hyötykuorman tavut.

Kiinteä otsikko on yksi tavu:

  • Bitit 7..4 ovat ohjauspaketin tyyppi. 0x3 on PUBLISH (joten ensimmäinen tavu alkaa yleensä 0x3?). 0x1 on CONNECT, 0x2 CONNACK, 0x8 SUBSCRIBE, 0xC PINGREQ, 0xE DISCONNECT jne.

  • Bitit 3..0 ovat pakettityyppikohtaisia lippuja. PUBLISH-paketissa liput koodaavat DUP-uudelleenlähetyslipun, QoS-tason (2 bittiä) ja RETAIN-lipun.

Remaining Length on 1–4 tavun vaihtuvanmittainen kokonaisluku, joka laskee jokaisen sen jälkeen tulevan tavun. Kunkin tavun ylin bitti on jatkomerkki – 1 tarkoittaa ”toinen pituustavu seuraa”, 0 tarkoittaa ”tämä on viimeinen”. Alle 128:n pituus mahtuu yhteen tavuun; suuremmat hyötykuormat käyttävät useampaa. Suurin koodattava pituus on 256 MiB.

PUBLISH-paketissa muuttuva otsikko on aiheen nimi – 2 tavun pituus, sitten UTF-8-tavut – jota seuraa 2 tavun paketin tunniste, joka on olemassa vain QoS:n ollessa 1 tai 2. Loput tavut ovat hyötykuormaa, jota protokolla käsittelee läpinäkymättöminä tavuina.

Minimaalinen QoS-0 PUBLISH, joka lähettää ok aiheeseen a/b, on:

30 07 00 03 'a' '/' 'b' 'o' 'k'
  • 30 – PUBLISH, kaikki liput nollia.

  • 07 – 7 tavua seuraa.

  • 00 03 – aiheen pituus 3.

  • 'a' '/' 'b' – aihe.

  • 'o' 'k' – hyötykuorma.

Yhdeksän tavua siirtotiellä, ja viesti päätyy jokaiselle aihetta a/b brokerilla tilaavalle.

9.18.4. QoS-tasot

Quality-of-Service ohjaa sitä, kuinka kovasti broker (ja asiakas) tekee työtä toimituksen varmistamiseksi. Kolme tasoa:

QoS 0 – korkeintaan kerran. Lähetä ja unohda. PUBLISH-paketti lähetetään eikä sitä koskaan vahvisteta. Jos TCP toimittaa, broker välittää eteenpäin. Jos yhteys katkeaa kesken lähetyksen, viesti on mennyttä. Useimmalle sensoritelemetrialle QoS 0 riittää – yksittäinen menetetty lämpötilalukema 30 sekunnin välein tulevassa virrassa ei ole merkityksellinen.

QoS 1 – vähintään kerran. Julkaisija sisällyttää paketin tunnisteen ja odottaa PUBACK-vahvistusta. Jos PUBACK ei saavu ennen aikakatkaisua, julkaisija lähettää uudelleen DUP-lippu asetettuna. Broker saattaa lopulta toimittaa saman viestin samalle tilaajalle kahdesti samalla tasolla; tilaajan on oltava valmis käsittelemään kopioita.

QoS 2 – täsmälleen kerran. Nelivaiheinen kättely (PUBREC / PUBREL / PUBCOMP) varmistaa, että viesti päätyy perille täsmälleen kerran, jopa uudelleenyhdistämisten yli. Kallis edestakaisten viestien ja brokerin tilan suhteen. Harvat kamerasovellukset tarvitsevat sitä.

Mukana toimitettu mqtt-asiakas toteuttaa QoS 0:n ja QoS 1:n; QoS 2 nostaa poikkeuksen, jos sitä pyydetään. Sensorilukemia raportoivalle kameralle QoS 0 on lähes aina oikea vastaus.

9.18.5. Säilytetyt viestit ja viimeinen tahto

Kaksi ominaisuutta kannattaa tuntea, koska ne muuttavat sitä, mitä broker muistaa aiheestasi.

RETAIN. Jos PUBLISH-paketissa on RETAIN-lippu asetettuna, broker tallentaa viestin ja välittää sen jokaiselle tulevalle tilaajalle heti kun tämä tilaa. Näin MQTT käsittelee kysymyksen ”mikä on nykyinen arvo?” – sensori julkaisee viimeisimmän lukemansa säilytettynä, ja kymmenen minuuttia myöhemmin tilaava koontinäyttö saa silti uusimman arvon sen sijaan, että odottaisi seuraavaa julkaisua. Saman aiheen uudelleenjulkaisu korvaa säilytetyn arvon; tyhjän hyötykuorman julkaiseminen tyhjentää sen.

Viimeinen tahto (last will). Yhdistäessään asiakas voi antaa brokerille ”viimeisen tahdon ja testamentin”: aiheen, hyötykuorman, QoS:n ja säilytyslipun. Jos kyseinen asiakas katkaisee yhteyden epäpuhtaasti – TCP RESET, virtakatkos, verkon katkos ilman DISCONNECT-pakettia – broker julkaisee tahdon asiakkaan puolesta. Tilaajat näkevät sen kameran ilmoituksena siitä, että se on poistunut verkosta. Kamera itse ei koskaan lähetä tahtoa; broker tekee sen, koska siihen mennessä kamera on poissa.

9.18.6. Pitoyhteys ja uudelleenyhdistäminen

CONNECT kuljettaa keepalive-välin sekunteina. Jos asiakas on ollut hiljaa niin pitkään, broker pitää sitä kuolleena. Tämän estämiseksi asiakas lähettää säännöllisesti PINGREQ-paketin (yksi tavu: 0xC0) ja saa takaisin PINGRESP-paketin (0xD0) – pienimmän, halvimman sykkeen, jonka protokolla voi kuljettaa. Useimmat kamerasovellukset asettavat keepalive-arvoksi 30 tai 60 sekuntia.

Jos TCP-yhteys katkeaa, molemmat puolet huomaavat sen ja yhdistävät uudelleen alusta. Ennen katkosta tehdyt tilaukset menetetään, ellei asiakas käyttänyt yhdistäessään pysyvää istuntoa; yksinkertaisissa kamerasovelluksissa uudelleenyhdistämisen yhteydessä tilaaminen on lyhyempi ja aivan yhtä hyvä tapa.

Tämä riittää MQTT-määrityksen lukemiseen tai asiakkaan käsin kirjoittamiseen socket.socket-olion päälle. Mukana toimitettu mqtt-asiakas tekee juuri tämän, lisäksi järkevän rajapinnan sovelluskoodille.