Tutoriais

Construa um Unified Namespace (UNS) com MQTT

Como construir um Unified Namespace com MQTT: hierarquia de tópicos ISA-95, estado retido, convenções de payload, o papel do Sparkplug B e scripts para testar.

Atualizado · 9 min de leitura

Construa um Unified Namespace (UNS) com MQTT

Um Unified Namespace (UNS) é um lugar único e orientado a eventos onde cada sistema de uma empresa publica seu estado atual, organizado por onde as coisas estão no negócio e não por qual software as produziu. Na prática, é um broker MQTT mais uma hierarquia de tópicos — geralmente baseada na ISA-95: empresa / planta / área / linha / célula — com mensagens retidas guardando o último valor de tudo.

Este tutorial mostra como desenhar essa hierarquia, decidir o que reter, escolher convenções de payload, encaixar o Sparkplug B e testar o namespace com alguns scripts pequenos.

Por que um UNS em vez de integrações ponto a ponto

Plantas clássicas integram sistema a sistema: o MES consulta o SCADA, o ERP importa um CSV do MES, um dashboard consulta um historiador. Cada novo consumidor significa uma nova integração, e ninguém tem um lugar único para ver o estado atual da operação.

Um UNS substitui isso por um hub:

Ponto a pontoUnified Namespace
IntegraçõesUma por par de sistemasUma por sistema (até o broker)
Adicionar um consumidorNova interface na origemAssinar um tópico
Estado atualEspalhado em bancos de dadosRetido no broker
Fluxo de dadosPolling, batchOrientado a eventos, por exceção
ResponsabilidadeCada interface separadamenteUm namespace, governado centralmente

Os produtores — gateways de CLP, MES, sistemas de qualidade, adaptadores de ERP — publicam no seu ramo da árvore. Os consumidores assinam o que precisarem. Ninguém precisa saber quem mais está conectado.

Desenhe a hierarquia ISA-95

A ISA-95 oferece um vocabulário para a estrutura física e organizacional de uma planta. Mapeá-la diretamente para níveis de tópico torna o namespace legível tanto para o pessoal de TA quanto de TI:

NívelExemploNível de tópico
EmpresaAcme Foodsacme
PlantaPlanta de Dallasdallas
ÁreaEnvasepackaging
Linha (work center)Linha 1line1
Célula (work unit)Enchedorafiller
DadoVelocidade, estado, contadoresspeed, state, good-count

Assim, a velocidade da enchedora fica em acme/dallas/packaging/line1/filler/speed. Abaixo dos níveis ISA-95, adicione um pequeno conjunto de subespaços por tipo de dado para que os consumidores diferenciem estado de eventos:

Um ramo do namespace
acme/dallas/packaging/line1/filler/state            "running"       retained
acme/dallas/packaging/line1/filler/speed            {"v":118,...}   retained
acme/dallas/packaging/line1/filler/good-count       {"v":48211,...} retained
acme/dallas/packaging/line1/filler/events/fault     {"code":"E14"}  not retained
acme/dallas/packaging/line1/mes/work-order          {"id":"WO-881"} retained
acme/dallas/packaging/line1/quality/last-check      {"result":"ok"} retained
acme/dallas/packaging/line1/filler/cmd/setpoint     (restricted)    not retained

Regras que mantêm um namespace saudável ao longo dos anos:

  • Minúsculas, sem espaços, só ASCII, com hífens dentro de um nível. Tópicos diferenciam maiúsculas e minúsculas, e misturá-las garante duplicatas.
  • Nomeie pelo lugar, não pela origem. line1/filler/speed, nunca kepware3/channel2/tag17. As origens mudam; a linha fica.
  • Mantenha fixa a profundidade dos níveis dentro de um ramo, para que acme/+/packaging/+/filler/state funcione em todas as plantas.
  • Separe os comandos sob cmd/ e proteja-os com ACLs; a maioria dos produtores nunca deve poder escrever ali.
  • Não coloque valores nos nomes de tópico (nada de .../temp/21.5); o valor vai no payload.

Os princípios gerais estão em boas práticas de design de tópicos. Antes de se comprometer com uma estrutura, teste seus padrões de assinatura no topic matcher.

Estado retido é o coração de um UNS

O namespace deve responder "o que está acontecendo agora?" para um cliente que se conectou há um segundo. É exatamente isso que as mensagens retidas fazem: o broker guarda a última mensagem retida de cada tópico e a entrega imediatamente a cada novo assinante.

  • Retenha estado: valores, status, ordem de produção atual, último resultado de qualidade, setpoints.
  • Não retenha eventos: falhas, peças rejeitadas, comandos. Um novo assinante não deve ver a falha de ontem como se tivesse acabado de acontecer.
  • Limpe tópicos obsoletos publicando um payload retido vazio quando uma máquina for desativada, ou o namespace vai se enchendo de fantasmas.
  • Use a expiração de mensagens do MQTT 5 em estados que perdem o sentido se não forem atualizados, para que valores velhos sumam em vez de mentir.

Acompanhe cada produtor de um tópico status retido e um Last Will (offline), como no tutorial de gateway Modbus. Assim os consumidores distinguem um valor estável de uma fonte morta.

Convenções de payload

Combine um único formato de payload para estado e mantenha-o. Um JSON simples e autodescritivo funciona bem:

Payload de estado
{ "v": 118, "unit": "bpm", "ts": "2026-10-17T08:41:07.214Z", "q": "good" }

v é o valor, unit evita adivinhação, ts é o timestamp de origem em UTC e q a qualidade. Metadados estáticos (descrições, faixas, unidades de engenharia) podem ficar em um tópico irmão _meta retido, para que o caminho quente continue leve.

Popule e navegue em um namespace de teste

Dois pequenos scripts com paho-mqtt 2.x permitem experimentar antes de existir qualquer produtor real. O primeiro publica um snapshot retido de uma planta minúscula com expiração de uma hora:

seed_uns.py
import json, time
import paho.mqtt.client as mqtt
from paho.mqtt.packettypes import PacketTypes
from paho.mqtt.properties import Properties

ROOT = "testmqtt/uns/acme/dallas/packaging/line1"
STATE = {
    "filler/state":      "running",
    "filler/speed":      {"v": 118, "unit": "bpm"},
    "filler/good-count": {"v": 48211, "unit": "pcs"},
    "capper/state":      "idle",
    "mes/work-order":    {"id": "WO-881", "product": "SKU-330ml"},
}

props = Properties(PacketTypes.PUBLISH)
props.MessageExpiryInterval = 3600          # segundos

c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
c.connect("broker.connectmq.com", 1883)
c.loop_start()
now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
for path, value in STATE.items():
    body = value if isinstance(value, str) else json.dumps({**value, "ts": now})
    c.publish(f"{ROOT}/{path}", body, qos=1, retain=True, properties=props).wait_for_publish()
c.loop_stop()
c.disconnect()
print("seeded", len(STATE), "topics")

O segundo se conecta como um cliente totalmente novo, coleta por alguns segundos o que o broker entrega e imprime o namespace como árvore — exatamente o que um novo consumidor veria:

browse_uns.py
import time
import paho.mqtt.client as mqtt

FILTER = "testmqtt/uns/acme/#"
seen = {}

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

def on_message(client, userdata, msg):
    seen[msg.topic] = (msg.payload.decode(errors="replace"), msg.retain)

c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
c.on_connect, c.on_message = on_connect, on_message
c.connect("broker.connectmq.com", 1883)
c.loop_start()
time.sleep(3)
c.loop_stop()

for topic in sorted(seen):
    payload, retained = seen[topic]
    depth = topic.count("/") - FILTER.count("/") + 1
    print("  " * depth + topic.rsplit("/", 1)[1], "=", payload, "(retained)" if retained else "")

Se o navegador de namespace imprimir cada tópico populado marcado como (retained), sua camada de estado funciona. Você pode fazer a mesma verificação visualmente no cliente MQTT online:

Navegue no namespace de teste pelo navegador

O endpoint compartilhado serve para testar a estrutura. Um namespace real guarda o estado ao vivo da sua operação, então rode-o em um broker que você controla.

Onde o Sparkplug B se encaixa

O Sparkplug B é uma especificação sobre MQTT para dispositivos industriais: estrutura de tópicos fixa (spBv1.0/<group>/<message type>/<edge node>/<device>), payloads Protobuf, certificados birth/death e um host primário que acompanha o estado. Ele resolve muito bem o problema do edge, mas seus tópicos são organizados por tipo de mensagem e edge node, não pela ISA-95.

AbordagemComo funcionaBom quando
UNS com MQTT simplesProdutores publicam JSON direto em tópicos ISA-95Consumidores mistos TI/TA, ferramentas simples, tópicos legíveis
Sparkplug no edge, UNS acimaEdge nodes falam Sparkplug; um host ou ponte republica em tópicos ISA-95Muitos dispositivos, necessidade de estado padronizado e payloads compactos
Só SparkplugTodos os consumidores entendem SparkplugEcossistema SCADA homogêneo

O modelo híbrido é popular por um motivo: os dispositivos ganham a gestão de estado do Sparkplug e o resto da empresa ganha um namespace legível. Os prós e contras estão em Sparkplug vs MQTT simples. Dados de servidores OPC UA podem entrar na mesma árvore por uma ponte OPC UA para MQTT.

Checklist de testes para o seu namespace

  1. Teste de assinante novo: um cliente novo assinando acme/<site>/# recebe na hora o estado atual de cada ativo (tudo retido).
  2. Teste de eventos: eventos chegam ao vivo, mas não são reenviados a um assinante novo.
  3. Teste de troca de origem: substitua um produtor (gateway simulado → gateway real); os consumidores não percebem nada.
  4. Teste de origem morta: mate um produtor; seu status muda para offline via Last Will.
  5. Teste de ACL: tente publicar no ramo de outra linha com as credenciais de um gateway — deve ser recusado.
  6. Teste de wildcards: cada filtro de dashboard bate exatamente com os tópicos pretendidos no topic matcher.

Um Unified Namespace tem menos a ver com tecnologia e mais com acordo: um broker, uma hierarquia que todos entendem, estado retido para o presente e regras claras sobre quem publica onde. Comece com uma linha, popule, navegue, quebre de propósito — e só cresça a árvore quando o primeiro ramo tiver se provado.

Perguntas frequentes

Unified Namespace é um produto?

Não. Um UNS é um padrão de arquitetura: um hub orientado a eventos, normalmente um broker MQTT, onde cada sistema publica seu estado atual em uma hierarquia de tópicos compartilhada e com o formato do negócio. Você o constrói com um broker, regras de nomes e os clientes que publicam e consomem.

Preciso de Sparkplug B para um Unified Namespace?

Não. Muitas implementações de UNS usam MQTT simples com payloads JSON e tópicos no formato ISA-95. O Sparkplug B é útil no edge pelo estado birth/death padronizado e payloads compactos; um padrão comum é Sparkplug dos dispositivos até um host, que republica no namespace ISA-95.

Toda mensagem do UNS deve ser retida?

Retenha o estado atual (valores, status, setpoints) para que qualquer consumidor novo veja o presente na hora. Não retenha eventos como alarmes, peças produzidas ou comandos; eles pertencem ao momento em que acontecem e, depois, a um historiador.