Reference in New Issue
Block a user
Delete Branch "feat/otopcua-v3-cutover"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refs #14. Fixes #17.
What
The OtOpcUa v3.0 cutover work for ScadaBridge's Data Connection Layer. Investigation established that #14 is a data re-bind, not a code cutover — there is zero OtOpcUa coupling in
src/(the DCL is a generic OPC UA client). What the code actually needed were two structural fixes.#14 — bind by namespace URI, not baked index (
d32050da)A stored
ns=<index>reference is only meaningful against one server's namespace table; a server that adds/removes/reorders a namespace silently re-points every binding — worst case to a different namespace with a colliding identifier, resolving to the wrong node with Good quality. ScadaBridge stored no URI anywhere, so nothing could detect it. OtOpcUa v3 makes it concrete: v2's sole custom namespace and v3'srawnamespace both sit at index 2.OpcUaNodeReferenceis the single translation seam.Resolve()accepts bothns=<index>(existing bindings, unchanged) and the durablensu=<uri>, mapping the URI to the live index at every parse site (subscribe/read/write/alarm/browse). An unpublished URI throws naming the URI rather than binding to whatever occupies the index; asvr=reference is rejected. The browser emitsToDurable(), so newly-authored bindings are index-proof — which also closes a round-trip gap where browse emitted anExpandedNodeIdstringNodeId.Parsecouldn't read back.#17 — route native alarms by binding identity, not event name (
846cc5d9)A native OPC UA alarm
SourceReferencehad to be a NodeId (to open the monitored item) and a name prefix (to route the event) at once — no string is both, so NodeId-form bindings dropped every transition. Since each feed is opened for one binding, the adapter now tags each transition's routing identity with the binding string verbatim (via the pureOpcUaAlarmMapper.BuildIdentity), so routing exact-matches regardless ofns=/nsu=form. MxGateway is untouched; the readable per-condition key is unchanged. Unblocked by lmxopcua#473.Verification
DataConnectionLayer.Tests: 253 green (12 new nsu= tests + 6 new alarm tests); every new alarm test proven red against the unfixed behaviour, then green.main(without the #15 work) to confirm no hidden dependency.Still open (not in this PR)
A native OPC UA alarm source's SourceReference has to be two things at once: it is parsed as a NodeId to open the monitored item, and matched as a plain-name prefix against the event's SourceName to route the transition to an instance. No string is both, so a NodeId-form binding subscribed correctly and then silently dropped every transition — "pymodbus/plc/HR200".StartsWith("nsu=...;s=pymodbus/plc/HR200") is false. Only the empty (Server-object) binding worked, because StartsWith("") matches everything, which is why the sole live smoke test never caught it. Each OPC UA alarm feed is opened for exactly one binding, so every transition on it belongs to that binding. The adapter now tags each transition's routing identity (SourceObjectReference) with the binding string verbatim via the pure OpcUaAlarmMapper.BuildIdentity, making DataConnectionActor's routing key an exact match regardless of whether the binding is stored as ns=<index> or the durable nsu=<uri> form. The Server-object aggregate feed keeps an empty routing identity, so it reaches only "mirror everything" subscribers and never leaks into a specific-node binding. The per-condition SourceReference key stays the readable SourceName.ConditionName, so persistence and display are unchanged, and MxGateway is untouched — its bindings are names and its mapper already emits matching names. Unblocked by lmxopcua#473 (OtOpcUa now populates SourceNode/SourceName/EventType on conditions); SourceName is the RawPath, so the per-condition key is unique. Live end-to-end verification against native alarms still needs a v3 rig. Fixes: Gitea #17