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>
8.4 KiB
MXAccess Client — Component Requirements
MXA-001: STA Thread with Message Pump
All MXAccess COM objects shall be created and called on a dedicated STA thread running a Win32 message pump to ensure COM callbacks are delivered.
Acceptance Criteria
- A dedicated thread is created with
ApartmentState.STAbefore any MXAccess COM objects are instantiated. - The thread runs a Win32 message pump using
GetMessage/TranslateMessage/DispatchMessageloop. - Work items are marshalled to the STA thread via
PostThreadMessage(WM_APP)and a concurrent queue. - The STA thread processes work items between message pump iterations.
- All COM object creation (
LMXProxyServerconstructor), method calls, and event callbacks happen on this thread.
Details
- Thread name:
MxAccess-STA(for diagnostics). - If the STA thread dies unexpectedly, log Fatal and trigger service shutdown. Do not attempt to create a replacement thread (COM objects on the dead thread are unrecoverable).
RunAsync(Action)method returns aTaskthat completes when the action executes on the STA thread. Callers canawaitit.
MXA-002: Connection Lifecycle
The client shall support Register/Unregister lifecycle with the LMXProxyServer COM object, tracking the connection handle.
Acceptance Criteria
Register(clientName)is called on the STA thread and returns a positive connection handle on success.- If Register returns handle <= 0, throw with descriptive error.
Unregister(handle)is called during disconnect after all subscriptions are removed.- Client name: configurable via
MxAccess:ClientName, defaultLmxOpcUa. Must be unique per MXAccess registration. - Connection state transitions: Disconnected → Connecting → Connected → Disconnecting → Disconnected (and Error from any state).
Details
ConnectedSincetimestamp (UTC) is recorded after successful Register.ReconnectCountis tracked for diagnostics and dashboard display.- State change events are raised for dashboard and health check consumption.
MXA-003: Tag Subscription
The client shall support subscribing to tags via AddItem + AdviseSupervisory, receiving value updates through OnDataChange callbacks.
Acceptance Criteria
- Subscribe sequence:
AddItem(handle, address)returns item handle, thenAdviseSupervisory(handle, itemHandle)starts the subscription. OnDataChangecallback delivers value, quality (integer), timestamp, and MXSTATUS_PROXY array.- Item address format:
tag_name.AttributeNamefor scalars,tag_name.AttributeName[]for whole arrays. - If AddItem fails (e.g., tag does not exist), log Warning and return failure to caller.
- Bidirectional maps of
address ↔ itemHandleare maintained for callback resolution.
Details
- Use
AdviseSupervisory(notAdvise) because this is a background service with no interactive user session. AdviseSupervisory allows secured/verified writes without user authentication. - Stored subscriptions dictionary maps address to callback for reconnect replay.
- On reconnect, all entries in stored subscriptions are re-subscribed (AddItem + AdviseSupervisory with new handles).
MXA-004: Tag Read/Write
The client shall support synchronous-style read and write operations, marshalled to the STA thread, with configurable timeouts.
Acceptance Criteria
- Read: implemented as subscribe-get-first-value-unsubscribe pattern (AddItem → AdviseSupervisory → wait for OnDataChange → UnAdvise → RemoveItem).
- Write: AddItem → AdviseSupervisory →
Write()→ awaitOnWriteCompletecallback → cleanup. - Read timeout: configurable via
MxAccess:ReadTimeoutSeconds, default 5 seconds. - Write timeout: configurable via
MxAccess:WriteTimeoutSeconds, default 5 seconds. On timeout, log Warning and return timeout error. - Concurrent operation limit: configurable semaphore via
MxAccess:MaxConcurrentOperations, default 10. - All operations marshalled to the STA thread.
Details
- Write uses security classification -1 (no security). Galaxy runtime handles security enforcement.
OnWriteCompletecallback: check MXSTATUS_PROXYsuccessfield. If 0, extract detail code and propagate error.- COM exceptions (
COMExceptionwith HRESULT) are caught and translated to meaningful error messages.
MXA-005: Auto-Reconnect
The client shall monitor connection health and automatically reconnect on failure, replaying all stored subscriptions after reconnect.
Acceptance Criteria
- Monitor loop runs on a background thread, checking connection health at configurable interval (
MxAccess:MonitorIntervalSeconds, default 5 seconds). - If disconnected, attempt reconnect. On success, replay all stored subscriptions.
- On reconnect failure, log Warning and retry at next interval (no exponential backoff — reconnect as quickly as possible on a plant-floor service).
- Reconnect count is incremented on each successful reconnect.
- Monitor loop is cancellable (for clean shutdown).
Details
- Reconnect cleans up old COM objects before creating new ones.
- After reconnect, probe subscription is re-established first, then stored subscriptions.
- No max retry limit — keep trying indefinitely until service is stopped.
MXA-006: Probe-Based Health Monitoring
The client shall optionally subscribe to a configurable probe tag and use OnDataChange callback staleness to detect silent connection failures.
Acceptance Criteria
- Subscribe to a configurable probe tag (a known-good Galaxy attribute that changes periodically).
- Track
_lastProbeValueTime(UTC) updated on each OnDataChange for the probe tag. - If
DateTime.UtcNow - _lastProbeValueTime > staleThreshold, force disconnect and reconnect. - Probe tag address: configurable via
MxAccess:ProbeTag. If not configured, probe monitoring is disabled. - Stale threshold: configurable via
MxAccess:ProbeStaleThresholdSeconds, default 60 seconds.
Details
- The probe tag should be an attribute that the Galaxy runtime updates regularly (e.g., a platform heartbeat or area-level timestamp). The specific tag is site-dependent.
- After forced reconnect, reset
_lastProbeValueTimetoDateTime.UtcNowto give the new connection a full threshold window.
MXA-007: COM Cleanup
On disconnect or disposal, the client shall unwire event handlers, unadvise/remove all items, unregister, and release COM objects via Marshal.ReleaseComObject.
Acceptance Criteria
- Cleanup order: UnAdvise all active subscriptions → RemoveItem all items → unwire OnDataChange and OnWriteComplete event handlers → Unregister →
Marshal.ReleaseComObject. - On dispose: run disconnect if still connected, then dispose STA thread.
- Each cleanup step is wrapped in try/catch (cleanup must not throw).
- After cleanup: handle maps are cleared, pending write TCS entries are abandoned, COM reference is set to null.
Details
_storedSubscriptionsis NOT cleared on disconnect (preserved for reconnect replay). Only cleared on Dispose.- Event handlers must be unwired BEFORE Unregister, or callbacks may fire on a dead object.
Marshal.ReleaseComObjectin a finally block, always, even if earlier steps fail.
MXA-008: Operation Metrics
The MXAccess client shall record timing and success/failure for Read, Write, and Subscribe operations.
Acceptance Criteria
- Each operation records: duration (ms), success/failure.
- Metrics are available for the status dashboard: count, success rate, avg/min/max/P95 latency.
- Uses a rolling 1000-entry buffer for percentile calculation.
- Metrics are exposed via a queryable interface consumed by the status report service.
Details
- Uses an
ITimingScopepattern:using (var scope = metrics.BeginOperation("read")) { ... }for automatic timing and success tracking. - Metrics are periodically logged at Debug level for diagnostics.
MXA-009: Error Code Translation
The client shall translate known MXAccess error codes from MXSTATUS_PROXY.detail into human-readable messages for logging and OPC UA status propagation.
Acceptance Criteria
- Error 1008 → "User lacks security permission"
- Error 1012 → "Secured write required (one signature)"
- Error 1013 → "Verified write required (two signatures)"
- Unknown error codes are logged with their numeric value.
- Translated messages are included in OPC UA StatusCode descriptions and log entries.