# 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 -- <command> [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
```
