> ## 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 TCP Route

> Connect to PLCs and industrial devices using Modbus TCP protocol with TAG-based polling and automatic batch optimization

## Modbus TCP Overview

The `MODBUS_TCP` route enables communication with PLCs, RTUs, and industrial devices using the Modbus TCP/IP protocol. It supports reading and writing registers and coils with configurable polling intervals and automatic batch optimization.

<Tip>
  Modbus TCP is one of the most widely supported industrial protocols. If your device supports Modbus, this route provides a reliable way to integrate it with MQTT.
</Tip>

## Basic Syntax

```lot theme={null}
DEFINE ROUTE ModbusDevice WITH TYPE MODBUS_TCP
    ADD MODBUS_CONFIG
        WITH HOST "192.168.1.100"
        WITH PORT 502
        WITH SLAVE_ID 1
    ADD MAPPING SensorReadings
        WITH EVERY 500 MILLISECONDS
        ADD TAG Temperature
            WITH ADDRESS "100"
            WITH ADDRESS_TYPE "HOLDING_REGISTER"
            WITH DATA_TYPE "FLOAT"
            WITH SOURCE_TOPIC "modbus/temperature"
```

***

## Connection Configuration

### MODBUS\_CONFIG Parameters

<ParamField path="HOST" type="string" required>
  Modbus TCP server hostname or IP address.
</ParamField>

<ParamField path="PORT" type="integer">
  Modbus TCP port. Default: 502.
</ParamField>

<ParamField path="SLAVE_ID" type="integer">
  Modbus slave/unit ID (1-247). Default: 1.
</ParamField>

<ParamField path="CONNECTION_TIMEOUT" type="integer">
  Connection timeout in milliseconds. Default: 5000.
</ParamField>

<ParamField path="READ_TIMEOUT" type="integer">
  Read operation timeout in milliseconds. Default: 1000.
</ParamField>

<ParamField path="WRITE_TIMEOUT" type="integer">
  Write operation timeout in milliseconds. Default: 1000.
</ParamField>

<ParamField path="RETRY_COUNT" type="integer">
  Number of retry attempts on failure. Default: 3.
</ParamField>

<ParamField path="RETRY_DELAY" type="integer">
  Delay between retries in milliseconds. Default: 100.
</ParamField>

### Connection Example

```lot theme={null}
ADD MODBUS_CONFIG
    WITH HOST "192.168.1.100"
    WITH PORT 502
    WITH SLAVE_ID 1
    WITH CONNECTION_TIMEOUT 5000
    WITH READ_TIMEOUT 1000
    WITH WRITE_TIMEOUT 1000
    WITH RETRY_COUNT 3
    WITH RETRY_DELAY 100
```

***

## Address Types

Modbus defines four address spaces:

| Address Type       | Description                 | Function Codes   | Typical Use                 |
| ------------------ | --------------------------- | ---------------- | --------------------------- |
| `HOLDING_REGISTER` | Read/Write 16-bit registers | FC03, FC06, FC16 | Setpoints, configuration    |
| `INPUT_REGISTER`   | Read-only 16-bit registers  | FC04             | Sensor values, measurements |
| `COIL`             | Read/Write single bits      | FC01, FC05, FC15 | Digital outputs, controls   |
| `DISCRETE_INPUT`   | Read-only single bits       | FC02             | Digital inputs, status      |

<Note>
  Most Modbus devices use Holding Registers for configuration and Input Registers for measured values. Check your device documentation for the correct address type.
</Note>

***

## Data Types

<ParamField path="DATA_TYPE" type="string" required>
  The data type determines how raw register values are interpreted:
</ParamField>

| Data Type | Size     | Description                               |
| --------- | -------- | ----------------------------------------- |
| `BOOL`    | 1 bit    | Boolean value (for coils/discrete inputs) |
| `BIT`     | 1 bit    | Alias for BOOL                            |
| `INT8`    | 8 bits   | Signed 8-bit integer                      |
| `UINT8`   | 8 bits   | Unsigned 8-bit integer                    |
| `INT16`   | 16 bits  | Signed 16-bit integer (1 register)        |
| `UINT16`  | 16 bits  | Unsigned 16-bit integer (1 register)      |
| `INT32`   | 32 bits  | Signed 32-bit integer (2 registers)       |
| `UINT32`  | 32 bits  | Unsigned 32-bit integer (2 registers)     |
| `INT64`   | 64 bits  | Signed 64-bit integer (4 registers)       |
| `UINT64`  | 64 bits  | Unsigned 64-bit integer (4 registers)     |
| `FLOAT`   | 32 bits  | 32-bit floating point (2 registers)       |
| `DOUBLE`  | 64 bits  | 64-bit floating point (4 registers)       |
| `STRING`  | Variable | ASCII string across multiple registers    |

***

## TAG Configuration

### Complete TAG Parameters

```lot theme={null}
ADD TAG TemperatureSensor
    WITH ADDRESS "100"
    WITH ADDRESS_TYPE "HOLDING_REGISTER"
    WITH DATA_TYPE "FLOAT"
    WITH SOURCE_TOPIC "modbus/sensors/temperature"
    WITH SCALING 0.1
    WITH OFFSET 0
    WITH UNIT "°C"
    WITH DECIMAL_PLACES 2
    WITH MIN_VALUE -40
    WITH MAX_VALUE 150
    WITH DEADBAND 0.5
    WITH PUBLISH_MODE "JSON"
    WITH WRITABLE "true"
    WITH DESTINATION_TOPIC "modbus/sensors/temperature/set"
    WITH BYTE_ORDER "BIGENDIAN"
    WITH WORD_ORDER "BIGENDIAN"
    WITH DESCRIPTION "Main process temperature sensor"
```

### TAG Parameters Reference

<AccordionGroup>
  <Accordion title="Address Configuration">
    <ParamField path="ADDRESS" type="string" required>
      Register or coil address (numeric value). This is the zero-based address in the Modbus address space.
    </ParamField>

    <ParamField path="ADDRESS_TYPE" type="string" required>
      Type of Modbus address: `HOLDING_REGISTER`, `INPUT_REGISTER`, `COIL`, or `DISCRETE_INPUT`.
    </ParamField>
  </Accordion>

  <Accordion title="Value Transformation">
    <ParamField path="SCALING" type="double">
      Multiplier applied to raw value. Default: 1.0. Example: Raw value 245 with SCALING 0.1 becomes 24.5.
    </ParamField>

    <ParamField path="OFFSET" type="double">
      Value added after scaling. Default: 0.0. Formula: `result = (raw * scaling) + offset`.
    </ParamField>

    <ParamField path="DECIMAL_PLACES" type="integer">
      Number of decimal places in published value. Default: 2.
    </ParamField>
  </Accordion>

  <Accordion title="Filtering">
    <ParamField path="MIN_VALUE" type="double">
      Minimum allowed value. Values below this threshold are not published.
    </ParamField>

    <ParamField path="MAX_VALUE" type="double">
      Maximum allowed value. Values above this threshold are not published.
    </ParamField>

    <ParamField path="DEADBAND" type="double">
      Minimum change required to publish a new value. Default: 0.0.
    </ParamField>
  </Accordion>

  <Accordion title="Publishing">
    <ParamField path="SOURCE_TOPIC" type="string">
      Topic where PLC values are published. Subscribe here to receive sensor data.
    </ParamField>

    <ParamField path="PUBLISH_MODE" type="string">
      Output format: `VALUE_ONLY` or `JSON`. Default: VALUE\_ONLY.
    </ParamField>

    <ParamField path="UNIT" type="string">
      Engineering unit for documentation (e.g., °C, bar, RPM).
    </ParamField>

    <ParamField path="DESCRIPTION" type="string">
      Human-readable description of the TAG.
    </ParamField>
  </Accordion>

  <Accordion title="Write Configuration">
    <ParamField path="WRITABLE" type="boolean">
      Allow writing to this register/coil. Default: false.
    </ParamField>

    <ParamField path="DESTINATION_TOPIC" type="string">
      Topic to send write commands. Publish a value here to write it to the PLC.
    </ParamField>
  </Accordion>

  <Accordion title="Byte Ordering">
    <ParamField path="BYTE_ORDER" type="string">
      Byte order for multi-byte values: `BIGENDIAN` or `LITTLEENDIAN`. Default: BIGENDIAN.
    </ParamField>

    <ParamField path="WORD_ORDER" type="string">
      Word order for 32/64-bit values: `BIGENDIAN` or `LITTLEENDIAN`. Default: BIGENDIAN.
    </ParamField>

    <Warning>
      Different manufacturers use different byte/word ordering. If values appear incorrect, try changing BYTE\_ORDER or WORD\_ORDER.
    </Warning>
  </Accordion>
</AccordionGroup>

***

## Event-Based Operations

For on-demand Modbus 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.

```lot theme={null}
ADD EVENT ReadOnDemand
    WITH SOURCE_TOPIC "modbus/commands/read"
    WITH DESTINATION_TOPIC "modbus/responses/read"
    WITH QUERY "{operation: READ_HOLDING_REGISTERS, start_address: 0, count: 10}"
```

### Query Syntax

The `WITH QUERY` value uses LoT object syntax: unquoted keys, unquoted identifiers, and numeric values. The entire query is wrapped in double quotes.

| Element          | Format               | Example                                |
| ---------------- | -------------------- | -------------------------------------- |
| Keys             | Unquoted             | `operation`, `start_address`, `count`  |
| Operation values | Unquoted identifiers | `READ_HOLDING_REGISTERS`, `READ_COILS` |
| Numeric values   | Unquoted             | `0`, `10`, `100`                       |
| String values    | Unquoted (addresses) | `address: 100`                         |

### Supported Operations

| Operation                  | Description                             |
| -------------------------- | --------------------------------------- |
| `READ_HOLDING_REGISTERS`   | Read holding registers (FC03)           |
| `READ_INPUT_REGISTERS`     | Read input registers (FC04)             |
| `READ_COILS`               | Read coils (FC01)                       |
| `READ_DISCRETE_INPUTS`     | Read discrete inputs (FC02)             |
| `WRITE_SINGLE_REGISTER`    | Write single holding register (FC06)    |
| `WRITE_MULTIPLE_REGISTERS` | Write multiple holding registers (FC16) |
| `WRITE_SINGLE_COIL`        | Write single coil (FC05)                |
| `WRITE_MULTIPLE_COILS`     | Write multiple coils (FC15)             |

### Query Parameters by Operation

| Operation                  | Parameters                | Example                                                                            |
| -------------------------- | ------------------------- | ---------------------------------------------------------------------------------- |
| `READ_HOLDING_REGISTERS`   | `start_address`, `count`  | `{operation: READ_HOLDING_REGISTERS, start_address: 0, count: 10}`                 |
| `READ_INPUT_REGISTERS`     | `start_address`, `count`  | `{operation: READ_INPUT_REGISTERS, start_address: 0, count: 5}`                    |
| `READ_COILS`               | `start_address`, `count`  | `{operation: READ_COILS, start_address: 0, count: 8}`                              |
| `READ_DISCRETE_INPUTS`     | `start_address`, `count`  | `{operation: READ_DISCRETE_INPUTS, start_address: 0, count: 8}`                    |
| `WRITE_SINGLE_REGISTER`    | `address`, `value`        | `{operation: WRITE_SINGLE_REGISTER, address: 100, value: 42}`                      |
| `WRITE_MULTIPLE_REGISTERS` | `start_address`, `values` | `{operation: WRITE_MULTIPLE_REGISTERS, start_address: 0, values: [100, 200]}`      |
| `WRITE_SINGLE_COIL`        | `address`, `value`        | `{operation: WRITE_SINGLE_COIL, address: 0, value: true}`                          |
| `WRITE_MULTIPLE_COILS`     | `start_address`, `values` | `{operation: WRITE_MULTIPLE_COILS, start_address: 0, values: [true, false, true]}` |

To write a value on demand, publish a message to SOURCE\_TOPIC. The route executes the write and publishes the result to DESTINATION\_TOPIC:

```lot theme={null}
ADD EVENT WriteSetpoint
    WITH SOURCE_TOPIC "modbus/commands/write"
    WITH DESTINATION_TOPIC "modbus/responses/write"
    WITH QUERY "{operation: WRITE_SINGLE_REGISTER, address: 100, value: 42}"
```

***

## Complete Examples

<Tabs>
  <Tab title="Basic Sensor Reading">
    Read temperature and pressure from a Modbus device:

    ```lot theme={null}
    DEFINE ROUTE ProcessSensors WITH TYPE MODBUS_TCP
        ADD MODBUS_CONFIG
            WITH HOST "192.168.1.100"
            WITH PORT 502
            WITH SLAVE_ID 1
        ADD MAPPING Sensors
            WITH EVERY 1 SECOND
            ADD TAG Temperature
                WITH ADDRESS "100"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "FLOAT"
                WITH SOURCE_TOPIC "process/temperature"
                WITH SCALING 0.1
                WITH UNIT "°C"
            ADD TAG Pressure
                WITH ADDRESS "102"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "FLOAT"
                WITH SOURCE_TOPIC "process/pressure"
                WITH UNIT "bar"
    ```
  </Tab>

  <Tab title="Multiple Polling Rates">
    Group TAGs by update frequency:

    ```lot theme={null}
    DEFINE ROUTE PLCConnection WITH TYPE MODBUS_TCP
        ADD MODBUS_CONFIG
            WITH HOST "192.168.1.50"
            WITH PORT 502
            WITH SLAVE_ID 1
        
        ADD MAPPING FastLoop
            WITH EVERY 100 MILLISECONDS
            ADD TAG MotorSpeed
                WITH ADDRESS "200"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "UINT16"
                WITH SOURCE_TOPIC "plc/motor/speed"
            ADD TAG MotorCurrent
                WITH ADDRESS "201"
                WITH ADDRESS_TYPE "INPUT_REGISTER"
                WITH DATA_TYPE "FLOAT"
                WITH SOURCE_TOPIC "plc/motor/current"
        
        ADD MAPPING SlowLoop
            WITH EVERY 5 SECONDS
            ADD TAG TotalRuntime
                WITH ADDRESS "300"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "UINT32"
                WITH SOURCE_TOPIC "plc/stats/runtime"
            ADD TAG FaultCode
                WITH ADDRESS "400"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "UINT16"
                WITH SOURCE_TOPIC "plc/status/fault"
    ```
  </Tab>

  <Tab title="Bidirectional Control">
    Read values and write setpoints:

    ```lot theme={null}
    DEFINE ROUTE ControlSystem WITH TYPE MODBUS_TCP
        ADD MODBUS_CONFIG
            WITH HOST "192.168.1.100"
            WITH PORT 502
            WITH SLAVE_ID 1
        ADD MAPPING ControlLoop
            WITH EVERY 500 MILLISECONDS
            ADD TAG ProcessValue
                WITH ADDRESS "100"
                WITH ADDRESS_TYPE "INPUT_REGISTER"
                WITH DATA_TYPE "FLOAT"
                WITH SOURCE_TOPIC "control/pv"
                WITH PUBLISH_MODE "JSON"
            ADD TAG Setpoint
                WITH ADDRESS "200"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "FLOAT"
                WITH SOURCE_TOPIC "control/sp"
                WITH WRITABLE "true"
                WITH DESTINATION_TOPIC "control/sp/set"
                WITH MIN_VALUE 0
                WITH MAX_VALUE 100
            ADD TAG OutputValue
                WITH ADDRESS "300"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "FLOAT"
                WITH SOURCE_TOPIC "control/output"
    ```
  </Tab>

  <Tab title="Coils and Digital I/O">
    Read digital inputs and control outputs:

    ```lot theme={null}
    DEFINE ROUTE DigitalIO WITH TYPE MODBUS_TCP
        ADD MODBUS_CONFIG
            WITH HOST "192.168.1.75"
            WITH PORT 502
            WITH SLAVE_ID 1
        ADD MAPPING DigitalInputs
            WITH EVERY 100 MILLISECONDS
            ADD TAG StartButton
                WITH ADDRESS "0"
                WITH ADDRESS_TYPE "DISCRETE_INPUT"
                WITH DATA_TYPE "BOOL"
                WITH SOURCE_TOPIC "io/inputs/start"
            ADD TAG StopButton
                WITH ADDRESS "1"
                WITH ADDRESS_TYPE "DISCRETE_INPUT"
                WITH DATA_TYPE "BOOL"
                WITH SOURCE_TOPIC "io/inputs/stop"
            ADD TAG RunningLight
                WITH ADDRESS "0"
                WITH ADDRESS_TYPE "COIL"
                WITH DATA_TYPE "BOOL"
                WITH SOURCE_TOPIC "io/outputs/running"
                WITH WRITABLE "true"
                WITH DESTINATION_TOPIC "io/outputs/running/set"
    ```
  </Tab>

  <Tab title="Combined (Cyclic + On-Demand)">
    Continuous monitoring with on-demand read and write events in the same route:

    ```lot theme={null}
    DEFINE ROUTE FullModbusSetup WITH TYPE MODBUS_TCP
        ADD MODBUS_CONFIG
            WITH HOST "192.168.1.100"
            WITH PORT 502
            WITH SLAVE_ID 1
        
        ADD MAPPING ProcessSensors
            WITH EVERY 500 MILLISECONDS
            ADD TAG Temperature
                WITH ADDRESS "100"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "FLOAT"
                WITH SOURCE_TOPIC "process/temperature"
                WITH UNIT "°C"
            ADD TAG Pressure
                WITH ADDRESS "102"
                WITH ADDRESS_TYPE "HOLDING_REGISTER"
                WITH DATA_TYPE "FLOAT"
                WITH SOURCE_TOPIC "process/pressure"
                WITH UNIT "bar"
        
        ADD EVENT ReadRegisters
            WITH SOURCE_TOPIC "modbus/commands/read"
            WITH DESTINATION_TOPIC "modbus/responses/read"
            WITH QUERY "{operation: READ_HOLDING_REGISTERS, start_address: 0, count: 10}"
        
        ADD EVENT WriteSetpoint
            WITH SOURCE_TOPIC "modbus/commands/write"
            WITH DESTINATION_TOPIC "modbus/responses/write"
            WITH QUERY "{operation: WRITE_SINGLE_REGISTER, address: 200, value: 0}"
    ```

    To trigger the on-demand read, publish any message to `modbus/commands/read`. The route reads registers 0-9 and publishes the result to `modbus/responses/read`. To write, publish to `modbus/commands/write`.
  </Tab>
</Tabs>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Connection Timeout">
    * Verify IP address and port are correct
    * Check firewall settings (port 502 must be open)
    * Ensure device is powered on and connected to network
    * Try increasing `CONNECTION_TIMEOUT`
  </Accordion>

  <Accordion title="Incorrect Values">
    * Verify `ADDRESS` matches device documentation
    * Check `BYTE_ORDER` and `WORD_ORDER` settings
    * Confirm `DATA_TYPE` matches register size
    * Verify `SCALING` and `OFFSET` calculations
  </Accordion>

  <Accordion title="Write Operations Fail">
    * Ensure `WRITABLE` is set to "true"
    * Verify `ADDRESS_TYPE` is `HOLDING_REGISTER` or `COIL`
    * Check device permissions for write operations
    * Verify value is within `MIN_VALUE` and `MAX_VALUE`
  </Accordion>

  <Accordion title="Slave ID Errors">
    * Confirm correct `SLAVE_ID` (usually 1 for TCP devices)
    * Some gateways use different slave IDs for different devices
    * Check device documentation for correct unit ID
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Modbus Serial" icon="plug" href="./modbus-serial">
    Connect to RS-232/RS-485 Modbus RTU devices.
  </Card>

  <Card title="Industrial Overview" icon="industry" href="./overview">
    Learn common patterns for all industrial routes.
  </Card>
</CardGroup>
