# Component: MxAccessClient ## Purpose The core component that wraps the ArchestrA MXAccess COM API, providing connection management, tag read/write operations, and subscription-based value change notifications. This is the bridge between the gRPC service layer and AVEVA System Platform. ## Location `src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.cs` — partial class split across 6 files: - `MxAccessClient.cs` — Main class, properties, disposal, factory. - `MxAccessClient.Connection.cs` — Connection lifecycle (connect, disconnect, reconnect, cleanup). - `MxAccessClient.ReadWrite.cs` — Read and write operations with retry and concurrency control. - `MxAccessClient.Subscription.cs` — Subscription management and stored subscription state. - `MxAccessClient.EventHandlers.cs` — COM event handlers (OnDataChange, OnWriteComplete, OperationComplete). - `MxAccessClient.NestedTypes.cs` — Internal types and enums. ## Responsibilities - Manage the MXAccess COM object lifecycle (create, register, unregister, release). - Maintain connection state (Disconnected, Connecting, Connected, Disconnecting, Error, Reconnecting) and fire state change events. - Execute read and write operations against MXAccess with concurrency control via semaphores. - Manage tag subscriptions via MXAccess advise callbacks and store subscription state for reconnection. - Handle COM threading constraints (STA thread context via `Task.Run`). ## 1. Connection Lifecycle ### 1.1 Connect `ConnectAsync()` wraps `ConnectInternal()` in `Task.Run` for STA thread context: 1. Validates not disposed. 2. Returns early if already connected. 3. Sets state to `Connecting`. 4. `InitializeMxAccessConnection()` — creates new `LMXProxyServer` COM object, wires event handlers (OnDataChange, OnWriteComplete, OperationComplete). 5. `RegisterWithMxAccess()` — calls `_lmxProxy.Register("ZB.MOM.WW.LmxProxy.Host")`, stores the returned connection handle. 6. Sets state to `Connected`. 7. On error, calls `Cleanup()` and re-throws. After successful connection, calls `RecreateStoredSubscriptionsAsync()` to restore any previously active subscriptions. ### 1.2 Disconnect `DisconnectAsync()` wraps `DisconnectInternal()` in `Task.Run`: 1. Checks `IsConnected`. 2. Sets state to `Disconnecting`. 3. `RemoveAllSubscriptions()` — unsubscribes all tags from MXAccess but retains subscription state in `_storedSubscriptions` for reconnection. 4. `UnregisterFromMxAccess()` — calls `_lmxProxy.Unregister(_connectionHandle)`. 5. `Cleanup()` — removes event handlers, calls `Marshal.ReleaseComObject(_lmxProxy)` to force-release all COM references, nulls the proxy and resets the connection handle. 6. Sets state to `Disconnected`. ### 1.3 Connection State - `IsConnected` property: `_lmxProxy != null && _connectionState == Connected && _connectionHandle > 0`. - `ConnectionState` enum: Disconnected, Connecting, Connected, Disconnecting, Error, Reconnecting. - `ConnectionStateChanged` event fires on all state transitions with previous state, current state, and optional message. ### 1.4 Auto-Reconnect When `AutoReconnect` is enabled (default), the `MonitorConnectionAsync` loop runs continuously: - Checks `IsConnected` every `MonitorIntervalSeconds` (default 5 seconds). - On disconnect, attempts reconnect via semaphore-protected `ConnectAsync()`. - On failure, logs warning and retries at the next interval. - Reconnection restores stored subscriptions automatically. ## 2. Thread Safety & COM Constraints - State mutations protected by `lock (_lock)`. - COM operations wrapped in `Task.Run` for STA thread context (MXAccess is 32-bit COM). - Concurrency control: `_readSemaphore` and `_writeSemaphore` limit concurrent MXAccess operations to `MaxConcurrentOperations` (default 10, configurable). - Default max concurrency constant: `DefaultMaxConcurrency = 10`. ## 3. Read Operations - `ReadAsync(address, ct)` — Applies Polly retry policy, calls `ReadSingleValueAsync()`, returns `Vtq`. - `ReadBatchAsync(addresses, ct)` — Creates parallel tasks per address via `ReadAddressWithSemaphoreAsync()`. Each task acquires `_readSemaphore` before reading. Returns `IReadOnlyDictionary
`. ## 4. Write Operations - `WriteAsync(address, value, ct)` — Applies Polly retry policy, calls `WriteInternalAsync(address, value, ct)`. - `WriteBatchAsync(values, ct)` — Parallel tasks via `WriteAddressWithSemaphoreAsync()`. Each task acquires `_writeSemaphore` before writing. - `WriteBatchAndWaitAsync(values, flagAddress, flagValue, responseAddress, responseValue, ct)` — Writes batch, writes flag, polls response tag until match. ## 5. Subscription Management - Subscriptions stored in `_storedSubscriptions` for reconnection persistence. - `SubscribeInternalAsync(addresses, callback, storeSubscription)` — registers tags with MXAccess and stores subscription state. - `RecreateStoredSubscriptionsAsync()` — called after reconnect to re-subscribe all previously active tags without re-storing. - `RemoveAllSubscriptions()` — unsubscribes from MXAccess but retains `_storedSubscriptions`. ## 6. Event Handlers - **OnDataChange** — Fired by MXAccess when a subscribed tag value changes. Routes the update to the SubscriptionManager. - **OnWriteComplete** — Fired when an async write operation completes. - **OperationComplete** — General operation completion callback. ## Dependencies - **ArchestrA.MXAccess** COM interop assembly (`lib/ArchestrA.MXAccess.dll`). - **Polly** — retry policies for read/write operations. - **Configuration** — `ConnectionConfiguration` for timeouts, concurrency limits, and auto-reconnect settings. ## Interactions - **GrpcServer** (ScadaGrpcService) delegates all SCADA operations to MxAccessClient via the `IScadaClient` interface. - **SubscriptionManager** receives value change callbacks originating from MxAccessClient's COM event handlers. - **HealthAndMetrics** queries `IsConnected` and `ConnectionState` for health checks. - **ServiceHost** manages the MxAccessClient lifecycle (create at startup, dispose at shutdown).