# Client CLI ## Overview `ZB.MOM.WW.OtOpcUa.Client.CLI` is a cross-platform command-line client for the OtOpcUa OPC UA server. It targets .NET 10 and uses the shared `IOpcUaClientService` from `Client.Shared` for all OPC UA operations. Commands are routed and parsed by [CliFx](https://github.com/Tyrrrz/CliFx). The CLI is the primary tool for operators and developers to test and interact with the server from a terminal. It supports all core operations: connectivity testing, browsing, reading, writing, subscriptions, alarm monitoring, alarm operator actions (acknowledge / confirm / shelve / enable / disable), history reads, and redundancy queries. Any driver surface exposed by the server (Galaxy, Modbus, S7, AB CIP, AB Legacy, TwinCAT, FOCAS, OPC UA Client) is reachable through these commands — the CLI is driver-agnostic because everything below the OPC UA endpoint is. ## Build and Run ```bash cd src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI dotnet build dotnet run -- [options] ``` The executable name is `otopcua-cli`. Dev boxes carrying a pre-task-#208 install may still have the legacy `{LocalAppData}/LmxOpcUaClient/` folder on disk; on first launch of any post-#208 CLI or UI build, `ClientStoragePaths` (`src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/ClientStoragePaths.cs`) migrates it to `{LocalAppData}/OtOpcUaClient/` automatically so trusted certificates + saved settings survive the rename. ## Architecture All commands inherit from `CommandBase`, which provides common connection options and helper methods. Every command follows the same lifecycle: 1. Build `ConnectionSettings` from common options 2. Create an `IOpcUaClientService` through the factory 3. Call `ConnectAsync` to establish a session 4. Perform the command-specific operation 5. Call `DisconnectAsync` in a `finally` block No command accesses the OPC UA `Session` directly; all operations go through the shared service abstraction. This ensures consistent behavior with the desktop UI client. ## Common Options All commands accept these options: | Flag | Description | |------|-------------| | `-u` / `--url` | OPC UA server endpoint URL (required) | | `-U` / `--username` | Username for `UserName` token authentication | | `-P` / `--password` | Password for `UserName` token authentication | | `-S` / `--security` | Transport security mode: `none`, `sign`, `encrypt`, `signandencrypt` (default: `none`) | | `-F` / `--failover-urls` | Comma-separated failover endpoint URLs for redundancy | | `--verbose` | Enable debug-level Serilog console logging (default: warning) | ### Authentication When `-U` and `-P` are provided, the shared service passes a `UserIdentity(username, password)` to the OPC UA session. Without credentials, anonymous identity is used. ```bash otopcua-cli write -u opc.tcp://localhost:4840 -n "ns=2;s=MyNode" -v 42 -U operator -P op123 ``` ### Failover When `-F` is provided, the shared service tries the primary URL first, then each failover URL in order. For long-running commands (`subscribe`, `alarms`), the service monitors the session via keep-alive and automatically reconnects to the next available server on failure. ```bash otopcua-cli connect -u opc.tcp://localhost:4840/OtOpcUa -F opc.tcp://localhost:4841/OtOpcUa ``` ### Transport Security When `sign` or `encrypt` is specified, the shared service: 1. Ensures a client application certificate exists under `{LocalAppData}/OtOpcUaClient/pki/` (auto-created if missing; pre-rename `LmxOpcUaClient/` is migrated in place on first launch) 2. Discovers server endpoints and selects one matching the requested security mode 3. Prefers `Basic256Sha256` when multiple matching endpoints exist 4. Fails with a clear error if no matching endpoint is found ```bash otopcua-cli browse -u opc.tcp://localhost:4840/OtOpcUa -S encrypt -U admin -P secret -r -d 2 ``` ### Verbose Logging The `--verbose` flag switches Serilog output from `Warning` to `Debug` level, showing internal connection lifecycle, endpoint discovery, and OPC UA SDK diagnostics on the console. ## Commands ### connect Tests connectivity to an OPC UA server. Creates a session, prints connection metadata, and disconnects. ```bash otopcua-cli connect -u opc.tcp://localhost:4840/OtOpcUa -U admin -P admin123 ``` Output: ```text Connected to: opc.tcp://localhost:4840/OtOpcUa Server: OtOpcUa Server Security Mode: None Security Policy: http://opcfoundation.org/UA/SecurityPolicy#None Connection successful. ``` ### read Reads the current value of a single node and prints the value, status code, and timestamps. ```bash otopcua-cli read -u opc.tcp://localhost:4840/OtOpcUa -n "ns=3;s=DEV.ScanState" -U admin -P admin123 ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Node ID to read (required) | Output: ```text Node: ns=3;s=DEV.ScanState Value: True Status: 0x00000000 Source Time: 2026-03-30T19:58:38.0961252Z Server Time: 2026-03-30T19:58:38.0971257Z ``` ### write Writes a value to a node. The shared service reads the current value first to determine the target data type, then converts the supplied string value using `ValueConverter.ConvertValue()`. ```bash otopcua-cli write -u opc.tcp://localhost:4840 -n "ns=2;s=MyNode" -v 42 ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Node ID to write to (required) | | `-v` / `--value` | Value to write (required) | ### browse Browses the OPC UA address space starting from the Objects folder or a specified node. Supports recursive traversal with a configurable depth limit. Output uses tree-style indentation with `[Object]`, `[Variable]`, and `[Method]` markers. ```bash # Browse top-level Objects folder otopcua-cli browse -u opc.tcp://localhost:4840/OtOpcUa -U admin -P admin123 # Browse a specific node recursively to depth 3 otopcua-cli browse -u opc.tcp://localhost:4840/OtOpcUa -U admin -P admin123 -r -d 3 -n "ns=3;s=ZB" ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Node ID to browse (default: Objects folder) | | `-d` / `--depth` | Maximum browse depth (default: 1) | | `-r` / `--recursive` | Browse recursively using `-d` as max depth | ### subscribe Monitors a node (or every Variable in its subtree) for value changes using OPC UA subscriptions. Prints each data-change notification with timestamp, value, and status code, then prints a summary on exit. Exits on Ctrl+C, or automatically after `--duration` seconds. ```bash # Subscribe to a single node otopcua-cli subscribe -u opc.tcp://localhost:4840 -n "ns=2;s=MyNode" -i 500 # Browse a subtree and subscribe to every Variable, run for 60 seconds, write the summary to disk otopcua-cli subscribe -u opc.tcp://localhost:4840 -n "ns=3;s=ZB" -r --max-depth 4 \ --duration 60 --quiet --summary-file C:\Temp\subscribe-summary.txt ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Node ID to monitor (required). When `--recursive` is set, this is the browse root. | | `-i` / `--interval` | Sampling interval in milliseconds (default: 1000) | | `-r` / `--recursive` | Browse recursively from `--node` and subscribe to every Variable found | | `--max-depth` | Maximum recursion depth when `--recursive` is set (default: 10) | | `-q` / `--quiet` | Suppress per-update output; only print the final summary | | `--duration` | Auto-exit after N seconds and print the summary (0 = run until Ctrl+C, default: 0) | | `--summary-file` | Also write the summary to this file path on exit | #### Summary buckets The summary prints per-node counts across these buckets: - **Ever went BAD during window** — node received at least one notification whose status was not Good. - **NEVER went bad (suspect)** — node received at least one notification and every one was Good. - **Last status GOOD / NOT-GOOD** — final observed status across nodes that received any update. - **No update received at all** — node was subscribed but no notification arrived during the window. ### historyread Reads historical data from a node. Supports raw history reads and aggregate (processed) history reads. `--start` and `--end` are parsed with `CultureInfo.InvariantCulture` and treated as UTC; supply them in ISO 8601 UTC form (`YYYY-MM-DDTHH:MM:SSZ`) for unambiguous behaviour across hosts. ```bash # Raw history otopcua-cli historyread -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001.TestHistoryValue" \ --start "2026-03-25T00:00:00Z" --end "2026-03-30T00:00:00Z" # Aggregate: 1-hour average otopcua-cli historyread -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001.TestHistoryValue" \ --start "2026-03-25T00:00:00Z" --end "2026-03-30T00:00:00Z" \ --aggregate Average --interval 3600000 ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Node ID to read history for (required) | | `--start` | Start time in ISO 8601 UTC format, e.g. `2026-01-15T08:00:00Z` (default: 24 hours ago) | | `--end` | End time in ISO 8601 UTC format, e.g. `2026-01-15T09:00:00Z` (default: now) | | `--max` | Maximum number of values (default: 1000) | | `--aggregate` | Aggregate function: Average, Minimum, Maximum, Count, Start, End, StandardDeviation | | `--interval` | Processing interval in milliseconds for aggregates (default: 3600000) | #### Aggregate mapping | Name | Aliases | OPC UA Node ID | |------|---------|---------------| | `Average` | `avg` | `AggregateFunction_Average` | | `Minimum` | `min` | `AggregateFunction_Minimum` | | `Maximum` | `max` | `AggregateFunction_Maximum` | | `Count` | | `AggregateFunction_Count` | | `Start` | `first` | `AggregateFunction_Start` | | `End` | `last` | `AggregateFunction_End` | | `StandardDeviation` | `stddev`, `stdev` | `AggregateFunction_StandardDeviationPopulation` | ### alarms Subscribes to alarm events on a node. Prints structured alarm output including source, condition, severity, active/acknowledged state, and message. Runs until Ctrl+C, then unsubscribes and disconnects cleanly. ```bash # Subscribe to alarm events on the Server node otopcua-cli alarms -u opc.tcp://localhost:4840/OtOpcUa # Subscribe to a specific source node with condition refresh otopcua-cli alarms -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001" --refresh ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Node ID to monitor for events (default: Server node) | | `-i` / `--interval` | Publishing interval in milliseconds (default: 1000) | | `--refresh` | Request a `ConditionRefresh` after subscribing to get current retained alarm states | ### ack Acknowledges an active alarm condition via the OPC UA Part 9 `Acknowledge` method. The server gates this on the **`AlarmAck`** LDAP role (fail-closed — a session without `AlarmAck` group membership gets `BadUserAccessDenied`; see [ScriptedAlarms.md](ScriptedAlarms.md) §"AlarmAck gate"). The `--event-id` is the hex-encoded `EventId` taken from a prior `alarms` subscription notification for the same condition. ```bash otopcua-cli ack -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001" -e 0A1B2C -c "Investigating" ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Condition node ID of the alarm to acknowledge (**required**) | | `-e` / `--event-id` | `EventId` from the alarm notification, hex-encoded (e.g. `0A1B2C`) (**required**) | | `-c` / `--comment` | Operator comment for the acknowledgment (optional) | Example output: ```text Acknowledge successful: ns=1;s=TestMachine_001 ``` ### confirm Confirms an already-acknowledged alarm condition via the OPC UA Part 9 `Confirm` method — the second stage of two-stage acknowledgment. Same `AlarmAck` role gate and same `--event-id` source (a prior `alarms` notification) as `ack`. ```bash otopcua-cli confirm -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001" -e 0A1B2C -c "Root cause cleared" ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Condition node ID of the alarm to confirm (**required**) | | `-e` / `--event-id` | `EventId` from the alarm notification, hex-encoded (**required**) | | `-c` / `--comment` | Operator comment for the confirmation (optional) | Example output: ```text Confirm successful: ns=1;s=TestMachine_001 ``` ### shelve Shelves or unshelves an active alarm condition via the OPC UA Part 9 `ShelvedStateMachine` (`OneShotShelve` / `TimedShelve` / `Unshelve`). Gated on the `AlarmAck` role. `--duration` is given in **seconds** (converted to milliseconds for the OPC UA call) and is required only for `--kind Timed`. ```bash # One-shot shelve until the next clear otopcua-cli shelve -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001" -k OneShot # Timed shelve for 300 seconds otopcua-cli shelve -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001" -k Timed -d 300 # Unshelve otopcua-cli shelve -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001" -k Unshelve ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Condition node ID of the alarm to shelve/unshelve (**required**) | | `-k` / `--kind` | Shelve operation: `OneShot` \| `Timed` \| `Unshelve` (**required**) | | `-d` / `--duration` | Shelving duration in **seconds** (must be > 0; **required for `--kind Timed`**) | Example output: ```text Timed successful: ns=1;s=TestMachine_001 ``` ### enable Enables an alarm condition via the OPC UA Part 9 `ConditionType.Enable` method (resumes evaluation/reporting for a previously disabled condition). Gated on the `AlarmAck` role. ```bash otopcua-cli enable -u opc.tcp://localhost:4840/OtOpcUa -n "ns=1;s=TestMachine_001" ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Condition node ID of the alarm to enable (**required**) | Example output: ```text Enable successful: ns=1;s=TestMachine_001 ``` ### disable Disables an alarm condition via the OPC UA Part 9 `ConditionType.Disable` method (stops evaluation/reporting). Gated on the `AlarmAck` role. ```bash otopcua-cli disable -u opc.tcp://localhost:4840/OtOpcUa -n "ns=1;s=TestMachine_001" ``` | Flag | Description | |------|-------------| | `-n` / `--node` | Condition node ID of the alarm to disable (**required**) | Example output: ```text Disable successful: ns=1;s=TestMachine_001 ``` ### redundancy Reads the OPC UA redundancy state from a server: redundancy mode, service level, server URIs, and application URI. ```bash otopcua-cli redundancy -u opc.tcp://localhost:4840/OtOpcUa -U admin -P admin123 ``` Example output: ```text Redundancy Mode: Warm Service Level: 200 Server URIs: - urn:localhost:OtOpcUa:instance1 - urn:localhost:OtOpcUa:instance2 Application URI: urn:localhost:OtOpcUa:instance1 ``` ## Testing The Client CLI has 104 unit tests covering option parsing, service invocation, output formatting, and cleanup behavior: ```bash dotnet test tests/Client/ZB.MOM.WW.OtOpcUa.Client.CLI.Tests ```