Node-RED

Mapeo de topics MQTT en Node-RED: convierte topics planos de dispositivos en una jerarquía ISA-95

Aprende a remapear topics planos y desordenados de dispositivos en un namespace ISA-95 estructurado con una tabla de búsqueda en Node-RED, normalizar los payloads por el camino y volver a publicarlo todo en un broker de ConnectMQ sin crear bucles.

Flujo de mapeo de topics MQTT en Node-RED que convierte topics planos de dispositivos en un árbol de topics ISA-95 en un broker en la nube
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

El mapeo de topics MQTT en Node-RED es la forma más rápida de convertir los topics planos y específicos de cada fabricante que tus dispositivos ya publican en una jerarquía ISA-95 limpia que el resto de la planta pueda entender. En este tutorial construirás un pequeño flujo de Node-RED que se suscribe a los topics en bruto de los dispositivos, busca cada dispositivo en una tabla de mapeo, normaliza el payload y lo vuelve a publicar en un namespace estructurado en un broker de ConnectMQ.

¿Por qué remapear topics MQTT?

La mayoría de las instalaciones existentes crecen de forma orgánica. Un gateway de temperatura publica en dev/th-0042/temp, un medidor de energía usa meter7/kw y un gateway de PLC escribe en plc/line2/status. Cada topic tenía sentido para quien configuró ese dispositivo, pero juntos no le dicen nada a un consumidor sobre dónde están los datos dentro de la planta.

Un Unified Namespace lo resuelve acordando una única jerarquía, que normalmente sigue los niveles de ISA-95: empresa, sitio, área, línea, celda o máquina y, por último, el punto de datos. Cuando cada mensaje está en una ruta predecible como acme/izmir/packaging/line2/filler1/temperature, los dashboards, los historiadores y las herramientas de analítica pueden suscribirse con comodines en lugar de mantener una larga lista de excepciones. El contexto de este patrón se explica en nuestra guía sobre Sparkplug B y el Unified Namespace.

A menudo no puedes reconfigurar los propios dispositivos. El firmware puede estar bloqueado, el fabricante puede haber desaparecido o tocar un controlador en producción requiere una ventana de cambios que no tendrás este trimestre. Node-RED se sitúa en medio y hace la traducción sin tocar el origen.

Diseñar la estructura de topics ISA-95 de destino

Decide la estructura de destino antes de escribir código. Un flujo de mapeo solo es tan bueno como el namespace en el que escribe. Una estructura práctica tiene este aspecto:

Nivel Valor de ejemplo Notas
Empresa acme A menudo implícita en el broker o en un prefijo fijo
Sitio izmir Uno por planta física
Área packaging Departamento o nave
Línea line2 Línea de producción o unidad de proceso
Máquina filler1 Celda, máquina o activo
Punto de datos temperature Una medida o un estado por topic

Unas pocas reglas mantienen sano el namespace con el tiempo:

  1. Usa minúsculas, ASCII y sin espacios, para que los topics sean fáciles de escribir en suscripciones y filtros.
  2. Publica un punto de datos por topic, o un objeto JSON bien definido por máquina, y mantén la elección de forma coherente.
  3. Nunca pongas marcas de tiempo, números de secuencia ni valores en el propio topic.
  4. Controla la profundidad. En el servidor MQTT público gratuito de broker.connectmq.com un topic puede tener como máximo 8 niveles, así que este tutorial usa un prefijo de demo de tres niveles más cinco niveles ISA-95.

Para la demo usaremos connectmq/demo/ab12cd como prefijo (sustituye ab12cd por tu propia cadena aleatoria), la entrada en bruto bajo connectmq/demo/ab12cd/raw/# y la salida estructurada como connectmq/demo/ab12cd/<site>/<area>/<line>/<machine>/<point>. Recuerda que cualquiera puede leer el área pública, así que envía allí solo datos de prueba.

Cómo funciona el mapeo de topics MQTT en Node-RED

El flujo tiene cuatro partes:

  1. Un nodo mqtt in se suscribe a los topics en bruto con un comodín.
  2. Un nodo function divide el topic entrante, busca el dispositivo en una tabla, construye el nuevo topic y normaliza el payload.
  3. Un nodo mqtt out publica el resultado. Si su propio campo topic está vacío, usa msg.topic, que es exactamente lo que queremos.
  4. Una segunda salida del nodo function envía los mensajes sin mapear a un nodo debug, para que puedas detectar dispositivos nuevos o mal configurados.

Si los nodos MQTT son nuevos para ti, empieza por el tutorial de Node-RED con MQTT y vuelve aquí después.

La tabla de mapeo

Mantén el mapeo en un solo lugar. Para un puñado de dispositivos basta con un objeto JavaScript dentro del nodo function. Para decenas, cárgalo desde un archivo o desde un mensaje MQTT retenido en el contexto global, para poder actualizarlo sin hacer redeploy.

El nodo function

Pega esto en un nodo function y configúralo con 2 salidas:

// Los topics en bruto tienen este aspecto: connectmq/demo/ab12cd/raw/<deviceId>/<rawPoint>
const PREFIX = "connectmq/demo/ab12cd";

// deviceId -> ubicación 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" }
};

// nombre del punto en bruto -> nombre normalizado y unidad
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];
}

// Aceptar números, cadenas u objetos JSON con un 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];

Establecer retain: true significa que cualquier nuevo suscriptor recibe de inmediato el último valor de cada punto de datos, que es como la mayoría de los consumidores de un Unified Namespace esperan que se comporte el árbol. El campo source mantiene un rastro hasta el topic original, lo que ayuda mucho al depurar.

Flujo importable

Puedes importar el flujo completo con Menu → Import. Tras importarlo, pega el código de la función anterior en el nodo function y cambia el client ID y el prefijo por tus propios 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":[]}
]

El nodo mqtt out deja vacíos qos y retain para que se usen los valores establecidos en el nodo function.

Probar el mapeo con mosquitto

Abre una terminal y observa el lado estructurado del árbol:

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

En una segunda terminal, simula dispositivos en el lado en 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"

Los dos primeros mensajes deberían aparecer bajo izmir/packaging/line2/filler1/temperature e izmir/utilities/main/meter7/power. El tercero debería aparecer en la barra lateral de debug de Node-RED como sin mapear.

Evitar bucles y otros errores habituales

El error más común en los flujos de remapeo es suscribirse a un comodín que también coincide con la salida. Si el nodo mqtt in escucha en connectmq/demo/ab12cd/# y el flujo vuelve a publicar bajo el mismo prefijo, cada mensaje republicado se recibe de nuevo, se remapea de nuevo y acaba inundando el broker. Mantén los datos en bruto y los estructurados en ramas separadas del árbol y suscríbete solo a la rama en bruto.

Con MQTT 5 también puedes activar la opción no local en el nodo mqtt in, para que el cliente nunca reciba los mensajes que él mismo publicó. Es una red de seguridad, no un sustituto de un buen diseño de topics, porque una segunda instancia de Node-RED seguiría viendo los mensajes.

Otros errores que conviene revisar:

Síntoma Causa probable Solución
No se republica nada La función devuelve null en ambas salidas Revisa la salida de debug y la comprobación de parts.length
El topic de salida contiene undefined Dispositivo en la tabla, pero le falta un nivel Valida que cada entrada de la tabla tenga los cuatro niveles
Reaparecen valores antiguos tras un cambio de nombre Retained messages en el topic antiguo Publica un payload retenido vacío en el topic antiguo para borrarlo
Cadenas en lugar de números aguas abajo El payload en bruto incluía unidades, como 23.7C Elimina las unidades en el nodo function antes de Number()
Desconexiones en el servidor público Más de 10 mensajes por segundo por cliente Limita con un nodo delay o usa tu propio broker

Pasar el flujo a producción

El servidor público está bien para experimentos, pero los datos de producción deben ir a un broker privado con credenciales y TLS. Crea un broker en ConnectMQ, copia el username, la password y el client ID generados en el portal y cambia el nodo de configuración del broker a:

  • Server broker.connectmq.com (o el host de tu broker indicado en el portal), puerto 8883, Use TLS activado
  • Client ID YOUR_CLIENT_ID, username YOUR_USERNAME, password YOUR_PASSWORD
  • Protocolo MQTT V5

El certificado del broker lo emite Let's Encrypt, así que el almacén de confianza por defecto del runtime Node.js de Node-RED lo valida sin subir ningún archivo de CA. Nuestra guía de Node-RED MQTT sobre TLS recorre los ajustes de TLS paso a paso.

Cuando los topics estructurados ya fluyen, el portal de ConnectMQ detecta el namespace con forma ISA-95 y muestra tus dispositivos agrupados por sitio, área, línea y máquina, y el explorador de topics en vivo te permite revisar el árbol sin abrir un cliente aparte. Si algunas de tus fuentes ya hablan Sparkplug B, quizá no necesites Node-RED para ellas: el broker puede decodificar Sparkplug B y volver a publicarlo en topics ISA-95, de modo que Node-RED solo tenga que encargarse de los topics planos. Para las fuentes PLC, la guía de Node-RED con Modbus muestra cómo leer registros y llevarlos al mismo flujo de mapeo. Un árbol ISA-95 limpio es además la base ideal para un gemelo digital 3D de la planta, porque cada máquina tiene una ruta predecible.

Como un broker MQTT gestionado se encarga de la disponibilidad, los certificados y los límites de conexión, el flujo de mapeo pasa a ser la única pieza móvil que mantienes. Esa es una gran parte de la razón por la que los equipos pasan de un servidor Mosquitto autoalojado a un broker MQTT gestionado a medida que crece el namespace.

Escalar la tabla de mapeo

Cuando el número de dispositivos supera lo que quieres editar dentro de un nodo function, saca la tabla fuera:

  • Topic de configuración retenido: publica el mapeo como un mensaje JSON retenido, por ejemplo en config/topic-map, suscríbete a él en un flujo aparte y guárdalo con global.set("topicMap", msg.payload). La función de mapeo lo lee con global.get("topicMap"). Los cambios surten efecto sin redeploy.
  • Archivo o base de datos: lee un archivo CSV o JSON al arrancar con un nodo file in, o consulta una pequeña tabla, algo adecuado para equipos que ya gestionan un registro de activos.
  • Reglas por patrón: si los IDs de dispositivo codifican su ubicación, como iz-pk-l2-filler1-temp, una regla basada en split puede sustituir la mayor parte de la tabla, con una tabla pequeña para las excepciones.

Elijas lo que elijas, mantén la salida de mensajes sin mapear conectada a algo visible. Los dispositivos nuevos aparecen en silencio, y un aviso de mensaje sin mapear suele ser la única señal de que un gateway se ha sustituido o se le ha cambiado el nombre.

Ejecuta tu mapeo de topics de Node-RED sobre ConnectMQ

Un namespace limpio necesita un broker fiable debajo. Regístrate en app.connectmq.com en el plan gratuito Serverless sin tarjeta de crédito, crea un broker y apunta tu flujo de mapeo hacia él con TLS en el puerto 8883. Obtienes MQTT 5, retained messages para el último valor de cada punto de datos y una vista de dispositivos que entiende tus niveles ISA-95 desde el primer mensaje. Para una visión más amplia de las opciones de alojamiento, lee nuestra guía sobre el broker MQTT en la nube.

Preguntas frecuentes

¿Puede Node-RED reescribir topics sin un nodo function?

Sí. Un nodo change puede establecer msg.topic con una expresión JSONata, y eso basta para cambios simples de prefijo. Un nodo function es más fácil de leer en cuanto necesitas una tabla de búsqueda, validación y normalización del payload en el mismo paso.

¿Debo publicar un topic por valor o un objeto JSON por máquina?

Ambas opciones funcionan en un Unified Namespace. Un valor por topic es más sencillo para dashboards y reglas de alerta, mientras que un objeto por máquina reduce el número de mensajes; elige una convención y aplícala en todas partes.

¿Cómo evito que el flujo vuelva a publicar sus propios mensajes?

Suscríbete solo a una rama en bruto en la que el flujo nunca escriba y activa la opción no local de MQTT 5 en el nodo mqtt in como protección adicional.

¿Remapea ConnectMQ los topics por sí mismo?

Vuelve a publicar los mensajes Sparkplug B en topics ISA-95, pero no ejecuta reglas arbitrarias de reescritura de topics. Para topics planos o específicos de un fabricante, ejecuta el mapeo en Node-RED o en otro cliente MQTT, como se muestra aquí.

Artículos relacionados

Cliente OPC UA en Node-RED hacia MQTT con topics ISA-95

Usa node-red-contrib-opcua para suscribirte a variables OPC UA de un PLC o de un servidor de simulación, asignar los node IDs a topics MQTT de estilo ISA-95 y publicar los valores con calidad y marcas de tiempo.

10 min de lectura

Node-RED Dashboard 2.0 con MQTT: indicadores, gráficos y controles

Un tutorial práctico de Node-RED Dashboard 2.0: suscríbete a telemetría MQTT en directo, muéstrala en indicadores y en un gráfico de líneas, y controla un dispositivo con un interruptor que publica comandos y refleja el estado real.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.