Fundamentos de MQTT

Estado online/offline de dispositivos MQTT con LWT y mensajes retenidos

Un patrón de presencia paso a paso para flotas MQTT: mensajes de nacimiento retenidos, un Last Will retenido, mensajes de apagado ordenado y un monitor que sigue en tiempo real el estado de cada dispositivo.

Panel de flota con indicadores verdes y grises que muestran el estado online MQTT de cada dispositivo
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

Saber qué dispositivos están conectados en este momento es lo primero que pide cualquier operador, y el estado online de los dispositivos MQTT es sorprendentemente fácil de implementar mal. El patrón fiable combina tres piezas: un mensaje de nacimiento retenido, un Last Will retenido y un mensaje offline explícito al apagar. En esta guía construirás ese patrón paso a paso, añadirás un monitor de flota en Python y aprenderás a evitar los casos límite que hacen que los paneles mientan.

Las piezas básicas

Cada pieza resuelve una parte del problema:

Pieza Quién publica Cuándo Cubre
Mensaje de nacimiento (online, retenido) Dispositivo Justo después de cada conexión correcta El dispositivo está activo, también tras reconexiones
Last Will (offline, retenido) Broker, en nombre del dispositivo Conexión perdida sin DISCONNECT Caídas, cortes de alimentación, fallos de red
Mensaje de apagado (offline, retenido) Dispositivo Antes de una desconexión limpia Reinicios y apagados planificados
Flag retain en los tres Todos Siempre Los paneles abiertos más tarde siguen viendo el estado actual

Si omites cualquiera de ellas, tendrás un punto ciego. Sin el mensaje de nacimiento, un dispositivo que se recupera sigue "offline". Sin el will, las caídas pasan desapercibidas. Sin el mensaje de apagado, un apagado planificado deja el dispositivo "online" para siempre, porque un DISCONNECT limpio indica al broker que descarte el will. Si el mecanismo del will es nuevo para ti, lee primero MQTT Last Will and Testament.

Diseñar el topic de estado

Asigna a cada dispositivo exactamente un topic de estado, en un lugar predecible:

connectmq/demo/<prefix>/<site>/<device-id>/status

Así, un comodín como connectmq/demo/<prefix>/+/+/status devuelve el estado de toda la flota con una sola suscripción. Como todos los mensajes de estado son retenidos, un monitor que arranca recibe de inmediato el último estado conocido de cada dispositivo. El funcionamiento de la retención se explica en mensajes retenidos MQTT.

Como payload, una cadena simple online u offline basta para muchas herramientas. JSON añade contexto que ayuda en operaciones:

{"state":"online","ts":1760600000,"fw":"2.4.1","ip":"10.0.4.17","reason":"boot"}

Para el will, el dispositivo no puede conocer la marca de tiempo de un fallo futuro, así que mantenlo simple: {"state":"offline","reason":"lwt"}. El monitor registra cuándo lo recibió.

Paso 1: el dispositivo

Este script con paho-mqtt 2.x se comporta como un dispositivo bien diseñado. Configura un will retenido antes de conectarse, publica un mensaje de nacimiento retenido dentro de on_connect para que se repita tras cada reconexión y publica un mensaje offline retenido en el apagado ordenado. Se conecta al servidor MQTT público gratuito en broker.connectmq.com, pensado para pruebas y prototipos, no para datos de producción:

import json
import signal
import sys
import time
import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
DEVICE_ID = sys.argv[1] if len(sys.argv) > 1 else "pump-07"
STATUS = f"connectmq/demo/z8b2/plant1/{DEVICE_ID}/status"

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id=f"z8b2-{DEVICE_ID}")
client.will_set(STATUS, json.dumps({"state": "offline", "reason": "lwt"}),
                qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=30)

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print("connect failed:", reason_code)
        return
    birth = {"state": "online", "ts": int(time.time()), "reason": "connect"}
    client.publish(STATUS, json.dumps(birth), qos=1, retain=True)
    print("online")

def shutdown(signum, frame):
    bye = {"state": "offline", "ts": int(time.time()), "reason": "shutdown"}
    client.publish(STATUS, json.dumps(bye), qos=1, retain=True).wait_for_publish(timeout=5)
    client.disconnect()
    client.loop_stop()
    sys.exit(0)

client.on_connect = on_connect
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)

client.connect(BROKER, 1883, keepalive=20)
client.loop_start()
while True:
    time.sleep(1)  # aquí se publicaría la telemetría real

Ejecútalo como python device.py pump-07 y, en otra terminal, como python device.py fan-02.

Paso 2: el monitor de flota

El monitor se suscribe a todos los topics de estado y mantiene una tabla de dispositivos. Como los estados son retenidos, conoce el estado completo de la flota a los pocos segundos de arrancar:

import json
import time
import uuid
import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
FILTER = "connectmq/demo/z8b2/+/+/status"
fleet = {}

def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(FILTER, qos=1)

def on_message(client, userdata, msg):
    device = msg.topic.split("/")[-2]
    if not msg.payload:  # payload retenido vacío = dispositivo dado de baja
        fleet.pop(device, None)
        return
    try:
        state = json.loads(msg.payload).get("state", "unknown")
    except ValueError:
        state = msg.payload.decode(errors="replace")
    previous = fleet.get(device, {}).get("state")
    fleet[device] = {"state": state, "since": time.strftime("%H:%M:%S")}
    tag = "retained" if msg.retain else "live"
    if previous != state:
        print(f"[{tag}] {device}: {previous} -> {state}")

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id=f"monitor-{uuid.uuid4().hex[:8]}")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, 1883, keepalive=30)
client.loop_forever()

Paso 3: prueba todos los modos de fallo

Ejecuta el monitor y los dos dispositivos, recorre esta lista y confirma que el monitor reacciona correctamente cada vez:

  1. Reinicio del monitor. Detén y vuelve a arrancar el monitor. Ambos dispositivos aparecen de inmediato como online, marcados como retained.
  2. Apagado ordenado. Pulsa Ctrl+C en fan-02. El monitor muestra offline con el motivo shutdown al instante.
  3. Caída. Mata pump-07 con kill -9. El sistema operativo cierra el socket, el broker publica el will y el monitor muestra offline.
  4. Pérdida de red silenciosa. Desconecta la red del dispositivo sin matar el proceso. Tras aproximadamente 1,5 veces el keepalive de 20 segundos, se dispara el will.
  5. Recuperación. Restablece la red. paho se reconecta, se ejecuta on_connect y el mensaje de nacimiento devuelve el dispositivo a online.
  6. Baja del dispositivo. Publica un mensaje retenido vacío en el topic de estado. El monitor elimina el dispositivo.

También puedes ver los mismos topics en directo desde el navegador; el cliente web y el explorador de topics del portal de ConnectMQ muestran los cambios de estado en el momento en que se producen cuando apuntas los dispositivos a tu propio broker.

Casos límite que hacen que los paneles mientan

Client ID duplicados. Dos dispositivos con el mismo ID se expulsan mutuamente. Cada toma de control puede disparar un will y un nacimiento, de modo que el estado oscila sin parar. Los ID únicos no son negociables; consulta la guía del client ID de MQTT.

Nacimiento publicado solo una vez al arrancar. Si publicas online fuera del callback de conexión, un dispositivo que se reconecta tras un corte de red se queda offline para siempre. Publícalo siempre en cada conexión.

Will no retenido. Un panel que se abre después del fallo nunca se entera de él.

Reconexiones rápidas que provocan parpadeo. Un dispositivo que se reconecta en segundos sigue generando un par offline/online. En MQTT 5, un intervalo de retardo del will suprime el will si la sesión se reanuda a tiempo.

Keepalive largo. Con un keepalive de 300 segundos, un dispositivo caído parece online durante más de siete minutos. Elige un keepalive acorde con la rapidez con la que necesitas enterarte.

"Conectado" no es "sano". Un dispositivo puede mantener su conexión MQTT aunque su sensor haya fallado. Publica un campo de salud o heartbeat aparte si necesitas comprobar que la aplicación sigue viva.

Presencia frente a heartbeat: ¿cuál necesitas?

La presencia basada en la conexión y los heartbeats de aplicación responden a preguntas distintas, y muchas flotas necesitan ambos.

Señal Responde a Tiempo de detección Coste
Nacimiento + LWT en el topic de estado ¿Está el dispositivo conectado al broker? De inmediato a 1,5 x keepalive Casi nulo; solo pings de keepalive
Mensaje de heartbeat periódico ¿Sigue ejecutándose el bucle de la aplicación? Uno o dos intervalos de heartbeat Una pequeña publicación por intervalo
Comprobación de frescura de la telemetría ¿Sigue el sensor produciendo datos? Depende de la frecuencia de publicación esperada Ninguno en el dispositivo; lógica en el consumidor

Un dispositivo cuyo bucle principal se ha colgado puede seguir respondiendo a los pings de keepalive si el cliente MQTT se ejecuta en su propio hilo, así que el estado de conexión sigue en online. Un heartbeat publicado desde el bucle principal, o un consumidor que marque los dispositivos cuya telemetría ha dejado de actualizarse, detecta ese caso. Mantén los heartbeats sin retener y con una frecuencia moderada; una vez por minuto es más que suficiente para la mayoría de la monitorización.

Usar los topics de estado en otras herramientas

Como el patrón son simplemente mensajes MQTT retenidos, cualquier herramienta compatible con MQTT puede consumirlo sin una integración especial. En Node-RED, un nodo mqtt in suscrito al comodín de estado alimenta un panel o un flujo de notificaciones. La integración MQTT de Home Assistant puede asignar un topic de estado a una configuración de disponibilidad con los payloads online y offline. La entrada MQTT consumer de Telegraf puede escribir los cambios de estado en una base de datos de series temporales para informes de disponibilidad. Cada una de ellas se conecta al broker como un cliente MQTT corriente, así que el único requisito es ponerse de acuerdo en los nombres de topic y los valores del payload. Las cadenas simples online y offline son la opción más portable si piensas usar herramientas estándar; JSON aporta un contexto más rico para tu propio código.

Estado en el lado del broker con ConnectMQ

El patrón anterior es portable a cualquier broker. Además, ConnectMQ publica el estado de conexión en $SYS/broker/connection/{clientId}/state, al que un monitor puede suscribirse como fuente independiente que no depende de que el firmware del dispositivo configure correctamente su will. La vista de dispositivos del portal organiza tus dispositivos por niveles ISA-95, y las reglas de alerta por umbral pueden avisarte de valores que superan los límites. En producción, usa TLS en el puerto 8883 con YOUR_USERNAME, YOUR_PASSWORD y YOUR_CLIENT_ID del portal. Un broker MQTT gestionado mantiene en funcionamiento la infraestructura de presencia para que no tengas que hacerlo tú; para los fundamentos que hay detrás, consulta el tutorial de MQTT.

Empieza con ConnectMQ

Crea un broker gratuito en app.connectmq.com con el plan Serverless, sin tarjeta de crédito. Despliega los scripts del dispositivo y del monitor con tus credenciales y tendrás una presencia de flota fiable en un broker MQTT en la nube en minutos.

Preguntas frecuentes

¿Por qué mi dispositivo aparece online después de apagarlo?

Un DISCONNECT limpio descarta el Last Will. Publica tú mismo un mensaje offline retenido antes de desconectarte.

¿Por qué mi dispositivo sigue offline después de reconectarse?

Probablemente el mensaje de nacimiento se publica solo una vez al arrancar. Publícalo en el callback de conexión para que cada reconexión actualice el estado.

¿Con qué rapidez se detecta un dispositivo offline?

De inmediato si la conexión TCP se cierra. Si el dispositivo desaparece en silencio, en aproximadamente 1,5 veces el intervalo de keepalive.

¿Deben ser retenidos los mensajes de estado?

Sí. Retener los mensajes de nacimiento, will y apagado garantiza que cualquier cliente que se suscriba más tarde reciba de inmediato el estado actual.

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.