> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Modbus Serial Client Route

> Connect to PLCs and industrial devices using Modbus RTU over RS-232/RS-485 serial connections

## Modbus Serial Client

> [!NOTE]
>
> This page documents the **client** route (`MODBUS_SERIAL`) over serial RTU—the broker connects **to** a remote Modbus device on a COM port. For Modbus TCP client, see [Modbus TCP Client](./modbus-tcp-client).

The `MODBUS_SERIAL` route enables communication with industrial devices using Modbus RTU protocol over serial connections (RS-232 or RS-485). It supports the same TAG-based configuration as Modbus TCP but adds serial-specific parameters for baud rate, parity, and data bits.

> [!TIP]
>
> Modbus Serial (RTU) is commonly used for legacy equipment, long-distance communication, and multi-drop RS-485 networks where multiple devices share a single bus.

## Basic Syntax

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE SerialDevice WITH TYPE MODBUS_SERIAL
    ADD MODBUS_CONFIG
        WITH CONNECTION_TYPE "SERIAL"
        WITH COM_PORT "COM1"
        WITH BAUD_RATE 9600
        WITH PARITY "NONE"
        WITH DATA_BITS 8
        WITH STOP_BITS "ONE"
        WITH SLAVE_ID 1
    ADD MAPPING SensorReadings
        WITH EVERY 1 SECOND
        ADD TAG Temperature
            WITH ADDRESS "100"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "serial/temperature"
```

***

## Serial Configuration

### MODBUS\_CONFIG Parameters

**CONNECTION\_TYPE** `string` *required*

Must be set to `SERIAL` for serial communication.

**COM\_PORT** `string` *required*

Serial port name. Windows: `COM1`, `COM2`, etc. Linux: `/dev/ttyUSB0`, `/dev/ttyS0`, etc.

**BAUD\_RATE** `integer` *required*

Communication speed in bits per second. Common values: 9600, 19200, 38400, 57600, 115200.

**PARITY** `string`

Parity checking: `NONE`, `ODD`, `EVEN`, `MARK`, or `SPACE`. Default: NONE.

**DATA\_BITS** `integer`

Number of data bits per character: 7 or 8. Default: 8.

**STOP\_BITS** `string`

Number of stop bits: `ONE`, `TWO`, or `ONEPOINTFIVE`. Default: ONE.

**SLAVE\_ID** `integer` *required*

Modbus slave/unit ID (1-247). Each device on the bus must have a unique ID.

**READ\_TIMEOUT** `integer`

Read operation timeout in milliseconds. Default: 1000.

**WRITE\_TIMEOUT** `integer`

Write operation timeout in milliseconds. Default: 1000.

**RETRY\_COUNT** `integer`

Number of retry attempts on failure. Default: 3.

**RETRY\_DELAY** `integer`

Delay between retries in milliseconds. Default: 100.

***

## Common Serial Settings

Different devices use different serial settings. Here are common configurations:

**Standard (Most Common)**

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD MODBUS_CONFIG
    WITH CONNECTION_TYPE "SERIAL"
    WITH COM_PORT "COM1"
    WITH BAUD_RATE 9600
    WITH PARITY "NONE"
    WITH DATA_BITS 8
    WITH STOP_BITS "ONE"
    WITH SLAVE_ID 1
```

Settings: 9600 8N1 (9600 baud, 8 data bits, No parity, 1 stop bit)

**Even Parity**

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD MODBUS_CONFIG
    WITH CONNECTION_TYPE "SERIAL"
    WITH COM_PORT "COM1"
    WITH BAUD_RATE 9600
    WITH PARITY "EVEN"
    WITH DATA_BITS 8
    WITH STOP_BITS "ONE"
    WITH SLAVE_ID 1
```

Settings: 9600 8E1 (9600 baud, 8 data bits, Even parity, 1 stop bit. Common in older equipment.)

**High Speed**

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD MODBUS_CONFIG
    WITH CONNECTION_TYPE "SERIAL"
    WITH COM_PORT "/dev/ttyUSB0"
    WITH BAUD_RATE 115200
    WITH PARITY "NONE"
    WITH DATA_BITS 8
    WITH STOP_BITS "ONE"
    WITH SLAVE_ID 1
```

Settings: 115200 8N1 (115200 baud, 8 data bits, No parity, 1 stop bit. For modern devices.)

**Linux USB Adapter**

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD MODBUS_CONFIG
    WITH CONNECTION_TYPE "SERIAL"
    WITH COM_PORT "/dev/ttyUSB0"
    WITH BAUD_RATE 9600
    WITH PARITY "NONE"
    WITH DATA_BITS 8
    WITH STOP_BITS "ONE"
    WITH SLAVE_ID 1
```

> [!NOTE]
>
> On Linux, USB-to-serial adapters typically appear as `/dev/ttyUSB0`, `/dev/ttyUSB1`, etc.

***

## RS-485 Multi-Drop Networks

RS-485 allows multiple devices on a single bus. Each device must have a unique slave ID:

```mermaid theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
graph LR
    subgraph bus [RS-485 Bus]
        Master[Coreflux]
        Device1[Device 1<br/>ID: 1]
        Device2[Device 2<br/>ID: 2]
        Device3[Device 3<br/>ID: 3]
    end
    
    Master --- Device1
    Master --- Device2
    Master --- Device3
```

### Multi-Device Configuration

Create separate routes or use different slave IDs:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE TemperatureController WITH TYPE MODBUS_SERIAL
    ADD MODBUS_CONFIG
        WITH CONNECTION_TYPE "SERIAL"
        WITH COM_PORT "COM1"
        WITH BAUD_RATE 9600
        WITH SLAVE_ID 1
    ADD MAPPING TempController
        WITH EVERY 1 SECOND
        ADD TAG Temperature
            WITH ADDRESS "0"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "modbus/device1/temperature"

DEFINE ROUTE FlowMeter WITH TYPE MODBUS_SERIAL
    ADD MODBUS_CONFIG
        WITH CONNECTION_TYPE "SERIAL"
        WITH COM_PORT "COM1"
        WITH BAUD_RATE 9600
        WITH SLAVE_ID 2
    ADD MAPPING FlowMeter
        WITH EVERY 1 SECOND
        ADD TAG FlowRate
            WITH ADDRESS "0"
            WITH ADDRESS_TYPE "INPUT_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "modbus/device2/flow"
```

> [!WARNING]
>
> When sharing a serial port between multiple slave IDs, ensure polling intervals are set to avoid bus collisions. The route handles sequencing automatically, but very fast polling across many devices may cause timeouts.

***

## TAG Configuration

The TAG configuration for Modbus Serial matches Modbus TCP, including string sizing. See the [Modbus TCP Client documentation](./modbus-tcp-client) for the full TAG parameter reference.

### String sizing

For `STRING` tags, `WITH LENGTH` sets the maximum string size in **bytes** (default **256**). `STRING_SIZE` is accepted as an alias. Each register holds two bytes, so the route reads enough **consecutive registers** from the tag's starting `ADDRESS` to cover the configured size.

```lot wrap focus={16} theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE ModbusStrings WITH TYPE MODBUS_SERIAL
    ADD MODBUS_CONFIG
        WITH CONNECTION_TYPE "SERIAL"
        WITH COM_PORT "/dev/ttyUSB0"
        WITH BAUD_RATE 9600
        WITH PARITY "NONE"
        WITH DATA_BITS 8
        WITH STOP_BITS "ONE"
        WITH SLAVE_ID 1
    ADD MAPPING ProductData
        WITH EVERY 1 SECOND
        ADD TAG ProductName
            WITH ADDRESS "0"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "STRING"
            WITH LENGTH 32
            WITH SOURCE_TOPIC "factory/product/name"
```

For cross-protocol defaults and caps, see [String sizing by Route](./overview#string-sizing-by-route) on the industrial overview.

### TAG parameters

**LENGTH** `integer` (default: 256)

**STRING only.** Maximum string size in bytes. The route allocates consecutive registers from the starting `ADDRESS` (2 bytes per register). Alias: `STRING_SIZE`.

### Quick Reference

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD TAG SensorValue
    WITH ADDRESS "100"
    WITH ADDRESS_TYPE "HOLDING_REGISTER"
    WITH DATA_TYPE "FLOAT"
    WITH SOURCE_TOPIC "serial/sensor"
    WITH SCALING 0.1
    WITH OFFSET 0
    WITH UNIT "°C"
    WITH DEADBAND 0.5
    WITH PUBLISH_MODE "JSON"
    WITH WRITABLE "true"
    WITH DESTINATION_TOPIC "serial/sensor/set"
    WITH BYTE_ORDER "BIGENDIAN"
    WITH WORD_ORDER "BIGENDIAN"
```

***

## Event-Based Operations

For on-demand Modbus Serial operations (not polling), use the EVENT syntax. Publish a message to SOURCE\_TOPIC to trigger the operation; the route executes it and publishes the result to DESTINATION\_TOPIC.

Modbus Serial supports the same EVENT operations as Modbus TCP. See the [Modbus TCP Client Event-Based Operations](./modbus-tcp-client#event-based-operations) for the full supported operations table and query parameter details.

### Read Example

Read 10 holding registers on demand:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD EVENT ReadRegisters
    WITH SOURCE_TOPIC "serial/commands/read"
    WITH DESTINATION_TOPIC "serial/responses/read"
    WITH QUERY "{operation: READ_HOLDING_REGISTERS, start_address: 0, count: 10}"
```

### Write Example

Write a single register on demand:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
ADD EVENT WriteRegister
    WITH SOURCE_TOPIC "serial/commands/write"
    WITH DESTINATION_TOPIC "serial/responses/write"
    WITH QUERY "{operation: WRITE_SINGLE_REGISTER, address: 100, value: 500}"
```

***

## Complete Examples

**Single Device**

Basic connection to a single Modbus RTU device:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE EnergyMeter WITH TYPE MODBUS_SERIAL
    ADD MODBUS_CONFIG
        WITH CONNECTION_TYPE "SERIAL"
        WITH COM_PORT "COM3"
        WITH BAUD_RATE 9600
        WITH PARITY "EVEN"
        WITH DATA_BITS 8
        WITH STOP_BITS "ONE"
        WITH SLAVE_ID 1
        WITH READ_TIMEOUT 1000
        WITH RETRY_COUNT 3
    ADD MAPPING Readings
        WITH EVERY 1 SECOND
        ADD TAG Voltage
            WITH ADDRESS "0"
            WITH ADDRESS_TYPE "INPUT_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "meter/voltage"
            WITH UNIT "V"
        ADD TAG Current
            WITH ADDRESS "2"
            WITH ADDRESS_TYPE "INPUT_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "meter/current"
            WITH UNIT "A"
        ADD TAG Power
            WITH ADDRESS "4"
            WITH ADDRESS_TYPE "INPUT_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "meter/power"
            WITH UNIT "kW"
```

**Temperature Controller**

Read process value and write setpoint:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE TempController WITH TYPE MODBUS_SERIAL
    ADD MODBUS_CONFIG
        WITH CONNECTION_TYPE "SERIAL"
        WITH COM_PORT "/dev/ttyUSB0"
        WITH BAUD_RATE 9600
        WITH PARITY "NONE"
        WITH DATA_BITS 8
        WITH STOP_BITS "ONE"
        WITH SLAVE_ID 1
    ADD MAPPING Control
        WITH EVERY 500 MILLISECONDS
        ADD TAG ProcessValue
            WITH ADDRESS "0"
            WITH ADDRESS_TYPE "INPUT_REGISTER"
            WITH DATA_TYPE "INT16"
            WITH SOURCE_TOPIC "tempctrl/pv"
            WITH SCALING 0.1
            WITH UNIT "°C"
        ADD TAG Setpoint
            WITH ADDRESS "1"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "INT16"
            WITH SOURCE_TOPIC "tempctrl/sp"
            WITH SCALING 0.1
            WITH UNIT "°C"
            WITH WRITABLE "true"
            WITH DESTINATION_TOPIC "tempctrl/sp/set"
        ADD TAG Output
            WITH ADDRESS "2"
            WITH ADDRESS_TYPE "INPUT_REGISTER"
            WITH DATA_TYPE "INT16"
            WITH SOURCE_TOPIC "tempctrl/output"
            WITH SCALING 0.1
            WITH UNIT "%"
```

**VFD (Variable Frequency Drive)**

Control a motor drive:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE VFDControl WITH TYPE MODBUS_SERIAL
    ADD MODBUS_CONFIG
        WITH CONNECTION_TYPE "SERIAL"
        WITH COM_PORT "COM2"
        WITH BAUD_RATE 19200
        WITH PARITY "EVEN"
        WITH DATA_BITS 8
        WITH STOP_BITS "ONE"
        WITH SLAVE_ID 1
    ADD MAPPING DriveStatus
        WITH EVERY 200 MILLISECONDS
        ADD TAG ActualSpeed
            WITH ADDRESS "8451"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "UINT16"
            WITH SOURCE_TOPIC "vfd/speed/actual"
            WITH SCALING 0.1
            WITH UNIT "Hz"
        ADD TAG MotorCurrent
            WITH ADDRESS "8448"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "UINT16"
            WITH SOURCE_TOPIC "vfd/current"
            WITH SCALING 0.01
            WITH UNIT "A"
        ADD TAG SpeedReference
            WITH ADDRESS "8502"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "UINT16"
            WITH SOURCE_TOPIC "vfd/speed/reference"
            WITH SCALING 0.1
            WITH UNIT "Hz"
            WITH WRITABLE "true"
            WITH DESTINATION_TOPIC "vfd/speed/set"
        ADD TAG ControlWord
            WITH ADDRESS "8501"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "UINT16"
            WITH SOURCE_TOPIC "vfd/control"
            WITH WRITABLE "true"
            WITH DESTINATION_TOPIC "vfd/control/set"
```

**Combined (Cyclic + On-Demand)**

Continuous monitoring with on-demand read and write events in the same route:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE FullSerialSetup WITH TYPE MODBUS_SERIAL
    ADD MODBUS_CONFIG
        WITH CONNECTION_TYPE "SERIAL"
        WITH COM_PORT "COM3"
        WITH BAUD_RATE 9600
        WITH PARITY "EVEN"
        WITH DATA_BITS 8
        WITH STOP_BITS "ONE"
        WITH SLAVE_ID 1
    
    ADD MAPPING ProcessReadings
        WITH EVERY 1 SECOND
        ADD TAG Voltage
            WITH ADDRESS "0"
            WITH ADDRESS_TYPE "INPUT_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "meter/voltage"
            WITH UNIT "V"
        ADD TAG Current
            WITH ADDRESS "2"
            WITH ADDRESS_TYPE "INPUT_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "meter/current"
            WITH UNIT "A"
    
    ADD EVENT ReadRegisters
        WITH SOURCE_TOPIC "serial/commands/read"
        WITH DESTINATION_TOPIC "serial/responses/read"
        WITH QUERY "{operation: READ_HOLDING_REGISTERS, start_address: 0, count: 10}"
    
    ADD EVENT WriteRegister
        WITH SOURCE_TOPIC "serial/commands/write"
        WITH DESTINATION_TOPIC "serial/responses/write"
        WITH QUERY "{operation: WRITE_SINGLE_REGISTER, address: 100, value: 0}"
```

The MAPPING continuously reads voltage and current. To trigger an on-demand register read, publish any message to `serial/commands/read`. To write, publish to `serial/commands/write`.

***

## Troubleshooting

> **No Communication**
>
> * Verify COM port is correct and not in use by another application
> * Check physical wiring (TX/RX, A/B for RS-485)
> * Ensure baud rate matches device settings
> * Verify slave ID is correct
> * Check device power and status indicators

> **CRC Errors**
>
> * CRC errors indicate data corruption
> * Check cable quality and length
> * For RS-485, ensure proper termination resistors (120Ω at each end)
> * Reduce baud rate for long cable runs
> * Check for electrical interference

> **Timeout Errors**
>
> * Increase `READ_TIMEOUT` value
> * Verify serial settings match device exactly
> * Check for bus conflicts (multiple masters)
> * For RS-485, ensure proper biasing

> **Incorrect Values**
>
> * Verify register addresses in device documentation
> * Check `BYTE_ORDER` and `WORD_ORDER` settings
> * Confirm `DATA_TYPE` matches register size
> * Some devices use 1-based addressing (subtract 1 from documented address)

> **Port Not Found**
>
> * Windows: Check Device Manager for correct COM port number
> * Linux: Check `/dev/tty*` with `ls -l /dev/tty*`
> * Ensure USB-to-serial driver is installed
> * Verify user has permissions to access serial port

***

## RS-485 Wiring Tips

> [!NOTE]
>
> Proper wiring is critical for reliable RS-485 communication.

| Wire | Function | Description |
| - | - | - |
| A (D-) | Data - | Negative data line |
| B (D+) | Data + | Positive data line |
| GND | Ground | Signal ground reference |

**Best Practices:**

* Use twisted pair cable for A and B lines
* Add 120Ω termination resistors at both ends of the bus
* Keep cable length under 1200m (4000ft)
* Use bias resistors if experiencing idle-line issues
* Ensure all grounds are connected

***

## Next Steps

> **[Modbus TCP Client](./modbus-tcp-client)**
>
> Connect to Modbus devices over Ethernet.

> **[Siemens S7](./siemens-s7)**
>
> Connect directly to Siemens PLCs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.