A Sparkplug primary host application is the main consumer of plant data, typically SCADA or an MES, that edge nodes watch before they publish anything. In this guide you will learn how the host announces itself with STATE messages, how edge nodes are expected to react when it goes offline, and how to test that behaviour end to end with a small Python host and a couple of command-line tools.
What a Host Application Does
Sparkplug defines two kinds of participants besides the broker. Edge nodes publish data. Host applications consume it, track the state of every node and device, and send commands such as rebirth requests or setpoint writes. A plant can have many host applications: SCADA, a historian, an analytics service, a Unified Namespace bridge.
A host application has several responsibilities:
- Subscribe to
spBv1.0/#(or the groups it manages) and maintain a live model of nodes, devices and metrics. - Use birth certificates to learn metric names, aliases and datatypes.
- Mark data stale when an NDEATH or DDEATH arrives, using bdSeq to discard deaths from old sessions.
- Watch sequence numbers and request a rebirth when a gap appears.
- Publish its own STATE so edge nodes know whether it is listening.
What Makes a Host Primary
One host can be designated the primary host application for a set of edge nodes. This is configured on the edge node, not on the broker: the node is told the host ID it depends on. The idea is simple. If the system that operators rely on is not connected, data published now would be lost to it, so the edge node should stop publishing live data, store it locally if it supports store and forward, and wait.
Without a primary host configured, an edge node publishes as soon as it is connected to the broker, regardless of who is listening. That is fine for monitoring and dashboards, but it is weaker for SCADA where gaps in history matter.
STATE Messages in Sparkplug 3.0
The host announces itself on a dedicated topic outside the group structure:
spBv1.0/STATE/{host_id}
| Property | Birth (online) | Death (offline) |
|---|---|---|
| How it is sent | Published by the host after connecting | Registered as MQTT will at connect |
| Payload | {"online": true, "timestamp": T} |
{"online": false, "timestamp": T} |
| QoS | 1 | 1 |
| Retained | Yes | Yes |
| Timestamp | Same T for this session | Same T for this session |
Retaining the message means an edge node that connects at any time immediately learns whether the host is online. The shared timestamp lets nodes ignore an old message: if a node has seen online with timestamp T2, an offline message with an older timestamp T1 is a leftover from a previous session and must not trigger a disconnect.
Older 2.2 systems use STATE/{host_id} with plain ONLINE and OFFLINE strings. The differences are summarised in our Sparkplug 3.0 comparison.
How Edge Nodes React
An edge node configured with a primary host follows a predictable pattern:
- Connect to the broker with its NDEATH registered as the will.
- Subscribe to
spBv1.0/STATE/{primary_host_id}and to its own NCMD and DCMD topics. - Wait for a STATE message with
"online": true. - Publish NBIRTH and DBIRTH, then data as usual.
- If STATE switches to
"online": falsewith a timestamp at least as new as the last one seen, publish NDEATH, disconnect, and buffer data if supported. - Reconnect (possibly to a different broker in a multi-server setup) and wait again for the host to be online.
- When the host returns, publish fresh births, then any buffered historical data flagged with
is_historical.
Step 7 is why STATE matters: births give the host a clean model, and historical flags let it backfill without overwriting live values. The birth and death mechanics are covered in Sparkplug birth and death certificates.
Test the Flow with a Minimal Python Host
This script plays the role of a primary host: it registers its offline will, publishes its online state and listens to a group. It uses paho-mqtt 2.x and needs no Protobuf because STATE payloads are JSON.
import json
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PORT = 1883
HOST_ID = "demo-host-" + uuid.uuid4().hex[:6]
GROUP = "demo-" + HOST_ID[-6:]
STATE_TOPIC = f"spBv1.0/STATE/{HOST_ID}"
SESSION_TS = int(time.time() * 1000)
def state(online):
return json.dumps({"online": online, "timestamp": SESSION_TS})
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print("connect failed:", reason_code)
return
client.subscribe(f"spBv1.0/{GROUP}/#", qos=1)
client.subscribe(STATE_TOPIC, qos=1)
client.publish(STATE_TOPIC, state(True), qos=1, retain=True)
print(f"host {HOST_ID} online, watching group {GROUP}")
def on_message(client, userdata, msg):
if msg.topic == STATE_TOPIC:
print("STATE:", msg.payload.decode())
else:
print(f"{msg.topic} ({len(msg.payload)} bytes)")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"host-{uuid.uuid4().hex[:8]}",
clean_session=True)
client.on_connect = on_connect
client.on_message = on_message
client.will_set(STATE_TOPIC, state(False), qos=1, retain=True)
client.connect(BROKER, PORT, keepalive=15)
try:
client.loop_forever()
except KeyboardInterrupt:
client.publish(STATE_TOPIC, state(False), qos=1, retain=True).wait_for_publish()
client.disconnect()
Run it, note the host ID it prints, and in a second terminal play the edge node watching that host:
mosquitto_sub -h broker.connectmq.com -p 1883 -i node-watch-91aa -q 1 -v \
-t "spBv1.0/STATE/demo-host-xxxxxx"
You will immediately receive the retained online message. Now kill the Python process without Ctrl+C (close the terminal or end the task). After the keepalive window passes, the broker publishes the will and the watcher prints "online": false with the same timestamp. Restart the host and you see a new online message with a newer timestamp. That is exactly the signal an edge node uses to decide when to publish births.
When you are done, clear the retained state so the shared public MQTT test broker does not keep it:
mosquitto_pub -h broker.connectmq.com -p 1883 -i cleanup-91aa -r -n -t "spBv1.0/STATE/demo-host-xxxxxx"
Watching Host State in a Topic Explorer
During commissioning you want to see host state and node births on one screen. ConnectMQ shows retained STATE messages in its portal topic explorer next to decoded Sparkplug births and data, and exposes client connection state at $SYS/broker/connection/{clientId}/state. Subscribing to that topic for your host's client ID is a quick cross-check: if the connection state says disconnected but STATE still says online, the will has not fired yet because the keepalive window has not expired.
Common Problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Edge nodes never publish births | Host ID mismatch between node config and STATE topic | Copy the exact host ID, case-sensitive |
| Nodes disconnect right after host restarts | Old offline will processed after new online | Nodes must compare timestamps; check 3.0 support |
| STATE missing for new nodes | STATE published without retain | Publish online and will with retain true |
| Long delay before nodes notice host loss | Large keepalive on host | Use a short keepalive such as 15 to 30 seconds |
| Nodes and host ignore each other | One side uses 2.2 STATE/ topic |
Align versions or enable compatibility mode |
Designing for Host Availability
Because edge nodes stop publishing when the primary host is offline, the host and its broker connection become critical. Run the host on reliable infrastructure, use a short keepalive so failures are noticed quickly, and make sure the broker itself is not the weak link. A fully managed MQTT service or a cloud MQTT broker removes the burden of patching and monitoring the broker. If a node loses its connection, the Sparkplug rebirth process is how the host gets back to a consistent view. For the full IIoT picture, see Industrial IoT with MQTT.
Get Started with ConnectMQ
Create a free Serverless broker at app.connectmq.com, no credit card required. Run the host script with your generated credentials over TLS on port 8883, connect an edge node configured with that host ID, and watch STATE, births and data in the ConnectMQ portal.
Frequently Asked Questions
Can there be more than one primary host application?
Each edge node is configured with one primary host ID. Different groups of nodes can depend on different hosts, and any number of non-primary host applications can consume data at the same time.
Is the STATE message retained?
Yes. Both the online message and the offline will are published retained at QoS 1, so any edge node that connects later learns the current host state immediately.
Does the broker need special Sparkplug support for STATE?
No. STATE relies on standard MQTT features: retained messages, QoS 1 and the last will. Any compliant broker can carry it.
What happens to data while the primary host is offline?
Edge nodes stop publishing live data. Nodes that support store and forward buffer it and send it later as historical metrics after new births.



