Files
lmxopcua/docs/AddressSpace.md
T

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 authority
  • src/Core/ZB.MOM.WW.OtOpcUa.Commons/Types/RawPaths.cs — RawPath/segment rules
  • src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs, SdkAddressSpaceSink.cs — SDK materialisation
  • src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceComposer.cs, AddressSpaceApplier.cs — compose and apply
  • src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/ITagDiscovery.cs, IAddressSpaceBuilder.cs, DriverAttributeInfo.cs — discovery-side contracts