Saber quais dispositivos estão conectados agora é a primeira coisa que todo operador pede, da sala de controle de uma empresa de saneamento à central de uma fazenda solar, e o status online de dispositivos MQTT é surpreendentemente fácil de implementar errado. O padrão confiável combina três blocos: uma birth message retida, um Last Will retido e uma mensagem offline explícita no desligamento. Neste guia você vai construir esse padrão passo a passo, adicionar um monitor de frota em Python e aprender a evitar os casos extremos que fazem os dashboards mentirem.
Os blocos de construção
Cada peça resolve uma parte do problema:
| Bloco | Quem publica | Quando | Cobre |
|---|---|---|---|
Birth message (online, retida) |
Dispositivo | Logo após cada conexão bem-sucedida | Dispositivo ativo, inclusive após reconexões |
Last Will (offline, retido) |
Broker, em nome do dispositivo | Conexão perdida sem DISCONNECT | Travamentos, falta de energia, falha de rede |
Mensagem de desligamento (offline, retida) |
Dispositivo | Antes de uma desconexão limpa | Reinícios e desligamentos planejados |
| Flag retain nos três | Todos | Sempre | Dashboards abertos depois ainda veem o estado atual |
Deixe qualquer um deles de fora e você terá um ponto cego. Sem a birth message, um dispositivo que se recupera continua "offline". Sem o will, travamentos passam despercebidos. Sem a mensagem de desligamento, um desligamento planejado deixa o dispositivo "online" para sempre, porque um DISCONNECT limpo diz ao broker para descartar o will. Se o mecanismo de will é novidade para você, leia antes MQTT Last Will and Testament.
Desenhando o topic de status
Dê a cada dispositivo exatamente um topic de status, em um lugar previsível:
connectmq/demo/<prefix>/<site>/<device-id>/status
Um wildcard como connectmq/demo/<prefix>/+/+/status retorna então o status de toda a frota em uma única assinatura. Como todas as mensagens de status são retidas, um monitor que acaba de iniciar recebe imediatamente o último estado conhecido de cada dispositivo. O funcionamento da retenção é explicado em retained messages no MQTT.
Para o payload, uma string simples online ou offline basta para muitas ferramentas. O JSON acrescenta contexto que ajuda na operação:
{"state":"online","ts":1760600000,"fw":"2.4.1","ip":"10.0.4.17","reason":"boot"}
Para o will, o dispositivo não tem como saber o timestamp de uma falha futura, então mantenha-o simples: {"state":"offline","reason":"lwt"}. O monitor registra quando o recebeu.
Passo 1: o dispositivo
Este script com paho-mqtt 2.x se comporta como um dispositivo bem projetado. Ele define um will retido antes de conectar, publica uma birth message retida dentro de on_connect para que ela se repita a cada reconexão e publica uma mensagem offline retida no desligamento controlado. Ele se conecta ao servidor MQTT público em broker.connectmq.com, que serve para testes e não para dados de produção:
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) # a telemetria real seria publicada aqui
Execute-o como python device.py pump-07 e, em outro terminal, como python device.py fan-02.
Passo 2: o monitor da frota
O monitor assina todos os topics de status e mantém uma tabela de dispositivos. Como os status são retidos, ele conhece o estado completo da frota segundos depois de iniciar:
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 retido vazio = dispositivo removido
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()
Passo 3: teste todos os modos de falha
Rode o monitor e os dois dispositivos, depois percorra este checklist e confirme que o monitor reage corretamente em cada caso:
- Reinício do monitor. Pare e inicie o monitor. Os dois dispositivos aparecem como
onlineimediatamente, marcados comoretained. - Desligamento controlado. Pressione Ctrl+C no
fan-02. O monitor mostraofflinecom o motivoshutdownna hora. - Travamento. Mate o
pump-07comkill -9. O sistema operacional fecha o socket, o broker publica o will e o monitor mostraoffline. - Perda silenciosa de rede. Desconecte a rede do dispositivo sem matar o processo. Depois de cerca de 1,5 vez o keepalive de 20 segundos, o will é disparado.
- Recuperação. Restaure a rede. O paho se reconecta, o
on_connecté executado e a birth message volta o dispositivo paraonline. - Descomissionamento. Publique uma mensagem retida vazia no topic de status. O monitor remove o dispositivo.
Você também pode acompanhar os mesmos topics ao vivo no navegador; o client integrado ao navegador e o explorador de topics do portal ConnectMQ mostram as mudanças de status em tempo real quando você aponta os dispositivos para o seu próprio broker.
Casos extremos que fazem os dashboards mentirem
Client IDs duplicados. Dois dispositivos com o mesmo ID derrubam um ao outro. Cada tomada de conexão pode disparar um will e uma birth, então o status oscila o tempo todo. IDs únicos são inegociáveis; veja o guia de client ID MQTT.
Birth publicada só uma vez no boot. Se você publica online fora do callback de conexão, um dispositivo que se reconecta após uma instabilidade de rede fica offline para sempre. Publique-a sempre, a cada conexão.
Will não retido. Um dashboard aberto depois da falha nunca fica sabendo dela.
Reconexões rápidas causando oscilação. Um dispositivo que se reconecta em segundos ainda gera um par offline/online. No MQTT 5, um will delay interval suprime o will se a sessão for retomada a tempo.
Keepalive longo. Com um keepalive de 300 segundos, um dispositivo morto parece online por mais de sete minutos. Escolha um keepalive compatível com a rapidez com que você precisa saber.
"Conectado" não é "saudável". Um dispositivo pode manter sua conexão MQTT enquanto o sensor falhou. Publique um campo separado de saúde ou heartbeat se você precisa de vivacidade no nível da aplicação.
Presença vs heartbeat: qual você precisa?
Presença baseada em conexão e heartbeats de aplicação respondem a perguntas diferentes, e muitas frotas precisam dos dois.
| Sinal | Responde | Tempo de detecção | Custo |
|---|---|---|---|
| Birth + LWT no topic de status | O dispositivo está conectado ao broker? | Imediato até 1,5 x keepalive | Quase nada; apenas pings de keepalive |
| Mensagem periódica de heartbeat | O loop da aplicação ainda está rodando? | Um ou dois intervalos de heartbeat | Uma pequena publicação por intervalo |
| Verificação de atualidade da telemetria | O sensor ainda está produzindo dados? | Depende da taxa de publicação esperada | Nenhum no dispositivo; lógica no consumidor |
Um dispositivo cujo loop principal travou ainda pode responder aos pings de keepalive se o client MQTT roda em sua própria thread, então o status de conexão continua online. Um heartbeat publicado a partir do loop principal, ou um consumidor que sinaliza dispositivos cuja telemetria ficou desatualizada, pega esse caso. Mantenha os heartbeats sem retain e com frequência moderada; uma vez por minuto é suficiente para a maior parte do monitoramento.
Usando os topics de status em outras ferramentas
Como o padrão é feito apenas de mensagens MQTT retidas, qualquer ferramenta compatível com MQTT pode consumi-lo sem integração especial. No Node-RED, um nó mqtt in assinando o wildcard de status alimenta um dashboard ou um fluxo de notificação. A integração MQTT do Home Assistant pode mapear um topic de status para uma configuração de disponibilidade com os payloads online e offline. O input de consumidor MQTT do Telegraf pode gravar as mudanças de estado em um banco de séries temporais para relatórios de disponibilidade. Cada um deles se conecta ao broker como um client MQTT comum, então o único requisito é combinar os nomes dos topics e os valores dos payloads. Strings simples online e offline são a opção mais portável se você planeja usar ferramentas prontas; o JSON dá um contexto mais rico para o seu próprio código.
Status do lado do broker no ConnectMQ
O padrão acima é portável para qualquer broker. Além disso, o ConnectMQ publica o estado da conexão em $SYS/broker/connection/{clientId}/state, que um monitor pode assinar como uma fonte independente, que não depende de o firmware do dispositivo configurar seu will corretamente. A visualização de dispositivos do portal organiza seus dispositivos pelos níveis ISA-95, e regras de alerta por limite podem notificar você sobre valores que ultrapassam limites. Em produção, use TLS na porta 8883 com YOUR_USERNAME, YOUR_PASSWORD e YOUR_CLIENT_ID obtidos no portal. Um broker MQTT gerenciado mantém a infraestrutura de presença funcionando para que você não precise fazê-lo; para os fundamentos por trás disso, veja o tutorial de MQTT.
Comece a usar o ConnectMQ
Crie um broker gratuito em app.connectmq.com no plano Serverless, sem cartão de crédito. Implante os scripts do dispositivo e do monitor com suas credenciais e você terá presença confiável da frota em um broker MQTT na nuvem em minutos.
Perguntas frequentes
Por que meu dispositivo aparece online depois de ter sido desligado?
Um DISCONNECT limpo descarta o Last Will. Publique você mesmo uma mensagem offline retida antes de desconectar.
Por que meu dispositivo continua offline depois de se reconectar?
Provavelmente a birth message é publicada só uma vez na inicialização. Publique-a no callback de conexão para que cada reconexão atualize o status.
Com que rapidez um dispositivo offline é detectado?
Imediatamente, se a conexão TCP for fechada. Se o dispositivo desaparecer silenciosamente, em cerca de 1,5 vez o intervalo de keepalive.
As mensagens de status devem ser retidas?
Sim. Reter as mensagens de birth, will e desligamento garante que qualquer client que assine depois receba o estado atual na hora.



