Skip to main content
Version: Next

OPC UA

The OPC UA driver (io.scadex.driver.opcua) connects the gateway to OPC UA servers. It keeps a session open to each server, subscribes to the nodes its tags are bound to, writes values back, and can turn a device object or a structured variable into a tag type (UDT). This page is for engineers who set up connections and tags.

It is built on the OPC Foundation .NET stack and declares SDK contract 1.2.

What has been proven​

The driver has read a Siemens S7-1500 (S7_VN_NIRO, namespace 3). Nothing else has been tried. Decoding of structured values to JSON is implemented but has not been verified against a real server.

Adding a connection​

Open Connections, then Devices, then OPC UA (/connections/opc-ua), and add a server. The connection form is rendered from the driver's descriptor, so these are the fields:

FieldKeyMeaningDefault
Endpoint URLurlRequired. The server's endpoint, such as opc.tcp://host:4840.none
SecuritysecurityModenone (no message security) or secure (the most secure endpoint the server offers).none
Trust the server's certificatetrustServerCertificateAccept an untrusted server certificate. Otherwise, move it from the module's pki/rejected folder to pki/trusted.false
UsernameusernameUser identity sent at session creation. Empty means anonymous.empty
PasswordpasswordSecret; stored encrypted with the module's state and never returned by the API.empty

Every connection also has a name (unique per driver, case-insensitive) and an Enabled switch. A disabled connection is dropped and its tags are marked bad with the detail "The connection is disabled."

The saved password is only reused when an update keeps the same host. Changing the URL to another host without supplying the password clears it.

Security and certificates​

  • With secure, the driver asks the server for its endpoints and picks the most secure. If the server offers only an unsecured endpoint, the connection fails rather than falling back, so a man-in-the-middle cannot force plain text by hiding the secure endpoints.
  • The gateway has one client certificate for all connections, created on first use under DATA_DIR/modules/io.scadex.driver.opcua/pki/own with subject CN=Scadex Gateway, O=scadex.io and application URI urn:scadex.io:gateway. A stored certificate that no longer validates is replaced with a new one.
  • Server certificates are checked against pki/trusted and pki/issuer; rejected ones are written to pki/rejected. With trustServerCertificate on, untrusted certificates are accepted automatically.

Test​

Test connection opens a throwaway session with the saved settings, closes it and reports success and latency in milliseconds. It gives up after 15 seconds with "Timed out connecting to the server." The running link is not touched.

  • The driver connects with a 20-second timeout and a session timeout of 60 seconds. When the session drops, every bound tag is marked bad with the last value kept, and the driver retries every 5 seconds.
  • Values come through one subscription per server with a publishing interval of 1,000 ms. Each bound tag is one monitored item with a sampling interval of 1,000 ms, a queue of 16 and discard-oldest. These intervals are not configurable.
  • A node the server refuses to monitor is reported as a bad quality on its tag rather than failing silently. A node id that does not parse marks its tag bad with the parse error.
  • Samples are queued (16,384 at most, dropping the oldest) and published to the tag runtime in batches of up to 256.
  • The source timestamp is the server's, unless it falls outside 1678–2261, in which case the gateway's clock is used.
  • On connect the driver loads the server's structure definitions (the complex type system). A server that cannot describe its types still connects; its structured values stay as bytes.

Restarting a tag (from the tag browser or after a type edit) removes and re-creates its monitored item, so the server sends its current value again.

Browsing​

Browse the address space opens the tree from the Objects folder (ns=0;i=85). The driver follows hierarchical references forward and shows Objects and Variables only. Each row shows:

  • the display name and the node id, in the ns=2;s=... form (a nsu=urn:...;s=... form is also accepted when you type one);
  • folder for an object, signal for a variable;
  • the node class in the row's metadata.

Browse needs a connected server; otherwise the result status is unavailable.

Creating tags​

Pick a variable and choose Create a tag from it. The dashboard asks the driver for a proposal (discover) and submits it to the gateway's tag API, so the gateway owns the tag and its lifecycle. A tag bound to this driver has the binding document:

{ "nodeId": "ns=3;s=\"DB_HMI\".\"Motor1\".\"Running\"" }

The suggested tag name is the last segment of the node id after /, ., ; or =, with non-alphanumeric characters replaced by _. The connection's linked tags are listed under Tags on the connection page, with the node id beside each path. See Tags for what happens next.

Reading and writing​

Reads arrive through the subscription described above; the driver never polls. A value's quality is mapped from the OPC UA status code.

Writes go through the gateway's tag write API and the driver's write handler. Before writing, the driver reads the node's declared data type, value rank and array dimensions (not the current value, so a write-only or currently bad node can still be written), converts the tag value to that type and writes the Value attribute. The server's status code decides: a bad status returns "The server rejected the write" with its symbolic id. The server must be connected or the write fails with "The OPC UA server for this tag is not connected."

Data type mapping​

OPC UATag value
Booleanboolean
SByte, Int16, Int32, Int64, Byte, UInt16, UInt32integer (64-bit)
UInt64unsigned 64-bit integer
Float, Doubledouble
Stringstring
ByteStringbytes
DateTimestring, ISO 8601 in UTC
Guidstring
NodeId, ExpandedNodeId, QualifiedName, LocalizedText, StatusCode, enumerationsstring
Arrays and matricesJSON array
Structures (ExtensionObject with a decodable body)JSON object
Structures the server cannot describebytes

For writes, Number resolves to Double, Integer to Int64, UInteger to UInt64 and Enumeration to Int32. Arrays of ExtensionObject, DataValue, DiagnosticInfo or an undeclared type cannot be written, and nor can multi-dimensional arrays. A string written to a ByteString must be base64.

Structures and tag types​

A variable whose value is a structure is decoded to a JSON object, so one tag holds the whole structure. The connection page offers a wand on such a row: Create a UDT type from this structure's fields. A second wand on object nodes offers Create a UDT type from this. Both open Make a type from this node with a Type path to fill in, prefilled from the node's type definition (its HasTypeDefinition reference) when the server has one.

The two modes differ:

  • From a structured value. The dashboard reads the node's current value to learn its fields. The type gets the parameters OPCUA (the server, set to this one) and ID (the node, defaulting to the instance name). Its root member doc holds the whole structure, with a derived member for each field and a folder for each nested structure. This is one subscription per instance instead of one per field.
  • From an object node. The dashboard browses the node (up to 8 levels deep, at most 2,000 variables) and makes a member for each variable beneath it, with folders for nested structures. Members are bound through OPCUA and ID, with the node written as {ID} in each member and ID defaulting to {InstanceName}. Give each instance its own node by setting its ID to the full node string.

Fields that could not be turned into members are reported as skipped. After the type is created the dashboard opens it in the tag browser. See Tag types for parameters, members and instances.

Connection status​

Each connection's state is published every two seconds under @gateway.connection.opcua.<name>.*, where <name> is the connection name turned into a path segment (no dots or spaces). The leaves are name, state (disabled, connecting, connected or faulted), connected, enabled, address (the URL without any user info), tags (bound tag count), error and since, plus the driver's own details such as value rates and tag-quality counts. The numeric and boolean leaves are real tags with history, so they can be trended and alarmed on. The folder @gateway.connection.opcua also reports how many connections there are and how many are connected.

The connection page shows the same figures live: whether it is connected, the last error as the driver reported it, and Tags subscribed.

Routes​

The gateway maps the baseline driver routes for this module under /api/v1/modules/io.scadex.driver.opcua: GET|POST /connections, GET|PUT|DELETE /connections/{connectionId}, POST /connections/{connectionId}/test, GET /connections/{connectionId}/tags, POST /connections/{connectionId}/browse and POST /connections/{connectionId}/discover. The module adds its own:

RequestPurpose
GET /servers, GET /servers/{id}The saved servers, passwords redacted
POST /servers, PUT /servers/{id}, DELETE /servers/{id}Create (name, url, enabled), rename or re-address, and delete
GET /statusWhether the module runs and, per connection, connected, tagCount and error
GET /servers/{id}/tagsThe tags bound to this server with their nodeId
GET /servers/{id}/browse?nodeIdChildren of a node (name, nodeId, nodeClass); 503 when not connected
GET /servers/{id}/read?nodeIdOne node's current value, dataType and quality
GET /servers/{id}/typeref?nodeIdThe node's type definition: name and nodeId
GET /servers/{id}/structure?nodeIdEvery variable beneath a node as relativePath and nodeId, for the type wand

GET routes need the viewer role; the others need engineer. While the module is stopped these routes answer 503; see Modules.