all posts

scada

Sparkplug B without the buzzword soup

A practical explainer of MQTT Sparkplug B — the namespace, device birth/death certificates, the session-state model, and what it's actually solving. For engineers who already know MQTT.

By Akshay Sarode· September 30, 2025· 8 min readmqttsparkplug-bscadaplc

Lead

If you already know MQTT — broker, topic, publish, subscribe, QoS — Sparkplug B is one weekend of reading. If you don't know MQTT, read this OPC UA explainer first, come back, and the contrast will land harder.

This post strips out the marketing layer. Sparkplug B is a specification on top of MQTT that defines four things: a topic namespace, a payload format, a session-state model with birth/death messages, and a small set of message types. That's it. Everything else "Sparkplug B" implies — Cirrus Link gateways, Ignition modules, "industrial pub/sub" decks — is implementation built on those four things.

By the end of this post you'll know what Sparkplug B actually is, what problem it solves that bare MQTT doesn't, and when you'd choose it over OPC UA.

What problem Sparkplug B solves

Bare MQTT has two design holes for industrial use:

  1. No standard topic structure. Every vendor invents their own. acme/plant1/line2/sensor3/temp works fine until you need to integrate two vendors' systems and they used different conventions.
  2. No session-state model. When a publisher disconnects, subscribers don't know whether the data is stale, the device is offline, or the network just blipped. You can fake it with retained messages and Last Will, but every vendor fakes it differently.

Sparkplug B fixes both. It's a spec from the Eclipse Foundation maintained by the Eclipse Sparkplug working group, with reference implementations from Cirrus Link, HiveMQ, and others.

The namespace

Every Sparkplug B topic has the exact same five-part shape:

spBv1.0/<group_id>/<message_type>/<edge_node_id>/<device_id>
  • spBv1.0 — the literal version prefix. Always.
  • group_id — your logical grouping. A plant, a region, a customer.
  • message_type — one of: NBIRTH, NDEATH, DBIRTH, DDEATH, NDATA, DDATA, NCMD, DCMD, STATE. Six characters. Read on.
  • edge_node_id — the gateway/edge device's name (e.g. gateway-line-2).
  • device_id — the specific device behind the gateway (e.g. plc-press-3). Optional for node-level messages.

Concrete example:

spBv1.0/plant_munich/DBIRTH/gateway-line-2/plc-press-3
spBv1.0/plant_munich/DDATA/gateway-line-2/plc-press-3
spBv1.0/plant_munich/DDEATH/gateway-line-2/plc-press-3

Any subscriber that knows the namespace can subscribe to spBv1.0/plant_munich/DDATA/+/+ and get every device data message in the plant. That's the magic — once everyone agrees on the namespace, integration is wildcard-subscribe.

The message types you actually need

Six message types matter in 90% of deployments:

TypeMeaningWhen sent
NBIRTHEdge node coming onlineFirst message after the gateway connects to the broker
NDEATHEdge node going offlineSet as MQTT Last Will when the gateway connects
DBIRTHDevice coming onlineWhen a specific device behind the gateway is registered
DDEATHDevice going offlineWhen a specific device drops off
NDATAEdge-node-level dataTelemetry from the gateway itself
DDATADevice-level dataTelemetry from a specific device

The other three (NCMD, DCMD, STATE) are for command-and-control and primary-host coordination — useful, but not required to start.

Birth and death certificates

This is the part that makes Sparkplug B more than "MQTT with conventions."

NBIRTH / DBIRTH — birth certificates

When a gateway connects to the broker, the first thing it publishes (after the initial CONNECT) is an NBIRTH message. The NBIRTH payload contains the complete current state of every metric the gateway will subsequently publish — every variable, with its name, type, current value, and a unique alias (a small integer).

Then for each device behind the gateway, the gateway publishes a DBIRTH with the same complete-snapshot semantics for that device's metrics.

Why this matters: any subscriber that connects after the gateway is already running has missed the BIRTH messages. To handle this, brokers retain BIRTH messages, and Sparkplug-aware clients (called Primary Application or Edge Compute Nodes in the spec) re-issue REBIRTH commands on connect to force gateways to republish their BIRTHs. The result: every subscriber can reconstruct the full state of every device, on demand.

NDEATH / DDEATH — death certificates

When the gateway connects to the broker, it sets NDEATH as its MQTT Last Will. If the gateway disconnects ungracefully (network fail, power off, crash), the broker automatically publishes the NDEATH on its behalf. Subscribers see the NDEATH and immediately know the gateway is gone. No polling, no timeout heuristics — the broker tells you the moment TCP closes.

Same pattern for DDEATH when an individual device behind the gateway drops.

This solves the bare-MQTT problem of "is this data stale or is the publisher offline?" — in Sparkplug B, you know.

Aliases — the bandwidth optimization that confuses everyone

In the BIRTH, every metric is published with its full name (e.g. Production/Line2/Press3/Temperature) and an integer alias. In subsequent NDATA/DDATA messages, the metric is referenced only by alias to save bandwidth. This is a real optimization — names can be 50+ bytes, aliases are 2 bytes — but it means a subscriber that joins late and missed the BIRTH cannot interpret the DATA messages until it has the BIRTH, hence the rebirth-on-connect dance.

If you've ever debugged a Sparkplug client that "shows nothing" after restart, this is almost always why. Issue a rebirth command. The BIRTH comes through. Now the DATA makes sense.

The payload — Protobuf

Sparkplug B payloads are Google Protocol Buffers, not JSON. The .proto schema is in the spec. A typical payload contains:

  • A timestamp (ms since epoch)
  • A sequence number (for ordering and gap detection)
  • A list of metrics, each with: name (only on BIRTH), alias, datatype, value, optional metadata

You don't write the protobuf yourself — every Sparkplug client library handles encoding. But it's worth knowing the wire format isn't JSON, because debugging by tcpdump requires a Sparkplug-aware decoder. Wireshark with the Sparkplug dissector is the practical tool.

When to use Sparkplug B over OPC UA

Both are industrial protocols. Both are session-aware. Both work. The choice usually comes down to:

Use caseChoose
Direct read from PLCs / CNC / DCSOPC UA — every modern PLC speaks it
Many edge gateways pushing to one or many cloud subscribersSparkplug B — pub/sub model fits
Bandwidth-constrained networks (cellular, satellite)Sparkplug B — alias compression and birth/death are bandwidth-efficient
Mixed-vendor brownfield with existing OPC UA serversOPC UA — don't add a layer if you don't need to
New edge deployments designed cloud-firstSparkplug B — modern industrial pub/sub default
You need methods, alarms, historical access, deep address-space typingOPC UA — Sparkplug B doesn't replicate those

In practice, modern plants often run both: OPC UA inside the plant for direct PLC integration, Sparkplug B over MQTT for cloud-bound telemetry with multiple subscribers. Sutrace ingests both natively, which is why we don't ask customers to pick.

Brokers and gateways

You need three things to run Sparkplug B:

  1. An MQTT broker. HiveMQ (commercial), EMQX (open source), Mosquitto (open source) all work. The broker doesn't need to be Sparkplug-aware — Sparkplug runs on top of standard MQTT 3.1.1 / 5.0.
  2. An edge gateway. This is the device that talks to PLCs (over Modbus, OPC UA, EtherNet/IP) and re-publishes the data as Sparkplug. Cirrus Link Edge Modules for Ignition are the most-deployed; HiveMQ Edge is a strong open option; many smaller hardware gateways (Opto 22 groov, Moxa) ship Sparkplug too.
  3. A Primary Host or subscriber. SCADA, observability platform, or your own service. Sutrace works as a Sparkplug subscriber out of the box.

For a hands-on HMI workflow that pairs naturally with Sparkplug B, FlowFuse's HMI guide walks through Node-RED as the gateway side.

What Sparkplug B is not

Three common misconceptions worth flushing:

  • Sparkplug B is not a replacement for OPC UA. It doesn't browse address spaces, it doesn't define methods, it doesn't do alarms-and-conditions natively. It's a transport-and-state layer.
  • Sparkplug B is not just MQTT with conventions. The birth/death model and the rebirth dance are real protocol semantics, not a topic-naming guide.
  • Sparkplug B does not require Cirrus Link or Ignition. It's an open Eclipse spec. Any compliant client works.

Where Sutrace fits

Sutrace is a Sparkplug B subscriber that ingests every NDATA/DDATA from your namespace, tracks BIRTH/DEATH state per node and device, and presents it as time-series, alarms, and dashboards alongside your OPC UA data, OTel traces, Prometheus metrics, and uptime checks. Same dashboard, all signal types. EU residency, no per-tag pricing. See /pricing.

Further reading

The deeper point

Industrial pub/sub finally has a default. Before Sparkplug B, every vendor invented its own MQTT topic structure and its own out-of-band state model, and integrating two vendors meant writing glue code. Sparkplug B is just barely opinionated enough — namespace, payload, state — to make integration a wildcard subscribe. That's the whole pitch. It's not a buzzword; it's a small spec that solved two real problems.

The buzzword soup happens when vendors wrap "Sparkplug B" around their full-stack offering. The protocol itself is simple. Read the spec, write a client, ship.