Skip to content
Get started

Modbus Component

The Modbus protocol is used by many consumer and industrial devices for communication. This component allows components in ESPHome to communicate to those devices via RTU protocol. You can access the coils, discrete inputs, holding registers and input registers from your devices as sensors, switches, selects, numbers or various other ESPHome components and present them to your favorite Home Automation system. You can even write them as binary or float outputs from ESPHome.

NOTE

This page is the starting point for everything Modbus in ESPHome, including hardware setup. See Modbus Controller for polling registers/coils into sensors, Modbus Client for ad-hoc requests, and Modbus Server for serving registers to another device.

The various sub-components implement some of the Modbus functions below (depending on their required functionality):

Function CodeDescription
1Read Coil Status
2Read Discrete input Status
3Read Holding Registers
4Read Input Registers
5Write Single Coil
6Write Single Register
15Write Multiple Coils
16Write Multiple Registers
23Read/Write Multiple Registers

Function code 23 (0x17, Read/Write Multiple Registers) is available in both roles - as a client action (modbus_client.read_write_multiple_registers) and as a server handler (Read/Write Multiple Registers).

Modbus RTU requires a UART Bus to communicate.

# Example configuration entry
uart:
...
modbus:
id: my_client_hub
on_...:
then:
- lambda: id(my_client_hub).queue_pdu(0x07, modbus::helpers::create_write_single_register_pdu(0x05, 0xBEEF));
  • id (Optional, ID): Manually specify the ID used for code generation.

  • uart_id (Optional, ID): Manually specify the ID of the UART Component if you want to use multiple UART buses.

  • flow_control_pin (Optional, Pin): The pin used to switch flow control. This is useful for RS485 transceivers that do not have automatic flow control switching, like the common MAX485. If your UART supports flow_control_pin, you should configure this in the uart component, not in the modbus component.

  • send_wait_time (Optional, Time): Time in milliseconds before the next ModBUS command is sent when an answer from a previous command has not yet started (i.e. when to timeout and assume no response is coming). Defaults to 2000 ms. Set this value to the maximum time required for the slowest device on the bus to begin responding (time to first byte). If a device starts responding within this time, the next command will be queued and sent after the response is finished, no matter how long the response. This option is only valid when the role is client.

  • turnaround_time (Optional, Time): Time in milliseconds before the next ModBUS command is sent after last response is received. This interval allows other devices on the bus time to process messages. Defaults to 600 ms. This value is ideally set to the maximum time required for the slowest device on the bus to process a message and be ready to process another. Note that all devices will receive all messages and will require time to process them even if they don’t need to reply. If devices don’t respond sometimes, it can help to increase this value. This option is only valid when the role is client.

  • role (Optional, string): The role of this component, client or server. Defaults to client.

The default settings are aimed to provide high stability (low error rate) for a wide variety of devices. If you are pushing for high throughput performance (i.e. lots of transmissions per minute) you will need to change the defaults.

The most impactful way to increase throughput is to reduce turnaround_time. If all your devices are able to process messages quickly without missing any, it’s safe to reduce this number, potentially all the way to 0. If you see devices failing to respond or responding when they shouldn’t (talking over each other) then you need to increase it.

Reducing send_wait_time generally does not impact performance at all, unless you have devices that are physically offline. When all devices are responding, send_wait_time does nothing.

Modbus RTU requires a silent gap between frames. Above 19200 baud the specification fixes that gap at 1.75 ms, which is far longer than the frames themselves, so at high baud rates the bus spends most of its time waiting. The component waits out the remainder of that gap before it transmits, and the last part of the wait is spent busy-waiting so the gap is met precisely.

On ESP32, increasing rx_timeout on the UART Component moves more of that waiting into the UART hardware. A larger value means the UART waits longer after the last byte before it declares a frame complete and hands it over, so the component is notified later and has less of the gap left to wait out itself. Throughput is unaffected, because the gap is measured from the last byte on the wire rather than from the moment the frame was handed over.

rx_timeout is counted in bytes, not in time: one unit is however long a byte takes to send at your baud rate, which is roughly ten bit times. At 115200 baud a byte is about 87 us, so the default of 2 is about 174 us and the 1.75 ms gap is about twenty.

On a bus running at 115200 baud, raising rx_timeout from the default of 2 to 15 can reduce the time the modbus component spends in each loop from around 1.67 ms to 0.43 ms, without affecting throughput. The CPU freed this way is available to WiFi and the rest of your configuration.

The saving is per transaction, so it only adds up if you are already pushing throughput: turnaround_time reduced to 0 and devices polled as fast as they will answer. With the default turnaround_time the bus is idle most of the time and there is little to reclaim.

There is a limit: if the value is so large that the frame is handed over too late for the component to process it and transmit within the remaining gap, throughput starts to fall. Staying under about three quarters of the gap leaves enough room, which is around 15 at 115200 baud. At 19200 baud and below the gap scales with the baud rate rather than being fixed, so it is only 3.5 bytes wide and the default of 2 already covers more than half of it: there is little left to reclaim and no room to raise it.

WARNING

A large rx_timeout also means the UART waits longer before deciding that a frame has ended, so anything arriving in that window is handed over as a single block. That makes it harder to recover cleanly from line noise, and setting it too large may lead to problems parsing frames; reduce it again if you see them.

You need an RS485 transceiver module:

See How is this RS485 module working? on stackexchange for more details.

The transceiver connects to the UART of the MCU. For ESP32, pin 16 to TXD and pin 17 to RXD are the default ones but any other pins can be used as well. 3.3V to VCC and naturally GND to GND.

On the bus side, you need 120 Ohm termination resistors at the ends of the bus cable as per Modbus standard. Some transceivers have this already soldered onboard, while some server devices may have them available via a jumper or a DIP switch.

NOTE

If you are using an ESP8266, serial logging may cause problems reading from UART. For best results, hardware serial is recommended. Software serial may not be able to read all received data if other components spend a lot of time in the loop().

For hardware serial only a limited set of pins can be used. Either tx_pin: GPIO1 and rx_pin: GPIO3 or tx_pin: GPIO15 and rx_pin: GPIO13.

The disadvantage of using the hardware UART is that you can’t use serial logging because the serial logs would be sent to the Modbus device(s) instead, causing errors.

Serial logging can be disabled by setting baud_rate: 0.

See Logger for more details

logger:
level: <level>
baud_rate: 0