# OPC UA Client

This page describes how Connectware can act as an OPC UA client. Connectware can also act as an [OPC UA server](https://docs.cybus.io/2-0-6/documentation/industry-protocol-details/opc-ua/opc-ua-server).

{% hint style="info" %}
For more information on OPC UA, see the [OPC Foundation Reference](https://reference.opcfoundation.org/).
{% endhint %}

## OPC UA Subscriptions

OPC UA distinguishes between *session*, *subscriptions* and *monitored items*.

* Each Connectware connection creates exactly one *session* in the OPC UA terminology, where Connectware acts as a *client* towards the server.
* Within one session, the set of data endpoints are grouped in *subscriptions*. A subscription carries parameters like *publishInterval* and *maxNotificationsPerPublish* so that it controls the data flow between server and client.
* Within each subscription, the actual data endpoints (also called *nodes*) are subscribed as *monitored items*. The monitored items are configured by parameters like *nodeId*, *attributeId*, *samplingInterval*, so monitored items control the collection of the data by the server. Each network *request* for creating these can create one or multiple monitored items per request.

## Connectware Subscriptions

Connectware tries to abstract this complexity away, combining as many endpoints as possible into the same subscription, and also monitored items creation into the same request. This is done according to the following criteria:

* Each OPC UA *Cybus::Connection* resource creates exactly one *session*
* Within this session, all OPC UA *Cybus::Endpoint* resources with the same *publishInterval* property are combined into one *subscription*. This holds even across different service commissioning files using inter-service referencing, see [here](https://docs.cybus.io/2-0-6/services/service-commissioning-files/resources#resource-id). Hence, enabling/disabling additional services with additional endpoints will add or remove those endpoints from the currently available *subscription*.
* Every OPC UA *Cybus::Endpoint* resource corresponds to one *monitored item*
* Within one subscription and within the same service commissioning file, all endpoints (= monitored items) with the same *samplingInterval* property are combined into one common creation request. This “create monitored items request” is working very efficiently also for thousands of monitored items.

### Subscription Limitations

Some OPC UA servers are known for imposing certain limits on the number of monitored items, either in total, or per subscription, or per request.

In particular, an embedded OPC UA server on a Siemens S7-1500 or S7-1200 PLC is known for certain restrictions, see [System limits of OPC UA Server](https://support.industry.siemens.com/cs/document/109755846/what-are-the-system-limits-of-the-opc-ua-server-with-s7-1500-and-s7-1200-?dti=0\&dl=en\&lc=de-DE).

* For example, the maximum number of monitored items within one “create monitored items request” might limited to e.g. 1000 nodes. This limit can even be looked up in the OPC UA node with nodeId `ns=0;i=11714`. On Connectware side this limit must be taken into account by setting the property *maxMonitoredItemsPerCall* in the *Cybus::Connection* resource to the respective value. This will ensure to split larger requests so that all requests stay within this limit.
* Also, some maximum number of monitored items in total might be configured in the TIAPortal project. Unfortunately it is not known how to look up this value except directly in TIAPortal. If such a value is set, it is not possible for Connectware to create any larger number of endpoints than this value.

Regarding the created *subscriptions*: As of today, *subscriptions* are combined only by the *publishInterval* parameter. The remaining properties related to subscriptions are currently taken only from the first endpoint to be subscribed, while differing settings at subsequent endpoints are currently ignored. This concerns the following endpoint properties: *requestedLifetimeCount*, *requestedMaxKeepAliveCount*, *maxNotificationsPerPublish*, *priority*.

### Example

This is an example configuration snippet with three endpoints (without the connection configuration):

{% code lineNumbers="true" %}

```yaml
resources:
  spindleSpeed:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      subscribe:
        nodeId: 'ns=1;s=spindleSpeed'
        publishInterval: 1000
        samplingInterval: 100
        maxNotificationsPerPublish: 100
  powerConsumption:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      subscribe:
        nodeId: 'ns=1;s=powerConsumption'
        publishInterval: 1000
        maxNotificationsPerPublish: 50
        samplingInterval: 1000
  temperature:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      subscribe:
        nodeId: 'ns=1;s=temperature'
        publishInterval: 15000
        samplingInterval: 15000
```

{% endcode %}

In the above example, two subscriptions will be created. One with *publishInterval* set to 1000ms and *maxNotificationsPerPublish* set to 100, the other one with *publishInterval* set to 15.000ms. The sampling of the individual source values will be set as expected by the specified *samplingInterval* property, but remember that OPC UA does not offer a fixed data sampling but rather applies a change-of-value filter to each particular data point automatically.

[Connection Properties](https://docs.cybus.io/2-0-6/documentation/industry-protocol-details/opc-ua/opc-ua-client/opcuaconnection)

[Endpoint Properties](https://docs.cybus.io/2-0-6/documentation/industry-protocol-details/opc-ua/opc-ua-client/opcuaendpoint)

## Service Commissioning File Example

### Basic example

This is a simple OPC UA connectivity example that only subscribes to the “Server Status” node of an OPC UA server. The proposed node (endpoint) can also be used if the server would otherwise close the connection, which has been observed for some specific versions of OPC UA servers on a S7 PLC.

The example will need some OPC UA server available in your network:

{% code lineNumbers="true" %}

```yaml
---
description: >

  Simple OPC UA connectivity example

metadata:
  name: Simple OPC UA connectivity
  version: 1.0.0
  icon: https://www.cybus.io/wp-content/uploads/2019/03/Cybus-logo-Claim-lang.svg
  provider: cybus
  homepage: https://www.cybus.io

parameters:
  opcuaHost:
    type: string
    description: OPC-UA Host
    default: 172.17.0.1

  opcuaPort:
    type: integer
    default: 4840

  opcuaUser:
    type: string
    default: ''

  opcuaPass:
    type: string
    default: ''

resources:
  opcuaConnection:
    type: Cybus::Connection
    properties:
      protocol: Opcua
      connection:
        host: !ref opcuaHost
        port: !ref opcuaPort
        username: !ref opcuaUser
        password: !ref opcuaPass

  serverStatusEndpoint:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      subscribe:
        nodeId: ns=0;i=2259
        samplingInterval: 2000
```

{% endcode %}

{% file src="<https://639096190-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfDpOJO2upcq5EpoSahvK%2Fuploads%2Fgit-blob-07e20fd4b14fa063b76083563a606b7b1e996220%2Fopcua-simple-example.yml?alt=media>" %}

### Advanced Example

More complex example including an OPC UA server container from public Docker Hub:

{% code lineNumbers="true" %}

```yaml
---
description: >

  Simulated OPC UA server

metadata:
  name: Simulated OPC UA
  version: 1.0.1
  icon: https://www.cybus.io/wp-content/uploads/2019/03/Cybus-logo-Claim-lang.svg
  provider: cybus
  homepage: https://www.cybus.io

parameters:
  opcuaHost:
    type: string
    description: OPC-UA Host
    default: 172.17.0.1

  opcuaPort:
    type: integer
    default: 50000

  opcuaUser:
    type: string
    default: user

  opcuaPass:
    type: string
    default: user

definitions:
  CYBUS_MQTT_ROOT: cybus-factory/opcua

resources:
  machineSimulatorContainer:
    type: Cybus::Container
    properties:
      image: mcr.microsoft.com/iotedge/opc-plc
      ports:
        - !sub '${opcuaPort}:50000/tcp'
      command:
        - --unsecuretransport
        - --autoaccept
        - --defaultuser=user
        - --defaultpassword=user

  opcuaConnection:
    type: Cybus::Connection
    properties:
      protocol: Opcua
      connection:
        host: !ref opcuaHost
        port: !ref opcuaPort
        username: !ref opcuaUser
        password: !ref opcuaPass

  currentTime:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: server/status/currenttime
      subscribe:
        nodeId: i=2258

  dipData:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: dip-data
      subscribe:
        nodeId: ns=2;s=DipData

  alternatingBool:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: alternating-bool
      subscribe:
        nodeId: ns=2;s=AlternatingBoolean

  negativeTrendData:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: negative-trend-data
      subscribe:
        nodeId: ns=2;s=NegativeTrendData

  positiveTrendData:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: positive-trend-data
      subscribe:
        nodeId: ns=2;s=PositiveTrendData

  randomSignedInt:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: random-signed-int32
      subscribe:
        nodeId: ns=2;s=RandomSignedInt32

  randomUnsignedInt:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: random-unsigned-int32
      subscribe:
        nodeId: ns=2;s=RandomUnsignedInt32

  spikeData:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: spike-data
      subscribe:
        nodeId: ns=2;s=SpikeData

  stepUp:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      topic: step-up
      subscribe:
        nodeId: ns=2;s=StepUp
```

{% endcode %}

{% file src="<https://639096190-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfDpOJO2upcq5EpoSahvK%2Fuploads%2Fgit-blob-9375d014c163eada9b1a30e7752500565c83e467%2Fopcua-example.yml?alt=media>" %}

### Example with Method Endpoint

{% code lineNumbers="true" %}

```yaml

description: OPC UA method endpoint

metadata:
  name: opcua-method-endpoint
  version: 1.0.0

resources:

opcuaConnection:
    type: Cybus::Connection
    properties:
      protocol: Opcua
      connection: # opc.tcp://opcuaserver.com:48010
        host: opcuaserver.com
        port: 48010

  multiplyEndpoint:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      publishError: true # will let the endpoint always publish error an error object that contains 'message' and error 'code' when an error occurs
      write:
        nodeId: ns=2;s=Demo.Method.Multiply
        nodeType: Method # mark that endpoint as method

```

{% endcode %}

{% file src="<https://639096190-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfDpOJO2upcq5EpoSahvK%2Fuploads%2Fgit-blob-c4f1ddb24c1fb466141681ce8a9ed18f8733a2d2%2Fopcua-client-method-example.yml?alt=media>" %}

## Output Format On Reading

If data is read from OPC UA the output will be provided as JSON object with value and timestamp.

The given timestamp is the OPC UA “Source Timestamp” which was set on the data source side, see <https://reference.opcfoundation.org/v104/Core/docs/Part4/7.7.3/>

{% code lineNumbers="true" %}

```json
{ "timestamp": "<unix timestamp in ms>", "value": "value" }
```

{% endcode %}

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

## Output Format On Writing

### Variables

When data is written to an OPC-UA endpoint, you will get the result of the operation over the **/res** topic like this:

{% code lineNumbers="true" %}

```json
{ "id": "29194", "timestamp": 1629351968526, "result": { "value": 0 } }
```

{% endcode %}

### Method Calls

Method calls follow the same pattern as variable operations:

1. Write the method call data to the `/set` topic.
2. Receive the result on the `/res` topic.

**Example**

{% code lineNumbers="true" %}

```json
{
  "id": "1234",
  "timestamp": 1738323576339,
  "value": {
    "outputArguments": [
      {
        "value": 42,
        "dataType": "Double",
        "arrayType": "Scalar"
      }
    ]
  }
}
```

{% endcode %}

## Input Format

### Variables

When writing to an OPC UA variable, format the data as a JSON object.

{% code lineNumbers="true" %}

```json
{ "value": "<value>" }
```

{% endcode %}

### Method Calls

Calling OPC UA methods follows the same pattern as writing to OPC UA variables.

The `value` has to include the input arguments like this. You can optionally include an `id` parameter.

{% code lineNumbers="true" %}

```json
{
  "id": "12345",
  "value": {
    "inputArguments": [
      {
        "value": 24
      },
      {
        "value": 2
      }
    ]
  }
}
```

{% endcode %}

## Reconnection Behavior

In OPC UA connections – just with any network connections – it can happen that the connection is lost. In that case the Connectware’s OpcuaConnection will automatically switch into *reconnecting* state and repeatedly try to re-establish the connection. The exact behavior on how often this is tried can be controlled by the optional `connectionStrategy` properties, see [Connection Properties](https://docs.cybus.io/2-0-6/documentation/industry-protocol-details/opc-ua/opc-ua-client/opcuaconnection). The important property is `maxDelay` which sets the maximum waiting time (delay) between consecutive re-tries, in milliseconds. The waiting time will start with the value `initialDelay`, then be increased step by step, until the `maxDelay` value.

Example with initially 500ms waiting time, increasing up to 10 seconds:

{% code lineNumbers="true" %}

```yaml
resources:
  myConnection:
    type: Cybus::Connection
    properties:
      protocol: Opcua
      connection:
        host: !ref opcuaHost
        port: !ref opcuaPort
        options:
          connectionStrategy:
            initialDelay: 500
            maxDelay: 10000
```

{% endcode %}

For further details see also the documentation of the internally used package *backoff*, <https://www.npmjs.com/package/backoff>

## Events for OPC UA

Event subscriptions can be created in Connectware by adding the properties `fields` and `eventTypes` to the *Cybus::Endpoint* resource as shown in the example below.

The names for the *fields* should be *Qualified Names*. Often, this will require the field name to also contain a namespace identifier, for example *4:MyQualifiedName.3:SubitemQualifiedName*. Also, the field names often additionally need a *sub-type*, which can be specified using a dot notation after the name.

{% code lineNumbers="true" %}

```yaml
---
description: >

  Simple OPC UA connectivity example

metadata:
  name: example for opcua connectivity
  version: 1.0.0
  icon: https://www.cybus.io/wp-content/uploads/2019/03/Cybus-logo-Claim-lang.svg
  provider: cybus
  homepage: https://www.cybus.io

parameters:
  opcuaHost:
    type: string
    description: OPC-UA Host
    default: 172.17.0.1

  opcuaPort:
    type: integer
    default: 4840

  opcuaUser:
    type: string
    default: ''

  opcuaPass:
    type: string
    default: ''

resources:
  opcuaConnection:
    type: Cybus::Connection
    properties:
      protocol: Opcua
      connection:
        host: !ref opcuaHost
        port: !ref opcuaPort
        username: !ref opcuaUser
        password: !ref opcuaPass

  # Listen to all events from the server node
  # A list of eventTypes can be selected
  serverEventsEndpoint:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      subscribe:
        nodeId: i=2253
        eventTypes:
          - 'i=85'
        fields:
          - 'EventType'
          - 'ReceiveTime'
          - 'Message'
          - 'SourceName'
          - 'Severity'
```

{% endcode %}

{% file src="<https://639096190-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfDpOJO2upcq5EpoSahvK%2Fuploads%2Fgit-blob-b288d2829b9a3b322efed5e79815e1b631f8128c%2Fopcua-events-example.yml?alt=media>" %}

## Accessing Status Codes for Values

The quality of a data value in OPC UA is represented by predefined status codes. The `` `statusCode` `` is transferred to the `` `$context` `` object and can be retrieved within the Rule Engine of a *Cybus::Endpoint* via `` `$context.raw.statusCode` `` as shown in the example below.

{% code lineNumbers="true" %}

```yaml
description: OPC UA Status Code Example
metadata:
  name: OPC UA Status Code Example
  version: 1.0.0
parameters:
  ipAddress:
    type: string
  port:
    type: integer
    default: 4334
  samplingIntervalMs:
    type: number
    default: 1000
  publishIntervalMs:
    type: number
    default: 1000
resources:
  opcuaConnection:
    type: Cybus::Connection
    properties:
      protocol: Opcua
      connection:
        host: !ref ipAddress
        port: !ref port

  A_000:
    type: Cybus::Endpoint
    properties:
      protocol: Opcua
      connection: !ref opcuaConnection
      subscribe:
        nodeId: ns=1;s=0
        samplingInterval: !ref samplingIntervalMs
        publishInterval: !ref publishIntervalMs
      rules:
        - transform:
            expression: |
              { 
                "statusCode": {
                  "name": $context.raw.statusCode.name,
                  "value": $context.raw.statusCode.value,
                  "description": $context.raw.statusCode.description
                },
                "value": $.value,
                "timestamp": $.timestamp
              }
      topic: A/000
```

{% endcode %}

{% file src="<https://639096190-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfDpOJO2upcq5EpoSahvK%2Fuploads%2Fgit-blob-cd3d1f2c7daf42afae85ba2b2cf1abfe1db31557%2Fopcua-statusCodes-example.yml?alt=media>" %}
