ESPHome + ESP32 as Modbus RTU Slave — Control Relay via Coil Write

:wrench: Setup

  • ESP32 with RS485 (MAX485)
  • Relay on GPIO19
  • ESPHome version: 2025.3.3
  • UART:
    • TX: GPIO17
    • RX: GPIO16
  • Modbus Master: Home Assistant OS (running on Intel NUC)

:dart: Goal

Make the ESP32 act as a Modbus RTU slave, and toggle a relay on GPIO19 when the master writes to a holding register (e.g. register 4 = 1 → turn on relay).


:x: Problem

ESPHome does not natively support reacting to Modbus coil or holding register writes when in server mode.


:question: What I’ve tried

  • modbus_controller + coil_states → only works in client mode
  • switch.modbus_controller → doesn’t exist
  • switch.uart → only transmits data, doesn’t react

:mag: Possibly helpful

This custom component looks promising:

:point_right: epiclabs-uc/esphome-modbus-server


:pushpin: Question

Has anyone successfully made ESPHome act as a Modbus RTU slave that can toggle GPIO pins based on register/coil writes?

Would appreciate any working example, custom component, or alternative solution :pray:


:brain: Current Configuration

:electric_plug: Master (Home Assistant OS)

yaml

KopierenBearbeiten

modbus:
  - name: modbus1
    type: serial
    method: rtu
    port: /dev/ttyUSB0
    baudrate: 9600
    stopbits: 1
    bytesize: 8
    parity: N
    timeout: 3

switch:
  - platform: modbus_controller
    modbus_controller_id: modbus1
    name: "Relay Control"
    slave: 1
    address: 4
    write_type: holding
    command_on: 1
    command_off: 0
    scan_interval: 1

:brick: Slave (ESPHome on ESP32)

yaml

KopierenBearbeiten

uart:
  id: uart_modbus
  tx_pin: GPIO17
  rx_pin: GPIO16
  baud_rate: 9600
  stop_bits: 1
  parity: NONE

modbus:
  id: modbus1
  uart_id: uart_modbus
  role: server

switch:
  - platform: gpio
    id: relay_light1
    name: "Light"
    pin: 18
    restore_mode: ALWAYS_OFF

sensor:
  - platform: modbus_controller
    modbus_controller_id: slave1
    id: relay_register
    name: "Relay Register"
    register_type: holding
    address: 0x4
    value_type: U_WORD
    on_value:
      then:
        - lambda: |-
            if (x == 1) {
              id(relay_light1).turn_on();
            } else {
              id(relay_light1).turn_off();
            }

modbus_controller:
  - modbus_id: modbus1
    id: slave1
    address: 0x1

Hello Rudi,
Thanks for coming here and asking a question.
Would you be so kind as to adjusting the format of your code so that we can read it properly & check the YAML spacing, etc. Editing your original is the preferred way. It is very hard for us to tell what is what when the text formatter jumbles everything like that.
Use the </> button or this:
Here is an example of how to fix it from the site FAQ Page.
How to help us help you - or How to ask a good question.

Hello thank you for your respond.
I will mod it when i am at home.

I’m only searching for the feature to set a pin on a slave ESP32 board to high or low.
To control a relay.

Everything works fine i can read sensors over Modbus in HOA can create entities with it.

But i don’t find the missing piece to give my slave a output command via Master.

Is that code from AI or you wrote it?
Just to understand why the slave code is like it is…

Hello,

the code is from me, i tried maybe 10 different ways i read every google page to page 20 in Google results in 4 different languages.

Watched every Youtube video an read the whole article about modbus in ESPhome Homepage.

The code in my post where only the last attempt.

I tried so many things in the slave config.
in the server_register tried UART switch, holding, coil registers. digital switches.

I am new to the topic, and there is no problem to read data from switches or motionsensors etc.
works fine. Therefor i know my wiring, soldering, and network stat are ok. i even can use it in HOA als entities, but the switch to put a Relay on pin 19 (tried other to) on high/low don’t work.

i played with baudrate, timeout of the master or my last attempt where to try a few possible lambda variations in the slave config (that’s the reason why the value x is x==1.

Its so a simple task but i miss this little detail how i get it to work.

it will be a simple setup:
Intel Nuk with HOA OS as master and a ESP32 with ESPhome as slave.
The Slave/s are daisychined with a KNX cable 120R on the end.
And then there are switches and motion sensors and on other boards relays.

The modbus_controller part in the slave config where left behind from the last attempt.

Has nobody somekind of Idea? :frowning:

Chances are low… Try on Discord.
To be sure I didn’t misunderstand:
You want esphome slave with write register?

Hello,

yeah you understand right.

I figured it out myself post tonight the answer to the Problem.

Little bit tricky but with a little c++ moding in esphome config no problem.

Share your solution here.
I just recently saw another forum member looking for help for some slave setup.

@Karosm and @RudlDudl I have been looking into this myself. It doesn’t seem like this is currently supported in the modbus_controller built into esphome.

I would love to be proven wrong, but my current investigation had led me to these conclusions:

The server / slave support is limited to READ_HOLDING_REGISTERS = 0x03 and READ_INPUT_REGISTERS = 0x04

The relevant sections of the code are in parse_modbus_byte_ function in modbus.cpp

      } else if (this->role == ModbusRole::SERVER && (function_code == 0x3 || function_code == 0x4)) {
         device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8),
                                          uint16_t(data[3]) | (uint16_t(data[2]) << 8));
       } else {
         device->on_modbus_data(data);
       }

and

the on_modbus_data function in modbus_controller.cpp

It seems that to add support for 0x05 Write Coil command there are two options:

  1. Add a new function to the modbus_controller API and insert it before the else clause that defaults to the handling the incoming MODBUS command with the on_modbus_data function in the parse_modbus_byte_ function in modbus.cpp

  2. Inherit from modbus_controller and override the on_modbus_data function which has not been marked final.

I have zero experience with the esphome code base besides investigating this issue. I have no idea how to properly integrate any changes to API into the yaml parsing front end.

You may be better off using some sort of external component. I have seen one more possibility beside the link posted by @rudldudl, but have not tested it.

It may be worth opening an feature request issue for write coil support. It seems like a useful feature and I don’t believe that there is any currently exposed method to do this in the current esphome code base.

@Karosm and @RudlDudl there is a current PR#8577 which adds 0x5 Write Coil Support. Write coils may be added to the existing modbus_controller component with the option server_coil_register.

See esphome-doc PR#4825 for an example showing how to use the write_lambda to control a relay. A link to the deploy preview of the updated docs are provided below.