# Modbus read task

> Read the coils and registers of a Modbus TCP server into channels.

A Modbus read task reads coils and registers from one Modbus server into channels. For example, it can record a tank level from holding registers 0 and 1 of a PLC at 10 Hz.

Before you start, you need a [connected server](https://docs.synnaxlabs.com/reference/hardware/modbus/connect-server).

## Registers and coils

A read task reads four types of data, each with its own address range:

| Console label    | Modbus name      | Function code | Value                         |
| ---------------- | ---------------- | ------------- | ----------------------------- |
| Coil             | Coil             | 01            | 0 or 1                        |
| Discrete         | Discrete input   | 02            | 0 or 1                        |
| Holding register | Holding register | 03            | A number, in 1 to 4 registers |
| Register         | Input register   | 04            | A number, in 1 to 4 registers |

A register holds 16 bits. An 8-bit or 16-bit value uses one register, and an 8-bit value is the low byte. A 32-bit value uses two registers, and a 64-bit value uses four. The **Data type** of the channel sets the size. A bit channel reads one bit of a register, and a string channel reads two characters from each register.

For each type, the Driver reads the addresses of the task in as few requests as it can. One request holds at most 125 registers or 2000 coils or discrete inputs. A request also reads the addresses between your channels, up to the **Max gap** of the [server](https://docs.synnaxlabs.com/reference/hardware/modbus/connect-server#connection-options). Lower **Max registers** or **Max gap** for a device that rejects long reads or unused addresses.

The Driver timestamps each read with the computer clock. See [timing](https://docs.synnaxlabs.com/reference/hardware/tasks#timing).

### Byte and word order

By default, the Driver reads the low word of a 32-bit or 64-bit value first (word swap, CDAB). Many devices send the high word first (big-endian, ABCD). The **Byte order** of the [server](https://docs.synnaxlabs.com/reference/hardware/modbus/connect-server#connection-options) sets the order for every register channel of the server.

To change the order of one channel, use its **Bytes** and **Words** selects. Each select has three options:

* **device** -> uses the byte order of the server, and shows its value, such as **device (swapped)**.
* **swapped** -> swaps the order of this channel, whatever the server sets.
* **not swapped** -> does not swap the order of this channel, whatever the server sets.

The Console hides **Words** for 8-bit and 16-bit data types, because they fit in one register. In code, leave `bytes_swapped` and `words_swapped` unset to use the order of the server.

## Create a Modbus read task

[Enterprise](https://docs.synnaxlabs.com/reference/get-started/editions "Needs Synnax Enterprise")

**Console**

1. In the devices toolbar, right-click the server and select **Create read task**.
2. Set **Sample rate** and **Stream rate**.
3. Click **Add channel**. Select the type, then enter the address. For a register, select the **Data type**. For a string, set the **Length**. For a bit, set the **Bit**.
4. Click (**Start**).

The Console creates the channels. For the server `Pump skid PLC`, a `float32` holding register at address 0 gives `Pump_skid_PLC_holding_register_input_0_f32`, and a coil at address 3 gives `Pump_skid_PLC_coil_input_3`. The index channel is `Pump_skid_PLC_time`.

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

**Python**

Python does not create channels. Create them first.

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

dev = client.devices.retrieve(name="Pump skid PLC")
time = client.channels.create(
    name="plc_time", is_index=True, data_type=sy.DataType.TIMESTAMP
)
level = client.channels.create(
    name="tank_level", index=time.key, data_type=sy.DataType.FLOAT32
)
pump_on = client.channels.create(
    name="pump_on", index=time.key, data_type=sy.DataType.UINT8
)

task = modbus.ReadTask(
    name="Pump skid",
    device=dev.key,
    sample_rate=10,
    stream_rate=5,
    channels=[
        modbus.HoldingRegisterReadChannel(
            address=0, data_type="float32", words_swapped=True, channel=level.key
        ),
        modbus.CoilReadChannel(address=3, channel=pump_on.key),
    ],
)
client.tasks.configure(task)
task.start()
```

**TypeScript**

TypeScript does not create channels. Create them first.

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

const [dev] = await client.devices.retrieve({
  where: { name: { eq: "Pump skid PLC" } },
});
const time = await client.channels.create({
  name: "plc_time",
  isIndex: true,
  dataType: "timestamp",
});
const level = await client.channels.create({
  name: "tank_level",
  index: time.key,
  dataType: "float32",
});

const task = await client.tasks.create({
  name: "Pump skid",
  rack: dev.rack,
  type: "modbus_read",
  config: modbus.readConfigZ.parse({
    device: dev.key,
    sampleRate: 10,
    streamRate: 5,
    channels: [
      {
        type: "holding_register",
        address: 0,
        dataType: "float32",
        wordsSwapped: true,
        channel: level.key,
      },
    ],
  }),
});
await task.start();
```

## Task options

| Console label | Python field           | Default | Notes                                                                                |
| ------------- | ---------------------- | ------- | ------------------------------------------------------------------------------------ |
| Modbus server | `device`               |         | The key of the server.                                                               |
| Sample rate   | `sample_rate`          | 10 Hz   | How often the Driver reads the server. Up to 50,000 Hz.                              |
| Stream rate   | `stream_rate`          | 5 Hz    | See [read tasks](https://docs.synnaxlabs.com/reference/hardware/tasks#read-tasks).   |
| Data saving   | `data_saving_disabled` | On      | See [data saving](https://docs.synnaxlabs.com/reference/hardware/tasks#data-saving). |
| Auto start    | `auto_start`           | Off     | Starts the task each time the Driver starts.                                         |

## Channel reference

Each channel has an `address`, from 0 to 65535, and a `channel`, the Synnax channel that records the value. Each table shows the Console label of a setting, with its Python field under it.

The Console also accepts a six-digit Modicon address, such as 300001 for input register 0. The first digit is the table: 0 for coils, 1 for discrete inputs, 3 for input registers, and 4 for holding registers. The Console shows the Modicon form next to each address.

**Coil**

#### [Coil](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#coil)

Python `CoilReadChannel`TypeScript `coil`

Reads a coil as 0 or 1, as `uint8`.

**Discrete**

#### [Discrete](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#discrete_input)

Python `DiscreteInputReadChannel`TypeScript `discrete_input`

Reads a discrete input as 0 or 1, as `uint8`.

**Holding register**

#### [Holding register](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#holding_register)

Python `HoldingRegisterReadChannel`TypeScript `holding_register`

Reads a number from one or more holding registers.

| Setting               | Default  | Notes                                                                                                                                                        |
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Data type `data_type` | `uint16` | `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`, `float32`, `float64`.                                                              |
| Bytes `bytes_swapped` | device   | `True` swaps the two bytes in each register. See [byte and word order](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#byte-and-word-order). |
| Words `words_swapped` | device   | `True` reads the high word first. Only for 32-bit and 64-bit data types.                                                                                     |

**Register**

#### [Register](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#input_register)

Python `InputRegisterReadChannel`TypeScript `input_register`

Reads a number from one or more input registers.

| Setting               | Default  | Notes                                                                                                                                                        |
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Data type `data_type` | `uint16` | `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`, `float32`, `float64`.                                                              |
| Bytes `bytes_swapped` | device   | `True` swaps the two bytes in each register. See [byte and word order](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#byte-and-word-order). |
| Words `words_swapped` | device   | `True` reads the high word first. Only for 32-bit and 64-bit data types.                                                                                     |

**Holding register string**

#### [Holding register string](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#holding_register_string)

Python `HoldingRegisterStringReadChannel`TypeScript `holding_register_string`

Reads a string from consecutive holding registers. See [strings](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#strings).

| Setting               | Default | Notes                                                                                                                                                             |
| --------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Length `length`       |         | The number of characters, from 1 to 250.                                                                                                                          |
| Bytes `bytes_swapped` | device  | `True` swaps the two characters in each register. See [byte and word order](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#byte-and-word-order). |

**Input register string**

#### [Input register string](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#input_register_string)

Python `InputRegisterStringReadChannel`TypeScript `input_register_string`

Reads a string from consecutive input registers. See [strings](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#strings).

| Setting               | Default | Notes                                                                                                                                                             |
| --------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Length `length`       |         | The number of characters, from 1 to 250.                                                                                                                          |
| Bytes `bytes_swapped` | device  | `True` swaps the two characters in each register. See [byte and word order](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#byte-and-word-order). |

**Holding register bit**

#### [Holding register bit](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#holding_register_bit)

Python `HoldingRegisterBitReadChannel`TypeScript `holding_register_bit`

Reads one bit of a holding register as 0 or 1, as `uint8`.

| Setting   | Default | Notes                             |
| --------- | ------- | --------------------------------- |
| Bit `bit` | `0`     | From 0 (least significant) to 15. |

**Register bit**

#### [Register bit](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#input_register_bit)

Python `InputRegisterBitReadChannel`TypeScript `input_register_bit`

Reads one bit of an input register as 0 or 1, as `uint8`.

| Setting   | Default | Notes                             |
| --------- | ------- | --------------------------------- |
| Bit `bit` | `0`     | From 0 (least significant) to 15. |

### Strings

Each register holds two characters, the high byte first. **Bytes** set to swapped puts the low byte first. The string ends at the first NUL character. The Synnax channel of a string must have the `string` data type.

## Troubleshooting

### “Illegal data address”

The server does not have one of the addresses in a request. The error names the channels and the addresses of the request. Check the address map of the device. Some devices count addresses from 1, so try the address minus 1. If the server does not have the addresses between two channels, set **Max gap** of the server to `0`.

### “unsupported data type: …”

The Driver does not support the data type of the channel. Select a data type from the [channel reference](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#channel-reference).

### The values are wrong

Set the [byte and word order](https://docs.synnaxlabs.com/reference/hardware/modbus/read-task#byte-and-word-order) of the server or the channel. Check that the data type matches the device.

### The connection dropped

The Driver reconnects and sends the request again at once, up to the **Retries** of the server. If that fails, the task keeps running and shows the warning “Failed to read data”. The Driver tries again, up to every 5 seconds, until the server is back.

For other problems, see [Connect a Modbus server](https://docs.synnaxlabs.com/reference/hardware/modbus/connect-server#troubleshooting) and [Troubleshoot hardware](https://docs.synnaxlabs.com/reference/hardware/troubleshooting).
