Protocolli e dati

MQTT QoS, messaggi retained e dati obsoleti

MQTT QoS 0, 1 e 2: conferme, messaggi retained, sessioni persistenti, Last Will, scadenza dei messaggi e controlli della telemetria obsoleta.

Una dashboard si collega a un broker MQTT alle 08:00 e mostra un contatore a 42 kW. Il contatore ha perso alimentazione alle 18:00 del giorno precedente. Il broker ha inviato alla dashboard l'ultimo messaggio conservato per il topic del contatore, ma il pacchetto MQTT non indicava che la lettura risaliva a 14 ore prima.

Questa guida spiega che cosa attestano le impostazioni di consegna MQTT: i tre livelli QoS, i messaggi retained, le sessioni persistenti, il Last Will e la scadenza dei messaggi. I riferimenti alle sezioni riguardano lo standard OASIS MQTT 5.0. Le differenze rispetto a MQTT 3.1.1 sono indicate esplicitamente.

Questa guida fa parte della serie su OPC UA e MQTT, che inizia con OPC UA, MQTT e Modbus a confronto. Per TLS, certificati e autorizzazioni sui topic, consultare la guida a MQTTS.

I tre livelli QoS

QoS (qualità del servizio) determina lo scambio di conferme per ciascuna tratta. La sezione 4.3 dello standard definisce tre livelli:

QoSNomePacchetti per messaggio su una trattaEsito su quella tratta
0Al massimo una voltaPUBLISHConsegnato una volta oppure perso. Nessuna conferma.
1Almeno una voltaPUBLISH, PUBACKConsegnato, ma può arrivare più volte.
2Esattamente una voltaPUBLISH, PUBREC, PUBREL, PUBCOMPConsegnato una volta, senza duplicati su quella tratta.

QoS si applica a ciascuna tratta

Un messaggio attraversa due tratte: dal publisher al broker e dal broker a ciascun subscriber. QoS si applica separatamente a ognuna. Il broker consegna al minore tra il QoS del PUBLISH e il QoS massimo concesso alla sottoscrizione. Una lettura pubblicata con QoS 2 verso una sottoscrizione QoS 0 arriva con QoS 0.

Un PUBACK è la risposta a un PUBLISH QoS 1 su una singola tratta; riceverlo non dimostra da solo che il messaggio sia stato accettato. Non dimostra che un subscriber lo abbia ricevuto né che un'applicazione lo abbia memorizzato. In MQTT 5, il PUBACK contiene un codice di risposta (sezione 3.4.2.1). I codici 0x00 Success e 0x10 No matching subscribers indicano entrambi l'accettazione. Il broker può inviare 0x10 se sa che nessun client è sottoscritto al topic, ma non è tenuto a farlo: 0x00 non prova l'esistenza di un subscriber. I codici da 0x80 in poi rifiutano il messaggio, per esempio 0x87 Not authorized e 0x97 Quota exceeded. Se il publisher considera riuscito ogni PUBACK, può perdere quelle letture senza segnalare un errore.

MQTT 3.1.1 non prevede codici di risposta. Un broker che rifiuta una pubblicazione deve confermarla normalmente oppure chiudere la connessione (sezione 3.3.5 di MQTT 3.1.1). Anche una pubblicazione confermata può quindi essere stata rifiutata.

Scegliere il livello per la telemetria

DatiScelta abituale
Letture frequenti per cui la perdita di un campione è accettabileQoS 0
Letture energetiche, contatori cumulativi ed eventi che devono arrivareQoS 1, con eliminazione dei duplicati sul ricevitore
Comandi e transazioni singoleQoS 1 o 2, scadenza del comando e conferma a livello applicativo

QoS 1 è la scelta abituale per i dati energetici. Richiede due pacchetti per messaggio su ciascuna tratta; QoS 2 ne richiede quattro. Alcuni servizi non offrono QoS 2: AWS IoT Core supporta solo QoS 0 e 1.

Assegnare a ogni comando una scadenza oltre al QoS. Un comando rimasto in coda durante un'interruzione viene inviato alla riconnessione del client. Un intervallo di scadenza MQTT 5 (sezione 3.3.2.3.3), oppure una data di validità nel payload, impedisce che il setpoint della sera precedente raggiunga l'impianto la mattina successiva.

Duplicati

Con QoS 1, il mittente ritrasmette ogni PUBLISH non confermato quando la connessione si interrompe (sezione 4.4). Se il ricevitore aveva già elaborato la prima copia, riceve la lettura due volte.

I campi del protocollo non identificano il duplicato applicativo. Il broker imposta il flag DUP per le proprie ritrasmissioni sulla tratta in uscita e non inoltra il flag DUP ricevuto (sezione 3.3.1.1). L'identificatore del pacchetto vale per una sola tratta; il mittente può riutilizzarlo appena riceve il PUBACK (sezione 2.2.1).

Eliminare i duplicati con una chiave applicativa: identità del dispositivo, canale e marca temporale della misura, oppure un numero di sequenza incrementato dalla sorgente per ogni lettura. Per esempio:

JSON
{"device":"meter-12","channel":"p_total","ts":"2026-09-23T14:05:00Z","seq":48213,"value":42.0,"unit":"kW"}

Un ricevitore che memorizza le letture con una chiave univoca composta da dispositivo, canale e ts scarta la seconda copia. Scrivere la marca temporale in UTC, nel formato RFC 3339 o in millisecondi dall'epoca Unix, e sincronizzare l'orologio della sorgente con NTP. La guida alle marche temporali distingue il momento della misura da quello dell'arrivo.

Messaggi retained

Quando un PUBLISH ha il flag retain impostato, il broker lo memorizza come ultimo valore del topic (sezione 3.3.1.3). Il broker conserva un messaggio retained per topic. Una nuova pubblicazione retained sostituisce la precedente; un payload vuoto la elimina. Se il messaggio retained è stato pubblicato con QoS 0, il broker può eliminarlo in qualsiasi momento.

Il broker invia il messaggio retained a ogni nuova sottoscrizione corrispondente, con il flag retain impostato. I successivi messaggi di un publisher attivo arrivano al subscriber con il flag disattivato. Il subscriber può così distinguere un valore conservato da uno appena pubblicato, ma il flag non ne indica l'età. In MQTT 5 una sottoscrizione con Retain As Published = 1 riceve il flag così come lo ha impostato il publisher. I bridge possono usare questa opzione: un subscriber a valle di un bridge non può quindi basarsi solo sul flag.

In MQTT 5 il subscriber può anche scegliere se ricevere i messaggi retained (sezione 3.8.3.1). Retain Handling 0 li invia a ogni sottoscrizione, 1 soltanto a una nuova sottoscrizione e 2 non li invia. MQTT 3.1.1 non prevede questa opzione.

Conservare con retain lo stato che un nuovo subscriber deve conoscere subito, per esempio la configurazione o lo stato online di un dispositivo. La telemetria retained crea il problema descritto all'inizio. Tre misure lo prevengono:

  1. Inserire la marca temporale della misura in ogni payload e farla verificare dal subscriber. Segnare la lettura come obsoleta dopo un numero definito di intervalli mancati: tre intervalli corrispondono a 3 minuti per un contatore che trasmette ogni minuto.
  2. Impostare l'intervallo di scadenza MQTT 5 pari al limite di validità, per esempio 180 s. Scaduto l'intervallo, il broker elimina il messaggio, anche se retained. Il broker riduce inoltre l'intervallo del tempo già trascorso in attesa, così che il subscriber veda il tempo residuo. MQTT 3.1.1 non prevede la scadenza dei messaggi: la verifica della marca temporale resta quindi l'unica protezione.
  3. Pubblicare le letture senza retain e conservare con retain soltanto il topic di stato.

Sessioni e Last Will

Una sessione persistente conserva le sottoscrizioni di un client mentre è disconnesso. Il broker accoda i messaggi QoS 1 e QoS 2 destinati al client e ritrasmette quelli non confermati all'interruzione della connessione. L'accodamento dei messaggi QoS 0 è facoltativo secondo lo standard (sezione 4.1).

In MQTT 5 il client si connette con Clean Start = 0 e un Session Expiry Interval maggiore di 0, per esempio 86.400 s per un giorno. Clean Start = 1 elimina la sessione precedente. Se Session Expiry Interval è 0 o assente, la sessione termina alla chiusura della connessione (sezione 3.1.2.11.2). In MQTT 3.1.1 il client usa Clean Session = 0 e il protocollo non definisce una scadenza della sessione. In entrambe le versioni, il flag Session Present nel CONNACK indica se il broker conserva ancora la sessione.

La coda del broker ha un limite. Mosquitto conserva fino a 1.000 messaggi QoS 1 e 2 per client oltre a quelli in transito (max_queued_messages) e scarta quelli che superano il limite. Per impostazione predefinita non accoda messaggi QoS 0 per un client disconnesso (queue_qos0_messages false). Un subscriber che riceve 6 letture al minuto riempie una coda da 1.000 messaggi in meno di 3 ore. Dimensionare la coda in base alla frequenza dei messaggi e alla massima interruzione prevista, oppure conservare la cronologia presso il publisher.

Il Last Will è un messaggio che il broker pubblica per conto del client quando la connessione termina senza un normale DISCONNECT: per esempio dopo un guasto di rete, un keep-alive mancato o la chiusura del socket (sezione 3.1.2.5). Un topic di stato lo usa così:

  1. Il gateway si connette specificando un Last Will «offline» sul proprio topic di stato, con Will Retain = 1.
  2. Quando la connessione è attiva, il gateway pubblica «online» sullo stesso topic con retain impostato.
  3. Prima di uno spegnimento pianificato, il gateway pubblica «offline» con retain impostato e poi si disconnette.

Senza Will Retain, il broker invia «offline» solo ai subscriber già connessi. Il valore retained «online» rimane sul topic e una dashboard collegata in seguito mostra il gateway ancora online. Il punto 3 è necessario perché un DISCONNECT con codice di risposta 0x00 elimina il Last Will senza pubblicarlo.

MQTT 5 aggiunge Will Delay Interval (sezione 3.1.3.2.2). Se il client si riconnette prima della fine del ritardo, il broker non pubblica il Last Will. Un ritardo di 30 s evita che una breve interruzione di rete mostri il gateway offline. Il broker pubblica il Last Will alla scadenza del ritardo oppure alla fine della sessione, a seconda di quale avviene prima. Con Session Expiry Interval = 0 la sessione termina alla disconnessione e il ritardo non ha effetto. MQTT 3.1.1 non prevede Will Delay Interval.

Lista di controllo della validità dei dati

Per ogni topic di telemetria, registrare:

  • il QoS di pubblicazione e quello di ogni sottoscrizione;
  • quali codici di risposta PUBACK il publisher considera errori;
  • se i messaggi sono retained e perché;
  • l'eventuale scadenza dei messaggi;
  • posizione e formato della marca temporale della misura nel payload;
  • la chiave con cui il ricevitore elimina i duplicati;
  • la scadenza della sessione e il limite della coda del broker;
  • il topic di stato e il Last Will della sorgente, con Will Retain impostato;
  • l'età massima della lettura prima che una dashboard o un calcolo la consideri obsoleta.

La guida ai dati obsoleti spiega come contrassegnare e gestire letture troppo vecchie.

MQTT con Edge

Edge sul Gateway ZGW-20 pubblica le letture verso il broker MQTT della piattaforma, tramite MQTTS quando la piattaforma lo richiede. Edge pubblica con QoS 1: la consegna è completa solo quando arriva il PUBACK del broker. Un'interruzione della connessione o un errore di pubblicazione colloca le letture nella coda locale persistente del Gateway. Per impostazione predefinita, Edge riprova l'invio dalla coda ogni minuto quando la destinazione è disponibile. Una lettura ritrasmessa conserva la marca temporale originale della misura. La coda non ha un limite temporale. Per impostazione predefinita, una protezione per spazio insufficiente sospende la registrazione di nuove letture quando lo spazio libero scende sotto il 10% del filesystem, con soglia massima di 1 GiB, oppure sotto 256 MiB. Le letture ancora in memoria quando il Gateway perde alimentazione non sono state inserite nella coda persistente e possono andare perse. La guida al buffer di memorizzazione e inoltro spiega come dimensionarlo.

Un PUBACK da solo non dimostra che la piattaforma abbia accettato o memorizzato la lettura. Controllare i codici di risposta quando si usa MQTT 5; MQTT 3.1.1 non può segnalare nel PUBACK un rifiuto del permesso di pubblicazione. Una ritrasmissione dopo la riconnessione può consegnare due volte la stessa lettura: la piattaforma deve eliminarne i duplicati usando dispositivo, canale e marca temporale. Durante la messa in servizio, trovare una lettura nota nella memoria persistente della piattaforma e confrontarne marca temporale e valore con quelli presenti in Edge.

Domande frequenti

Usare QoS 1 o QoS 2 per i dati dei sensori?

Usare QoS 1 ed eliminare i duplicati sul ricevitore con dispositivo, canale e marca temporale della misura. QoS 2 richiede quattro pacchetti per messaggio su ciascuna tratta ed elimina i duplicati solo su quella tratta. Alcuni servizi non lo supportano: AWS IoT Core offre QoS 0 e 1. Un publisher che ritrasmette le letture dopo un’interruzione può comunque creare duplicati che QoS 2 non riconosce.

Come eliminare un messaggio retained?

Pubblicare sullo stesso topic un messaggio con flag retain e payload vuoto. Il broker elimina il messaggio retained senza conservare quello vuoto. I subscriber già connessi ricevono comunque il messaggio vuoto e devono ignorarlo anziché interpretarlo come una lettura.

Perché una dashboard mostra online un dispositivo guasto?

Spesso il Last Will è stato inviato senza Will Retain. Il broker pubblica «offline» solo ai subscriber già connessi, mentre «online» resta il valore retained per quelli che si collegano in seguito. Un’altra causa è un Last Will che non viene eseguito: un normale DISCONNECT lo elimina, quindi il client deve pubblicare «offline» prima di uno spegnimento pianificato.