

Progettare Fleet ESP32 Production-Grade: MQTT, OTA, Failure Recovery e Vincoli Reali
Un singolo ESP32 è semplice. Una fleet non lo è.
Questa è la lezione che abbiamo imparato costruendo NestGrow — un sistema IoT di controllo dell’irrigazione distribuito all’edge, dispiegato in ambienti serra con decine di nodi ESP32 per installazione. Nel momento in cui si scala oltre una manciata di dispositivi, il problema ingegneristico smette di riguardare l’hardware e diventa comportamento della fleet in condizioni di guasto.
Questo articolo documenta l’architettura, i failure mode che abbiamo incontrato e i principi di design che rendono un sistema genuinamente production-grade — non solo “funziona sul mio banco”.
Il Problema Reale: Fleet, Non Dispositivi
Appena si scala oltre pochi nodi, emergono tre problemi che il pensiero da singolo dispositivo non riesce a risolvere:
- Affidabilità dei messaggi su reti instabili — i messaggi MQTT si perdono, si duplicano o si ritardano
- Consistenza del firmware su nodi eterogenei — il version drift è silenzioso e pericoloso
- Stati di guasto parziale — un dispositivo che non è né completamente online né completamente offline è il caso più difficile da gestire
Il cambio concettuale fondamentale è questo:
L’IoT non riguarda i dispositivi. Riguarda lo stato distribuito sotto incertezza.
Architettura di Riferimento
Una fleet ESP32 production-grade è composta da quattro layer che lavorano insieme.
Layer 1 — Nodi Edge (ESP32)
Ogni nodo è responsabile dell’acquisizione dei sensori, dell’attuazione locale, della comunicazione MQTT e della ricezione degli aggiornamenti OTA. Il vincolo critico: ogni nodo deve continuare a operare autonomamente anche quando la rete non è disponibile. Il backend è un policy engine, non un controller real-time.
Stack hardware usato in NestGrow: ESP32 WROOM-32D, relay HW-316 a 4 canali (logica invertita: LOW=ON), sensori di umidità capacitivi HW-390 solo su ADC1 (GPIO 32–35, 36 VP).
Layer 2 — Messaging (MQTT Broker)
Eclipse Mosquitto 2 gestisce la comunicazione pub/sub. Un principio critico deve essere interiorizzato fin dall’inizio:
MQTT è un layer di trasporto, non un sistema di record.
Lo stato non deve mai essere ricostruito da MQTT. Il broker è effimero per design.
Layer 3 — Backend di Orchestrazione (FastAPI)
Il backend gestisce il tracking dello stato della fleet, il coordinamento OTA, la valutazione delle policy rule-based e il monitoraggio della salute. Ricostruisce lo stato autorevole dall’event stream — non si fida direttamente dello stato riportato dai dispositivi.
Layer 4 — Persistenza (MariaDB)
Tutti gli eventi di telemetria, i record di irrigazione e i metadati dei dispositivi sono salvati in MariaDB. Il database è l’unica fonte di verità. Il backend ricostruisce la propria visione della fleet da esso all’avvio.
Strategia OTA
L’OTA è dove la maggior parte dei sistemi IoT fallisce in produzione. La regola fondamentale:
L’OTA deve assumere il guasto come stato di default, non come eccezione.
Un flusso OTA sicuro per ESP32:
- Scarica il firmware sulla partizione secondaria (non quella attiva)
- Verifica il checksum SHA-256 prima di qualsiasi cambio di partizione
- Cambia la partizione di boot atomicamente
- Riporta la versione del firmware al backend dopo il reboot riuscito
Se si perde l’alimentazione durante il passo 1 o 2, il dispositivo si avvia dalla partizione primaria invariata. Se si perde durante il passo 3, il bootloader ESP32 rileva lo switch incompleto e fa rollback. Nessun intervento manuale necessario.
Failure Mode nei Deploy Reali
Perdita di rete durante l’aggiornamento di stato
La disconnessione MQTT durante l’invio della telemetria causa perdita di messaggi. Mitigazione: design idempotente dei messaggi (ogni messaggio porta un timestamp, il processing duplicato è sicuro) e buffer FIFO locale sull’ESP32 per la sincronizzazione alla riconnessione.
Incompatibilità ADC2 con WiFi
Questo è poco documentato e causa guasti silenziosi. Sull’ESP32, ADC2 è multiplexato via hardware con la radio WiFi. Quando il WiFi è attivo, le letture di ADC2 restituiscono valori casuali. Tutti i sensori analogici devono usare ADC1 (GPIO 32–35, GPIO 36 VP).
Inversione della logica del relay
Il modulo relay HW-316 usa logica invertita: LOW = relay ON, HIGH = relay OFF. Il firmware che non tiene conto di questo attiverà le pompe all’avvio o nei momenti sbagliati. Ogni comando relay nel firmware NestGrow è esplicitamente invertito.
Water hammer (logico)
Quando le soglie di irrigazione sono impostate in modo troppo aggressivo, il backend emette comandi troppo frequentemente, stressando gli attuatori. Soluzione: imporre una finestra di cooldown per zona (10 minuti in NestGrow) a livello del rule engine, indipendentemente dalle letture dei sensori.
Deriva dei sensori (capacitivi)
I sensori di umidità capacitivi derivano nel corso di settimane di utilizzo. Zone che leggono in modo accurato all’installazione mostrano un offset del +6–8% dopo 3–4 settimane. Soluzione: calibrazione dinamica della baseline per zona con routine di ricalibrazione settimanale.
Regole di Design
Questi sono gli invarianti architetturali di una fleet IoT stabile. Violateli e prima o poi si avranno incidenti in produzione.
| Regola | Motivazione |
|---|---|
| Lo stato del dispositivo è sempre transitorio | Non fidarsi mai di ciò che un dispositivo riporta come verità assoluta |
| Lo stato del backend è sempre autorevole | Ricostruire dall’event stream, non dal dispositivo |
| MQTT è trasporto, non verità | Il restart del broker non deve mai perdere lo stato del sistema |
| Ogni messaggio deve essere idempotente | La consegna duplicata è garantita su scala |
| Il guasto è la modalità operativa normale | Progettare per il guasto, non per il percorso felice |
| L’OTA deve sopravvivere alla perdita di alimentazione | Dual-partition + checksum è non negoziabile |
Anti-Pattern Comuni
Usare il dispositivo come fonte di verità. Porta a deriva dello stato e inconsistenza su scala. Il dispositivo sa cosa ha misurato — il backend decide cosa significa.
Assumere connettività stabile. Funziona sul banco. Si rompe immediatamente in ambienti reali con interferenze WiFi, power cycling o crash del firmware.
Logica OTA stateless. Qualsiasi implementazione OTA che non gestisce i download interrotti come caso di prima classe prima o poi manderà in brick un dispositivo in produzione.
Nessun layer di riconciliazione. Senza un processo backend che periodicamente riconcilia lo stato atteso vs. quello effettivo dei dispositivi, la desincronizzazione silenziosa si accumula invisibilmente.
Conclusione
Una fleet ESP32 in produzione non è definita dalla connettività — è definita dalla sua capacità di sopravvivere ai guasti parziali, recuperare uno stato deterministico, mantenere la consistenza del firmware e operare in condizioni instabili.
Il modello mentale corretto non è “dispositivi IoT connessi a un backend”. È:
Un sistema distribuito di nodi inaffidabili gestito attraverso la ricostruzione deterministica dello stato.
NestGrow è costruito esattamente su questo principio. Il codice sorgente completo del firmware ESP32 è disponibile come open source (licenza MIT) su github.com/jaffa2970/nestgrow-esp32. Il backend Docker è disponibile su lake8.dev/projects/nestgrow-docker.
Fonti
Espressif Systems — ESP32 Technical Reference Manual
https://www.espressif.com/en/support/documents/technical-documents
2023
OASIS Standard — MQTT Version 5.0
https://docs.oasis-open.org/mqtt/mqtt/v5.0/
2019
lake8.dev — Progetto NestGrow
https://lake8.dev/projects/nestgrow-docker/
2026
Diritti e attribuzioni
Immagini, loghi e fotografie citati o mostrati in questo articolo sono di proprietà dei legittimi titolari. Non si intende violare alcun diritto: i materiali sono utilizzati in relazione a fonti terze e con finalità di commento.
← Back to journal