Provides technical documentation covering OPC UA server, address space, Galaxy repository, MXAccess bridge, data types, read/write, subscriptions, alarms, historian, incremental sync, configuration, dashboard, service hosting, and CLI tool. Updates README with component documentation table. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.5 KiB
Address Space
The address space maps the Galaxy object hierarchy and attribute definitions into an OPC UA browse tree. LmxNodeManager builds the tree from data queried by GalaxyRepositoryService, while AddressSpaceBuilder provides a testable in-memory model of the same structure.
Root ZB Folder
Every address space starts with a single root folder node named ZB (NodeId ns=1;s=ZB). This folder is added under the standard OPC UA Objects folder via an Organizes reference. The reverse reference is registered through MasterNodeManager.AddReferences because BuildAddressSpace runs after CreateAddressSpace has already consumed the external references dictionary.
The root folder has EventNotifier = SubscribeToEvents enabled so alarm events propagate up to clients subscribed at the root level.
Area Folders vs Object Nodes
Galaxy objects fall into two categories based on template_definition.category_id:
- Areas (
category_id = 13) becomeFolderStatenodes withFolderTypetype definition andOrganizesreferences. They represent logical groupings in the Galaxy hierarchy (e.g., production lines, cells). - Non-area objects (AppEngine, Platform, UserDefined, etc.) become
BaseObjectStatenodes withBaseObjectTypetype definition andHasComponentreferences. These represent runtime automation objects that carry attributes.
Both node types use contained_name as the browse name. When contained_name is null or empty, tag_name is used as a fallback.
Variable Nodes for Attributes
Each Galaxy attribute becomes a BaseDataVariableState node under its parent object. The variable is configured with:
- DataType -- Mapped from
mx_data_typeviaMxDataTypeMapper(see DataTypeMapping.md) - ValueRank --
OneDimension(1) for arrays,Scalar(-1) for scalars - ArrayDimensions -- Set to
[array_dimension]when the attribute is an array - AccessLevel --
CurrentReadOrWriteorCurrentReadbased on security classification, withHistoryReadadded for historized attributes - Historizing -- Set to
truefor attributes with aHistoryExtensionprimitive - Initial value --
nullwithStatusCode = BadWaitingForInitialDatauntil the first MXAccess callback delivers a live value
Primitive Grouping
Galaxy objects can have primitive components (e.g., alarm extensions, history extensions) that attach sub-attributes to a parent attribute. The address space handles this with a two-pass approach:
First pass: direct attributes
Attributes with an empty PrimitiveName are created as direct variable children of the object node. If a direct attribute shares its name with a primitive group, the variable node reference is saved for the second pass.
Second pass: primitive child attributes
Attributes with a non-empty PrimitiveName are grouped by that name. For each group:
- If a direct attribute variable with the same name already exists, the primitive's child attributes are added as
HasComponentchildren of that variable node. This merges alarm/history sub-attributes (e.g.,InAlarm,Priority) under the parent variable they describe. - If no matching direct attribute exists, a new
BaseObjectStatenode is created with NodeIdns=1;s={TagName}.{PrimitiveName}, and the primitive's attributes are added under it.
This structure means that browsing TestMachine_001/SomeAlarmAttr reveals both the process value and its alarm sub-attributes (InAlarm, Priority, DescAttrName) as children.
NodeId Scheme
All node identifiers use string-based NodeIds in namespace index 1 (ns=1):
| Node type | NodeId format | Example |
|---|---|---|
| Root folder | ns=1;s=ZB |
ns=1;s=ZB |
| Area folder | ns=1;s={tag_name} |
ns=1;s=Area_001 |
| Object node | ns=1;s={tag_name} |
ns=1;s=TestMachine_001 |
| Scalar variable | ns=1;s={tag_name}.{attr} |
ns=1;s=TestMachine_001.MachineID |
| Array variable | ns=1;s={tag_name}.{attr} |
ns=1;s=MESReceiver_001.MoveInPartNumbers |
| Primitive sub-object | ns=1;s={tag_name}.{prim} |
ns=1;s=TestMachine_001.AlarmPrim |
For array attributes, the [] suffix present in full_tag_reference is stripped from the NodeId. The full_tag_reference (with []) is kept internally for MXAccess subscription addressing. This means MESReceiver_001.MoveInPartNumbers[] in the Galaxy maps to NodeId ns=1;s=MESReceiver_001.MoveInPartNumbers.
Topological Sort
The hierarchy query returns objects ordered by parent_gobject_id, tag_name, but this does not guarantee that a parent appears before all of its children in all cases. LmxNodeManager.TopologicalSort performs a depth-first traversal to produce a list where every parent is guaranteed to precede its children. This allows the build loop to look up parent nodes from _nodeMap without forward references.
Incremental Sync
On address space rebuild (triggered by a Galaxy deploy change), SyncAddressSpace uses AddressSpaceDiff to identify which gobject_id values have changed between the old and new snapshots. Only the affected subtrees are torn down and rebuilt, preserving unchanged nodes and their active subscriptions. Affected subscriptions are snapshot before teardown and replayed after rebuild.
If no previous state is cached (first build), the full BuildAddressSpace path runs instead.
Key source files
src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxNodeManager.cs-- Node manager withBuildAddressSpace,SyncAddressSpace, andTopologicalSortsrc/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/AddressSpaceBuilder.cs-- Testable in-memory model builder