From 07f37e12cb41256487d4bf7c30132a4e0ea452e2 Mon Sep 17 00:00:00 2001 From: Bonne Eggleston Date: Sun, 14 Sep 2025 15:21:17 -0700 Subject: [PATCH 1/6] Add notes for turnaround_time, and clarify disable_crc, flow_control_pin --- content/components/modbus.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/content/components/modbus.md b/content/components/modbus.md index b7beff5b4c..39f23a406b 100644 --- a/content/components/modbus.md +++ b/content/components/modbus.md @@ -43,13 +43,17 @@ modbus: - **flow_control_pin** (*Optional*, [Pin](#config-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. + like the common MAX485. If your UART support `flow_control_pin` you should preferentially set it the `uart` component over the `mobus` component. - **send_wait_time** (*Optional*, [Time](#config-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 250 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. -- **disable_crc** (*Optional*, boolean): Ignores a bad CRC if set to `true`. Defaults to `false` +- **turnaround_time** (*Optional*, [Time](#config-time)): Time in milliseconds before the next ModBUS command is sent after last response is received (i.e. how long to allow all devices to process messages on the bus). Defaults to 100 ms. + Set this value 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 hear all messages, so will need to process them even if they don't need to reply. + If devices don't respond sometimes, it can help to increase this value. + +- **disable_crc** (*Optional*, boolean): Ignores a bad CRC if set to `true`. This will reduce error messages, but generally never help fix communication issues. Increasing times above first if you see CRC errors. Defaults to `false` - **role** (*Optional*, string): The role of this component, `client` or `server`. Defaults to `client`. From aa414de93a2fa130ca07d1eae4b530e6d33fdf76 Mon Sep 17 00:00:00 2001 From: Bonne Eggleston Date: Sun, 14 Sep 2025 15:29:37 -0700 Subject: [PATCH 2/6] Fix typo --- content/components/modbus.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/components/modbus.md b/content/components/modbus.md index 39f23a406b..fe0b772e86 100644 --- a/content/components/modbus.md +++ b/content/components/modbus.md @@ -43,7 +43,7 @@ modbus: - **flow_control_pin** (*Optional*, [Pin](#config-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 support `flow_control_pin` you should preferentially set it the `uart` component over the `mobus` component. + like the common MAX485. If your UART support `flow_control_pin` you should preferentially set it in the `uart` component over the `mobus` component. - **send_wait_time** (*Optional*, [Time](#config-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 250 ms. Set this value to the maximum time required for the slowest device on the bus to begin responding (time to first byte). From 47af9cdde321c790fa6b0ce5b4a7c627cbe6294a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:45:37 +1000 Subject: [PATCH 3/6] [lvgl] Update examples with selected_digit for spinbox (#5630) * [lvgl] Update examples with selected_digit for spinbox * Changes from review --------- Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- content/components/lvgl/widgets.md | 3 +-- content/cookbook/lvgl.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/content/components/lvgl/widgets.md b/content/components/lvgl/widgets.md index 06b441e50b..550bde74b4 100644 --- a/content/components/lvgl/widgets.md +++ b/content/components/lvgl/widgets.md @@ -1538,7 +1538,6 @@ The `slider` can be also integrated as {{< docref "/components/number/lvgl" "Num See [Light brightness slider](/cookbook/lvgl#lvgl-cookbook-bright) and [Media player volume slider](/cookbook/lvgl#lvgl-cookbook-volume) for examples which demonstrate how to use a slider to control entities in Home Assistant. -{{< anchor "lvgl-widget-canvas" >}} {{< anchor "lvgl-widget-spinbox" >}} ## `spinbox` @@ -1589,7 +1588,7 @@ The spinbox contains a numeric value (as text) which can be increased or decreas text_align: center range_from: -10 range_to: 40 - step: 0.5 + selected_digit: 2 digits: 3 decimal_places: 1 diff --git a/content/cookbook/lvgl.md b/content/cookbook/lvgl.md index 0dbaf7b1b9..219982e814 100644 --- a/content/cookbook/lvgl.md +++ b/content/cookbook/lvgl.md @@ -523,7 +523,7 @@ lvgl: width: 50 range_from: 15 range_to: 35 - step: 0.5 + selected_digit: 0 rollover: false digits: 3 decimal_places: 1 From c9bf37932f9581e70a2f6f4e40005a0cb69f70b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:25:02 +1300 Subject: [PATCH 4/6] Bump actions/checkout from 5.0.0 to 5.0.1 (#5638) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-label-pr.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/docker.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index ce7bbd64ec..0e8ec8e17d 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -26,7 +26,7 @@ jobs: if: github.event.action != 'labeled' || github.event.sender.type != 'Bot' steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Generate a token id: generate-token diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93c2653124..112d1dea4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: repo: cloudcannon/pagefind - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Hugo uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0 @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Python 3.12 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c633d435a4..dff43d1803 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,7 +28,7 @@ jobs: repo: cloudcannon/pagefind - name: Checkout source code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 From 597be25a8182ed6802289d6b4b9104275eac1278 Mon Sep 17 00:00:00 2001 From: Bonne Eggleston Date: Tue, 18 Nov 2025 08:12:57 -0800 Subject: [PATCH 5/6] Document splitting out modbus_server from modbus_controller --- content/components/modbus.md | 1 + content/components/modbus_controller.md | 48 +-------- content/components/modbus_server.md | 131 ++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 content/components/modbus_server.md diff --git a/content/components/modbus.md b/content/components/modbus.md index 7d042a265e..03c670f8a4 100644 --- a/content/components/modbus.md +++ b/content/components/modbus.md @@ -61,6 +61,7 @@ modbus: - {{< docref "/components/modbus_controller" >}} - {{< docref "/components/sensor/modbus_controller" >}} +- {{< docref "/components/sensor/modbus_server" >}} - {{< docref "/components/binary_sensor/modbus_controller" >}} - {{< docref "/components/output/modbus_controller" >}} - {{< docref "/components/switch/modbus_controller" >}} diff --git a/content/components/modbus_controller.md b/content/components/modbus_controller.md index c89784dd33..2d183da0de 100644 --- a/content/components/modbus_controller.md +++ b/content/components/modbus_controller.md @@ -7,12 +7,9 @@ params: image: modbus.png --- -The `modbus_controller` component creates a RS485 connection to either: +The `modbus_controller` component creates a RS485 connection to control a Modbus server (slave) device, letting your ESPHome node to act as a Modbus client (master). You can access the coils, inputs, holding, read 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 ouptputs from ESPHome. -- control a Modbus server (slave) device, letting your ESPHome node to act as a Modbus client (master). You can access the coils, inputs, holding, read 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 ouptputs from ESPHome. -- let your ESPHome node act as a Modbus server, allowing a ModBUS client to read data (like sensor values) from your ESPHome node. - -To choose the role, set the `role` attribute of the {{< docref "/components/modbus" >}} upon which this `modbus_controller` component relies. `client` is the default. +Set the `role` attribute of the {{< docref "/components/modbus" >}} upon which this `modbus_controller` component relies to `client`, which is the default. {{< img src="modbus.png" alt="Image" width="25%" class="align-center" >}} @@ -68,44 +65,6 @@ On the bus side, you need 120 Ohm termination resistors at the ends of the bus c - **max_cmd_retries** (*Optional*, integer): How many times a command will be retried if no response is received. It doesn't include the initial transmition. Defaults to 4. -- **server_courtesy_response** (*Optional*): Configuration block to enable the courtesy response feature when the device is acting as a Modbus server. - - - **enabled** (*Optional*, boolean): Whether to enable the courtesy response feature. - Defaults to `false`. - - **register_last_address** (*Optional*, integer): The highest Modbus register address (inclusive) up to which undefined registers are allowed to be read and will be padded with a default value. - Any read request that includes undefined registers within this range will return the value specified by `register_value` instead of triggering an exception. - Defaults to `65535` - - **register_value** (*Optional*, integer): The 16-bit value (range: 0–65535) to return for undefined registers within the address range defined by `register_last_address`. - Defaults to `0`. - -- **server_registers** (*Optional*): A list of registers that are responded to when acting as a server. - - - **address** (**Required**, integer): start address of the first register in a range - - **value_type** (*Optional*): datatype of the mod_bus register data. The default data type for ModBUS is a 16 bit integer in **big endian** format (network byte order, MSB first) - - - `U_WORD` : unsigned 16 bit integer, 1 register, `uint16_t` - - `S_WORD` : signed 16 bit integer, 1 register, `int16_t` - - `U_DWORD` : unsigned 32 bit integer, 2 registers, `uint32_t` - - `S_DWORD` : signed 32 bit integer, 2 registers, `int32_t` - - `U_DWORD_R` : **little endian** unsigned 32 bit integer, 2 registers, `uint32_t` - - `S_DWORD_R` : **little endian** signed 32 bit integer, 2 registers, `int32_t` - - `U_QWORD` : unsigned 64 bit integer, 4 registers, `uint64_t` - - `S_QWORD` : signed 64 bit integer, 4 registers `int64_t` - - `U_QWORD_R` : **little endian** unsigned 64 bit integer, 4 registers, `uint64_t` - - `S_QWORD_R` : **little endian** signed 64 bit integer, 4 registers, `int64_t` - - `FP32` : 32 bit IEEE 754 floating point, 2 registers, `float` - - `FP32_R` : **little endian** 32 bit IEEE 754 floating point, 2 registers, `float` - - Defaults to `U_WORD`. - - - **read_lambda** (**Required**, [lambda](/automations/templates#config-lambda)): - Lambda that returns the value of this register. - - - **write_lambda** (*Optional*, [lambda](/automations/templates#config-lambda)): - Lambda that sets the value of this register. A variable `x` of the appropriate type (`uint16_t`, `int32_t`, etc, see above) is provided with the value, - as well as `address` containing the address of this register. You must return `true` if the operation was successful, `false` otherwise, in which case - a ModBUS exception code `4` will be sent to the client. - Automations: - **on_command_sent** (*Optional*, [Automation](/automations)): An automation to perform when a modbus command has been sent. See [`on_command_sent`](#modbus_controller-on_command_sent) @@ -191,6 +150,8 @@ modbus_controller: modbus_id: modbus_client address: 0x2 update_interval: 5s + +modbus_server: - modbus_id: modbus_server address: 0x4 server_registers: @@ -734,6 +695,7 @@ modbus_controller: - {{< docref "/components/modbus" >}} - {{< docref "/components/sensor/modbus_controller" >}} +- {{< docref "/components/sensor/modbus_server" >}} - {{< docref "/components/binary_sensor/modbus_controller" >}} - {{< docref "/components/output/modbus_controller" >}} - {{< docref "/components/switch/modbus_controller" >}} diff --git a/content/components/modbus_server.md b/content/components/modbus_server.md new file mode 100644 index 0000000000..3a7733756d --- /dev/null +++ b/content/components/modbus_server.md @@ -0,0 +1,131 @@ +--- +description: "Instructions for setting up the Modbus Server component." +title: "Modbus Server" +params: + seo: + description: Instructions for setting up the Modbus Server component. + image: modbus.png +--- + +The `modbus_server` component creates a RS485 connection to let your ESPHome node act as a Modbus server, allowing a ModBUS client to read data (like sensor values) from your ESPHome node. + +You must set the `role` attribute of the {{< docref "/components/modbus" >}} to `server`. + +{{< img src="modbus.png" alt="Image" width="25%" class="align-center" >}} + +## Hardware setup + +You need an RS485 transceiver module: + +{{< img src="rs485.jpg" alt="Image" >}} + +See [How is this RS485 module working?](https://electronics.stackexchange.com/questions/244425/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 slave 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 {{< docref "logger/" >}} for more details + +```yaml +logger: + level: + baud_rate: 0 +``` + +{{< /note >}} + +## Configuration variables + +- **modbus_id** (*Optional*, [ID](#config-id)): Manually specify the ID of the `modbus` hub. + +- **address** (**Required**, [ID](#config-id)): The Modbus address of the server device. + +- **server_courtesy_response** (*Optional*): Configuration block to enable the courtesy response feature when the device is acting as a Modbus server. + + - **enabled** (*Optional*, boolean): Whether to enable the courtesy response feature. + Defaults to `false`. + - **register_last_address** (*Optional*, integer): The highest Modbus register address (inclusive) up to which undefined registers are allowed to be read and will be padded with a default value. + Any read request that includes undefined registers within this range will return the value specified by `register_value` instead of triggering an exception. + Defaults to `65535` + - **register_value** (*Optional*, integer): The 16-bit value (range: 0–65535) to return for undefined registers within the address range defined by `register_last_address`. + Defaults to `0`. + +- **server_registers** (*Optional*): A list of registers that are responded to when acting as a server. + + - **address** (**Required**, integer): start address of the first register in a range + - **value_type** (*Optional*): datatype of the mod_bus register data. The default data type for ModBUS is a 16 bit integer in **big endian** format (network byte order, MSB first) + + - `U_WORD` : unsigned 16 bit integer, 1 register, `uint16_t` + - `S_WORD` : signed 16 bit integer, 1 register, `int16_t` + - `U_DWORD` : unsigned 32 bit integer, 2 registers, `uint32_t` + - `S_DWORD` : signed 32 bit integer, 2 registers, `int32_t` + - `U_DWORD_R` : **little endian** unsigned 32 bit integer, 2 registers, `uint32_t` + - `S_DWORD_R` : **little endian** signed 32 bit integer, 2 registers, `int32_t` + - `U_QWORD` : unsigned 64 bit integer, 4 registers, `uint64_t` + - `S_QWORD` : signed 64 bit integer, 4 registers `int64_t` + - `U_QWORD_R` : **little endian** unsigned 64 bit integer, 4 registers, `uint64_t` + - `S_QWORD_R` : **little endian** signed 64 bit integer, 4 registers, `int64_t` + - `FP32` : 32 bit IEEE 754 floating point, 2 registers, `float` + - `FP32_R` : **little endian** 32 bit IEEE 754 floating point, 2 registers, `float` + + Defaults to `U_WORD`. + + - **read_lambda** (**Required**, [lambda](/automations/templates#config-lambda)): + Lambda that returns the value of this register. + + - **write_lambda** (*Optional*, [lambda](/automations/templates#config-lambda)): + Lambda that sets the value of this register. A variable `x` of the appropriate type (`uint16_t`, `int32_t`, etc, see above) is provided with the value, + as well as `address` containing the address of this register. You must return `true` if the operation was successful, `false` otherwise, in which case + a ModBUS exception code `4` will be sent to the client. + +## Example + +The following code allows a ModBUS client to read a sensor value from your ESPHome node, that the node itself read from some local sensor. + +```yaml +uart: + - id: uart_modbus_server + tx_pin: 25 + rx_pin: 35 + +modbus: + - uart_id: uart_modbus_server + id: modbus_server + role: server + +modbus_server: + - modbus_id: modbus_server + address: 0x4 + server_registers: + - address: 0x0002 + value_type: U_DWORD_R + read_lambda: |- + return id(uptime_sens).state; + +sensor: + - platform: uptime + id: uptime_sens +``` + + +## See Also + +- {{< docref "/components/modbus" >}} +- {{< docref "/components/sensor/modbus_controller" >}} +- {{< docref "/components/binary_sensor/modbus_controller" >}} +- {{< docref "/components/output/modbus_controller" >}} +- {{< docref "/components/switch/modbus_controller" >}} +- {{< docref "/components/number/modbus_controller" >}} +- {{< docref "/components/select/modbus_controller" >}} +- {{< docref "/components/text_sensor/modbus_controller" >}} +- [Modbus RTU Protocol Description](https://www.modbustools.com/modbus.html) From c43117931c3247cb30e46c34a88ff6c775538814 Mon Sep 17 00:00:00 2001 From: Bonne Eggleston Date: Tue, 18 Nov 2025 08:17:39 -0800 Subject: [PATCH 6/6] Update configuration options for modbus --- content/components/modbus.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/content/components/modbus.md b/content/components/modbus.md index 03c670f8a4..d07a0c99c8 100644 --- a/content/components/modbus.md +++ b/content/components/modbus.md @@ -45,16 +45,14 @@ modbus: This is useful for RS485 transceivers that do not have automatic flow control switching, like the common MAX485. If your UART support `flow_control_pin` you should preferentially set it in the `uart` component over the `mobus` component. -- **send_wait_time** (*Optional*, [Time](/guides/configuration-types#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 250 ms. +- **send_wait_time** (*Optional*, [Time](/guides/configuration-types#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. -- **turnaround_time** (*Optional*, [Time](#config-time)): Time in milliseconds before the next ModBUS command is sent after last response is received (i.e. how long to allow all devices to process messages on the bus). Defaults to 100 ms. +- **turnaround_time** (*Optional*, [Time](#config-time)): Time in milliseconds before the next ModBUS command is sent after last response is received (i.e. how long to allow all devices to process messages on the bus). Defaults to 600 ms. Set this value 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 hear all messages, so will need to process them even if they don't need to reply. If devices don't respond sometimes, it can help to increase this value. -- **disable_crc** (*Optional*, boolean): Ignores a bad CRC if set to `true`. This will reduce error messages, but generally never help fix communication issues. Increasing times above first if you see CRC errors. Defaults to `false` - - **role** (*Optional*, string): The role of this component, `client` or `server`. Defaults to `client`. ## See Also