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.
| Layer | Client entity | Server entity | Role |
|---|---|---|---|
| Controller | ModbusController | ModbusTcpServerController | Manages connection lifecycle and timeouts |
| Endpoint | ModbusTcpEndpoint / ModbusRtuEndpoint | ModbusTcpServerEndpoint | Transport parameters (address, port, serial settings, TLS) |
| Mapper | ModbusMapper | ModbusServerMapper | Binds 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.
| Code | Name | Register type | Direction |
|---|---|---|---|
| FC 01 | Read Coils | Coil | Read |
| FC 02 | Read Discrete Inputs | DiscreteInput | Read |
| FC 03 | Read Holding Registers | HoldingRegister | Read |
| FC 04 | Read Input Registers | InputRegister | Read |
| FC 05 | Write Single Coil | Coil | Write |
| FC 06 | Write Single Register | HoldingRegister | Write |
| FC 15 | Write Multiple Coils | Coil | Write |
| FC 16 | Write Multiple Registers | HoldingRegister | Write |
Register types
| Register type | Width | Access | Maps to |
|---|---|---|---|
| Coil | 1 bit | Read / write | Bool |
| DiscreteInput | 1 bit | Read-only | Bool |
| InputRegister | 16 bit | Read-only | Int / Float / Blob |
| HoldingRegister | 16 bit | Read / write | Int / 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.
| Field | Default | Description |
|---|---|---|
| Host | 127.0.0.1 | Remote Modbus server address |
| Port | 502 | TCP port of the remote server |
| UnitId | 1 | Modbus unit / slave ID for the target device |
| TlsEnabled | false | Encrypt the connection with TLS |
| Certificate | (none) | Certificate used for TLS verification |
| ConnectionTimeoutMs | 30000 | Timeout for establishing the connection |
| OperationTimeoutMs | 10000 | Timeout for an individual read or write |
| Logging | false | Log 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.
| Field | Default | Options |
|---|---|---|
| SerialPort | /dev/ttyUSB0 | OS serial device path |
| UnitId | 1 | Slave ID on the RS-485 bus |
| BaudRate | 9600 | e.g. 9600, 19200, 38400, 115200 |
| DataBits | 8 | Number of data bits |
| Parity | None | None, Odd, Even |
| StopBits | One | One, Two |
| ConnectionTimeoutMs | 30000 | Connection establishment timeout |
| OperationTimeoutMs | 10000 | Per-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.
| Field | Default | Description |
|---|---|---|
| Direction | Read | Read (poll device) or Write (push to device) |
| RegisterType | (required) | Coil, DiscreteInput, InputRegister, HoldingRegister |
| Address | 0 | Starting register address |
| RegisterCount | 1 | Number of 16-bit registers per value |
| ByteOrder | BigEndian | BigEndian or LittleEndian |
| WordOrder | HighWordFirst | HighWordFirst or LowWordFirst |
| PollIntervalMs | 1000 | How often to poll, in milliseconds |
| TargetEntity / TargetField | (required) | Entity and field that receive the value |
| Disabled | false | Temporarily 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.
BigEndianis the Modbus standard. - WordOrder controls how the 16-bit registers are combined.
HighWordFirstis standard;LowWordFirstswaps 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 valueServer 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.
| Field | Default | Description |
|---|---|---|
| ListenAddress | 0.0.0.0 | Interface the server binds to |
| ListenPort | 502 | TCP port to listen on |
| UnitId | 1 | Unit ID the server responds as |
| TlsEnabled | false | Require TLS for incoming connections |
| Certificate | (none) | Server certificate for TLS |
| Logging | false | Log 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.fieldServer 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
- Create a
ModbusTcpEndpointorModbusRtuEndpointwith the device's connection details. - Create a
ModbusControllerthat references the endpoint (TCP vs RTU is auto-detected). - Add
ModbusMapperentities specifying register type, address, count, byte/word order, poll interval, direction, and target field. - The controller connects at startup; mappers poll their registers and update target values.
- For server mode, create a
ModbusTcpServerEndpoint, aModbusTcpServerController, andModbusServerMapperentities for each exposed field. - 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.