OtOpcUa v3.0 dual-namespace cutover — rebind Data Connection Layer NodeId/namespace #14
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
Summary
OtOpcUa v3.0 (merged to
master2026-07-16,lmxopcuaPR #472, mergeec6598ce) replaces its single OPC UA namespace + NodeId scheme with a two-subtree, dual-namespace address space. This is the wire contract ScadaBridge's Data Connection Layer binds against, so ScadaBridge needs a cutover pass on its OtOpcUa OPC UA client bindings before it can consume v3.0.What changed on the OtOpcUa server
https://zb.com/otopcua/nshttps://zb.com/otopcua/raw+https://zb.com/otopcua/unsns=<raw>;s=<RawPath>where RawPath =Folder/…/Driver/Device/TagGroup/…/Tag(slash-joined, ordinal, case-sensitive)EquipmentNodeIds={equipmentId}/{folderPath}/{name}ns=<uns>;s=<Area>/<Line>/<Equipment>/<EffectiveName>EquipmentNodeIdsschemeSemantics ScadaBridge can rely on:
Organizes-references its backing raw node (cross-tree link is browseable).ReportEvent) to the raw device folder and every referencing equipment folder — exactly one Server-object copy; ack/confirm/shelve key on ConditionId = RawPath.Impact on ScadaBridge (what to review)
…/otopcua/ns) — now resolve both…/rawand…/unsfrom the serverNamespaceArray.EquipmentNodeIds({equipmentId}/{folderPath}/{name}) shape.References
lmxopcuamaster @ ec6598cedocs/plans/2026-07-15-raw-uns-two-subtree-v3-design.md(OtOpcUa repo)docs/plans/2026-07-16-v3-batch4-PR.md(OtOpcUa repo)scadaproj/CLAUDE.mdOtOpcUa entry (updated 2026-07-16)Filed as the post-merge cross-repo coordination step from the OtOpcUa v3 program (Batches 1–4 complete).
Investigation: the premise above is largely wrong — re-scoping
Investigated both sides before starting. There is no OtOpcUa coupling in ScadaBridge to rebind. Grepping
src/,docker/,deploy/,infra/and all config forotopcua,EquipmentNodeIds,equipment,uns,rawreturns zero hits in any source, config or Docker file (only prose indocs/plans/and one telemetry comment).The Data Connection Layer is a fully generic OPC UA client: it stores an opaque string and hands it to
NodeId.Parseat the SDK boundary (RealOpcUaClient.cs:413/831/851), knowing only spec-fixedns=0NodeIds. So three of the five "Impact" bullets above — hardcoded namespace URI,EquipmentNodeIdsparsing, browse-path assumptions — describe code that does not exist.OtOpcUa's own design doc agrees (
2026-07-15-raw-uns-two-subtree-v3-design.md:381-395):What is actually true
1. This is a data re-bind, and there is no code to write for it. Every binding bakes
ns=2into a stored string, and ScadaBridge stores no namespace URI anywhere — so there is no code to fix, and equally no code that could detect the rot. Affected columns:TemplateAttribute.DataSourceReference,InstanceConnectionBinding.DataSourceReferenceOverride,TemplateNativeAlarmSource.SourceReference,InstanceNativeAlarmSourceOverride.SourceReferenceOverride, plusOpcUaHeartbeatConfig.TagPathand any exported Transport bundles / CSVs in flight. Re-authoring needs a live v3 rig; the design states there is no reliable automatic old→new mapping (greenfield).Trap: v3's Raw namespace also lands at ns=2 — the same index v2's single namespace used. Old bindings will not reliably fail loudly.
2. Corrections to the v3 contract as described above. Verified against the OtOpcUa source:
ns=3;s=<EquipmentId>); tag references use name paths (ns=3;s=<Area>/<Line>/<Equipment>/<EffectiveName>); VirtualTags keep the old{equipmentId}/{folderPath}/{name}shape with only the namespace moving 2→3 (AddressSpaceApplier.cs:414-427, 645-655, 794).Objects/OtOpcUa(ns=2;s=OtOpcUa) — there is no/Rawand no/UNStop-level folder. The "two subtrees" are realised by namespace index only (OtOpcUaNodeManager.cs:3167-3184,:122-123).BrowseNameis the full path string, not the leaf name (:1595,:1689,:3193).DisplayNamecarries the friendly leaf.3. Native alarms are blocked by a server-side defect, not by NodeId shape. OtOpcUa v3 emits null
SourceNode,SourceNameandEventTypeon every condition (OtOpcUaNodeManager.cs:781-809never assigns them; the SDK's auto-fillingBaseEventState.Initializeoverload is never called). ScadaBridge selects exactly those fields (RealOpcUaClient.cs:464) and derives identity fromSourceNamefalling back toSourceNode(:749-751), so its key degrades to".HR200". Filed as lmxopcua#473.ScadaBridge has its own half of this: it never selects
ConditionId, yetConditionId(= RawPath) is the identity both Part 9 and OtOpcUa'sdocs/AlarmTracking.md:47key on. Because the server resolvesConditionIdfrom the condition node itself rather than a populated field, re-keying onConditionIdwould fix alarm identity here without waiting for lmxopcua#473 — only condition-type filtering needs the server fix (or anOfTypewhere-clause instead of reading the nullEventTypefield).Related latent defect, independent of v3:
SourceReferenceis documented as a NodeId (TemplateNativeAlarmSource.cs:22) and parsed as one (RealOpcUaClient.cs:478), but transitions are routed bySourceObjectReference.StartsWith(sourceRef)(DataConnectionActor.cs:1978) against the A&C SourceName — so a NodeId-form source drops every transition. Only the empty-string (Server-object) path is smoke-tested (OpcUaAlarmLiveSmokeTests.cs:64), which makes the prefix match trivially true.Re-scoped plan
SourceNode/SourceName/EventTypeon conditionsnsu=URI-qualified bindings + resolve URI→index at connectExpandedNodeIdstringsNodeId.Parsecannot re-parse (RealOpcUaClient.cs:1069vs the correct:1126)ConditionId; type filter viaOfTypeThis issue now tracks the two code items in bold: making bindings namespace-table-proof (
nsu=), which is the structural fix both OtOpcUa's design doc and this analysis land on, and the browse round-trip gap.