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.
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:
- No standard topic structure. Every vendor invents their own.
acme/plant1/line2/sensor3/tempworks fine until you need to integrate two vendors' systems and they used different conventions. - 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:
| Type | Meaning | When sent |
|---|---|---|
| NBIRTH | Edge node coming online | First message after the gateway connects to the broker |
| NDEATH | Edge node going offline | Set as MQTT Last Will when the gateway connects |
| DBIRTH | Device coming online | When a specific device behind the gateway is registered |
| DDEATH | Device going offline | When a specific device drops off |
| NDATA | Edge-node-level data | Telemetry from the gateway itself |
| DDATA | Device-level data | Telemetry 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 case | Choose |
|---|---|
| Direct read from PLCs / CNC / DCS | OPC UA — every modern PLC speaks it |
| Many edge gateways pushing to one or many cloud subscribers | Sparkplug 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 servers | OPC UA — don't add a layer if you don't need to |
| New edge deployments designed cloud-first | Sparkplug B — modern industrial pub/sub default |
| You need methods, alarms, historical access, deep address-space typing | OPC 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:
- 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.
- 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.
- 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
- Eclipse Sparkplug specification — the actual spec
- Eclipse Tahu — reference implementations and Wireshark dissector
- HiveMQ on Sparkplug — practical multi-part series
- OPC UA explained for people who only know REST — the companion explainer
- Industrial monitoring for the mid-market
- Sutrace as an Ignition SCADA alternative — Ignition's Sparkplug story is mature
- FlowFuse on Node-RED HMI — the open-source pairing
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.