Docs/Timebase Components/Sparkplug B Bridge Plugin
BridgeReference

Sparkplug B Bridge Plugin

The Sparkplug B Bridge publishes Atlas values to an MQTT broker as a proper Sparkplug session, so they arrive in a Sparkplug UNS with birth certificates, aliases and death handling.

Updated 4 Nov 2026Timebase Docs

Overview#

The Sparkplug B Bridge subscribes to Atlas value-hub slices and publishes them to an MQTT broker as a Sparkplug B session: birth certificates when it connects, aliased data messages on change, and a death certificate if it drops. Use it when the consumer on the other side speaks Sparkplug - Ignition MQTT Engine, Cirrus Link tooling, or the Sparkplug support in HiveMQ and EMQX - rather than plain MQTT.

This is not the MQTT plugin with a different payload. The MQTT Bridge publishes free-form JSON on topics you choose, one message per row, and holds no state. Sparkplug is a stateful protocol: the topic namespace is fixed by the specification, payloads are protobuf, metrics are declared once and then referenced by number, and the broker holds a death certificate on your behalf. Pick this plugin when you need that contract, and the MQTT plugin when you do not.

Templates written before Timebase 2.0 must be updated. The tokens were previously written in curly braces with flat names - {instanceUri}, {branch} and so on. Those are no longer recognised, and a destination still carrying one is reported as a configuration error at load and refuses to resolve, rather than being passed through as text. Rewrite them as [instance.uri], [property.uri], [type.uri], [subscription.branch], [subscription.domain] and [subscription.name].

Configuration#

A Sparkplug egress instance is one entry in the host's Bridges dictionary.

The Sparkplug B target's Settings tab in Bridge: the broker fields, Group ID, Edge Node ID, Retain births and QoSThe Sparkplug B target's Settings tab in Bridge: the broker fields, Group ID, Edge Node ID, Retain births and QoS

{
  "Name": "line1-uns",
  "Type": "SparkplugB",
  "Enabled": true,
  "Host": "localhost",
  "Port": 1883,
  "ClientId": "timebase-bridge",
  "Username": "",
  "Password": "",
  "UseTls": false,
  "KeepAlive": "00:00:15",
  "GroupId": "Timebase",
  "EdgeNodeId": "bridge",
  "RetainBirths": false,
  "Qos": "AtLeastOnce",
  "Subscriptions": {
    "fillers-live": {
      "Branch": "main",
      "Domain": "default",
      "Mode": "Type",
      "TypeUri": "urn:demo:Filler",
      "EmitFullRow": true,
      "DeviceId": "[instance.uri]"
    }
  }
}

Connection and identity options#

Field Required Default Notes
Type Yes - Must be SparkplugB
Host Yes - Broker hostname or IP
Port No 1883 1883 plain, 8883 TLS
ClientId, Username, Password No - Leave blank for an anonymous broker
UseTls No false Connect over TLS
KeepAlive No 00:00:15 Also bounds how quickly the broker publishes this node's death certificate after an ungraceful drop
GroupId Yes - Sparkplug Group ID, the top of the topic namespace. Fixed for the instance's lifetime
EdgeNodeId Yes - Sparkplug Edge Node ID. Fixed for the instance's lifetime
RetainBirths No false Retain the birth messages so a late consumer sees the current model. Data messages are never retained
Qos No AtLeastOnce QoS for published messages. The death certificate is always QoS 1
Subscriptions Yes - At least one named subscription

Subscription options#

Field Required Default Notes
Branch, Domain Yes - The Atlas world to subscribe to
Mode Yes - Instance, InstanceProperty, Type, TypeProperty or TypeDescription
TypeUri, InstanceUri, PropertyUri Mode-dependent - As required by the mode. TypeDescription needs TypeUri only. See Subscription Modes
EmitFullRow No true Leave this on. A birth certificate has to declare the complete metric set, so a delta-only stream would birth a partial device
EnumValues No Key What an enumeration-typed property's value is written as: Key (the key Atlas stores, the default), Name (the member's name) or Label (its label, falling back to the name). In Name or Label mode the column arrives as a string. See Enumeration values. In Name or Label mode the metric is a String metric.
DeviceId No blank Sparkplug Device ID. Blank means [instance.@uri] for a Values subscription and [type.@name] for a Type description subscription. Tokens: [instance.uri], [property.uri], [type.uri], [subscription.branch], [subscription.domain], [subscription.name], plus [instance.label], [type.label], [property.label], [instance.<property>] and [instance.<compound>.<leaf>], which are read from the Atlas model. See Tokens from the Atlas model
Important

with Mode set to Type or TypeProperty, keep an instance token ([instance.uri], [instance.label] or an instance property) in DeviceId. A fixed value collapses every instance onto a single Sparkplug Device, and they overwrite each other's metrics.

Note

every substituted value in the Device ID is made legal for the Sparkplug topic: /, +, #, % and control characters are percent-encoded, so the label Pump 1/A+B# becomes Pump 1%2FA%2BB%23. The text you typed in the template is never changed. See Values are made legal for the sink for the full table. A Device ID built from a value that contains / changes on upgrade, because that value is now encoded.

Note

a Device ID is an identity on the broker, so prefer model values that do not change. A property value that changes at runtime moves the device to a different Device ID from the next row on. When a token in DeviceId has no value yet, the row is held instead of dropped and published in its original order once the value is known. Past Queue:HeldRowsPerInstance (default 1000) the oldest held row is dropped and one warning per hold is logged. See Rows wait for their destination.

How Atlas maps onto Sparkplug#

Timebase Sparkplug
One plugin instance One Edge Node (GroupId + EdgeNodeId)
One Atlas instance it publishes One Device under that node
One property or column One metric

A compound property becomes a metric folder, so flowRate.Data.Value is published as flowRate/Data/Value.

To run several Edge Nodes, configure several Bridges entries. One instance is deliberately one node.

Topic namespace#

Fixed by the specification, not something you configure:

spBv1.0/{GroupId}/NBIRTH/{EdgeNodeId}                 published
spBv1.0/{GroupId}/NDEATH/{EdgeNodeId}                 published, as the MQTT Will
spBv1.0/{GroupId}/DBIRTH/{EdgeNodeId}/{DeviceId}      published
spBv1.0/{GroupId}/DDATA/{EdgeNodeId}/{DeviceId}       published
spBv1.0/{GroupId}/NCMD/{EdgeNodeId}                   subscribed

Session behaviour#

  • On connect, the plugin opens a new session, registers the death certificate as its MQTT Will, and publishes a node birth carrying the session number and a rebirth control metric.
  • The first row for a device publishes a device birth declaring every metric with its name, a numeric alias, its datatype and its value.
  • Later rows publish data by alias only, reporting by exception. An unchanged value is omitted; a genuine null is sent as null rather than skipped.
  • Sequence numbers roll 0 to 255 across every message from the node, so a consumer can detect a gap. A separate session number pairs each birth with its matching death.
  • An ungraceful drop leaves the broker to publish the death certificate. A graceful stop suppresses it, which is the standard Sparkplug behaviour and means a planned shutdown does not look like a failure.
  • Re-subscribing re-arms every device's birth from a fresh value-hub snapshot without starting a new session.

Rebirth on command#

The node subscribes to its command topic. When a primary application sets Node Control/Rebirth, the plugin re-issues its node birth and then a device birth for every device.

Every metric that has been born is cached with its name, alias, datatype and last value, so a rebirth re-declares the full model immediately rather than waiting for Atlas to push values again. The rebirth is handled on the same gate as normal publishing, so a command arriving mid-stream interleaves without breaking the sequence numbering.

Only Node Control/Rebirth is honoured. Other node commands are ignored, and device-level commands are out of scope: this is a read-only egress and never writes back into Atlas.

Type mapping#

Atlas column Sparkplug datatype
Double Double
Float Float
Long Int64
Int Int32
Bool Boolean
String String
DateTime DateTime, as Unix milliseconds

Non-finite doubles and floats - NaN, Infinity - are published as null, because Sparkplug has no representation for them.

Type description subscriptions#

A subscription with Mode set to TypeDescription publishes a type's compiled description instead of values. It needs a TypeUri, and nothing else from the URI row. For when Bridge sends a description, and the document it contains, see Type Description Subscriptions.

  • One device row per description. The device carries a single metric, a String named TypeDescription, whose value is the whole JSON document described in the overview. A consumer reads the string and parses it.
  • The device ID resolves without an instance. A blank DeviceId means [type.@name] for this kind (and [instance.@uri] for a Values subscription), so each described type becomes its own device. [type.uri], [type.name] and the [subscription.*] tokens work as usual. An [instance.*] token in DeviceId is a validation error when the configuration loads, because a description has no instance.
  • The first description is the device's birth, and each later one is a data message. The first description is published as the device's DBIRTH carrying the TypeDescription metric, and each later description is a DDATA carrying the new document.
  • A description is published at the start and on a shape change. A deleted type publishes a final one with "deleted": true and an empty schema.
  • EmitFullRow and IncludeWatermarks are ignored, and backfill does not apply.
"type-descriptions": {
  "Branch": "main",
  "Domain": "default",
  "Mode": "TypeDescription",
  "TypeUri": "urn:demo:Filler"
}

Configuration warnings#

The plugin checks its configuration at load and on every reload, and logs a warning without refusing to start, for:

  • a blank or wildcarded GroupId or EdgeNodeId
  • a /, + or # in a DeviceId outside a token, which would break the topic
  • a Type or TypeProperty subscription whose DeviceId has no [instance.*] token ([instance.label] or an instance property counts)
  • a mode missing the URI it requires
  • an [instance.*] token in the DeviceId of a TypeDescription subscription

These are warnings rather than errors so a single bad subscription does not take down the rest of the host. Check the log after a configuration change.

Verifying it works#

Point the instance at a broker and watch the Sparkplug namespace:

mosquitto_sub -h localhost -t 'spBv1.0/#' -v

You should see a node birth, then a device birth per Atlas instance, then data messages as values change. An Ignition MQTT Engine or any Tahu-based decoder will reconstruct the values with their types, timestamps and null semantics intact.

Limitations#

  • One Edge Node per instance. Configure multiple instances to publish several nodes.
  • Egress only. Nothing written on the Sparkplug side reaches Atlas.
  • The Sparkplug hierarchy is explicit. GroupId, EdgeNodeId and DeviceId are configuration. Deriving them from an ISA-95 equipment model in Atlas is not available.
esc
↑↓ navigate · ↵ open · ⌘K anywhere