Un Unified Namespace (UNS) es un lugar único y orientado a eventos donde cada sistema de una empresa publica su estado actual, organizado según dónde están las cosas en el negocio y no según qué software las produjo. En la práctica es un broker MQTT más una jerarquía de topics —normalmente basada en ISA-95: empresa / planta / área / línea / celda— con mensajes retenidos que guardan el último valor de todo.
Este tutorial muestra cómo diseñar esa jerarquía, decidir qué retener, elegir convenciones de payload, encajar Sparkplug B y probar el namespace con unos pocos scripts.
Por qué un UNS en lugar de integraciones punto a punto
Las plantas clásicas integran sistema a sistema: el MES consulta al SCADA, el ERP importa un CSV del MES, un dashboard consulta un historiador. Cada nuevo consumidor implica una nueva integración y nadie tiene un único lugar para ver el estado actual de la operación.
Un UNS lo sustituye por un hub:
| Punto a punto | Unified Namespace | |
|---|---|---|
| Integraciones | Una por cada par de sistemas | Una por sistema (hacia el broker) |
| Añadir un consumidor | Nueva interfaz en el origen | Suscribirse a un topic |
| Estado actual | Repartido en bases de datos | Retenido en el broker |
| Flujo de datos | Polling, batch | Orientado a eventos, por excepción |
| Propiedad | Cada interfaz por separado | Un namespace, gobernado de forma central |
Los productores —pasarelas de PLC, MES, sistemas de calidad, adaptadores de ERP— publican en su rama del árbol. Los consumidores se suscriben a lo que necesiten. Nadie necesita saber quién más está conectado.
Diseña la jerarquía ISA-95
ISA-95 te da un vocabulario para la estructura física y organizativa de una planta. Mapearlo directamente a niveles de topic hace que el namespace sea legible tanto para la gente de OT como de IT:
| Nivel | Ejemplo | Nivel de topic |
|---|---|---|
| Empresa | Acme Foods | acme |
| Planta | Planta de Dallas | dallas |
| Área | Envasado | packaging |
| Línea (work center) | Línea 1 | line1 |
| Celda (work unit) | Llenadora | filler |
| Dato | Velocidad, estado, contadores | speed, state, good-count |
Así, la velocidad de la llenadora vive en acme/dallas/packaging/line1/filler/speed. Debajo de los niveles ISA-95, añade un pequeño conjunto de subespacios por tipo de dato para que los consumidores distingan estado de eventos:
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 retainedReglas que mantienen sano un namespace durante años:
- Minúsculas, sin espacios, solo ASCII, con guiones dentro de un nivel. Los topics distinguen mayúsculas y minúsculas, y mezclarlas garantiza duplicados.
- Nombra por lugar, no por origen.
line1/filler/speed, nuncakepware3/channel2/tag17. Los orígenes cambian; la línea se queda. - Mantén fija la profundidad de los niveles dentro de una rama, para que
acme/+/packaging/+/filler/statefuncione en todas las plantas. - Separa los comandos bajo
cmd/y protégelos con ACL; la mayoría de los productores nunca deberían poder escribir ahí. - No pongas valores en los nombres de topic (nada de
.../temp/21.5); el valor va en el payload.
Los principios generales están en buenas prácticas de diseño de topics. Antes de comprometerte con una estructura, prueba tus patrones de suscripción en el topic matcher.
El estado retenido es el corazón de un UNS
El namespace debe responder "¿qué está pasando ahora mismo?" a un cliente que se conectó hace un segundo. Eso es exactamente lo que hacen los mensajes retenidos: el broker guarda el último mensaje retenido de cada topic y lo entrega al instante a cada nuevo suscriptor.
- Retén el estado: valores, estados, orden de trabajo actual, último resultado de calidad, consignas.
- No retengas eventos: fallos, piezas rechazadas, comandos. Un nuevo suscriptor no debe ver el fallo de ayer como si acabara de ocurrir.
- Limpia los topics obsoletos publicando un payload retenido vacío cuando se retire una máquina, o el namespace se irá llenando de fantasmas.
- Usa la expiración de mensajes de MQTT 5 en el estado que deja de tener sentido si no se refresca, para que los valores viejos desaparezcan en lugar de mentir.
Acompaña a cada productor con un topic status retenido y un Last Will (offline), como en el tutorial de pasarela Modbus. Así los consumidores distinguen un valor estable de una fuente muerta.
Convenciones de payload
Acordad una única forma de payload para el estado y mantenedla. Un JSON simple y autodescriptivo funciona bien:
{ "v": 118, "unit": "bpm", "ts": "2026-10-17T08:41:07.214Z", "q": "good" }v es el valor, unit evita adivinar, ts es el timestamp de origen en UTC y q la calidad. Los metadatos estáticos (descripciones, rangos, unidades de ingeniería) pueden vivir en un topic hermano _meta retenido para que el camino caliente siga siendo ligero.
Siembra y explora un namespace de prueba
Dos pequeños scripts con paho-mqtt 2.x te permiten experimentar antes de que exista ningún productor real. El primero publica una instantánea retenida de una planta diminuta con expiración de una hora:
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")El segundo se conecta como un cliente totalmente nuevo, recoge durante unos segundos lo que le entrega el broker e imprime el namespace como un árbol, justo lo que vería un consumidor nuevo:
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 "")Si el explorador imprime cada topic sembrado marcado como (retained), tu capa de estado funciona. Puedes hacer la misma comprobación de forma visual en el cliente MQTT online:
Explora el namespace de prueba en tu navegador
El endpoint compartido sirve para probar la estructura. Un namespace real contiene el estado en vivo de tu operación, así que ejecútalo en un broker que controles.
Dónde encaja Sparkplug B
Sparkplug B es una especificación sobre MQTT para dispositivos industriales: una estructura de topics fija (spBv1.0/<group>/<message type>/<edge node>/<device>), payloads Protobuf, certificados birth/death y un host primario que sigue el estado. Resuelve muy bien el problema del edge, pero sus topics se organizan por tipo de mensaje y edge node, no por ISA-95.
| Enfoque | Cómo funciona | Conviene cuando |
|---|---|---|
| UNS con MQTT simple | Los productores publican JSON directamente en topics ISA-95 | Consumidores mixtos IT/OT, herramientas sencillas, topics legibles |
| Sparkplug en el edge, UNS por encima | Los edge nodes hablan Sparkplug; un host o puente republica en topics ISA-95 | Muchos dispositivos, necesidad de estado estándar y payloads compactos |
| Solo Sparkplug | Todos los consumidores entienden Sparkplug | Ecosistema SCADA homogéneo |
El modelo híbrido es popular por algo: los dispositivos obtienen la gestión de estado de Sparkplug y el resto de la empresa un namespace legible. Las ventajas e inconvenientes se analizan en Sparkplug vs MQTT simple. Los datos de servidores OPC UA pueden entrar en el mismo árbol mediante un puente OPC UA a MQTT.
Checklist de pruebas para tu namespace
- Prueba de suscriptor nuevo: un cliente nuevo suscrito a
acme/<site>/#recibe al instante el estado actual de cada activo (todo retenido). - Prueba de eventos: los eventos llegan en vivo pero no se reproducen a un suscriptor nuevo.
- Prueba de cambio de origen: sustituye un productor (pasarela simulada → pasarela real); los consumidores no notan nada.
- Prueba de origen caído: mata un productor; su
statuspasa aofflinegracias al Last Will. - Prueba de ACL: intenta publicar en la rama de otra línea con las credenciales de una pasarela; debe ser rechazado.
- Prueba de wildcards: cada filtro de dashboard coincide exactamente con los topics previstos en el topic matcher.
Un Unified Namespace tiene menos que ver con la tecnología que con el acuerdo: un broker, una jerarquía que todos entienden, estado retenido para el presente y reglas claras sobre quién publica dónde. Empieza con una línea, siémbrala, explórala, rómpela a propósito, y haz crecer el árbol solo cuando la primera rama haya demostrado su valor.
Preguntas frecuentes
¿Un Unified Namespace es un producto?
No. Un UNS es un patrón de arquitectura: un hub orientado a eventos, normalmente un broker MQTT, donde cada sistema publica su estado actual en una jerarquía de topics compartida y con forma de negocio. Se construye con un broker, reglas de nombres y los clientes que publican y consumen.
¿Necesito Sparkplug B para un Unified Namespace?
No. Muchas implementaciones de UNS usan MQTT simple con payloads JSON y topics con forma ISA-95. Sparkplug B es útil en el edge por su estado birth/death estándar y sus payloads compactos; un patrón común es Sparkplug desde los dispositivos hasta un host, que republica en el namespace ISA-95.
¿Debe ser retenido cada mensaje del UNS?
Retén el estado actual (valores, estados, consignas) para que cualquier consumidor nuevo vea el presente al instante. No retengas eventos como alarmas, piezas producidas o comandos: pertenecen al momento en que ocurren y, después, a un historiador.

