From e1aa92b9831a605c91997953eed3864e879ef943 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2026 08:13:37 -1000 Subject: [PATCH 1/8] [rotary_encoder] Fix templatable value type to use cg.int32 (#15567) --- esphome/components/rotary_encoder/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index 246db023f4..21239863e4 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -129,6 +129,6 @@ async def to_code(config): async def sensor_template_publish_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_VALUE], args, int) + template_ = await cg.templatable(config[CONF_VALUE], args, cg.int32) cg.add(var.set_value(template_)) return var From b83edf6c175120fbc0319bf58cf9e1a836038c97 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2026 08:57:56 -1000 Subject: [PATCH 2/8] [script] Resolve IncludeFile objects in component config merge (#15575) --- script/merge_component_configs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/script/merge_component_configs.py b/script/merge_component_configs.py index 41bbafcd02..df7ad4a28c 100755 --- a/script/merge_component_configs.py +++ b/script/merge_component_configs.py @@ -288,6 +288,9 @@ def merge_component_configs( for pkg_name, pkg_value in list(packages_value.items()): if pkg_name in common_bus_packages: continue + # Resolve deferred !include files before checking type + if isinstance(pkg_value, yaml_util.IncludeFile): + pkg_value = pkg_value.load() if not isinstance(pkg_value, dict): continue # Component-specific package - expand its content into top level @@ -295,6 +298,9 @@ def merge_component_configs( elif isinstance(packages_value, list): # List format - expand all package includes for pkg_value in packages_value: + # Resolve deferred !include files before checking type + if isinstance(pkg_value, yaml_util.IncludeFile): + pkg_value = pkg_value.load() if not isinstance(pkg_value, dict): continue comp_data = merge_config(comp_data, pkg_value) From 869cace2f3ce753bbd1ddd0120aa0a31d838466c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2026 08:59:49 -1000 Subject: [PATCH 3/8] [web_server] Truncate update entity summary to 256 characters (#15570) --- esphome/components/web_server/web_server.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index a57a8d26ff..1daec1786d 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -2207,7 +2207,11 @@ json::SerializationBuffer<> WebServer::update_json_(update::UpdateEntity *obj, J if (start_config == DETAIL_ALL) { root[ESPHOME_F("current_version")] = obj->update_info.current_version; root[ESPHOME_F("title")] = obj->update_info.title; - root[ESPHOME_F("summary")] = obj->update_info.summary; + // Truncate long changelogs — full text available via release_url + constexpr size_t max_summary_len = 256; + root[ESPHOME_F("summary")] = obj->update_info.summary.size() <= max_summary_len + ? obj->update_info.summary + : obj->update_info.summary.substr(0, max_summary_len); root[ESPHOME_F("release_url")] = obj->update_info.release_url; this->add_sorting_info_(root, obj); } From a2bd83382b585de1b0e19647cfe712d485670191 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2026 09:00:59 -1000 Subject: [PATCH 4/8] [codegen] Fix templatable uint8 type to use cg.uint8 (#15572) --- esphome/components/ags10/sensor.py | 2 +- esphome/components/aic3204/audio_dac.py | 2 +- esphome/components/grove_tb6612fng/__init__.py | 8 ++++---- esphome/components/htu21d/sensor.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/ags10/sensor.py b/esphome/components/ags10/sensor.py index e94504ff1a..6491d7d810 100644 --- a/esphome/components/ags10/sensor.py +++ b/esphome/components/ags10/sensor.py @@ -97,7 +97,7 @@ AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value( async def ags10newi2caddress_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - address = await cg.templatable(config[CONF_ADDRESS], args, int) + address = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8) cg.add(var.set_new_address(address)) return var diff --git a/esphome/components/aic3204/audio_dac.py b/esphome/components/aic3204/audio_dac.py index a644638f69..b478b573a3 100644 --- a/esphome/components/aic3204/audio_dac.py +++ b/esphome/components/aic3204/audio_dac.py @@ -43,7 +43,7 @@ async def aic3204_set_volume_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config.get(CONF_MODE), args, int) + template_ = await cg.templatable(config.get(CONF_MODE), args, cg.uint8) cg.add(var.set_auto_mute_mode(template_)) return var diff --git a/esphome/components/grove_tb6612fng/__init__.py b/esphome/components/grove_tb6612fng/__init__.py index 27a47953b3..ae64c049f5 100644 --- a/esphome/components/grove_tb6612fng/__init__.py +++ b/esphome/components/grove_tb6612fng/__init__.py @@ -78,7 +78,7 @@ async def grove_tb6612fng_run_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) template_speed = await cg.templatable(config[CONF_SPEED], args, cg.uint16) cg.add(var.set_channel(template_channel)) cg.add(var.set_speed(template_speed)) @@ -101,7 +101,7 @@ async def grove_tb6612fng_break_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) cg.add(var.set_channel(template_channel)) return var @@ -121,7 +121,7 @@ async def grove_tb6612fng_stop_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) cg.add(var.set_channel(template_channel)) return var @@ -175,6 +175,6 @@ async def grove_tb6612fng_change_address_to_code(config, action_id, template_arg var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_channel = await cg.templatable(config[CONF_ADDRESS], args, int) + template_channel = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8) cg.add(var.set_address(template_channel)) return var diff --git a/esphome/components/htu21d/sensor.py b/esphome/components/htu21d/sensor.py index ed4fb5968a..942a28475a 100644 --- a/esphome/components/htu21d/sensor.py +++ b/esphome/components/htu21d/sensor.py @@ -98,7 +98,7 @@ async def to_code(config): async def set_heater_level_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - level_ = await cg.templatable(config[CONF_LEVEL], args, int) + level_ = await cg.templatable(config[CONF_LEVEL], args, cg.uint8) cg.add(var.set_level(level_)) return var From 063a8ce666d0cd79d145670a311efdd48aed838a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2026 09:03:25 -1000 Subject: [PATCH 5/8] [codegen] Fix templatable uint32 type to use cg.uint32 (#15574) --- esphome/components/pulse_counter/sensor.py | 2 +- esphome/components/pulse_meter/sensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index c09d778eda..3326745846 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -160,6 +160,6 @@ async def to_code(config): async def set_total_action_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_VALUE], args, int) + template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint32) cg.add(var.set_total_pulses(template_)) return var diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index 499b7309c8..ab3dd2a249 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -110,6 +110,6 @@ async def to_code(config): async def set_total_action_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_VALUE], args, int) + template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint32) cg.add(var.set_total_pulses(template_)) return var From 0a42a11f1cc8b1c6334dc281604165f09846434b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2026 09:10:46 -1000 Subject: [PATCH 6/8] [at581x] Fix non-templated frequency/power_consumption constants for TemplatableFn (#15576) --- esphome/components/at581x/__init__.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/esphome/components/at581x/__init__.py b/esphome/components/at581x/__init__.py index 4923491f0c..0bd26bbe5f 100644 --- a/esphome/components/at581x/__init__.py +++ b/esphome/components/at581x/__init__.py @@ -173,10 +173,9 @@ async def at581x_settings_to_code(config, action_id, template_arg, args): cg.add(var.set_hw_frontend_reset(template_)) if freq := config.get(CONF_FREQUENCY): - if cg.is_template(freq): - template_ = await cg.templatable(freq, args, cg.int32) - else: - template_ = int(freq / 1000000) + if not cg.is_template(freq): + freq = int(freq / 1000000) + template_ = await cg.templatable(freq, args, cg.int_) cg.add(var.set_frequency(template_)) if (sens_dist := config.get(CONF_SENSING_DISTANCE)) is not None: @@ -204,10 +203,9 @@ async def at581x_settings_to_code(config, action_id, template_arg, args): cg.add(var.set_stage_gain(template_)) if power := config.get(CONF_POWER_CONSUMPTION): - if cg.is_template(power): - template_ = await cg.templatable(power, args, cg.int32) - else: - template_ = int(power * 1000000) + if not cg.is_template(power): + power = int(power * 1000000) + template_ = await cg.templatable(power, args, cg.int_) cg.add(var.set_power_consumption(template_)) return var From cfa41b34677a682b7464de1607240c4b0aa74f52 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2026 09:20:16 -1000 Subject: [PATCH 7/8] [codegen] Add cg.int8 type and fix templatable int8 types (#15573) --- esphome/codegen.py | 1 + esphome/components/at581x/__init__.py | 2 +- esphome/components/dfrobot_sen0395/__init__.py | 4 ++-- esphome/cpp_types.py | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 30e3135360..a5b5abe447 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -79,6 +79,7 @@ from esphome.cpp_types import ( # noqa: F401 float_, global_ns, gpio_Flags, + int8, int16, int32, int64, diff --git a/esphome/components/at581x/__init__.py b/esphome/components/at581x/__init__.py index 0bd26bbe5f..acd2bcc608 100644 --- a/esphome/components/at581x/__init__.py +++ b/esphome/components/at581x/__init__.py @@ -169,7 +169,7 @@ async def at581x_settings_to_code(config, action_id, template_arg, args): # Radar configuration if frontend_reset := config.get(CONF_HW_FRONTEND_RESET): - template_ = await cg.templatable(frontend_reset, args, int) + template_ = await cg.templatable(frontend_reset, args, cg.int8) cg.add(var.set_hw_frontend_reset(template_)) if freq := config.get(CONF_FREQUENCY): diff --git a/esphome/components/dfrobot_sen0395/__init__.py b/esphome/components/dfrobot_sen0395/__init__.py index 0becaf3543..feb79eeacf 100644 --- a/esphome/components/dfrobot_sen0395/__init__.py +++ b/esphome/components/dfrobot_sen0395/__init__.py @@ -159,7 +159,7 @@ async def dfrobot_sen0395_settings_to_code(config, action_id, template_arg, args await cg.register_parented(var, config[CONF_ID]) if factory_reset_config := config.get(CONF_FACTORY_RESET): - template_ = await cg.templatable(factory_reset_config, args, int) + template_ = await cg.templatable(factory_reset_config, args, cg.int8) cg.add(var.set_factory_reset(template_)) if CONF_DETECTION_SEGMENTS in config: @@ -200,7 +200,7 @@ async def dfrobot_sen0395_settings_to_code(config, action_id, template_arg, args template_ = template_.total_milliseconds / 1000 cg.add(var.set_delay_after_disappear(template_)) if CONF_SENSITIVITY in config: - template_ = await cg.templatable(config[CONF_SENSITIVITY], args, int) + template_ = await cg.templatable(config[CONF_SENSITIVITY], args, cg.int8) cg.add(var.set_sensitivity(template_)) return var diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 8dd77de843..aeaa4480a8 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -13,6 +13,7 @@ std_string = std_ns.class_("string") std_string_ref = std_ns.namespace("string &") std_vector = std_ns.class_("vector") std_span = std_ns.class_("span") +int8 = global_ns.namespace("int8_t") uint8 = global_ns.namespace("uint8_t") uint16 = global_ns.namespace("uint16_t") uint32 = global_ns.namespace("uint32_t") From 7de060ed554bd0a03c3f0037d6e152a1ab2c176b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 9 Apr 2026 05:22:24 +1000 Subject: [PATCH 8/8] [lvgl] Fix args for lambda in set_rotation action (#15555) --- esphome/components/lvgl/automation.py | 6 +++--- esphome/components/lvgl/widgets/tabview.py | 3 ++- tests/components/lvgl/lvgl-package.yaml | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index b825320a40..977f1af9b4 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -2,6 +2,7 @@ from collections.abc import Callable from typing import Any from esphome import automation +from esphome.automation import StatelessLambdaAction import esphome.codegen as cg from esphome.components.display import validate_rotation import esphome.config_validation as cv @@ -201,7 +202,7 @@ def _validate_rotation(value): @automation.register_action( "lvgl.display.set_rotation", - ObjUpdateAction, + StatelessLambdaAction, cv.maybe_simple_value( LVGL_SCHEMA.extend( { @@ -214,8 +215,7 @@ def _validate_rotation(value): ) async def lvgl_set_rotation(config, action_id, template_arg, args): lv_comp = await cg.get_variable(config[CONF_LVGL_ID]) - async with LambdaContext() as context: - add_line_marks(where=action_id) + async with LambdaContext(args, where=action_id) as context: lv_add(lv_comp.set_rotation(config[CONF_ROTATION])) return cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) diff --git a/esphome/components/lvgl/widgets/tabview.py b/esphome/components/lvgl/widgets/tabview.py index 7629b03e9d..108bb38df5 100644 --- a/esphome/components/lvgl/widgets/tabview.py +++ b/esphome/components/lvgl/widgets/tabview.py @@ -2,6 +2,7 @@ from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( + CONF_BUTTON, CONF_ID, CONF_INDEX, CONF_ITEMS, @@ -73,7 +74,7 @@ class TabviewType(WidgetType): ) def get_uses(self): - return CONF_BUTTONMATRIX, TYPE_FLEX + return CONF_BUTTONMATRIX, TYPE_FLEX, CONF_BUTTON async def to_code(self, w: Widget, config: dict): await w.set_property( diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 967fe51592..d3565c6c59 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -194,6 +194,7 @@ lvgl: text: "Close" on_click: then: + - lvgl.display.set_rotation: 0 - lvgl.widget.hide: message_box - lvgl.style.update: id: style_test