MQTT – это протокол, который используется для передачи и приема сообщений в сети Интернет. Также он очень популярен в сфере интернета вещей (Internet of Things, IoT). В данной статье мы рассмотрим основные принципы протокола MQTT и будем использовать плату Raspberry Pi в качестве локального MQTT брокера (MQTT broker). С помощью данного брокера и панели инструментов приложения MQTT мы будем управлять светодиодом, подключенным к NodeMCU ESP12E. Также к NodeMCU будет подключен датчик температуры и влажности DHT11, данные с которого мы будем передавать MQTT брокеру, установленному на Raspberry Pi.
Основные принципы работы протокола MQTT
Протокол MQTT (Message Queue Telemetry Transport) был разработан компанией IBM и может применяться для передачи и приема сообщений в сети интернет. Протокол очень простой и "легкий" и может применяться устройствами, имеющими малую полосу пропускания, то есть подключенными к каналу связи с маленькой пропускной способностью. В настоящее время данный протокол широко используется в устройствах интернета вещей (IoT) для передачи и приема данных от различных датчиков. Достоинством данного протокола при его работе в сфере интернета вещей является то, что он требует сравнительно мало интернет трафика для своего функционирования.
В протоколе MQTT используются следующие основные термины:
- Subscribe and Publish (издатель-подписчик).
- Message (сообщение).
- Topic (тема).
- Broker (брокер).
1. Subscribe (подписка) и Publish (публикование): подписка означает получение данных от другого устройства, а публикование означает передачу данных другому устройству.
Когда устройство 1 передает данные устройству 2, то устройство 1 называют издателем (Publisher), а устройство 2 – подписчиком (Subscriber).
2. Message (сообщение). Сообщения – это информация, которую мы передаем и получаем. Это могут быть или данные, или команда. Например, если мы передаем (publishing) данные температуры в облако, то эти данные температуры в протоколе MQTT называются сообщением (Message).
3. Topic (тема) – это способ указать ваши интересы для входящих или исходящих сообщений. Темы (топики) в протоколе MQTT представляются строками, разделенными знаком прямого слэша. Данные публикуются в соответствующих темах протокола MQTT и затем MQTT устройства подписываются на интересующие их темы чтобы получать данные.
4. MQTT Broker (брокер) – это устройство-координатор ответственно за прием всех сообщений от всех издателей (publishers), фильтрацию этих сообщений и передачу сообщений тем подписчикам (subscribers), которым эти сообщения интересны.
Когда хостингом MQTT брокера является облако (cloud), оно называется MQTT облаком. В современном мире существует уже достаточно много подобных "облачных" MQTT брокеров, таких как Adafruit IO, MQTT.IO, IBM bluemix, Microsoft Azure и т.д.
Но мы можем сконструировать своего собственного MQTT брокера с использованием платы Raspberry Pi. Наш брокер будет локальным, то есть он сможет передавать и принимать сообщения только в нашей локальной сети. Таким образом, в этом проекте мы будем устанавливать Mosquitto MQTT брокера на плату Raspberry Pi чтобы превратить ее в локального MQTT брокера, с помощью которого мы будем управлять светодиодом, подключенным к NodeMCU (устройство управления многосторонней связью). Также мы будем передавать данные температуры и влажности от NodeMCU в панель инструментов MQTT приложения.
Установка Mosquitto MQTT брокера на Raspberry Pi
Откройте окно терминала на вашей плате Raspberry Pi и введите там следующие команды чтобы установить брокер:
1 2 |
sudo apt update sudo apt install -y mosquitto mosquitto-clients |
Подождите пока закончится процесс установки. После этого для запуска брокера введите команду:
1 |
sudo systemctl enable mosquitto.service |
Это все, что нужно сделать для установки и запуска MQTT брокера на плату Raspberry Pi. Чтобы проверить корректность его установки введите команду:
1 |
mosquitto -v |
В результате выполнения данной команды вам выдастся номер версии MQTT брокера. Он должен быть 1.4.x или выше.
Тестирование работы Mosquitto MQTT брокера на Raspberry Pi
1. Запустите Mosquitto брокера в фоновом режиме с помощью команды:
1 |
mosquitto -d |
2. После этого подпишемся на тему с названием exampleTopic.
1 |
mosquitto_sub -d -t exampleTopic |
3. Далее опубликуем приветственное сообщение в теме exampleTopic.
1 |
mosquitto_pub -d -t exampleTopic -m "Hello world!" |
После этого вы получите сообщение "Hello world" в терминале.
После этого можно управлять и получать данные с другого устройства, в нашем случае мы будем использовать NodeMCU и панель инструментов MQTT приложения (MQTT dashboard app - его нужно скачать на ваш смартфон из Play Market).
Вначале мы будем управлять светодиодом передавая команды из приложения, то есть NodeMCU будет выступать в качестве подписчика (subscriber), а приложения – в качестве издателя (publisher).
Также в нашем проекте к NodeMCU ESP12E подключен датчик DHT11, с которого считываются данные температуры и передаются приложению MQTT, то есть в данном случае приложение будет подписчиком, а NodeMCU – издателем. Для передачи этих сообщений в соответствующих темах (Topics) будет использоваться MQTT брокер.
Схема проекта
Схема подключения светодиода и датчика DHT11 к NodeMCU ESP12E представлена на следующем рисунке.
Для измерения температуры мы использовали датчик DHT11, который мы ранее уже использовали во множестве проектов.
Для работы с проектом нам необходимо установить ряд библиотек.
Объяснение кода программы для NodeMCU
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Основой программы для нашего проекта будет библиотека Adafruit MQTT library. Также код нашей программы с небольшими внесенными в него изменениями можно будет использовать для опубликования и получения данных в облаке Adafruit IO.
Первым делом скачаем библиотеку Adafruit MQTT с помощью пункта меню Sketch -> Include Library -> Manage Libraries. В открывшемся окне выполните поиск библиотеки Adafruit MQTT и установите ее. После установки библиотеки откройте пример по адресу examples -> Adafruit mqtt library -> mqtt_esp8266.
В открывшемся коде примера измените следующие данные: IP адрес Raspberry Pi и данные для подключения к сети Wi-Fi.
Подключим все используемые библиотеки в программе:
1 2 3 4 |
#include <ESP8266WiFi.h> #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h" #include "DHT.h" |
Затем введем идентификатор (SSID) и пароль Wi-Fi сети, к которой вы будете подключаться с помощью ESP-12e. Убедитесь в том, что ваша плата Raspberry Pi и NodeMCU подключаются к одной и той же сети.
1 2 |
#define WLAN_SSID "xxxxxxxx" #define WLAN_PASS "xxxxxxxxxxx" |
Далее введем данные сервера Adafruit: IP адрес вашей платы Raspberry Pi и порт сервера.
1 2 |
#define AIO_SERVER "ip address of your Pi" #define AIO_SERVERPORT 1883 |
Следующие поля оставим пустые поскольку мы не используем облако Adafruit (Adafruit cloud).
1 2 |
#define AIO_USERNAME "" #define AIO_KEY "" |
Далее создадим класс ESP8266 WiFiClient чтобы подключаться к MQTT серверу.
1 |
WiFiClient client; |
Создадим объект MQTT с необходимыми параметрами: адрес сервера, порт сервера и данные для логина к серверу.
1 |
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); |
Создадим фид (feed) с названием 'Temperature' для опубликования данных температуры.
1 |
Adafruit_MQTT_Publish Temperature = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/temperature"); |
Создадим фид с названием 'led1' чтобы подписаться на изменения.
1 |
Adafruit_MQTT_Subscribe led1 = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/led"); |
В функции setup мы объявим используемые контакты NodeMCU. Далее подключим NodeMCU к точке доступа Wi-fi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void setup() { Serial.begin(115200); delay(10); pinMode(LED, OUTPUT); Serial.println(F("Adafruit MQTT demo")); // Connect to WiFi access point. Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(WLAN_SSID); WiFi.begin(WLAN_SSID, WLAN_PASS); while (WiFi.status() != WL_CONNECTED) { …. …. … //Setup MQTT subscription for led feed. mqtt.subscribe(&led1); } |
В функции loop мы будем проверять "живо" ли соединение с MQTT сервером с помощью функции MQTT_connect().
1 2 |
void loop() { MQTT_connect(); |
Далее мы будем подписываться на фид ‘led’, получать строку с сообщением от MQTT брокера и преобразовывать эту строку в число с помощью функции atoi(). После этого мы будем подавать это число на контакт, к которому подключен светодиод, с помощью функции digitalWrite().
1 2 3 4 5 6 7 8 9 |
Adafruit_MQTT_Subscribe *subscription; while ((subscription = mqtt.readSubscription(20000))) { if (subscription == &led1) { Serial.print(F("Got: ")); Serial.println((char *)led1.lastread); int led1_State = atoi((char *)led1.lastread); digitalWrite(LED, led1_State); } |
Далее мы будем считывать значение температуры в переменную и публиковать это значение с помощью функции Temperature.publish(t).
1 2 3 4 5 6 7 8 9 |
float t = dht.readTemperature(); … .. if (! Temperature.publish(t)) { Serial.println(F("Failed")); } else { Serial.println(F("OK!")); } |
В конце статьи вы можете посмотреть видео, на котором подробно показана работа проекта. Для тестирования работы проекта загрузите код программы в NodeMCU и откройте MQTT dashboard app (панель инструментов приложения MQTT), которую вы ранее скачали на свой смартфон.
Также подобным образом вы можете управлять контактами ввода/вывода (GPIO) платы Raspberry Pi из любой точки Земли используя облачные сервисы MQTT, такие как Adafruit IO, MQTT.IO и т.д.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
#include <ESP8266WiFi.h> #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h" #include "DHT.h" #define LED D5 #define DHTPIN D4 #define WLAN_SSID "awesome" #define WLAN_PASS "awesome12" #define AIO_SERVER "192.168.43.177" //IP адрес вашей платы Raspberry Pi #define AIO_SERVERPORT 1883 #define AIO_USERNAME "" #define AIO_KEY "" WiFiClient client; Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); Adafruit_MQTT_Publish Temperature = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/temperature"); Adafruit_MQTT_Subscribe led1 = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/led"); #define DHTTYPE DHT11 // DHT 11 DHT dht(DHTPIN, DHTTYPE); uint32_t delayMS; /*************************** Sketch Code ************************************/ void MQTT_connect(); void setup() { Serial.begin(115200); delay(10); pinMode(LED, OUTPUT); Serial.println(F("Adafruit MQTT demo")); // Connect to WiFi access point. Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(WLAN_SSID); WiFi.begin(WLAN_SSID, WLAN_PASS); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); //инициализация датчика DHT dht.begin(); // инициализация подписки MQTT для фида onoff mqtt.subscribe(&led1); } uint32_t x = 0; void loop() { // удостоверьтесь в том, что соединение с сервером MQTT работает ("живо") // connection and automatically reconnect when disconnected). See the MQTT_connect // function definition further below. MQTT_connect(); // this is our 'wait for incoming subscription packets' busy subloop // try to spend your time here (здесь необходимо подождать) Adafruit_MQTT_Subscribe *subscription; while ((subscription = mqtt.readSubscription(20000))) { if (subscription == &led1) { Serial.print(F("Got: ")); Serial.println((char *)led1.lastread); int led1_State = atoi((char *)led1.lastread); digitalWrite(LED, led1_State); Serial.println("onnn"); } } // считываем значение температуры в градусах Цельсия float t = dht.readTemperature(); // проверяем удалось ли считывание, если нет – то осуществляем ранний выход (чтобы сделать новую попытку) if (isnan(t)) { Serial.println("Failed to read from DHT sensor!"); return; } if (! Temperature.publish(t)) { Serial.println(F("Failed")); } else { Serial.println(F("OK!")); } } // функция для соединения и повторного соединения (reconnect) (если необходимо) к MQTT серверу // будет вызываться в функции loop void MQTT_connect() { int8_t ret; // остановка если соединение уже есть if (mqtt.connected()) { return; } Serial.print("Connecting to MQTT... "); uint8_t retries = 3; // 3 попытки соединения while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected Serial.println(mqtt.connectErrorString(ret)); Serial.println("Retrying MQTT connection in 5 seconds..."); mqtt.disconnect(); delay(5000); // ждем 5 секунд retries--; if (retries == 0) { // basically die and wait for WDT to reset me while (1); } } Serial.println("MQTT Connected!"); } |
Видео, демонстрирующее работу проекта
Также можете посмотреть еще одно видео (на английском языке), которое подробно объясняет процесс использования MQTT брокера в плате Raspberry Pi.
mqtt.readSubscription(20000) - можно пояснить 20000 для чего?
Здесь 20000 - это время в миллисекундах, в течение которого мы будем "слушать" сообщения, приходящие от MQTT сервера
1. Получается что 20 секунд программа "стоит" и ничего другого не может делать потому что занята "прослушкой"? А без этого никак? Ну то есть как организовать общение нескольких ESP8266, среди которых есть "мастера" (назовём его вторичный прибор) которым нужна информация для решения, а другие это контроллеры с датчиками (назовем их первичными приборами)? К примеру первички отравляют информацию брокеру с периодичностью 5 секунд, а вторичные запрашивают у брокера с периодичностью 20 секунд?
2. MQTT subscription for led feed. - что это такое и почему заканчивается точкой?
3. subscription = mqtt.readSubscription(20000) - возвращает истину в случае если появилась новая публикация по любой из наших подписок (если подписок несколько)?
4. subscription я так понял int, а если необходимо обмениваться не целочисленными значениями или сообщениями?
5. Пока использую Blynk, но хотел бы уйти от него. На роутере Keenetic установлен Mosquitto. Подойдут ли приведенные Вами библиотеки Adafruit или что то необходимо другое?
И спасибо большое за то что отвечаете!
1. Если программа примет сообщение гораздо быстрее чем за 20 секунд, то сразу же начнет исполняться тело цикла и в этом случае обработка сообщения завершится гораздо быстрее. Если же сообщение по какой то причине не принимается то да, программа будет ждать 20 секунд.
2. Это просто комментарий к команде, которая идет после него. Просто в описании программы пропущен знак комментария (сейчас поправлю). В полном тексте программы значок комментария стоит и текст комментария переведен.
3. Да, возвращает истину в случае если появилась новая публикация по любой из наших подписок, а потом в теле цикла мы с помощью условного оператора должны проверить сообщение по какой подписке у нас пришло. В нашем примере у нас используется всего одна подписка - led1.
4. Изначально в представленном проекте мы получаем сообщение в виде строки, а потом преобразуем эту строку в число с помощью функции atoi(). Ну а в общем случае мы в этой строке можем передавать что угодно, не обязательно число.
5. Тут не знаю, к сожалению. Никогда не работал с Mosquitto на роутерах.
"Когда устройство 1 передает данные устройству 2", всё таки не устройству наверное, а брокеру?
Нет, в статье написано: когда устройство 1 передает данные устройству 2, то устройство 1 называют издателем (Publisher), а устройство 2 – подписчиком (Subscriber). Брокер является посредником между ними, то есть через него передаются эти данные, но не он получатель этих данных