Modbus Adapter

Connect Conduit to Modbus TCP and RTU devices - PLCs, sensors, and field instruments.

Modbus Adapter

The Modbus adapter connects Conduit to Modbus TCP and Modbus RTU devices, enabling data collection from PLCs, sensors, meters, and other field instruments.

Overview

The Modbus adapter supports:

  • Modbus TCP: Ethernet-based Modbus communication
  • Modbus RTU over TCP: Serial encapsulation over TCP
  • All Register Types: Coils, discrete inputs, holding registers, input registers
  • Multiple Data Types: Integer, float, string, BCD
  • Polling: Configurable intervals per register group

Prerequisites

  1. Network Access: TCP connectivity to Modbus device
  2. Device Documentation: Register map for your device
  3. Unit ID: Modbus unit/slave address

Configuration

Basic TCP Configuration

adapter:
  type: modbus
  name: modbus-plc-1

  connection:
    type: tcp
    host: 192.168.1.50
    port: 502
    unitId: 1

  polling:
    defaultInterval: 1000  # ms

RTU over TCP Configuration

adapter:
  type: modbus
  name: modbus-rtu-gateway

  connection:
    type: rtu-tcp
    host: 192.168.1.60
    port: 4001
    unitId: 5

Register Configuration

Register Types

| Code | Type | Access | Address Range | |------|------|--------|---------------| | 0 | Coils | Read/Write | 00001-09999 | | 1 | Discrete Inputs | Read Only | 10001-19999 | | 3 | Input Registers | Read Only | 30001-39999 | | 4 | Holding Registers | Read/Write | 40001-49999 |

Defining Registers

registers:
  - name: Tank1_Temperature
    address: 40001
    type: holding
    dataType: float32
    byteOrder: big-endian
    unit: "°F"
    description: "Tank 1 Process Temperature"

  - name: Tank1_Level
    address: 40003
    type: holding
    dataType: uint16
    scale: 0.1
    offset: 0
    unit: "%"

  - name: Pump1_Running
    address: 00001
    type: coil
    dataType: bool

  - name: Alarm_Status
    address: 10001
    type: discrete
    dataType: bool

Data Types

| Type | Registers | Description | |------|-----------|-------------| | bool | 1 | Boolean (coils/discrete) | | int16 | 1 | Signed 16-bit integer | | uint16 | 1 | Unsigned 16-bit integer | | int32 | 2 | Signed 32-bit integer | | uint32 | 2 | Unsigned 32-bit integer | | float32 | 2 | 32-bit float (IEEE 754) | | float64 | 4 | 64-bit float (IEEE 754) | | string | N | ASCII string | | bcd | N | Binary-coded decimal |

Byte Order

Configure byte ordering for multi-register values:

registers:
  - name: Temperature
    address: 40001
    dataType: float32
    byteOrder: big-endian      # AB CD (default)
    # byteOrder: little-endian # CD AB
    # byteOrder: mid-big       # BA DC
    # byteOrder: mid-little    # DC BA

Polling Configuration

Group-Based Polling

pollGroups:
  - name: fast
    interval: 100  # ms
    registers:
      - Tank1_Temperature
      - Tank1_Pressure
      - Pump1_Running

  - name: normal
    interval: 1000
    registers:
      - Tank1_Level
      - Flow_Rate

  - name: slow
    interval: 10000
    registers:
      - Daily_Production
      - Runtime_Hours

Contiguous Read Optimization

optimization:
  coalesceReads: true
  maxGap: 10  # Combine reads up to 10 register gap

Scaling & Engineering Units

Linear Scaling

registers:
  - name: Pressure
    address: 40010
    dataType: uint16
    rawMin: 0
    rawMax: 65535
    engMin: 0
    engMax: 100
    unit: "PSI"

Formula: engValue = (rawValue - rawMin) / (rawMax - rawMin) * (engMax - engMin) + engMin

Custom Expression

registers:
  - name: Temperature_C
    address: 40020
    dataType: int16
    expression: "value * 0.1 - 40"
    unit: "°C"

Connection Management

Reconnection

connection:
  reconnect:
    enabled: true
    interval: 5000    # ms
    maxAttempts: -1   # Infinite

  timeout:
    connect: 5000     # ms
    read: 3000
    write: 3000

Connection Pooling

connection:
  pool:
    enabled: true
    size: 3
    waitTimeout: 10000

Quality Handling

Communication Errors

quality:
  onError: stale    # Mark as stale on comm error
  staleTimeout: 30  # seconds before marking bad
  substitution:
    enabled: false
    value: 0

Value Range Validation

registers:
  - name: Temperature
    address: 40001
    dataType: float32
    validation:
      min: -40
      max: 200
      onInvalid: bad  # Mark quality as bad

Writing Values

Enable Writes

writes:
  enabled: true
  confirmation: true  # Read back after write
  timeout: 5000

Write Protection

writes:
  protected:
    - Pump1_Running  # Require explicit unlock
    - Setpoint_*

  locks:
    timeout: 60  # Auto-unlock after 60s

Multiple Devices

Device Groups

adapter:
  type: modbus
  name: modbus-building-1

devices:
  - name: chiller-1
    host: 192.168.1.50
    port: 502
    unitId: 1
    registers: *chiller-registers

  - name: chiller-2
    host: 192.168.1.51
    port: 502
    unitId: 1
    registers: *chiller-registers

registerTemplates:
  chiller-registers: &chiller-registers
    - name: Supply_Temp
      address: 40001
      dataType: float32
    - name: Return_Temp
      address: 40003
      dataType: float32

Troubleshooting

Connection Issues

Connection Refused

  • Verify IP and port
  • Check firewall rules
  • Confirm device is powered on

Timeout

  • Reduce timeout values
  • Check network latency
  • Verify unit ID is correct

Data Issues

Incorrect Values

  • Check byte order configuration
  • Verify data type matches device
  • Check scaling configuration

All Zeros

  • Register address might be wrong
  • Check register type (holding vs input)
  • Some devices use 0-based addressing

Performance Issues

High Latency

  • Enable contiguous read optimization
  • Reduce polling frequency for slow-changing values
  • Use multiple poll groups

Example: VFD (Variable Frequency Drive)

adapter:
  type: modbus
  name: vfd-pump-room

  connection:
    type: tcp
    host: 192.168.1.100
    port: 502
    unitId: 1

  registers:
    - name: VFD1_Speed_Hz
      address: 40001
      dataType: uint16
      scale: 0.01
      unit: "Hz"

    - name: VFD1_Current_A
      address: 40002
      dataType: uint16
      scale: 0.01
      unit: "A"

    - name: VFD1_Power_kW
      address: 40003
      dataType: uint16
      scale: 0.1
      unit: "kW"

    - name: VFD1_Running
      address: 40010
      dataType: uint16
      expression: "(value & 0x01) != 0"

    - name: VFD1_Fault
      address: 40010
      dataType: uint16
      expression: "(value & 0x02) != 0"

  pollGroups:
    - name: status
      interval: 500
      registers: [VFD1_Running, VFD1_Fault]

    - name: values
      interval: 2000
      registers: [VFD1_Speed_Hz, VFD1_Current_A, VFD1_Power_kW]

Next Steps