Fundamentos de MQTT

Opciones de suscripción de MQTT 5: No Local, Retain As Published y Retain Handling

Qué hace cada opción de suscripción de MQTT 5, cuándo usarla y cómo configurarla en MQTT.js y paho-mqtt, con ejemplos ejecutables contra el servidor MQTT público gratuito con MQTT 5.

Diagrama de las opciones de suscripción MQTT con los flags No Local, Retain As Published y Retain Handling en un paquete SUBSCRIBE
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

Las opciones de suscripción MQTT son flags por suscripción introducidos en MQTT 5.0 que controlan cómo te entrega el broker los mensajes: si recibes tus propias publicaciones, si se conserva el flag retain y si los mensajes retenidos se reenvían al suscribirte. Al terminar este artículo sabrás qué hace cada opción, qué problemas reales resuelve y cómo configurarla en JavaScript y Python.

Dónde residen las opciones de suscripción

En MQTT 3.1.1, un paquete SUBSCRIBE lleva un filtro de topic y un byte que contiene el QoS solicitado. MQTT 5.0 conserva ese byte, pero aprovecha los bits libres para nuevas opciones. Cada filtro de topic de un SUBSCRIBE tiene su propio byte de opciones:

Bits Opción Valores
0–1 Maximum QoS 0, 1 o 2
2 No Local 0 = desactivada, 1 = activada
3 Retain As Published 0 = desactivada, 1 = activada
4–5 Retain Handling 0, 1 o 2
6–7 Reservados deben ser 0

Como las opciones son por filtro, un mismo cliente puede suscribirse a chat/room1 con No Local activado y a config/# con un comportamiento de retención distinto en el mismo paquete. Las definiciones completas están en la especificación OASIS MQTT 5.0. Las opciones solo existen cuando el cliente se conecta con la versión 5 del protocolo; una conexión 3.1.1 no puede expresarlas. Para conocer el resto de la versión, consulta la visión general de las novedades de MQTT 5.

Maximum QoS

Es el QoS de siempre de MQTT 3.1.1, renombrado para dejar claro su significado. Es el QoS más alto con el que el broker entregará mensajes en esta suscripción. Un mensaje publicado con QoS 2 en una suscripción con Maximum QoS 1 llega con QoS 1. Un mensaje publicado con QoS 0 nunca se eleva. Si los niveles de QoS de MQTT son nuevos para ti, lee primero esa guía, porque la regla de reducción es el origen de muchas sorpresas en la entrega.

No Local: deja de recibir tus propios mensajes

Por defecto, si un cliente publica en un topic al que también está suscrito, el broker le devuelve el mensaje. Con No Local activado, el broker no reenvía un mensaje a la conexión que lo publicó.

Casos típicos:

  • Aplicaciones de chat y colaboración. Cada participante publica en chat/room1 y se suscribe a él. Sin No Local, cada usuario ve su propio mensaje dos veces: una desde la interfaz local y otra como eco del broker.
  • Sincronización bidireccional. Un servicio que publica y escucha en devices/+/config procesaría de otro modo sus propias actualizaciones y podría entrar en bucle.
  • Bridges. Un bridge que se suscribe y vuelve a publicar en el mismo broker puede crear bucles de mensajes; No Local en el lado del bridge los rompe.

Dos reglas que conviene recordar. No Local funciona por conexión, identificada por el client ID, así que dos clientes distintos ejecutados por la misma aplicación siguen recibiendo los mensajes del otro. Y activar No Local en una suscripción compartida ($share/...) es un error de protocolo, porque "local" no tiene sentido para un grupo.

Retain As Published

Cuando el broker reenvía un mensaje en directo a los suscriptores, por defecto borra el flag RETAIN, aunque el publicador lo haya activado. El flag solo se activa en los mensajes que el broker envía a raíz de una nueva suscripción (el mensaje retenido almacenado). Ese comportamiento por defecto permite a los suscriptores distinguir entre "este es el último valor almacenado" y "esto acaba de ocurrir".

Con Retain As Published activado, el broker mantiene el flag RETAIN exactamente como lo fijó el publicador. Lo necesitas sobre todo cuando el suscriptor vuelve a publicar los mensajes en otro lugar y el estado de retención debe sobrevivir:

  • Bridges entre brokers que replican el estado retenido.
  • Gateways y proxies que reenvían a un segundo sistema que a su vez respeta los mensajes retenidos.
  • Herramientas de depuración que quieren mostrar lo que el publicador envió realmente.

Para los suscriptores de aplicación normales, déjalo desactivado.

Retain Handling: controla el reenvío de retenidos

Los mensajes retenidos se envían a un cliente cuando se suscribe. Retain Handling decide cuándo ocurre:

Valor Comportamiento Úsalo cuando
0 Envía los mensajes retenidos en cada suscripción (por defecto) Dashboards y consumidores que necesitan el estado actual
1 Envía los mensajes retenidos solo si la suscripción no existía ya Clientes con sesiones persistentes que vuelven a suscribirse al reconectar
2 No envía nunca mensajes retenidos para esta suscripción Procesadores de eventos a los que solo les interesan los mensajes nuevos

El valor 1 resuelve un problema sutil. Un dispositivo con una sesión persistente se reconecta y vuelve a suscribirse por precaución. Con el valor 0 recibe de nuevo todos los mensajes retenidos y, si son mensajes de configuración, puede volver a aplicarlos. Con el valor 1, el broker solo reenvía los mensajes retenidos cuando la suscripción es realmente nueva.

El valor 2 es útil para loggers y servicios de alertas. Si un servicio de alertas se suscribe a alarms/# y el broker guarda alarmas retenidas de hace horas, el valor 0 haría que disparase alertas antiguas cada vez que se reinicia.

Cómo configurar las opciones de suscripción MQTT en código

Ambos ejemplos se conectan al servidor MQTT público gratuito broker.connectmq.com sobre TLS con MQTT 5. Usa tu propio prefijo de topic único; cualquiera puede leer el área pública, así que mantén datos inofensivos.

MQTT.js (Node.js)

// npm install mqtt
const mqtt = require("mqtt");

const prefix = "connectmq/demo/c41e";
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId: "subopts-js-c41e",
  protocolVersion: 5,
});

client.on("connect", () => {
  // No Local: no nos devuelvas como eco nuestros propios mensajes de chat
  client.subscribe(`${prefix}/chat/room1`, { qos: 1, nl: true }, (err, granted) => {
    if (err) return console.error(err);
    console.log("chat granted:", granted);
    client.publish(`${prefix}/chat/room1`, "hello from js", { qos: 1 });
  });

  // Retain Handling 2: ignora las alarmas almacenadas, solo las nuevas
  client.subscribe(`${prefix}/alarms/#`, { qos: 1, rh: 2 });

  // Retain As Published: conserva el flag RETAIN tal como se envió
  client.subscribe(`${prefix}/config/#`, { qos: 1, rap: true, rh: 1 });
});

client.on("message", (topic, payload, packet) => {
  console.log(`${topic} retain=${packet.retain} -> ${payload.toString()}`);
});

Ejecútalo y no verás impreso "hello from js", porque No Local suprime el eco. Publica en el mismo topic de chat desde otro cliente y el mensaje aparecerá.

Python paho-mqtt 2.x

import ssl
import paho.mqtt.client as mqtt
from paho.mqtt.subscribeoptions import SubscribeOptions

PREFIX = "connectmq/demo/c41e"

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print("Connect failed:", reason_code)
        return
    client.subscribe([
        (f"{PREFIX}/chat/room1", SubscribeOptions(qos=1, noLocal=True)),
        (f"{PREFIX}/alarms/#", SubscribeOptions(
            qos=1, retainHandling=SubscribeOptions.RETAIN_DO_NOT_SEND)),
        (f"{PREFIX}/config/#", SubscribeOptions(
            qos=1, retainAsPublished=True,
            retainHandling=SubscribeOptions.RETAIN_SEND_IF_NEW_SUB)),
    ])

def on_subscribe(client, userdata, mid, reason_code_list, properties):
    print("SUBACK:", [str(rc) for rc in reason_code_list])

def on_message(client, userdata, msg):
    print(f"{msg.topic} retain={msg.retain} -> {msg.payload.decode()}")

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id="subopts-py-c41e", protocol=mqtt.MQTTv5)
client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.connect("broker.connectmq.com", 8883, keepalive=60)
client.loop_forever()

Cómo verificar el comportamiento

Un plan de prueba rápido que puedes completar en pocos minutos:

  1. Publica un mensaje retenido en connectmq/demo/c41e/alarms/pump1 desde cualquier cliente, por ejemplo el cliente MQTT integrado en el navegador del portal de ConnectMQ o mosquitto_pub -r.
  2. Inicia el script de Python. La suscripción de alarmas usa Retain Handling 2, así que la alarma retenida no se entrega.
  3. Publica una alarma nueva no retenida. Llega de inmediato.
  4. Publica un mensaje retenido en .../config/rate. Como Retain As Published está activado, el script imprime retain=True para el mensaje en directo.
  5. Reinicia el script sin cambiar nada más y confirma que el comportamiento es repetible.

Errores comunes

  • Conectarse con MQTT 3.1.1. Las librerías ignoran sin avisar las opciones de suscripción en una conexión 3.1.1. Fija siempre protocolVersion: 5 o protocol=mqtt.MQTTv5.
  • No Local en suscripciones compartidas. El broker lo trata como un error de protocolo y cierra la conexión.
  • Esperar que Retain Handling 1 funcione con sesiones limpias. Si la sesión es nueva cada vez, todas las suscripciones son nuevas, así que el valor 1 se comporta como el valor 0.
  • Usar Retain As Published en dashboards. El dashboard deja de poder distinguir un valor almacenado de uno reciente. Mantén el valor por defecto salvo que reenvíes mensajes.
  • Ignorar el SUBACK. Un reason code como 0x80 o 0x87 significa que la suscripción fue rechazada. Registra los reason codes del SUBACK.

Cómo elegir las opciones según el rol

Rol del cliente Max QoS No Local Retain As Published Retain Handling
Dashboard 1 desactivada desactivada 0
Participante de chat 1 activada desactivada 0 o 2
Servicio de alertas 1 desactivada desactivada 2
Dispositivo con sesión persistente 1 desactivada desactivada 1
Bridge o reenviador 2 activada activada 0

Estas opciones forman parte de lo que hace que ConnectMQ y otros brokers MQTT 5 sean más fáciles de usar como base que sus predecesores 3.1.1: un comportamiento que antes exigía filtrado a nivel de aplicación es ahora un flag en el paquete SUBSCRIBE. Combínalas con un buen diseño de topics basado en los comodines de MQTT y ten a mano el tutorial de MQTT si todavía estás aprendiendo lo básico.

Empieza con ConnectMQ

Prueba las opciones de suscripción contra el servidor MQTT público gratuito y después pasa a un broker MQTT gestionado privado con MQTT 5.0, TLS y credenciales generadas. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito.

Preguntas frecuentes

¿Están disponibles las opciones de suscripción MQTT en MQTT 3.1.1?

No. MQTT 3.1.1 solo transporta el QoS solicitado. No Local, Retain As Published y Retain Handling requieren una conexión MQTT 5.0.

¿Cuál es el valor por defecto de cada opción de suscripción?

No Local y Retain As Published están desactivadas por defecto, y Retain Handling vale 0 por defecto, lo que envía los mensajes retenidos en cada suscripción. Esto coincide con el comportamiento de MQTT 3.1.1.

¿Por qué sigo recibiendo mis propios mensajes con No Local activado?

Comprueba que estás conectado con MQTT 5 y que el publicador y el suscriptor son la misma conexión. Un segundo cliente, aunque esté en el mismo proceso, no es local.

¿Puedo cambiar las opciones de suscripción sin cancelar la suscripción?

Sí. Enviar un nuevo SUBSCRIBE para el mismo filtro de topic sustituye las opciones de la suscripción existente. Con Retain Handling 1, los mensajes retenidos no se reenvían porque la suscripción ya existe.

Artículos relacionados

¿Quién inventó MQTT? La historia, de los oleoductos al IoT

MQTT lo inventaron en 1999 Andy Stanford-Clark, de IBM, y Arlen Nipper, de Arcom, para monitorizar oleoductos vía satélite. Sigue su camino hacia el open source, OASIS, ISO y los brokers MQTT gestionados en la nube de hoy.

8 min de lectura

Por qué tu cliente MQTT se desconecta constantemente (y cómo solucionarlo)

Los bucles de reconexión son uno de los problemas más habituales de MQTT. Conoce las cinco causas típicas, desde los client ID duplicados hasta los timeouts de keep alive y los paquetes demasiado grandes, y sigue un flujo de diagnóstico que localiza la tuya.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.