# OPC UA

> An OPC UA client for third-party servers, with certificate-gated trust, node-id addressing and address-space discovery.

The OPC UA driver is a **client** for third-party OPC UA servers — PLCs, gateways,
historians, other SCADA systems — over `opc.tcp`. It is the outgoing counterpart of the
embedded OPC UA server that publishes this station's own address space (described under
[Settings](settings-opcua-server)); the two share the station's application identity but
trust their peers independently.

## Device {#device}

| Field | Meaning | Default |
| --- | --- | --- |
| **Host** | DNS name, IPv4 or IPv6 address of the server. | `localhost` when left empty |
| **Port** | TCP port of the endpoint. | 4840 (a configured 0 falls back to 4840) |
| **Resource path** | The endpoint path some servers require after host and port, producing `opc.tcp://host:port/path`. Leave empty when the server answers at the root. | empty |
| **Session security** | Off, the session is signed and encrypted and equipment that offers nothing better is refused. **Allow a session without security** opens it anyway, in the clear, for equipment that advertises no secure endpoint. | off |
| **Username** / **Password** | The user identity for the session. Empty username means an anonymous session. The password is protected at rest with per-user Windows DPAPI and can only be read on the machine and Windows account that entered it. | anonymous |
| **Poll interval (ms)** | Default polling rate for tags that do not override it. | 1000 |

## Security and certificates {#security-and-certificates}

Connecting starts with the server's endpoint list: the client picks the **most
secure endpoint the server advertises** and authenticates the application with
this station's own certificate — the same OPC UA application identity the
embedded server uses. Many servers require that certificate to be trusted on
their side before they accept the session; that step happens on the equipment.

Equipment that advertises nothing better than an **unsecured endpoint** is
refused, and the device says why. That session would carry the readings and the
sign-in in the clear and prove no equipment identity, so it opens only where you
asked for it on that device: **Session security**, **Allow a session without
security**, on the connection card. The choice belongs to that one device,
travels in its address as `?security=none`, and covers the reading session and
the discovery browse alike. Turning it on never downgrades equipment that does
offer security: the most secure endpoint advertised is still the one taken.

In the other direction, trust is **fail-closed and explicit**. The equipment's
certificate is checked before the session opens:

- A certificate this station has never approved is refused, recorded for review,
  and the device panel shows a notice naming the approval it is waiting for —
  the device is not "offline", it is waiting for a decision.
- You review certificates under **Settings → Equipment access**, the outgoing half
  of certificate trust: a recorded certificate sits under **Waiting for approval**
  until you **Approve** it (new sessions may open) or **Block** it (refused without
  raising repeated prompts). This is the equipment list; **Client access**, on the
  same page, is the other direction, who may connect inbound to this station's own
  server.
- **Withdraw approval**, on an approved row, closes the live sessions running on
  that certificate and returns it to the review list, so the next connection asks
  you again.
- Equipment that presents **no certificate at all** proves nothing about who
  answered, so there is nothing to approve and the session is refused, with its
  address written to the journal. It connects only under the same **Allow a
  session without security**, which is then what stands in for the approval.

Approving a device server never authorizes that certificate to connect inbound to
this station's own server: the two directions keep separate stores on purpose.

## Session behavior {#session-behavior}

Tags are **polled**: each read asks the server for the node's current value. The
server's own verdict travels with every sample — a value stamped Bad or
Uncertain is refused and the tag reads bad quality, so a doubtful source is
never laundered into a good reading. A node that no longer exists on the server
reads as no value rather than failing the whole device.

The session is supervised with keep-alives; after three missed keep-alives the
client starts an automatic reconnect and carries on. The app's background retry
loop owns initial connection, so an unreachable server costs nothing at startup.

## Tag addressing {#tag-addressing}

A tag is addressed by its **Node id**, typed as free text and sent verbatim.
Accepted forms are the standard OPC UA notations:

| Example | Meaning |
| --- | --- |
| `ns=2;s=Temperature` | String identifier in namespace index 2 |
| `i=2258` | Numeric identifier in namespace 0 (here: the server's CurrentTime) |
| `ns=3;i=1042` | Numeric identifier in namespace index 3 |
| `nsu=urn:vendor:app;s=Line1.Speed` | Expanded form naming the namespace by URI instead of index — robust against index changes across restarts |

The tag reads and writes the node's **Value** attribute.

### Value types {#value-types}

| Offered types |
| --- |
| Boolean, Byte, Int16, Int32, Int64, Float, Double, String |

Values are **scalars only**: arrays, structures and other complex values are
refused as unreadable rather than flattened into a misleading number, and the
tag reads bad quality. A tag like that fails on its own; the device's other tags
keep reading, and nothing is reconnected over it. An empty string is a value, not
a failure. Types outside the offered set (unsigned widths, DateTime, Guid,
ByteString) are not available on this driver.

A scan of a server therefore offers fewer channels than the server holds: a node
that declares a list of values, and a node whose type is outside the offered set,
are left out. They are not dropped in silence, though. The scan counts them and
its closing message says how many and of which type, for example "5 channel(s)
were left out: 3 of type UInt32, 2 that hold a list of values instead of one
value.", so a short list reads as a short list rather than as a bare controller.

## Writes {#writes}

A writable tag writes the node's Value attribute through the OPC UA Write
service. The server's response is the verdict: a rejected write surfaces as a
failure, and the cached value advances only on acceptance. As everywhere, the
write field takes the engineering value and the [value pipeline](connector-value-pipeline) is
reversed before the raw value is sent.

## Discovery {#discovery}

The **Discover** command takes a target in the field beside it: an OPC UA server
address such as `opc.tcp://plc-host:4840`. The scan has two stages, and the
progress line names the one running:

1. **Endpoints** — the target's standard discovery service is queried and every
   advertised discovery URL becomes a candidate endpoint. Custom ports and
   server paths round-trip into the candidate's connection fields.
2. **Channels** — each candidate's address space is browsed from the Objects
   folder down, and every readable variable is listed as a candidate tag with
   its type and access. The server's diagnostics areas are skipped.

Browsing signs in anonymously (with the same most-secure-endpoint selection, the
same refusal of an unsecured session, and the same equipment-certificate gate). A
server that refuses anonymous sessions, a server that advertises no secure
endpoint, or a browse that fails for any reason, still leaves the endpoint
addable with zero tags: add it, set the credentials and the session security on
the device, and configure tags by hand. Nothing is persisted until you **Add** a
candidate, and canceling keeps the candidates that were already complete.

## Limits worth knowing {#limits-worth-knowing}

- Reads are polls of the Value attribute; the driver does not create
  subscriptions on the remote server.
- Scalar values only — a tag cannot address an array element or a structure
  field.
- The approval gate applies per certificate: equipment that rotates its certificate
  returns to the review list and waits for a new decision.
