ESP32 come client MQTT: publish e subscribe con PubSubClient

Pubblicato il 13/07/2026 ESP32

MQTT è il protocollo più usato per far comunicare dispositivi IoT perché è leggero, funziona bene su reti instabili e separa chi produce dati (publisher) da chi li consuma (subscriber) tramite un broker centrale. Su ESP32 la libreria più diffusa per parlare MQTT è PubSubClient di Nick O'Leary, che si appoggia a un client TCP (WiFiClient) già presente nel core Arduino. In questo articolo vediamo come collegarsi a un broker, pubblicare il valore di un sensore e ricevere comandi su un topic dedicato.

Cosa serve

Oltre alla scheda ESP32 e una rete WiFi funzionante, serve un broker MQTT raggiungibile: per test locali il più semplice è Mosquitto installato su un Raspberry Pi o su un PC nella stessa LAN, oppure un broker cloud (es. HiveMQ, EMQX) se vuoi evitare di gestire l'infrastruttura. Installa la libreria "PubSubClient" dal Library Manager dell'Arduino IDE o via PlatformIO (knolleary/PubSubClient).

Connessione al broker

Il pattern base è: connetti il WiFi, poi apri la connessione MQTT sopra un WiFiClient. Serve anche una funzione di riconnessione perché il link MQTT può cadere senza che cada il WiFi.

#include <WiFi.h>
#include <PubSubClient.h>

const char* ssid = "la-tua-rete";
const char* password = "la-tua-password";
const char* mqtt_server = "192.168.1.10";
const int   mqtt_port = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

void setupWiFi() {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connesso, IP: " + WiFi.localIP().toString());
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Connessione MQTT...");
    String clientId = "esp32-" + String(WiFi.macAddress());
    if (client.connect(clientId.c_str())) {
      Serial.println("connesso");
      client.subscribe("casa/soggiorno/comando");
    } else {
      Serial.print("fallita, rc=");
      Serial.print(client.state());
      Serial.println(" riprovo tra 5s");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  setupWiFi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

Publish di un valore sensore

Nel loop principale chiama sempre client.loop(): è quello che tiene viva la connessione (ping keep-alive) e processa i messaggi in arrivo. La pubblicazione va fatta a intervalli regolari, non ad ogni giro di loop, per non intasare il broker.

unsigned long lastPublish = 0;
const long interval = 10000; // 10 secondi

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastPublish > interval) {
    lastPublish = now;
    float temperatura = leggiTemperatura(); // tua funzione sensore
    char payload[10];
    dtostrf(temperatura, 4, 1, payload);
    client.publish("casa/soggiorno/temperatura", payload);
    Serial.println("Pubblicato: " + String(payload));
  }
}

Il terzo parametro opzionale di publish() è il flag retain: se impostato a true, il broker conserva l'ultimo valore e lo consegna subito a ogni nuovo subscriber, utile per dashboard che devono mostrare l'ultimo stato noto senza aspettare il prossimo publish.

Subscribe a un topic di comando

La callback viene invocata da client.loop() ogni volta che arriva un messaggio su un topic sottoscritto. Il payload arriva come array di byte, non come stringa C, quindi va convertito esplicitamente.

void callback(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (unsigned int i = 0; i < length; i++) {
    msg += (char)payload[i];
  }
  Serial.println("Messaggio su " + String(topic) + ": " + msg);

  if (String(topic) == "casa/soggiorno/comando") {
    if (msg == "ON") {
      digitalWrite(RELE_PIN, HIGH);
    } else if (msg == "OFF") {
      digitalWrite(RELE_PIN, LOW);
    }
  }
}

Nota pratica

PubSubClient di default limita i pacchetti a 256 byte (MQTT_MAX_PACKET_SIZE): se pubblichi payload JSON più corposi devi aumentare questa costante ridefinendola prima di includere la libreria, oppure editando PubSubClient.h. Ricorda inoltre che il client non è thread-safe: se usi FreeRTOS con più task, tutte le chiamate a client.* devono avvenire dallo stesso task, altrimenti rischi corruzioni difficili da diagnosticare.