Protocolos y datos

MQTT: QoS, mensajes retenidos y datos obsoletos

MQTT QoS 0, 1 y 2: confirmaciones, mensajes retenidos, sesiones persistentes, último testamento, caducidad de mensajes y comprobaciones de telemetría obsoleta.

Un panel se conecta a un broker MQTT a las 08:00 y muestra que un contador marca 42 kW. El contador perdió la alimentación a las 18:00 del día anterior. El broker envió al panel el último mensaje retenido del tema del contador; nada en el paquete MQTT indicaba que el valor tenía 14 horas de antigüedad.

Esta guía explica qué demuestra cada opción de entrega MQTT: los tres niveles QoS, los mensajes retenidos, las sesiones persistentes, el último testamento y la caducidad de mensajes. Los números de sección remiten a la norma OASIS MQTT 5.0; se indican las diferencias con MQTT 3.1.1.

Esta guía forma parte de la serie sobre OPC UA y MQTT, que comienza con OPC UA frente a MQTT y Modbus. Para TLS, certificados y permisos sobre temas, consulte la guía de MQTTS.

Los tres niveles QoS

QoS (calidad de servicio) establece el intercambio de confirmaciones en cada tramo. La sección 4.3 de la norma define tres niveles:

QoSNombrePaquetes por mensaje en un tramoResultado en ese tramo
0Como máximo una vezPUBLISHSe entrega una vez o se pierde. Sin confirmación.
1Al menos una vezPUBLISH, PUBACKSe entrega, pero puede llegar más de una vez.
2Exactamente una vezPUBLISH, PUBREC, PUBREL, PUBCOMPSe entrega una vez, sin duplicados en ese tramo.

QoS se aplica por tramo

Un mensaje atraviesa dos tramos: del publicador al broker y del broker a cada suscriptor. QoS se aplica por separado en cada uno. El broker entrega con el menor de dos valores: el QoS del PUBLISH y el QoS máximo concedido a la suscripción. Una lectura publicada con QoS 2 hacia una suscripción de QoS 0 llega con QoS 0.

PUBACK es la respuesta a un PUBLISH QoS 1 en un solo salto; recibirlo no demuestra por sí solo que el mensaje se haya aceptado. No demuestra que un suscriptor lo recibiera ni que una aplicación lo almacenara. En MQTT 5, PUBACK incluye un código de motivo (sección 3.4.2.1). Tanto 0x00 Success como 0x10 No matching subscribers significan aceptación. El broker puede enviar 0x10 si sabe que nadie está suscrito al tema, pero no está obligado a hacerlo; por eso 0x00 no demuestra que exista un suscriptor. Los códigos 0x80 y superiores rechazan el mensaje, por ejemplo 0x87 Not authorized y 0x97 Quota exceeded. Un publicador que considera correcto cualquier PUBACK pierde esas lecturas sin mostrar un error.

MQTT 3.1.1 no tiene códigos de motivo. Un broker que rechaza una publicación debe confirmarla de forma normal o cerrar la conexión (sección 3.3.5 de 3.1.1). Por tanto, incluso una publicación confirmada puede haber sido rechazada.

Elegir un nivel para telemetría

DatosOpción habitual
Lecturas frecuentes donde perder una muestra no importaQoS 0
Lecturas de energía, contadores y eventos que deben llegarQoS 1, eliminando duplicados en el receptor
Órdenes y transacciones únicasQoS 1 o 2, con caducidad de la orden y confirmación en la aplicación

QoS 1 suele elegirse para datos energéticos. Requiere dos paquetes por mensaje en cada tramo, frente a cuatro para QoS 2. Algunos servicios no ofrecen QoS 2: AWS IoT Core solo admite QoS 0 y 1.

Asigne a cada orden una caducidad además de un QoS. Una orden que espera en una cola durante una interrupción se envía cuando el cliente vuelve a conectarse. Un intervalo de caducidad de MQTT 5 (sección 3.3.2.3.3), o una hora límite en la carga útil, evita que la consigna de anoche llegue a la instalación esta mañana.

Duplicados

Con QoS 1, el emisor reenvía los PUBLISH no confirmados cuando se interrumpe la conexión (sección 4.4). Si el receptor ya procesó la primera copia, recibe la lectura dos veces.

Los campos del protocolo no identifican el duplicado. El broker establece la marca DUP para sus propios reintentos en el tramo de salida y no transmite la marca DUP que recibió (sección 3.3.1.1). El identificador de paquete pertenece a un solo tramo y el emisor puede reutilizarlo en cuanto recibe PUBACK (sección 2.2.1).

Elimine duplicados con una clave de aplicación: identidad del dispositivo, canal y marca temporal de medición, o un número de secuencia que la fuente incremente por cada lectura. Por ejemplo:

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

Un receptor que almacena las lecturas con una clave única formada por dispositivo, canal y ts descarta la segunda copia. Escriba la marca temporal en UTC, como texto RFC 3339 o milisegundos desde la época Unix, y sincronice el reloj de la fuente con NTP. La guía de marcas temporales explica la diferencia entre hora de medición y de llegada.

Mensajes retenidos

Si un PUBLISH lleva activada la marca retain, el broker lo almacena como último valor de ese tema (sección 3.3.1.3). Conserva un mensaje retenido por tema; uno nuevo sustituye al anterior y otro con carga útil vacía lo elimina. Si el retenido se publicó con QoS 0, el broker puede descartarlo en cualquier momento.

El broker envía el retenido a cada suscripción nueva que coincida con el tema, con la marca retain activada. Los mensajes posteriores de un publicador en directo llegan con la marca desactivada. El suscriptor puede distinguir así un valor almacenado de otro en directo, pero la marca no indica la antigüedad del dato. En MQTT 5, una suscripción con Retain As Published = 1 recibe la marca tal como la estableció el publicador. Los puentes utilizan esta opción, por lo que un suscriptor detrás de un puente no puede confiar en la marca.

En MQTT 5, el suscriptor también decide si recibe mensajes retenidos (sección 3.8.3.1). Retain Handling 0 los envía en cada suscripción, 1 solo en una suscripción nueva y 2 nunca. MQTT 3.1.1 no ofrece esta opción.

Retenga el estado que un suscriptor nuevo necesita conocer de inmediato, como la configuración o el estado de conexión de un dispositivo. La telemetría retenida provoca el problema del ejemplo inicial. Evítelo con estas medidas:

  1. Incluya la marca temporal de medición en cada carga útil y haga que el suscriptor la compruebe. Marque una lectura como obsoleta tras un número fijo de intervalos perdidos. Tres intervalos equivalen a 3 minutos para un contador que informa cada minuto.
  2. Establezca un intervalo de caducidad de MQTT 5 igual al límite de antigüedad, por ejemplo 180 s. Al cumplirse, el broker descarta el mensaje, incluso si está retenido. También reduce el intervalo según el tiempo que el mensaje estuvo en espera, de modo que el suscriptor conoce cuánto le queda. MQTT 3.1.1 no tiene caducidad de mensajes; allí la única protección es comprobar la marca temporal.
  3. Publique las lecturas sin la marca retain y retenga solo un tema de estado.

Sesiones y último testamento

Una sesión persistente conserva las suscripciones de un cliente mientras está desconectado. El broker pone en cola los mensajes QoS 1 y 2 que llegan para ese cliente y reenvía los que quedaron sin confirmar al interrumpirse la conexión. La norma permite, pero no exige, poner en cola mensajes QoS 0 (sección 4.1).

En MQTT 5, el cliente se conecta con Clean Start = 0 y un Session Expiry Interval superior a 0, por ejemplo 86.400 s para un día. Clean Start = 1 descarta la sesión anterior. Si Session Expiry Interval es 0 o no se incluye, la sesión termina al cerrarse la conexión (sección 3.1.2.11.2). En MQTT 3.1.1, el cliente utiliza Clean Session = 0 y el protocolo no fija una caducidad de sesión. En ambas versiones, la marca Session Present de CONNACK indica si el broker conservaba la sesión.

La cola del broker tiene un límite. Mosquitto admite hasta 1.000 mensajes QoS 1 y 2 por cliente, además de los que están en vuelo (max_queued_messages), y descarta los que superen ese límite. Por defecto no pone en cola QoS 0 para un cliente desconectado (queue_qos0_messages false). Un suscriptor que recibe 6 lecturas por minuto llena una cola de 1.000 mensajes en menos de 3 horas. Dimensione la cola según el ritmo de mensajes y la interrupción más larga prevista, o conserve el historial en el publicador.

El último testamento es un mensaje que el broker publica por el cliente si la conexión termina sin un DISCONNECT normal, por ejemplo tras un fallo de red, un keep-alive incumplido o el cierre del socket (sección 3.1.2.5). Un tema de estado lo utiliza así:

  1. El Gateway se conecta con un testamento «offline» en su tema de estado y Will Retain = 1.
  2. Cuando la conexión está activa, publica «online» en ese tema con la marca retain.
  3. Antes de apagarse de forma planificada, publica «offline» con retain y después se desconecta.

Sin Will Retain, el broker envía «offline» solo a los suscriptores actuales. El «online» retenido permanece en el tema y un panel que se conecta después muestra el Gateway como activo. El tercer paso es necesario porque DISCONNECT con código de motivo 0x00 elimina el testamento sin publicarlo.

MQTT 5 añade Will Delay Interval (sección 3.1.3.2.2). Si el cliente vuelve a conectarse antes de que termine, el broker no publica el testamento. Un retraso de 30 s evita que una breve caída de red muestre el Gateway como desconectado. El broker publica el testamento cuando termina el retraso o la sesión, lo que ocurra primero. Con Session Expiry Interval = 0, la sesión termina al desconectarse y el retraso no tiene efecto. MQTT 3.1.1 no tiene Will Delay.

Lista de comprobación de vigencia

Para cada tema que transporta telemetría, registre:

  • el QoS de publicación y de cada suscripción;
  • qué códigos de motivo PUBACK considera fallo el publicador;
  • si los mensajes se retienen y por qué;
  • la caducidad de mensajes, si existe;
  • dónde está la marca temporal de medición en la carga útil y en qué formato;
  • la clave que usa el receptor para eliminar duplicados;
  • la caducidad de sesión y el límite de la cola del broker;
  • el tema de estado y el último testamento de la fuente, con Will Retain activado;
  • a partir de qué antigüedad un panel o un cálculo considera obsoleta una lectura.

La guía de datos obsoletos explica cómo marcar y tratar lecturas demasiado antiguas.

MQTT con Edge

Edge en el Gateway ZGW-20 publica lecturas en el broker MQTT de una plataforma, por MQTTS cuando esta lo exige. Edge publica con QoS 1 y solo considera completa una entrega cuando llega el PUBACK del broker. Una desconexión o un error de publicación lleva las lecturas a una cola de salida local del Gateway. Por defecto, Edge reintenta enviar esa cola cada minuto mientras el destino esté disponible. Las lecturas reenviadas conservan su marca temporal original. La cola de salida no tiene límite de antigüedad. Por defecto, una protección frente a poco espacio en disco pausa la grabación de nuevas lecturas cuando el espacio libre baja del 10 % del sistema de archivos, con un máximo de 1 GiB, o de 256 MiB. Las lecturas que aún están en memoria cuando el Gateway pierde la alimentación todavía no están en la cola de salida y pueden perderse. La guía de almacenamiento y envío diferido explica cómo dimensionarla.

Un PUBACK por sí solo no demuestra que la plataforma haya aceptado o almacenado la lectura. Compruebe los códigos de motivo cuando se use MQTT 5; MQTT 3.1.1 no puede indicar en PUBACK una denegación del permiso de publicación. Un reenvío tras reconectar también puede entregar una lectura dos veces; la plataforma debe eliminar duplicados mediante dispositivo, canal y marca temporal. Durante la puesta en marcha, busque una lectura conocida en el almacenamiento de la plataforma. Compare su marca temporal y su valor con la lectura de Edge.

Preguntas frecuentes

¿Debo usar QoS 1 o QoS 2 para datos de sensores?

Use QoS 1 y elimine duplicados en el receptor con dispositivo, canal y marca temporal de medición. QoS 2 requiere cuatro paquetes por mensaje en cada tramo, solo elimina duplicados en ese tramo y no está disponible en algunos servicios: AWS IoT Core admite QoS 0 y 1. Un publicador que reenvía lecturas tras una interrupción puede generar duplicados que QoS 2 no detecta.

¿Cómo elimino un mensaje retenido?

Publique en el mismo tema un mensaje con la marca retain y la carga útil vacía. El broker elimina el mensaje retenido y no almacena el vacío. Los suscriptores actuales sí reciben el mensaje vacío y deben ignorarlo en vez de interpretarlo como una lectura.

¿Por qué un panel muestra un dispositivo como conectado después de fallar?

Normalmente porque el testamento se envió sin Will Retain. El broker publica «offline» solo a los suscriptores actuales, mientras el «online» retenido permanece para los siguientes. También puede ocurrir si el testamento nunca se activa: DISCONNECT normal lo elimina, por lo que el cliente debe publicar «offline» antes de un apagado planificado.