# 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.OtOpcUa.Client.Shared` | Class library | Core OPC UA client, models, interfaces |
| `ZB.MOM.WW.OtOpcUa.Client.CLI` | Console app | Command-line interface using CliFx |
| `ZB.MOM.WW.OtOpcUa.Client.UI` | Avalonia app | Desktop UI with tree browser, subscriptions, alarms |
| `ZB.MOM.WW.OtOpcUa.Client.Shared.Tests` | Test project | Unit tests for shared library |
| `ZB.MOM.WW.OtOpcUa.Client.CLI.Tests` | Test project | Unit tests for CLI commands |
| `ZB.MOM.WW.OtOpcUa.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
