Files
scadalink-design/lmxproxy/docs/requirements/Component-MxAccessClient.md
Joseph Doherty 683aea0fbe docs: add LmxProxy requirements documentation with v2 protocol as authoritative design
Generate high-level requirements and 10 component documents derived from source code
and protocol specs. Uses lmxproxy_updates.md (v2 TypedValue/QualityCode) as the source
of truth, with v1 string-based encoding documented as legacy context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:38:11 -04:00

109 lines
6.0 KiB
Markdown

# 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<address, Vtq>`.
## 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).