Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
5.7 KiB
Address Space
The server serves two OPC UA namespaces under one shared root folder. Identity is owned by
V3NodeIds (src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/V3NodeIds.cs) and AddressSpaceRealm; the tree is
built from the deploy artifact (see docs/IncrementalSync.md), not by driver discovery at runtime.
| Realm | Namespace URI | Subtree | NodeId s= identifier |
|---|---|---|---|
Raw |
https://zb.com/otopcua/raw |
/raw device tree: Folder → Driver → Device → TagGroup → Tag |
the node's RawPath (e.g. Plant/Modbus/dev1/Speed) |
Uns |
https://zb.com/otopcua/uns |
UNS tree: Area → Line → Equipment → signal | slash-joined Area/Line/Equipment[/EffectiveName] |
OtOpcUaNodeManager registers both URIs (raw is index NamespaceIndexes[0], UNS [1]). The retired
single namespace https://zb.com/otopcua/ns and the EquipmentNodeIds scheme no longer exist. The realm
travels beside the identifier at every sink seam (IOpcUaAddressSpaceSink); it is never parsed out of the
id string. Authoring and semantics: docs/Raw.md, docs/Uns.md.
Root folder
OtOpcUaNodeManager.CreateAddressSpace creates one shared root FolderState — identifier and browse
name OtOpcUa (V3NodeIds.SharedRootFolderId), minted in the raw namespace — under the standard
Objects folder with an Organizes reference. Top-level Raw folders and top-level UNS Areas both parent
under it. The identifier is a display name, not a path segment; the data-plane authorizer maps it onto the
trie's zero-segment realm-root scope (docs/security.md). The application URI is
OpcUaApplicationHostOptions.ApplicationUri (default urn:OtOpcUa); there is no per-driver namespace.
NodeId scheme
Segments are validated by RawPaths.ValidateSegment (no embedded /, no leading/trailing whitespace;
ordinal, case-sensitive). V3NodeIds.Raw(rawPath) is a pass-through; V3NodeIds.Uns(...) /
UnsChild(parent, segment) build UNS ids.
| Node | Realm | s= identifier |
Example |
|---|---|---|---|
| Shared root | Raw | OtOpcUa |
OtOpcUa |
| Raw folder / driver / device / tag group / tag | Raw | its own RawPath | Plant/Modbus/dev1/Speed |
| UNS Area / Line / Equipment folder | Uns | Area, Area/Line, Area/Line/Equipment |
packaging/line-1/press-07 |
UNS reference variable (UnsTagReference), virtual tag, scripted alarm |
Uns | Area/Line/Equipment/EffectiveName |
packaging/line-1/press-07/speed |
Area, Line and Equipment names must match ^[a-z0-9-]{1,32}$ (docs/Uns.md). EffectiveNames are unique per
equipment across references, virtual tags and scripted alarms.
One source, fanned to both namespaces
Every device value has exactly one source: the raw tag's node, keyed by RawPath. A UnsTagReference
projects that raw tag into an equipment as a UNS variable that carries an Organizes reference to its raw
node and mirrors it. DriverHostActor fans one driver publish for a RawPath to the raw NodeId and every
referencing UNS NodeId with identical value, quality and timestamp. Writes via either NodeId route to the
same driver reference under the same realm-qualified WriteOperate gate. Native alarms materialise once at
the raw tag (ConditionId = RawPath) and fan through notifiers to the raw device folder and every
referencing equipment folder. Both NodeIds register the same historian tagname.
Materialisation
AddressSpaceComposer turns the deployment artifact into an AddressSpaceComposition;
AddressSpaceApplier (both in src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/) walks it (MaterialiseHierarchy,
MaterialiseRawSubtree, MaterialiseUnsReferences, MaterialiseEquipmentVirtualTags,
MaterialiseScriptedAlarms, ...) into the write-only IOpcUaAddressSpaceSink, whose production binding
SdkAddressSpaceSink forwards to OtOpcUaNodeManager, the CustomNodeManager2 that owns the
FolderState / BaseDataVariableState instances. Variables are created through EnsureVariable:
ValueRank is OneDimension with ArrayDimensions=[arrayLength] for array tags and Scalar otherwise;
AccessLevel carries the write bit only for writable nodes (array nodes only when the driver can write the
array); Historizing plus the HistoryRead bit are set when a historian tagname is supplied.
The nodes start BadWaitingForInitialData until the first read or subscription push. Datatype names resolve
through OtOpcUaNodeManager.ResolveBuiltInDataType; each driver first maps its native type into
DriverDataType (Galaxy: Driver.Galaxy/Browse/DataTypeMap.cs).
Driver discovery is the browse picker, not the address space
ITagDiscovery.DiscoverAsync (streaming into IAddressSpaceBuilder: Folder / Variable /
AddProperty, each variable described by a DriverAttributeInfo) feeds the /raw browse-commit flow via
Commons/Browsing/DiscoveryDriverBrowser: the operator browses a device, picks leaves and commits them as
raw tags, which then reach the server as an ordinary deployment. Drivers that signal backend change via
IRediscoverable only raise an operator "re-browse" prompt — the served tree never changes on its own.
See docs/IncrementalSync.md. GenericDriverNodeManager (Core/OpcUa/) is leftover scaffolding with no
production caller.
Key source files
src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/V3NodeIds.cs,AddressSpaceRealm.cs— identity authoritysrc/Core/ZB.MOM.WW.OtOpcUa.Commons/Types/RawPaths.cs— RawPath/segment rulessrc/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs,SdkAddressSpaceSink.cs— SDK materialisationsrc/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceComposer.cs,AddressSpaceApplier.cs— compose and applysrc/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/ITagDiscovery.cs,IAddressSpaceBuilder.cs,DriverAttributeInfo.cs— discovery-side contracts