Skip to content

OPC-UA server

Serve the live machine tags of OpexFlow to SCADA and MES clients over OPC-UA: one folder per machine, typed variables with quality and source timestamps, subscriptions, read-only, secure by default.

Updated View as Markdown

OpexFlow can serve OPC-UA as well as read it. A SCADA, MES, historian or any OPC-UA client (UaExpert, Ignition, WinCC, KEPServerEX, a script) connects to the connector, browses the machines and reads or subscribes to their tags. The server is built into the binary (feature opcua-server, on by default) and off until you enable it.

What it serves is exactly what the flow’s hub sink emits: every record a flow routes to the hub also updates the OPC-UA address space. A record that never reaches the hub sink is not visible to OPC-UA clients.

Enable it

opcua_server:
  enabled: true
  endpoint: "opc.tcp://0.0.0.0:4840"   # default opc.tcp://127.0.0.1:4840 (loopback, like the API)
  advertised_host: "flow.plant.local"  # name clients use when binding a wildcard address
  users:
    - { name: scada, password: "${OPCUA_SCADA_PASSWORD}" }

opcua_server: is a top-level section (a sibling of adapters:). It needs a restart to change (/api/config/reload does not touch it). GET /api/status has an opcua_server object: {enabled:false} when it is off, else the endpoint, sessions, devices, variables, records, lagged_records and skipped.

Key Default Meaning
enabled false Start the server.
endpoint opc.tcp://127.0.0.1:4840 Address to bind. Loopback by default; 0.0.0.0 serves the network. Port 0 picks a free port.
advertised_host machine host name Host in the endpoint URLs the server hands to clients when endpoint is a wildcard (0.0.0.0, ::).
application_name OpexFlow Shown by clients.
application_uri urn:opex-flow:<connector.name> Application URI, also inside the certificate.
namespace_uri urn:opex-flow:machines Namespace of the machine nodes.
security_policies [Basic256Sha256] Basic256Sha256, Aes128Sha256RsaOaep, Aes256Sha256RsaPss, and None. None is offered only when listed. The deprecated Basic128Rsa15 and Basic256 are not available.
security_modes [Sign, SignAndEncrypt] Modes offered for every policy except None.
anonymous false Accept clients without a login.
users none {name, password} logins; passwords as ${ENV} or ${secret:name}. Redacted in GET /api/config.
pki_dir <data_dir>/opcua-server-pki Certificate and trust store (relative paths are under data_dir).
trust_client_certs false Accept every client certificate without it being in <pki_dir>/trusted.
allow_insecure false Allow the two unsafe exposures below on a non-loopback bind.
max_devices 1000 Machine folders the address space may hold.
max_metadata_per_device 200 metadata.* variables per machine.
max_sessions 50 Concurrent client sessions.

At least one of anonymous: true or a users entry is required; a server nobody can log in to is a configuration error.

Security

  • Secure by default. The default offers Basic256Sha256 with Sign and SignAndEncrypt, bound to loopback. A user name and password are always encrypted: even on a None endpoint the login is encrypted with the server certificate.

  • Certificate. A self-signed certificate is generated into <pki_dir>/own on the first start and reused afterwards. It is replaced automatically (and clients must trust it again) when it has less than 30 days left or no longer matches application_uri.

  • Client trust. With trust_client_certs: false a client’s certificate is rejected until you move it from <pki_dir>/rejected to <pki_dir>/trusted. Clients also verify the server certificate: trust <pki_dir>/own/cert.der in the client.

  • Refused at startup (also reported by --check-config), unless allow_insecure: true:

    1. policy None together with anonymous: true on a non-loopback bind (every tag readable unauthenticated and unencrypted from the network);
    2. trust_client_certs: true together with anonymous: true on a non-loopback bind (any client with a self-made certificate can read everything).

    With allow_insecure: true the server starts and logs a SECURITY: warning at every start. Policy None, anonymous access and trust_client_certs on their own also log a warning.

  • An unwritable pki_dir or a port already in use stops OpexFlow at startup rather than silently running without the server.

What clients see

One folder per machine under Objects, browse name = machine.id, display name = machine.name when the record has one. Folders and variables are created when the first record of a machine arrives, so an empty connector shows nothing. Node ids are strings in the namespace namespace_uri: ns=2;s=<machine> for the folder and ns=2;s=<machine>.<variable> for a variable (the namespace index is not fixed; resolve it from the namespace array, or browse).

Variable Data type From
status.state String running, idle, alarm, offline, unknown
status.alarmCode, status.alarmMessage String Created when a record first carries them; set to "" when a later record has none
spindle.speed, spindle.load Double spindle (load only when present)
axis.<name>.position Double axes[]; the unit is the variable’s description
metadata.<key> Boolean, Int64, UInt64, Double or String metadata; arrays and objects as JSON text, null values are skipped
quality String good, uncertain, bad
source String The record’s source
timestamp DateTime The record timestamp

A metadata variable keeps the type of its first value and only widens: Int64 and UInt64 become Double when a float arrives, anything else mixed becomes String (the DataType attribute changes accordingly). A variable whose field is missing from a later record keeps its last value and its older timestamp.

Quality and time. Each value’s StatusCode comes from the record’s quality (absent = good): good → Good, uncertain → Uncertain, bad → BadCommunicationError. A per-value valueQuality entry can only make its own value worse, never better. The value is still delivered with a bad status. SourceTimestamp is the record timestamp, ServerTimestamp is the time the server applied the record.

A record of a degraded device (for example the offline record: state offline, quality bad, no axes or spindle) also degrades every variable it does not carry: they keep their last value and their last real timestamp, but their StatusCode becomes the record’s. The next good record that carries them makes them good again.

Subscriptions. Values are pushed from the data hub, so a client subscription gets a data change whenever a value changes. A change of the StatusCode alone is reported only to subscriptions that request a data change filter with trigger Status/Value (the async-opcua server applies the OPC-UA default only when the filter is sent; most SCADA clients have this as a per-item option). The quality and status.state variables change value with the quality, so they notify either way. If the server falls behind the hub’s broadcast buffer, the missed records are counted in lagged_records.

Limits and what is not included

  • Read-only by design; there is no flow node that writes to an OPC-UA variable path, and no history (HistoryRead is unsupported): use the storage and historian sinks for history.
  • Records of machines beyond max_devices and metadata beyond max_metadata_per_device are not served; they are counted in skipped in GET /api/status.
  • Two machines whose ids make the same node id (m1 with a variable status.state against a machine called m1.status.state) clash: the later node is not created and a warning is logged.
  • On an HA pair each node runs its own server; the standby has no data until it is promoted.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close