IT Академия Samsung – MQTT # Протокол MQTT <div class="validation"> **Внимание!** <br /> Описание данного практикума **отличается** от того, что приведено на сайте [IT Академии](https://innovationcampus.ru/lms/mod/book/view.php?id=1332)! </div> ## Общие сведения об MQTT Интернет работает на сотнях различных протоколов: HTTP, FTP, SMTP, POP3, SSH и&nbsp;т.д. и&nbsp;т.д. Какой же протокл наиболее предпочтительно использовать для Интернета вещей? Наиболее распространенными являются HTTP RESTful, CoAP и MQTT. Все эти протоколы относятся к **прикладному** уровню эталонной модели OSI (собственно, в концептуальной модели стека TCP/IP – тоже к прикладному уровню): ![mqtt_tcp](/assets/images/iot-academy/mqtt_tcp.png) Каким же требованиям должен удовлетворять протокол, предназначенный для Интернета вещей? С точки зрения безопасности было бы лучше, если бы связь устанавливалась по инициативе устройства, а не сервера. Такая модель предотвращает возможные атаки – если злоумышленник взломает сервер, то это не окажет негативного воздействия на конечное устройство, так как в этой модели устройство не может управляться входящим запросом. Далее, датчики в Интернете вещей, как правило, автономны. Они работают от батареи – вот почему размер передаваемого пакета данных должен быть как можно меньше. Чем меньше времени тратится на приём/передачу, тем меньше энергопотребление. HTTP RESTful и CoAP являются **документно-ориентированными** протоколами, поэтому они имеют большие издержки, а протокол MQTT – нет. MQTT или Message Queue Telemetry Transport – это легкий, компактный и открытый протокол обмена данными созданный для передачи данных на удалённых локациях, где требуется небольшой размер кода и есть ограничения по пропускной способности канала. Вышеперечисленные достоинства позволяют применять его в системах M2M (Машинно-Машинное взаимодействие) и IIoT (Промышленный Интернет вещей). Основные особенности протокола MQTT: * асинхронный протокол; * компактные сообщения; * работа в условиях нестабильной связи на линии передачи данных; * поддержка нескольких уровней качества обслуживания (QoS); * легкая интеграция новых устройств. Протокол MQTT работает на прикладном уровне поверх TCP/IP и использует по умолчанию 1883 порт (8883 при подключении через SSL). Сам протокол был разработан в 1999&nbsp;году фирмой IBM для обмена данными между компьютерами (M2M), а если быть более точным, то для систем транспортировки нефти требовалось передать данные от различных датчиков в SCADA-систему в режиме реального времени. Протокол MQTT хорош для применения в ситуациях, когда пропускная способность канала ограничена (в 1999-м стоимость связи была гораздо выше, чем сейчас, и это предопределило особенности протокола MQTT). Обмен сообщениями в протоколе MQTT осуществляется между клиентом (client), который может быть **издателем** или **подписчиком** (publisher/subscriber) сообщений, и **брокером** (broker) сообщений – сервером (например, Mosquitto MQTT). Издатель отправляет **сообщения** на MQTT брокер, указывая в сообщении определенную тему – **топик** (topic). Подписчики могут получать разные данные от множества издателей в зависимости от подписки на соответствующие топики. Устройства MQTT используют определенные типы сообщений для взаимодействия с брокером, ниже представлены основные: * Connect – установить соединение с брокером; * Disconnect – разорвать соединение с брокером; * Publish – опубликовать данные в топик на брокере; * Subscribe – подписаться на топик на брокере; * Unsubscribe – отписаться от топика. Схема простого взаимодействия между подписчиком, издателем и брокером приведена на рисунке: ![mqtt_flow](/assets/images/iot-academy/mqtt_flow.png) Что делает MQTT интересным для нас протоколом? Если говорить языком книги Эндрю Ханта и Дэвида Томаса "Программист-прагматик", он позволяет нам соблюдать принцип *ортогональности*, то есть строить программу как набор слабо связанных между собой сущностей. Иными словами, каждый клиент – это независимое устройство, а система в целом образуется из множества таких устройств за счет организации связи. ![mqtt_sys](/assets/images/iot-academy/mqtt_sys.png) Топик – это основная единица хранения информации. Топики представляют собой символы с кодировкой UTF-8. Иерархическая структура топиков имеет формат «дерева», что упрощает их организацию и доступ к данным. Топики состоят из одного или нескольких уровней, которые разделены между собой символом «/». В целом, базовые требования к структуре топиков неплохо описаны The HiveMQ Team в их [блоге](https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/): 1. Уровни топиков разделяются с помощью "/" 2. Именем топика может быть любая UTF-8 строка 3. Имя каждого уровня топика должно содержать минимум один символ 4. Имена чувстВиТеЛьНы к РеГисТру 5. "/" тоже может быть именем топика, но это может запутать <div class="warning"> Обратите внимание, что структуру топиков в вашем проекте определяете только вы сами и имеете полную свободу действий. При публикации сообщении вы можете указать вообще любой топик или субтопик. </div> Таким образом, протокол MQTT хорошо и довольно эффективно подходит для приложений, в которых необходимо собирать данные и управлять простыми устройствами. В качестве клиентов могут выступать любые устройства. Например, это могут быть умный датчик, компьютер или смартфон. ![mqtt](/assets/images/iot-academy/mqtt.png) Если у вас есть датчики температуры на кухне, в спальне и пара датчиков в гостиной, то структура топиков может быть задана следующим образом: ``` home/kitchen/temperature home/bedroom/temperature home/living_room/temperature1 home/living_room/temperature2 ``` В топик можно опубликовать (PUBLISH) любой пакет информации и он будет доставлен всем, кто подписался (SUBSCRIBE) на этот топик. Полная информация – http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html. Подписчик может так же получать данные сразу из нескольких топиков, для этого существуют символы подстановок (**wildcard**). Они бывают двух типов: одноуровневые (задаются символом&nbsp;«**+**») и многоуровневые (задаются символом&nbsp;«**#**»). Одноуровневый wildcard (символ «+») позволяет как бы «пропустить» один уровень в дереве топиков, в то время как многоуровневый wildcard (символ «#») – текущий уровень и все нижележащие. Если устройства подпишутся на топики так, как указано на рисунке ![MQTT_Wildcard](/assets/images/iot-academy/mqtt_sub_wildcard.png "Подписка с подстановками") то в итоге они буду принимать информацию от следующих датчиков: | Подписчик | Топик | Сообщения от датчиков (id) | |:----------|-------------------------:|:--------------------------:| | 1 | home/kitchen/temperature | 1 | | 2 | home/+/+ | 1, 2, 3, 4, 5 | | 3 | home/+/temperature | 1, 2, 4 | | 4 | home/living_room/# | 4, 5 | <div class="warning"> <p>Обратите внимание, символы подстановки можно использовать для подписки!</p> <p>**Публикация сообщений всегда остуществляется в конкретный топик!**</p> </div> <div class="info"> Интересно, что изначально название протокола расшифровывалось как Message Queue Telemetry Transport. Однако это вносило путаницу, поскольку в MQTT используется другой паттерн: "Издатель/Подписчик" (Publisher/Subscriber), а не "Очередь сообщений" (Message Queue). Поэтому от такой расшифровки отказались, и на данный момент MQ в названии официально ничего не значит. </div> ## MQTT брокер (Установка mosquitto) В рамках этого практикума подробно рассмотрим протокол MQTT. Для уверенной работы с ним стоит проделать несколько упражнений. ![mosquitto_logo](/assets/images/iot-academy/mosquitto_logo.png) Сервер Mosquitto – лишь один из возможных вариантов сервера. Можно использовать и другие: HiveMQTT, HBMQTT. Но мы будем использовать Mosquitto, поскольку это самый популярный Open Source-сервер. Установите себе на компьютер `mosquitto`. Это сервер, который можно запустить локально и потренироваться на нём. Установка на Ubuntu делается просто: ```bash sudo apt install mosquitto ``` Mosquitto также существует в виде пакетов и для других дистрибутивов. Его можно поставить и в embedded-компьютере наподобие Raspberry&nbsp;Pi – это важно, если компьютер функционирует как шлюз между локальной сетью Интернета вещей (LoRa или ZigBee), и собственно Интернетом (WWW). Можно даже установить Mosquitto в Windows, просто скачав дистрибутив с [сайта](https://mosquitto.org/download/). Тогда он появится в “Службах” Windows, и его можно будет запускать и останавливать из этого окна: ![mosquitto_win](/assets/images/iot-academy/mosquitto_win.png) При установке под Windows не забудьте добавить путь до выполняемых файлов в переменную окружения среды `PATH` (чтобы сожно было запускать утилиты из командной строки): ![add_mosquitto_path_win](/assets/images/iot-academy/add_mosquitto_path_win.png) Но всё же настоятельно рекомендуется работать именно в Linux. Вам предстоит освоить базовый функционал локального MQTT-сервера, чтобы далее выполнять упражнения с ним. ## Hello World в mosquitto ### Запуск сервера Запустите сервер через терминал: ```bash mosquitto ``` Результат: ![mosquitto_server](/assets/images/iot-academy/mosquitto_server.png) Если запустить не получается из-за того, что порт уже используется, выберите другой порт через ключ `-p`. Вообще обычно по умолчанию сервер стартует на 1883 порту и запускается в режиме демона, то есть без вашего участия. И порт может быть занят просто потому, что `mosquitto` уже стартовал в вашей системе (можете проверить это, выведя список всех процессов). Но нам сейчас важно потренироваться, поэтому запустим на другом порту для наглядности (пусть это будет порт 1893). ```bash mosquitto -p 1893 ``` ![mosquitto_server_1893](/assets/images/iot-academy/mosquitto_server_1893.png) ### Подписка на топик В **другом** окне терминала введите команду подписки на топик: ```bash mosquitto_sub -h "localhost" -t "mytopic" -q 1 ``` или ```bash mosquitto_sub -h "localhost" -p 1893 -t "mytopic" -q 1 ``` если сам брокер запущен на порту 1893. В качестве хоста указан `localhost` (то есть сам компьютер), а в качестве топика — вымышленный `mytopic`. Результат: программа будет ждать сообщений из этого топика. ![mosquitto_sub_localhost](/assets/images/iot-academy/mosquitto_sub_localhost.png) Обязательно посмотрите в предыдущее окно, где стартовал сервер: вы увидите, что там добавилась новая информация. ### Отправка сообщения Наконец, в **третьем** окне терминала введите команду, отправляющую сообщение — `mosquitto_pub` (при необходимости укажите номер порта через ключ `-p` аналогично тому, как это было сделано в предыдущем случае): ```bash mosquitto_pub -h "localhost" -t "mytopic" -m "Hello World" -q 1 ``` ![mosquitto_pub](/assets/images/iot-academy/mosquitto_pub.png) Параметры следующие: - `-h` — адрес сервера (IP-адрес или доменное имя, в примере `localhost` — то есть "тот же самый компьютер"); - `-t` — название топика, в котором публикуется сообщение (в примере это `mytopic`); - `-m` — текст сообщения (`Hello World`); - `-q` — качество обслуживания (QoS**1**). В результате, вы увидите в окне с подпиской текст "Hello World": ![mosquitto_sub_result](/assets/images/iot-academy/mosquitto_sub_result.png) <div class="info"> Возможно, вам пригодятся еще и следующие ключи для `mosquitto_pub` и `mosquitto_sub`: <ul> <li>`-p` — номер порта (вместо стандартного 1883);</li> <li>`-u` — имя пользователя;</li> <li>`-P` — пароль.</li> </ul> </div> ### Один сервер, много клиентов Теперь попробуем общаться по протоколу MQTT в рамках класса, между отдельными машинами. Выберите один компьютер — он будет сервером. Узнайте его IP-адрес (если это Linux, то самый простой способ — утилита `ifconfig`). Запустите на нём сервер MQTT командой `mosquitto`. Все остальные могут публиковать свои сообщения командой `mosquitto_sub`. Не заканчивайте упражнение, пока сообщение каждого из участников группы не достигнет назначения. При желании можно посмотреть, какие сообщения приходят на сервер, командой: ```bash mosquitto_sub -h "192.168.1.18" -t "#" ``` Вы будете получать сообщения из всех топиков (напомним, что символ `#` означает все нижестоящие). В некоторых случаях нам потребуется подключение к брокеру с авторизацией ## Качество обслуживания (QoS) Чтобы далее успешно работать с MQTT-сервером, нужно знать, по какой схеме происходит коммуникация. Проведите самостоятельно ряд экспериментов. Запустите сервер `mosquitto` в отладочном режиме (при необходимости — задайте номер порта): ```bash mosquitto -v ``` Теперь, проделывая все те же самые действия, что и в прошлом задании, вы увидите большое количество промежуточных шагов: ![mosquitto_verbose](/assets/images/iot-academy/mosquitto_verbose.png) Кроме того, вы можете увидеть промежуточные сообщения команд `mosquitto_sub` и `mosquitto_pub`, запустив их с ключом `-d` (debug): ![mosquitto_pub_with_d_key](/assets/images/iot-academy/mosquitto_pub_with_d_key.png) Для изображения различных протоколов удобно использовать диаграммы последовательности (Sequence Diagram) UML. Вот, например, как выглядит жизненный цикл `mosquitto_pub` с качеством обслуживания 0, согласно вышеприведенному скриншоту: ![sequence_diagram](/assets/images/iot-academy/sequence_diagram.png) Если подобного рода диаграммы вам неизвестны, то посмотрите короткий обучающий [ролик](https://youtu.be/XIQKt5Bs7II), посвящённый диаграммам последовательности. ## Топики в MQTT Используя локальный MQTT-сервер, выполните следующие задания: 1. Проверьте, чувствительны ли названия топиков к регистру. 2. Можно ли послать сообщение сразу всем субтопикам определённого топика? Предположим, что у вас есть топик `light`, в нём топики `lamp1` и `lamp2`, и вы хотите послать сигнал всем лампочкам включиться. 3. Будет ли работать подписка на топик, если в конце названия топика добавить символ «/»? А если в начале? 4. Что будет, если вызвать `mosquitto_pub` с ключом `-l`? 5. Изучите, что такое **retain**. Отправьте сообщение с ключом `-r`, но важно, что в этот момент подписчики ещё не подключены к брокеру. Затем запустите клиент и подпишитесь на топик. Что произойдет? Что будет, если подписчик отключится от брокера, а затем подключится снова? Что, если оставить второе сообщение с тем же ключом? (Чтобы вернуть всё к прежнему состоянию, отправьте пустое сообщение с ключом `-r`). 6. Изучите, что такое «Последняя воля и завещание» (Last Will and Testament). Для этого при публикации не задавайте тело сообщения, но укажите параметры `--will-topic` и `--will-payload` — название топика и собственно текст «завещания». В результате, если вы в другом окне оформите подписку на этот топик, и издатель (`mosquitto_pub`) незапланированно отключится, то подписчик (`mosquitto_sub`) получит от сервера «завещание». Обратите внимание, что команда `mosquitto_pub` в чистом виде отправляет сообщение и сразу отключается без аварийного завершения, поэтому у вас не получится сделать это задание сразу. Подумайте, как смоделировать случай, когда издатель подключается на какое-то время и затем внезапно отключается.