Files
lmxopcua/docs/reqs/ClientRequirements.md
Joseph Doherty 8fae2cb790 Remove tools/opcuacli-dotnet and point all docs to Client.CLI
The standalone CLI tool is superseded by src/ZB.MOM.WW.LmxOpcUa.Client.CLI
which uses the shared IOpcUaClientService abstraction. Renames CliTool.md to
Client.CLI.md and updates README, CLAUDE.md, Security, and Redundancy docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 08:08:57 -04:00

228 lines
8.7 KiB
Markdown

# OPC UA Client Requirements
## Overview
Three new .NET 10 cross-platform projects providing a shared OPC UA client library, a CLI tool, and an Avalonia desktop UI. All projects target Windows and macOS.
## Projects
| Project | Type | Purpose |
|---------|------|---------|
| `ZB.MOM.WW.LmxOpcUa.Client.Shared` | Class library | Core OPC UA client, models, interfaces |
| `ZB.MOM.WW.LmxOpcUa.Client.CLI` | Console app | Command-line interface using CliFx |
| `ZB.MOM.WW.LmxOpcUa.Client.UI` | Avalonia app | Desktop UI with tree browser, subscriptions, alarms |
| `ZB.MOM.WW.LmxOpcUa.Client.Shared.Tests` | Test project | Unit tests for shared library |
| `ZB.MOM.WW.LmxOpcUa.Client.CLI.Tests` | Test project | Unit tests for CLI commands |
| `ZB.MOM.WW.LmxOpcUa.Client.UI.Tests` | Test project | Unit tests for UI view models |
## Technology Stack
- .NET 10, C#
- OPC UA: OPCFoundation.NetStandard.Opc.Ua.Client
- Logging: Serilog
- CLI: CliFx
- UI: Avalonia 11.x with CommunityToolkit.Mvvm
- Tests: xUnit 3, Shouldly, Microsoft.Testing.Platform runner
## Client.Shared
### ConnectionSettings Model
```
EndpointUrl: string (required)
FailoverUrls: string[] (optional)
Username: string? (optional, first-class property)
Password: string? (optional, first-class property)
SecurityMode: enum (None, Sign, SignAndEncrypt) — default None
SessionTimeoutSeconds: int — default 60
AutoAcceptCertificates: bool — default true
CertificateStorePath: string? — default platform-appropriate location
```
### IOpcUaClientService Interface
Single service interface covering all OPC UA operations:
**Lifecycle:**
- `ConnectAsync(ConnectionSettings)` — connect to server, handle endpoint discovery, security, auth
- `DisconnectAsync()` — close session cleanly
- `IsConnected` property
**Read/Write:**
- `ReadValueAsync(NodeId)` — returns DataValue (value, status, timestamps)
- `WriteValueAsync(NodeId, object value)` — auto-detects target type, returns StatusCode
**Browse:**
- `BrowseAsync(NodeId? parent)` — returns list of BrowseResult (NodeId, DisplayName, NodeClass)
- Lazy-load compatible (browse one level at a time)
**Subscribe:**
- `SubscribeAsync(NodeId, int intervalMs)` — create monitored item subscription
- `UnsubscribeAsync(NodeId)` — remove monitored item
- `event DataChanged` — fires on value change with (NodeId, DataValue)
**Alarms:**
- `SubscribeAlarmsAsync(NodeId? source, int intervalMs)` — subscribe to alarm events
- `UnsubscribeAlarmsAsync()` — remove alarm subscription
- `RequestConditionRefreshAsync()` — trigger condition refresh
- `event AlarmEvent` — fires on alarm state change with AlarmEventArgs
**History:**
- `HistoryReadRawAsync(NodeId, DateTime start, DateTime end, int maxValues)` — raw historical values
- `HistoryReadAggregateAsync(NodeId, DateTime start, DateTime end, AggregateType, double intervalMs)` — aggregated values
**Redundancy:**
- `GetRedundancyInfoAsync()` — returns RedundancyInfo (mode, service level, server URIs, app URI)
**Failover:**
- Automatic failover across FailoverUrls with keep-alive monitoring
- `event ConnectionStateChanged` — fires on connect/disconnect/failover
### Models
- `BrowseResult`: NodeId, DisplayName, NodeClass, HasChildren
- `AlarmEventArgs`: SourceName, ConditionName, Severity, Message, Retain, ActiveState, AckedState, Time
- `RedundancyInfo`: Mode, ServiceLevel, ServerUris, ApplicationUri
- `ConnectionState`: enum (Disconnected, Connecting, Connected, Reconnecting)
- `AggregateType`: enum (Average, Minimum, Maximum, Count, Start, End)
### Type Conversion
Port the existing `ConvertValue` logic from the CLI tool: reads the current node value to determine the target type, then coerces the input value.
### Certificate Management
- Cross-platform certificate store path (default: `{AppData}/LmxOpcUaClient/pki/`)
- Auto-generate client certificate on first use
- Auto-accept untrusted server certificates (configurable)
### Logging
Serilog with `ILogger` passed via constructor or `Log.ForContext<T>()`. No sinks configured in the library — consumers configure sinks.
## Client.CLI
### Commands
All 8 commands:
| Command | Description |
|---------|-------------|
| `connect` | Test server connectivity |
| `read` | Read a node value |
| `write` | Write a value to a node |
| `browse` | Browse address space (with depth/recursive) |
| `subscribe` | Monitor node for value changes |
| `historyread` | Read historical data (raw + aggregates) |
| `alarms` | Subscribe to alarm events |
| `redundancy` | Query redundancy state |
All commands use the shared `IOpcUaClientService`. Each command:
1. Creates `ConnectionSettings` from CLI options
2. Creates `OpcUaClientService`
3. Calls the appropriate method
4. Formats and prints results
### Common Options (all commands)
- `-u, --url` (required): Endpoint URL
- `-U, --username`: Username
- `-P, --password`: Password
- `-S, --security`: Security mode (none/sign/encrypt)
- `-F, --failover-urls`: Comma-separated failover endpoints
### Logging
Serilog console sink at Warning level by default, with `--verbose` flag for Debug.
## Client.UI
### Window Layout
Single-window Avalonia application:
```
┌─────────────────────────────────────────────────────────┐
│ [Endpoint URL] [User] [Pass] [Security▼] [Connect] │
│ Redundancy: Mode=Warm ServiceLevel=200 AppUri=... │
├──────────────┬──────────────────────────────────────────┤
│ │ ┌─Read/Write─┬─Subscriptions─┬─Alarms─┬─History─┐│
│ Address │ │ Node: ns=3;s=Tag.Attr ││
│ Space │ │ Value: 42.5 ││
│ Tree │ │ Status: Good ││
│ Browser │ │ [Write: ____] [Send] ││
│ │ │ ││
│ (lazy-load) │ │ ││
│ │ └──────────────────────────────────────┘│
├──────────────┴──────────────────────────────────────────┤
│ Status: Connected | Session: abc123 | 3 subscriptions │
└─────────────────────────────────────────────────────────┘
```
### Views and ViewModels (CommunityToolkit.Mvvm)
**MainWindowViewModel:**
- Connection settings properties (bound to top bar inputs)
- ConnectCommand / DisconnectCommand (RelayCommand)
- ConnectionState property
- RedundancyInfo property
- SelectedTreeNode property
- StatusMessage property
**BrowseTreeViewModel:**
- Root nodes collection (ObservableCollection)
- Lazy-load children on expand via `BrowseAsync`
- TreeNodeViewModel: NodeId, DisplayName, NodeClass, Children, IsExpanded, HasChildren
**ReadWriteViewModel:**
- SelectedNode (from tree selection)
- CurrentValue, Status, SourceTimestamp
- WriteValue input + WriteCommand
- Auto-read on node selection
**SubscriptionsViewModel:**
- ActiveSubscriptions collection (ObservableCollection)
- AddSubscription / RemoveSubscription commands
- Live value updates dispatched to UI thread
- Columns: NodeId, Value, Status, Timestamp
**AlarmsViewModel:**
- AlarmEvents collection (ObservableCollection)
- SubscribeCommand / UnsubscribeCommand / RefreshCommand
- MonitoredNode property
- Live alarm events dispatched to UI thread
**HistoryViewModel:**
- SelectedNode (from tree selection)
- StartTime, EndTime, MaxValues, AggregateType, Interval
- ReadCommand
- Results collection (ObservableCollection)
- Columns: Timestamp, Value, Status
### UI Thread Dispatch
All events from `IOpcUaClientService` must be dispatched to the Avalonia UI thread via `Dispatcher.UIThread.Post()` before updating ObservableCollections.
## Test Projects
### Client.Shared.Tests
- ConnectionSettings validation
- Type conversion (ConvertValue)
- BrowseResult model construction
- AlarmEventArgs model construction
- FailoverUrl parsing
### Client.CLI.Tests
- Command option parsing (via CliFx test infrastructure)
- Output formatting
### Client.UI.Tests
- ViewModel property change notifications
- Command can-execute logic
- Tree node lazy-load behavior (with mocked IOpcUaClientService)
### Test Framework
- xUnit 3 with Microsoft.Testing.Platform runner
- Shouldly for assertions
- No live OPC UA server required — mock IOpcUaClientService for unit tests