MQTT Publishing New in 1.0.0
Everything else in this section is about getting data in. This page is the other direction: mirroring live namespace tags out to an MQTT broker QUBIQ does not host, so a cloud platform, an analytics stack or another SCADA system can subscribe to your plant.
It does not matter where a tag came from. An OPC-UA tag, a Modbus register, an expression tag and a tag QUBIQ computes itself all publish the same way, because a publisher reads the namespace, not a protocol.
Broker egress belongs to the MQTT Driver module. Without it you can still create, edit and turn publishers off — but none can be turned on, and the Publishing view says so by name rather than failing generically. → Licensing & modules
Where it lives
Publishing is part of the Namespace panel, not Settings — it is a fence over the tags that panel owns, and it belongs beside them.
| To… | Go to |
|---|---|
| See and manage every publisher | Namespace → MQTT Publishing (the toolbar toggle; it replaces the tree with the publisher list) |
| See where one tag goes, and how often | Edit the tag → Publishing tab |
| Publish a tag, a folder or a multi-selection | Right-click in the tree → Publishing |
The checkbox is the verb Changed in 1.0.6
Right-click → Publishing lists the brokers themselves with a checkbox beside each, and that is the whole gesture — for a tag, a folder and a multi-selection alike. Ticking one sends the selection there; unticking stops it. The menu stays open, because ticking a second broker is the usual next move.
A folder is not published — its tags are — so a folder or a multi-selection gets a third state, drawn as a dash:
| The box shows | Means |
|---|---|
| Ticked | The whole selection goes to that broker. |
| Empty | The fence does not reach any of it. |
| A dash | The fence reaches into the selection — some of it publishes, some does not. |
Clicking a dashed box does not guess. "Publish the rest" and "stop the ones that are" are equally plausible readings of the same click, and this menu writes immediately, so it hands the decision to the dialog instead of sending a subtree to an outside broker on one click from somebody who meant the opposite. Two other cases hand off the same way, because each needs a sentence the menu has no room for: a publish the Except fence would swallow, and a stop that would leave the publisher with an empty scope, which the server refuses outright.
Stopping a folder stops the subtree. A publisher scoped to Line1/Pump and Line1/Valve does
not literally cover Line1, and stopping Line1 used to find nothing to remove and report no
change — leaving both tags publishing with no way to stop them from the folder they live in. A
stop now owns everything under the target.
There is no longer a Publish to MQTT… / Stop Publishing… pair of verbs, and the dialog has no Action radio. A tick means this broker sends these tags and an untick means it stops; the difference from what is true today is the change. Nothing is staged until something is changed, so opening the dialog and closing it cannot alter anything, and a broker nobody touched is never part of the save.
One publisher per broker
A publisher is exactly one broker, one format, one fence. Sending the same tags to two brokers is two publishers, not a list inside one — they are independent by construction, with their own client, their own buffer and their own liveness, so a broker that is down or slow degrades only itself.
The publisher list answers four questions per row:
| Column | What it shows |
|---|---|
| Broker | The publisher's name, the connection it sends to, and a dot: green when enabled. |
| Sends | How much of the plant it reaches, then the format, QoS, and whether values are retained or buffered. |
| Status | What it is actually doing right now — published, dropped, failed, stopped. |
| On | The enable switch, plus edit and delete. |
Status comes from the running bridge service, not from the database. A saved publisher and a running one are different things, and telling them apart is the entire point of that column. If the bridge is not answering, the column says so — it does not render as "0 published", which would read as a publisher running and idle, a different problem with a different fix.
Two more facts join the line when they are true, and stay off it when they are not: 1.0.7
N untyped— Sparkplug metrics QUBIQ left out because it could not give them a Sparkplug data type or encode their value: a tag in the fence that is configured to publish and is not arriving. These used to be added to failed, which means the broker refused it — for a message the broker never saw. The count is of refusals; the server log names each tag once, by path.Oven-PLC offline, or2 of 5 devices offline— for a Sparkplug publisher using Devices. One device is named, because "which PLC?" is the first question; a healthy device says nothing, because "3 devices" is not news.
Creating a publisher
Add publisher asks for the following. Nothing publishes until you turn the publisher on afterwards — a new one is created disabled.
| Field | Default | Notes |
|---|---|---|
| Name | — | How the publisher is told apart in the list. |
| Broker | — | Any MQTT connection. |
| Format | Classic MQTT | Or Sparkplug B — see below. |
| QoS | 1 — at least once | 0 at most once · 1 at least once · 2 exactly once. Classic only in effect: a Sparkplug publisher is saved at 0 whatever is picked — see below. |
| Group id / Edge node id | — | Sparkplug B only, and required for it. |
| Devices | None | Sparkplug B only. None, or One per source connection — see Devices. 1.0.7 |
| Topic prefix | qubiq | Prepended to every tag's namespace path. A live preview under the field shows the topic a real tag would land on. |
| Tags → Publish | All tags | The scope fence. |
| Tags → Except | (nothing) | Subtracted from the scope. |
| Retain last value | on | The broker keeps the last message per topic, so a subscriber that connects later learns the current state instead of waiting for the next change. Classic only — a Sparkplug publisher never retains. |
| Publish on change only | on | Report by exception — see below. |
| …seconds — send anyway if nothing changed | 0 (off) | The heartbeat. Only offered while Publish on change only is on. New in 1.0.6 |
| Never miss a value | off | See Delivery. Refused for Sparkplug B. |
Every one of those switches is honoured at creation, not only on a later edit. Unticking Retain, the loop guard or Publish on change only while creating a publisher used to be discarded silently, and only a subsequent save stuck. 1.0.6
The two fences
Publish and Except sit side by side and are always visible, because how much of the plant a publisher sends is the riskiest fact about it and it should not be behind a click.
- Publish is the scope.
*means the whole plant, stated explicitly. Anything else is a list of namespace paths; a folder covers everything under it, including tags added later — which is what makes "all of Line 1" a rule rather than a snapshot. - Except is subtracted.
Line1in Publish andLine1/Diagnosticsin Except is the shape most real configurations take.
This is deliberate, on both sides of the wire. If an empty list meant "everything", then the riskiest
configuration in the product — every tag in the plant leaving for an outside broker — would be the
one you get from a dropped field. So "everything" is *, and it is never inferred.
The same rule makes excluding * a legitimate way to pause a publisher without losing its
configuration.
There is no per-tag publish flag. A fence does the same job with no per-tag rows, using the same picker for both halves, and it expresses what people actually want: everything except the diagnostics folder. A tag does carry its own reporting cadence — but that decides how often, never whether.
Topics
A tag's topic is the prefix followed by its namespace path — both already use /, so this is a join,
not a translation:
prefix: qubiq
tag path: Plant1/Line2/Oven1/Temperature
topic: qubiq/Plant1/Line2/Oven1/Temperature
Repeated separators collapse and the outer ones are trimmed, so a stray / in a prefix cannot
produce an empty topic level.
A prefix may not contain + or # — those belong to a topic filter, not a topic name, and the
usual way one arrives here is somebody pasting a subscription they tested in an MQTT client. It may
not begin with $ either; that namespace belongs to the broker.
Payload — classic MQTT
One topic per tag, carrying a small JSON envelope:
{ "v": 71.4, "q": "Good", "t": "2026-09-07T04:21:08.132Z", "u": "°C" }
| Key | Meaning |
|---|---|
v | The value. |
q | Quality, verbatim from the source. |
t | The source timestamp, RFC 3339. |
u | Engineering unit. Omitted when the tag has none. |
The keys are short on purpose: this is written once per tag change per broker, and on a plant with
tens of thousands of tags the difference between value and v is real bytes on the wire and real
storage in every retained message. The vocabulary is the one Sparkplug and most SCADA bridges already
use, so a subscriber needs no QUBIQ-specific documentation to read it.
Sparkplug B egress
Choosing Sparkplug B makes the publisher an edge node rather than a topic writer, and the group id and edge node id become part of every topic it sends.
| Message | When |
|---|---|
NBIRTH | On connect, on every reconnect, and again on request. Declares the full metric set with aliases and datatypes. |
NDATA | Every subsequent value, numbered and carrying aliases only. |
NDEATH | Registered with the broker as the Will before the connection exists, so an ungraceful disconnect still announces the node as dead — and, since 1.0.7, published by QUBIQ itself on an orderly stop. |
DBIRTH / DDATA / DDEATH | Only with Devices turned on. 1.0.7 |
The details that matter in practice:
bdSeqpairs a death with the birth it ends. TheNDEATHpublished as the Will carries the same birth/death sequence theNBIRTHannounced, so a stale death left over from a previous session cannot kill the current one.- A host can demand a rebirth — and is told it may.
Node Control/Rebirthon the node'sNCMDtopic makes the publisher re-announce its whole metric set, which is how a host that missed the birth resynchronises without restarting anything. EveryNBIRTHnow also advertises that metric (as Booleanfalse, which is what the specification requires): a conformant host reads the birth to learn which commands a node supports, so before 1.0.7 the recovery path worked but, by the book, a host had no way to know it existed. 1.0.7 - A reconnect re-births. 1.0.7 When the broker link drops
and comes back, the node sends a fresh
NBIRTHbefore any more data. It used to resumeNDATAstraight away — against an alias table the host had thrown out when it processed theNDEATH— and stayed invisible until a host happened to ask for a rebirth.bdSeqdeliberately does not advance on an automatic reconnect: the broker still holds the Will registered at the original connect, and a birth that announced a new number could never be paired with it. - An orderly stop says goodbye. 1.0.7 A broker publishes
the Will only when a client vanishes; a clean disconnect is exactly the case in which it is
discarded. So every restart, config reload or upgrade of QUBIQ used to leave every host believing
the gateway was alive and merely quiet. The node now publishes its own
NDEATHbefore disconnecting.
Sequence numbers wrap at 256 and aliases are fixed for the life of a session. Get either wrong and a compliant host discards the stream silently, because that is what the specification tells it to do — which is why the session state machine is a separate, exhaustively tested piece rather than something the publisher improvises.
One bad tag costs one tag. 1.0.7 A metric that cannot be
encoded is now left out of the message and counted as untyped; the rest
go out. It used to abort the whole payload. For a birth that was permanent — the node believed it
was born, so every later NDATA referred to a dictionary no host had received, and the whole edge
node was dark over a single tag. A birth that was built but never reached the broker (the in-flight
window was full, or the link was down) no longer leaves the node believing a host holds its
dictionary either; the next flush sends it again.
What a Sparkplug publisher does not let you choose Changed in 1.0.7
The specification fixes three things that classic MQTT leaves to you, and a Sparkplug publisher now applies them when it is saved — so the publisher list shows what is actually on the wire:
| Setting | Sparkplug B | Why |
|---|---|---|
| Retain last value | Always off | A retained NBIRTH is served by the broker to every future subscriber — with an alias table from a session that has ended — and a retained NDATA replays a sequence number into a session that has moved on. A host reads either as a corrupt stream. NBIRTH is Sparkplug's own answer to the late subscriber. |
| QoS | 0 for NBIRTH/NDATA (the NDEATH keeps 1) | Sparkplug recovers a lost message through the sequence gap and a rebirth, so an acknowledgement on every data message pays for an assurance the session already gives. |
| Never miss a value | Refused | The buffer delivers on its own schedule, across outages. A buffered message carries a sequence number from a session that may have ended, and replaying it into a new one hands a host a stream it must discard — under a backlog, a rebirth storm. |
The dialog still shows the QoS and Retain controls for a Sparkplug publisher; whatever they say,
the saved publisher is QoS 0 and not retained, and the list row says so.
Before 1.0.7 a Sparkplug publisher sent NBIRTH and NDATA retained, at QoS 1. The first load
after upgrading switches it to not-retained QoS 0. Messages the broker already holds as retained
from before the upgrade are not cleared by QUBIQ: a host that subscribes to spBv1.0/# can
still be handed one old birth per edge node. Clear them with your broker's tooling (an empty
retained message on each such topic). A Sparkplug publisher saved with Never miss a value on
is refused the next time it is saved or switched on, until that is turned off.
Values that went out wrong Changed in 1.0.7
Four value shapes reached a Sparkplug host plausible-looking and wrong, with nothing reporting a problem. (The classic JSON envelope carries the value as it is and was not affected.)
| Tag | Went out as | Now |
|---|---|---|
| Boolean whose value reaches the publisher as an integer — which is how the internal stream carries any integer source | Always false — a running motor reported stopped | Non-zero is true, as every PLC means it |
| DateTime carrying a text timestamp (OPC UA, expressions, every JSON-sourced protocol) | 1970 — which a host reads as a real, absurdly stale reading | The timestamp it names |
| JSON | Go syntax, map[a:1], which no subscriber can parse | The JSON document |
| Raw bytes | Their decimal values, [104 101 108] | The text they hold |
The Boolean and DateTime fixes change what a host receives for the same tag after upgrading. The same coercion serves writes to a Sparkplug device, so they are corrected there too — see MQTT & Sparkplug B.
Devices: one per source connection New in 1.0.7
Without Devices, every tag is a metric on the edge node and the only death a host can see is
NDEATH — the whole gateway gone. So when one PLC behind QUBIQ drops, the publisher stays healthy
and that PLC's tags simply stop changing, which a host cannot tell apart from a process standing
still.
Devices → One per source connection gives each connection that feeds the fence its own Sparkplug
device under the edge node, with its own DBIRTH, DDATA and DDEATH:
spBv1.0/Plant1/DBIRTH/Gateway1/Line-1-Oven-PLC
spBv1.0/Plant1/DDATA/Gateway1/Line-1-Oven-PLC
spBv1.0/Plant1/DDEATH/Gateway1/Line-1-Oven-PLC
| The device goes offline | When its connection is reported disconnected or failed. A DDEATH names it; the edge node and every other device keep publishing. |
| It comes back | That device alone re-births. The node does not, because an NBIRTH would reset every other device's dictionary for nothing. |
| A connection that is merely stale | Connected, nothing arriving. The device stays alive — a DDEATH would claim it is gone, which is false, and the recovery would flap it on and off forever. |
| A connection is deleted | Its device dies and is forgotten; the node re-births so its dictionary no longer names it. |
| Internal and expression tags | Have no source connection, so they stay on the edge node, exactly as before. They do not stop publishing. |
| A connection with no tag in the fence | Is not a device. A device is a connection that has produced a metric for this publisher. |
The device id is the connection's name, made safe for a topic. A / would split the level in
two and +/# are wildcards, so those — and spaces — become -, and runs collapse: Line 1 / Oven PLC publishes as Line-1-Oven-PLC. A name with nothing usable left falls back to the connection
id. Two connections that come out the same both get a suffix of their connection id's first
eight characters — never just the second one, because which counts as "second" would depend on
which produced a value first, and the same plant would publish different ids across a restart.
The preview is the real answer. Choosing the option shows, inside it, the table the fence actually splits into — each device id (with the connection's own name beside it where the two differ), its protocol and its tag count, plus a final edge node row for internal and expression tags that is always present, including at zero. The server works it out with the same function the publisher uses, so a collision suffix in the preview is one you will see on the broker. Past eight devices the table scrolls and a filter appears, matching id, name or protocol.
None is the default, and it is what every publisher created before 1.0.7 loads as — nothing changes until you choose otherwise. Devices are a Sparkplug concept; a classic publisher refuses the setting, since it has one topic per tag and no hierarchy for a device to sit in.
QUBIQ's edge node answers NCMD rebirth requests. It does not act on DCMD — a host cannot write
to a tag through a device.
Delivery: live, or buffered
Never miss a value is the one switch that changes what happens to a value while the broker is away, and it is a genuine trade rather than a quality setting.
| Off — live (default) | On — buffered | |
|---|---|---|
| How it publishes | Asynchronously, up to 256 un-acked messages in flight | One at a time, in order, blocking on each ack |
| When the broker cannot keep up | The value is dropped; the newest wins | The value waits in a durable buffer |
| Can it fall behind? | No — it stays live | Yes, and that is what the buffer is for |
| Right for | Dashboards and HMIs, where the current value is the whole point | A consumer doing arithmetic on every sample — totalisers, billing, batch records |
Live mode leans on Retain last value to cover the gap: a subscriber that connects after a drop still gets the current value from the broker's retained message.
Buffered mode is at-least-once, not exactly-once. A redelivery after an ack that was lost in flight republishes a value the broker already has, which for a tag stream is harmless — the same value on the same topic. Nothing here pretends otherwise.
Buffered mode is classic MQTT only; a Sparkplug publisher refuses it — see above.
One publisher's trouble stays its own. 1.0.7 Every publisher on the gateway is fed from one internal stream, and two things used to let a single publisher slow all of them down:
- A buffered publisher wrote each value to its durable buffer on that stream, waiting up to five seconds for each. One buffered publisher therefore throttled every other publisher on the box. It now hands the value to a queue with its own writer; the value is still durable before it is sent.
- While a broker connection was reconnecting, a live publisher's sends did not fail — they queued, and after a hundred the next one blocked the stream for the rest of the outage. A send during a reconnect is now counted as dropped at once, which is what live mode means.
Report by exception
Publish on change only is on by default, and it does real work: the internal stream carries the scan rate, not the change rate, so a polled protocol producing a reading every 200 ms would otherwise send 300 identical messages a minute to your broker.
A reading is sent when it says something new, which is two questions and not one:
- Has anything changed — the value, of any type, or the quality. Quality is part of the reading, not metadata about it. With Retain on, suppressing a Good→Bad transition at an unmoved value would leave the broker serving a Good-looking number to every late subscriber for as long as the process stood still, so quality is checked first and on its own.
- Did it move far enough — and only where the tag declares a deadband. A tag already tuned for the historian is already tuned for the broker. A publisher can override that with an absolute deadband in engineering units through the API — absolute, never percent; the percentage form belongs to alarm hysteresis, which is a different concept on a different model.
Changed in 1.0.6 Report by exception was on by default before 1.0.6 and did
nothing for the ordinary tag. The gate was the tag's deadband alone, and a deadband of 0 — which
is the default, and is documented as a threshold for storage — accepted everything; booleans and
strings were never suppressed at all. So most installations have been publishing at the scan rate
whatever the switch said.
The first run after upgrading drops those publishers from scan rate to change rate, which is the intent and a large reduction. It is also a real change for any subscriber that was using message arrival as a liveness signal. That is what the heartbeat below is for.
The heartbeat New in 1.0.6
Report by exception makes silence ambiguous: a process standing still and a dead gateway are both a topic that stopped updating. …seconds — send anyway if nothing changed restores no news is good news without giving the scan rate back — a tag on a 250 ms poll with a 60-second heartbeat costs the broker one message a minute instead of 240.
| Set per | Publisher. 0 is off, and off is the default. |
| Minimum | 10 seconds. |
| What it sends | The value already held — it is not a re-read, so it costs the plant nothing and cannot invent a reading the device never gave. |
| Effect on the rest | None. It touches neither the change baselines nor the storm breaker. |
| Shown | Only while Publish on change only is on. Without it every scan is already a message, so a heartbeat would be noise with no question to answer. |
Classic MQTT only. A Sparkplug host already tracks edge-node liveness through NBIRTH /
NDEATH and the Will, which is the protocol's own answer to the same question and a better one.
Per-tag: how often this tag is sent New in 1.0.6
Cadence is a property of the signal, not of the broker it happens to go to. A counter, a heartbeat or a batch step has to arrive at its scan rate wherever it is sent — and one tag can be in the scope of several publishers at once. So the opt-out lives on the tag, on its Publishing tab:
| Setting | What it does |
|---|---|
| Follow the broker (default) | Each publisher applies its own Publish on change only setting. The tab names what that currently resolves to, so "inherit" is something you can see rather than work out. |
| Only when it changes | Sent when the value moves or its quality does, however often the device is polled — even from a publisher that sends every sample. |
| Every reading | Every reading is sent, even an identical one. Right for a counter, a heartbeat or a batch step, where arriving at all is the signal. |
It is saved with the tag and applies to every publisher below it on that tab. This is a cadence setting, not a coverage one: it cannot put a tag on a broker the fences do not reach, and it cannot take one off.
On a UDT, it now holds for computed members too. 1.0.7 A member template's publish setting and deadband are copied to every instance, but they only took effect on members bound to a device. An internal or expression member ignored both, in every instance of the type, with nothing to show it had.
A type can set it, and an instance can override it. 1.0.8 The member editor has a Publishing tab of its own, and the choice on an instance tag's Publishing tab is kept as an override. Before 1.0.8 nothing on the type set it, and an instance's choice was undone by the next sync. Which broker an instance goes to is still a publisher's scope, not a setting of the type. → What a member carries
Tags that stop arriving Changed in 1.0.7
A publisher keeps a little state for every tag it has sent — the last value, and for Sparkplug its alias. That state used to grow with every tag ever seen and never shrink, and on Sparkplug it was worse than a memory cost: a renamed or deleted tag was re-announced in every future birth, at the value it held when it disappeared.
A tag that has produced no reading at all for about an hour is now forgotten. A tag held back by Publish on change only still counts as arriving, so a steady value is not affected. If a forgotten tag starts producing again, it is simply new: on Sparkplug that is a fresh birth that includes it.
Loop protection
Publishing tags to a broker QUBIQ also reads from is how a feedback loop starts: a value goes out, comes back in as an update, and goes out again. Four layers stand in the way, and the last one is the one that matters.
- Configuration. A cycle the configuration can prove is rejected before it runs.
- Origin exclusion. A value is never published back to the connection it came from. This kills self-echo outright, and the direct leg of an A→B→A pair. A value whose origin cannot be determined is dropped, not passed: an unattributable value is exactly the one that might be a loop, and dropping it costs one sample where publishing it risks a storm.
- Timestamp. A value that is not newer than the last one sent is collapsed. This works for Sparkplug sources; classic MQTT ingest re-stamps values with its own receive time, so a looped classic value always looks newer and this layer cannot fire for it — which is why there is a fourth.
- The storm breaker. Each tag is capped at 50 messages per second per broker by default. A tag that sustains more than that is a feedback loop, not a process — no physical measurement needs 50 updates a second on a broker.
The breaker is a token bucket, so a genuine process event that jumps and settles spends burst allowance and recovers; only a sustained rate trips it. And tripping is sticky: the tag is quarantined on that broker and stays stopped until somebody releases it. A rolling limiter would publish 50 messages a second forever, which is a slower storm rather than no storm, and would never present an operator with a fact to act on.
A quarantined tag shows in the Publishing view and on the tag's own Publishing tab, with a badge on the toolbar counting them. Releasing one needs Edit Connection and is audited.
Permissions
Publishing reuses the connection vocabulary rather than inventing a second one — a publisher is a use of a connection, and anyone who may edit connections can already point one anywhere.
| Action | Permission |
|---|---|
| See publishers, their status and their coverage | View Connections |
| Create a publisher | Create Connection |
| Edit one, turn one on or off, release a quarantine | Edit Connection |
| Delete one | Delete Connection |
Turning a publisher on is what starts plant data leaving the building, so it is gated on edit rather than view, and it is audited on its own rather than as part of a save. → Audit journal
Troubleshooting
| Symptom | Check |
|---|---|
| Publisher is on, nothing arrives at the broker | Is a tag in the Publish fence also caught by Except? The exception wins. The bulk dialog says so before you save; the fences themselves do not. |
| Far fewer messages than before the upgrade | Expected — see the upgrade note. Add a heartbeat if a subscriber was using message arrival as a liveness signal. |
| One tag floods the broker, or one tag has gone quiet | Its own cadence overrides the publisher's. Check How often this tag is sent on the tag's Publishing tab. |
| "Nothing is published to a broker" and the switch will not turn on | The MQTT Driver module is not licensed. Check that the page says so in the server's own words — a failed request now reports itself as a failed request and says explicitly that it is not a licensing problem. 1.0.6 |
| Stopping a folder reports no change | Fixed in 1.0.6 — a stop now owns the whole subtree. On an earlier build, stop each tag. |
| Status shows nothing at all | The bridge service is not answering — check it under Administration → Services. |
| A tag stopped publishing on its own | The storm breaker quarantined it. Find the loop before releasing it, or it will trip again. |
| Values arrive but a Sparkplug host ignores them | A missed NBIRTH. Send Node Control/Rebirth, and check the group and edge node ids are unique on that broker. On a broker that ran a Sparkplug publisher before 1.0.7, clear the old retained births. |
| A Sparkplug publisher shows QoS 0 and no Retained after saving | Expected — the specification fixes both. See what a Sparkplug publisher does not let you choose. |
| A Sparkplug Boolean or DateTime reads differently after upgrading | The old value was wrong — see values that went out wrong. |
| Status shows untyped | A tag's value could not be given a Sparkplug type. The server log names the tag. |
| A device id is not the connection's name | Characters a topic level cannot hold became -, or two names collided and both got a suffix. The Devices preview shows the exact id. |
| A PLC dropped but its Sparkplug device is still alive | Its connection is stale, not down — a device dies only on a disconnect or failure. |
| A subscriber that joins late sees nothing until the next change | Retain last value is off. |
| Topics have an empty level in them | A stray separator in a namespace name — the join collapses repeats, so this is a name to fix rather than a prefix. |
See also
- MQTT & Sparkplug B connections — the inbound half
- Tags — deadband, quality and units, all of which travel with a published value
- Store and forward — the same durable-buffer idea, for database writes