Modbus Read Task
Learn how to acquire data from Modbus TCP devices with Synnax.
For the task lifecycle, see Task Basics.
Task Configuration Reference
Register Types Reference
Holding Register Input (holding_register)
holding_register)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:
int8/uint8/int16/uint16: 1 register (2 bytes)int32/uint32/float32: 2 registers (4 bytes)int64/uint64/float64: 4 registers (8 bytes)
Input Register (input_register)
input_register)Reads from 16-bit read-only registers (Function Code 04). Typically used for analog sensor values like temperature, pressure, or flow.
Data Type Sizes:
int8/uint8/int16/uint16: 1 register (2 bytes)int32/uint32/float32: 2 registers (4 bytes)int64/uint64/float64: 4 registers (8 bytes)
Coil Input (coil)
coil)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
- Software timing -> Samples are timestamped in software with ~100 µs precision, which degrades under heavy load.
- Byte/word order -> Swap settings must match the server for multi-register data types.
- 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
Python
TypeScript
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,
channels=[
modbus.InputRegisterReadChannel(
channel=input_reg_0.key,
address=0,
data_type="uint8",
),
modbus.InputRegisterReadChannel(
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({ names: ["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",
rack: dev.rack,
type: "modbus_read",
config: {
device: dev.key,
sampleRate: 10,
streamRate: 10,
channels: [
{
type: "input_register",
channel: inputReg0.key,
address: 0,
dataType: "uint8",
},
{
type: "input_register",
channel: inputReg1.key,
address: 1,
dataType: "uint8",
},
],
},
});
// Start task
await task.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.stop();
await streamer.close(); Edit task configuration
import { modbus } from "@synnaxlabs/client";
// Retrieve existing task
const task = await client.tasks.retrieve({ name: "Modbus Read Task" });
// Parse and update configuration
const config = modbus.readConfigZ.parse(task.config);
// Update task-level configuration
config.autoStart = true;
config.streamRate = 5;
// Update first channel configuration
const firstRegister = config.channels[0] as modbus.InputRegisterReadChannel;
firstRegister.address = 10;
firstRegister.dataType = "uint16";
// Update second channel configuration
const secondRegister = config.channels[1] as modbus.InputRegisterReadChannel;
secondRegister.address = 11;
secondRegister.dataType = "uint16";
// Apply changes
await client.tasks.create({
key: task.key,
rack: task.rack,
name: task.name,
type: task.type,
config,
});