Modbus Read Task
Learn how to acquire data from Modbus TCP devices with Synnax.
For task lifecycle management, see the Task Basics page.
Task Configuration Reference
Register Types Reference
Holding Register Input (holding_register_input)
holding_register_input)Reads from 16-bit read/write registers (Function Code 03). Typically used for configuration parameters and analog outputs that can also be read back.
Data Type Sizes:
int16/uint16: 1 register (2 bytes)int32/uint32/float32: 2 registers (4 bytes)
Input Register (register_input)
register_input)Reads from 16-bit read-only registers (Function Code 04). Typically used for analog sensor values like temperature, pressure, or flow.
Data Type Sizes:
int16/uint16: 1 register (2 bytes)int32/uint32/float32: 2 registers (4 bytes)
Coil Input (coil_input)
coil_input)Reads from 1-bit read/write coils (Function Code 01). Typically used for discrete output states that can be read back (e.g., relay states, valve positions).
Data Type: Always boolean (0/1)
Discrete Input (discrete_input)
discrete_input)Reads from 1-bit read-only discrete inputs (Function Code 02). Typically used for binary sensor inputs (e.g., limit switches, proximity sensors).
Data Type: Always boolean (0/1)
Important Rules
- Sample rates -> All channels in a task sample at the same rate. Create separate tasks for different rates.
- Software timing -> Modbus tasks use software timing with ~100 μs precision (may degrade under heavy load).
- One running task per channel -> A channel can only receive live data from one task at a time.
- Stream rate optimization -> For low-rate tasks (< 50 Hz), set the stream rate to the sample rate. For high-rate tasks, keep the stream rate less than 50 Hz for better performance.
- Byte/word order -> Ensure swap settings match your server configuration for multi-register data types.
How-To
Configure and run task
import synnax as sy
from synnax import modbus
client = sy.Synnax()
# Retrieve device
dev = client.devices.retrieve(name="Modbus Server")
# Create index channel
modbus_time = client.channels.create(
name="modbus_time",
is_index=True,
data_type=sy.DataType.TIMESTAMP,
retrieve_if_name_exists=True,
)
# Create data channels
input_reg_0 = client.channels.create(
name="input_register_0",
index=modbus_time.key,
data_type=sy.DataType.UINT8,
retrieve_if_name_exists=True,
)
input_reg_1 = client.channels.create(
name="input_register_1",
index=modbus_time.key,
data_type=sy.DataType.UINT8,
retrieve_if_name_exists=True,
)
# Create and configure task
task = modbus.ReadTask(
name="Modbus Read Task",
device=dev.key,
sample_rate=sy.Rate.HZ * 10,
stream_rate=sy.Rate.HZ * 10,
data_saving=True,
channels=[
modbus.InputRegisterChan(
channel=input_reg_0.key,
address=0,
data_type="uint8",
),
modbus.InputRegisterChan(
channel=input_reg_1.key,
address=1,
data_type="uint8",
),
],
)
client.tasks.configure(task)
# Start task and read data
with task.run():
with client.open_streamer(["input_register_0", "input_register_1"]) as streamer:
for _ in range(10):
frame = streamer.read()
print(frame) Edit task configuration
# Retrieve existing task
task = client.tasks.retrieve(name="Modbus Read Task")
task = modbus.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].address = 10
task.config.channels[0].data_type = "uint16"
# Update second channel configuration
task.config.channels[1].address = 11
task.config.channels[1].data_type = "uint16"
# Apply changes
client.tasks.configure(task) Configure and run task
import { Synnax } from "@synnaxlabs/client";
const client = new Synnax();
// Retrieve device
const dev = await client.devices.retrieve({ name: "Modbus Server" });
// Create index channel
const modbusTime = await client.channels.create({
name: "modbus_time",
isIndex: true,
dataType: "timestamp",
retrieveIfNameExists: true,
});
// Create data channels
const inputReg0 = await client.channels.create({
name: "input_register_0",
index: modbusTime.key,
dataType: "uint8",
retrieveIfNameExists: true,
});
const inputReg1 = await client.channels.create({
name: "input_register_1",
index: modbusTime.key,
dataType: "uint8",
retrieveIfNameExists: true,
});
// Create and configure task
const task = await client.tasks.create({
name: "Modbus Read Task",
type: "modbus_read",
config: JSON.stringify({
device: dev.key,
sample_rate: 10,
stream_rate: 10,
data_saving: true,
channels: [
{
type: "register_input",
channel: inputReg0.key,
address: 0,
data_type: "uint8",
},
{
type: "register_input",
channel: inputReg1.key,
address: 1,
data_type: "uint8",
},
],
}),
});
// Start task
await task.executeCommandSync("start");
// Read data
const streamer = await client.openStreamer(["input_register_0", "input_register_1"]);
for (let i = 0; i < 10; i++) {
const frame = await streamer.read();
console.log(frame);
}
// Stop task
await task.executeCommandSync("stop");
await streamer.close(); Edit task configuration
// Retrieve existing task
const task = await client.tasks.retrieve({ name: "Modbus Read Task" });
// Parse and update configuration
const config = JSON.parse(task.config);
// Update task-level configuration
config.auto_start = true;
config.stream_rate = 5;
// Update first channel configuration
config.channels[0].address = 10;
config.channels[0].data_type = "uint16";
// Update second channel configuration
config.channels[1].address = 11;
config.channels[1].data_type = "uint16";
// Apply changes
await client.tasks.create({
key: task.key,
name: task.name,
type: task.type,
config: JSON.stringify(config),
});