ESP32 + MQTT: invia i dati a Home Assistant con auto-discovery

ESP32 + MQTT: invia i dati a Home Assistant con auto-discovery

Nella stazione ambientale del tutorial precedente i dati restavano solo sul display OLED. Ora colleghiamo l’ESP32 al Wi-Fi e inviamo le misure via MQTT verso Home Assistant, così da costruire uno storico, dei grafici e delle automazioni (es. “se l’umidità supera il 70% accendi il deumidificatore”).

Cos’è MQTT in due minuti

MQTT è un protocollo publish/subscribe pensato per l’IoT: leggero, pensato per reti instabili e dispositivi con poche risorse. I concetti chiave:

  • Broker: il server centrale a cui tutti i dispositivi si collegano (es. Mosquitto)
  • Topic: una specie di “canale” con nome gerarchico, es. casa/soggiorno/temperatura
  • Publish: un dispositivo pubblica un valore su un topic
  • Subscribe: un altro dispositivo (o Home Assistant) si iscrive a quel topic e riceve i valori in tempo reale

Prerequisiti

  • Home Assistant già installato e funzionante
  • Add-on Mosquitto broker installato e avviato (Impostazioni → Add-on → Add-on Store → Mosquitto broker)
  • Integrazione MQTT configurata in Home Assistant (Impostazioni → Dispositivi e servizi → Aggiungi integrazione → MQTT, puntando a localhost se Mosquitto gira come add-on)
  • Un utente Home Assistant dedicato per i dispositivi MQTT (consigliato, non usare l’utente admin principale)

Librerie necessarie

Nel platformio.ini, aggiungi alle dipendenze del tutorial precedente:

lib_deps =
    adafruit/Adafruit BME280 Library@^2.2.4
    adafruit/Adafruit SSD1306@^2.5.13
    adafruit/Adafruit GFX Library@^1.11.10
    adafruit/Adafruit BusIO@^1.16.1
    knolleary/PubSubClient@^2.8
    bblanchon/ArduinoJson@^7.0.4

Il codice

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

const char* WIFI_SSID = "IL_TUO_SSID";
const char* WIFI_PASSWORD = "LA_TUA_PASSWORD";

const char* MQTT_BROKER = "192.168.1.10";  // IP di Home Assistant
const int MQTT_PORT = 1883;
const char* MQTT_USER = "mqtt_esp32";
const char* MQTT_PASSWORD = "PASSWORD_MQTT";

const char* DEVICE_ID = "esp32-stazione-01";
const char* STATE_TOPIC = "casa/stazione01/state";

WiFiClient espClient;
PubSubClient mqtt(espClient);
Adafruit_BME280 bme;

void connectWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connessione Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Connesso, IP: ");
  Serial.println(WiFi.localIP());
}

void publishDiscovery() {
  // Home Assistant MQTT discovery: fa comparire i sensori automaticamente
  // senza doverli configurare a mano in configuration.yaml
  struct SensorConfig { const char* key; const char* name; const char* unit; const char* deviceClass; };
  SensorConfig sensors[] = {
    {"temperature", "Temperatura", "°C", "temperature"},
    {"humidity", "Umidita", "%", "humidity"},
    {"pressure", "Pressione", "hPa", "pressure"},
  };

  for (auto &s : sensors) {
    String configTopic = String("homeassistant/sensor/") + DEVICE_ID + "_" + s.key + "/config";

    JsonDocument doc;
    doc["name"] = s.name;
    doc["state_topic"] = STATE_TOPIC;
    doc["unit_of_measurement"] = s.unit;
    doc["device_class"] = s.deviceClass;
    doc["value_template"] = String("{{ value_json.") + s.key + " }}";
    doc["unique_id"] = String(DEVICE_ID) + "_" + s.key;
    doc["device"]["identifiers"][0] = DEVICE_ID;
    doc["device"]["name"] = "Stazione ambientale ESP32";
    doc["device"]["manufacturer"] = "WebAppPortal DIY";

    String payload;
    serializeJson(doc, payload);
    mqtt.publish(configTopic.c_str(), payload.c_str(), true);  // retained
  }
}

void connectMQTT() {
  while (!mqtt.connected()) {
    Serial.print("Connessione MQTT...");
    if (mqtt.connect(DEVICE_ID, MQTT_USER, MQTT_PASSWORD)) {
      Serial.println("connesso");
      publishDiscovery();
    } else {
      Serial.printf("fallita, rc=%d, riprovo tra 5s\n", mqtt.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);

  if (!bme.begin(0x76, &Wire)) {
    Serial.println("BME280 non trovato");
    while (1) delay(10);
  }

  connectWiFi();
  mqtt.setServer(MQTT_BROKER, MQTT_PORT);
  connectMQTT();
}

void loop() {
  if (!mqtt.connected()) {
    connectMQTT();
  }
  mqtt.loop();

  JsonDocument doc;
  doc["temperature"] = bme.readTemperature();
  doc["humidity"] = bme.readHumidity();
  doc["pressure"] = bme.readPressure() / 100.0F;

  String payload;
  serializeJson(doc, payload);
  mqtt.publish(STATE_TOPIC, payload.c_str());

  Serial.println("Pubblicato: " + payload);
  delay(30000);  // ogni 30 secondi
}

Come funziona

  • Il dispositivo pubblica un unico messaggio JSON su casa/stazione01/state con tutti e tre i valori, invece di tre topic separati: è più efficiente e più facile da gestire
  • publishDiscovery() pubblica dei messaggi speciali su topic homeassistant/sensor/.../config con retained=true: Home Assistant li legge automaticamente e crea le entità sensore senza bisogno di configurazione manuale in YAML
  • value_template dice a Home Assistant come estrarre il singolo valore dal JSON ricevuto sullo state_topic
  • Il ciclo loop() ripubblica lo stato ogni 30 secondi; per un nodo a batteria questo intervallo andrà allungato e combinato con il deep sleep, argomento del prossimo tutorial

Verifica

Dopo il primo avvio, in Home Assistant vai su Impostazioni → Dispositivi e servizi → MQTT: dovresti vedere comparire il dispositivo “Stazione ambientale ESP32” con le tre entità già pronte da aggiungere a una dashboard. Per debug avanzato puoi anche usare uno strumento come MQTT Explorer per vedere i messaggi grezzi che transitano sul broker.

Va oltre

Il nodo ora funziona, ma resta sempre acceso e consuma corrente in continuazione — va bene su USB, ma non su batteria. Nel prossimo tutorial vediamo come usare il deep sleep per costruire un nodo a batteria che dura mesi con una singola carica.

Imprenditore, maker, MBSE enthusiast e co-autore di Web App Portal. Collegati su LinkedIn.