# OPC UA Client (Gateway) Driver

Getting-started guide for the OPC UA Client driver. This is the short path — for
the full per-field spec read [`docs/v2/driver-specs.md §8`](../v2/driver-specs.md),
and for the test-harness map read [OpcUaClient-Test-Fixture.md](OpcUaClient-Test-Fixture.md).

## What it talks to

A **remote OPC UA server**. This driver runs the *opposite* direction from the
usual "server exposes PLC data" flow: it acts as an OPC UA **client**, opens a
`Session` against an upstream server, and re-exposes that server's address space
through the local OtOpcUa server. Browse, read, write, subscribe, alarm, and
history calls are passed through to the upstream endpoint.

It is built on the OPC Foundation UA .NET Standard reference SDK and runs
in-process in the OtOpcUa server's .NET 10 AnyCPU host — pure managed, no
out-of-process isolation.

> There is **no standalone driver CLI** for the OPC UA Client driver. To exercise
> a remote OPC UA endpoint by hand, point the general-purpose
> [Client CLI](../Client.CLI.md) at it directly.

## Project split

| Project | Target | Role |
|---------|--------|------|
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/` | net10.0 | In-process driver — session lifetime, read / write / subscribe / alarm / history passthrough |
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/` | net10.0 | `IDriverBrowser` — live address-picker browse used by the AdminUI |
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts/` | net10.0 | Config records + enums bound from `DriverConfig` JSON |

## Minimum deployment

```jsonc
"Drivers": {
  "upstream-1": {
    "Type": "OpcUaClient",
    "Config": {
      "EndpointUrl": "opc.tcp://plc.internal:4840",
      "SecurityPolicy": "None",
      "SecurityMode": "None",
      "AuthType": "Anonymous",
      "TargetNamespaceKind": "Equipment"
    }
  }
}
```

`EndpointUrls` (a list) takes precedence over the single-URL `EndpointUrl` and
provides ordered **failover** — the driver tries each candidate in turn at init
and on session drop, and the first to connect wins (e.g. a hot-standby pair on
4840 / 4841). See
`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts/OpcUaClientDriverOptions.cs`
for every field (security policy/mode, auth type, session timeout, keep-alive,
reconnect period, browse root, node/depth caps).

### Session lifetime

A single `Session` per driver instance; subscriptions multiplex onto it. The
SDK reconnect handler takes the session down and brings it back on remote-server
restart, re-sending subscriptions on reconnect so monitored-item handles don't
dangle. Stored NodeIds embed the server-stable namespace **URI** (not the
session-relative `ns=N` index) so a remote namespace-table reorder across a
restart doesn't silently re-point references at the wrong namespace.

### Namespace assignment

This is the only driver that gateways into **either** namespace kind, decided
per instance via `TargetNamespaceKind`:

- `Equipment` — the remote server exposes raw equipment data; remote browse
  paths are remapped to UNS via a required `UnsMappingTable`.
- `SystemPlatform` — the remote server exposes processed/derived data; the
  remote hierarchy is preserved with no UNS conversion (and the mapping table
  must be empty).

The choice is enforced at startup so a misconfiguration fails draft validation
rather than surfacing as a runtime surprise.

## Capability surface

`OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IAlarmSource, IHistoryProvider`
(`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs:31`).

| Capability | Path | Notes |
|------------|------|-------|
| `ITagDiscovery` | `DiscoverAsync` (recursive browse) | Mirrors the upstream tree from `BrowseRoot` (default `ObjectsFolder` i=85), bounded by `MaxDiscoveredNodes` / `MaxBrowseDepth` |
| `IReadable` | `ReadAsync` → `Session.ReadAsync` | Upstream `StatusCode`s pass through verbatim (cascading-quality rule) |
| `IWritable` | `WriteAsync` → `Session.WriteAsync` | Passthrough write |
| `ISubscribable` | native OPC UA subscriptions / monitored items | The remote server pushes data changes |
| `IHostConnectivityProbe` | session keep-alive | Host key is the endpoint URL actually connected to after the failover sweep |
| `IAlarmSource` | `SubscribeAlarmsAsync` (EventFilter) + `AcknowledgeAsync` | Subscribes to upstream alarm/condition events and forwards acks |
| `IHistoryProvider` | `ReadRawAsync` / `ReadProcessedAsync` / `ReadAtTimeAsync` → `Session.HistoryReadAsync` | **Unique to this driver** — passthrough history read against the upstream server |

> This driver does **not** implement `IRediscoverable` — there is no
> push-driven rediscovery signal from a remote OPC UA server in this driver.
> `IHistoryProvider` is implemented by no other driver; history reads for every
> other source route server-side through `IHistoryRouter`.

### History passthrough

`IHistoryProvider` forwards `HistoryRead` to the upstream server's own historian.
Raw, processed (Average / Minimum / Maximum / Total / Count aggregates mapped to
OPC UA Part 13 standard aggregate NodeIds), and at-time reads are supported; each
returned `DataValue` keeps its upstream `StatusCode` and timestamps verbatim.
Event-history (`ReadEventsAsync`) is left at the interface default — the
interface doesn't yet carry the EventFilter surface needed to forward it.

### Certificate trust

`AutoAcceptCertificates` accepts any self-signed / untrusted server certificate.
It is **dev-only** — leave it `false` in production so a MITM against the
opc.tcp channel fails closed.

## Testing

- **Unit tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/`
  cover the session lifecycle, namespace remapping, alarm/history passthrough,
  and config binding against a faked SDK session.
- **Integration fixture** — exercises the driver against a reference OPC UA
  server (opc-plc) on the shared docker host; see
  [OpcUaClient-Test-Fixture.md](OpcUaClient-Test-Fixture.md) for the coverage map.

## Further reading

- [`docs/v2/driver-specs.md §8`](../v2/driver-specs.md) — full per-field spec,
  namespace-assignment rules, and cascading-quality detail
- [OpcUaClient-Test-Fixture.md](OpcUaClient-Test-Fixture.md) — test-harness map
- [Client.CLI.md](../Client.CLI.md) — general-purpose OPC UA client CLI for
  ad-hoc browsing of any endpoint
