LmxProxy is no longer needed. Moved the entire lmxproxy/ workspace, DCL adapter files, and related docs to deprecated/. Removed LmxProxy registration from DataConnectionFactory, project reference from DCL, protocol option from UI, and cleaned up all requirement docs.
8.8 KiB
Component: Client
Purpose
A .NET 10 class library providing a typed gRPC client for consuming the LmxProxy service. Used by ScadaLink's Data Connection Layer to connect to AVEVA System Platform via the LmxProxy Host.
Location
src/ZB.MOM.WW.LmxProxy.Client/ — all files in this project.
Key files:
ILmxProxyClient.cs— public interface.LmxProxyClient.cs— main implementation (partial class across multiple files).LmxProxyClientBuilder.cs— fluent builder for client construction.ServiceCollectionExtensions.cs— DI integration and options classes.ILmxProxyClientFactory.cs— factory interface and implementation.StreamingExtensions.cs— batch and parallel streaming helpers.Domain/ScadaContracts.cs— code-first gRPC contracts.Security/GrpcChannelFactory.cs— TLS channel creation.
Responsibilities
- Connect to and communicate with the LmxProxy Host gRPC service.
- Manage session lifecycle (connect, keep-alive, disconnect).
- Execute read, write, and subscribe operations with retry and concurrency control.
- Provide a fluent builder and DI integration for configuration.
- Track client-side performance metrics.
- Support TLS and mutual TLS connections.
1. Public Interface (ILmxProxyClient)
| Method | Description |
|---|---|
ConnectAsync(ct) |
Establish gRPC channel and session |
DisconnectAsync() |
Graceful disconnect |
IsConnectedAsync() |
Thread-safe connection state check |
ReadAsync(address, ct) |
Read single tag, returns Vtq |
ReadBatchAsync(addresses, ct) |
Read multiple tags, returns dictionary |
WriteAsync(address, value, ct) |
Write single tag value |
WriteBatchAsync(values, ct) |
Write multiple tag values |
SubscribeAsync(addresses, onUpdate, onStreamError, ct) |
Subscribe to tag updates with value and error callbacks |
GetMetrics() |
Return operation counts, errors, latency stats |
DefaultTimeout |
Configurable timeout (default 30s, range 1s–10min) |
Implements IDisposable and IAsyncDisposable.
2. Connection Management
2.1 Connect
ConnectAsync():
- Creates a gRPC channel via
GrpcChannelFactory(HTTP or HTTPS based on TLS config). - Creates a
protobuf-net.Grpcclient forIScadaService. - Calls the
ConnectRPC with a client ID (format:ScadaBridge-{guid}) and optional API key. - Stores the returned session ID.
- Starts the keep-alive timer.
2.2 Keep-Alive
- Timer-based ping every 30 seconds (hardcoded).
- Sends a lightweight
GetConnectionStateRPC. - On failure: stops the timer, marks disconnected, triggers subscription cleanup.
2.3 Disconnect
DisconnectAsync():
- Stops keep-alive timer.
- Calls
DisconnectRPC. - Clears session ID.
- Disposes gRPC channel.
2.4 Connection State
IsConnected property: !_disposed && _isConnected && !string.IsNullOrEmpty(_sessionId).
3. Builder Pattern (LmxProxyClientBuilder)
| Method | Default | Constraint |
|---|---|---|
WithHost(string) |
Required | Non-null/non-empty |
WithPort(int) |
5050 | 1–65535 |
WithApiKey(string?) |
null | Optional |
WithTimeout(TimeSpan) |
30 seconds | > 0 and ≤ 10 minutes |
WithLogger(ILogger) |
NullLogger | Optional |
WithSslCredentials(string?) |
Disabled | Optional cert path |
WithTlsConfiguration(ClientTlsConfiguration) |
null | Full TLS config |
WithRetryPolicy(int, TimeSpan) |
3 attempts, 1s delay | maxAttempts > 0, delay > 0 |
WithMetrics() |
Disabled | Enables metric collection |
WithCorrelationIdHeader(string) |
null | Custom header name |
4. Retry Policy
Polly-based exponential backoff:
- Default: 3 attempts with 1-second initial delay.
- Backoff sequence:
delay * 2^(retryAttempt - 1)→ 1s, 2s, 4s. - Transient errors retried:
Unavailable,DeadlineExceeded,ResourceExhausted,Aborted. - Each retry is logged with correlation ID at Warning level.
5. Subscription
5.1 Subscribe API
SubscribeAsync(addresses, onUpdate, onStreamError, ct) returns an ISubscription:
- Calls the
SubscribeRPC (server streaming) with the tag list and default sampling interval (1000ms). - Processes streamed
VtqMessageitems asynchronously, invoking theonUpdate(tag, vtq)callback for each. - On stream termination (server disconnect, gRPC error, or connection drop), invokes the
onStreamErrorcallback exactly once. - On stream error, the client immediately nullifies its session ID, causing
IsConnectedto returnfalse. This triggers the DCL adapter'sDisconnectedevent and reconnection cycle. - Errors are logged per-subscription.
5.2 ISubscription
Dispose()— synchronous disposal with 5-second timeout.- Automatic callback on disposal for cleanup.
6. DI Integration
6.1 Service Collection Extensions
| Method | Lifetime | Description |
|---|---|---|
AddLmxProxyClient(IConfiguration) |
Singleton | Bind LmxProxy config section |
AddLmxProxyClient(IConfiguration, string) |
Singleton | Bind named config section |
AddLmxProxyClient(Action<Builder>) |
Singleton | Builder action |
AddScopedLmxProxyClient(IConfiguration) |
Scoped | Per-scope lifetime |
AddNamedLmxProxyClient(string, Action<Builder>) |
Keyed singleton | Named/keyed registration |
6.2 Configuration Options (LmxProxyClientOptions)
Bound from appsettings.json:
| Setting | Default | Description |
|---|---|---|
| Host | localhost |
Server hostname |
| Port | 5050 | Server port |
| ApiKey | null | API key |
| Timeout | 30 seconds | Operation timeout |
| UseSsl | false | Enable TLS |
| CertificatePath | null | SSL certificate path |
| EnableMetrics | false | Enable client metrics |
| CorrelationIdHeader | null | Custom correlation header |
| Retry:MaxAttempts | 3 | Retry attempts |
| Retry:Delay | 1 second | Initial retry delay |
6.3 Factory Pattern
ILmxProxyClientFactory creates configured clients:
CreateClient()— uses defaultLmxProxyconfig section.CreateClient(string)— uses named config section.CreateClient(Action<Builder>)— uses builder action.
Registered as singleton in DI.
7. Streaming Extensions
Helper methods for large-scale batch operations:
| Method | Default Batch Size | Description |
|---|---|---|
ReadStreamAsync |
100 | Batched reads, 2 retries per batch, stops after 3 consecutive errors. Returns IAsyncEnumerable<KeyValuePair<string, Vtq>>. |
WriteStreamAsync |
100 | Batched writes from async enumerable input. Returns total count written. |
ProcessInParallelAsync |
— | Parallel processing with max concurrency of 4 (configurable). Semaphore-based rate limiting. |
SubscribeStreamAsync |
— | Wraps callback-based subscription into IAsyncEnumerable<Vtq> via System.Threading.Channels. |
8. Client Metrics
When metrics are enabled (WithMetrics()):
- Per-operation tracking: counts, error counts, latency.
- Rolling buffer of 1000 latency samples per operation (prevents memory growth).
- Snapshot via
GetMetrics()returns:{op}_count,{op}_errors,{op}_avg_latency_ms,{op}_p95_latency_ms,{op}_p99_latency_ms.
9. Value and Quality Handling
9.1 Values (TypedValue)
Read responses and subscription updates return values as TypedValue (protobuf oneof). The client extracts the value directly from the appropriate oneof field (e.g., vtq.Value.DoubleValue, vtq.Value.BoolValue). Write operations construct TypedValue with the correct oneof case for the value's native type. No string serialization or parsing is needed.
9.2 Quality (QualityCode)
Quality is received as a QualityCode message. Category checks use bitmask: IsGood = (statusCode & 0xC0000000) == 0x00000000, IsBad = (statusCode & 0xC0000000) == 0x80000000. The symbolic_name field provides human-readable quality for logging and display.
9.3 Current Implementation (V1 Legacy)
The current codebase still uses v1 string-based encoding. During v2 migration, the following will be removed:
ConvertToVtq()— parses string values via heuristic (double → bool → null → raw string).ConvertToString()— serializes values via.ToString().
Dependencies
- protobuf-net.Grpc — code-first gRPC client.
- Grpc.Net.Client — HTTP/2 gRPC transport.
- Polly — retry policies.
- Microsoft.Extensions.DependencyInjection — DI integration.
- Microsoft.Extensions.Configuration — options binding.
- Microsoft.Extensions.Logging — logging abstraction.
Interactions
- ScadaLink Data Connection Layer consumes the client library via
ILmxProxyClient. - Protocol — the client uses code-first contracts (
IScadaService) that are wire-compatible with the Host's proto-generated service. - Security —
GrpcChannelFactorycreates TLS-configured channels matching the Host's TLS configuration.