Add UI features, alarm ack, historian UTC fix, and Client.UI documentation
Major changes across the client stack: - Settings persistence (connection, subscriptions, alarm source) - Deferred OPC UA SDK init for instant startup - Array/status code formatting, write value popup, alarm acknowledgment - Severity-colored alarm rows, condition dedup on server side - DateTimeRangePicker control with preset buttons and UTC text input - Historian queries use wwTimezone=UTC and OPCQuality column - Recursive subscribe from tree, multi-select remove - Connection panel with expander, folder chooser for cert path - Dynamic tab headers showing subscription/alarm counts - Client.UI.md documentation with headless-rendered screenshots Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
264
docs/Client.UI.md
Normal file
264
docs/Client.UI.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Client UI
|
||||
|
||||
## Overview
|
||||
|
||||
`ZB.MOM.WW.LmxOpcUa.Client.UI` is a cross-platform Avalonia desktop application for connecting to and interacting with the LmxOpcUa OPC UA server. It targets .NET 10 and uses the shared `IOpcUaClientService` from `Client.Shared` for all OPC UA operations.
|
||||
|
||||
The UI provides a single-window interface for browsing the address space, reading and writing values, monitoring live subscriptions, managing alarms, and querying historical data.
|
||||
|
||||
## Build and Run
|
||||
|
||||
```bash
|
||||
cd src/ZB.MOM.WW.LmxOpcUa.Client.UI
|
||||
dotnet build
|
||||
dotnet run
|
||||
```
|
||||
|
||||
## Technology Stack
|
||||
|
||||
| Component | Technology |
|
||||
|-----------|-----------|
|
||||
| Framework | .NET 10 |
|
||||
| UI Toolkit | Avalonia 11.2 |
|
||||
| MVVM | CommunityToolkit.Mvvm |
|
||||
| OPC UA | OPCFoundation.NetStandard.Opc.Ua.Client |
|
||||
| Logging | Serilog |
|
||||
| Theme | Avalonia Fluent |
|
||||
|
||||
## Window Layout
|
||||
|
||||
The application uses a single-window layout with five main areas:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ [Endpoint URL ] [Connect] [Disconnect] │
|
||||
│ ▸ Connection Settings │
|
||||
│ Redundancy: Warm Service Level: 200 URI: urn:... │
|
||||
├──────────────┬──────────────────────────────────────────────┤
|
||||
│ │ ┌─Read/Write─┬─Subscriptions─┬─Alarms─┬─History─┐│
|
||||
│ Address │ │ ││
|
||||
│ Space │ │ ││
|
||||
│ Tree │ │ (active tab content) ││
|
||||
│ Browser │ │ ││
|
||||
│ │ │ ││
|
||||
│ (lazy-load) │ └──────────────────────────────────────────────┘│
|
||||
├──────────────┴──────────────────────────────────────────────┤
|
||||
│ Connected to opc.tcp://... | LmxOpcUa | Session: ... | 3 subs│
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Connection Panel
|
||||
|
||||

|
||||
|
||||
The top bar provides the endpoint URL, Connect, and Disconnect buttons. The **Connection Settings** expander reveals additional options when expanded:
|
||||
|
||||
| Setting | Description |
|
||||
|---------|-------------|
|
||||
| Endpoint URL | OPC UA server endpoint (e.g., `opc.tcp://localhost:4840/LmxOpcUa`) |
|
||||
| Username / Password | Credentials for `UserName` token authentication |
|
||||
| Security Mode | Transport security: None, Sign, SignAndEncrypt |
|
||||
| Failover URLs | Comma-separated backup endpoints for redundancy failover |
|
||||
| Session Timeout | Session timeout in seconds (1–3600, default 60) |
|
||||
| Certificate Store Path | Path to the client certificate store (folder chooser) |
|
||||
| Auto-accept certificates | Whether to accept untrusted server certificates |
|
||||
|
||||
### Settings Persistence
|
||||
|
||||
Connection settings are saved to `{LocalAppData}/LmxOpcUaClient/settings.json` after each successful connection and on window close. The settings are reloaded on next launch, including:
|
||||
|
||||
- All connection parameters
|
||||
- Active subscription node IDs (restored after reconnection)
|
||||
- Alarm subscription source node (restored with condition refresh)
|
||||
|
||||
### Redundancy
|
||||
|
||||
When connected, the redundancy row displays the server's redundancy mode, service level, and application URI. The shared service handles automatic failover to backup endpoints if configured.
|
||||
|
||||
## Browse Tree
|
||||
|
||||
The left panel shows the OPC UA address space as a lazy-loaded tree. Nodes are loaded on demand when expanded.
|
||||
|
||||
### Context Menu
|
||||
|
||||
Right-click on tree nodes to access:
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| **Subscribe** | Subscribe to data changes on the selected node(s). For Object nodes, recursively subscribes all Variable descendants (up to 10 levels deep). Switches to the Subscriptions tab. |
|
||||
| **View History** | Set the selected Variable node as the target in the History tab and switch to it. Only enabled for Variable nodes. |
|
||||
| **Monitor Alarms** | Stop any active alarm subscription and subscribe to alarm events on the selected node. Switches to the Alarms tab with automatic condition refresh. |
|
||||
|
||||
Multi-select is supported (Ctrl+Click, Shift+Click) for the Subscribe action.
|
||||
|
||||
## Read/Write Tab
|
||||
|
||||
Select a node in the browse tree to auto-read its current value. The tab displays:
|
||||
|
||||
- Node ID
|
||||
- Current value (arrays displayed as `[0,1,2,3]`)
|
||||
- Status code (e.g., `0x00000000 (Good)`)
|
||||
- Source and server timestamps
|
||||
|
||||
To write a value, enter the new value and click Send. The service reads the current value first to determine the target type, then converts and writes.
|
||||
|
||||
## Subscriptions Tab
|
||||
|
||||

|
||||
|
||||
Monitor live data changes from subscribed nodes. The tab shows a data grid with:
|
||||
|
||||
| Column | Description |
|
||||
|--------|-------------|
|
||||
| Node ID | The monitored node identifier |
|
||||
| Value | Current value (arrays formatted as `[v1,v2,...]`) |
|
||||
| Status | OPC UA status code with description (e.g., `0x00000000 (Good)`) |
|
||||
| Timestamp | Source timestamp in ISO 8601 format |
|
||||
|
||||
### Adding Subscriptions
|
||||
|
||||
- Type a node ID and click **Add**, or
|
||||
- Right-click nodes in the browse tree and select **Subscribe**
|
||||
|
||||
### Removing Subscriptions
|
||||
|
||||
Select one or more rows (Ctrl+Click for multi-select) and click **Remove**.
|
||||
|
||||
### Writing Values
|
||||
|
||||
Double-click a subscription row to open a write dialog. The dialog:
|
||||
|
||||
1. Pre-fills the current value
|
||||
2. Validates the input can parse to the target type before writing
|
||||
3. Shows the write result status
|
||||
4. Closes automatically on success, shows error in red on failure
|
||||
|
||||
### Tab Header
|
||||
|
||||
The tab header shows the active subscription count: `Subscriptions (26)`.
|
||||
|
||||
### Persistence
|
||||
|
||||
Active subscription node IDs are saved when the application closes or disconnects, and restored on the next connection.
|
||||
|
||||
## Alarms Tab
|
||||
|
||||

|
||||
|
||||
Monitor alarm/condition events from the server.
|
||||
|
||||
### Subscribing
|
||||
|
||||
Enter an optional source node ID and click **Subscribe**. A condition refresh is automatically requested to display current retained alarms. Alternatively, right-click a node in the browse tree and select **Monitor Alarms**.
|
||||
|
||||
### Alarm Display
|
||||
|
||||
The data grid shows retained alarm conditions with color-coded rows:
|
||||
|
||||
| Severity Range | Color |
|
||||
|---------------|-------|
|
||||
| Inactive | Light grey |
|
||||
| Low (0–332) | Light blue |
|
||||
| Medium (333–665) | Light yellow |
|
||||
| High (666–899) | Light red |
|
||||
| Critical (900–1000) | Red |
|
||||
|
||||
Alarms are updated in place when the server re-sends condition state changes. Non-retained alarms are automatically removed.
|
||||
|
||||
### Acknowledging Alarms
|
||||
|
||||
Right-click an active, unacknowledged alarm and select **Acknowledge...**. Enter an acknowledgment comment in the popup dialog. The alarm is acknowledged via the OPC UA `Acknowledge` method on the condition node.
|
||||
|
||||
### Tab Header
|
||||
|
||||
The tab header shows active unacknowledged alarm count: `Alarms (2)`.
|
||||
|
||||
### Persistence
|
||||
|
||||
The alarm subscription source node is saved and restored on reconnection with automatic condition refresh.
|
||||
|
||||
## History Tab
|
||||
|
||||

|
||||
|
||||
Read historical data from the Wonderware Historian.
|
||||
|
||||
### Time Range
|
||||
|
||||

|
||||
|
||||
The date/time range picker provides:
|
||||
|
||||
- **Text input** — Type start and end times in `yyyy-MM-dd HH:mm:ss` format (UTC)
|
||||
- **Preset buttons** — Quick selection: 5m (last 5 minutes), 1h (last hour), 1d (last day), 1w (last week)
|
||||
|
||||
All times are in UTC. Invalid input turns red on blur.
|
||||
|
||||
### Query Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| Aggregate | Raw (default), Average, Minimum, Maximum, Count, Start, End |
|
||||
| Interval (ms) | Processing interval for aggregate queries (shown only for aggregates) |
|
||||
| Max Values | Maximum number of raw values to return (default 1000) |
|
||||
|
||||
### Results
|
||||
|
||||
The results grid displays:
|
||||
|
||||
| Column | Description |
|
||||
|--------|-------------|
|
||||
| Value | The historical value (arrays formatted) |
|
||||
| Status | OPC UA status code with description |
|
||||
| Source Timestamp | When the value was recorded |
|
||||
| Server Timestamp | When the server processed the value |
|
||||
|
||||
## Status Bar
|
||||
|
||||
The bottom status bar shows:
|
||||
|
||||
- Connection state and endpoint URL
|
||||
- Server name and session identifier
|
||||
- Active subscription count
|
||||
|
||||
## Architecture
|
||||
|
||||
### Deferred Initialization
|
||||
|
||||
The OPC UA SDK is not loaded until the user clicks Connect. This keeps application startup instant. The `IOpcUaClientService` and all child ViewModels are created on first connection.
|
||||
|
||||
### UI Thread Dispatch
|
||||
|
||||
All service event handlers (data changes, alarm events, connection state changes) are dispatched through an `IUiDispatcher` abstraction before updating `ObservableCollection`s. In production this wraps `Dispatcher.UIThread.Post()`; in tests it runs synchronously.
|
||||
|
||||
### ViewModels
|
||||
|
||||
| ViewModel | Responsibility |
|
||||
|-----------|---------------|
|
||||
| `MainWindowViewModel` | Connection lifecycle, tab coordination, settings persistence |
|
||||
| `BrowseTreeViewModel` | Root node loading, tree clearing |
|
||||
| `TreeNodeViewModel` | Lazy-load children on expand via `BrowseAsync` |
|
||||
| `ReadWriteViewModel` | Auto-read on selection, write with type coercion |
|
||||
| `SubscriptionsViewModel` | Add/remove subscriptions, DataChanged event handling |
|
||||
| `AlarmsViewModel` | Alarm subscribe/unsubscribe, event filtering, acknowledge |
|
||||
| `HistoryViewModel` | Raw and aggregate history reads |
|
||||
|
||||
### Custom Controls
|
||||
|
||||
| Control | Description |
|
||||
|---------|-------------|
|
||||
| `DateTimeRangePicker` | UTC start/end text inputs with preset duration buttons |
|
||||
|
||||
## Testing
|
||||
|
||||
The UI has 102 unit tests covering ViewModel logic and headless rendering:
|
||||
|
||||
```bash
|
||||
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Client.UI.Tests
|
||||
```
|
||||
|
||||
Tests use:
|
||||
- `FakeOpcUaClientService` — configurable fake implementing `IOpcUaClientService`
|
||||
- `SynchronousUiDispatcher` — runs dispatch actions inline for deterministic testing
|
||||
- `FakeSettingsService` — tracks save/load calls for settings persistence tests
|
||||
- Avalonia headless rendering — screenshot capture for visual verification
|
||||
Reference in New Issue
Block a user