Node-RED

Mapeamento de topics MQTT no Node-RED: remapeie topics planos de dispositivos para uma hierarquia ISA-95

Aprenda a remapear topics planos e desorganizados de dispositivos para um namespace ISA-95 estruturado com uma tabela de consulta no Node-RED, normalizar os payloads no caminho e republicar tudo em um broker ConnectMQ sem criar loops.

Fluxo de mapeamento de topics MQTT no Node-RED convertendo topics planos de dispositivos em uma árvore de topics ISA-95 em um broker na nuvem
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

O mapeamento de topics MQTT no Node-RED é a forma mais rápida de transformar os topics planos e específicos de cada fabricante, que seus dispositivos já publicam, em uma hierarquia ISA-95 limpa que o resto da planta consegue entender. Neste tutorial você vai montar um pequeno fluxo no Node-RED que assina os topics brutos dos dispositivos, procura cada dispositivo em uma tabela de mapeamento, normaliza o payload e o republica em um namespace estruturado em um broker ConnectMQ.

Por que remapear topics MQTT?

A maioria das instalações brownfield cresce de forma orgânica. Um gateway de temperatura publica em dev/th-0042/temp, um medidor de energia usa meter7/kw e um gateway de CLP escreve em plc/line2/status. Cada topic fazia sentido para quem configurou aquele dispositivo, mas juntos eles não dizem nada a um consumidor sobre onde os dados estão na planta.

Um Unified Namespace resolve isso com a adoção de uma única hierarquia, normalmente seguindo os níveis da ISA-95: empresa, site, área, linha, célula ou máquina e, por fim, o ponto de dado. Quando cada mensagem fica em um caminho previsível como acme/izmir/packaging/line2/filler1/temperature, dashboards, historiadores e ferramentas de análise podem assinar com wildcards em vez de manter uma longa lista de exceções. O contexto desse padrão está no nosso guia sobre Sparkplug B e o Unified Namespace.

Muitas vezes não é possível reconfigurar os próprios dispositivos. O firmware pode estar travado, o fabricante pode ter deixado de existir ou mexer em um controlador de produção exige uma janela de mudança que você não vai conseguir neste trimestre. O Node-RED fica no meio do caminho e faz a tradução sem tocar na origem.

Projetando a estrutura de topics ISA-95 de destino

Defina a estrutura de destino antes de escrever qualquer código. Um fluxo de mapeamento só é tão bom quanto o namespace em que ele escreve. Um layout prático é este:

Nível Valor de exemplo Observações
Empresa acme Muitas vezes implícito pelo broker ou por um prefixo fixo
Site izmir Um por planta física
Área packaging Departamento ou galpão
Linha line2 Linha de produção ou unidade de processo
Máquina filler1 Célula, máquina ou ativo
Ponto de dado temperature Uma medição ou estado por topic

Algumas regras mantêm o namespace saudável ao longo do tempo:

  1. Use letras minúsculas, ASCII e nenhum espaço, para que os topics sejam fáceis de digitar em assinaturas e filtros.
  2. Publique um ponto de dado por topic, ou um objeto JSON bem definido por máquina, e mantenha a escolha consistente.
  3. Nunca coloque timestamps, números de sequência ou valores no próprio topic.
  4. Controle a profundidade. No servidor MQTT público em broker.connectmq.com, um topic pode ter no máximo 8 níveis, por isso este tutorial usa um prefixo de demonstração de três níveis mais cinco níveis ISA-95.

Na demonstração vamos usar connectmq/demo/ab12cd como prefixo (substitua ab12cd por uma string aleatória sua), a entrada bruta em connectmq/demo/ab12cd/raw/# e a saída estruturada como connectmq/demo/ab12cd/<site>/<area>/<line>/<machine>/<point>. Lembre-se de que a área pública pode ser lida por qualquer pessoa, então envie apenas dados de teste para lá.

Como funciona o mapeamento de topics MQTT no Node-RED

O fluxo tem quatro partes:

  1. Um nó mqtt in assina os topics brutos com um wildcard.
  2. Um nó function divide o topic recebido, encontra o dispositivo em uma tabela de consulta, monta o novo topic e normaliza o payload.
  3. Um nó mqtt out publica o resultado. Se o campo de topic dele estiver vazio, ele usa msg.topic, que é exatamente o que queremos.
  4. Uma segunda saída do nó function envia as mensagens não mapeadas para um nó debug, para que você identifique dispositivos novos ou mal configurados.

Se você está começando com os nós MQTT, comece pelo tutorial Node-RED MQTT e volte aqui depois.

A tabela de mapeamento

Mantenha o mapeamento em um único lugar. Para alguns poucos dispositivos, um objeto JavaScript dentro do nó function é suficiente. Para dezenas, carregue-o de um arquivo ou de uma retained message MQTT no contexto global, para poder atualizá-lo sem fazer um novo deploy.

O nó function

Cole isto em um nó function e configure-o com 2 saídas:

// Os topics brutos têm o formato: connectmq/demo/ab12cd/raw/<deviceId>/<rawPoint>
const PREFIX = "connectmq/demo/ab12cd";

// deviceId -> localização ISA-95
const devices = {
    "th-0042":  { site: "izmir", area: "packaging", line: "line2", machine: "filler1" },
    "meter7":   { site: "izmir", area: "utilities", line: "main",  machine: "meter7" },
    "plc-l2":   { site: "izmir", area: "packaging", line: "line2", machine: "plc" }
};

// nome do ponto bruto -> nome normalizado do ponto e unidade
const points = {
    "temp":   { name: "temperature", unit: "C" },
    "hum":    { name: "humidity",    unit: "%" },
    "kw":     { name: "power",       unit: "kW" },
    "status": { name: "state",       unit: null }
};

const parts = msg.topic.split("/");
// parts: [connectmq, demo, ab12cd, raw, deviceId, rawPoint]
const deviceId = parts[4];
const rawPoint = parts[5];

const loc = devices[deviceId];
const point = points[rawPoint];

if (!loc || !point || parts.length !== 6) {
    node.warn("Unmapped topic: " + msg.topic);
    return [null, msg];
}

// Aceita números simples, strings ou objetos JSON com um campo "value"
let value = msg.payload;
if (typeof value === "object" && value !== null && "value" in value) {
    value = value.value;
}
const numeric = Number(value);

const out = {
    topic: [PREFIX, loc.site, loc.area, loc.line, loc.machine, point.name].join("/"),
    payload: {
        value: isNaN(numeric) ? value : numeric,
        unit: point.unit,
        ts: new Date().toISOString(),
        source: msg.topic
    },
    retain: true,
    qos: 1
};

return [out, null];

Definir retain: true faz com que qualquer novo assinante receba imediatamente o último valor de cada ponto de dado, que é como a maioria dos consumidores de um Unified Namespace espera que a árvore se comporte. O campo source mantém um rastro até o topic original, o que ajuda muito na depuração.

Fluxo importável

Você pode importar o fluxo completo em Menu → Import. Depois da importação, cole o código da função acima no nó function e troque o client ID e o prefixo pelos seus próprios valores.

[
  {"id":"b1","type":"mqtt-broker","name":"public broker","broker":"broker.connectmq.com","port":"1883","clientid":"nr-mapper-ab12cd","autoConnect":true,"usetls":false,"protocolVersion":"5","keepalive":"60","cleansession":true},
  {"id":"in1","type":"mqtt in","name":"raw devices","topic":"connectmq/demo/ab12cd/raw/#","qos":"1","datatype":"auto-detect","broker":"b1","nl":false,"rap":true,"rh":0,"inputs":0,"x":140,"y":100,"wires":[["fn1"]]},
  {"id":"fn1","type":"function","name":"map to ISA-95","func":"return [msg, null];","outputs":2,"x":340,"y":100,"wires":[["out1"],["dbg1"]]},
  {"id":"out1","type":"mqtt out","name":"publish ISA-95","topic":"","qos":"","retain":"","broker":"b1","x":560,"y":80,"wires":[]},
  {"id":"dbg1","type":"debug","name":"unmapped","active":true,"complete":"true","x":550,"y":140,"wires":[]}
]

O nó mqtt out deixa qos e retain vazios para que sejam usados os valores definidos no nó function.

Testando o mapeamento com mosquitto

Abra um terminal e acompanhe o lado estruturado da árvore:

mosquitto_sub -h broker.connectmq.com -p 1883 -i sub-ab12cd -v -t "connectmq/demo/ab12cd/izmir/#"

Em um segundo terminal, simule dispositivos no lado bruto:

mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-ab12cd -t "connectmq/demo/ab12cd/raw/th-0042/temp" -m "23.7"
mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-ab12cd -t "connectmq/demo/ab12cd/raw/meter7/kw" -m '{"value": 4.2}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-ab12cd -t "connectmq/demo/ab12cd/raw/unknown-1/temp" -m "19"

As duas primeiras mensagens devem aparecer em izmir/packaging/line2/filler1/temperature e izmir/utilities/main/meter7/power. A terceira deve aparecer na barra lateral de debug do Node-RED como não mapeada.

Evitando loops e outras armadilhas

O erro mais comum em fluxos de remapeamento é assinar um wildcard que também corresponde à saída. Se o nó mqtt in escuta connectmq/demo/ab12cd/# e o fluxo republica sob o mesmo prefixo, cada mensagem republicada é recebida de novo, remapeada de novo e acaba inundando o broker. Mantenha os dados brutos e os estruturados em ramos separados da árvore e assine apenas o ramo bruto.

Com MQTT 5 você também pode ativar a opção no local no nó mqtt in, para que o cliente nunca receba as mensagens que ele mesmo publicou. Isso é uma rede de segurança, não um substituto para um bom projeto de topics, porque uma segunda instância do Node-RED ainda veria as mensagens.

Outras armadilhas que vale verificar:

Sintoma Causa provável Correção
Nada é republicado A função retorna null nas duas saídas Verifique a saída de debug e a checagem de parts.length
O topic de saída contém undefined Dispositivo na tabela, mas com um nível faltando Valide se cada entrada da tabela tem os quatro níveis
Valores antigos reaparecem após uma renomeação Retained messages no topic antigo Publique um payload retained vazio no topic antigo para limpá-lo
Strings em vez de números mais adiante O payload bruto tinha unidades, como 23.7C Remova as unidades no nó function antes de Number()
Desconexões no broker público Mais de 10 mensagens por segundo por cliente Limite a taxa com um nó delay ou use o seu próprio broker

Levando o fluxo para produção

O broker público serve para experimentos, mas dados de produção devem ficar em um broker privado com credenciais e TLS. Crie um broker na ConnectMQ, copie do portal o nome de usuário, a senha e o client ID gerados e altere o nó de configuração do broker para:

  • Servidor broker.connectmq.com (ou o host do seu broker indicado no portal), porta 8883, Use TLS ativado
  • Client ID YOUR_CLIENT_ID, usuário YOUR_USERNAME, senha YOUR_PASSWORD
  • Protocolo MQTT V5

O certificado do broker é emitido pela Let's Encrypt, então o repositório de confiança padrão do runtime Node.js do Node-RED o valida sem precisar enviar um arquivo de CA. Nosso guia de Node-RED MQTT sobre TLS mostra as configurações de TLS passo a passo.

Assim que os topics estruturados estiverem fluindo, o portal da ConnectMQ detecta o namespace no formato ISA-95 e mostra seus dispositivos agrupados por site, área, linha e máquina, e o explorador de topics ao vivo permite conferir a árvore sem abrir um cliente separado. Se algumas das suas fontes já falam Sparkplug B, talvez você nem precise do Node-RED para elas: o broker consegue decodificar Sparkplug B e republicá-lo em topics ISA-95, de modo que o Node-RED só precisa tratar os topics simples e planos. Para fontes de CLP, o guia de Node-RED com Modbus mostra como ler registradores e alimentá-los no mesmo fluxo de mapeamento.

Como um broker gerenciado cuida da disponibilidade, dos certificados e dos limites de conexão, o fluxo de mapeamento passa a ser a única peça móvel que você mantém. Esse é um dos principais motivos pelos quais equipes, de uma planta em Joinville a uma mineradora, trocam um servidor Mosquitto próprio por um broker MQTT gerenciado quando o namespace cresce.

Escalando a tabela de mapeamento

Quando o número de dispositivos passar do que você quer editar dentro de um nó function, tire a tabela de lá:

  • Topic de configuração retained: publique o mapeamento como uma mensagem JSON retained, por exemplo em config/topic-map, assine-o em um fluxo separado e armazene-o com global.set("topicMap", msg.payload). A função de mapeamento o lê com global.get("topicMap"). As atualizações entram em vigor sem novo deploy.
  • Arquivo ou banco de dados: leia um arquivo CSV ou JSON na inicialização com um nó file in, ou consulte uma pequena tabela, o que combina com equipes que já mantêm um cadastro de ativos.
  • Regras por padrão: se os IDs dos dispositivos codificam a localização, como iz-pk-l2-filler1-temp, uma regra baseada em split pode substituir a maior parte da tabela, com uma pequena tabela para as exceções.

Seja qual for a escolha, mantenha a saída de não mapeados ligada a algo visível. Dispositivos novos aparecem em silêncio, e um aviso de não mapeado muitas vezes é o único sinal de que um gateway foi substituído ou renomeado.

Rode o seu mapeamento de topics do Node-RED na ConnectMQ

Um namespace limpo precisa de um broker confiável por baixo. Cadastre-se em app.connectmq.com no plano Serverless gratuito, sem cartão de crédito, crie um broker e aponte o seu fluxo de mapeamento para ele com TLS na porta 8883. Você ganha MQTT 5, retained messages com o último valor de cada ponto de dado e uma visão de dispositivos que entende seus níveis ISA-95 desde a primeira mensagem. Para uma visão mais ampla das opções de hospedagem, leia nosso guia de broker MQTT na nuvem.

Perguntas frequentes

O Node-RED consegue reescrever topics sem um nó function?

Sim. Um nó change pode definir msg.topic com uma expressão JSONata, e isso basta para trocas simples de prefixo. Um nó function fica mais fácil de ler quando você precisa de uma tabela de consulta, validação e normalização de payload na mesma etapa.

Devo publicar um topic por valor ou um objeto JSON por máquina?

As duas opções funcionam em um Unified Namespace. Um valor por topic é mais simples para dashboards e regras de alerta, enquanto um objeto por máquina reduz o número de mensagens; escolha uma convenção e aplique-a em todo lugar.

Como impeço o fluxo de republicar as próprias mensagens?

Assine apenas um ramo bruto no qual o fluxo nunca escreve e ative a opção no local do MQTT 5 no nó mqtt in como proteção extra.

A ConnectMQ remapeia topics sozinha?

Ela republica mensagens Sparkplug B em topics ISA-95, mas não executa regras arbitrárias de reescrita de topics. Para topics planos ou específicos de fabricante, faça o mapeamento no Node-RED ou em outro cliente MQTT, como mostrado aqui.

Artigos relacionados

Node-RED Dashboard 2.0 com MQTT: gauges, gráficos e controles

Um tutorial prático do Node-RED Dashboard 2.0: assine telemetria MQTT ao vivo, exiba-a em gauges e em um gráfico de linha e controle um dispositivo com um switch que publica comandos e reflete o estado real.

9 min de leitura

Cliente OPC UA no Node-RED para MQTT com topics ISA-95

Use o node-red-contrib-opcua para assinar variáveis OPC UA em um CLP ou servidor de simulação, mapear node IDs para topics MQTT no estilo ISA-95 e publicar os valores com qualidade e timestamps.

10 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.