Renames all 11 projects (5 src + 6 tests), the .slnx solution file, all source-file namespaces, all axaml namespace references, and all v1 documentation references in CLAUDE.md and docs/*.md (excluding docs/v2/ which is already in OtOpcUa form). Also updates the TopShelf service registration name from "LmxOpcUa" to "OtOpcUa" per Phase 0 Task 0.6.
Preserves runtime identifiers per Phase 0 Out-of-Scope rules to avoid breaking v1/v2 client trust during coexistence: OPC UA `ApplicationUri` defaults (`urn:{GalaxyName}:LmxOpcUa`), server `EndpointPath` (`/LmxOpcUa`), `ServerName` default (feeds cert subject CN), `MxAccessConfiguration.ClientName` default (defensive — stays "LmxOpcUa" for MxAccess audit-trail consistency), client OPC UA identifiers (`ApplicationName = "LmxOpcUaClient"`, `ApplicationUri = "urn:localhost:LmxOpcUaClient"`, cert directory `%LocalAppData%\LmxOpcUaClient\pki\`), and the `LmxOpcUaServer` class name (class rename out of Phase 0 scope per Task 0.5 sed pattern; happens in Phase 1 alongside `LmxNodeManager → GenericDriverNodeManager` Core extraction). 23 LmxOpcUa references retained, all enumerated and justified in `docs/v2/implementation/exit-gate-phase-0.md`.
Build clean: 0 errors, 30 warnings (lower than baseline 167). Tests at strict improvement over baseline: 821 passing / 1 failing vs baseline 820 / 2 (one flaky pre-existing failure passed this run; the other still fails — both pre-existing and unrelated to the rename). `Client.UI.Tests`, `Historian.Aveva.Tests`, `Client.Shared.Tests`, `IntegrationTests` all match baseline exactly. Exit gate compliance results recorded in `docs/v2/implementation/exit-gate-phase-0.md` with all 7 checks PASS or DEFERRED-to-PR-review (#7 service install verification needs Windows service permissions on the reviewer's box).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
265 lines
10 KiB
Markdown
265 lines
10 KiB
Markdown
# Client UI
|
||
|
||
## Overview
|
||
|
||
`ZB.MOM.WW.OtOpcUa.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.OtOpcUa.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.OtOpcUa.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
|