Doc refresh (task #205) — requirements updated for multi-driver OtOpcUa three-process deploy
Per-file summary: - docs/reqs/OpcUaServerReqs.md — rewritten driver-agnostic. OPC-001..OPC-013 re-scoped to multi-driver address-space composition + capability dispatch; OPC-014 AuthorizationGate + permission trie; OPC-015 dynamic ServiceLevel via RedundancyCoordinator; OPC-017 surgical generation-apply rebuild; OPC-012 capability dispatch via CapabilityInvoker (decision #143 idempotence-aware retry); OPC-013 per-host Polly isolation (decision #144); OPC-019 OpenTelemetry metrics. Transport-security profile matrix (OPC-010) + UserName/LDAP (OPC-011) preserved. - docs/reqs/GalaxyRepositoryReqs.md — scope clarified as Galaxy-driver-only (not platform). GR-001..GR-004 tied to ITagDiscovery.DiscoverAsync + IRediscoverable; all SQL runs inside OtOpcUa.Galaxy.Host and streams to Proxy via named pipe. GR-008 capability wrapping via CapabilityInvoker added. Cross-links to docs/v2/driver-specs.md + docs/GalaxyRepository.md. - docs/reqs/MxAccessClientReqs.md — scope clarified as Galaxy-Host-only. MXA-001..MXA-009 preserved (STA pump, register/unregister, subscription refcount, auto-reconnect, probe, COM cleanup, operation metrics, error translation). MXA-010 Proxy-side capability wrapping + MXA-011 pipe ACL + per-process shared secret (OTOPCUA_ALLOWED_SID / OTOPCUA_GALAXY_SECRET) added. - docs/reqs/ServiceHostReqs.md — rewritten for three-process deployment. Shared section (SVC-SHARED-001/002) for Serilog + bootstrap-only appsettings. SRV-* for OtOpcUa.Server (net10 x64, Microsoft.Extensions.Hosting + AddWindowsService, in-process driver hosting, redundancy-node bootstrap). ADM-* for OtOpcUa.Admin (Blazor Server, cookie+LDAP auth, CanEdit/CanPublish policies, sole DB writer, Prometheus /metrics, audit logging). GHX-* for OtOpcUa.Galaxy.Host (TopShelf, net48 x86, named-pipe IPC bootstrap, STA backend lifecycle, crash handling tied to supervisor). - docs/reqs/ClientRequirements.md — restructured as numbered, verifiable requirements. SHR-* for Client.Shared (single IOpcUaClientService, ConnectionSettings, failover, cross-platform certs, type-coercing write, UI-thread neutrality). CLI-001..CLI-011 cover connect/read/write/browse/subscribe/historyread/alarms/redundancy. UI-001..UI-008 cover connection panel, tree browser, each tab, connection-state reflection, cross-platform build. Reference design content (IOpcUaClientService shape, models, view-model map, mock layout) preserved. - docs/reqs/StatusDashboardReqs.md — retired cleanly. Replaced with a pointer to docs/v2/admin-ui.md + HLR-015 / HLR-016 / HLR-017 / ADM-*. Mapping table shows each retired DASH-001..DASH-009 requirement's replacement (live cluster-node view via SignalR, Prometheus metrics, driver-instance detail views, etc.). Note that a formal AdminUiReqs.md can be written later if needed for cert compliance. HighLevelReqs.md was already at the target shape (HLR-001..HLR-018 with Revision header noting retired HLR-009) as of commit f217636; verified identical and no additional edit required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
# OPC UA Client Requirements
|
||||
|
||||
## Overview
|
||||
> **Revision** — Refreshed 2026-04-19 for the OtOpcUa v2 multi-driver platform (task #205). The Client surface (shared library + CLI + UI) shipped for v2 is preserved; this refresh restructures the document into numbered, directly-verifiable requirements (CLI-* and UI-* prefixes) layered on top of the existing detailed design content. Requirement coverage added for the `redundancy` command, alarm subscribe/ack round-trip, history-read, and UI tree-browser drag-to-subscribe behaviors. Original design-spec material for `ConnectionSettings`, `IOpcUaClientService`, models, and view-models is retained as reference-level details below the numbered requirements.
|
||||
|
||||
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.
|
||||
Parent: [HLR-001](HighLevelReqs.md#hlr-001-opc-ua-server), [HLR-009](HighLevelReqs.md#hlr-009-transport-security-and-authentication), [HLR-013](HighLevelReqs.md#hlr-013-cluster-redundancy)
|
||||
|
||||
See also: `docs/Client.CLI.md`, `docs/Client.UI.md`.
|
||||
|
||||
## Projects
|
||||
|
||||
@@ -10,134 +12,161 @@ Three new .NET 10 cross-platform projects providing a shared OPC UA client libra
|
||||
|---------|------|---------|
|
||||
| `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 |
|
||||
| `ZB.MOM.WW.OtOpcUa.Client.UI` | Avalonia app | Desktop UI |
|
||||
| `ZB.MOM.WW.OtOpcUa.Client.Shared.Tests` | Test project | Shared-library unit tests |
|
||||
| `ZB.MOM.WW.OtOpcUa.Client.CLI.Tests` | Test project | CLI command tests |
|
||||
| `ZB.MOM.WW.OtOpcUa.Client.UI.Tests` | Test project | ViewModel unit tests |
|
||||
|
||||
## Shared Requirements (Client.Shared)
|
||||
|
||||
### SHR-001: Single Service Interface
|
||||
|
||||
The Client.Shared library shall expose a single service interface `IOpcUaClientService` covering connect, disconnect, read, write, browse, subscribe, alarm-subscribe, alarm-ack, history-read-raw, history-read-aggregate, and get-redundancy-info operations.
|
||||
|
||||
### SHR-002: ConnectionSettings Model
|
||||
|
||||
The library shall expose a `ConnectionSettings` record with the fields: `EndpointUrl` (required), `FailoverUrls[]`, `Username`, `Password`, `SecurityMode` (None/Sign/SignAndEncrypt; default None), `SessionTimeoutSeconds` (default 60), `AutoAcceptCertificates` (default true), `CertificateStorePath`.
|
||||
|
||||
### SHR-003: Automatic Failover
|
||||
|
||||
The library shall monitor session keep-alive and automatically fail over across `FailoverUrls` when the primary endpoint is unreachable, emitting a `ConnectionStateChanged` event on each transition (Disconnected / Connecting / Connected / Reconnecting).
|
||||
|
||||
### SHR-004: Cross-Platform Certificate Store
|
||||
|
||||
The library shall auto-generate a client certificate on first use and store it in a cross-platform path (default `{AppData}/OtOpcUaClient/pki/`). Server certificates are auto-accepted when `AutoAcceptCertificates = true`.
|
||||
|
||||
### SHR-005: Type-Coercing Write
|
||||
|
||||
The library's `WriteValueAsync(NodeId, object)` shall read the node's current value to determine target type and coerce the input value before sending.
|
||||
|
||||
### SHR-006: UI-Thread Dispatch Neutrality
|
||||
|
||||
The library shall not assume any specific synchronization context. Events (`DataChanged`, `AlarmEvent`, `ConnectionStateChanged`) are raised on the OPC UA stack thread; the consuming CLI / UI is responsible for dispatching to its UI thread.
|
||||
|
||||
---
|
||||
|
||||
## CLI Requirements (Client.CLI)
|
||||
|
||||
### CLI-001: Command Surface
|
||||
|
||||
The CLI shall expose the following commands: `connect`, `read`, `write`, `browse`, `subscribe`, `historyread`, `alarms`, `redundancy`.
|
||||
|
||||
### CLI-002: Common Options
|
||||
|
||||
All CLI commands shall accept the options `-u, --url` (required), `-U, --username`, `-P, --password`, `-S, --security none|sign|encrypt`, `-F, --failover-urls` (comma-separated), `--verbose`.
|
||||
|
||||
### CLI-003: Connect Command
|
||||
|
||||
The `connect` command shall attempt to establish a session using the supplied options and print `Connected` plus the resolved endpoint's `ServerUriArray` and `ApplicationUri` on success, or a diagnostic error message on failure.
|
||||
|
||||
### CLI-004: Read Command
|
||||
|
||||
The `read -n <NodeId>` command shall print `NodeId`, `Value`, `StatusCode`, `SourceTimestamp`, `ServerTimestamp` one per line.
|
||||
|
||||
### CLI-005: Write Command
|
||||
|
||||
The `write -n <NodeId> -v <value>` command shall coerce the value to the node's current type (per SHR-005) and print the resulting `StatusCode`. A `Bad_UserAccessDenied` result is printed verbatim so operators see the authorization outcome.
|
||||
|
||||
### CLI-006: Browse Command
|
||||
|
||||
The `browse [-n <parent>] [-r] [-d <depth>]` command shall list child nodes under `parent` (or the `Objects` folder if omitted). `-r` enables recursion up to `-d` depth (default 1).
|
||||
|
||||
### CLI-007: Subscribe Command
|
||||
|
||||
The `subscribe -n <NodeId> -i <intervalMs>` command shall create a monitored item at `intervalMs` publishing interval, print each `DataChanged` event as `<timestamp> <nodeId> <value> <status>` until Ctrl-C, then cleanly unsubscribe.
|
||||
|
||||
### CLI-008: Historyread Command
|
||||
|
||||
The `historyread -n <NodeId> --start <utc> --end <utc> [--max <n>] [--aggregate <type> --interval <ms>]` command shall print raw values or aggregate buckets. Supported aggregate types: Average, Minimum, Maximum, Count, Start, End.
|
||||
|
||||
### CLI-009: Alarms Command
|
||||
|
||||
The `alarms [-n <source>] [-i <intervalMs>]` command shall subscribe to alarm events, print each event as `<time> <source> <condition> <severity> <state> <acked> <message>`, accept `ack <conditionId>` commands interactively, and support `refresh` to trigger `RequestConditionRefreshAsync`.
|
||||
|
||||
### CLI-010: Redundancy Command
|
||||
|
||||
The `redundancy` command shall call `GetRedundancyInfoAsync` and print `Mode`, `ServiceLevel`, `ApplicationUri`, and `ServerUris` (one per line). Suitable for redundancy-failover smoke tests.
|
||||
|
||||
### CLI-011: Logging
|
||||
|
||||
The CLI shall use Serilog console sink at `Warning` minimum by default; `--verbose` raises to `Debug`.
|
||||
|
||||
---
|
||||
|
||||
## UI Requirements (Client.UI)
|
||||
|
||||
### UI-001: Connection Panel
|
||||
|
||||
The UI shall present a top-bar connection panel with fields for Endpoint URL, Username, Password, Security mode, and a Connect / Disconnect button. The resolved `RedundancyInfo` is displayed next to the bar on successful connect.
|
||||
|
||||
### UI-002: Tree Browser
|
||||
|
||||
The UI shall present a left-pane tree browser backed by `IOpcUaClientService.BrowseAsync`, lazy-loading children on node expansion (one level per `BrowseAsync` call).
|
||||
|
||||
### UI-003: Read/Write Tab
|
||||
|
||||
The UI shall provide a Read/Write tab that auto-reads the selected tree node's current value, displays `Value` + `StatusCode` + `SourceTimestamp`, and accepts a write value with a Send button.
|
||||
|
||||
### UI-004: Subscriptions Tab
|
||||
|
||||
The UI shall provide a Subscriptions tab that lists active monitored items (columns: NodeId, Value, Status, Timestamp), supports Add and Remove, and dispatches `DataChanged` events to the Avalonia UI thread via `Dispatcher.UIThread.Post`.
|
||||
|
||||
### UI-005: Alarms Tab
|
||||
|
||||
The UI shall provide an Alarms tab that supports SubscribeAlarms / UnsubscribeAlarms / RefreshConditions commands, displays live alarm events, and supports `Acknowledge` on selected events. Acknowledgment failure (including `Bad_UserAccessDenied`) is surfaced to the user.
|
||||
|
||||
### UI-006: History Tab
|
||||
|
||||
The UI shall provide a History tab with inputs for StartTime, EndTime, MaxValues, AggregateType, Interval, a Read command, and a results table with columns (Timestamp, Value, Status).
|
||||
|
||||
### UI-007: Connection State Reflects in UI
|
||||
|
||||
All tabs shall reflect the connection state — when disconnected, all action commands are disabled; the status bar shows `Disconnected` / `Connecting` / `Connected` / `Reconnecting` tied to the `ConnectionStateChanged` event.
|
||||
|
||||
### UI-008: Cross-Platform
|
||||
|
||||
The UI shall build and run on Windows (win-x64) and macOS (osx-arm64 / osx-x64). No platform-specific OPC UA stack APIs are used.
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- .NET 10, C#
|
||||
- OPC UA: OPCFoundation.NetStandard.Opc.Ua.Client
|
||||
- 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
|
||||
## Client.Shared — Design Detail
|
||||
|
||||
### ConnectionSettings Model
|
||||
### IOpcUaClientService Interface (reference)
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
**Lifecycle:** `ConnectAsync(ConnectionSettings)`, `DisconnectAsync()`, `IsConnected`.
|
||||
|
||||
### IOpcUaClientService Interface
|
||||
**Read/Write:** `ReadValueAsync(NodeId)`, `WriteValueAsync(NodeId, object)`.
|
||||
|
||||
Single service interface covering all OPC UA operations:
|
||||
**Browse:** `BrowseAsync(NodeId? parent)` → `BrowseResult[]` (NodeId, DisplayName, NodeClass, HasChildren); lazy-load compatible.
|
||||
|
||||
**Lifecycle:**
|
||||
- `ConnectAsync(ConnectionSettings)` — connect to server, handle endpoint discovery, security, auth
|
||||
- `DisconnectAsync()` — close session cleanly
|
||||
- `IsConnected` property
|
||||
**Subscribe:** `SubscribeAsync(NodeId, int intervalMs)`, `UnsubscribeAsync(NodeId)`, `event DataChanged(NodeId, DataValue)`.
|
||||
|
||||
**Read/Write:**
|
||||
- `ReadValueAsync(NodeId)` — returns DataValue (value, status, timestamps)
|
||||
- `WriteValueAsync(NodeId, object value)` — auto-detects target type, returns StatusCode
|
||||
**Alarms:** `SubscribeAlarmsAsync(NodeId? source, int intervalMs)`, `UnsubscribeAlarmsAsync()`, `AcknowledgeAsync(conditionId, comment)`, `RequestConditionRefreshAsync()`, `event AlarmEvent(AlarmEventArgs)`.
|
||||
|
||||
**Browse:**
|
||||
- `BrowseAsync(NodeId? parent)` — returns list of BrowseResult (NodeId, DisplayName, NodeClass)
|
||||
- Lazy-load compatible (browse one level at a time)
|
||||
**History:** `HistoryReadRawAsync(NodeId, start, end, maxValues)`, `HistoryReadAggregateAsync(NodeId, start, end, AggregateType, intervalMs)`.
|
||||
|
||||
**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
|
||||
**Redundancy:** `GetRedundancyInfoAsync()` → `RedundancyInfo` (Mode, ServiceLevel, ServerUris, ApplicationUri).
|
||||
|
||||
### 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)
|
||||
- `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
|
||||
## Client.UI — View Layout (reference)
|
||||
|
||||
Single-window Avalonia application:
|
||||
|
||||
@@ -146,82 +175,43 @@ 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) │ │ ││
|
||||
│ │ └──────────────────────────────────────┘│
|
||||
│ │ ┌Read/Write┬Subscriptions┬Alarms┬History┐│
|
||||
│ Address │ │ Node: ns=3;s=Tag.Attr ││
|
||||
│ Space │ │ Value: 42.5 Status: Good ││
|
||||
│ Tree │ │ [Write: ____] [Send] ││
|
||||
│ Browser │ └───────────────────────────────────────┘│
|
||||
├──────────────┴──────────────────────────────────────────┤
|
||||
│ Status: Connected | Session: abc123 | 3 subscriptions │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Views and ViewModels (CommunityToolkit.Mvvm)
|
||||
### 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.
|
||||
- `MainWindowViewModel` — connection fields, connect/disconnect commands, `ConnectionState`, `RedundancyInfo`, `SelectedTreeNode`, `StatusMessage`.
|
||||
- `BrowseTreeViewModel` — root collection (`ObservableCollection<TreeNodeViewModel>`), lazy-load on expand.
|
||||
- `ReadWriteViewModel` — auto-read on selection, `WriteValue` + `WriteCommand`.
|
||||
- `SubscriptionsViewModel` — `ActiveSubscriptions`, `AddSubscription`, `RemoveSubscription`, live `DataChanged` dispatch to UI thread.
|
||||
- `AlarmsViewModel` — `AlarmEvents`, Subscribe / Unsubscribe / Refresh / Acknowledge commands.
|
||||
- `HistoryViewModel` — `StartTime`, `EndTime`, `MaxValues`, `AggregateType`, `Interval`, `ReadCommand`, `Results`.
|
||||
|
||||
## Test Projects
|
||||
|
||||
### Client.Shared.Tests
|
||||
- ConnectionSettings validation
|
||||
- Type conversion (ConvertValue)
|
||||
- BrowseResult model construction
|
||||
- AlarmEventArgs model construction
|
||||
- `ConnectionSettings` validation
|
||||
- Type conversion
|
||||
- `BrowseResult` / `AlarmEventArgs` / `RedundancyInfo` model construction
|
||||
- FailoverUrl parsing
|
||||
|
||||
### Client.CLI.Tests
|
||||
- Command option parsing (via CliFx test infrastructure)
|
||||
- Output formatting
|
||||
- Output formatting for each command
|
||||
|
||||
### Client.UI.Tests
|
||||
- ViewModel property change notifications
|
||||
- Command can-execute logic
|
||||
- Tree node lazy-load behavior (with mocked IOpcUaClientService)
|
||||
- ViewModel property-change notifications
|
||||
- Command `CanExecute` logic
|
||||
- Tree 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
|
||||
- Shouldly
|
||||
- No live OPC UA server required — mock `IOpcUaClientService` for unit tests
|
||||
|
||||
Reference in New Issue
Block a user