OPC-UA Source Node
The OPC-UA Source is currently configured through the ZephFlow SDK and DAG YAML rather than the visual workflow builder. See the ZephFlow OPC-UA Source reference for the SDK configuration keys and a full DAG example. The fields below describe the same settings.
Quick Reference
Endpoint URL
The OPC-UA server endpoint to connect to.
ex: opc.tcp://plc-host:4840
Monitored Nodes The OPC-UA variables to subscribe to. Each needs a namespace index, an identifier, and an identifier type.
Security Policy
None for an unencrypted channel, or Basic256Sha256 to sign and encrypt.
Username / Password (optional) Leave blank to connect anonymously.
Publishing / Sampling Interval How often the server publishes changes and samples each variable, in milliseconds. Default: 1000 each.
Advanced settings — Server Queue Size, Inbound Queue Capacity, Offer Timeout, Shutdown Grace, and Request Timeout tune buffering and connection behavior; the defaults suit most deployments.
Overview
The OPC-UA Source node subscribes to variables on an OPC-UA server and emits one record per data-change notification. It targets industrial automation data — PLCs, SCADA systems, and historians that expose an OPC-UA endpoint.
It is subscription-based rather than polling: you name the variables ("nodes") you care about, and the server pushes a notification only when a value actually changes. Each notification becomes a record carrying the value, its OPC-UA status code, and both the source and server timestamps, so bad-quality reads stay visible downstream instead of silently arriving as values.
Unlike other source nodes there is no encoding type to choose. OPC-UA is a typed protocol, so values arrive already structured and are mapped into a fixed record shape.
Configuration
| Field | Description | Required | Default |
|---|---|---|---|
| Endpoint URL | The OPC-UA server endpoint. Must start with opc.tcp:// (e.g. opc.tcp://plc-host:4840). | Yes | — |
| Monitored Nodes | The OPC-UA variables to subscribe to. At least one is required. See Monitored Nodes. | Yes | — |
| Security Policy | None for an unencrypted channel, or Basic256Sha256 to sign and encrypt. | No | None |
| Username | Username for a username identity. Leave blank to connect anonymously. | No | — |
| Password | Password. Only valid together with a username. | No | — |
| Publishing Interval (ms) | How often the server publishes accumulated changes for the subscription. | No | 1000 |
| Sampling Interval (ms) | How often the server samples each monitored variable. | No | 1000 |
| Server Queue Size | Server-side queue depth per monitored variable, buffering changes between publishes. | No | 10 |
| Inbound Queue Capacity | Size of the bounded buffer between the OPC-UA callback and the workflow loop. | No | 10000 |
| Offer Timeout (ms) | How long to wait for room in a full inbound queue before dropping the notification. | No | 1000 |
| Shutdown Grace (ms) | On shutdown, how long to wait for the queue to drain before forcing exit. | No | 5000 |
| Request Timeout (ms) | OPC-UA request timeout. | No | 5000 |
Monitored Nodes
Each monitored node identifies one OPC-UA variable with three values:
| Field | Description | Required |
|---|---|---|
| Namespace Index | The variable's namespace index on the server. Must be 0 or greater. | Yes |
| Identifier | The node identifier, interpreted according to the identifier type. | Yes |
| Identifier Type | Numeric, String, GUID, or Opaque. | Yes |
The identifier type determines how the identifier is read:
| Identifier Type | Expected format | Example |
|---|---|---|
| Numeric | An unsigned integer | 2258 |
| String | Any string, used as-is | Machine1.Temperature |
| GUID | A UUID | 09087e15-1e1f-4c40-b76e-4e5c8b1a0f3a |
| Opaque | Base64-encoded bytes | AQIDBA== |
Identifiers are checked when the workflow is validated, so a Numeric identifier that isn't a number, or a malformed GUID, is caught before the workflow runs.
Record Shape
Each data-change notification produces one record:
{
"node_id": "ns=2;s=Machine1.Temperature",
"namespace_index": 2,
"identifier": "Machine1.Temperature",
"value": 21.4,
"status_code": 0,
"status_good": true,
"source_timestamp_ms": 1753280531318,
"server_timestamp_ms": 1753280531402,
"received_at_ms": 1753280531455
}
| Field | Description |
|---|---|
node_id | The full NodeId in OPC-UA's parseable form |
namespace_index | Namespace index of the variable |
identifier | The variable's identifier as a string |
value | The value read, or empty when the notification carried none |
status_code | Raw OPC-UA status code |
status_good | true only when a status code is present and good |
source_timestamp_ms | When the source sampled the value, in epoch milliseconds |
server_timestamp_ms | When the server observed the value, in epoch milliseconds |
received_at_ms | When this workflow received the notification |
Check status_good before using value. OPC-UA reports bad-quality reads as notifications with a non-good status, and this node passes them through rather than dropping them. Add a filter node on $.status_good == true when you only want trustworthy readings.
Note that records arrive one per variable per change, not as a combined row per device. If you need several tags together, correlate them with a downstream processing node.
Security
Setting Security Policy to Basic256Sha256 signs and encrypts the channel. In this mode the node generates an ephemeral self-signed client certificate in memory at startup, and accepts the server's certificate without verifying it.
With Basic256Sha256, the channel is encrypted but the server's identity is not verified — there is no certificate pinning, so this does not protect against a man-in-the-middle between the workflow and the endpoint. Use it on trusted networks and treat the Endpoint URL as the trust boundary.
Because the client certificate is regenerated on every start, servers that require a pre-registered and trusted client certificate will reject the connection.
The node connects to an endpoint offering the configured policy, so the server must actually advertise it.
Handling Backpressure
OPC-UA gives the consumer no flow control here, so the inbound queue absorbs bursts. When it fills, the node waits up to the Offer Timeout for room; if none frees up, the notification is dropped and counted in the opcua_dropped_count metric.
Sustained drops mean the workflow can't keep up with how fast tags are changing. Either raise Inbound Queue Capacity to ride out bursts, or raise Sampling Interval / Publishing Interval to slow the incoming rate.
Related Nodes
- Sparkplug B Source: Consume Sparkplug B industrial telemetry over MQTT
- MQTT Source: Consume raw messages from an MQTT broker
- Azure IoT Hub Source: Read device telemetry from Azure IoT Hub
- TimescaleDB Sink: Write the resulting readings to a time-series database