Merge review R5 fixes: MTConnect session publish + generation bump atomic, superseded pumps joined on stop; MQTT writes encode the birth's own Sparkplug datatype (unsupported birth types refused BadNotSupported); AbLegacy dead init join removed + Dispose join test; MQTT QoS1 and Postgres key-normalisation documented
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+18
-1
@@ -467,10 +467,27 @@ Both publish with `retain: false`. A **Plain** write goes at **QoS 1** (STAB-26)
|
||||
once the broker PUBACKs it, so `Good` means "the broker accepted it", and a broker refusal or a connection
|
||||
that died under the send is `Bad`. **Sparkplug commands stay at QoS 0** — a redelivered NCMD/DCMD would be
|
||||
re-applied. A Sparkplug metric the tag did not give a `dataType` is commanded with the type its live birth
|
||||
certificate declared (STAB-25), the same authored-beats-birth precedence reads use. Retain is the one that
|
||||
certificate declared (STAB-25), the same authored-beats-birth precedence reads use — using the birth's **own**
|
||||
Sparkplug datatype, not the lossy driver-type mapping, so Int8/UInt8/Text/Uuid/Bytes/File metrics are commanded
|
||||
with their real type; a birth type the encoder cannot represent (any `*Array`, DataSet, Template, …) is refused
|
||||
`BadNotSupported` rather than sent as another type. Retain is the one that
|
||||
matters and is deliberately not configurable: a retained command would be replayed by the broker to the device on *every* future
|
||||
subscribe — including the device's own reconnect — so one stale write would be re-applied forever.
|
||||
|
||||
**Plain QoS 1 is at-least-once.** Know the consequences before pointing a Plain `writeTopic` at a non-idempotent
|
||||
device topic:
|
||||
|
||||
- A lost PUBACK can make a device subscribed at QoS ≥ 1 with a persistent session receive the command **twice**
|
||||
(at QoS 0 delivery was downgraded and a duplicate could not happen).
|
||||
- A lost PUBACK, or the `ConnectTimeoutSeconds` (15 s) deadline, reports `Bad` and the node is **reverted**, even
|
||||
though the broker may already have delivered the write. `Good` is "the broker accepted it", `Bad` is "not
|
||||
confirmed", never "not delivered".
|
||||
- Each write holds `DriverInstanceActor`'s mailbox until the PUBACK arrives — up to 15 s per write, one after
|
||||
another; comparable to a Modbus round trip.
|
||||
- A broker advertising MQTT 5 Maximum QoS 0 will disconnect on every Plain write.
|
||||
|
||||
There is no per-tag QoS option; make the receiving topic idempotent (absolute setpoints, not increments).
|
||||
|
||||
### `writeTopic` is separate from `topic`, and its absence is meaningful
|
||||
|
||||
A tag commonly reads `…/state` and writes `…/set`, so the destination is its own authored field. A
|
||||
|
||||
@@ -95,6 +95,13 @@ that holds an INTEGER station id can be authored either `"7"` or `7`. A JSON obj
|
||||
is not a value and is refused. On **PostgreSQL** a string bind is sent untyped (`NpgsqlDbType.Unknown`,
|
||||
`PostgresDialect.ConfigureParameter`, STAB-24) so the server infers its type from the compared column;
|
||||
bound as `text`, a `"7"` against an `integer` key fails with `operator does not exist: integer = text`.
|
||||
|
||||
⚠️ **Known limitation — key normalisation.** Row slice-back matches the returned key cell by its *text* form
|
||||
(`Convert.ToString`, ordinal), while the server now matches by the column's inferred type. An authored key the
|
||||
server treats as equal but whose text differs therefore selects a row that the driver then cannot find, and the
|
||||
tag reads **no-row**: `"007"` against an `integer` column (returns `7`), `"1.50"` against `numeric` (returns
|
||||
`1.5`), and an upper-case GUID against a `uuid` column (returned lower-case). Author keys in the column's
|
||||
canonical text form. SQL Server behaves the same way.
|
||||
(They were read asymmetrically until 2026-08-10, so a numerically
|
||||
authored `keyValue` refused the tag while the identically-bound `whereValue` was served — and the
|
||||
deploy gate reported the key as *missing* when it was right there in the blob.)
|
||||
|
||||
Reference in New Issue
Block a user