From fb5d697c2238edacf50f282b403bc255e6ff5b21 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 14 Apr 2025 08:45:30 +1000 Subject: [PATCH] [lvgl] Add restore_value to select and number (#8494) --- esphome/components/lvgl/lvcode.py | 3 +- esphome/components/lvgl/number/__init__.py | 43 +++++++++++--------- esphome/components/lvgl/number/lvgl_number.h | 41 ++++++++++++------- esphome/components/lvgl/select/__init__.py | 24 +++++------ esphome/components/lvgl/select/lvgl_select.h | 43 ++++++++++++-------- tests/components/lvgl/common.yaml | 1 + tests/components/lvgl/lvgl-package.yaml | 10 +++++ 7 files changed, 99 insertions(+), 66 deletions(-) diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index c8d744dfc8..67a87d24bf 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -173,7 +173,8 @@ class LambdaContext(CodeContext): class LvContext(LambdaContext): """ - Code generation into the LVGL initialisation code (called in `setup()`) + Code generation into the LVGL initialisation code, called before setup() and loop() + Basically just does cg.add, so now fairly redundant. """ added_lambda_count = 0 diff --git a/esphome/components/lvgl/number/__init__.py b/esphome/components/lvgl/number/__init__.py index b41a36bc0f..98f8423b7c 100644 --- a/esphome/components/lvgl/number/__init__.py +++ b/esphome/components/lvgl/number/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import number import esphome.config_validation as cv +from esphome.const import CONF_RESTORE_VALUE from esphome.cpp_generator import MockObj from ..defines import CONF_ANIMATED, CONF_UPDATE_ON_RELEASE, CONF_WIDGET @@ -10,21 +11,21 @@ from ..lvcode import ( EVENT_ARG, UPDATE_EVENT, LambdaContext, - LvContext, + ReturnStatement, lv, - lv_add, lvgl_static, ) from ..types import LV_EVENT, LvNumber, lvgl_ns from ..widgets import get_widgets, wait_for_widgets -LVGLNumber = lvgl_ns.class_("LVGLNumber", number.Number) +LVGLNumber = lvgl_ns.class_("LVGLNumber", number.Number, cg.Component) CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend( { cv.Required(CONF_WIDGET): cv.use_id(LvNumber), cv.Optional(CONF_ANIMATED, default=True): animated, cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, } ) @@ -32,32 +33,34 @@ CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend( async def to_code(config): widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] - var = await number.new_number( - config, - max_value=widget.get_max(), - min_value=widget.get_min(), - step=widget.get_step(), - ) - await wait_for_widgets() + async with LambdaContext([], return_type=cg.float_) as value: + value.add(ReturnStatement(widget.get_value())) async with LambdaContext([(cg.float_, "v")]) as control: await widget.set_property( "value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED] ) lv.event_send(widget.obj, API_EVENT, cg.nullptr) - control.add(var.publish_state(widget.get_value())) - async with LambdaContext(EVENT_ARG) as event: - event.add(var.publish_state(widget.get_value())) event_code = ( LV_EVENT.VALUE_CHANGED if not config[CONF_UPDATE_ON_RELEASE] else LV_EVENT.RELEASED ) - async with LvContext(): - lv_add(var.set_control_lambda(await control.get_lambda())) - lv_add( - lvgl_static.add_event_cb( - widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code - ) + var = await number.new_number( + config, + await control.get_lambda(), + await value.get_lambda(), + event_code, + config[CONF_RESTORE_VALUE], + max_value=widget.get_max(), + min_value=widget.get_min(), + step=widget.get_step(), + ) + async with LambdaContext(EVENT_ARG) as event: + event.add(var.on_value()) + await cg.register_component(var, config) + cg.add( + lvgl_static.add_event_cb( + widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code ) - lv_add(var.publish_state(widget.get_value())) + ) diff --git a/esphome/components/lvgl/number/lvgl_number.h b/esphome/components/lvgl/number/lvgl_number.h index 77fadd2a29..277494673b 100644 --- a/esphome/components/lvgl/number/lvgl_number.h +++ b/esphome/components/lvgl/number/lvgl_number.h @@ -3,33 +3,46 @@ #include #include "esphome/components/number/number.h" -#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/preferences.h" namespace esphome { namespace lvgl { -class LVGLNumber : public number::Number { +class LVGLNumber : public number::Number, public Component { public: - void set_control_lambda(std::function control_lambda) { - this->control_lambda_ = std::move(control_lambda); - if (this->initial_state_.has_value()) { - this->control_lambda_(this->initial_state_.value()); - this->initial_state_.reset(); + LVGLNumber(std::function control_lambda, std::function value_lambda, lv_event_code_t event, + bool restore) + : control_lambda_(std::move(control_lambda)), + value_lambda_(std::move(value_lambda)), + event_(event), + restore_(restore) {} + + void setup() override { + float value = this->value_lambda_(); + if (this->restore_) { + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + if (this->pref_.load(&value)) { + this->control_lambda_(value); + } } + this->publish_state(value); } + void on_value() { this->publish_state(this->value_lambda_()); } + protected: void control(float value) override { - if (this->control_lambda_ != nullptr) { - this->control_lambda_(value); - } else { - this->initial_state_ = value; - } + this->control_lambda_(value); + this->publish_state(value); + if (this->restore_) + this->pref_.save(&value); } - std::function control_lambda_{}; - optional initial_state_{}; + std::function control_lambda_; + std::function value_lambda_; + lv_event_code_t event_; + bool restore_; + ESPPreferenceObject pref_{}; }; } // namespace lvgl diff --git a/esphome/components/lvgl/select/__init__.py b/esphome/components/lvgl/select/__init__.py index bd5ef8f237..4f9d12266e 100644 --- a/esphome/components/lvgl/select/__init__.py +++ b/esphome/components/lvgl/select/__init__.py @@ -1,18 +1,19 @@ +import esphome.codegen as cg from esphome.components import select import esphome.config_validation as cv -from esphome.const import CONF_OPTIONS +from esphome.const import CONF_ID, CONF_OPTIONS, CONF_RESTORE_VALUE from ..defines import CONF_ANIMATED, CONF_WIDGET, literal -from ..lvcode import LvContext from ..types import LvSelect, lvgl_ns -from ..widgets import get_widgets, wait_for_widgets +from ..widgets import get_widgets -LVGLSelect = lvgl_ns.class_("LVGLSelect", select.Select) +LVGLSelect = lvgl_ns.class_("LVGLSelect", select.Select, cg.Component) CONFIG_SCHEMA = select.select_schema(LVGLSelect).extend( { cv.Required(CONF_WIDGET): cv.use_id(LvSelect), cv.Optional(CONF_ANIMATED, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, } ) @@ -21,12 +22,9 @@ async def to_code(config): widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] options = widget.config.get(CONF_OPTIONS, []) - selector = await select.new_select(config, options=options) - await wait_for_widgets() - async with LvContext() as ctx: - ctx.add( - selector.set_widget( - widget.var, - literal("LV_ANIM_ON" if config[CONF_ANIMATED] else "LV_ANIM_OFF"), - ) - ) + animated = literal("LV_ANIM_ON" if config[CONF_ANIMATED] else "LV_ANIM_OFF") + selector = cg.new_Pvariable( + config[CONF_ID], widget.var, animated, config[CONF_RESTORE_VALUE] + ) + await select.register_select(selector, config, options=options) + await cg.register_component(selector, config) diff --git a/esphome/components/lvgl/select/lvgl_select.h b/esphome/components/lvgl/select/lvgl_select.h index 4538e339c3..5b43209a5f 100644 --- a/esphome/components/lvgl/select/lvgl_select.h +++ b/esphome/components/lvgl/select/lvgl_select.h @@ -11,12 +11,20 @@ namespace esphome { namespace lvgl { -class LVGLSelect : public select::Select { +class LVGLSelect : public select::Select, public Component { public: - void set_widget(LvSelectable *widget, lv_anim_enable_t anim = LV_ANIM_OFF) { - this->widget_ = widget; - this->anim_ = anim; + LVGLSelect(LvSelectable *widget, lv_anim_enable_t anim, bool restore) + : widget_(widget), anim_(anim), restore_(restore) {} + + void setup() override { this->set_options_(); + if (this->restore_) { + size_t index; + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + if (this->pref_.load(&index)) + this->widget_->set_selected_index(index, LV_ANIM_OFF); + } + this->publish(); lv_obj_add_event_cb( this->widget_->obj, [](lv_event_t *e) { @@ -24,11 +32,6 @@ class LVGLSelect : public select::Select { it->set_options_(); }, LV_EVENT_REFRESH, this); - if (this->initial_state_.has_value()) { - this->control(this->initial_state_.value()); - this->initial_state_.reset(); - } - this->publish(); auto lamb = [](lv_event_t *e) { auto *self = static_cast(e->user_data); self->publish(); @@ -37,21 +40,25 @@ class LVGLSelect : public select::Select { lv_obj_add_event_cb(this->widget_->obj, lamb, lv_update_event, this); } - void publish() { this->publish_state(this->widget_->get_selected_text()); } + void publish() { + this->publish_state(this->widget_->get_selected_text()); + if (this->restore_) { + auto index = this->widget_->get_selected_index(); + this->pref_.save(&index); + } + } protected: void control(const std::string &value) override { - if (this->widget_ != nullptr) { - this->widget_->set_selected_text(value, this->anim_); - } else { - this->initial_state_ = value; - } + this->widget_->set_selected_text(value, this->anim_); + this->publish(); } void set_options_() { this->traits.set_options(this->widget_->get_options()); } - LvSelectable *widget_{}; - optional initial_state_{}; - lv_anim_enable_t anim_{LV_ANIM_OFF}; + LvSelectable *widget_; + lv_anim_enable_t anim_; + bool restore_; + ESPPreferenceObject pref_{}; }; } // namespace lvgl diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index c7d635db1c..174df56749 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -38,6 +38,7 @@ number: widget: slider_id name: LVGL Slider update_on_release: true + restore_value: true - platform: lvgl widget: lv_arc id: lvgl_arc_number diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 78c261c01d..a0b7dd096f 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -990,3 +990,13 @@ color: green_int: 123 blue_int: 64 white_int: 255 + +select: + - platform: lvgl + id: lv_roller_select + widget: lv_roller + restore_value: true + - platform: lvgl + id: lv_dropdown_select + widget: lv_dropdown + restore_value: false