ESP32 MQTT: умный дом с Home Assistant
Умный дом у меня начинался с WiFi-реле, которые жили в каждом приложении отдельно: одно для Sonoff, другое для Tuya, третье для китайских реле с AliExpress. Через год я снёс все приложения и поднял Home Assistant на малине, а реле перевёл на MQTT — единый протокол, единый интерфейс, единая автоматизация. ESP32 в этой схеме — сервер умного дома в миниатюре: реле на освещение, датчик температуры, кнопка-сценарий, и всё это слушает один топик в брокере. Скетч на 80 строк, и панель HA сама находит устройство через автодискавери.
Ниже — связка ESP32 + MQTT + Home Assistant от первого подключения до сценариев: поднимаем Mosquitto на малине, ставим PubSubClient на ESP32, шлём телеметрию и слушаем команды, и включаем автодискавери в HA — устройство появляется в панели без правки YAML. Логику реле и кнопки проверяю в симуляторе платы до пайки: компонент relay на D7, button на D2, код на Uno, а дальше перевожу под ESP32.
Архитектура: ESP32 → MQTT → Home Assistant
Три роли в схеме: ESP32 — устройство, Mosquitto — брокер, Home Assistant — умный дом. ESP32 публикует телеметрию (температура, статус реле) в топик home/sensor/kitchen/temperature и подписана на команды home/relay/kitchen/set. Брокер Mosquitto гоняет сообщения между устройствами и HA. HA подписана на телеметрию, отображает в дашборде и по таймерам или триггерам публикует команды. Так реле на ESP32 управляется с телефона через HA, по расписанию или от сработки датчика движения — без единого провода.
- ESP32 (PubSubClient): публикация в топики и подписка на команды; до 5 одновременных подписок без потерь.
- Mosquitto на Raspberry Pi или сервере: listens 1883 (локально) или 8883 (TLS); 200-500 руб/мес за VPS.
- Home Assistant: подписан на discovery-топики, рисует панель; ставится на Pi, Mini PC или сервер.
- MQTT Explorer (десктоп): для отладки — видите все топики и payload в реальном времени; бесплатный.
- Структура топиков: home/<room>/<device>/<property> — иерархия с wildcard + и # для подписок.
Mosquitto на малине: 5 минут от apt до первого сообщения
Raspberry Pi 3 или 4 с Raspberry Pi OS — ставится через sudo apt install mosquitto mosquitto-clients. После установки брокер слушает 1883 локально, для сети нужен конфиг /etc/mosquitto/conf.d/listen.conf с двумя строками: listener 1883 и allow_anonymous true (для домашней сети) или с паролем через password_file. Перезапуск sudo systemctl restart mosquitto, проверка: mosquitto_sub -h 192.168.1.10 -t test на одной консоли и mosquitto_pub -h 192.168.1.10 -t test -m hello на другой — сообщение прилетает за миллисекунды.
ESP32: PubSubClient, скетч реле с обратной связью
Пакет PubSubClient ставится через Менеджер библиотек Arduino IDE. Скетч: WiFi.begin, PubSubClient.setServer(broker, 1883), setCallback(onMessage), connect(clientId). На команду home/relay/kitchen/set с payload ON или OFF переключаем реле на GPIO23, и публикуем состояние в home/relay/kitchen/state. Так HA видит подтверждение, что реле переключилось. Reconnect с backoff — обязательный, иначе при ребуте брокера плата зависнет в цикле переподключения.
#include <WiFi.h>#include <PubSubClient.h> const char* SSID = "HomeNet";const char* PASS = "passw0rd";const char* BROKER = "192.168.1.10";const int PORT = 1883; WiFiClient net;PubSubClient client(net);const int RELAY = 23; void onMessage(char* topic, byte* payload, unsigned int len) { String msg; for (unsigned int i = 0; i < len; i++) msg += (char)payload[i]; if (String(topic) == "home/relay/kitchen/set") { if (msg == "ON") digitalWrite(RELAY, HIGH); if (msg == "OFF") digitalWrite(RELAY, LOW); client.publish("home/relay/kitchen/state", digitalRead(RELAY) ? "ON" : "OFF", true); // retained }} void setup() { Serial.begin(115200); pinMode(RELAY, OUTPUT); WiFi.begin(SSID, PASS); while (WiFi.status() != WL_CONNECTED) delay(300); client.setServer(BROKER, PORT); client.setCallback(onMessage); client.connect("esp32-kitchen"); client.subscribe("home/relay/kitchen/set");} void loop() { if (!client.connected()) { if (client.connect("esp32-kitchen")) client.subscribe("home/relay/kitchen/set"); } client.loop();}После заливки в MQTT Explorer видны три топика: home/relay/kitchen/set (команды), home/relay/kitchen/state (retained-статус), и home/relay/kitchen/availability (LWT, «в сети» или «офлайн»). Публикация ON в set включает реле, и в state приходит подтверждение. Полный разбор PubSubClient с callback-маршрутизацией — в уроке про callback MQTT; настройка retain и LWT — в уроке про завещание.
Home Assistant: автодискавери без правки YAML
ESP32 публикует в homeassistant/switch/kitchen/config JSON с полями name, command_topic, state_topic, availability_topic. HA при первом запуске подписан на homeassistant/#, видит конфиг и создаёт switch.kitchen в панели автоматически. Никакого правки configuration.yaml — устройство появляется в «Настройки → Устройства» через 10 секунд после публикации. Если удалить топик discovery, HA через 30 секунд уберёт устройство из панели — так работает clean-up. Подробно — в уроке про MQTT discovery HA.
Сценарии: расписание, движение, кнопка
В HA автоматизации на YAML или через визуальный редактор: «когда время 07:00, опубликовать ON в home/relay/kitchen/set», «когда binary_sensor.motion сработал, опубликовать ON». Сценарий «Уходим» — одна кнопка в HA выключает все реле и закрывает шторы. На стороне ESP32 это просто подписка на топик home/scenario/away/set с payload ON — скетч проходит по всем реле и выключает их. Дополнительно у меня висит «спящий режим» — приглушает подсветку после 22:00, на основе топика home/mode/sleep/state.
Грабли ESP32 + MQTT, на которые я наступал
- WiFi-рассоединение при DNS-fail: клиент зависал в reconnect-цикле. Лечится WiFi.setAutoReconnect(true) и backoff в PubSubClient (reconnect через 5, 10, 20 секунд).
- Цикл buffer 256 байт: PubSubClient по умолчанию, для discovery-JSON 600 байт не хватает. client.setBufferSize(1024) решает, но ест 1 КБ RAM.
- Подписка на # ловит все топики: удобно для отладки, но на каждом сообщении срабатывает callback, и при десятке устройств ESP32 захлёбывается. Подписки должны быть конкретными.
- Retained-сообщения накапливаются: при переподключении клиент получает все retained-публикации, в том числе старые команды. Публикуйте пустой retained, чтобы стереть.
- LWT публикуется только после первого connect: если плата не дойдёт до брокера, LWT не выставится, и HA будет считать её «в сети». Лечится retained LWT при настройке и сбросом при первом connect.