Files
scadalink-design/lmxproxy/docs/requirements/Component-Client.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

8.8 KiB
Raw Blame History

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 1s10min)

Implements IDisposable and IAsyncDisposable.

2. Connection Management

2.1 Connect

ConnectAsync():

  1. Creates a gRPC channel via GrpcChannelFactory (HTTP or HTTPS based on TLS config).
  2. Creates a protobuf-net.Grpc client for IScadaService.
  3. Calls the Connect RPC with a client ID (format: ScadaBridge-{guid}) and optional API key.
  4. Stores the returned session ID.
  5. Starts the keep-alive timer.

2.2 Keep-Alive

  • Timer-based ping every 30 seconds (hardcoded).
  • Sends a lightweight GetConnectionState RPC.
  • On failure: stops the timer, marks disconnected, triggers subscription cleanup.

2.3 Disconnect

DisconnectAsync():

  1. Stops keep-alive timer.
  2. Calls Disconnect RPC.
  3. Clears session ID.
  4. 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 165535
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 Subscribe RPC (server streaming) with the tag list and default sampling interval (1000ms).
  • Processes streamed VtqMessage items asynchronously, invoking the onUpdate(tag, vtq) callback for each.
  • On stream termination (server disconnect, gRPC error, or connection drop), invokes the onStreamError callback exactly once.
  • On stream error, the client immediately nullifies its session ID, causing IsConnected to return false. This triggers the DCL adapter's Disconnected event 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 default LmxProxy config 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.
  • SecurityGrpcChannelFactory creates TLS-configured channels matching the Host's TLS configuration.