Ir al contenido

Conector UNS (Unified Namespace)

El conector UNS (Unified Namespace) es un procesador que construye una jerarquía de tópicos alineada con ISA-95 a partir de la configuración del conector y la carga entrante, y luego emite o bien una carga JSON con una clave _uns_topic inyectada o un sobre estilo SparkplugB que envuelve los datos en un array de métricas. Es el bloque de construcción canónico para organizar los datos de planta en un Unified Namespace tal como lo popularizó Walker Reynolds y el estándar ISA-95.

Tipos de Conector:

  • UNS - Procesador sin estado que decora las cargas con un tópico ISA-95 y reforma la salida como JSON o SparkplugB
  • ✅ Jerarquía ISA-95: Enterprise / Site / Area / Line / Cell
  • ✅ Niveles de tópico dinámicos opcionales basados en la carga (mappings)
  • ✅ Dos formatos de salida: json (passthrough + _uns_topic) y sparkplugb (sobre con array de métricas)
  • ✅ Omite niveles de jerarquía vacíos con elegancia
  • ✅ Timestamp por mensaje (milisegundos Unix) en la salida SparkplugB
{
"type": "UNS",
"config": {
"enterprise": "Acme",
"site": "Milan",
"area": "Assembly",
"line": "Line1",
"cell": "Station3",
"outputFormat": "json"
}
}

Niveles de Tópico Dinámicos desde la Carga

Sección titulada «Niveles de Tópico Dinámicos desde la Carga»
{
"type": "UNS",
"config": {
"enterprise": "Acme",
"site": "Milan",
"area": "Assembly",
"mappings": [
{ "inputKey": "machine_id", "topicLevel": "machine" },
{ "inputKey": "shift_id", "topicLevel": "shift" }
],
"outputFormat": "json"
}
}
{
"type": "UNS",
"config": {
"enterprise": "Acme",
"site": "Milan",
"area": "Packaging",
"line": "Line2",
"outputFormat": "sparkplugb"
}
}

Obligatorio. La empresa ISA-95 de nivel superior (nombre de la compañía/división).

{ "enterprise": "Acme" }

Obligatorio. El sitio físico (planta, fábrica, edificio).

{ "site": "Milan" }

Opcional. Un área funcional dentro del sitio.

{ "area": "Assembly" }

Opcional. Una línea de producción dentro del área.

{ "line": "Line1" }

Opcional. Una célula o estación de trabajo específica dentro de la línea.

{ "cell": "Station3" }

Opcional. Un array de mapeos de carga a nivel de tópico. Para cada mapeo, el conector busca data[inputKey] y añade <topicLevel>/<value> al tópico.

{
"mappings": [
{ "inputKey": "machine_id", "topicLevel": "machine" },
{ "inputKey": "shift_id", "topicLevel": "shift" }
]
}

Comportamiento:

  • Tanto inputKey como topicLevel son obligatorios en cada entrada
  • Las claves de entrada faltantes se reportan a través del canal de errores; el nivel de tópico se omite
  • Los valores de entrada no string se coercionan con fmt.Sprintf("%v", ...)

Obligatorio. Debe ser uno de:

  • json - Passthrough de la carga original con _uns_topic inyectado
  • sparkplugb - Sobre { topic, timestamp, metrics } donde metrics es una lista de pares { name, value }
{ "outputFormat": "sparkplugb" }

Los niveles del tópico se unen con /. Los niveles opcionales vacíos (Area, Line, Cell) se omiten. Los mappings aparecen en el orden en el que se declaran.

Ejemplo:

Configuración:

{
"enterprise": "Acme",
"site": "Milan",
"area": "Assembly",
"line": "Line1",
"mappings": [
{ "inputKey": "machine_id", "topicLevel": "machine" }
]
}

Carga entrante:

{ "machine_id": "M-007", "temperature": 24.5 }

Tópico resultante:

Acme/Milan/Assembly/Line1/machine/M-007

Entrada:

{
"machine_id": "M-007",
"temperature": 24.5,
"pressure": 5.2
}

Configuración:

{
"enterprise": "Acme",
"site": "Milan",
"area": "Assembly",
"line": "Line1",
"mappings": [
{ "inputKey": "machine_id", "topicLevel": "machine" }
],
"outputFormat": "json"
}

Salida:

{
"machine_id": "M-007",
"temperature": 24.5,
"pressure": 5.2,
"_uns_topic": "Acme/Milan/Assembly/Line1/machine/M-007"
}

Misma entrada y configuración base con outputFormat: sparkplugb:

{
"topic": "Acme/Milan/Assembly/Line1/machine/M-007",
"timestamp": 1747740875123,
"metrics": [
{ "name": "machine_id", "value": "M-007" },
{ "name": "temperature", "value": 24.5 },
{ "name": "pressure", "value": 5.2 }
]
}

El timestamp es milisegundos Unix en el momento de procesado. Ten en cuenta que el orden de las entradas de metrics no está garantizado (la iteración de mapas de Go no está ordenada).

DataPayload → UNS → DataPayload + _uns_topic (json) O { topic, timestamp, metrics } (sparkplugb)

1. Puenteo de Datos PLC a MQTT con Tópico ISA-95

Sección titulada «1. Puenteo de Datos PLC a MQTT con Tópico ISA-95»

Reforma las cargas crudas del PLC en un tópico MQTT conforme a UNS:

{
"type": "UNS",
"config": {
"enterprise": "Acme",
"site": "Detroit",
"area": "Stamping",
"line": "PressLine1",
"outputFormat": "json"
}
}

Un MqttV5Writer descendente usa luego _uns_topic como tópico de publicación a través de un paso Reshape que mapea _uns_topic → tópico MQTT.

2. Publicación SparkplugB para Ignition o HiveMQ

Sección titulada «2. Publicación SparkplugB para Ignition o HiveMQ»

Muchas plataformas SCADA (Ignition, HiveMQ, etc.) esperan sobres SparkplugB. Usa la salida sparkplugb para coincidir:

{
"type": "UNS",
"config": {
"enterprise": "Acme",
"site": "Milan",
"area": "Bottling",
"line": "Filler1",
"outputFormat": "sparkplugb"
}
}

3. Enrutamiento de Tópico Multi-Tenant por Cliente

Sección titulada «3. Enrutamiento de Tópico Multi-Tenant por Cliente»

Usa mappings dinámicos para incrustar el identificador de cliente en el tópico:

{
"type": "UNS",
"config": {
"enterprise": "ConnectCo",
"site": "Cloud",
"mappings": [
{ "inputKey": "tenant_id", "topicLevel": "tenant" },
{ "inputKey": "site_id", "topicLevel": "customer-site" },
{ "inputKey": "device_id", "topicLevel": "device" }
],
"outputFormat": "json"
}
}

Tópico resultante para {tenant_id: "T-1", site_id: "S-99", device_id: "D-42"}:

ConnectCo/Cloud/tenant/T-1/customer-site/S-99/device/D-42

Problema: El consumidor descendente no ve la clave _uns_topic

Soluciones:

  1. Confirma que outputFormat: json está definido (el sobre SparkplugB reemplaza la carga por completo; tiene topic en el nivel superior en lugar de _uns_topic)
  2. Verifica que el conector UNS está realmente en el pipeline — usa un escritor Log para volcar cargas intermedias

Problema: Un mapping configurado referencia una clave que no está presente en la carga

Soluciones:

  1. Verifica que la clave de entrada coincide con la carga ascendente exactamente (sensible a mayúsculas)
  2. Usa un Reshape o Transform aguas arriba para asegurar que existen las claves requeridas
  3. Si la clave a veces falta, considera hacerla parte de los valores estáticos area/line/cell

Problema: El sobre de salida es { topic, timestamp, metrics: [] }

Soluciones:

  1. El array de métricas se construye a partir de las claves de la carga de entrada. Una entrada vacía → métricas vacías
  2. Verifica que el conector ascendente está entregando cargas no vacías

El Tópico Tiene Barras Inesperadas o Segmentos Vacíos

Sección titulada «El Tópico Tiene Barras Inesperadas o Segmentos Vacíos»

Problema: El tópico contiene // o barras finales

Soluciones:

  1. No incluyas barras en enterprise, site, etc. — el conector las inserta. Las barras incrustadas producirán rutas malformadas
  2. Evita cadenas vacías para los niveles opcionales — deja el campo sin definir en lugar de pasar ""

Preocupaciones de Ordenación para Métricas SparkplugB

Sección titulada «Preocupaciones de Ordenación para Métricas SparkplugB»

Problema: La ordenación de las métricas es inconsistente entre ejecuciones

Soluciones:

  1. La iteración de mapas de Go no está ordenada intencionadamente. Si a los consumidores descendentes les importa el orden, ordena aguas arriba a través de un Reshape que convierta las métricas en una lista explícita antes de alcanzar este conector
  2. O haz que el consumidor ordene por name al recibir

La jerarquía del tópico es consumida por dashboards, historiadores y otros pipelines de Meddle. Una vez publicado, trátalo como una interfaz estable — renombrar area o line después de los hechos rompe a los consumidores descendentes.

2. Usa Niveles Estáticos para Jerarquías Estables

Sección titulada «2. Usa Niveles Estáticos para Jerarquías Estables»

Prefiere enterprise/site/area/line/cell estáticos sobre mappings siempre que la jerarquía no dependa de la carga. Los mappings son potentes pero añaden una dependencia de la forma de la carga.

3. Prefiere JSON para Greenfield, SparkplugB para Puente SCADA

Sección titulada «3. Prefiere JSON para Greenfield, SparkplugB para Puente SCADA»

json es más simple y funciona con cualquier consumidor descendente. Usa sparkplugb sólo cuando debas interoperar con un ecosistema SparkplugB existente (Ignition, HiveMQ Edge, etc.).

La mayoría de escritores MQTT requieren que el tópico se defina explícitamente. Después del conector UNS, usa un paso Reshape que promueva _uns_topic al campo de tópico de publicación:

... → UNS → Reshape (mueve _uns_topic → topic) → MqttV5Writer

5. Documenta tu UNS a Nivel de Organización

Sección titulada «5. Documenta tu UNS a Nivel de Organización»

Elige un esquema ISA-95 una vez para toda la organización y aplícalo de forma consistente en todos los sitios y líneas. La deriva de esquema entre despliegues de Meddle crea árboles de tópicos incompatibles.

ModbusReader → Reshape → UNS → Reshape → MqttV5Writer
  1. ModbusReader: Extrae los datos de registros crudos de un PLC
  2. Reshape: Renombra direcciones crudas a claves legibles por humanos
  3. UNS: Decora con _uns_topic
  4. Reshape: Promueve _uns_topic al campo topic de MQTT
  5. MqttV5Writer: Publica en un broker cloud, organizado bajo el UNS
OpcuaReader → Predictive → UNS (sparkplugb) → MqttV5Writer
  1. OpcuaReader: Agrega desde múltiples endpoints OPC UA
  2. Predictive: Añade trend/RUL/health-score
  3. UNS: Envuelve la carga enriquecida en un sobre SparkplugB
  4. MqttV5Writer: Publica en un broker compatible con SparkplugB (ej. HiveMQ Edge → Ignition)
[pipeline Sitio A] ─┐
[pipeline Sitio B] ─┼─→ UNS → InfluxDb2Writer
[pipeline Sitio C] ─┘

Cada pipeline de sitio etiqueta sus datos con su propia configuración UNS, asegurando que los consumidores descendentes puedan desambiguar sólo por tópico.

  • MQTT v5 - Publica cargas decoradas con UNS a un broker
  • MQTT v3 - Publicación MQTT heredada
  • Reshape - Promueve _uns_topic al campo de tópico del escritor
  • Filter - Filtra por patrones de _uns_topic
  • Router - Enrutamiento ramificado basado en la jerarquía UNS