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:
- Reinicio del monitor. Detén y vuelve a arrancar el monitor. Ambos dispositivos aparecen de inmediato como
online, marcados comoretained. - Apagado ordenado. Pulsa Ctrl+C en
fan-02. El monitor muestraofflinecon el motivoshutdownal instante. - Caída. Mata
pump-07conkill -9. El sistema operativo cierra el socket, el broker publica el will y el monitor muestraoffline. - 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.
- Recuperación. Restablece la red. paho se reconecta, se ejecuta
on_connecty el mensaje de nacimiento devuelve el dispositivo aonline. - 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.



