Tutoriais

Design de topics para casa inteligente: uma estrutura MQTT prática

Uma estrutura de topics MQTT prática para casas inteligentes: cômodos e dispositivos, topics de estado e de comando, retained, disponibilidade, Home Assistant e ACLs.

Atualizado · 9 min de leitura

Design de topics para casa inteligente: uma estrutura MQTT prática

Uma boa estrutura de topics MQTT para casa inteligente é previsível e sem surpresas: um ramo por cômodo, um por dispositivo e um conjunto fixo de topics finais para estado, comandos e disponibilidade. Este artigo propõe uma estrutura concreta que você pode copiar, explica por que comandos e estado ficam em topics separados, quais mensagens reter e como a estrutura se encaixa no discovery do Home Assistant e nas ACLs do broker.

A estrutura
home/<room>/<device>/state          current state, JSON, retained
home/<room>/<device>/set            commands to the device, NOT retained
home/<room>/<device>/availability   "online" / "offline", retained, Last Will
home/<room>/<device>/event          one-off events (button press), NOT retained

Regras de nomenclatura

Os nomes dos topics fazem parte da API da sua casa. Mudá-los depois significa mexer em todas as automações, então defina as regras uma vez:

  • ASCII minúsculo, palavras unidas por -: living-room, ceiling-light. Sem espaços, sem acentos, sem emoji.
  • Sem barra inicial. /home/kitchen cria um primeiro nível vazio e não casa com home/#.
  • Nunca comece com $: esse prefixo é reservado para topics do broker como $SYS.
  • Substantivos para lugares e coisas, palavras fixas para as folhas: state, set, availability, event. Não misture status, cmd e command entre dispositivos.
  • Mantenha a profundidade constante. Todo dispositivo fica exatamente em home/<room>/<device>, então home/+/+/state sempre significa "o estado de todos os dispositivos".

Orientações mais gerais estão em boas práticas de design de topics MQTT.

Por cômodo ou por ID?

EstiloExemploBom paraContrapartida
Por cômodohome/kitchen/ceiling-light/stateCasas típicas, automações escritas à mãoMover um dispositivo muda seus topics
Por IDhome/device/shelly-a8032a/stateInstalações grandes ou que mudam com frequênciaO cômodo precisa ficar em outro lugar (metadados, áreas do HA)
Híbridohome/kitchen/shelly-a8032a/stateNomes únicos com agrupamento legívelAinda muda quando o dispositivo é movido

Na maioria das casas, a estrutura por cômodo vence pela legibilidade. Se você tem dispositivos que mudam de lugar (um aquecedor portátil, um sensor em um carrinho), dê a eles um ID estável e coloque-os em um ramo neutro como home/mobile/.

Comandos vs. estado

A regra mais importante: o dispositivo publica o estado, todos os outros publicam comandos, e eles nunca compartilham um topic.

Um acionamento, de ponta a ponta
app      ──PUBLISH home/kitchen/ceiling-light/set   {"state":"ON"}            (not retained)
light    ──applies the command, then
light    ──PUBLISH home/kitchen/ceiling-light/state {"state":"ON","brightness":255}  (retained)
app, HA  ◀─ receive the new state and update the UI

Por que separá-los?

  • O topic de estado sempre reflete a realidade. Se o comando falhar (relé travado, dispositivo offline), o topic de estado continua mostrando a verdade e a interface não mente.
  • Sem loops de eco. Um dispositivo inscrito no próprio topic de estado receberia as próprias atualizações. Com set e state separados, cada lado assina apenas o que precisa.
  • ACLs seguras. Um usuário de dashboard pode ter permissão para escrever em .../set e ler .../state sem conseguir falsificar o estado do dispositivo.

O dispositivo deve publicar o novo estado depois de aplicar o comando, mesmo que nada tenha mudado. Essa confirmação é o que interfaces e automações aguardam. Para padrões de requisição/resposta com IDs de correlação, veja comando e controle via MQTT.

O que reter

TopicReter?Por quê
.../stateSimUm dashboard aberto depois mostra o estado atual na hora
.../availabilitySimNovos assinantes sabem quais dispositivos estão offline
.../setNãoUm comando retido é reproduzido a cada reconexão: acionamentos fantasmas após queda de energia
.../eventNão"Botão pressionado" só faz sentido no momento em que acontece
homeassistant/.../configSimAs configurações de discovery precisam sobreviver a reinícios do Home Assistant

Se um comando foi retido por engano, limpe-o publicando uma mensagem retida vazia no mesmo topic. Detalhes em mensagens retidas.

Disponibilidade com Last Will

Cada dispositivo define um Last Will no seu topic availability ao conectar: payload offline, retido, QoS 1. Logo após conectar, ele publica online, retido. Se o dispositivo perder energia ou Wi-Fi, o broker publica offline quando o keep alive expira, cerca de 1,5 × o intervalo de keep alive. Veja MQTT Last Will.

Um dispositivo em 40 linhas

Este script Python (paho-mqtt 2.x) se comporta como uma lâmpada dimerizável que segue a estrutura. Os exemplos usam o prefixo testmqtt/ para poderem rodar com segurança em um endpoint de testes compartilhado; na sua casa, você o remove.

fake_light.py
import json, os
import paho.mqtt.client as mqtt

BASE = "testmqtt/home/kitchen/ceiling-light"
state = {"state": "OFF", "brightness": 0}

def publish_state(client):
    client.publish(f"{BASE}/state", json.dumps(state), qos=1, retain=True)

def on_connect(client, userdata, flags, reason_code, properties):
    client.publish(f"{BASE}/availability", "online", qos=1, retain=True)
    client.subscribe(f"{BASE}/set", qos=1)
    publish_state(client)

def on_message(client, userdata, msg):
    try:
        cmd = json.loads(msg.payload)
    except ValueError:
        cmd = {"state": msg.payload.decode(errors="replace").strip().upper()}
    if not isinstance(cmd, dict):
        cmd = {}
    if cmd.get("state") in ("ON", "OFF"):
        state["state"] = cmd["state"]
    if isinstance(cmd.get("brightness"), int):
        state["brightness"] = max(0, min(255, cmd["brightness"]))
    publish_state(client)  # always confirm with the real state

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="kitchen-ceiling-light")
if os.getenv("MQTT_USER"):
    client.username_pw_set(os.environ["MQTT_USER"], os.getenv("MQTT_PASS"))
client.will_set(f"{BASE}/availability", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_forever()

Publicar o estado em on_connect é importante: após cada reconexão o estado retido é atualizado e as assinaturas são recriadas mesmo que o broker não tenha mantido sessão.

Teste a estrutura

Acompanhe o estado e a disponibilidade de todos os dispositivos em um terminal:

Terminal 1 — observar
mosquitto_sub -h broker.connectmq.com -p 1883 -v \
  -t 'testmqtt/home/+/+/state' -t 'testmqtt/home/+/+/availability'

Envie comandos de outro:

Terminal 2 — comandos
mosquitto_pub -h broker.connectmq.com -p 1883 -q 1 \
  -t 'testmqtt/home/kitchen/ceiling-light/set' -m '{"state":"ON","brightness":180}'

mosquitto_pub -h broker.connectmq.com -p 1883 -q 1 \
  -t 'testmqtt/home/kitchen/ceiling-light/set' -m 'off'

Em seguida, confira os comportamentos que a estrutura promete:

  1. Cada comando gera exatamente uma mensagem de estado.
  2. Reinicie o terminal 1: o estado atual e online aparecem imediatamente (retidos).
  3. Encerre o script com kill -9: após o timeout do keep alive, aparece offline.
  4. Reinicie o script: nenhum comando é reproduzido, porque set nunca foi retido.

Você pode fazer o mesmo no navegador com o cliente MQTT online e conferir qualquer padrão de assinatura contra seus topics com o topic matcher.

Assinaturas úteis

NecessidadeAssinatura
Tudo da casahome/#
Tudo da cozinhahome/kitchen/#
Estado de todos os dispositivoshome/+/+/state
Quais dispositivos estão offlinehome/+/+/availability
Todos os toques em botõeshome/+/+/event

Como a profundidade é constante, curingas de um nível + bastam e você raramente precisa de # fora da depuração. Veja curingas MQTT em profundidade.

Discovery do Home Assistant

O Home Assistant pode criar entidades automaticamente a partir de mensagens de configuração retidas em homeassistant/<component>/<object_id>/config. A estrutura se encaixa diretamente nos campos de discovery:

homeassistant/switch/kitchen_ceiling_light/config (retido)
{
  "name": "Kitchen ceiling light",
  "unique_id": "kitchen_ceiling_light",
  "state_topic": "home/kitchen/ceiling-light/state",
  "value_template": "{{ value_json.state }}",
  "command_topic": "home/kitchen/ceiling-light/set",
  "payload_on": "ON",
  "payload_off": "OFF",
  "availability_topic": "home/kitchen/ceiling-light/availability"
}

payload_available e payload_not_available têm como padrão online e offline, exatamente o que o Last Will acima publica. Para a configuração completa da integração, veja Home Assistant e MQTT.

ACLs que seguem a estrutura

Uma árvore consistente deixa as permissões curtas:

ClienteEscritaLeitura
Cada dispositivoseus próprios state, availability, eventseu próprio set
Home Assistant / hub de automaçãohome/+/+/set, homeassistant/#home/#
Tablet de parede com dashboardhome/+/+/sethome/+/+/state, home/+/+/availability
Usuário convidado ou de monitoramentonadahome/+/+/availability

Uma credencial por dispositivo significa que uma tomada inteligente comprometida só pode mentir sobre si mesma, e não destrancar a fechadura da porta de entrada.

Adotar essa estrutura não custa nada e compensa a cada novo dispositivo: profundidade constante, state e set separados, estado e disponibilidade retidos, comandos nunca retidos. Documente as regras, teste cada novo dispositivo com as quatro verificações acima e suas automações continuarão funcionando conforme a casa cresce.

Perguntas frequentes

Os topics de comando MQTT devem ser retidos?

Não. Um comando retido é reenviado ao dispositivo toda vez que ele reconecta, então uma lâmpada pode acender sozinha depois de uma queda de energia. Retenha o estado e a disponibilidade; envie comandos sem a flag retain.

O cômodo deve fazer parte do topic?

Em uma casa típica, sim: home/kitchen/ceiling-light é fácil de ler e permite assinar um cômodo inteiro com home/kitchen/#. A desvantagem é que mover um dispositivo muda seus topics. Instalações maiores costumam usar IDs de dispositivo estáveis e guardar o cômodo como metadado.

Um topic de estado JSON ou um topic por propriedade?

Uma única mensagem de estado JSON por dispositivo é mais fácil de manter consistente e é o que os templates JSON do Home Assistant esperam. Topics por propriedade são mais simples para sensores muito pequenos e algumas ferramentas. Escolha um estilo e use-o em todos os dispositivos.