Progetto esterno — robustezza di un sistema IoT
Centraline di un cliente che perdevano la rete. Come leggere 30.000 righe ha battuto quattro fix a tentativi — e ha tenuto 60 giorni.
Firmware embedded, integrazione cellulare, diagnostica
- Arduino Edge Control nRF52840
- SIM7080G Cat-M / NB-IoT
- TinyGSM (patch interne)
- PubSubClient / MQTT
- Node-RED
- emnify (SIM IoT)
- Oltre 30.000 righe lette tra manuale e librerie
- 5 bug strutturali scoperti in una libreria diffusa
- Il manuale del modem: 379 pagine, lette per intero
01 Colpo d'occhio
01 Il problema
Due centraline IoT in campo, su un cliente esterno: un'altra azienda agricola, un'altra rete cellulare al limite della copertura. Lo stesso hardware di Madonnina, lo stesso approccio — ma in condizioni davvero critiche: segnale al limite, gestore cellulare che riconfigurava silenziosamente la rete, ore notturne in cui il modem perdeva la connessione dati senza un motivo evidente nei log.
Il primo tentativo è stato veloce: applicare le patch già usate altrove. Non ha funzionato. Il secondo: un fix mirato a quello che sembrava il sintomo. Nemmeno quello. Il terzo, il quarto. Quattro versioni del firmware in quattro giorni, e il sistema ricadeva ogni volta.
A quel punto era chiaro che continuare a tentare correzioni rapide era una perdita di tempo. Le condizioni estreme di questo cliente non erano un fastidio da aggirare: erano l'occasione per scavare più a fondo di quanto qualunque cliente "facile" avrebbe mai richiesto.
02 Cosa ho costruito
Oggi le due centraline sono in campo e in esercizio. Il firmware regge la rete instabile in modo autonomo: quando perde la connessione, prova diversi percorsi di recupero e torna online da solo, senza che qualcuno debba andare sul posto a riavviare nulla.
Accanto al firmware, una diagnostica che rende visibile in tempo reale cosa sta facendo il modem: a quale antenna è agganciato, su quale banda, qual è la qualità del segnale, quanti tentativi di riconnessione ha fatto nelle ultime ore. Le informazioni che prima erano rumore bianco nei log adesso sono leggibili.
02 Superficie
03 Come l'ho costruito
Mi sono fermato. Invece del quinto fix in cinque giorni, ho deciso di leggere tutto: il manuale del modem SIM7080G, trecentosettantanove pagine; il sorgente della libreria cellulare TinyGSM, oltre mille e cinquecento righe; il codice della libreria MQTT, mille righe; lo sketch della centralina, millesettecento righe; le mie cinque analisi precedenti. Più di trentamila righe in totale, lette per intero — non saltate, non a campione.
Leggere non bastava. Per capire perché il modem perdeva la connessione *a quell'ora* e *in quel punto*, ho incrociato dati che di solito vivono in mondi separati: le posizioni delle antenne del gestore cellulare, la geoposizione precisa di ogni centralina, lo storico dei riavvii sul broker, gli eventi meteo della stessa notte, i comandi inviati dal Node-RED. Una diagnosi cross-dominio, fatta con nove agenti AI specializzati ognuno su un asse: rete cellulare, MQTT, firmware C++, NAT del gestore, infrastruttura cloud, hardware.
Da quell'incrocio sono emersi cinque bug strutturali — non miei: in TinyGSM, una libreria open source usata in tutto il mondo per i modem SIM7080G. Un comando di restart che non smonta in modo pulito i contesti dati. Una connessione che si dichiara ancora attiva mentre è morta. Un retry che, a ogni tentativo, forza la centralina a una nuova registrazione completa alla rete — quattro chiamate di sistema dove ne bastava una. Ho scritto le patch e le ho applicate al firmware.
Per spiegare la diagnosi al cliente — un imprenditore agricolo, non un ingegnere — ho costruito con NotebookLM un podcast di diciotto minuti, in italiano comprensibile, che adesso può ascoltare in macchina.
04 La decisione chiave
Specializzare per le condizioni reali, invece di mantenere la portabilità ovunque. Due scelte conseguenti.
Sulla rete: ho bloccato il modem sulle tre bande Cat-M effettivamente disponibili in Italia (3, 8, 20) invece di lasciarlo libero di scansionare le diciassette bande dello standard mondiale. La scansione completa impiegava oltre quattro minuti per agganciarsi a una rete; con il band lock, l'aggancio scende a ventotto secondi — circa otto volte più veloce. Costo: il firmware non è più portabile fuori dall'Italia senza ricompilare. Beneficio: un sistema che si riprende molto più in fretta dopo ogni evento di rete.
Sul codice scoperto in questo cliente: ho portato il pattern di mitigazione anche su Madonnina, ma in versione ridotta. Madonnina ha condizioni di rete migliori e non soffre dello stesso difetto strutturale; ne bastano quattro sentinelle di sorveglianza invece di cinque. Il fix integrale di questo cliente non è andato in produzione su Madonnina: avrebbe aggiunto complessità senza beneficio.
Il compromesso è onesto: due firmware su due profili diversi invece di un'unica codebase universale. Più disciplina di manutenzione, in cambio di sistemi tarati esattamente sulle condizioni reali in cui devono funzionare.
03 Profondità
05 Sotto il cofano
Il dettaglio tecnico Hardware, scelte di architettura, compromessi — per chi legge il codice
Hardware. Stesso di Madonnina: Arduino Edge Control con nRF52840 e modulo M5Stack SIM7080G. Qui le centraline sono due, su rete Cat-M/NB-IoT con SIM dedicate.
Le cinque patch a TinyGSM. Cinque interventi nel sorgente della libreria cellulare:
1. `modem.restart()` usava `+CREBOOT`, che non smonta in modo pulito i contesti PDP lato gestore — gli orfani si accumulavano fino a saturare i timer NAT. Sostituito con una sequenza che smonta prima e riaccende dopo. 2. `isGprsConnected()` ritornava `true` anche su contesti morti: ignorava un URC del modem (`+APP PDP: 0,DEACTIVE`) che segnala la caduta del PDP. Il parser ora lo cattura. 3. `gprsConnect()` chiamava sempre `gprsDisconnect()` prima di ogni retry, forzando un detach completo: cinque retry diventavano cinque registrazioni complete alla rete. Aggiunto un fast-path che riusa la sessione esistente quando il modulo è ancora agganciato. 4. La verifica di stato MQTT usava `mqtt.connected()`, che ha un side-effect (manda `AT+CACLOSE` se il client interno non risponde). Sostituita con `mqtt.state() < 0`, senza effetti collaterali. 5. Il timeout di rete prima dell'handshake era 15 secondi: troppo poco sulle reti italiane reali, dove un aggancio legittimo richiede 25-30 secondi. Esteso a 300 secondi, controllato da un watchdog hardware indipendente.
Recovery del modem a stadi. Quando una delle sentinelle scatta, parte una procedura a livelli crescenti di invasività: soft reset (`AT+CFUN=0/1`), reset profondo, infine power cycle hardware via GPIO sul pin di enable. Il livello successivo si attiva solo se quello precedente non risolve in trenta secondi.
Cinque sentinelle MQTT liveness. Cinque controlli indipendenti che si interrogano a vicenda per evitare falsi positivi: polling del TCP raw via `AT+CASTATE?` (verità del modem, ignora la cache della libreria), heartbeat applicativo con ack atteso entro 150 secondi, rilevamento del cambio IP via `AT+CGPADDR=1`, stato del client `PubSubClient`, keepalive del protocollo MQTT. Madonnina, in condizioni di rete migliori, ne usa quattro.
Band lock Cat-M. Il modem è bloccato sulle bande italiane B3 (1800 MHz), B8 (900 MHz), B20 (800 MHz). L'aggancio iniziale è 28 secondi in stress test; la scansione completa delle 17 bande dello standard impiegava oltre 4 minuti — abbastanza da far scattare lo stesso watchdog di rete pensato per recuperare da uno stallo.
L'infrastruttura cloud del cliente non è materia raccontabile in dettaglio.
06 Cosa è cambiato
Cinque bug strutturali documentati. In uno stress test in laboratorio il sistema ha tenuto: una sola recovery del modem in trenta minuti, recupero autonomo in cinque minuti dopo un blackout della SIM di novanta secondi.
In campo, le centraline sono in produzione e stabili da circa quindici giorni dopo il fix.
Cosa viene dopo: portare le cinque patch alla repository pubblica di TinyGSM con una pull request. La libreria è usata da migliaia di progetti IoT in tutto il mondo; le condizioni critiche di questo cliente hanno permesso di trovare qualcosa che può servire anche ad altri.