Un Unified Namespace (UNS) est un lieu unique et orienté événements où chaque système d'une entreprise publie son état courant, organisé selon l'emplacement des choses dans l'entreprise plutôt que selon le logiciel qui les a produites. En pratique, c'est un broker MQTT plus une hiérarchie de topics — généralement calquée sur l'ISA-95 : entreprise / site / zone / ligne / cellule — avec des messages retenus qui conservent la dernière valeur de tout.
Ce tutoriel montre comment concevoir cette hiérarchie, décider quoi retenir, choisir des conventions de payload, situer Sparkplug B et tester le namespace avec quelques petits scripts.
Pourquoi un UNS plutôt que des intégrations point à point
Les usines classiques intègrent système par système : le MES interroge le SCADA, l'ERP importe un CSV du MES, un tableau de bord interroge un historien. Chaque nouveau consommateur implique une nouvelle intégration, et personne ne dispose d'un endroit unique pour voir l'état actuel de l'exploitation.
Un UNS remplace cela par un hub :
| Point à point | Unified Namespace | |
|---|---|---|
| Intégrations | Une par paire de systèmes | Une par système (vers le broker) |
| Ajouter un consommateur | Nouvelle interface sur la source | S'abonner à un topic |
| État courant | Éparpillé dans des bases de données | Retenu sur le broker |
| Flux de données | Polling, batch | Orienté événements, sur changement |
| Responsabilité | Chaque interface séparément | Un namespace, gouverné de façon centrale |
Les producteurs — passerelles d'automates, MES, systèmes qualité, adaptateurs ERP — publient dans leur branche de l'arbre. Les consommateurs s'abonnent à ce dont ils ont besoin. Personne n'a besoin de savoir qui d'autre est connecté.
Concevoir la hiérarchie ISA-95
L'ISA-95 fournit un vocabulaire pour la structure physique et organisationnelle d'une usine. La transposer directement en niveaux de topic rend le namespace lisible pour les équipes OT comme IT :
| Niveau | Exemple | Niveau de topic |
|---|---|---|
| Entreprise | Acme Foods | acme |
| Site | Usine de Dallas | dallas |
| Zone | Conditionnement | packaging |
| Ligne (work center) | Ligne 1 | line1 |
| Cellule (work unit) | Remplisseuse | filler |
| Donnée | Vitesse, état, compteurs | speed, state, good-count |
La vitesse de la remplisseuse se trouve donc sous acme/dallas/packaging/line1/filler/speed. Sous les niveaux ISA-95, ajoutez un petit ensemble de sous-espaces par nature de donnée pour que les consommateurs distinguent l'état des événements :
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 retainedDes règles qui gardent un namespace sain pendant des années :
- Minuscules, sans espaces, ASCII uniquement, avec des tirets à l'intérieur d'un niveau. Les topics sont sensibles à la casse, et la mélanger garantit des doublons.
- Nommer par lieu, pas par source.
line1/filler/speed, jamaiskepware3/channel2/tag17. Les sources changent ; la ligne reste. - Garder une profondeur fixe à l'intérieur d'une branche, pour que
acme/+/packaging/+/filler/statefonctionne sur tous les sites. - Séparer les commandes sous
cmd/et les protéger par des ACL ; la plupart des producteurs ne devraient jamais pouvoir y écrire. - Ne pas mettre de valeurs dans les noms de topic (pas de
.../temp/21.5) ; la valeur va dans le payload.
Les principes généraux sont détaillés dans bonnes pratiques de conception des topics. Avant de figer une arborescence, essayez vos motifs d'abonnement dans le topic matcher.
L'état retenu est le cœur d'un UNS
Le namespace doit répondre à « que se passe-t-il en ce moment ? » pour un client connecté il y a une seconde. C'est exactement le rôle des messages retenus : le broker conserve le dernier message retenu de chaque topic et le livre immédiatement à chaque nouvel abonné.
- Retenir l'état : valeurs, statuts, ordre de fabrication en cours, dernier résultat qualité, consignes.
- Ne pas retenir les événements : défauts déclenchés, pièces rejetées, commandes. Un nouvel abonné ne doit pas voir le défaut d'hier comme s'il venait d'arriver.
- Nettoyer les topics obsolètes en publiant un payload retenu vide quand une machine est retirée, sinon le namespace se remplit lentement de fantômes.
- Utiliser l'expiration des messages de MQTT 5 pour les états qui perdent leur sens sans rafraîchissement, afin que les valeurs périmées disparaissent au lieu de mentir.
Associez à chaque producteur un topic status retenu et un Last Will (offline), comme dans le tutoriel de passerelle Modbus. Les consommateurs peuvent alors distinguer une valeur stable d'une source morte.
Conventions de payload
Mettez-vous d'accord sur une seule forme de payload pour l'état et tenez-vous-y. Un JSON simple et autodescriptif fonctionne bien :
{ "v": 118, "unit": "bpm", "ts": "2026-10-17T08:41:07.214Z", "q": "good" }v est la valeur, unit évite de deviner, ts est l'horodatage source en UTC, q la qualité. Les métadonnées statiques (descriptions, plages, unités physiques) peuvent vivre dans un topic frère _meta retenu pour que le chemin chaud reste léger.
Peupler et parcourir un namespace de test
Deux petits scripts paho-mqtt 2.x permettent d'expérimenter avant l'arrivée du moindre producteur réel. Le premier publie un instantané retenu d'une mini-usine avec une expiration d'une heure :
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 # secondes
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")Le second se connecte comme un tout nouveau client, collecte pendant quelques secondes ce que le broker lui transmet et affiche le namespace sous forme d'arbre — exactement ce que verrait un nouveau consommateur :
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 le navigateur de namespace affiche chaque topic peuplé marqué (retained), votre couche d'état fonctionne. Vous pouvez faire la même vérification visuellement dans le client MQTT en ligne :
Parcourir le namespace de test dans le navigateur
L'endpoint partagé suffit pour essayer l'arborescence. Un vrai namespace contient l'état en direct de votre exploitation : faites-le tourner sur un broker que vous contrôlez.
La place de Sparkplug B
Sparkplug B est une spécification au-dessus de MQTT pour les équipements industriels : une structure de topics fixe (spBv1.0/<group>/<message type>/<edge node>/<device>), des payloads Protobuf, des certificats birth/death et un host primaire qui suit l'état. Il résout très bien le problème de l'edge, mais ses topics sont organisés par type de message et edge node, pas selon l'ISA-95.
| Approche | Fonctionnement | Adaptée quand |
|---|---|---|
| UNS en MQTT simple | Les producteurs publient du JSON directement dans des topics ISA-95 | Consommateurs IT/OT mixtes, outillage simple, topics lisibles |
| Sparkplug en edge, UNS au-dessus | Les edge nodes parlent Sparkplug ; un host ou une passerelle republie dans des topics ISA-95 | Beaucoup d'équipements, besoin d'un état standard et de payloads compacts |
| Sparkplug uniquement | Tous les consommateurs comprennent Sparkplug | Écosystème SCADA homogène |
Le modèle hybride est populaire pour une bonne raison : les équipements bénéficient de la gestion d'état de Sparkplug et le reste de l'entreprise d'un namespace lisible. Les compromis sont détaillés dans Sparkplug vs MQTT simple. Les données des serveurs OPC UA peuvent entrer dans le même arbre via une passerelle OPC UA vers MQTT.
Checklist de test pour votre namespace
- Test du nouvel abonné : un nouveau client abonné à
acme/<site>/#reçoit immédiatement l'état courant de chaque équipement (tout est retenu). - Test des événements : les événements arrivent en direct mais ne sont pas rejoués à un nouvel abonné.
- Test de changement de source : remplacez un producteur (passerelle simulée → passerelle réelle) ; les consommateurs ne remarquent rien.
- Test de source morte : tuez un producteur ; son
statuspasse àofflinegrâce au Last Will. - Test d'ACL : tentez de publier dans la branche d'une autre ligne avec les identifiants d'une passerelle — cela doit être refusé.
- Test des wildcards : chaque filtre de tableau de bord correspond exactement aux topics prévus dans le topic matcher.
Un Unified Namespace relève moins de la technologie que de l'accord : un broker, une hiérarchie que tout le monde comprend, un état retenu pour le présent et des règles claires sur qui publie où. Commencez par une ligne, peuplez-la, parcourez-la, cassez-la volontairement — et ne faites grandir l'arbre que lorsque la première branche a fait ses preuves.
Questions fréquentes
Un Unified Namespace est-il un produit ?
Non. Un UNS est un modèle d'architecture : un hub orienté événements, en général un broker MQTT, où chaque système publie son état courant dans une hiérarchie de topics partagée, calquée sur l'entreprise. On le construit avec un broker, des règles de nommage et les clients qui publient et consomment.
Faut-il Sparkplug B pour un Unified Namespace ?
Non. Beaucoup d'implémentations UNS utilisent MQTT simple avec des payloads JSON et des topics de forme ISA-95. Sparkplug B est utile en edge pour son état birth/death standard et ses payloads compacts ; un schéma courant est Sparkplug des équipements vers un host, qui republie dans le namespace ISA-95.
Chaque message de l'UNS doit-il être retenu ?
Retenez l'état courant (valeurs, statuts, consignes) pour que tout nouveau consommateur voie le présent immédiatement. Ne retenez pas les événements comme les alarmes déclenchées, les pièces produites ou les commandes : ils appartiennent au moment où ils se produisent, puis à un historien.

