Skip to main content

ControlForge Sparkplug B Protocol Guide

James M. Belcher Founder, JMB Technical Services LLC May 2026 | ControlForge v1.0.865


1. Architecture Overview

ControlForge implements Sparkplug B v3.0 — both the edge-node (publisher) and the host-application (subscriber) roles — callable directly from IEC 61131-3 Structured Text. No Ignition modules, no Java dependencies, no external gateway software. ControlForge connects to any MQTT 3.1.1 broker (Mosquitto, EMQX, HiveMQ Cloud, AWS IoT) and speaks native Sparkplug B with Google Protocol Buffer payloads, the full birth/death lifecycle, metric aliases for bandwidth-efficient NDATA, PropertySet metadata, quality flags, devices, primary-host STATE awareness, and seq-gap-driven rebirth requests.

RoleFunctionsUse Case
Edge NodeSPARKPLUG_NODE_CREATE / SPARKPLUG_NODE_BIRTH / SPARKPLUG_NODE_DATAPublish PLC data to SCADA via Sparkplug-aware infrastructure
MetricsSPARKPLUG_METRIC_ADD / SPARKPLUG_METRIC_SET / SPARKPLUG_METRIC_GETRegister and update named data points; NBIRTH+alias / alias-only NDATA
PropertiesSPARKPLUG_METRIC_SET_UNIT / _SET_RANGE / _SET_DESCPropertySet metadata (engUnit / engHigh / engLow / description) — Ignition tag browser displays it
Quality flagsSPARKPLUG_METRIC_SET_NULL / _MARK_HISTORICAL / _MARK_TRANSIENTis_null suppresses value; is_historical / is_transient flag store-and-forward replays
Extended typesSPARKPLUG_METRIC_ADD_TEXT / _BYTES / _*_ARRAYText, UUID, Bytes, File, DateTime, and all 13 array types from spec §16
DevicesSPARKPLUG_DEVICE_ADD / _BIRTH / _DATA / DCMD_*Sub-devices under one edge node (DBIRTH/DDEATH/DDATA/DCMD)
CommandsSPARKPLUG_CMD_SUBSCRIBE / _GET / _CLEARReceive NCMD writes from SCADA; Node Control/Rebirth handled automatically
Host STATE awarenessSPARKPLUG_NODE_SET_PRIMARY_HOST / _HOST_ONLINESuspend NDATA when configured primary host is offline; auto-rebirth on host online
Host ApplicationSPARKPLUG_HOST_CREATE / _START / _GET_METRIC / _PUBLISH_NCMDControlForge as Sparkplug consumer — subscribe to remote EoNs, read metrics, push NCMD/DCMD

All functions are controlled entirely from IEC 61131-3 Structured Text in ControlForge's browser-based IDE.

System Diagram

Sparkplug B Concepts

ConceptDescription
Group IDLogical grouping (e.g., Plant, Building1) — first level of the topic namespace
Edge Node IDUnique identifier for this ControlForge instance within the group
MetricA named data point (temperature, pressure, motor state) with type, value, and timestamp
NBIRTHNode Birth certificate — full metric catalog published on connect
NDEATHNode Death certificate — pre-registered LWT, published by broker on disconnect
NDATANode Data — incremental update with only changed metrics
NCMDNode Command — write-back from SCADA to edge node
Sequence Number0-255 monotonic counter in every message — consumers detect gaps as missed data

Sparkplug B Topic Namespace

spBv1.0/{group_id}/{message_type}/{edge_node_id}

Examples:
spBv1.0/Plant/Line1/NBIRTH/ControlForge-Edge1 ← birth certificate
spBv1.0/Plant/Line1/NDEATH/ControlForge-Edge1 ← death certificate (LWT)
spBv1.0/Plant/Line1/NDATA/ControlForge-Edge1 ← data updates
spBv1.0/Plant/Line1/NCMD/ControlForge-Edge1 ← commands from SCADA

Why Sparkplug over raw MQTT? Raw MQTT requires every subscriber to know every topic. Sparkplug adds structure: a birth certificate defines every metric (name, type, initial value), consumers auto-discover tags, the death certificate handles ungraceful disconnects, and sequence numbers detect data loss. Ignition SCADA discovers and displays all ControlForge tags automatically — zero configuration on the Ignition side.


2. Node Lifecycle

2.1 SPARKPLUG_NODE_CREATE -- Create Edge Node

ok := SPARKPLUG_NODE_CREATE('node1', 'Plant/Line1', 'ControlForge-Edge1',
'tcp://10.0.0.144:1883', 'goplc-sparkplug-1');
ParamTypeRequiredDescription
nameSTRINGYesInstance name (used in all subsequent calls)
groupIDSTRINGYesSparkplug group ID (e.g., Plant/Line1)
edgeNodeIDSTRINGYesUnique edge node identifier
brokerURLSTRINGYesMQTT broker URL: tcp://host:port or ssl://host:port
clientIDSTRINGYesMQTT client ID (must be unique per broker)

Returns TRUE on success. The node is created but not yet connected -- call SPARKPLUG_NODE_CONNECT next.

For authenticated connections, use SPARKPLUG_NODE_CREATE_AUTH:

(* No authentication *)
ok := SPARKPLUG_NODE_CREATE('node1', 'Plant', 'Edge1',
'tcp://broker:1883', 'goplc-sp-1');

(* With authentication — use SPARKPLUG_NODE_CREATE_AUTH *)
ok := SPARKPLUG_NODE_CREATE_AUTH('node1', 'Plant', 'Edge1',
'tcp://broker:1883', 'goplc-sp-1',
'goplc_user', 's3cretP@ss');

(* TLS + authentication *)
ok := SPARKPLUG_NODE_CREATE_AUTH('node1', 'Plant', 'Edge1',
'ssl://broker:8883', 'goplc-sp-1',
'goplc_user', 's3cretP@ss');

Client ID uniqueness: If two nodes connect with the same client ID, the broker disconnects the first one. Use a unique ID per ControlForge instance — hostname or MAC address works well.

2.2 SPARKPLUG_NODE_CONNECT / Disconnect / IsConnected

(* Connect to broker — registers NDEATH as LWT *)
ok := SPARKPLUG_NODE_CONNECT('node1');

(* Check connection state *)
IF SPARKPLUG_NODE_IS_CONNECTED('node1') THEN
(* publish metrics *)
END_IF;

(* Graceful disconnect — triggers NDEATH from broker LWT *)
SPARKPLUG_NODE_DISCONNECT('node1');

SPARKPLUG_NODE_CONNECT establishes the MQTT connection and registers the NDEATH message as the broker's Last Will and Testament (LWT). If ControlForge crashes or loses network, the broker publishes NDEATH automatically — Ignition and other consumers see the node go offline immediately.

2.3 SPARKPLUG_NODE_DELETE / SPARKPLUG_NODE_LIST

(* Remove a node *)
SPARKPLUG_NODE_DELETE('node1');

(* List all Sparkplug nodes *)
names := SPARKPLUG_NODE_LIST();
(* Returns: 'node1,node2' — comma-separated string *)

3. Metrics

3.1 SPARKPLUG_METRIC_ADD -- Register a Metric

(* Add metrics with initial values — type is auto-detected *)
ok := SPARKPLUG_METRIC_ADD('node1', 'Temperature', 72.5); (* Float *)
ok := SPARKPLUG_METRIC_ADD('node1', 'MotorRunning', TRUE); (* Boolean *)
ok := SPARKPLUG_METRIC_ADD('node1', 'BatchCount', 0); (* Integer *)
ok := SPARKPLUG_METRIC_ADD('node1', 'RecipeName', 'Default'); (* String *)
ParamTypeDescription
nameSTRINGNode instance name
metricNameSTRINGMetric name (appears in Sparkplug birth certificate and SCADA tag browser)
valueANYInitial value — type is inferred (BOOL, INT, REAL, STRING)

Returns TRUE on success. Metrics must be added before calling SPARKPLUG_NODE_BIRTH — the birth certificate includes the complete metric catalog.

Metric naming: Use descriptive, hierarchical names. Ignition displays them as-is in the tag browser. Line1/Motor/Speed is better than N7_0. Sparkplug metric names support / separators — Ignition renders them as a folder tree.

3.2 SPARKPLUG_METRIC_SET -- Update a Metric Value

(* Update metric — marks it as changed for next NDATA *)
ok := SPARKPLUG_METRIC_SET('node1', 'Temperature', 73.1);
ok := SPARKPLUG_METRIC_SET('node1', 'MotorRunning', FALSE);
ok := SPARKPLUG_METRIC_SET('node1', 'BatchCount', 42);
ParamTypeDescription
nameSTRINGNode instance name
metricNameSTRINGMetric name (must have been added with SPARKPLUG_METRIC_ADD)
valueANYNew value

Returns TRUE on success. This does not immediately publish — it marks the metric as changed. Call SPARKPLUG_NODE_DATA to publish all changed metrics in a single NDATA message.

3.3 SPARKPLUG_METRIC_GET -- Read Current Value

temp := SPARKPLUG_METRIC_GET('node1', 'Temperature');
(* Returns: 73.1 *)

running := SPARKPLUG_METRIC_GET('node1', 'MotorRunning');
(* Returns: TRUE *)

Returns the current local value of the metric. This reads from ControlForge's in-memory metric store, not from the broker.

Type-Specific Getters

For type safety, use the typed variants instead of the generic SPARKPLUG_METRIC_GET:

temp := SPARKPLUG_METRIC_GET_REAL('node1', 'Temperature'); (* Returns: 73.1 as REAL *)
count := SPARKPLUG_METRIC_GET_INT('node1', 'BatchCount'); (* Returns: 42 as INT *)
running := SPARKPLUG_METRIC_GET_BOOL('node1', 'MotorRunning'); (* Returns: TRUE as BOOL *)
recipe := SPARKPLUG_METRIC_GET_STR('node1', 'RecipeName'); (* Returns: "Batch-A" as STRING *)

The generic SPARKPLUG_METRIC_GET returns ANY and relies on the runtime to infer the type. The typed variants guarantee the return type and return a default value (0.0, 0, FALSE, or empty string) if the metric is not found.


4. Publishing

4.1 SPARKPLUG_NODE_BIRTH -- Send NBIRTH

ok := SPARKPLUG_NODE_BIRTH('node1');

Publishes the Node Birth Certificate to spBv1.0/{groupID}/NBIRTH/{edgeNodeID}. The birth message contains:

  • All registered metrics with their current values and Sparkplug data types
  • Sequence number reset to 0
  • Timestamp (milliseconds since epoch)

The birth certificate is a retained message — new subscribers (like Ignition connecting later) receive the full metric catalog immediately.

When to send NBIRTH: Call SPARKPLUG_NODE_BIRTH once after connecting and adding all metrics. If Ignition sends a rebirth request via NCMD, call it again to republish the full metric catalog.

4.2 SPARKPLUG_NODE_DEATH -- Send NDEATH

ok := SPARKPLUG_NODE_DEATH('node1');

Publishes the Node Death Certificate to spBv1.0/{groupID}/NDEATH/{edgeNodeID}. This signals an intentional, graceful shutdown. The broker also publishes NDEATH automatically (via LWT) if the connection drops unexpectedly.

Graceful vs. ungraceful: SPARKPLUG_NODE_DISCONNECT triggers the broker's LWT (NDEATH). SPARKPLUG_NODE_DEATH sends it explicitly before disconnecting. Both result in the same NDEATH message reaching consumers — the distinction matters only for timing.

4.3 SPARKPLUG_NODE_DATA -- Send NDATA (Changed Metrics Only)

ok := SPARKPLUG_NODE_DATA('node1');

Publishes an NDATA message containing only metrics that changed since the last NDATA or NBIRTH. This is the primary data publishing function — call it on every scan cycle or at your desired publish rate.

(* Typical scan cycle pattern *)
SPARKPLUG_METRIC_SET('node1', 'Temperature', current_temp);
SPARKPLUG_METRIC_SET('node1', 'Pressure', current_pressure);
SPARKPLUG_METRIC_SET('node1', 'MotorRunning', motor_fb);

(* Publish only changed values *)
SPARKPLUG_NODE_DATA('node1');

If no metrics have changed, SPARKPLUG_NODE_DATA returns TRUE without publishing — no empty messages are sent. The sequence number only increments when a message is actually published.

4.4 SPARKPLUG_GET_SEQ -- Current Sequence Number

seq := SPARKPLUG_GET_SEQ('node1');
(* Returns: 42 — current sequence number (0-255, wraps) *)

Returns the current Sparkplug sequence number. Consumers use this to detect missed messages — a gap in the sequence means data was lost and a rebirth should be requested.


5. Commands (NCMD)

5.1 SPARKPLUG_CMD_SUBSCRIBE -- Listen for SCADA Commands

ok := SPARKPLUG_CMD_SUBSCRIBE('node1');

Subscribes to the NCMD topic: spBv1.0/{groupID}/NCMD/{edgeNodeID}. SCADA systems (Ignition, etc.) publish NCMD messages to write values back to the edge node — setpoints, mode changes, rebirth requests.

5.2 SPARKPLUG_CMD_HAS / CmdGet / CmdClear

(* Check if a command arrived for a specific metric *)
IF SPARKPLUG_CMD_HAS('node1', 'Setpoint') THEN
(* Read the commanded value *)
new_sp := SPARKPLUG_CMD_GET('node1', 'Setpoint');
(* Apply it *)
target_temp := new_sp;
(* Clear the command flag *)
SPARKPLUG_CMD_CLEAR('node1', 'Setpoint');
END_IF;

(* Handle rebirth request from Ignition *)
IF SPARKPLUG_CMD_HAS('node1', 'Node Control/Rebirth') THEN
SPARKPLUG_CMD_CLEAR('node1', 'Node Control/Rebirth');
SPARKPLUG_NODE_BIRTH('node1'); (* republish full metric catalog *)
END_IF;
FunctionParamsReturnsDescription
SPARKPLUG_CMD_HAS(name, metricName)BOOLCheck if a command arrived
SPARKPLUG_CMD_GET(name, metricName)ANYRead the commanded value
SPARKPLUG_CMD_CLEAR(name, metricName)BOOLClear the command flag

Ignition rebirth: When Ignition connects to a broker and finds an existing edge node, it sends a rebirth request via NCMD with metric name Node Control/Rebirth. Your ST program must handle this by calling SPARKPLUG_NODE_BIRTH to republish the full metric catalog.


6. Complete Example: Production Line to Ignition

This example connects a ControlForge edge node to Ignition SCADA via Sparkplug B, publishing production data and accepting setpoint commands:

PROGRAM POU_Sparkplug_Production
VAR
state : INT := 0;
ok : BOOL;
(* Process values — updated from other programs or I/O *)
line_speed : REAL := 0.0;
motor_temp : REAL := 0.0;
conveyor_running : BOOL := FALSE;
batch_count : DINT := 0;
reject_count : DINT := 0;
(* Setpoint from SCADA *)
speed_setpoint : REAL := 100.0;
new_sp : REAL;
publish_counter : INT := 0;
END_VAR

CASE state OF
0: (* Create Sparkplug edge node *)
ok := SPARKPLUG_NODE_CREATE('prod', 'Factory/Line1', 'ControlForge-Line1',
'tcp://10.0.0.144:1883', 'goplc-line1-sp');
IF ok THEN state := 1; END_IF;

1: (* Connect to broker *)
ok := SPARKPLUG_NODE_CONNECT('prod');
IF ok THEN state := 2; END_IF;

2: (* Register all metrics *)
SPARKPLUG_METRIC_ADD('prod', 'Line/Speed', line_speed);
SPARKPLUG_METRIC_ADD('prod', 'Line/SpeedSetpoint', speed_setpoint);
SPARKPLUG_METRIC_ADD('prod', 'Motor/Temperature', motor_temp);
SPARKPLUG_METRIC_ADD('prod', 'Conveyor/Running', conveyor_running);
SPARKPLUG_METRIC_ADD('prod', 'Production/BatchCount', batch_count);
SPARKPLUG_METRIC_ADD('prod', 'Production/RejectCount', reject_count);
state := 3;

3: (* Publish birth certificate — Ignition auto-discovers all tags *)
ok := SPARKPLUG_NODE_BIRTH('prod');
IF ok THEN state := 4; END_IF;

4: (* Subscribe to commands from Ignition *)
ok := SPARKPLUG_CMD_SUBSCRIBE('prod');
IF ok THEN state := 10; END_IF;

10: (* Running — update metrics and publish *)
(* Update metric values from process *)
SPARKPLUG_METRIC_SET('prod', 'Line/Speed', line_speed);
SPARKPLUG_METRIC_SET('prod', 'Motor/Temperature', motor_temp);
SPARKPLUG_METRIC_SET('prod', 'Conveyor/Running', conveyor_running);
SPARKPLUG_METRIC_SET('prod', 'Production/BatchCount', batch_count);
SPARKPLUG_METRIC_SET('prod', 'Production/RejectCount', reject_count);

(* Publish changed metrics every 10 scans (~1 second at 100ms scan) *)
publish_counter := publish_counter + 1;
IF publish_counter >= 10 THEN
SPARKPLUG_NODE_DATA('prod');
publish_counter := 0;
END_IF;

(* Handle setpoint commands from Ignition *)
IF SPARKPLUG_CMD_HAS('prod', 'Line/SpeedSetpoint') THEN
new_sp := SPARKPLUG_CMD_GET('prod', 'Line/SpeedSetpoint');
speed_setpoint := new_sp;
SPARKPLUG_METRIC_SET('prod', 'Line/SpeedSetpoint', speed_setpoint);
SPARKPLUG_CMD_CLEAR('prod', 'Line/SpeedSetpoint');
END_IF;

(* Handle rebirth request *)
IF SPARKPLUG_CMD_HAS('prod', 'Node Control/Rebirth') THEN
SPARKPLUG_CMD_CLEAR('prod', 'Node Control/Rebirth');
SPARKPLUG_NODE_BIRTH('prod');
END_IF;
END_CASE;
END_PROGRAM

7. Ignition SCADA Integration

Connecting Ignition to ControlForge via Sparkplug

  1. Install Cirrus Link MQTT Transmission/Engine modules in Ignition (or use the built-in Sparkplug support in Ignition 8.1+).

  2. Configure MQTT Engine to connect to the same broker ControlForge uses:

    • Server URL: tcp://10.0.0.144:1883
    • Group ID filter: Factory (or leave blank for all)
  3. Start ControlForge with the Sparkplug program above. Ignition auto-discovers the edge node and creates tags under:

    [MQTT Engine]Factory/Line1/ControlForge-Line1/Line/Speed
    [MQTT Engine]Factory/Line1/ControlForge-Line1/Motor/Temperature
    [MQTT Engine]Factory/Line1/ControlForge-Line1/Conveyor/Running
    ...
  4. Bind Ignition tags to Vision/Perspective screens. Writes from Ignition flow back as NCMD messages, which ControlForge receives via SPARKPLUG_CMD_HAS/SPARKPLUG_CMD_GET.

Tag Quality and Stale Detection

Ignition tracks tag quality based on Sparkplug lifecycle:

Sparkplug EventIgnition Tag Quality
NBIRTH receivedGood
NDATA receivedGood (updated)
NDEATH receivedBad (stale)
Sequence gap detectedBad (stale) — Ignition requests rebirth
No NDATA for timeout periodUncertain (stale)

Metric Naming Best Practices for Ignition

(* Good — creates folder hierarchy in Ignition tag browser *)
SPARKPLUG_METRIC_ADD('prod', 'Line1/Motor/Speed', 0.0);
SPARKPLUG_METRIC_ADD('prod', 'Line1/Motor/Temperature', 0.0);
SPARKPLUG_METRIC_ADD('prod', 'Line1/Motor/Running', FALSE);
SPARKPLUG_METRIC_ADD('prod', 'Line1/Conveyor/Speed', 0.0);

(* Bad — flat namespace, hard to navigate in Ignition *)
SPARKPLUG_METRIC_ADD('prod', 'line1_motor_speed', 0.0);
SPARKPLUG_METRIC_ADD('prod', 'line1_motor_temp', 0.0);

8. Sparkplug B Message Lifecycle

Startup Sequence

1. SPARKPLUG_NODE_CREATE() → Register node (no network)
2. SPARKPLUG_METRIC_ADD() ×N → Register all metrics (aliases auto-assigned)
3. SPARKPLUG_METRIC_SET_UNIT(...) → (optional) Add property metadata
4. SPARKPLUG_DEVICE_ADD() / DEVICE_METRIC_ADD() → (optional) Add sub-devices
5. SPARKPLUG_NODE_SET_PRIMARY_HOST() → (optional) Track a host's STATE
6. SPARKPLUG_NODE_CONNECT() → MQTT CONNECT (LWT carries current bdSeq)
7. SPARKPLUG_NODE_BIRTH() → Publish NBIRTH (name+alias, seq=0)
8. SPARKPLUG_DEVICE_BIRTH() ×N → Publish DBIRTH for each device
9. SPARKPLUG_CMD_SUBSCRIBE() → Subscribe to NCMD (and DCMD per device)
10. loop: SPARKPLUG_NODE_DATA() / DEVICE_DATA() → NDATA/DDATA, alias-only

Shutdown Sequence

1. SPARKPLUG_NODE_DISCONNECT() → Publish NDEATH (matching bdSeq), close MQTT
2. SPARKPLUG_NODE_DELETE() → Free resources

Ungraceful Disconnect & Recovery

1. Network drops / process crashes
2. Broker detects TCP timeout (keepalive, default 30s)
3. Broker publishes NDEATH from the LWT (carries the session's bdSeq)
4. SCADA marks all tags Bad/Stale
5. ControlForge's ConnectionLostHandler fires:
• bdSeq is bumped for the next session
• paho client is rebuilt with a fresh LWT (new bdSeq)
• Reconnect attempted with exponential backoff (1s → 30s cap)
6. On reconnect: OnConnect handler auto-publishes NBIRTH + all DBIRTHs
(any prior NCMD subscription is re-applied too — clean sessions forget them)

Host-driven Rebirth (NCMD)

If a SCADA host sends Node Control/Rebirth = true on the NCMD topic, ControlForge intercepts it (does not surface to ST) and republishes NBIRTH followed by DBIRTH for every birthed device. This complies with Sparkplug 3.0 §5.4.

Primary-Host STATE Awareness

SPARKPLUG_NODE_SET_PRIMARY_HOST(name, host_id) subscribes the node to spBv1.0/STATE/<host_id>. While the host is offline, NDATA/DDATA publishing is suspended (no point talking into the void). When the host comes back online, the node automatically republishes NBIRTH + DBIRTHs so the host rebuilds its view. Both Sparkplug 3.0 JSON STATE ({"online":bool,...}) and legacy 2.x plain-text (ONLINE/OFFLINE) payloads are parsed.

Protobuf Payload Structure

Every Sparkplug message body is a Google Protocol Buffer (org.eclipse.tahu.protobuf.Payload):

Payload {
timestamp: uint64 // ms since epoch — field 1
seq: uint64 // 0–255, wraps — field 3
metrics: [
{
name: string // field 1 (NBIRTH only when alias is used)
alias: uint64 // field 2 — stable per session
timestamp: uint64 // field 3
datatype: uint32 // field 4 (1–18 per Sparkplug 3.0 §6.4.6 + 22–34 for arrays)
is_historical: bool // field 5
is_transient: bool // field 6
is_null: bool // field 7 (suppresses value field below)
properties: PropertySet // field 9 (NBIRTH only — engUnit / engHigh / engLow / description / ...)
value: oneof // int_value(10) / long_value(11) / float_value(12) /
// double_value(13) / boolean_value(14) / string_value(15) /
// bytes_value(16, for Bytes/File/arrays) / dataset_value(17)
},
...
]
}

ControlForge handles all Protobuf encoding/decoding automatically. You work with native ST types (INT, REAL, BOOL, STRING) plus the typed _ADD_* builtins for binary/arrays, and ControlForge maps them to the correct Sparkplug data types.


9. Troubleshooting

Common Issues

SymptomCauseFix
Ignition shows no tagsNBIRTH not publishedEnsure SPARKPLUG_NODE_BIRTH is called after all metrics are added
Tags show Bad qualityNDEATH receivedCheck ControlForge connection to broker; verify keepalive
Stale data in IgnitionNDATA not publishingVerify SPARKPLUG_NODE_DATA is called periodically
Ignition requests rebirth repeatedlySequence gapEnsure no duplicate client IDs; check for network drops
Commands not arrivingNCMD not subscribedCall SPARKPLUG_CMD_SUBSCRIBE after connecting
Metrics missing from birthAdded after NBIRTHAdd all metrics before calling SPARKPLUG_NODE_BIRTH
Broker rejects connectionDuplicate client IDUse unique clientID per ControlForge instance
TLS handshake failureCertificate mismatchVerify broker CA cert and hostname match ssl:// URL

Appendix A: Function Quick Reference

Edge Node — lifecycle

FunctionParamsReturnsDescription
SPARKPLUG_NODE_CREATE(name, groupID, edgeNodeID, brokerURL, clientID)BOOLRegister node (empty clientID auto-generates)
SPARKPLUG_NODE_CREATE_AUTH(name, groupID, edgeNodeID, brokerURL, clientID, user, pass)BOOLAs above, with broker credentials
SPARKPLUG_NODE_CONNECT(name)BOOLConnect; LWT carries current bdSeq
SPARKPLUG_NODE_DISCONNECT(name)BOOLPublish NDEATH + close; bumps bdSeq for next session
SPARKPLUG_NODE_IS_CONNECTED(name)BOOLTrue if currently connected
SPARKPLUG_NODE_DELETE(name)BOOLStop reconnect loop + close + free
SPARKPLUG_NODE_LIST()STRINGComma-separated node names
SPARKPLUG_NODE_BIRTH(name)BOOLNBIRTH (full metric catalog, name+alias)
SPARKPLUG_NODE_DEATH(name)BOOLNDEATH explicitly (matches session's bdSeq)
SPARKPLUG_NODE_DATA(name)BOOLNDATA (changed metrics, alias-only); suspended while primary host offline
SPARKPLUG_GET_SEQ(name)INTCurrent Sparkplug seq counter (0–255)

Edge Node — metrics

FunctionParamsReturnsDescription
SPARKPLUG_METRIC_ADD(name, metric, value)BOOLRegister with inferred type + auto alias
SPARKPLUG_METRIC_SET(name, metric, value)BOOLUpdate + mark changed
SPARKPLUG_METRIC_GET / _GET_REAL / _INT / _BOOL / _STR(name, metric)typedRead current local value
SPARKPLUG_METRIC_GET_ALIAS(name, metric)INTAlias assigned in NBIRTH (0 if no such metric)
SPARKPLUG_METRIC_ADD_TEXT / _UUID / _BYTES / _FILE / _DATETIME(name, metric, value)BOOLTyped adds for extended Sparkplug types
SPARKPLUG_METRIC_ADD_INT_ARRAY / _LONG_ / _REAL_ / _DOUBLE_ / _BOOL_ / _STR_ARRAY(name, metric, csv_str)BOOLAdd array metric — CSV input, packed LE per spec §16

Edge Node — properties + flags

FunctionParamsReturnsDescription
SPARKPLUG_METRIC_SET_UNIT(name, metric, unit_str)BOOLSets engUnit property (e.g. "°C")
SPARKPLUG_METRIC_SET_RANGE(name, metric, low_real, high_real)BOOLSets engLow + engHigh
SPARKPLUG_METRIC_SET_DESC(name, metric, desc_str)BOOLSets description
SPARKPLUG_METRIC_SET_DOC(name, metric, doc_str)BOOLSets documentation
SPARKPLUG_METRIC_SET_PROP_STR / _REAL / _INT / _BOOL(name, metric, prop_name, value)BOOLGeneric typed property setter
SPARKPLUG_METRIC_SET_NULL(name, metric, is_null)BOOLToggle is_null (suppresses value on wire)
SPARKPLUG_METRIC_MARK_HISTORICAL(name, metric, hist)BOOLMark as historical (store-and-forward replay)
SPARKPLUG_METRIC_MARK_TRANSIENT(name, metric, trans)BOOLMark as transient (host should not historize)

Edge Node — NCMD

FunctionParamsReturnsDescription
SPARKPLUG_CMD_SUBSCRIBE(name)BOOLSubscribe to NCMD topic (auto-re-applied on reconnect)
SPARKPLUG_CMD_HAS(name, metric)BOOLTrue if command arrived for that metric
SPARKPLUG_CMD_GET / _GET_REAL / _INT / _BOOL / _STR(name, metric)typedRead commanded value
SPARKPLUG_CMD_CLEAR(name, metric)BOOLClear the "has cmd" flag

Internal NCMD metrics — Node Control/Rebirth, Node Control/Next Server, Node Control/Reboot — are intercepted and handled automatically; they do not appear to ST.

Devices

FunctionParamsReturnsDescription
SPARKPLUG_DEVICE_ADD(node, device)BOOLRegister a sub-device
SPARKPLUG_DEVICE_DELETE(node, device)BOOLPublish DDEATH + remove
SPARKPLUG_DEVICE_LIST(node)STRINGComma-separated device IDs
SPARKPLUG_DEVICE_METRIC_ADD / _SET / _GET[_*](node, device, metric, ...)typedPer-device metric ops (shared alias namespace)
SPARKPLUG_DEVICE_BIRTH / _DEATH / _DATA(node, device)BOOLDBIRTH/DDEATH/DDATA — seq from parent's counter
SPARKPLUG_DCMD_SUBSCRIBE / _HAS / _GET[_*] / _CLEAR(node, device, metric, ...)typedDCMD inbound on .../<device>

Primary-Host STATE awareness

FunctionParamsReturnsDescription
SPARKPLUG_NODE_SET_PRIMARY_HOST(node, host_id)BOOLTrack spBv1.0/STATE/<host_id>; empty disables
SPARKPLUG_NODE_HOST_ONLINE(node)BOOLCurrent host online status (true while STATE unknown)

Host Application role

FunctionParamsReturnsDescription
SPARKPLUG_HOST_CREATE(name, host_id, broker, client_id)BOOLRegister a host-app identity
SPARKPLUG_HOST_CREATE_AUTH(name, host_id, broker, client_id, user, pass)BOOLAs above with auth
SPARKPLUG_HOST_START(name)BOOLConnect, publish retained ONLINE STATE, subscribe to spBv1.0/+/...
SPARKPLUG_HOST_STOP(name)BOOLPublish OFFLINE STATE + disconnect
SPARKPLUG_HOST_DELETE(name)BOOLTear down
SPARKPLUG_HOST_LIST()STRINGComma-separated host names
SPARKPLUG_HOST_NODE_LIST(name)STRINGgroup/edge of every remote node discovered
SPARKPLUG_HOST_NODE_IS_ONLINE(name, group, edge)BOOLRemote node online state
SPARKPLUG_HOST_DEVICE_IS_ONLINE(name, group, edge, device)BOOLRemote device online state
SPARKPLUG_HOST_GET_METRIC / _REAL / _INT / _BOOL / _STR(name, "group/edge[/device]/metric")typedRead a remote metric
SPARKPLUG_HOST_PUBLISH_NCMD(name, group, edge, metric, value)BOOLIssue NCMD write to a remote edge node
SPARKPLUG_HOST_PUBLISH_DCMD(name, group, edge, device, metric, value)BOOLIssue DCMD write to a remote device
SPARKPLUG_HOST_REQUEST_REBIRTH(name, group, edge)BOOLSugar for Node Control/Rebirth=true NCMD

The host automatically issues Node Control/Rebirth when it detects an NDATA/DDATA sequence gap or an NDATA/DDATA arriving without a prior NBIRTH.


ControlForge v1.0.865 | Sparkplug B v3.0 | Eclipse Tahu Protobuf + MQTT 3.1.1

© 2026 JMB Technical Services LLC. All rights reserved. Back to All Guides