# OPC UA Server

Connectware can act as an OPC UA server, exposing data to OPC UA clients. For OPC UA client functionality, see [OPC UA Client](https://docs.cybus.io/connectors/shop-floor-connectors/opc-ua/opc-ua-client).

## Configuration

Define a server resource of type `Cybus::Server::Opcua` in your service commissioning file. OPC UA clients can connect to this server using:

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

{% hint style="warning" %}
Connectware uses port 4841 instead of the standard OPC UA port 4840 to avoid conflicts with other OPC UA servers. Only one OPC UA server instance can run per Connectware installation.
{% endhint %}

If Connectware is accessible via DNS hostname, specify this hostname in the `hostname` property to allow connections from outside the Docker network. Do not use `localhost` as it only refers to the local container.

The `resourcePath` property defines the connection string prefix and defaults to `/UA/CybusOpcuaServer`. This path must be included in the connection URL for clients to connect successfully.

### Node Structure

Define data points as `Cybus::Node::Opcua` resources. Nodes form a tree hierarchy:

* One root node with its `parent` property referencing the server.
* All other nodes reference the root node or intermediate nodes as `parent`.

Nodes can be defined in the same service as the server or in separate services using [service-id](https://docs.cybus.io/documentation/services/serviceid) references. This allows adding or removing nodes dynamically by commissioning additional services.

## Security Settings

For production environments, only use the `SignAndEncrypt` security profile. Other profiles allow communication to be intercepted or manipulated. By default, the OPC UA server only permits `SignAndEncrypt` connections (configured in `securityModes`).

Authenticate using Connectware username and password credentials via user token.

## Properties Reference

* [OPC UA Server Properties](https://docs.cybus.io/connectors/servers/opc-ua-server/opcuaserver)
* [OPC UA Node Properties](https://docs.cybus.io/connectors/servers/opc-ua-server/opcuanode)

## Service Commissioning File Example

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

{% code lineNumbers="true" %}

```yaml
---
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
```

{% endcode %}

### 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:

{% code lineNumbers="true" %}

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

{% endcode %}

### 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:

{% code lineNumbers="true" %}

```yaml
{ '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 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](https://docs.cybus.io/documentation/services/flowsync).

When configured in transaction mode, [Cybus::Nodes](https://docs.cybus.io/documentation/services/service-commissioning-files/resources/cybus-node) 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](https://docs.cybus.io/documentation/services/flowsync/flowsync-example-6).

{% code lineNumbers="true" %}

```yaml
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
```

{% endcode %}

### 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](https://docs.cybus.io/documentation/services/flowsync/flowsync-example-6).

{% code lineNumbers="true" %}

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

{% endcode %}

### Response Data Format

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

**Example**

In the [FlowSync Example 6](https://docs.cybus.io/documentation/services/flowsync/flowsync-example-6), the response will look like this:

{% code lineNumbers="true" %}

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

{% endcode %}

#### 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**

{% code lineNumbers="true" %}

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

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.cybus.io/connectors/servers/opc-ua-server.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
