feat(mqtt): Sparkplug primary-host STATE publishing (ActAsPrimaryHost) #511

Open
opened 2026-07-27 13:50:30 -04:00 by dohertj2 · 0 comments
Owner

MqttSparkplugOptions.ActAsPrimaryHost (Driver.Mqtt.Contracts/MqttDriverOptions.cs:193) is accepted by
config and by the AdminUI driver form, but does nothing. Setting it logs a startup warning
(MqttDriver.cs:933) rather than sitting silently inert.

Why it shipped unimplemented rather than half-implemented. Being a Sparkplug primary host is a three-part
contract:

  1. a retained STATE message asserting ONLINE,
  2. a matching OFFLINE registered as the MQTT Last Will, at connect time,
  3. an explicit OFFLINE published on clean shutdown.

Shipping (1) without (2) is strictly worse than shipping neither: a crashed node leaves a retained ONLINE
on the broker asserting forever that a dead host is alive, and edge nodes trusting that assertion will not
take their own offline actions. The warning is the deliberate middle ground.

Extra consideration for this server: both redundancy pair nodes subscribe independently (MQTT fan-out is
per-connection, and the Primary gate applies downstream of ingest, not to it). Two nodes both claiming primary
host on the same host id would fight over the retained STATE. An implementation must decide whether
primary-host duty is Primary-gated — and if it is, whether a failover republishes ONLINE fast enough to
avoid an edge-visible flap.

Acceptance. With ActAsPrimaryHost: true, the broker holds a retained ONLINE while the node runs; a
kill -9 of the node leaves OFFLINE (the Will fires); a clean shutdown leaves OFFLINE. Verified live
against the Mosquitto fixture, and the pair-node question answered explicitly rather than left to chance.

`MqttSparkplugOptions.ActAsPrimaryHost` (`Driver.Mqtt.Contracts/MqttDriverOptions.cs:193`) is accepted by config and by the AdminUI driver form, but does nothing. Setting it **logs a startup warning** (`MqttDriver.cs:933`) rather than sitting silently inert. **Why it shipped unimplemented rather than half-implemented.** Being a Sparkplug primary host is a three-part contract: 1. a **retained** `STATE` message asserting `ONLINE`, 2. a matching `OFFLINE` registered as the MQTT **Last Will**, at *connect time*, 3. an explicit `OFFLINE` published on clean shutdown. Shipping (1) without (2) is strictly **worse than shipping neither**: a crashed node leaves a retained `ONLINE` on the broker asserting forever that a dead host is alive, and edge nodes trusting that assertion will not take their own offline actions. The warning is the deliberate middle ground. **Extra consideration for this server:** both redundancy pair nodes subscribe independently (MQTT fan-out is per-connection, and the Primary gate applies downstream of ingest, not to it). Two nodes both claiming primary host on the same host id would fight over the retained `STATE`. An implementation must decide whether primary-host duty is **Primary-gated** — and if it is, whether a failover republishes `ONLINE` fast enough to avoid an edge-visible flap. **Acceptance.** With `ActAsPrimaryHost: true`, the broker holds a retained `ONLINE` while the node runs; a `kill -9` of the node leaves `OFFLINE` (the Will fires); a clean shutdown leaves `OFFLINE`. Verified live against the Mosquitto fixture, and the pair-node question answered explicitly rather than left to chance.
dohertj2 added the driver/mqtt label 2026-07-27 13:50:30 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#511