diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 69286ada88..f60d60d9a4 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -18,13 +18,13 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_TYPE, ) -from esphome.core import CORE, ID +from esphome.core import CORE, ID, Lambda from esphome.cpp_generator import MockObj from esphome.final_validate import full_config from esphome.helpers import write_file_if_changed from . import defines as df, helpers, lv_validation as lvalid -from .automation import disp_update, focused_widgets, update_to_code +from .automation import disp_update, focused_widgets, refreshed_widgets, update_to_code from .defines import add_define from .encoders import ( ENCODERS_CONFIG, @@ -240,6 +240,13 @@ def final_validation(configs): "A non adjustable arc may not be focused", path, ) + for w in refreshed_widgets: + path = global_config.get_path_for_id(w) + widget_conf = global_config.get_config_for_path(path[:-1]) + if not any(isinstance(v, Lambda) for v in widget_conf.values()): + raise cv.Invalid( + f"Widget '{w}' does not have any templated properties to refresh", + ) async def to_code(configs): diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index 4a71872022..5fea9bfdb1 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -35,7 +35,13 @@ from .lvcode import ( lv_obj, lvgl_comp, ) -from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema +from .schemas import ( + ALL_STYLES, + DISP_BG_SCHEMA, + LIST_ACTION_SCHEMA, + LVGL_SCHEMA, + base_update_schema, +) from .types import ( LV_STATE, LvglAction, @@ -57,6 +63,7 @@ from .widgets import ( # Record widgets that are used in a focused action here focused_widgets = set() +refreshed_widgets = set() async def action_to_code( @@ -361,3 +368,45 @@ async def obj_update_to_code(config, action_id, template_arg, args): return await action_to_code( widgets, do_update, action_id, template_arg, args, config ) + + +def validate_refresh_config(config): + for w in config: + refreshed_widgets.add(w[CONF_ID]) + return config + + +@automation.register_action( + "lvgl.widget.refresh", + ObjUpdateAction, + cv.All( + cv.ensure_list( + cv.maybe_simple_value( + { + cv.Required(CONF_ID): cv.use_id(lv_obj_t), + }, + key=CONF_ID, + ) + ), + validate_refresh_config, + ), +) +async def obj_refresh_to_code(config, action_id, template_arg, args): + widget = await get_widgets(config) + + async def do_refresh(widget: Widget): + # only update style properties that might have changed, i.e. are templated + config = {k: v for k, v in widget.config.items() if isinstance(v, Lambda)} + await set_obj_properties(widget, config) + # must pass all widget-specific options here, even if not templated, but only do so if at least one is + # templated. First filter out common style properties. + config = {k: v for k, v in widget.config.items() if k not in ALL_STYLES} + if any(isinstance(v, Lambda) for v in config.values()): + await widget.type.to_code(widget, config) + if ( + widget.type.w_type.value_property is not None + and widget.type.w_type.value_property in config + ): + lv.event_send(widget.obj, UPDATE_EVENT, nullptr) + + return await action_to_code(widget, do_refresh, action_id, template_arg, args) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 6fd0b5e3c4..db55da9225 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -212,7 +212,7 @@ lvgl: - animimg: height: 60 id: anim_img - src: [cat_image, dog_image] + src: !lambda "return {dog_image, cat_image};" repeat_count: 10 duration: 1s auto_start: true @@ -224,6 +224,7 @@ lvgl: id: anim_img src: !lambda "return {dog_image, cat_image};" duration: 2s + - lvgl.widget.refresh: anim_img - label: on_boot: lvgl.label.update: