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>
228 lines
8.7 KiB
Markdown
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
|