# OPC UA read task

> Learn how to acquire data from OPC UA servers with Synnax.

This task follows the standard [task lifecycle](https://docs.synnaxlabs.com/reference/driver/task-basics#task-lifecycle).

## Task configuration reference

| Parameter     | Type    | Required | Default | Description                                                                                 |
| ------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------- |
| `device`      | string  | Yes      | -       | Key of the OPC UA server device                                                             |
| `sample_rate` | number  | No       | `50`    | Samples per second (Hz)                                                                     |
| `stream_rate` | number  | No       | `25`    | Rate data is streamed to Synnax (Hz), must be ≤ `sample_rate`. Used when `array_mode=false` |
| `array_mode`  | boolean | No       | `false` | Enable array sampling mode for high-rate tasks                                              |
| `array_size`  | number  | No       | `1`     | Number of samples to read in bulk. Required when `array_mode=true`                          |
| `channels`    | array   | Yes      | -       | List of input channel configurations                                                        |

## Channel configuration reference

[OPC UA Read Service Specification](https://reference.opcfoundation.org/specs/OPC-10000-4/5.11.2)

Standard mode (`array_mode=false`)

Reads scalar values from an OPC UA node at the specified sample rate. Suitable for most applications with standard data rates.

| Parameter | Type   | Required | Default | Description                               |
| --------- | ------ | -------- | ------- | ----------------------------------------- |
| `channel` | number | Yes      | -       | Synnax channel key                        |
| `node_id` | string | Yes      | -       | OPC UA NodeId (e.g., `ns=2;s=MyVariable`) |

**NodeId format examples:**

* Numeric: `ns=2;i=1000`
* String: `ns=2;s=Temperature`
* GUID: `ns=2;g=12345678-1234-1234-1234-123456789012`
* Opaque: `ns=2;b=aGVsbG8=`

Array mode (`array_mode=true`)

Reads array data from an OPC UA node in bulk. Designed for high-frequency data (>500 Hz) where the server writes samples into arrays.

> When `array_mode=true`, the task reads multiple samples in bulk from the OPC UA server. This is more efficient for high-rate tasks but requires careful configuration to avoid undersampling or oversampling.

| Parameter | Type   | Required | Default | Description                                 |
| --------- | ------ | -------- | ------- | ------------------------------------------- |
| `channel` | number | Yes      | -       | Synnax channel key                          |
| `node_id` | string | Yes      | -       | OPC UA NodeId pointing to an array variable |

**Array sampling guidelines:**

* Set `sample_rate` to match the OPC UA server’s sample rate
* Set `array_size` to an integer factor of the sample rate
* **Oversampling** occurs when the server doesn’t fully replace array values between reads
* **Undersampling** occurs when the server writes faster than Synnax reads

Server timestamp read

Reads timestamps directly from the OPC UA server for high-precision timing instead of using Synnax-generated timestamps.

| Parameter | Type   | Required | Default | Description                                              |
| --------- | ------ | -------- | ------- | -------------------------------------------------------- |
| `channel` | number | Yes      | -       | Synnax index channel key (must be `TIMESTAMP` data type) |
| `node_id` | string | Yes      | -       | OPC UA NodeId containing server timestamps               |

> If timestamp channels are not added to the task, Synnax automatically generates timestamps with \~100 µs precision using software timing.

## Important rules

* **Software timing** -> Samples are timestamped in software with \~100 µs precision, which degrades under heavy load.
* **Array mode** -> Use array mode only above 500 Hz. It needs tuning to avoid under- or oversampling.
* **Stream rate** -> For sample rates under 50 Hz, set the stream rate equal to the sample rate. Above that, keep the stream rate under 50 Hz.

## How-to

**Console**

[](https://synnax.nyc3.cdn.digitaloceanspaces.com/docs/device-drivers/opc-ua/read-task/read-dark.mp4#t=0.001)

**Python**

Configure and run task

```python
import synnax as sy
from synnax import opcua

client = sy.Synnax()

# Retrieve OPC UA server device
dev = client.devices.retrieve(name="my_opc_server")

# Create index channel
data_time = client.channels.create(
    name="data_time",
    is_index=True,
    data_type=sy.DataType.TIMESTAMP,
    retrieve_if_name_exists=True,
)

# Create data channels
temp_sensor = client.channels.create(
    name="temperature",
    index=data_time.key,
    data_type=sy.DataType.FLOAT32,
    retrieve_if_name_exists=True,
)

pressure_sensor = client.channels.create(
    name="pressure",
    index=data_time.key,
    data_type=sy.DataType.FLOAT32,
    retrieve_if_name_exists=True,
)

# Create and configure task
task = opcua.ReadTask(
    name="OPC UA Read Task",
    device=dev.key,
    sample_rate=sy.Rate.HZ * 10,
    stream_rate=sy.Rate.HZ * 10,
    channels=[
        opcua.ReadChannel(
            channel=temp_sensor.key,
            node_id="ns=2;s=TemperatureSensor",
        ),
        opcua.ReadChannel(
            channel=pressure_sensor.key,
            node_id="ns=2;s=PressureSensor",
        ),
    ],
)

client.tasks.configure(task)

# Start task and read data
with task.run():
    with client.open_streamer(["temperature", "pressure"]) as streamer:
        for _ in range(10):
            frame = streamer.read()
            print(frame)
```

Edit task configuration

```python
# Retrieve existing task
task = client.tasks.retrieve(name="OPC UA Read Task")
task = opcua.ReadTask(internal=task)

# Update task-level configuration
task.config.auto_start = True
task.config.stream_rate = int(sy.Rate.HZ * 5)

# Update first channel configuration
task.config.channels[0].node_id = "ns=2;s=TemperatureSensor2"

# Update second channel configuration
task.config.channels[1].node_id = "ns=2;s=PressureSensor2"

# Apply changes
client.tasks.configure(task)
```

**TypeScript**

Configure and run task

```typescript
import { Synnax } from "@synnaxlabs/client";

const client = new Synnax();

// Retrieve OPC UA server device
const [dev] = await client.devices.retrieve({ names: ["my_opc_server"] });

// Create index channel
const dataTime = await client.channels.create(
  {
    name: "data_time",
    isIndex: true,
    dataType: "timestamp",
  },
  { retrieveIfNameExists: true },
);

// Create data channels
const tempSensor = await client.channels.create(
  {
    name: "temperature",
    index: dataTime.key,
    dataType: "float32",
  },
  { retrieveIfNameExists: true },
);

const pressureSensor = await client.channels.create(
  {
    name: "pressure",
    index: dataTime.key,
    dataType: "float32",
  },
  { retrieveIfNameExists: true },
);

// Create and configure task
const task = await client.tasks.create({
  name: "OPC UA Read Task",
  rack: dev.rack,
  type: "opc_read",
  config: {
    device: dev.key,
    sampleRate: 10,
    streamRate: 10,
    arrayMode: false,
    channels: [
      {
        channel: tempSensor.key,
        nodeId: "ns=2;s=TemperatureSensor",
      },
      {
        channel: pressureSensor.key,
        nodeId: "ns=2;s=PressureSensor",
      },
    ],
  },
});

// Start task
await task.start();

// Read data
const streamer = await client.openStreamer(["temperature", "pressure"]);
for (let i = 0; i < 10; i++) {
  const frame = await streamer.read();
  console.log(frame);
}

// Stop task
await task.stop();
await streamer.close();
```

Edit task configuration

```typescript
import { opcua } from "@synnaxlabs/client";

// Retrieve existing task
const task = await client.tasks.retrieve({ name: "OPC UA Read Task" });

// Parse and update configuration
const config = opcua.readConfigZ.parse(task.config);

// Update task-level configuration
config.autoStart = true;
config.streamRate = 5;

// Update first channel configuration
config.channels[0].nodeId = "ns=2;s=TemperatureSensor2";

// Update second channel configuration
config.channels[1].nodeId = "ns=2;s=PressureSensor2";

// Apply changes
await client.tasks.create({
  key: task.key,
  rack: task.rack,
  name: task.name,
  type: task.type,
  config,
});
```
