OPC UA Server

This page describes how Connectware can act as an OPC UA server. Connectware can also act as a OPC UA client.

To run an OPC UA server, the commissioning file must contain a server resource of type Cybus::Server::Opcua. This will start an OPC UA server that can be accessed by OPC UA clients. From the OPC UA client, this server is reachable by the following address:

opc.tcp://<connectwareHost>:4841<resourcePath>

If your Connectware instance is running on a system that is reachable by a DNS hostname, this hostname must be specified in the hostname property, so that the server is reachable from outside of the Connectware docker network. The value localhost is not valid in this case, as localhost refers to the local Docker container but not the host itself, which means this name is not reachable from other containers or the host system.

Additionally, the property resourcePath is important when when connecting to the OPC UA server. This property defines the prefix of the connection string and defaults to the value /UA/CybusOpcuaServer. Please note that this string has to be added to the URL when connecting from a client to the OPC UA Server. Otherwise the client might not be able to connect successfully.

The server configuration is specified by the properties of the server resource. The actual data points (nodes in OPC UA) are specified by defining resources of type Cybus::Node::Opcua, one resource for each node. The nodes are structured in a tree-like hierarchy. There must be exactly one root node, which has its parent property set as a reference to the server object. All other nodes reference either the root node or other intermediate nodes as parent, forming a tree of nodes on the OPC UA Server.

Nodes can be defined within the same service as the OPC UA server, or also in other services using inter-service referencing using the service-id. It is thus possible to add or remove nodes while the OPC UA server is running, by adding more service commissioning files.

Security Settings

For production use, the connection to the OPC UA server should only be established using the security profile SignAndEncrypt. Any other security profile bears the risk that the communication between client and server can easily get manipulated or compromised. Per default, the built-in OPC UA server only allows connections with SignAndEncrypt security setting enabled (in the property securityModes). Please use your Connectware credentials when authenticating to the OPC UA server by Connectware username and password user token.

Custom Server Properties

Custom Node Properties

Service Commissioning File Example

---
description: >

  This is a fixture showing server resource functionality

metadata:
  name: OPC UA Server example
  version: 1.0.0
  icon: https://www.cybus.io/wp-content/uploads/2017/10/for-whom1.svg
  provider: cybus
  homepage: https://www.cybus.io

parameters:
  influxPort:
    type: integer
    default: 8086
    title: Influx Database Port

  retentionTime:
    type: integer
    default: 356
    title: Retention Time

definitions:
  databaseName: opcuaHistory

resources:
  influxdb:
    type: Cybus::Container
    properties:
      image: influxdb:1.8-alpine
      ports:
        - !sub '${influxPort}:8086/tcp'
      volumes:
        - !sub '${influxdbVolume}:/var/lib/influxdb'
      environment:
        INFLUXDB_DB: !ref databaseName
        INFLUXDB_HTTP_FLUX_ENABLED: true

  influxdbVolume:
    type: Cybus::Volume

  opcuaServer:
    type: Cybus::Server::Opcua
    properties:
      database:
        host: 172.17.0.1
        name: !ref databaseName
        retention: !ref retentionTime
      allowAnonymous: false
      certificateFile: /connectware_certs/cybus_server.crt
      privateKeyFile: /connectware_certs/cybus_server.key

  parentNodeRoot:
    type: Cybus::Node::Opcua
    properties:
      browseName: parentNodeRoot
      nodeId: ns=1;s=parentNodeRoot
      parent: !ref opcuaServer
      nodeType: Object

  parentNode1:
    type: Cybus::Node::Opcua
    properties:
      browseName: parentNode1
      nodeId: ns=1;s=parentNode1
      parent: !ref parentNodeRoot
      nodeType: Object

  parentNode2a:
    type: Cybus::Node::Opcua
    properties:
      browseName: parentNode2a
      nodeId: ns=1;s=parentNode2a
      parent: !ref parentNode1
      nodeType: Object

  parentNode2b:
    type: Cybus::Node::Opcua
    properties:
      browseName: parentNode2b
      nodeId: ns=1;s=parentNode2b
      parent: !ref parentNode1
      nodeType: Object

  dataNodeRoot1:
    type: Cybus::Node::Opcua
    properties:
      browseName: dataNodeRoot1
      nodeId: ns=1;s=dataNodeRoot1
      parent: !ref parentNodeRoot
      nodeType: Variable
      operation: serverProvides
      dataType: Boolean

  dataNodeRoot2:
    type: Cybus::Node::Opcua
    properties:
      browseName: dataNodeRoot2
      nodeId: ns=1;s=dataNodeRoot2
      parent: !ref parentNodeRoot
      nodeType: Variable
      operation: serverReceives
      dataType: DateTime

  dataNodeRoot3:
    type: Cybus::Node::Opcua
    properties:
      browseName: dataNodeRoot3
      nodeId: ns=1;s=dataNodeRoot3
      parent: !ref parentNodeRoot
      nodeType: Variable
      initialValue: 42.0
      operation: serverProvidesAndReceives
      dataType: Float
      historize: true

  dataNode1:
    type: Cybus::Node::Opcua
    properties:
      browseName: dataNode1
      nodeId: ns=1;s=dataNode1
      parent: !ref parentNode1
      nodeType: Variable
      operation: serverReceives
      dataType: Int32

  dataNode2a:
    type: Cybus::Node::Opcua
    properties:
      browseName: dataNode2a
      nodeId: ns=1;s=dataNode2a
      parent: !ref parentNode2a
      nodeType: Variable
      operation: serverProvides
      dataType: Double
      historize: true

  dataNode2b:
    type: Cybus::Node::Opcua
    properties:
      browseName: dataNode2b
      nodeId: ns=1;s=dataNode2b
      parent: !ref parentNode2b
      nodeType: Variable
      operation: serverProvides
      dataType: String

  mapping:
    type: Cybus::Mapping
    properties:
      mappings:
        - publish:
            topic: my/opcuaData/dataNode1
          subscribe:
            endpoint: !ref dataNode1

        - publish:
            endpoint: !ref dataNode2a
          subscribe:
            topic: my/opcuaData/dataNode2a

Output Format

If the server receives data from an external OPC UA client, the output on the internal MQTT broker will be provided as JSON object:

{ 'timestamp': '<unix timestamp in ms>', 'value': 'value' }

Input Format

If the server should provide data to an external OPC UA client, the message on the internal MQTT broker must be published in this format:

{ 'value': '<value>' }

Note: If 64-bit integers are being used (which are unsupported in JSON, but are supported in Javascript by the BigInt class), the value must be given as a string that contains the decimal number.

OPC UA Method Implementation

OPC UA methods can only be implemented on servers when used in conjunction with FlowSync.

When configured in transaction mode, Cybus::Nodes handle operations differently than standard variable nodes. Any node with the setting operation: transaction follows this workflow:

  1. Publishes request data to the /treq topic.

  2. Waits to receive a response on the /tres topic before proceeding.

Example

The following example configuration is taken from FlowSync Example 6

opcuaServer:
  type: Cybus::Server::Opcua
  properties:
    hostname: 127.0.0.1
    allowAnonymous: true
    securityPolicies: ['None']
    securityModes: ['None']

rootNode:
  type: Cybus::Node::Opcua
  properties:
    browseName: Cybus
    nodeId: ns=1;s=Cybus
    parent: !ref opcuaServer
    nodeType: Object

getIpForDomainNode:
  type: Cybus::Node::Opcua
  properties:
    topic: getIpForDomain
    browseName: getIpForDomain
    nodeId: ns=1;s=getIpForDomain
    parent: !ref rootNode
    nodeType: Method # mark node as method
    operation: transaction # activate FlowSync
    inputArguments:
      - name: domain
        dataType: String
        description: the domain to resolve
    outputArguments:
      - name: ip
        dataType: String
        description: the IP address of the domain

Request Data Structure

When a method is called, it publishes request details to the /treq topic in the following format:

  • Each request contains a unique id for tracking.

  • The response must include this same id to complete the flow.

This approach ensures proper request-response pairing and flow completion.

Example

The data structure matches the format shown in FlowSync Example 6.

{
  "id": "b9cc7c92-1f8c-4f00-8871-945a858ec3f8",
  "timestamp": 1738332862812,
  "value": {
    "browseName": "getIpForDomain",
    "nodeId": "ns=1;s=getIpForDomain",
    "inputArguments": [
      {
        "dataType": "String",
        "arrayType": "Scalar",
        "value": "cybus.io"
      }
    ]
  }
}

Response Data Format

Each method call must receive a corresponding response published to the /res topic.

Example

In the FlowSync Example 6, the response will look like this:

{
  "id": "b9cc7c92-1f8c-4f00-8871-945a858ec3f8",
  "timestamp": 1738332862983,
  "value": {
    "outputArguments": [
      {
        "value": "18.195.30.255"
      }
    ]
  }
}

Error Response Format

To return an error response from a node, you must include an error property containing the appropriate error code. This code will be used in the method call response. Note that any error.message provided will be ignored.

Example

{
  "id": "b9cc7c92-1f8c-4f00-8871-945a858ec3f8",
  "timestamp": 1738332862983,
  "error": {
    "message": "Domain Not Found",
    "code": "BadNotFound"
  }
}

Last updated

Was this helpful?