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>
6.9 KiB
MXAccess Bridge
The MXAccess bridge connects the OPC UA server to the AVEVA System Platform runtime through the ArchestrA.MxAccess COM API. It handles all COM threading requirements, translates between OPC UA read/write requests and MXAccess operations, and manages connection health.
STA Thread Requirement
MXAccess is a COM-based API that requires a Single-Threaded Apartment (STA). All COM objects -- LMXProxyServer instantiation, Register, AddItem, AdviseSupervisory, Write, and cleanup calls -- must execute on the same STA thread. Calling COM objects from the wrong thread causes marshalling failures or silent data corruption.
StaComThread provides a dedicated STA thread with the apartment state set before the thread starts:
_thread = new Thread(ThreadEntry) { Name = "MxAccess-STA", IsBackground = true };
_thread.SetApartmentState(ApartmentState.STA);
Work items are queued via RunAsync(Action) or RunAsync<T>(Func<T>), which enqueue the work to a ConcurrentQueue<Action> and post a WM_APP message to wake the pump. Each work item is wrapped in a TaskCompletionSource so callers can await the result from any thread.
Win32 Message Pump
COM callbacks (like OnDataChange) are delivered through the Windows message loop. StaComThread runs a standard Win32 message pump using P/Invoke:
PeekMessageprimes the message queue (required beforePostThreadMessageworks)GetMessageblocks until a message arrivesWM_APPmessages drain the work queueWM_APP + 1drains the queue and postsWM_QUITto exit the loop- All other messages are passed through
TranslateMessage/DispatchMessagefor COM callback delivery
Without this message pump, MXAccess COM callbacks would never fire and the server would receive no live data.
LMXProxyServer COM Object
MxProxyAdapter wraps the real ArchestrA.MxAccess.LMXProxyServer COM object behind the IMxProxy interface. This abstraction allows unit tests to substitute a fake proxy without requiring the ArchestrA runtime.
The COM object lifecycle:
Register(clientName)-- Creates a newLMXProxyServerinstance, wires upOnDataChangeandOnWriteCompleteevent handlers, and callsRegisterto obtain a connection handleUnregister(handle)-- Unwires event handlers, callsUnregister, and releases the COM object viaMarshal.ReleaseComObject
Register/AddItem/AdviseSupervisory Pattern
Every MXAccess data operation follows a three-step pattern, all executed on the STA thread:
AddItem(handle, address)-- Resolves a Galaxy tag reference (e.g.,TestMachine_001.MachineID) to an integer item handleAdviseSupervisory(handle, itemHandle)-- Subscribes the item for supervisory data change callbacks- The runtime begins delivering
OnDataChangeevents for the item
For writes, after AddItem + AdviseSupervisory, Write(handle, itemHandle, value, securityClassification) sends the value to the runtime. The OnWriteComplete callback confirms or rejects the write.
Cleanup reverses the pattern: UnAdviseSupervisory then RemoveItem.
OnDataChange and OnWriteComplete Callbacks
OnDataChange
Fired by the COM runtime on the STA thread when a subscribed tag value changes. The handler in MxAccessClient.EventHandlers.cs:
- Maps the integer
phItemHandleback to a tag address via_handleToAddress - Maps the MXAccess quality code to the internal
Qualityenum - Checks
MXSTATUS_PROXYfor error details and adjusts quality accordingly - Converts the timestamp to UTC
- Constructs a
Vtq(Value/Timestamp/Quality) and delivers it to:- The stored per-tag subscription callback
- Any pending one-shot read completions
- The global
OnTagValueChangedevent (consumed byLmxNodeManager)
OnWriteComplete
Fired when the runtime acknowledges or rejects a write. The handler resolves the pending TaskCompletionSource<bool> for the item handle. If MXSTATUS_PROXY.success == 0, the write is considered failed and the error detail is logged.
Reconnection Logic
MxAccessClient implements automatic reconnection through two mechanisms:
Monitor loop
StartMonitor launches a background task that polls at MonitorIntervalSeconds. On each cycle:
- If the state is
DisconnectedorErrorandAutoReconnectis enabled, it callsReconnectAsync - If connected and a probe tag is configured, it checks the probe staleness threshold
Reconnect sequence
ReconnectAsync performs a full disconnect-then-connect cycle:
- Increment the reconnect counter
DisconnectAsync-- Tears down all active subscriptions (UnAdviseSupervisory+RemoveItemfor each), detaches COM event handlers, callsUnregister, and clears all handle mappingsConnectAsync-- Creates a freshLMXProxyServer, registers, replays all stored subscriptions, and re-subscribes the probe tag
Stored subscriptions (_storedSubscriptions) persist across reconnects. When ConnectAsync succeeds, ReplayStoredSubscriptionsAsync iterates all stored entries and calls AddItem + AdviseSupervisory for each.
Probe Tag Health Monitoring
A configurable probe tag (e.g., a frequently updating Galaxy attribute) serves as a connection health indicator. After connecting, the client subscribes to the probe tag and records _lastProbeValueTime on every OnDataChange callback.
The monitor loop compares DateTime.UtcNow - _lastProbeValueTime against ProbeStaleThresholdSeconds. If the probe value has not updated within the threshold, the connection is assumed stale and a reconnect is forced. This catches scenarios where the COM connection is technically alive but the runtime has stopped delivering data.
Why Marshal.ReleaseComObject Is Needed
The .NET runtime's garbage collector releases COM references non-deterministically. For MXAccess, delayed release can leave stale COM connections open, preventing clean re-registration. MxProxyAdapter.Unregister calls Marshal.ReleaseComObject(_lmxProxy) in a finally block to immediately release the COM reference count to zero. This ensures the underlying COM server is freed before a reconnect attempt creates a new instance.
Key source files
src/ZB.MOM.WW.LmxOpcUa.Host/MxAccess/StaComThread.cs-- STA thread and Win32 message pumpsrc/ZB.MOM.WW.LmxOpcUa.Host/MxAccess/MxAccessClient.cs-- Core client class (partial)src/ZB.MOM.WW.LmxOpcUa.Host/MxAccess/MxAccessClient.Connection.cs-- Connect, disconnect, reconnectsrc/ZB.MOM.WW.LmxOpcUa.Host/MxAccess/MxAccessClient.Subscription.cs-- Subscribe, unsubscribe, replaysrc/ZB.MOM.WW.LmxOpcUa.Host/MxAccess/MxAccessClient.ReadWrite.cs-- Read and write operationssrc/ZB.MOM.WW.LmxOpcUa.Host/MxAccess/MxAccessClient.EventHandlers.cs-- OnDataChange and OnWriteComplete handlerssrc/ZB.MOM.WW.LmxOpcUa.Host/MxAccess/MxAccessClient.Monitor.cs-- Background health monitorsrc/ZB.MOM.WW.LmxOpcUa.Host/MxAccess/MxProxyAdapter.cs-- COM object wrappersrc/ZB.MOM.WW.LmxOpcUa.Host/Domain/IMxAccessClient.cs-- Client interface