Sparkplug B Driver — Technical Reference
The Sparkplug B driver in PlantPulse Edge is a native Java implementation of an MQTT 3.1.1 client plus a Sparkplug B payload Protobuf decoder. It has no external paho/tahu jar dependencies, so it works out of the box when deployed as a single jar.
Source: plantpulse.driver.protocol.sparkplug.*
| Class | Responsibility |
|---|---|
SparkplugBDriver | ProtocolDriver implementation — connect/read/write/close, alias and metric cache |
SparkplugMqttClient | Self-contained MQTT 3.1.1 client (CONNECT / SUBSCRIBE / PUBLISH / PINGREQ) |
SparkplugBPayload | Sparkplug B payload protobuf wire encoder/decoder (varint / fixed32 / fixed64 / length-delim) |
Operating Flow
broker ──PUBLISH──> SparkplugMqttClient ──onMessage(topic, payload)──> SparkplugBDriver
│
├─ topic 파싱 (spBv1.0/G/<msgType>/edge[/dev])
├─ Payload 디코드 → metric list
├─ NBIRTH/DBIRTH 시 alias→name 등록
└─ metricCache[edge[/dev]/name] = formatValue()
PlantPulse 폴러 ──read(addr)──> metricCache[addr] (in-memory hit, 외부 RTT 없음)
write(addr, value) ──encode──> NCMD/DCMD publish → broker
Options
| Key | Default | Description |
|---|---|---|
group-id | default | Sparkplug Group ID. Used as part of the wildcard when subscribing. The driver filters out messages whose group does not match. |
edge-node-id | + | Edge node filter. + means all nodes in the group. |
keep-alive | 60 | MQTT keep-alive in seconds. 0 disables ping. |
connect-timeout | 5000 | TCP connection timeout in ms. |
The subscribe topic follows the spBv1.0/<group-id>/+/<edge-node-id>/# pattern (+ is the message_type, # is the device-part wildcard).
Sparkplug Datatype Mapping
SparkplugBPayload.Metric.formatValue() converts each wire datatype to an appropriate String.
| Sparkplug datatype | Storage field | format result |
|---|---|---|
| Int8 / Int16 / Int32 | intValue (varint) | signed int (Integer.toString) |
| UInt8 / UInt16 / UInt32 | intValue | unsigned (& 0xFFFFFFFFL) |
| Int64 / DateTime | longValue | signed long |
| UInt64 | longValue | Long.toUnsignedString |
| Float | floatValue (fixed32) | Float.toString |
| Double | doubleValue (fixed64) | Double.toString |
| Boolean | booleanValue (varint) | "true" / "false" |
| String / Text / UUID | stringValue | UTF-8 as-is |
| Bytes / File | bytesValue | hex string |
A is_null=true metric evaluates to an empty string.
Alias Learning from NBIRTH / DBIRTH
The core of Sparkplug efficiency: BIRTH transmits metric name and alias pairs, after which NDATA/DDATA send the alias only.
1) edge1 의 NBIRTH:
metric { name="Temperature" alias=7 datatype=Float float_value=23.5 }
→ driver: aliasByPrefix.put("edge1", {7 → "Temperature"})
metricCache.put("edge1/Temperature", "23.5")
2) edge1 의 NDATA (이름 생략):
metric { alias=7 datatype=Float float_value=24.1 }
→ driver: name 없음 → alias=7 → "Temperature"
metricCache.put("edge1/Temperature", "24.1")
If NDATA arrives while NBIRTH is missing, the alias lookup fails and the message is ignored (the cache is not updated). It waits until the publisher reissues NBIRTH after a broker restart.
NDEATH / DDEATH
When an edge node or device shuts down, the broker publishes NDEATH/DDEATH via LWT (Last Will and Testament). The driver clears only the alias map and keeps the cache — the last known value is preserved as the polling result (the PlantPulse monitor keeps displaying the lastValue UI as-is).
Sending NCMD / DCMD
write(ProtocolAddress) branches automatically based on the number of address segments.
| address | publish topic | Note |
|---|---|---|
edge1/SetPoint | spBv1.0/<group>/NCMD/edge1 | Node-level command |
edge1/dev1/SetPoint | spBv1.0/<group>/DCMD/edge1/dev1 | Device-level command |
Phase 1 publishes all commands with the Sparkplug datatype String — the receiving side (Tahu / Ignition) converts them to the metric's defined datatype for processing.
Limitations and Future Work
- TLS / authentication — Currently plaintext and anonymous only. username/password and mTLS options to follow.
- QoS — All publish/subscribe use QoS 0. Sparkplug recommends QoS 1 for NBIRTH only, with QoS 0 for NDATA/DDATA.
- DataSet / Template metric — The wire form is preserved as raw bytes (
datasetRaw/templateRaw), but decode/format is not implemented. - Properties / Metadata — Raw bytes are preserved only; decode is not implemented.
- bdSeq — NBIRTH bdSeq matching and NDEATH validation are not implemented.
Test Location
test/java/plantpulse/driver/protocol/sparkplug/SparkplugBDriverTest.java — 27 tests.
- Initial state / option parsing
- Safe read/write when not connected
- BIRTH alias registration, NDATA alias-only resolution, group mismatch blocking, DEATH alias cleanup
- formatValue accuracy per datatype (Int32 / Float / Double / Boolean / String / UInt32 / UInt64 / is_null)
- Payload encode→decode round-trip
- MQTT remaining length / utf8 wire utility
- safeId clientId sanitization