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/room1y 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/+/configprocesarí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:
- Publica un mensaje retenido en
connectmq/demo/c41e/alarms/pump1desde cualquier cliente, por ejemplo el cliente MQTT integrado en el navegador del portal de ConnectMQ omosquitto_pub -r. - Inicia el script de Python. La suscripción de alarmas usa Retain Handling 2, así que la alarma retenida no se entrega.
- Publica una alarma nueva no retenida. Llega de inmediato.
- Publica un mensaje retenido en
.../config/rate. Como Retain As Published está activado, el script imprimeretain=Truepara el mensaje en directo. - 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: 5oprotocol=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
0x80o0x87significa 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.



