Modbus TCP/RTU Guide

Connect PLCs, drives, and sensors to ControlBird over Modbus.

Overview

ControlBird speaks Modbus in both directions. As a client, it polls PLCs, variable-frequency drives, power meters, and sensors and writes their register values straight into Store entities. As a server, it exposes selected entity fields at fixed register addresses so external SCADA systems and Modbus masters can read and write them. Both TCP and RTU (serial) transports are supported on the client side, and TCP is supported on the server side.

Every part of a Modbus integration is created and monitored through the Device Manager app in the Platform UI. There are no manual configuration files to edit. For a hands-on introduction, see walkthrough step 7: Connect a Device, and for securing TCP connections see the Certificates guide.

Architecture

A Modbus integration is assembled from three layers of entities. The controller manages the connection lifecycle, the endpoint carries the transport parameters, and the mappers bind individual registers to entity fields.

LayerClient entityServer entityRole
ControllerModbusControllerModbusTcpServerControllerManages connection lifecycle and timeouts
EndpointModbusTcpEndpoint / ModbusRtuEndpointModbusTcpServerEndpointTransport parameters (address, port, serial settings, TLS)
MapperModbusMapperModbusServerMapperBinds a register range to a target entity field

The client-side ModbusController uses TCP or RTU based on the endpoint type it references. Both the TCP and RTU endpoints share common settings: logging, connection health monitoring, and TLS.

Supported Function Codes

ControlBird implements the standard Modbus function codes for reading and writing the four register types. Function codes are selected automatically from the mapper's RegisterType and Direction.

CodeNameRegister typeDirection
FC 01Read CoilsCoilRead
FC 02Read Discrete InputsDiscreteInputRead
FC 03Read Holding RegistersHoldingRegisterRead
FC 04Read Input RegistersInputRegisterRead
FC 05Write Single CoilCoilWrite
FC 06Write Single RegisterHoldingRegisterWrite
FC 15Write Multiple CoilsCoilWrite
FC 16Write Multiple RegistersHoldingRegisterWrite

Register types

Register typeWidthAccessMaps to
Coil1 bitRead / writeBool
DiscreteInput1 bitRead-onlyBool
InputRegister16 bitRead-onlyInt / Float / Blob
HoldingRegister16 bitRead / writeInt / Float / Blob

Coils are boolean only

Coils and discrete inputs are 1-bit values and can only be mapped to Bool entity fields. To carry numeric data, use input or holding registers.

Client Configuration (TCP)

To poll a device over Modbus TCP, create a ModbusTcpEndpoint that points at the remote server, then attach it to a ModbusController.

FieldDefaultDescription
Host127.0.0.1Remote Modbus server address
Port502TCP port of the remote server
UnitId1Modbus unit / slave ID for the target device
TlsEnabledfalseEncrypt the connection with TLS
Certificate(none)Certificate used for TLS verification
ConnectionTimeoutMs30000Timeout for establishing the connection
OperationTimeoutMs10000Timeout for an individual read or write
LoggingfalseLog all TX/RX bytes for troubleshooting

Client Configuration (RTU / Serial)

For serial devices, create a ModbusRtuEndpoint instead. A ModbusController referencing it will automatically use the RTU transport. The serial parameters must match the device exactly.

FieldDefaultOptions
SerialPort/dev/ttyUSB0OS serial device path
UnitId1Slave ID on the RS-485 bus
BaudRate9600e.g. 9600, 19200, 38400, 115200
DataBits8Number of data bits
ParityNoneNone, Odd, Even
StopBitsOneOne, Two
ConnectionTimeoutMs30000Connection establishment timeout
OperationTimeoutMs10000Per-operation timeout

Addressing multiple devices on one bus

On an RS-485 multidrop bus, each physical device has a distinct unit ID. Set the matching UnitId on the endpoint to target a specific slave. RTU mode is client-only; ControlBird does not implement an RTU server.

Mapping Registers

A ModbusMapper binds a register range on the device to a field on a target entity. The controller connects at startup, then each mapper polls its registers at the configured interval and writes the decoded value into the Store.

FieldDefaultDescription
DirectionReadRead (poll device) or Write (push to device)
RegisterType(required)Coil, DiscreteInput, InputRegister, HoldingRegister
Address0Starting register address
RegisterCount1Number of 16-bit registers per value
ByteOrderBigEndianBigEndian or LittleEndian
WordOrderHighWordFirstHighWordFirst or LowWordFirst
PollIntervalMs1000How often to poll, in milliseconds
TargetEntity / TargetField(required)Entity and field that receive the value
DisabledfalseTemporarily disable the mapper

Data types and word order

Register values can be decoded as INT16, UINT16, INT32, UINT32, INT64, FLOAT32, FLOAT64, or BLOB (raw binary). Multi-register values combine consecutive 16-bit registers using RegisterCount. A 32-bit value spans 2 registers; a 64-bit value spans 4.

  • ByteOrder controls the order of bytes within each register. BigEndian is the Modbus standard.
  • WordOrder controls how the 16-bit registers are combined. HighWordFirst is standard; LowWordFirst swaps the 16-bit word pairs for non-standard devices.

Word order only matters for multi-register values

Word order is applied only when RegisterCount is greater than 1. Single-register reads always use the natural byte order of the register.

Example mapper configurations

# 32-bit integer from holding registers 100-101
RegisterType   = HoldingRegister
Address        = 100
RegisterCount  = 2
ByteOrder      = BigEndian
WordOrder      = HighWordFirst
PollIntervalMs = 1000
Direction      = Read

# 32-bit float from input registers 0-1
RegisterType   = InputRegister
Address        = 0
RegisterCount  = 2
ByteOrder      = BigEndian
Direction      = Read   # TargetField is a Float value

# Single coil (on/off) at address 10
RegisterType   = Coil
Address        = 10
RegisterCount  = 1
Direction      = Read   # TargetField is a Bool value

Server Mode

ControlBird can also act as a Modbus TCP server, presenting entity fields to external masters. Create a ModbusTcpServerEndpoint that binds a listen address and port, attach it to a ModbusTcpServerController, then add ModbusServerMapper entities to expose individual fields.

FieldDefaultDescription
ListenAddress0.0.0.0Interface the server binds to
ListenPort502TCP port to listen on
UnitId1Unit ID the server responds as
TlsEnabledfalseRequire TLS for incoming connections
Certificate(none)Server certificate for TLS
LoggingfalseLog all TX/RX bytes

Server mappers use Get and Put directions, which differ from the client's Read and Write. Get services client reads of the field, and Put accepts client writes into the field. Each server mapper defines a StartAddress in the server's address space.

# Expose an entity field as holding register 200
StartAddress  = 200
RegisterType  = HoldingRegister
RegisterCount = 1
Direction     = Get        # serviced when a client reads
SourcePath    = entity.field

Server mode is poll-only

The Modbus server does not push events to clients. External masters must poll the exposed registers; there is no subscription or server-initiated update mechanism.

Setup Workflow

  1. Create a ModbusTcpEndpoint or ModbusRtuEndpoint with the device's connection details.
  2. Create a ModbusController that references the endpoint (TCP vs RTU is auto-detected).
  3. Add ModbusMapper entities specifying register type, address, count, byte/word order, poll interval, direction, and target field.
  4. The controller connects at startup; mappers poll their registers and update target values.
  5. For server mode, create a ModbusTcpServerEndpoint, a ModbusTcpServerController, and ModbusServerMapper entities for each exposed field.
  6. Enable per-endpoint communication logging while bringing a device online, then disable it once stable.

Limitations

  • RTU is supported on the client side only; there is no RTU server.
  • The server is poll-only: no server-initiated pushes or subscriptions.
  • Coils and discrete inputs map to boolean fields only and cannot carry numeric values.
  • Word order applies only to multi-register values (RegisterCount > 1).
  • The default port 502 is below 1024, so binding it on Unix requires elevated privileges.

Troubleshooting

If values look scrambled, the most common cause is mismatched byte or word order: try toggling WordOrder to LowWordFirst for 32/64-bit values. Enable endpoint logging to inspect the raw TX/RX bytes, and confirm the UnitId matches the target device.