Files
lmxopcua/docs/ClientRequirements.md
Joseph Doherty a2883b82d9 Add cross-platform OPC UA client stack: shared library, CLI tool, and Avalonia UI
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>
2026-03-30 15:49:42 -04:00

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, 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

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:

  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