Sparkplug B
Sparkplug B is MQTT — it does not replace the CMS MQTT integration, it is a specification that runs on top of it (MQTT 3.1.1), filling in what MQTT deliberately leaves open:
Plain MQTT (MQTT_TOPIC_SUBSCRIBER) | Sparkplug B (SPARKPLUG_SUBSCRIBER) | |
|---|---|---|
| Topic | you type it (plant/line1/oven/temp) | fixed namespace spBv1.0/{group}/{type}/{edge}/{device} |
| Payload | whatever the source sends (JSON, text, number) | protobuf, with type and timestamp per metric |
| Tag discovery | someone documents it and you register it | arrives in the BIRTH, when the device connects |
| Device went down | silence, indistinguishable from “nothing changed” | DEATH published by the broker, right away |
| Lost message | invisible | gap in the sequence number (seq) |
| Writing back | publish to an agreed topic | standardized NCMD/DCMD |
Use Sparkplug when the other side speaks Sparkplug — Ignition, HiveMQ Edge, Kepware, Cirrus Link, Opto 22. A sensor publishing JSON to a topic is still a plain MQTT case.
Connection
No new connection type: use the same MQTT External Connection of the broker (host, port, credentials). There is a single extra, optional field:
Primary Host ID — when filled in, the CMS announces itself as the namespace Host Application:
it publishes spBv1.0/STATE/{hostId} as ONLINE on connect and registers OFFLINE as its Last Will.
Many gateways use that signal to switch to store-and-forward when the host goes away.
Only fill in the Primary Host ID if no other host (an Ignition, for example) already plays that role with the same ID in your namespace. Left empty, the CMS is just another subscriber and does not interfere.
Collection — SPARKPLUG_SUBSCRIBER
The Collection points at a slice of the namespace, not at a topic:
| Field | Purpose |
|---|---|
| Group ID | groups the edge nodes of a plant/area |
| Edge Node ID | the gateway |
| Device ID | optional — empty collects from every device of the node |
| Include edge node metrics | also brings NBIRTH/NDATA (gateway CPU, uptime) |
| Request Rebirth | on connect, asks the node to re-announce its metrics |
Each NDATA/DDATA becomes one message carrying the metrics that changed (report by exception,
as the spec itself works):
{ "grupo": "Fabrica01", "edgeNode": "GW_L1", "device": "Forno_A", "tipo": "DDATA", "seq": 42,
"timestamp": "2026-08-21T19:00:00.000Z", "recebidoEm": "2026-08-21T19:00:03.120Z",
"metricas": { "Temperatura": 812.5, "Status": "RUN" } }timestamp is the source stamp (when the device measured), not the arrival time — one of the
reasons Sparkplug exists, and what lets you correctly order data delayed by the network.
Discover tags
The Discover tags button on the Collection screen lists the metric catalog of each device: name, data type and the last announced value. The catalog is stored, so the screen opens with it even when the device is offline; the button publishes a Rebirth and refreshes it.
Rebirth and ordering
After the BIRTH, metrics travel with the numeric alias only — without the catalog there is no way
to know what alias 7 is. That is why the CMS requests a Rebirth on connect, when data arrives from a
node with no known BIRTH, and when seq jumps (a sign of a lost message). Requests are throttled to
one every 30s per node.
An edge node (NDEATH) or device (DDEATH) going down marks the collection as disconnected and
raises the Sparkplug Device Offline alert — distinct from MQTT Connection Lost, which is the
broker going down.
Float in Sparkplug is 32-bit: a value of 33.3 comes back as 33.29999923706055. For exact decimal
precision use Double metrics on the gateway.
Delivery — SPARKPLUG_CMD
Writes metrics to the device by publishing DCMD (Device ID filled in) or NCMD (empty). The
mapping mirrors the OPC UA Write Tags: each row links an alias expected in the payload to a
device metric, with the data type used for encoding.
| Row field | Purpose |
|---|---|
| Alias | key in the message payload |
| Metric | name on the device (command target) |
| Type | Boolean, Int32, Int64, Float, Double, String or DateTime |
| Default value | used when the message does not carry the alias |
| Required | with no value (and no default), the delivery fails and nothing is published |
Aliases are resolved against the already transformed payload — if there is a Transformer on the definition or on the forwarding, its output is what counts. (OPC UA/Modbus Write Tags do the opposite: they read the original payload.)
The stored return value is {"topico": "spBv1.0/.../DCMD/...", "metricas": N}.
Testing without a real gateway
The repository ships an edge node simulator that publishes a full lifecycle and answers commands:
docker compose -f docker-compose.test.yml up -d mosquitto # local broker (optional)
node tools/sparkplug-simulador/edge-node.mjs --broker mqtt://localhost:18830
node tools/sparkplug-simulador/edge-node.mjs --morrer 30 # publishes NDEATH after 30s