Files
lmxopcua/docs/reqs/OpcUaServerReqs.md
Joseph Doherty a7576ffb38 Implement LmxOpcUa server — all 6 phases complete
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>
2026-03-25 05:55:27 -04:00

10 KiB

OPC UA Server — Component Requirements

Parent: HLR-001, HLR-002, HLR-004

OPC-001: Server Endpoint

The OPC UA server shall listen on a configurable TCP port (default 4840) using the OPC Foundation .NET Standard stack.

Acceptance Criteria

  • Server starts and accepts TCP connections on the configured port.
  • Port is read from appsettings.json under OpcUa:Port; defaults to 4840 if absent.
  • Endpoint URL format: opc.tcp://<hostname>:<port>/LmxOpcUa.
  • If the port is in use at startup, log an Error and fail to start (do not silently pick another port).
  • Security policy: None (no certificate validation). This is an internal plant-floor service.

Details

  • Configurable items: port (default 4840), endpoint path (default /LmxOpcUa), server application name (default LmxOpcUa).
  • Server shall use the OPCFoundation.NetStandard.Opc.Ua.Server NuGet package.
  • On startup, log the endpoint URL at Information level.

OPC-002: Address Space Structure

The server shall create folder nodes for areas and object nodes for automation objects, organized in the same parent-child hierarchy as the Galaxy.

Acceptance Criteria

  • The root folder node has BrowseName ZB (hardcoded Galaxy name).
  • Objects where is_area = 1 are created as FolderType nodes (organizational).
  • Objects where is_area = 0 are created as BaseObjectType nodes.
  • Parent-child relationships use Organizes references (for areas) and HasComponent references (for contained objects).
  • A client browsing Root → Objects → ZB → DEV → TestArea → TestMachine_001 → DelmiaReceiver sees the same structure as gr/layout.md.

Details

  • NodeIds use a string-based identifier scheme: ns=1;s=<tag_name> for object nodes, ns=1;s=<tag_name>.<attribute_name> for variable nodes.
  • Infrastructure objects (AppEngines, Platforms) are included in the tree but may have no variable children.
  • When contained_name is null or empty, fall back to tag_name as the BrowseName.

OPC-003: Variable Nodes for Attributes

Each user-defined attribute on a deployed object shall be represented as an OPC UA variable node under its parent object node.

Acceptance Criteria

  • Each row from attributes.sql creates one variable node under the matching object node (matched by gobject_id).
  • Variable node BrowseName and DisplayName are set to attribute_name.
  • Variable node stores full_tag_reference as its runtime MXAccess address.
  • Variable nodes have AccessLevel = CurrentRead | CurrentWrite (3) by default.
  • Objects with no user-defined attributes still appear as object nodes with zero children.

Details

  • Security classification from the attributes query is noted but not enforced at the OPC UA level (Galaxy runtime handles security).
  • Attributes whose names start with _ are already filtered by the SQL query.

OPC-004: Browse Name Translation

Browse names shall use contained names (human-readable, scoped to parent). The server shall internally translate browse paths to tag_name references for MXAccess operations.

Acceptance Criteria

  • A variable node browsed as ZB/DEV/TestArea/TestMachine_001/DelmiaReceiver/DownloadPath correctly translates to MXAccess reference DelmiaReceiver_001.DownloadPath.
  • Translation uses the tag_name stored on the parent object node, not the browse path.
  • No runtime path parsing — the mapping is baked into each node at build time.

Details

  • Each variable node stores its full_tag_reference (e.g., DelmiaReceiver_001.DownloadPath) at address-space build time. Read/write operations use this stored reference directly.

OPC-005: Data Type Mapping

Variable nodes shall use OPC UA data types mapped from Galaxy mx_data_type values per the mapping in gr/data_type_mapping.md.

Acceptance Criteria

  • Every mx_data_type value in the mapping table produces the correct OPC UA DataType NodeId on the variable node.
  • Unknown/unmapped mx_data_type values default to String (i=12).
  • ElapsedTime (type 7) maps to Double representing seconds.

Details

  • Full mapping table in gr/data_type_mapping.md.
  • DateTime conversion: Galaxy may store local time; convert to UTC for OPC UA.
  • LocalizedText (type 15): use empty locale string with the text value.

OPC-006: Array Support

Attributes marked as arrays shall have ValueRank=1 and ArrayDimensions set to the attribute's array_dimension value.

Acceptance Criteria

  • is_array = 1 produces ValueRank = 1 (OneDimension) and ArrayDimensions = [array_dimension].
  • is_array = 0 produces ValueRank = -1 (Scalar) and no ArrayDimensions.
  • MXAccess reference for array attributes uses tag_name.attribute[] (whole array) format.

Details

  • Individual array element access (tag_name.attribute[n]) is not required for initial implementation. Whole-array read/write only.
  • If array_dimension is null or 0 when is_array = 1, log a Warning and default to ArrayDimensions = [0] (variable-length).

OPC-007: Read Operations

The server shall fulfill OPC UA Read requests by reading the corresponding tag value from MXAccess using the tag_name.AttributeName reference.

Acceptance Criteria

  • OPC UA Read request for a variable node results in a read via MXAccess using the node's stored full_tag_reference.
  • Returned value is converted from the COM variant to the OPC UA data type specified on the node.
  • OPC UA StatusCode reflects MXAccess quality: Good maps to Good, Bad/Uncertain map appropriately.
  • If MXAccess is not connected, return StatusCode = Bad_NotConnected.
  • Read timeout: configurable, default 5 seconds. On timeout, return Bad_Timeout.

Details

  • Prefer cached subscription-delivered values over on-demand reads to reduce COM round-trips.
  • If no subscription is active for the tag, perform an on-demand read (AddItem, AdviseSupervisory, wait for first OnDataChange, then UnAdvise/RemoveItem).
  • Concurrency: semaphore-limited to configurable max (default 10) concurrent MXAccess operations.

OPC-008: Write Operations

The server shall fulfill OPC UA Write requests by writing to the corresponding tag via MXAccess.

Acceptance Criteria

  • OPC UA Write request results in an MXAccess Write() call with completion confirmed via OnWriteComplete() callback.
  • Write timeout: configurable, default 5 seconds. On timeout, log Warning and return Bad_Timeout.
  • MXSTATUS_PROXY with success = 0 causes the OPC UA write to return Bad_InternalError with the detail message.
  • MXAccess errors 1008 (no permission), 1012 (secured write), 1013 (verified write) return Bad_UserAccessDenied.
  • Write to a non-existent tag returns Bad_NodeIdUnknown.
  • The server shall attempt to convert the written value to the expected Galaxy data type before passing to Write().

Details

  • Write uses security classification -1 (no security). Galaxy runtime handles security enforcement.
  • Write sequence: uses existing subscription handle if available, otherwise AddItem + AdviseSupervisory + Write + await OnWriteComplete + cleanup.
  • Concurrent write limit: same semaphore as reads (configurable, default 10).

OPC-009: Subscriptions

The server shall support OPC UA subscriptions by mapping them to MXAccess advisory subscriptions and forwarding data change notifications.

Acceptance Criteria

  • OPC UA CreateMonitoredItems results in MXAccess AdviseSupervisory() subscriptions for the requested tags.
  • Data changes from OnDataChange callback are forwarded as OPC UA notifications to all subscribed clients.
  • Shared subscriptions: if two OPC UA clients subscribe to the same tag, only one MXAccess subscription exists (ref-counted).
  • Last subscriber unsubscribing triggers UnAdvise/RemoveItem on the MXAccess side.
  • After MXAccess reconnect, all active MXAccess subscriptions are re-established automatically.

Details

  • Publishing interval from the OPC UA subscription request is honored on the OPC UA side; MXAccess delivers changes as fast as it receives them.
  • OPC UA quality mapping from MXAccess quality integers: 192+ = Good, 64-191 = Uncertain, 0-63 = Bad.
  • OnDataChange with MXSTATUS_PROXY failure: deliver notification with Bad quality to subscribed clients.

OPC-010: Address Space Rebuild

When a Galaxy deployment change is detected, the server shall rebuild the address space without dropping existing OPC UA client connections where possible.

Acceptance Criteria

  • When Galaxy Repository detects a deployment change, the OPC UA address space is rebuilt.
  • Existing OPC UA client sessions are preserved — clients stay connected.
  • Subscriptions for tags that still exist after rebuild continue to work.
  • Subscriptions for tags that no longer exist receive a Bad_NodeIdUnknown status notification.
  • Rebuild is logged at Information level with timing (duration).

Details

  • Rebuild is a full replace, not an incremental diff. Re-query hierarchy and attributes, build new tree, swap atomically.
  • During rebuild, reads/writes against the old address space may fail briefly. This is acceptable.
  • New MXAccess subscriptions for new tags are established; removed tags are unsubscribed.

OPC-011: Server Diagnostics Node

The server shall expose a ServerStatus node under the standard OPC UA Server object with ServerState, CurrentTime, and StartTime. This is required by the OPC UA specification for compliant servers.

Acceptance Criteria

  • ServerState reports Running during normal operation.
  • CurrentTime returns the server's current UTC time.
  • StartTime returns the UTC time when the service started.

OPC-012: Namespace Configuration

The server shall register a namespace URI at namespace index 1. All application-specific NodeIds shall use this namespace.

Acceptance Criteria

  • Namespace URI: urn:ZB:LmxOpcUa (Galaxy name is configurable).
  • All object and variable NodeIds created from Galaxy data use namespace index 1.
  • Standard OPC UA nodes remain in namespace 0.

OPC-013: Session Management

The server shall support multiple concurrent OPC UA client sessions.

Acceptance Criteria

  • Maximum concurrent sessions: configurable, default 100.
  • Session timeout: configurable, default 30 minutes of inactivity.
  • Expired sessions are cleaned up and their subscriptions removed.
  • Session count is reported to the status dashboard.