Skip to main content

Sparkplug-B Ingestion

Many industrial and building-automation fleets already publish telemetry as Eclipse Sparkplug B over an MQTT broker they operate. DeviceChain can ingest directly from those networks without asking the devices to change anything. It joins your Sparkplug environment as a Host Application and translates the edge traffic into the same events every other transport produces.

With plain MQTT, DeviceChain is the broker. Sparkplug ingestion works the other way around: DeviceChain connects out to your broker as a client, subscribes to the Sparkplug groups you configure, and follows the Sparkplug session protocol.

What it does​

  • Announces itself as a Host Application. DeviceChain publishes the Sparkplug STATE message so edge nodes know a consumer is online. It follows the birth/death handshake, so it always knows which nodes and devices are live.

  • Follows the Sparkplug session. Sparkplug is a stateful protocol. An edge node sends a BIRTH certificate that defines its metrics (and compact aliases), then a stream of DATA messages that reference them, and a DEATH when it goes offline. DeviceChain runs the full session state machine: it tracks each node's aliases and message sequence, and detects a gap or a missed birth. When it needs to resynchronize, it asks the node to re-announce (a rebirth), so a dropped message never silently corrupts what it decodes.

  • Maps edge identities to devices. Each Sparkplug {group}/{node} (or {group}/{node}/{device} for a device under a node) becomes the externalId of a DeviceChain device. If you enable auto-registration for a source, a device is created automatically the first time it is seen. Otherwise, unknown identities are dropped and counted, so you stay in control of what enters your registry. Traffic for a tenant that has been deleted is also dropped while its data is being reclaimed, and is counted separately. An operator watching drops can therefore tell "not in the registry" from "the tenant is gone."

  • Produces authoritative presence. A node or device BIRTH marks the corresponding device online, and a DEATH marks it offline, immediately and explicitly. This makes Sparkplug a presence-asserting transport, as is LwM2M: a Sparkplug device's online state is authoritative, not inferred from a timeout. See Death handling for which deaths DeviceChain applies.

  • Feeds the same pipeline. Decoded measurements and presence changes flow into the normal decode → resolve → persist pipeline. Everything downstream — history, live state, dashboards and the detection engine — treats Sparkplug telemetry exactly like any other. A message with more than 256 metric values is stored as several consecutive events of at most 256 each, up to 16,384 metrics in one message; a message carrying more has none of its readings stored. Every value keeps its own timestamp, and a redelivered message produces the same events rather than duplicates.

Death handling​

DeviceChain is deliberately strict about which deaths it applies:

  • Birth sequence must match. When a node's BIRTH declares a birth sequence number (as the Sparkplug specification requires), a DEATH is accepted only when it carries a matching one. A stale or duplicate will left over from an earlier connection is ignored rather than tearing down a session a newer BIRTH has already re-established.
  • No birth sequence, no correlation. A node whose BIRTH declared no birth sequence number cannot be correlated at all, so its death is accepted as-is.
  • Unborn devices. A device DEATH for a device that was never born emits nothing at all, because there is no presence to end.
  • Node death cascades. A node death marks the node offline and every known device beneath it, because in Sparkplug a node's death implies its devices are gone with it.

Tenancy and configuration​

Each Sparkplug source is configured for one tenant. It holds the broker URL, the credentials (supplied as a projected secret, never in plaintext config) and the groups to subscribe to. You give dcctl the sources as a file, with --sparkplug-source; Running the Edge Services shows it.

Every message that arrives on a source is attributed to that source's tenant. The tenant is fixed by which broker the message came in on and is never read from the Sparkplug topic, so one tenant's edge network can never be mistaken for another's.

A node born before its device type exists​

A node is registered the first time its birth arrives, under the deviceTypeToken its source names. If that device type does not exist yet (the usual order mistake when a source is connected before the tenant is provisioned), or device-management is unreachable for longer than the short retry, the birth is dropped and counted. A node that is already born does not announce itself again on its own, so it would stay unknown.

The platform therefore remembers which ids it could not register and asks the node to rebirth (an NCMD Node Control/Rebirth), 10 seconds after the failure and then at doubling intervals up to once a minute, until the ids that failed resolve; a sibling device registering does not end it. Only two causes are retried: the device type does not exist, and device-management being unreachable. A refusal device-management answered with for any other reason is logged once and not retried by rebirth, because a second birth would meet it again. After five requests a node drops to one request an hour, at most five nodes are asked per five-second tick, and a node cleared and failing again soon resumes its interval rather than starting over. Create the device type and the node appears within a minute, without restarting anything. Nothing is replayed from memory: the births that answer are ingested like any other, so a node can never be registered twice. The first failure for a node is logged at error with its cause, every request at warning, and birth_recovery_pending stays above zero while any node is waiting (see the metrics). A node that goes offline is forgotten; its next birth is a fresh attempt.

High availability​

Only one replica of the Sparkplug service connects to a given broker at a time. A Sparkplug Host owns per-node alias, sequence and session state, so two connected instances would each see part of the traffic and publish conflicting STATE. Single ownership is therefore a correctness requirement, not a tuning choice. It is enforced by a fenced ownership lease, and the deployment refuses to render more than one replica.

The lease makes replacement safe and automatic. When the serving pod goes away — a crash, an eviction, a node loss or a rollout — its replacement cannot begin serving until it has acquired the lease, so there is never a window with two Hosts on the broker. On acquiring the lease, the new leader:

  1. Re-establishes the session, asking nodes to re-announce.
  2. Reconciles device presence, so a disconnect that happened during the changeover is not missed and no device is left wrongly showing online.

Recovery is automatic but not instantaneous. It takes as long as the replacement pod needs to schedule and start, plus up to the lease's 30-second fencing window. There is no warm standby holding a second connection, because that would be the thing single ownership exists to prevent.

Status

Sparkplug-B ingestion is available as an opt-in service. It ingests measurements and drives authoritative device presence. It connects to a broker over TLS or plaintext, per the configured URL. A second standards-native edge protocol, LwM2M, is also available. Custom-CA / mutual-TLS to a private broker is planned.

Commands​

DeviceChain can send commands down to a Sparkplug node or device, over the same Host Application session it already holds with your broker. This is opt-in and off by default: until a source enables it, a command to a Sparkplug device fails at once as undeliverable, exactly as before. Enabling it is a setting of the source, described in Sparkplug commands.

Commands are ordinary commands: declared on the device profile, sent from the device page, a batch or an automation rule, and subject to the same permissions, maintenance windows and audit trail. The console's Add Sparkplug standard commands button declares the three below on a profile.

Command keyWhat it sendsAllowed when
sparkplug.write with {"metric": name, "value": v}A DCMD to a device, or an NCMD to a node, setting one metricThe metric is on the source's writable list, and the node declared it in its birth
sparkplug.rebirthAn NCMD asking the node to publish its birth again, with every current valueAlways, once commands are enabled
sparkplug.rebootAn NCMD setting Node Control/RebootThe command is declared DISRUPTIVE or DESTRUCTIVE, the source sets allowReboot, the node declared the metric in its birth, the node is online, and it has not been rebooted within the reboot window

Whether a command goes to a node or a device follows the target: a device that sits under a node gets a DCMD, a node gets an NCMD. A metric the birth gave an alias is addressed by alias, as Sparkplug requires; otherwise by name. The value is encoded into the datatype the node declared, and a value of the wrong kind is refused before anything is sent, with the metric, the datatype and your value in the reason. Arrays, data sets and other structured datatypes are not supported. A command is published once, at QoS 0 and unretained.

If the device has not been born on the session yet (the node is offline, or the platform has just restarted and the births have not arrived), the command is parked and sent when the birth arrives. A reboot is the exception: it is sent only to a node that is online, and is refused otherwise.

What the outcomes mean​

Sparkplug has no command acknowledgement. A node that receives a DCMD does not answer it. So the platform publishes the command and then waits for the node to report the requested value in a later data or birth message, and the outcome tells you which of these happened:

StatusMeaning
SENTPublished; waiting for the node to report the value. Lasts up to the confirm window
SUCCESSFUL, reportedThe node reported the metric at the requested value after the command was published. The result records the metric, the value, when it was reported and in which message
SUCCESSFUL, already at the valueThe node had already reported that value, and reported nothing different within the window. The platform did not see the node act on this command
TIMEOUTNothing confirmed it within the window (2 minutes by default). This is not a failure: the node may have ignored or rejected the command, the broker may have dropped it, or the node obeyed and has not reported yet
FAILED, not deliveredRefused before anything was sent: commands not enabled for that source, an invalid value, a metric that is not allowed, an expired command, or the breaker below. The stored response carries a reason code saying which

Two cautions follow from that. Confirmed means the metric reached the value, not that this request caused it: Sparkplug does not authenticate who published, so anyone who can publish to the node's topic on your broker can make a metric read any value, and the broker's publish ACLs are what stand behind the evidence. And a metric a node reports only on change may leave a command at TIMEOUT even though it worked. A report older than the command, and a report flagged historical, never count as confirmation.

A newer write to the same metric on the same device ends the older one that is still waiting, as TIMEOUT with the reason superseded. A rebirth is confirmed by the node's next birth, and a reboot by a birth with a new birth sequence number, within its own, longer window.

Why a command was refused​

A command refused before anything was sent ends as FAILED, not delivered, and its stored response is {"reason": "<code>"}. The error text says the same in words; the code is what a script or the console should key on, because the wording can change and the codes are a fixed set. The console shows a sentence for each.

CodeMeaning
commands_not_enabledCommands are off for the tenant, or for the source the device is on
broker_refusedThe breaker is open: the broker refused an earlier publish
expiredThe command's own expiry passed before it could be sent
invalid_parametersThe parameters are not what the command takes (a write needs a metric name and a value of a scalar kind; a rebirth and a reboot take none)
unknown_commandNot one of sparkplug.write, sparkplug.rebirth or sparkplug.reboot
metric_protocol_controlThe metric is a protocol control metric (bdSeq, Node Control/..., Device Control/...)
metric_not_writableThe metric is not on the source's writableMetrics list
metric_not_declaredThe node's birth did not declare the metric
datatype_unsupportedThe metric's datatype is one commands do not carry
value_not_acceptedThe value cannot be written as the datatype the node declared, or is out of its range
datatype_changedThe node re-birthed with a different datatype for the metric while the command waited
node_rebirthingThe node kept re-birthing while the command was being recorded
node_offlineA reboot is sent only to a node that is online when it is dispatched
reboot_impactThe reboot command is not declared disruptive or destructive on the profile
reboot_not_allowedallowReboot is off on the source
reboot_not_a_nodeA reboot was addressed to a device rather than to an edge node
reboot_not_declaredThe node did not declare Node Control/Reboot in its birth
reboot_not_booleanThe node declares Node Control/Reboot with a datatype other than Boolean
reboot_unverifiedThe record of recent reboots could not be read yet, so the one-reboot-per-window rule could not be applied
reboot_rate_limitedThe node was already rebooted within the reboot window
no_dispatchThe delivery could not be tracked, so the command was neither parked nor sent
publish_failedThe publish to your broker failed

Primary and secondary host​

Sparkplug recommends that only a node's primary host send it commands. A source declares which it is with hostRole, which has no default because it is a fact about your plant:

  • primary: your nodes name this source's host id as their primary host. The already at the value outcome is available.
  • secondary: another host is the primary. Commands are still sent, but a node, or the broker's ACLs, may ignore or block them, and the primary may write the value back. The already at the value outcome is turned off, because the other host may be the one holding it there, and the console marks a command confirmed by a secondary host with a warning.

When the broker refuses the publish​

A broker that refuses a publish may drop the whole session, which marks the source offline and makes every node rebirth, and it would do that again for every queued command. So each source has a circuit breaker: if the session is lost within 5 seconds of a command being published, the breaker opens, that command ends as not confirmed with a reason that says the broker likely refused it, and commands to that source fail at once as not delivered for 5 minutes, doubling to 1 hour on each re-open. Telemetry is not affected. After the pause one command is let through to test it, and the breaker closes when it is published without a disconnect. The usual cause is a credential that may not publish on the command topics; see the broker ACL.

Updating an edge node​

Sparkplug B defines no firmware update, so DeviceChain's over-the-air updates do not reach a device through this service, and DeviceChain does not invent a firmware convention on top of Sparkplug. An edge node that should be updated through DeviceChain also connects to DeviceChain as an ordinary MQTT device, with its own credential, and uses the normal update channel. Building an Updatable Device shows that device side.

Operations​

Because a single replica owns the broker connection, Sparkplug ingestion has a few operational properties to understand before you depend on it in production: what a changeover costs, why a device can be left showing online, and what the drop counters are telling you. Running the Edge Services covers them.