# Modbus write task

> Learn how to send commands to 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       |
| `channels` | array  | Yes      | -       | List of output channel configurations |

## Register types reference

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

Writes to 16-bit read/write registers (Function Code 06/16). Typically used for setpoints, control values, and configuration parameters.

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

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

Writes to 1-bit read/write coils (Function Code 05/15). Typically used for binary control signals like relay states and valve positions.

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

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

## Important rules

* **Byte/word order** -> Ensure swap settings match your server configuration for multi-register data types.
* **Read-only registers** -> Input registers and discrete inputs cannot be written to.

## How-to

When you create channels with a client library, give each command channel its own index channel. A writer that holds several commands on one index must write all of them in every frame.

**Console**

[](https://synnax.nyc3.cdn.digitaloceanspaces.com/docs/device-drivers/modbus/write-task/write-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 command channel for coil
coil_cmd_time = client.channels.create(
    name="coil_cmd_time",
    is_index=True,
    data_type=sy.DataType.TIMESTAMP,
    retrieve_if_name_exists=True,
)
coil_cmd = client.channels.create(
    name="coil_cmd",
    index=coil_cmd_time.key,
    data_type=sy.DataType.UINT8,
    retrieve_if_name_exists=True,
)

# Create command channel for holding register
holding_reg_cmd_time = client.channels.create(
    name="holding_reg_cmd_time",
    is_index=True,
    data_type=sy.DataType.TIMESTAMP,
    retrieve_if_name_exists=True,
)
holding_reg_cmd = client.channels.create(
    name="holding_reg_cmd",
    index=holding_reg_cmd_time.key,
    data_type=sy.DataType.UINT8,
    retrieve_if_name_exists=True,
)

# Create and configure task
task = modbus.WriteTask(
    name="Modbus Write Task",
    device=dev.key,
    channels=[
        modbus.CoilWriteChannel(
            channel=coil_cmd.key,
            address=0,
        ),
        modbus.HoldingRegisterWriteChannel(
            channel=holding_reg_cmd.key,
            address=2,
            data_type="uint8",
        ),
    ],
)

client.tasks.configure(task)

# Start task and send commands
with task.run():
    with client.open_writer(
        start=sy.TimeStamp.now(),
        channels=["coil_cmd", "holding_reg_cmd"],
    ) as writer:
        # Write commands
        writer.write({
            "coil_cmd": 1,
            "holding_reg_cmd": 100,
        })
```

Edit task configuration

```python
# Retrieve existing task
task = client.tasks.retrieve(name="Modbus Write Task")
task = modbus.WriteTask(internal=task)

# Update task-level configuration
task.config.auto_start = True

# Update coil configuration
task.config.channels[0].address = 10

# Update holding register configuration
task.config.channels[1].address = 20
task.config.channels[1].data_type = "uint16"

# Apply changes
client.tasks.configure(task)
```

**TypeScript**

Configure and run task

```typescript
import { Synnax, TimeStamp } from "@synnaxlabs/client";

const client = new Synnax();

// Retrieve device
const [dev] = await client.devices.retrieve({ names: ["Modbus Server"] });

// Create command channel for coil
const coilCmdTime = await client.channels.create(
  {
    name: "coil_cmd_time",
    isIndex: true,
    dataType: "timestamp",
  },
  { retrieveIfNameExists: true },
);
const coilCmd = await client.channels.create(
  {
    name: "coil_cmd",
    index: coilCmdTime.key,
    dataType: "uint8",
  },
  { retrieveIfNameExists: true },
);

// Create command channel for holding register
const holdingRegCmdTime = await client.channels.create(
  {
    name: "holding_reg_cmd_time",
    isIndex: true,
    dataType: "timestamp",
  },
  { retrieveIfNameExists: true },
);
const holdingRegCmd = await client.channels.create(
  {
    name: "holding_reg_cmd",
    index: holdingRegCmdTime.key,
    dataType: "uint8",
  },
  { retrieveIfNameExists: true },
);

// Create and configure task
const task = await client.tasks.create({
  name: "Modbus Write Task",
  rack: dev.rack,
  type: "modbus_write",
  config: {
    device: dev.key,
    channels: [
      {
        type: "coil",
        channel: coilCmd.key,
        address: 0,
      },
      {
        type: "holding_register",
        channel: holdingRegCmd.key,
        address: 2,
        dataType: "uint8",
      },
    ],
  },
});

// Start task
await task.start();

// Send commands
const writer = await client.openWriter({
  start: TimeStamp.now(),
  channels: ["coil_cmd", "holding_reg_cmd"],
});

await writer.write({
  coil_cmd: 1,
  holding_reg_cmd: 100,
});

// Stop task
await task.stop();
await writer.close();
```

Edit task configuration

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

// Retrieve existing task
const task = await client.tasks.retrieve({ name: "Modbus Write Task" });

// Parse and update configuration
const config = modbus.writeConfigZ.parse(task.config);

// Update task-level configuration
config.autoStart = true;

// Update coil configuration
const coil = config.channels[0] as modbus.CoilWriteChannel;
coil.address = 10;

// Update holding register configuration
const holdingRegister = config.channels[1] as modbus.HoldingRegisterWriteChannel;
holdingRegister.address = 20;
holdingRegister.dataType = "uint16";

// Apply changes
await client.tasks.create({
  key: task.key,
  rack: task.rack,
  name: task.name,
  type: task.type,
  config,
});
```
