[lvgl] Add restore_value to select and number (#8494)

This commit is contained in:
Clyde Stubbs 2025-04-14 08:45:30 +10:00 committed by GitHub
parent df4642208e
commit fb5d697c22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 99 additions and 66 deletions

View File

@ -173,7 +173,8 @@ class LambdaContext(CodeContext):
class LvContext(LambdaContext): 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 added_lambda_count = 0

View File

@ -1,6 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import number from esphome.components import number
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_RESTORE_VALUE
from esphome.cpp_generator import MockObj from esphome.cpp_generator import MockObj
from ..defines import CONF_ANIMATED, CONF_UPDATE_ON_RELEASE, CONF_WIDGET from ..defines import CONF_ANIMATED, CONF_UPDATE_ON_RELEASE, CONF_WIDGET
@ -10,21 +11,21 @@ from ..lvcode import (
EVENT_ARG, EVENT_ARG,
UPDATE_EVENT, UPDATE_EVENT,
LambdaContext, LambdaContext,
LvContext, ReturnStatement,
lv, lv,
lv_add,
lvgl_static, lvgl_static,
) )
from ..types import LV_EVENT, LvNumber, lvgl_ns from ..types import LV_EVENT, LvNumber, lvgl_ns
from ..widgets import get_widgets, wait_for_widgets 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( CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend(
{ {
cv.Required(CONF_WIDGET): cv.use_id(LvNumber), cv.Required(CONF_WIDGET): cv.use_id(LvNumber),
cv.Optional(CONF_ANIMATED, default=True): animated, cv.Optional(CONF_ANIMATED, default=True): animated,
cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean, 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): async def to_code(config):
widget = await get_widgets(config, CONF_WIDGET) widget = await get_widgets(config, CONF_WIDGET)
widget = widget[0] 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() 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: async with LambdaContext([(cg.float_, "v")]) as control:
await widget.set_property( await widget.set_property(
"value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED] "value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED]
) )
lv.event_send(widget.obj, API_EVENT, cg.nullptr) 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 = ( event_code = (
LV_EVENT.VALUE_CHANGED LV_EVENT.VALUE_CHANGED
if not config[CONF_UPDATE_ON_RELEASE] if not config[CONF_UPDATE_ON_RELEASE]
else LV_EVENT.RELEASED else LV_EVENT.RELEASED
) )
async with LvContext(): var = await number.new_number(
lv_add(var.set_control_lambda(await control.get_lambda())) config,
lv_add( 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( lvgl_static.add_event_cb(
widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code
) )
) )
lv_add(var.publish_state(widget.get_value()))

View File

@ -3,33 +3,46 @@
#include <utility> #include <utility>
#include "esphome/components/number/number.h" #include "esphome/components/number/number.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
namespace esphome { namespace esphome {
namespace lvgl { namespace lvgl {
class LVGLNumber : public number::Number { class LVGLNumber : public number::Number, public Component {
public: public:
void set_control_lambda(std::function<void(float)> control_lambda) { LVGLNumber(std::function<void(float)> control_lambda, std::function<float()> value_lambda, lv_event_code_t event,
this->control_lambda_ = std::move(control_lambda); bool restore)
if (this->initial_state_.has_value()) { : control_lambda_(std::move(control_lambda)),
this->control_lambda_(this->initial_state_.value()); value_lambda_(std::move(value_lambda)),
this->initial_state_.reset(); event_(event),
restore_(restore) {}
void setup() override {
float value = this->value_lambda_();
if (this->restore_) {
this->pref_ = global_preferences->make_preference<float>(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: protected:
void control(float value) override { void control(float value) override {
if (this->control_lambda_ != nullptr) {
this->control_lambda_(value); this->control_lambda_(value);
} else { this->publish_state(value);
this->initial_state_ = value; if (this->restore_)
this->pref_.save(&value);
} }
} std::function<void(float)> control_lambda_;
std::function<void(float)> control_lambda_{}; std::function<float()> value_lambda_;
optional<float> initial_state_{}; lv_event_code_t event_;
bool restore_;
ESPPreferenceObject pref_{};
}; };
} // namespace lvgl } // namespace lvgl

View File

@ -1,18 +1,19 @@
import esphome.codegen as cg
from esphome.components import select from esphome.components import select
import esphome.config_validation as cv 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 ..defines import CONF_ANIMATED, CONF_WIDGET, literal
from ..lvcode import LvContext
from ..types import LvSelect, lvgl_ns 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( CONFIG_SCHEMA = select.select_schema(LVGLSelect).extend(
{ {
cv.Required(CONF_WIDGET): cv.use_id(LvSelect), cv.Required(CONF_WIDGET): cv.use_id(LvSelect),
cv.Optional(CONF_ANIMATED, default=False): cv.boolean, 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 = await get_widgets(config, CONF_WIDGET)
widget = widget[0] widget = widget[0]
options = widget.config.get(CONF_OPTIONS, []) options = widget.config.get(CONF_OPTIONS, [])
selector = await select.new_select(config, options=options) animated = literal("LV_ANIM_ON" if config[CONF_ANIMATED] else "LV_ANIM_OFF")
await wait_for_widgets() selector = cg.new_Pvariable(
async with LvContext() as ctx: config[CONF_ID], widget.var, animated, config[CONF_RESTORE_VALUE]
ctx.add(
selector.set_widget(
widget.var,
literal("LV_ANIM_ON" if config[CONF_ANIMATED] else "LV_ANIM_OFF"),
)
) )
await select.register_select(selector, config, options=options)
await cg.register_component(selector, config)

View File

@ -11,12 +11,20 @@
namespace esphome { namespace esphome {
namespace lvgl { namespace lvgl {
class LVGLSelect : public select::Select { class LVGLSelect : public select::Select, public Component {
public: public:
void set_widget(LvSelectable *widget, lv_anim_enable_t anim = LV_ANIM_OFF) { LVGLSelect(LvSelectable *widget, lv_anim_enable_t anim, bool restore)
this->widget_ = widget; : widget_(widget), anim_(anim), restore_(restore) {}
this->anim_ = anim;
void setup() override {
this->set_options_(); this->set_options_();
if (this->restore_) {
size_t index;
this->pref_ = global_preferences->make_preference<size_t>(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( lv_obj_add_event_cb(
this->widget_->obj, this->widget_->obj,
[](lv_event_t *e) { [](lv_event_t *e) {
@ -24,11 +32,6 @@ class LVGLSelect : public select::Select {
it->set_options_(); it->set_options_();
}, },
LV_EVENT_REFRESH, this); 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 lamb = [](lv_event_t *e) {
auto *self = static_cast<LVGLSelect *>(e->user_data); auto *self = static_cast<LVGLSelect *>(e->user_data);
self->publish(); self->publish();
@ -37,21 +40,25 @@ class LVGLSelect : public select::Select {
lv_obj_add_event_cb(this->widget_->obj, lamb, lv_update_event, this); 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: protected:
void control(const std::string &value) override { void control(const std::string &value) override {
if (this->widget_ != nullptr) {
this->widget_->set_selected_text(value, this->anim_); this->widget_->set_selected_text(value, this->anim_);
} else { this->publish();
this->initial_state_ = value;
}
} }
void set_options_() { this->traits.set_options(this->widget_->get_options()); } void set_options_() { this->traits.set_options(this->widget_->get_options()); }
LvSelectable *widget_{}; LvSelectable *widget_;
optional<std::string> initial_state_{}; lv_anim_enable_t anim_;
lv_anim_enable_t anim_{LV_ANIM_OFF}; bool restore_;
ESPPreferenceObject pref_{};
}; };
} // namespace lvgl } // namespace lvgl

View File

@ -38,6 +38,7 @@ number:
widget: slider_id widget: slider_id
name: LVGL Slider name: LVGL Slider
update_on_release: true update_on_release: true
restore_value: true
- platform: lvgl - platform: lvgl
widget: lv_arc widget: lv_arc
id: lvgl_arc_number id: lvgl_arc_number

View File

@ -990,3 +990,13 @@ color:
green_int: 123 green_int: 123
blue_int: 64 blue_int: 64
white_int: 255 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