MQTT на ESP32: PubSubClient, брокер и топики
MQTT это стандартный протокол умного дома: устройства публикуют данные в топики и подписываются на команды через брокера. Для ESP32 основная библиотека это PubSubClient автора Nick O'Leary, а брокер для дома Mosquitto или встроенный в Home Assistant.
Теория протокола и практика публикации собраны в уроках MQTT: как устроен протокол и ESP32 публикует данные.
Ниже разбираем архитектуру, именование топиков, три причины, по которым publish() молча не срабатывает, разницу между MQTT и HTTP и список из семи шагов для проверки, что канал связи живой.
Архитектура за 30 секунд
Устройства не общаются напрямую. Датчик публикует значение в топик home/livingroom/temperature, брокер принимает сообщение и раздаёт его всем, кто на этот топик подписан, включая Home Assistant. Команды идут в обратную сторону: устройство подписывается на home/livingroom/relay и получает туда строку ON. Брокер это единственная точка, через которую проходит весь обмен, и от его настроек зависит, увидят ли устройства друг друга.
Второе свойство протокола, которое делает его удобным, это retained-сообщения. Если брокер хранит последнее значение топика и отдаёт его новому подписчику сразу при подписке, то датчик не обязан публиковать данные постоянно: достаточно один раз записать показание, и каждый, кто подключится позже, сразу получит актуальное значение. Третье свойство это QoS, уровень гарантии доставки: ноль означает отправить и забыть, единица гарантирует доставку хотя бы раз с возможными дубликатами, двойка даёт ровно один раз, но тяжеловесна.
Минимальный код: публикация температуры
#include <WiFi.h>#include <PubSubClient.h> WiFiClient net;PubSubClient mqtt(net); void setup() { Serial.begin(115200); WiFi.begin("MoyaSet", "parol12345"); while (WiFi.status() != WL_CONNECTED) delay(500); mqtt.setServer("192.168.1.50", 1883);} void loop() { if (!mqtt.connected()) { mqtt.connect("esp32-temp"); } mqtt.loop(); static unsigned long last = 0; if (millis() - last > 5000) { last = millis(); mqtt.publish("home/livingroom/temp", "23.7"); }}Три строки делают всю работу. Объект PubSubClient работает поверх обычного WiFiClient, и в setServer указывается адрес брокера и порт 1883 для незащищённого соединения. В loop() сначала проверяется connected(): если связи нет, вызывается connect с идентификатором клиента, и именно здесь задаются логин и пароль, если у брокера есть учётные записи. Затем mqtt.loop() обслуживает протокол: читает входящие сообщения, отвечает на ping и следит за keepalive. Без этого вызова клиент молча выпадает из сети. Последний блок публикует значение раз в пять секунд по millis(), чтобы не блокировать loop.
Где взять брокера
| Вариант | Для чего | Нюансы |
|---|---|---|
| Mosquitto на Raspberry или сервере | Локальный умный дом | Порт 1883, анонимный доступ только для тестов |
| Home Assistant (Mosquitto addon) | Интеграция с HA | Auto-discovery топиков |
| Публичные брокеры (broker.emqx.io и другие) | Обучение, тесты | Нет приватности: не публикуйте личные данные |
| Cloud (HiveMQ, AWS IoT) | Продакшен | TLS и аккаунты обязательны |
Топики: как называть, чтобы не запутаться
Имена топиков это фактически ваша база данных, и путаница в них всплывает через месяц, когда устройств станет больше десятка. Рабочая схема простая: дома, потом комната, потом устройство, потом свойство. Получается home/livingroom/temp, home/kitchen/relay, home/bedroom/lamp/state. Разделитель косой черты в MQTT не имеет особого смысла, это просто удобная для человека запись, но чем она единообразнее, тем проще потом искать.
- Один топик на одно свойство: температура отдельно, влажность отдельно, состояние реле отдельно
- Отдельные топики для команд и для состояний, чтобы команда ON не путалась с чтением
- Retained-сообщения только на состояния, чтобы новый подписчик сразу увидел текущее значение
- Имена латиницей и строчными буквами: регистр в топиках различается, а в Windows и Linux это разные системы
Тупик: publish() возвращает false
Симптом выглядит так: плата подключена, mqtt.connected() возвращает true, а в мониторе порта нет ни ошибки, ни сообщений. Топик вроде бы правильный, подписчик на другой стороне есть, а данных нет. Функция publish() при этом возвращает false, но код её результат не проверяет, поэтому всё выглядит как «работает молча».
Причин у этого три, и мы проходили их по одной. Первая и самая частая: сообщение не помещается в буфер. У PubSubClient размер пакета по умолчанию 256 байт, и всё вместе с заголовками протокола длинная строка в него просто не влезает. Лечится увеличением буфера через mqtt.setBufferSize(512), но правильнее сокращать полезную нагрузку. Вторая: публикация идёт до того, как соединение реально установлено, и брокер не получает пакет вовсе. Третья: в топике или в данных есть символ, который не проходит проверку, и библиотека отказывается отправлять. Мы теперь проверяем результат publish() и печатаем его в Serial, и этого хватает, чтобы за минуту понять, в чём дело.
Личный опыт: брокер, которого не слышно извне
С Mosquitto на Raspberry Pi у нас была странная картина: с самого Raspberry Pi брокер отвечал, а плата на другом конце сети к нему подключаться не могла. Мы проверяли порт с ноутбука, порт был закрыт для внешних подключений, и мы решили, что дело в настройках ESP32.
Дело было в том, что Mosquitto в свежих версиях по умолчанию слушает только локальный интерфейс и не пускает анонимных клиентов. Нужно явно разрешить прослушивание на сетевом адресе и пустые логины, а для рабочей системы завести учётные записи и закрыть анонимный доступ. После правки конфигурации плата подключилась сразу. Вывод мы записали: брокер это отдельный сервис со своими правилами доступа, и «работает локально» ещё не значит «доступен с платы». Кстати, для учебных экспериментов публичный брокер снимает эту задачу целиком: настройки там готовые, и ваша задача только в клиенте.
Авторская позиция: MQTT или HTTP
Мы советуем начинать с MQTT, если устройств больше одного и они должны видеть друг друга. Обмен идёт через брокера, каждый публикует то, что умеет, и подписывается на то, что ему нужно, без знания адресов и портов друг друга. Добавление нового устройства не требует правок в существующих, а это ровно то свойство, ради которого умный дом и строят на шине.
HTTP остаётся лучшим выбором там, где устройство одно и с ним работает браузер или телефон: открыть страницу с показаниями проще, чем поднимать брокер. Практическое правило, которым мы пользуемся: HTTP для панели и настройки, MQTT для взаимодействия устройств между собой и для вывода данных в Home Assistant. Смешивать их в одном проекте не страшно, но не стоит тащить MQTT туда, где хватит одного веб-сервера: лишний сервис это лишние настройки и ещё одно место, где что-то может не запуститься.