Skip to content

UNS (Unified Namespace) Connector

The UNS (Unified Namespace) connector is a processor that constructs an ISA-95-aligned topic hierarchy from the connector configuration and the incoming payload, then emits either a JSON payload with an injected _uns_topic key or a SparkplugB-style envelope wrapping the data in a metrics array. It is the canonical building block for organizing plant data into a Unified Namespace as popularized by Walker Reynolds and the ISA-95 standard.

Connector Types:

  • UNS - Stateless processor that decorates payloads with an ISA-95 topic and reshapes the output as JSON or SparkplugB
  • ✅ ISA-95 hierarchy: Enterprise / Site / Area / Line / Cell
  • ✅ Optional payload-driven dynamic topic levels (mappings)
  • ✅ Two output formats: json (passthrough + _uns_topic) and sparkplugb (envelope with metrics array)
  • ✅ Skips empty hierarchy levels gracefully
  • ✅ Per-message timestamp (Unix milliseconds) on SparkplugB output
{
"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"
}
}

Required. The top-level ISA-95 enterprise (company/division name).

{ "enterprise": "Acme" }

Required. The physical site (plant, factory, building).

{ "site": "Milan" }

Optional. A functional area within the site.

{ "area": "Assembly" }

Optional. A production line within the area.

{ "line": "Line1" }

Optional. A specific cell or workstation within the line.

{ "cell": "Station3" }

Optional. An array of payload-to-topic-level mappings. For each mapping, the connector looks up data[inputKey] and appends <topicLevel>/<value> to the topic.

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

Behavior:

  • Both inputKey and topicLevel are required for each entry
  • Missing input keys are reported via the error channel; the topic level is omitted
  • Non-string input values are coerced with fmt.Sprintf("%v", ...)

Required. Must be one of:

  • json - Passthrough of the original payload with _uns_topic injected
  • sparkplugb - Envelope { topic, timestamp, metrics } where metrics is a list of { name, value } pairs
{ "outputFormat": "sparkplugb" }

Topic levels are joined with /. Empty optional levels (Area, Line, Cell) are skipped. Mappings appear in the order they’re declared.

Example:

Configuration:

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

Incoming payload:

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

Resulting topic:

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

Input:

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

Configuration:

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

Same input and base configuration with 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 }
]
}

The timestamp is Unix milliseconds at processing time. Note that the order of metrics entries is not guaranteed (Go map iteration is unordered).

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

1. Bridging PLC Data to MQTT with ISA-95 Topic

Section titled “1. Bridging PLC Data to MQTT with ISA-95 Topic”

Reshape raw PLC payloads into a UNS-compliant MQTT topic:

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

Downstream MqttV5Writer then uses _uns_topic as the publish topic via a Reshape step that maps _uns_topic → MQTT topic.

2. SparkplugB Publishing for Ignition or HiveMQ

Section titled “2. SparkplugB Publishing for Ignition or HiveMQ”

Many SCADA platforms (Ignition, HiveMQ, etc.) expect SparkplugB envelopes. Use sparkplugb output to match:

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

Use dynamic mappings to embed the customer identifier in the 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"
}
}

Resulting topic for {tenant_id: "T-1", site_id: "S-99", device_id: "D-42"}:

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

Problem: Downstream consumer does not see the _uns_topic key

Solutions:

  1. Confirm outputFormat: json is set (the SparkplugB envelope replaces the payload entirely; it has topic at the top level instead of _uns_topic)
  2. Verify the UNS connector is actually in the pipeline — use a Log writer to dump intermediate payloads

Problem: A configured mapping references a key that isn’t present in the payload

Solutions:

  1. Verify the input key matches the upstream payload exactly (case-sensitive)
  2. Use a Reshape or Transform upstream to ensure required keys exist
  3. If the key is sometimes missing, consider making it part of static area/line/cell instead

Problem: Output envelope is { topic, timestamp, metrics: [] }

Solutions:

  1. The metrics array is built from the keys of the input payload. An empty input → empty metrics
  2. Verify the upstream connector is delivering non-empty payloads

Topic Has Unexpected Slashes or Empty Segments

Section titled “Topic Has Unexpected Slashes or Empty Segments”

Problem: Topic contains // or trailing slashes

Solutions:

  1. Don’t include slashes in enterprise, site, etc. — the connector inserts them. Embedded slashes will produce malformed paths
  2. Avoid empty strings for optional levels — leave the field unset rather than passing ""

Problem: Metrics ordering is inconsistent between runs

Solutions:

  1. Go map iteration is intentionally unordered. If downstream consumers care about order, sort upstream via a Reshape that converts the metrics into an explicit list before reaching this connector
  2. Or have the consumer sort by name on receipt

The topic hierarchy is consumed by dashboards, historians, and other Meddle pipelines. Once published, treat it as a stable interface — renaming area or line after the fact breaks downstream consumers.

2. Use Static Levels for Stable Hierarchies

Section titled “2. Use Static Levels for Stable Hierarchies”

Prefer static enterprise/site/area/line/cell over mappings whenever the hierarchy doesn’t depend on the payload. Mappings are powerful but add a dependency on payload shape.

3. Prefer JSON for Greenfield, SparkplugB for SCADA Bridging

Section titled “3. Prefer JSON for Greenfield, SparkplugB for SCADA Bridging”

json is simpler and works with any downstream consumer. Use sparkplugb only when you must interoperate with an existing SparkplugB ecosystem (Ignition, HiveMQ Edge, etc.).

Most MQTT writers require the topic to be set explicitly. After the UNS connector, use a Reshape step that promotes _uns_topic to the publish topic field:

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

Pick an ISA-95 schema once for the whole organization and apply it consistently across all sites and lines. Schema drift across Meddle deployments creates incompatible topic trees.

ModbusReader → Reshape → UNS → Reshape → MqttV5Writer
  1. ModbusReader: Pulls raw register data from a PLC
  2. Reshape: Renames raw addresses to human-readable keys
  3. UNS: Decorates with _uns_topic
  4. Reshape: Promotes _uns_topic to the MQTT topic field
  5. MqttV5Writer: Publishes to a cloud broker, organized under the UNS
OpcuaReader → Predictive → UNS (sparkplugb) → MqttV5Writer
  1. OpcuaReader: Aggregates from multiple OPC UA endpoints
  2. Predictive: Adds trend/RUL/health-score
  3. UNS: Wraps the enriched payload in a SparkplugB envelope
  4. MqttV5Writer: Publishes to a SparkplugB-capable broker (e.g., HiveMQ Edge → Ignition)
[Site A pipeline] ─┐
[Site B pipeline] ─┼─→ UNS → InfluxDb2Writer
[Site C pipeline] ─┘

Each site pipeline labels its data with its own UNS configuration, ensuring downstream consumers can disambiguate by topic alone.

  • MQTT v5 - Publish UNS-decorated payloads to a broker
  • MQTT v3 - Legacy MQTT publishing
  • Reshape - Promote _uns_topic to the writer’s topic field
  • Filter - Filter by _uns_topic patterns
  • Router - Branch routing based on the UNS hierarchy