Task Basics
Learn the fundamentals of working with tasks in Synnax.
Prerequisites
Before creating any tasks, you’ll need to have connected and configured a hardware device:
What Are Tasks?
Tasks are the primary method for communicating with hardware devices in Synnax. Tasks can be used for both control and data acquisition purposes. A task defines a background process that either reads data from or writes data to your hardware.
Tasks can be started, stopped, and re-configured at any time. Synnax permanently stores the configuration of each task, so it’s easy to set up multiple tasks for different purposes and switch between them as needed.
Creating a Task
Devices Toolbar
Command Palette
Layout Selector
Open the Command Palette
and type Create. Select the task type you need to open the task configuration dialog.
Click on the device icon () on the left-hand side of the screen. Find the device you’d like to create the task for, right-click on it, and select the appropriate task type.
Click on the add icon () in the top-right corner of the central mosaic and select the task type from the menu.
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. See Timing for how samples are timestamped.
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:
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
Python
TypeScript
Running a task
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
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:
# 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
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
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) Running a task
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
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:
// 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
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
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);