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:
- Usa minúsculas, ASCII y sin espacios, para que los topics sean fáciles de escribir en suscripciones y filtros.
- 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.
- Nunca pongas marcas de tiempo, números de secuencia ni valores en el propio topic.
- 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:
- Un nodo mqtt in se suscribe a los topics en bruto con un comodín.
- Un nodo function divide el topic entrante, busca el dispositivo en una tabla, construye el nuevo topic y normaliza el payload.
- Un nodo mqtt out publica el resultado. Si su propio campo topic está vacío, usa
msg.topic, que es exactamente lo que queremos. - 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), puerto8883, Use TLS activado - Client ID
YOUR_CLIENT_ID, usernameYOUR_USERNAME, passwordYOUR_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 conglobal.set("topicMap", msg.payload). La función de mapeo lo lee conglobal.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í.



