Full OPC UA server on .NET Framework 4.8 (x86) exposing AVEVA System Platform Galaxy tags via MXAccess. Mirrors Galaxy object hierarchy as OPC UA address space, translating contained-name browse paths to tag-name runtime references. Components implemented: - Configuration: AppConfiguration with 4 sections, validator - Domain: ConnectionState, Quality, Vtq, MxDataTypeMapper, error codes - MxAccess: StaComThread, MxAccessClient (partial classes), MxProxyAdapter using strongly-typed ArchestrA.MxAccess COM interop - Galaxy Repository: SQL queries (hierarchy, attributes, change detection), ChangeDetectionService with auto-rebuild on deploy - OPC UA Server: LmxNodeManager (CustomNodeManager2), LmxOpcUaServer, OpcUaServerHost with programmatic config, SecurityPolicy None - Status Dashboard: HTTP server with HTML/JSON/health endpoints - Integration: Full 14-step startup, graceful shutdown, component wiring 175 tests (174 unit + 1 integration), all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.1 KiB
OPC UA Server Layout — Build Plan
Overview
Extract the Galaxy object hierarchy and tag definitions from the ZB (Galaxy Repository) database to construct an OPC UA server address space. The root node is hardcoded as ZB.
Step 1: Build the Browse Tree
Run queries/hierarchy.sql to get all deployed automation objects and their parent-child relationships.
For each row returned:
parent_gobject_id = 0→ child of the root ZB nodeis_area = 1→ create as an OPC UA folder node (organizational)is_area = 0→ create as an OPC UA object node (container for tags)- Use
browse_nameas the OPC UA BrowseName/DisplayName - Store
gobject_idandtag_namefor attribute lookup and tag reference translation
Build the tree by matching each row's parent_gobject_id to another row's gobject_id. The result is:
ZB (root, hardcoded)
└── DEV (folder, is_area=1)
├── DevAppEngine (object)
├── DevPlatform (object)
└── TestArea (folder, is_area=1)
├── DevTestObject (object)
└── TestMachine_001 (object)
├── DelmiaReceiver (object, browse_name from contained_name)
└── MESReceiver (object, browse_name from contained_name)
Step 2: Attach Attributes as Tag Nodes
Run queries/attributes.sql to get all user-defined attributes for deployed objects.
For each attribute row:
- Match to the browse tree via
gobject_id - Create an OPC UA variable node under the matching object node
- Use
attribute_nameas the BrowseName/DisplayName - Use
full_tag_referenceas the runtime tag path for read/write operations - Map
mx_data_typeto OPC UA built-in types:
| mx_data_type | Description | OPC UA Type |
|---|---|---|
| 1 | Boolean | Boolean |
| 2 | Integer | Int32 |
| 3 | Float | Float |
| 4 | Double | Double |
| 5 | String | String |
| 6 | Time | DateTime |
| 7 | ElapsedTime | Double (seconds) or Duration |
- If
is_array = 1, create the variable as an array with rank 1 and dimension fromarray_dimension
Step 3: Monitor for Changes
Poll queries/change_detection.sql on a regular interval (e.g., every 30 seconds).
SELECT time_of_last_deploy FROM galaxy;
Compare the returned time_of_last_deploy to the last known value:
- No change → do nothing
- Changed → a deployment occurred; re-run Steps 1 and 2 to rebuild the address space
This handles objects being deployed, undeployed, added, or removed.
Connection Details
See connectioninfo.md for database connection parameters and sqlcmd usage.
sqlcmd -S localhost -d ZB -E -Q "YOUR QUERY HERE"
Query Files
| File | Purpose |
|---|---|
queries/hierarchy.sql |
Deployed object hierarchy with browse names and parent relationships |
queries/attributes.sql |
User-defined attributes with data types and array dimensions |
queries/attributes_extended.sql |
All attributes (system + user-defined) with data types and array dimensions |
queries/change_detection.sql |
Poll galaxy.time_of_last_deploy for deployment changes |