deprecate(lmxproxy): move all LmxProxy code, tests, and docs to deprecated/
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.
This commit is contained in:
200
deprecated/lmxproxy/docs/requirements/Component-Client.md
Normal file
200
deprecated/lmxproxy/docs/requirements/Component-Client.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# 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()`:
|
||||
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 | 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 `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.
|
||||
- **Security** — `GrpcChannelFactory` creates TLS-configured channels matching the Host's TLS configuration.
|
||||
Reference in New Issue
Block a user