Implements Client.Shared (IOpcUaClientService with connection lifecycle, failover, browse, read/write, subscriptions, alarms, history, redundancy), Client.CLI (8 CliFx commands mirroring tools/opcuacli-dotnet), and Client.UI (Avalonia desktop app with tree browser, read/write, subscriptions, alarms, and history tabs). All three target .NET 10 and are covered by 249 unit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.7 KiB
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, authDisconnectAsync()— close session cleanlyIsConnectedproperty
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 subscriptionUnsubscribeAsync(NodeId)— remove monitored itemevent DataChanged— fires on value change with (NodeId, DataValue)
Alarms:
SubscribeAlarmsAsync(NodeId? source, int intervalMs)— subscribe to alarm eventsUnsubscribeAlarmsAsync()— remove alarm subscriptionRequestConditionRefreshAsync()— trigger condition refreshevent AlarmEvent— fires on alarm state change with AlarmEventArgs
History:
HistoryReadRawAsync(NodeId, DateTime start, DateTime end, int maxValues)— raw historical valuesHistoryReadAggregateAsync(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, HasChildrenAlarmEventArgs: SourceName, ConditionName, Severity, Message, Retain, ActiveState, AckedState, TimeRedundancyInfo: Mode, ServiceLevel, ServerUris, ApplicationUriConnectionState: 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
Port all 8 commands from the existing tools/opcuacli-dotnet/:
| 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:
- Creates
ConnectionSettingsfrom CLI options - Creates
OpcUaClientService - Calls the appropriate method
- 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