9.18. MQTT, байт за байтом#
На цьому етапі камера має все необхідне для зв’язку з реальним сервісом в інтернеті: TCP-сокет, TLS для шифрування, DNS для визначення адреси вузла та asyncio, щоб той самий скрипт міг виконувати іншу роботу, поки з’єднання відкрите. MQTT — це перший мережевий протокол, який об’єднує всі ці компоненти в щось, що реально використовується у розгорнутому продукті.
Ця сторінка охоплює сам протокол – формат на рівні дротових даних, ролі кожного учасника та компроміси в його дизайні – достатньо докладно, щоб вбудований клієнт mqtt виглядав як очевидна обгортка вже відомого, а не стрибок у невідоме.
9.18.1. Pub/sub проти запит/відповідь#
HTTP – протокол, до якого більшість проєктів із камерою звертається першочергово – є запит/відповідь. Клієнт запитує у конкретного сервера конкретний ресурс; сервер відповідає. Кожен обмін є «один до одного», і обидва кінці заздалегідь знають адреси один одного.
MQTT є публікація/підписка. Клієнти підключаються до третьої сторони посередині, яка називається брокером. Видавець надсилає повідомлення в іменовану тему, не знаючи і не дбаючи про те, хто слухає. Підписник повідомляє брокеру, які теми він хоче отримувати, і отримує всі повідомлення, опубліковані в цих темах. Брокер здійснює розподіл: одна публікація в yard-cam/motion досягає кожного пристрою, підписаного на yard-cam/motion, навіть якщо їх нуль, один або п’ятдесят.
З цієї зміни моделі випливають три речі:
Розв’язання. Видавці не повинні знати про існування підписників. Підписники можуть приходити і йти, не помічаючись видавцем. Додавання другої панелі моніторингу — це один рядок коду на новій панелі; камера не змінюється.
Розгалуження. Брокер обробляє всі дублікати, тому камера надсилає один пакет незалежно від кількості пристроїв, які його читають. Саме для цього варіанту використання і був розроблений MQTT.
Асиметрія. Брокер є обов’язковим елементом інфраструктури – без нього протокол не працює. Для домашніх проєктів це зазвичай безкоштовний публічний брокер (
test.mosquitto.org,broker.hivemq.com) або невеликий, який ви запускаєте самостійно.
9.18.2. Теми#
Теми — це рядки, розділені косою рискою. Угода передбачає найбільш загальне зліва, найбільш конкретне справа:
yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3
У підписках (але не при публікації) діють два символи підстановки:
+відповідає одному рівню.+/motionпідписується на тему motion кожної камери;yard-cam/+підписується на кожну підтему yard-cam.#відповідає одному або декільком кінцевим рівням.yard-cam/#підписується наyard-cam/motion,yard-cam/temperature,yard-cam/temperature/sensor-3та будь-що інше вyard-cam/. Він повинен знаходитись у кінці підписки.
Рядки тем чутливі до регістру. Відповідно до специфікації, провідний символ $ позначає внутрішні теми брокера ($SYS/...), до яких видавці не повинні записувати.
9.18.3. Формат пакету#
MQTT працює поверх TCP. Кожен керуючий пакет починається з однобайтного фіксованого заголовку, за яким слідує поле змінної довжини Залишкова довжина, потім змінний заголовок конкретного типу пакету, а потім навантаження. Однаковий зовнішній формат охоплює всі команди – CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT та інші – саме тому клієнт MQTT можна написати у кількох сотнях рядків.
Фіксований заголовок складається з одного байта:
Біти 7..4 — це тип керуючого пакету.
0x3— PUBLISH (тому перший байт зазвичай починається з0x3?).0x1— CONNECT,0x2— CONNACK,0x8— SUBSCRIBE,0xC— PINGREQ,0xE— DISCONNECT тощо.Біти 3..0 — це прапорці, специфічні для типу пакету. Для PUBLISH прапорці кодують прапорець повторної передачі DUP, рівень QoS (2 біти) та прапорець RETAIN.
Залишкова довжина — це цілочисельне значення змінної довжини від 1 до 4 байтів, яке підраховує кожен байт після себе. Старший біт кожного байта є маркером продовження – 1 означає «іде ще один байт довжини», 0 означає «це останній». Довжина до 128 вміщується в одному байті; більші навантаження використовують більше. Максимальна закодована довжина становить 256 МіБ.
Для PUBLISH змінний заголовок є іменем теми – 2-байтна довжина, потім байти UTF-8 – за яким слідує 2-байтний ідентифікатор пакету, який існує лише тоді, коли QoS дорівнює 1 або 2. Решта байтів є навантаженням, яке протокол трактує як непрозорі байти.
Мінімальний PUBLISH з QoS-0 ok в a/b виглядає так:
30 07 00 03 'a' '/' 'b' 'o' 'k'
30– PUBLISH, всі прапорці нульові.07– далі 7 байтів.00 03– довжина теми 3.'a' '/' 'b'– тема.'o' 'k'– навантаження.
Дев’ять байтів у мережі, і повідомлення надходить до кожного підписника a/b на брокері.
9.18.4. Рівні QoS#
Quality-of-Service визначає, наскільки старанно брокер (і клієнт) забезпечує доставку. Три рівні:
QoS 0 – не більше одного разу. Надіслати і забути. Пакет PUBLISH надсилається і ніколи не підтверджується. Якщо TCP доставив – брокер пересилає. Якщо з’єднання обривається під час відправки – повідомлення втрачається. Більшість телеметрії датчиків нормально працює з QoS 0 – одиничне пропущене значення температури в потоці, що надсилається кожні 30 секунд, не має значення.
QoS 1 – щонайменше один раз. Видавець включає ідентифікатор пакету і чекає на PUBACK. Якщо PUBACK не надходить до закінчення тайм-ауту, видавець повторно передає пакет з встановленим прапорцем DUP. Брокер може в підсумку двічі доставити те саме повідомлення підписнику на тому ж рівні; підписник повинен бути готовий обробляти дублікати.
QoS 2 – рівно один раз. Чотириетапне рукостискання (PUBREC / PUBREL / PUBCOMP) гарантує, що повідомлення потрапляє рівно один раз, навіть після перепідключення. Дорого з точки зору кількості обходів та стану брокера. Мало застосунків для камери потребують цього.
Вбудований клієнт mqtt реалізує QoS 0 та QoS 1; QoS 2 викликає виняток, якщо ви його запросите. Для камери, яка звітує про показники датчиків, QoS 0 майже завжди є правильним вибором.
9.18.5. Збережені повідомлення та останнє волевиявлення#
Дві функції варто знати, оскільки вони змінюють те, що брокер пам’ятає про вашу тему.
RETAIN. Якщо для PUBLISH встановлено прапорець RETAIN, брокер зберігає повідомлення та пересилає його кожному майбутньому підписнику в момент підписки. Саме так MQTT відповідає на питання «яке поточне значення?» – датчик публікує своє останнє значення з RETAIN, і панель моніторингу, яка підпишеться через десять хвилин, все одно отримає найсвіжіше значення, не чекаючи наступної публікації. Повторна публікація в ту саму тему перезаписує збережене значення; публікація порожнього навантаження очищає його.
Останнє волевиявлення. При підключенні клієнт може надати брокеру «останнє волевиявлення та заповіт»: тему, навантаження, QoS та прапорець RETAIN. Якщо цей клієнт відключається аварійно – TCP RESET, втрата живлення, обрив мережі без пакету DISCONNECT – брокер публікує заповіт від імені клієнта. Підписники бачать це як сповіщення камери про те, що вона перейшла в офлайн. Сама камера ніколи не надсилає заповіт; це робить брокер, тому що на той момент камера вже недоступна.
9.18.6. Keepalive та перепідключення#
CONNECT несе в собі інтервал keepalive у секундах. Якщо клієнт мовчить протягом цього часу, брокер вважає його мертвим. Щоб запобігти цьому, клієнт періодично надсилає PINGREQ (один байт: 0xC0) та отримує у відповідь PINGRESP (0xD0) – найменше та найдешевше підтвердження активності, яке може підтримувати протокол. Більшість застосунків для камери встановлюють keepalive на 30 або 60 секунд.
Якщо TCP-з’єднання обривається, обидві сторони помічають це і перепідключаються з нуля. Підписки, зроблені до обриву, втрачаються, якщо клієнт не використовував постійну сесію при підключенні; для простих застосунків для камери шаблон повторної підписки при перепідключенні є коротшим і не менш ефективним.
Цього достатньо, щоб прочитати специфікацію MQTT або написати клієнт вручну поверх socket.socket. Вбудований клієнт у mqtt робить саме це, плюс зручний API для коду застосунку.