Salta ai contenuti

Connettore UNS (Unified Namespace)

Il connettore UNS (Unified Namespace) è un processore che costruisce una gerarchia di topic allineata a ISA-95 dalla configurazione del connettore e dal payload in ingresso, quindi emette un payload JSON con una chiave _uns_topic iniettata o un envelope in stile SparkplugB che avvolge i dati in un array di metriche. È il blocco costitutivo canonico per organizzare i dati di pianta in un Unified Namespace come reso popolare da Walker Reynolds e dallo standard ISA-95.

Tipi Connettore:

  • UNS - Processore senza stato che decora i payload con un topic ISA-95 e rimodella l’output come JSON o SparkplugB
  • ✅ Gerarchia ISA-95: Enterprise / Site / Area / Line / Cell
  • ✅ Livelli di topic dinamici opzionali guidati dal payload (mapping)
  • ✅ Due formati di output: json (passthrough + _uns_topic) e sparkplugb (envelope con array di metriche)
  • ✅ Salta livelli gerarchici vuoti con grazia
  • ✅ Timestamp per messaggio (millisecondi Unix) sull’output SparkplugB
{
"type": "UNS",
"config": {
"enterprise": "Acme",
"site": "Milan",
"area": "Assembly",
"line": "Line1",
"cell": "Station3",
"outputFormat": "json"
}
}
{
"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"
}
}

Richiesto. L’enterprise ISA-95 di primo livello (nome azienda/divisione).

{ "enterprise": "Acme" }

Richiesto. Il sito fisico (impianto, fabbrica, edificio).

{ "site": "Milan" }

Opzionale. Un’area funzionale all’interno del sito.

{ "area": "Assembly" }

Opzionale. Una linea di produzione all’interno dell’area.

{ "line": "Line1" }

Opzionale. Una cella specifica o stazione di lavoro all’interno della linea.

{ "cell": "Station3" }

Opzionale. Un array di mapping da payload a livello di topic. Per ogni mapping, il connettore cerca data[inputKey] e aggiunge <topicLevel>/<value> al topic.

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

Comportamento:

  • Sia inputKey che topicLevel sono richiesti per ogni voce
  • Le chiavi di input mancanti vengono riportate tramite il canale di errore; il livello del topic viene omesso
  • I valori di input non stringa vengono convertiti con fmt.Sprintf("%v", ...)

Richiesto. Deve essere uno tra:

  • json - Passthrough del payload originale con _uns_topic iniettato
  • sparkplugb - Envelope { topic, timestamp, metrics } dove metrics è una lista di coppie { name, value }
{ "outputFormat": "sparkplugb" }

I livelli del topic sono uniti con /. I livelli opzionali vuoti (Area, Line, Cell) vengono saltati. I mapping appaiono nell’ordine in cui sono dichiarati.

Esempio:

Configurazione:

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

Payload in ingresso:

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

Topic risultante:

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

Input:

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

Configurazione:

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

Output:

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

Stesso input e configurazione di 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 }
]
}

Il timestamp è in millisecondi Unix al momento dell’elaborazione. Nota che l’ordine delle voci metrics non è garantito (l’iterazione delle map Go è non ordinata).

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

Rimodella payload PLC raw in un topic MQTT conforme a UNS:

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

Un MqttV5Writer a valle utilizza poi _uns_topic come topic di pubblicazione tramite uno step Reshape che mappa _uns_topic → topic MQTT.

Molte piattaforme SCADA (Ignition, HiveMQ, ecc.) si aspettano envelope SparkplugB. Usa l’output sparkplugb per allinearti:

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

Usa mapping dinamici per incorporare l’identificatore del cliente nel topic:

{
"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"
}
}

Topic risultante per {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: Il consumer a valle non vede la chiave _uns_topic

Soluzioni:

  1. Conferma che outputFormat: json sia impostato (l’envelope SparkplugB sostituisce interamente il payload; ha topic al livello superiore invece di _uns_topic)
  2. Verifica che il connettore UNS sia effettivamente nella pipeline — usa un writer Log per dumpare payload intermedi

Problema: Un mapping configurato fa riferimento a una chiave non presente nel payload

Soluzioni:

  1. Verifica che la chiave di input corrisponda esattamente al payload a monte (case-sensitive)
  2. Usa un Reshape o Transform a monte per assicurarti che le chiavi richieste esistano
  3. Se la chiave a volte manca, considera di renderla parte di area/line/cell statici invece

Problema: L’envelope di output è { topic, timestamp, metrics: [] }

Soluzioni:

  1. L’array metrics è costruito dalle chiavi del payload di input. Un input vuoto → metrics vuoto
  2. Verifica che il connettore a monte stia consegnando payload non vuoti

Problema: Il topic contiene // o slash di coda

Soluzioni:

  1. Non includere slash in enterprise, site, ecc. — il connettore li inserisce. Slash incorporati produrranno percorsi malformati
  2. Evita stringhe vuote per livelli opzionali — lascia il campo non impostato invece di passare ""

Problema: L’ordinamento delle metriche è incoerente tra esecuzioni

Soluzioni:

  1. L’iterazione delle map Go è intenzionalmente non ordinata. Se i consumer a valle si preoccupano dell’ordine, ordina a monte tramite un Reshape che converte le metriche in una lista esplicita prima di raggiungere questo connettore
  2. Oppure fai sì che il consumer ordini per name alla ricezione

La gerarchia di topic è consumata da dashboard, historian e altre pipeline Meddle. Una volta pubblicata, trattala come un’interfaccia stabile — rinominare area o line dopo i fatti rompe i consumer a valle.

Preferisci enterprise/site/area/line/cell statici ai mapping ogni volta che la gerarchia non dipende dal payload. I mapping sono potenti ma aggiungono una dipendenza dalla forma del payload.

3. Preferisci JSON per Greenfield, SparkplugB per Bridge SCADA

Sezione intitolata “3. Preferisci JSON per Greenfield, SparkplugB per Bridge SCADA”

json è più semplice e funziona con qualsiasi consumer a valle. Usa sparkplugb solo quando devi interoperare con un ecosistema SparkplugB esistente (Ignition, HiveMQ Edge, ecc.).

La maggior parte dei writer MQTT richiede che il topic sia impostato esplicitamente. Dopo il connettore UNS, usa uno step Reshape che promuova _uns_topic al campo topic di pubblicazione:

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

Scegli uno schema ISA-95 una volta per l’intera organizzazione e applicalo coerentemente su tutti i siti e le linee. Il drift di schema tra deployment Meddle crea alberi di topic incompatibili.

ModbusReader → Reshape → UNS → Reshape → MqttV5Writer
  1. ModbusReader: Estrae dati raw di registro da un PLC
  2. Reshape: Rinomina gli indirizzi raw in chiavi leggibili
  3. UNS: Decora con _uns_topic
  4. Reshape: Promuove _uns_topic al campo topic MQTT
  5. MqttV5Writer: Pubblica a un broker cloud, organizzato sotto l’UNS
OpcuaReader → Predictive → UNS (sparkplugb) → MqttV5Writer
  1. OpcuaReader: Aggrega da più endpoint OPC UA
  2. Predictive: Aggiunge trend/RUL/health-score
  3. UNS: Avvolge il payload arricchito in un envelope SparkplugB
  4. MqttV5Writer: Pubblica a un broker capace di SparkplugB (es. HiveMQ Edge → Ignition)
[pipeline Sito A] ─┐
[pipeline Sito B] ─┼─→ UNS → InfluxDb2Writer
[pipeline Sito C] ─┘

Ogni pipeline di sito etichetta i propri dati con la propria configurazione UNS, garantendo che i consumer a valle possano disambiguare per topic solo.

  • MQTT v5 - Pubblica payload decorati UNS su un broker
  • MQTT v3 - Pubblicazione MQTT legacy
  • Reshape - Promuovi _uns_topic al campo topic del writer
  • Filter - Filtra per pattern di _uns_topic
  • Router - Routing branchiato basato sulla gerarchia UNS