OPC-UA Source Node
The opcua_source node subscribes to variables on an OPC-UA server and emits one record per data-change notification. It is aimed at industrial automation data — PLCs, SCADA systems, and historians that expose an OPC-UA endpoint — and reports each value together with its OPC-UA status code and source/server timestamps.
Unlike most sources, this node has no encodingType: OPC-UA is a typed protocol, so values arrive already structured and are mapped directly into a fixed record shape.
Key Features
- Subscription-based: the server pushes only on data change, rather than the node polling every tag on an interval
- Per-node sampling control: publishing and sampling intervals and the server-side queue size are configurable
- All four NodeId flavors:
NUMERIC,STRING,GUID, andOPAQUEidentifiers - Quality preserved: each record carries the OPC-UA status code and a
status_goodflag, so bad-quality reads are visible downstream instead of silently becoming values - Both timestamps: source timestamp (when the device sampled) and server timestamp (when the server observed) are emitted separately
- Optional security: unencrypted, or
Basic256Sha256sign-and-encrypt; anonymous or username/password identity - Bounded buffering with accounting: a full inbound queue drops notifications and increments an
opcua_dropped_countmetric rather than growing without limit - Infinite streaming: never exhausts — runs until the pipeline is terminated
Configuration
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
endpointUrl | String | Yes | — | OPC-UA endpoint. Must start with opc.tcp://, e.g. opc.tcp://plc-host:4840 |
nodeIds | List<Object> | Yes | — | Nodes to monitor. Must be non-empty. See Monitored Nodes |
securityPolicy | String | No | NONE | NONE for an unencrypted channel, or BASIC256SHA256 to sign and encrypt |
username | String | No | — | Username. Omit for an anonymous identity |
password | String | No | — | Password. Only valid together with username |
publishingIntervalMs | double | No | 1000.0 | Subscription publishing interval in milliseconds. Must be > 0 |
samplingIntervalMs | double | No | 1000.0 | Per-item sampling interval in milliseconds. Must be > 0 |
serverQueueSize | int | No | 10 | Server-side queue depth per monitored item. Must be > 0 |
inboundQueueCapacity | int | No | 10000 | Bounded client-side queue between the OPC-UA callback and the pipeline loop |
offerTimeoutMs | long | No | 1000 | How long to wait offering to a full inbound queue before dropping the notification |
shutdownGraceMs | long | No | 5000 | On shutdown, how long to wait for the queue to drain before forcing exit |
requestTimeoutMs | long | No | 5000 | OPC-UA request timeout in milliseconds |
Monitored Nodes
nodeIds is a list of objects, each identifying one OPC-UA variable:
| Field | Type | Required | Description |
|---|---|---|---|
namespaceIndex | int | Yes | The node's namespace index. Must be >= 0 |
identifier | String | Yes | The node identifier, parsed according to identifierType |
identifierType | String | Yes | NUMERIC, STRING, GUID, or OPAQUE |
How identifier is parsed depends on identifierType:
identifierType | Expected identifier format | Example |
|---|---|---|
NUMERIC | An unsigned integer | 2258 |
STRING | Any string, used verbatim | Machine1.Temperature |
GUID | A UUID | 09087e15-1e1f-4c40-b76e-4e5c8b1a0f3a |
OPAQUE | Base64-encoded bytes | AQIDBA== |
Identifiers are parsed at validation time, so a NUMERIC identifier that isn't a number, or a malformed GUID, fails when the pipeline is validated rather than mid-run.
config:
nodeIds:
- namespaceIndex: 2
identifier: "Machine1.Temperature"
identifierType: "STRING"
- namespaceIndex: 2
identifier: "Machine1.Pressure"
identifierType: "STRING"
- namespaceIndex: 0
identifier: "2258"
identifierType: "NUMERIC"
Record Shape
Each data-change notification becomes 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 node |
identifier | The node's identifier as a string |
value | The value read. null when the notification carried no value |
status_code | Raw OPC-UA status code. null when absent |
status_good | true only when a status code is present and good |
source_timestamp_ms | Epoch millis when the source sampled the value. null when the server did not supply one |
server_timestamp_ms | Epoch millis when the server observed the value. null when not supplied |
received_at_ms | Epoch millis when this pipeline received the notification |
Always check status_good before trusting value — OPC-UA reports bad-quality reads as notifications with a non-good status, and the node passes them through rather than dropping them.
Because one record is emitted per node per change, downstream nodes see a stream of individual tag readings, not a combined row per device. Use a processing node to correlate tags if you need them together.
Security
Security policy
NONE (the default) opens an unencrypted channel and no client certificate is involved.
BASIC256SHA256 signs and encrypts the channel. In this mode the node generates an ephemeral self-signed client certificate in memory at startup, and trusts the server's certificate without pinning it.
With BASIC256SHA256, the server certificate is accepted without validation. The channel is encrypted, but the server's identity is not verified, so this does not protect against a man-in-the-middle on the path to the endpoint. Use it on trusted networks, and treat endpointUrl as the trust boundary.
Because the client certificate is generated fresh in memory on each start, servers that require a pre-registered and trusted client certificate will reject the connection.
The node selects an endpoint whose security policy matches the configured one, so the server must actually offer that policy.
Identity
Setting username (with optional password) authenticates with a username identity; omitting it connects anonymously. Supplying a password without a username is rejected at validation.
How It Works
- The node connects to
endpointUrl, selecting an endpoint matchingsecurityPolicy, and authenticates with the configured identity. - It creates a subscription with
publishingIntervalMsand registers each entry innodeIdsas a monitored item usingsamplingIntervalMsandserverQueueSize. - The server pushes data-change notifications, which are offered to a bounded queue sized by
inboundQueueCapacity. - The pipeline loop drains the queue, converting each notification into a record.
Backpressure and drops
OPC-UA has no consumer-driven flow control here, so the client-side queue is the buffer. When it is full, the node waits up to offerTimeoutMs for room; if none frees up, the notification is dropped and the opcua_dropped_count metric is incremented.
Sustained drops mean the pipeline cannot keep up with the tag change rate. Raise inboundQueueCapacity to absorb bursts, or raise samplingIntervalMs / publishingIntervalMs to reduce the incoming rate.
On shutdown the node waits up to shutdownGraceMs for the queue to drain before exiting.
DAG Example
jobContext:
otherProperties: {}
metricTags: {}
dlqConfig:
dag:
- id: "source"
commandName: "opcua_source"
config:
endpointUrl: "opc.tcp://plc-host:4840"
securityPolicy: "NONE"
nodeIds:
- namespaceIndex: 2
identifier: "Machine1.Temperature"
identifierType: "STRING"
- namespaceIndex: 2
identifier: "Machine1.Pressure"
identifierType: "STRING"
publishingIntervalMs: 1000.0
samplingIntervalMs: 1000.0
serverQueueSize: 10
inboundQueueCapacity: 10000
outputs:
- "good_only"
- id: "good_only"
commandName: "filter"
config:
expression: $.status_good == true
outputs:
- "sink"
- id: "sink"
commandName: "stdout"
config:
encodingType: "JSON_OBJECT"
Related Nodes
- sparkplugbsource: Consume Sparkplug B industrial telemetry over MQTT
- mqttsource: Consume raw messages from an MQTT broker
- azureiothubsource: Consume device telemetry from Azure IoT Hub
- timescaledbsink: Write the resulting readings to a time-series database