# Modbus read task

> Learn how to acquire data from Modbus TCP devices 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 Modbus server device                               |
| `sample_rate` | number | No       | `10`    | Samples per second (Hz)                                       |
| `stream_rate` | number | No       | `5`     | Rate data is streamed to Synnax (Hz), must be ≤ `sample_rate` |
| `channels`    | array  | Yes      | -       | List of input channel configurations                          |

## Register types reference

Holding register input ([`holding_register`](https://www.modbustools.com/modbus.html))

Reads from 16-bit read/write registers (Function Code 03). Typically used for configuration parameters and analog outputs that can also be read back.

| Parameter       | Type    | Required | Default | Description                                                                                                         |
| --------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `channel`       | number  | Yes      | -       | Synnax channel key                                                                                                  |
| `address`       | number  | Yes      | -       | Modbus register address (0-65535)                                                                                   |
| `data_type`     | string  | No       | `uint8` | Data type: `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`, `float32`, `float64`, `string` |
| `bytes_swapped` | boolean | No       | `false` | Swap byte order within 16-bit words                                                                                 |
| `words_swapped` | boolean | No       | `false` | Swap word order for 32-bit+ values                                                                                  |
| `string_length` | number  | No       | `0`     | Register count to read as a string. `0` reads a number                                                              |

**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`](https://www.modbustools.com/modbus.html))

Reads from 16-bit read-only registers (Function Code 04). Typically used for analog sensor values like temperature, pressure, or flow.

| Parameter       | Type    | Required | Default | Description                                                                                                         |
| --------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `channel`       | number  | Yes      | -       | Synnax channel key                                                                                                  |
| `address`       | number  | Yes      | -       | Modbus register address (0-65535)                                                                                   |
| `data_type`     | string  | No       | `uint8` | Data type: `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`, `float32`, `float64`, `string` |
| `bytes_swapped` | boolean | No       | `false` | Swap byte order within 16-bit words                                                                                 |
| `words_swapped` | boolean | No       | `false` | Swap word order for 32-bit+ values                                                                                  |
| `string_length` | number  | No       | `0`     | Register count to read as a string. `0` reads a number                                                              |

**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`](https://www.modbustools.com/modbus.html#Function01))

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).

| Parameter | Type   | Required | Default | Description                   |
| --------- | ------ | -------- | ------- | ----------------------------- |
| `channel` | number | Yes      | -       | Synnax channel key            |
| `address` | number | Yes      | -       | Modbus coil address (0-65535) |

**Data type:** Always boolean (0/1)

Discrete input ([`discrete_input`](https://www.modbustools.com/modbus.html))

Reads from 1-bit read-only discrete inputs (Function Code 02). Typically used for binary sensor inputs (e.g., limit switches, proximity sensors).

| Parameter | Type   | Required | Default | Description                             |
| --------- | ------ | -------- | ------- | --------------------------------------- |
| `channel` | number | Yes      | -       | Synnax channel key                      |
| `address` | number | Yes      | -       | Modbus discrete input address (0-65535) |

**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**

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

**Python**

Configure and run task

```python
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

```python
# 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)
```

**TypeScript**

Configure and run task

```typescript
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

```typescript
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,
});
```
