MQTT QoS (calidad de servicio) establece cómo se confirma un mensaje en cada tramo. QoS 0 lo envía como máximo una vez sin confirmación, QoS 1 al menos una vez con un PUBACK y QoS 2 exactamente una vez con un intercambio de cuatro paquetes. Un mensaje retenido es el último mensaje que el broker conserva para un tema y envía a cada suscriptor nuevo.
Ninguna de las dos opciones indica la antigüedad de una lectura. 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.
Para los brokers, los temas, los comodines y los puertos, empiece por ¿Qué es MQTT?.
Niveles QoS 0, 1 y 2 de MQTT
QoS (calidad de servicio) establece el intercambio de confirmaciones en cada tramo. La sección 4.3 de la norma define tres niveles:
| QoS | Nombre | Paquetes por mensaje en un tramo | Resultado en ese tramo |
|---|---|---|---|
| 0 | Como máximo una vez | PUBLISH | Se entrega una vez o se pierde. Sin confirmación. |
| 1 | Al menos una vez | PUBLISH, PUBACK | Se entrega, pero puede llegar más de una vez. |
| 2 | Exactamente una vez | PUBLISH, PUBREC, PUBREL, PUBCOMP | Se 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.
Códigos de motivo de MQTT 5 y confirmaciones negativas
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 son confirmaciones negativas: el receptor rechaza el mensaje.
| Código | Nombre | Significado |
|---|---|---|
| 0x80 | Unspecified error | Rechazado sin indicar motivo |
| 0x83 | Implementation specific error | El PUBLISH es válido, pero el receptor no lo acepta |
| 0x87 | Not authorized | El cliente no puede publicar en este tema |
| 0x90 | Topic Name invalid | El tema está bien formado, pero no se acepta |
| 0x91 | Packet Identifier in use | El identificador de paquete ya está en uso |
| 0x97 | Quota exceeded | Se ha superado un límite de la implementación o un límite administrativo |
| 0x99 | Payload format invalid | La carga útil no coincide con su Payload Format Indicator |
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
| Datos | Opción habitual |
|---|---|
| Lecturas frecuentes donde perder una muestra no importa | QoS 0 |
| Lecturas de energía, contadores y eventos que deben llegar | QoS 1, eliminando duplicados en el receptor |
| Órdenes y transacciones únicas | QoS 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:
{"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 de MQTT y la marca retain
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:
- 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.
- 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. MQTT 3.1.1 no tiene caducidad de mensajes; allí la única protección es comprobar la marca temporal.
- Publique las lecturas sin la marca retain y retenga solo un tema de estado.
Caducidad de mensajes de MQTT y otros intervalos
Message Expiry Interval es una propiedad de PUBLISH en MQTT 5 (sección 3.3.2.3.3). Indica la vida útil del mensaje en segundos. Si no se incluye, el mensaje no caduca. Si el intervalo se cumple antes de que el broker empiece a entregar una copia a un suscriptor, el broker elimina esa copia. Cuando sí reenvía el mensaje, fija el intervalo en el valor recibido menos el tiempo que el mensaje esperó en el broker, de modo que el suscriptor conoce cuánto le queda.
MQTT 5 tiene tres intervalos que se confunden con facilidad. Los tres se expresan en segundos, y MQTT 3.1.1 no tiene ninguno.
| Intervalo | Se fija en | Si no se incluye | Qué controla |
|---|---|---|---|
| Message Expiry Interval | PUBLISH | El mensaje no caduca | Cuánto tiempo conserva el broker un mensaje retenido o en cola |
| Session Expiry Interval | CONNECT; el broker puede devolver otro valor en CONNACK | 0: la sesión termina al cerrarse la conexión | Cuánto tiempo conserva el broker las suscripciones y los mensajes en cola tras una desconexión; 0xFFFFFFFF no caduca nunca |
| Will Delay Interval | Las propiedades del testamento en CONNECT | 0: sin retraso | Cuánto espera el broker tras una desconexión antes de publicar el testamento |
Sesiones persistentes, caducidad de sesión 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, y un valor de 0xFFFFFFFF significa que la sesión nunca caduca (sección 3.1.2.11.2). El broker puede usar un valor distinto del que envió el cliente. En ese caso devuelve su valor en CONNACK (sección 3.2.2.3.2), así que lea CONNACK en lugar de suponer la caducidad solicitada. 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, y una cola llena descarta mensajes sin avisar al publicador. 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í:
- El Gateway se conecta con un testamento «offline» en su tema de estado y Will Retain = 1.
- Cuando la conexión está activa, publica «online» en ese tema con la marca retain.
- 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 el registro 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
¿Cuáles son los niveles QoS de MQTT?
QoS 0 es como máximo una vez: un PUBLISH, sin confirmación, y el mensaje puede perderse. QoS 1 es al menos una vez: el receptor responde con un PUBACK y el mensaje puede llegar más de una vez. QoS 2 es exactamente una vez: cuatro paquetes por mensaje eliminan los duplicados en ese tramo. Cada nivel se aplica a un solo tramo, del publicador al broker o del broker al suscriptor.
¿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.
¿Qué es el intervalo de caducidad de mensajes de MQTT?
Una propiedad de PUBLISH de MQTT 5 que indica la vida útil del mensaje en segundos. Si no se incluye, el mensaje no caduca. El broker elimina una copia cuya entrega no ha empezado cuando se cumple el intervalo, y reenvía la vida restante, no el valor original. MQTT 3.1.1 no tiene caducidad de mensajes.
¿Qué es el intervalo de caducidad de sesión de MQTT?
Una propiedad de CONNECT de MQTT 5 que fija cuánto tiempo, en segundos, conserva el broker la sesión de un cliente después de cerrarse la conexión. Con 0, o sin valor, la sesión termina al desconectarse, y 0xFFFFFFFF significa que la sesión nunca caduca. El broker puede usar otro valor y, en ese caso, lo devuelve en CONNACK.
¿Qué ocurre cuando la cola de un broker MQTT está llena?
El broker descarta los mensajes que superan su límite para ese cliente. En Mosquitto el límite es max_queued_messages, 1.000 por cliente por defecto. El publicador no recibe ningún aviso, porque su PUBACK llegó del broker antes de que la cola se desbordara. Dimensione la cola según el ritmo de mensajes y la interrupción más larga prevista, o conserve el historial en el publicador.