# Task basics

> Learn the fundamentals of working with tasks in Synnax.

## Prerequisites

Before creating a task, connect and configure a device:

* [EtherCAT](https://docs.synnaxlabs.com/reference/driver/ethercat/configure-device)
* [HTTP](https://docs.synnaxlabs.com/reference/driver/http/connect-server)
* [LabJack](https://docs.synnaxlabs.com/reference/driver/labjack/configure-device)
* [Modbus](https://docs.synnaxlabs.com/reference/driver/modbus/connect-server)
* [National Instruments](https://docs.synnaxlabs.com/reference/driver/ni/configure-device)
* [OPC UA](https://docs.synnaxlabs.com/reference/driver/opc-ua/connect-server)
* [PagerDuty](https://docs.synnaxlabs.com/reference/driver/pagerduty/alert-task)

## What are tasks?

A task is a background process on the Driver that reads data from or writes data to a device. Tasks are how Synnax acquires data from and controls hardware.

Tasks can be started, stopped, and reconfigured at any time. Synnax stores every task’s configuration, so you can keep several tasks and switch between them.

## Creating a task

**Devices toolbar**

Click the device icon () on the left-hand side of the screen. Right-click the device you want to create the task for and select the task type.

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

**Command palette**

Open the [command palette](https://docs.synnaxlabs.com/reference/console/ui-overview#search-and-command-palette) and type `Create`. Select the task type you need to open the task configuration dialog.

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

**Layout selector**

Click the add icon () in the top-right corner of the central mosaic and select the task type from the menu.

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

## Task lifecycle

The task form saves every edit. Click play () to send the configuration to the Driver and start the task. Click stop () to stop it.

> Edits made while a task is running are saved but not applied. Click `Redeploy` to apply them.

## Read tasks

A read task samples hardware inputs and streams the values into Synnax channels. Every channel in a task shares one sample rate, and a channel can be fed by only one running task at a time.

For tasks under 50 Hz, set the stream rate equal to the sample rate. For faster tasks, keep the stream rate under 50 Hz. How samples are [timestamped](https://docs.synnaxlabs.com/reference/driver/task-basics#timing) depends on whether the task is hardware or software timed.

## Write tasks

A write task listens on command channels (`_cmd`) and writes each value to the hardware. Outputs that report back also get a state channel (`_state`), refreshed at the task’s state rate. Only one running task can listen on a given command channel.

Modbus, OPC UA, and HTTP write tasks have no state channels. Configure a read task on the same target to read the state back.

## Timing

### Hardware timed tasks

Where the acquisition module has a sample clock, the Driver uses it and interpolates the time between samples. NI analog read tasks, NI digital read tasks with a timing source, and LabJack read tasks without thermocouple channels are hardware timed. A task sampling at 100 Hz and streaming at 10 Hz acquires 10 samples per read:

| Sample index | Time (s) | Notes                       |
| ------------ | -------- | --------------------------- |
| 0            | 0.000    | First sample of first read  |
| 1            | 0.010    |                             |
| 2            | 0.020    |                             |
| 3            | 0.030    |                             |
| 4            | 0.040    |                             |
| 5            | 0.050    |                             |
| 6            | 0.060    |                             |
| 7            | 0.070    |                             |
| 8            | 0.080    |                             |
| 9            | 0.090    | Last sample of first read   |
| 10           | 0.100    | First sample of second read |
| …            | …        | …                           |
| 19           | 0.190    | Last sample of second read  |
| 20           | 0.200    | First sample of third read  |

Clock drift accumulates over time. On a non-real-time operating system acquisition loops run early or late, the hardware clock and the configured rate differ slightly, and transports skip samples under load. A skew correction algorithm periodically adjusts the spacing between samples so timestamps stay aligned with the host system clock and with software timed tasks on the same Driver. Valve commands from a digital write task, for example, stay lined up with the analog samples they caused.

Skew correction is on by default. Disable it with `timing.correct_skew: false` in the Driver configuration file, `--correct-skew=false` on the command line, or `SYNNAX_DRIVER_CORRECT_SKEW=false` in the environment.

### Software timed tasks

Without a hardware clock, the Driver timestamps samples when it receives them, which is less accurate. NI digital read tasks without a timing source, NI counter read tasks, LabJack read tasks with thermocouple channels, Modbus read tasks, HTTP read tasks, and all write tasks are software timed.

## How-to

**Console**

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

**Python**

Running a task

```python
import synnax as sy
from synnax import ni

client = sy.Synnax()

# Retrieve your task. Wrap it in the class for its integration to get the
# start, stop, and run methods along with a parsed configuration.
task = ni.AnalogReadTask(internal=client.tasks.retrieve(name="My Analog Read Task"))

# Option 1: Use the run() context manager (recommended). It starts the task on
# entry and stops it on exit, even if an exception occurs.
with task.run():
    with client.open_streamer(["ai_0", "ai_1"]) as streamer:
        for i in range(100):
            frame = streamer.read()

# Option 2: Start the task and leave it running. The task keeps running after
# the script exits.
task.start()

# Later, you can stop it with:
# task.stop()
```

List all tasks and retrieve by name

```python
import synnax as sy

client = sy.Synnax()

# Retrieve task by name
task = client.tasks.retrieve(name="My Example Task")

# Retrieve multiple tasks by names
tasks = client.tasks.retrieve(names=["Task 1", "Task 2"])

# List all tasks
all_tasks = client.tasks.list()
for task in all_tasks:
    print(f"Task: {task.name}, Type: {task.type}")

# List tasks by type
analog_read_tasks = [t for t in all_tasks if t.type == "ni_analog_read"]

# Available task types:
#   arc,
#   ethercat_read, ethercat_write,
#   ni_analog_read, ni_analog_write,
#   ni_counter_read,
#   ni_digital_read, ni_digital_write,
#   labjack_read, labjack_write,
#   modbus_read, modbus_write,
#   opc_read, opc_write,
#   http_read, http_write,
#   pagerduty_alert,
```

Copy and edit a task

```python
import synnax as sy
from synnax import modbus

client = sy.Synnax()

# Retrieve the original task
original_task = client.tasks.retrieve(name="My Example Task")

# Copy the task with a new name
copied_task_raw = client.tasks.copy(
    key=original_task.key,
    name="My Example Task Copy"
)

# Convert to the appropriate task type to modify configuration
# For this example, we are assuming a modbus task
copied_task = modbus.ReadTask(internal=copied_task_raw)

# Modify the configuration
copied_task.config.auto_start = True

# Apply the changes
client.tasks.configure(copied_task)
```

Stop and delete task

```python
import synnax as sy
from synnax import modbus

client = sy.Synnax()

# Retrieve the task and wrap it in the class for its integration.
task = modbus.ReadTask(internal=client.tasks.retrieve(name="My Example Task"))

# Stop the task if it is running
task.stop()

# Delete the task
client.tasks.delete(task.key)
```

**TypeScript**

Running a task

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

const client = new Synnax();

// Retrieve your task
const task = await client.tasks.retrieve({ name: "My Analog Read Task" });

// Option 1: Use run() context manager (Recommended)
// Automatically starts and stops the task, even if
// an exception occurs
await task.run(async () => {
  // task.start() called under the hood
  const streamer = await client.openStreamer(["ai_0", "ai_1"]);
  try {
    for (let i = 0; i < 100; i++) {
      const frame = await streamer.read();
    }
  } finally {
    await streamer.close();
  }
});
// task.stop() called under the hood

// Option 2: Start task and leave it running
// Task continues running even after the script exits
await task.start();

// Later, you can stop it with:
// await task.stop();
```

List all tasks and retrieve by name

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

const client = new Synnax();

// Retrieve task by name
const task = await client.tasks.retrieve({ name: "My Example Task" });

// Retrieve multiple tasks by names
const tasks = await client.tasks.retrieve({ names: ["Task 1", "Task 2"] });

// List all tasks
const allTasks = await client.tasks.list();
allTasks.forEach((task) => {
  console.log(`Task: ${task.name}, Type: ${task.type}`);
});

// List tasks by type
const analogReadTasks = allTasks.filter((t) => t.type === "ni_analog_read");

// Available task types:
//   arc,
//   ethercat_read, ethercat_write,
//   ni_analog_read, ni_analog_write,
//   ni_counter_read,
//   ni_digital_read, ni_digital_write,
//   labjack_read, labjack_write,
//   modbus_read, modbus_write,
//   opc_read, opc_write,
//   http_read, http_write,
//   pagerduty_alert,
```

Copy and edit a task

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

const client = new Synnax();

// Retrieve the original task
const originalTask = await client.tasks.retrieve({ name: "My Example Task" });

// Copy the task with a new name
const copiedTask = await client.tasks.copy(
  originalTask.key,
  "My Example Task Copy",
  false, // snapshot = false
);

// Modify the configuration
const config = { ...(copiedTask.config as Record<string, unknown>), autoStart: true };

// Update the task. Creating with an existing key updates that task.
await client.tasks.create({
  key: copiedTask.key,
  rack: copiedTask.rack,
  name: copiedTask.name,
  type: copiedTask.type,
  config,
});
```

Stop and delete task

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

const client = new Synnax();

// Retrieve task
const task = await client.tasks.retrieve({ name: "My Example Task" });

// Stop task if running
await task.stop();

// Delete task
await client.tasks.delete(task.key);
```
