1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 08:31:47 +00:00

[lvgl] Trigger improvements and additions (#11628)

This commit is contained in:
Clyde Stubbs
2025-11-01 16:27:28 +10:00
committed by GitHub
parent 5a5894eaa3
commit 8df5a3a630
7 changed files with 86 additions and 45 deletions

View File

@@ -58,7 +58,7 @@ from .types import (
FontEngine, FontEngine,
IdleTrigger, IdleTrigger,
ObjUpdateAction, ObjUpdateAction,
PauseTrigger, PlainTrigger,
lv_font_t, lv_font_t,
lv_group_t, lv_group_t,
lv_style_t, lv_style_t,
@@ -151,6 +151,13 @@ for w_type in WIDGET_TYPES.values():
create_modify_schema(w_type), create_modify_schema(w_type),
)(update_to_code) )(update_to_code)
SIMPLE_TRIGGERS = (
df.CONF_ON_PAUSE,
df.CONF_ON_RESUME,
df.CONF_ON_DRAW_START,
df.CONF_ON_DRAW_END,
)
def as_macro(macro, value): def as_macro(macro, value):
if value is None: if value is None:
@@ -244,9 +251,9 @@ def final_validation(configs):
for w in refreshed_widgets: for w in refreshed_widgets:
path = global_config.get_path_for_id(w) path = global_config.get_path_for_id(w)
widget_conf = global_config.get_config_for_path(path[:-1]) widget_conf = global_config.get_config_for_path(path[:-1])
if not any(isinstance(v, Lambda) for v in widget_conf.values()): if not any(isinstance(v, (Lambda, dict)) for v in widget_conf.values()):
raise cv.Invalid( raise cv.Invalid(
f"Widget '{w}' does not have any templated properties to refresh", f"Widget '{w}' does not have any dynamic properties to refresh",
) )
@@ -366,16 +373,16 @@ async def to_code(configs):
conf[CONF_TRIGGER_ID], lv_component, templ conf[CONF_TRIGGER_ID], lv_component, templ
) )
await build_automation(idle_trigger, [], conf) await build_automation(idle_trigger, [], conf)
for conf in config.get(df.CONF_ON_PAUSE, ()): for trigger_name in SIMPLE_TRIGGERS:
pause_trigger = cg.new_Pvariable( if conf := config.get(trigger_name):
conf[CONF_TRIGGER_ID], lv_component, True trigger_var = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
await build_automation(trigger_var, [], conf)
cg.add(
getattr(
lv_component,
f"set_{trigger_name.removeprefix('on_')}_trigger",
)(trigger_var)
) )
await build_automation(pause_trigger, [], conf)
for conf in config.get(df.CONF_ON_RESUME, ()):
resume_trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], lv_component, False
)
await build_automation(resume_trigger, [], conf)
await add_on_boot_triggers(config.get(CONF_ON_BOOT, ())) await add_on_boot_triggers(config.get(CONF_ON_BOOT, ()))
# This must be done after all widgets are created # This must be done after all widgets are created
@@ -443,16 +450,15 @@ LVGL_SCHEMA = cv.All(
), ),
} }
), ),
cv.Optional(df.CONF_ON_PAUSE): validate_automation( **{
cv.Optional(x): validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PlainTrigger),
} },
), single=True,
cv.Optional(df.CONF_ON_RESUME): validate_automation( )
{ for x in SIMPLE_TRIGGERS
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger), },
}
),
cv.Exclusive(df.CONF_WIDGETS, CONF_PAGES): cv.ensure_list( cv.Exclusive(df.CONF_WIDGETS, CONF_PAGES): cv.ensure_list(
WIDGET_SCHEMA WIDGET_SCHEMA
), ),

View File

@@ -400,7 +400,8 @@ async def obj_refresh_to_code(config, action_id, template_arg, args):
# must pass all widget-specific options here, even if not templated, but only do so if at least one is # 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. # templated. First filter out common style properties.
config = {k: v for k, v in widget.config.items() if k not in ALL_STYLES} 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()): # Check if v is a Lambda or a dict, implying it is dynamic
if any(isinstance(v, (Lambda, dict)) for v in config.values()):
await widget.type.to_code(widget, config) await widget.type.to_code(widget, config)
if ( if (
widget.type.w_type.value_property is not None widget.type.w_type.value_property is not None

View File

@@ -483,6 +483,8 @@ CONF_MSGBOXES = "msgboxes"
CONF_OBJ = "obj" CONF_OBJ = "obj"
CONF_ONE_CHECKED = "one_checked" CONF_ONE_CHECKED = "one_checked"
CONF_ONE_LINE = "one_line" CONF_ONE_LINE = "one_line"
CONF_ON_DRAW_START = "on_draw_start"
CONF_ON_DRAW_END = "on_draw_end"
CONF_ON_PAUSE = "on_pause" CONF_ON_PAUSE = "on_pause"
CONF_ON_RESUME = "on_resume" CONF_ON_RESUME = "on_resume"
CONF_ON_SELECT = "on_select" CONF_ON_SELECT = "on_select"

View File

@@ -82,6 +82,18 @@ static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
area->y2 = (area->y2 + draw_rounding) / draw_rounding * draw_rounding - 1; area->y2 = (area->y2 + draw_rounding) / draw_rounding * draw_rounding - 1;
} }
void LvglComponent::monitor_cb(lv_disp_drv_t *disp_drv, uint32_t time, uint32_t px) {
ESP_LOGVV(TAG, "Draw end: %" PRIu32 " pixels in %" PRIu32 " ms", px, time);
auto *comp = static_cast<LvglComponent *>(disp_drv->user_data);
comp->draw_end_();
}
void LvglComponent::render_start_cb(lv_disp_drv_t *disp_drv) {
ESP_LOGVV(TAG, "Draw start");
auto *comp = static_cast<LvglComponent *>(disp_drv->user_data);
comp->draw_start_();
}
lv_event_code_t lv_api_event; // NOLINT lv_event_code_t lv_api_event; // NOLINT
lv_event_code_t lv_update_event; // NOLINT lv_event_code_t lv_update_event; // NOLINT
void LvglComponent::dump_config() { void LvglComponent::dump_config() {
@@ -101,7 +113,10 @@ void LvglComponent::set_paused(bool paused, bool show_snow) {
lv_disp_trig_activity(this->disp_); // resets the inactivity time lv_disp_trig_activity(this->disp_); // resets the inactivity time
lv_obj_invalidate(lv_scr_act()); lv_obj_invalidate(lv_scr_act());
} }
this->pause_callbacks_.call(paused); if (paused && this->pause_callback_ != nullptr)
this->pause_callback_->trigger();
if (!paused && this->resume_callback_ != nullptr)
this->resume_callback_->trigger();
} }
void LvglComponent::esphome_lvgl_init() { void LvglComponent::esphome_lvgl_init() {
@@ -225,13 +240,6 @@ IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeo
}); });
} }
PauseTrigger::PauseTrigger(LvglComponent *parent, TemplatableValue<bool> paused) : paused_(std::move(paused)) {
parent->add_on_pause_callback([this](bool pausing) {
if (this->paused_.value() == pausing)
this->trigger();
});
}
#ifdef USE_LVGL_TOUCHSCREEN #ifdef USE_LVGL_TOUCHSCREEN
LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent) { LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent) {
this->set_parent(parent); this->set_parent(parent);
@@ -474,6 +482,12 @@ void LvglComponent::setup() {
return; return;
} }
} }
if (this->draw_start_callback_ != nullptr) {
this->disp_drv_.render_start_cb = render_start_cb;
}
if (this->draw_end_callback_ != nullptr) {
this->disp_drv_.monitor_cb = monitor_cb;
}
#if LV_USE_LOG #if LV_USE_LOG
lv_log_register_print_cb([](const char *buf) { lv_log_register_print_cb([](const char *buf) {
auto next = strchr(buf, ')'); auto next = strchr(buf, ')');
@@ -502,8 +516,9 @@ void LvglComponent::loop() {
if (this->paused_) { if (this->paused_) {
if (this->show_snow_) if (this->show_snow_)
this->write_random_(); this->write_random_();
} } else {
lv_timer_handler_run_in_period(5); lv_timer_handler_run_in_period(5);
}
} }
#ifdef USE_LVGL_ANIMIMG #ifdef USE_LVGL_ANIMIMG

View File

@@ -171,7 +171,9 @@ class LvglComponent : public PollingComponent {
void add_on_idle_callback(std::function<void(uint32_t)> &&callback) { void add_on_idle_callback(std::function<void(uint32_t)> &&callback) {
this->idle_callbacks_.add(std::move(callback)); this->idle_callbacks_.add(std::move(callback));
} }
void add_on_pause_callback(std::function<void(bool)> &&callback) { this->pause_callbacks_.add(std::move(callback)); }
static void monitor_cb(lv_disp_drv_t *disp_drv, uint32_t time, uint32_t px);
static void render_start_cb(lv_disp_drv_t *disp_drv);
void dump_config() override; void dump_config() override;
bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; } bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; }
lv_disp_t *get_disp() { return this->disp_; } lv_disp_t *get_disp() { return this->disp_; }
@@ -213,12 +215,20 @@ class LvglComponent : public PollingComponent {
size_t draw_rounding{2}; size_t draw_rounding{2};
display::DisplayRotation rotation{display::DISPLAY_ROTATION_0_DEGREES}; display::DisplayRotation rotation{display::DISPLAY_ROTATION_0_DEGREES};
void set_pause_trigger(Trigger<> *trigger) { this->pause_callback_ = trigger; }
void set_resume_trigger(Trigger<> *trigger) { this->resume_callback_ = trigger; }
void set_draw_start_trigger(Trigger<> *trigger) { this->draw_start_callback_ = trigger; }
void set_draw_end_trigger(Trigger<> *trigger) { this->draw_end_callback_ = trigger; }
protected: protected:
// these functions are never called unless the callbacks are non-null since the
// LVGL callbacks that call them are not set unless the start/end callbacks are non-null
void draw_start_() const { this->draw_start_callback_->trigger(); }
void draw_end_() const { this->draw_end_callback_->trigger(); }
void write_random_(); void write_random_();
void draw_buffer_(const lv_area_t *area, lv_color_t *ptr); void draw_buffer_(const lv_area_t *area, lv_color_t *ptr);
void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
std::vector<display::Display *> displays_{}; std::vector<display::Display *> displays_{};
size_t buffer_frac_{1}; size_t buffer_frac_{1};
bool full_refresh_{}; bool full_refresh_{};
@@ -235,7 +245,10 @@ class LvglComponent : public PollingComponent {
std::map<lv_group_t *, lv_obj_t *> focus_marks_{}; std::map<lv_group_t *, lv_obj_t *> focus_marks_{};
CallbackManager<void(uint32_t)> idle_callbacks_{}; CallbackManager<void(uint32_t)> idle_callbacks_{};
CallbackManager<void(bool)> pause_callbacks_{}; Trigger<> *pause_callback_{};
Trigger<> *resume_callback_{};
Trigger<> *draw_start_callback_{};
Trigger<> *draw_end_callback_{};
lv_color_t *rotate_buf_{}; lv_color_t *rotate_buf_{};
}; };
@@ -248,14 +261,6 @@ class IdleTrigger : public Trigger<> {
bool is_idle_{}; bool is_idle_{};
}; };
class PauseTrigger : public Trigger<> {
public:
explicit PauseTrigger(LvglComponent *parent, TemplatableValue<bool> paused);
protected:
TemplatableValue<bool> paused_;
};
template<typename... Ts> class LvglAction : public Action<Ts...>, public Parented<LvglComponent> { template<typename... Ts> class LvglAction : public Action<Ts...>, public Parented<LvglComponent> {
public: public:
explicit LvglAction(std::function<void(LvglComponent *)> &&lamb) : action_(std::move(lamb)) {} explicit LvglAction(std::function<void(LvglComponent *)> &&lamb) : action_(std::move(lamb)) {}

View File

@@ -3,6 +3,7 @@ import sys
from esphome import automation, codegen as cg from esphome import automation, codegen as cg
from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE
from esphome.cpp_generator import MockObj, MockObjClass from esphome.cpp_generator import MockObj, MockObjClass
from esphome.cpp_types import esphome_ns
from .defines import lvgl_ns from .defines import lvgl_ns
from .lvcode import lv_expr from .lvcode import lv_expr
@@ -42,8 +43,11 @@ lv_event_code_t = cg.global_ns.enum("lv_event_code_t")
lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t") lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t")
lv_key_t = cg.global_ns.enum("lv_key_t") lv_key_t = cg.global_ns.enum("lv_key_t")
FontEngine = lvgl_ns.class_("FontEngine") FontEngine = lvgl_ns.class_("FontEngine")
PlainTrigger = esphome_ns.class_("Trigger<>", automation.Trigger.template())
DrawEndTrigger = esphome_ns.class_(
"Trigger<uint32_t, uint32_t>", automation.Trigger.template(cg.uint32, cg.uint32)
)
IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template()) IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template())
PauseTrigger = lvgl_ns.class_("PauseTrigger", automation.Trigger.template())
ObjUpdateAction = lvgl_ns.class_("ObjUpdateAction", automation.Action) ObjUpdateAction = lvgl_ns.class_("ObjUpdateAction", automation.Action)
LvglCondition = lvgl_ns.class_("LvglCondition", automation.Condition) LvglCondition = lvgl_ns.class_("LvglCondition", automation.Condition)
LvglAction = lvgl_ns.class_("LvglAction", automation.Action) LvglAction = lvgl_ns.class_("LvglAction", automation.Action)

View File

@@ -68,5 +68,13 @@ lvgl:
enter_button: pushbutton enter_button: pushbutton
group: general group: general
initial_focus: lv_roller initial_focus: lv_roller
on_draw_start:
- logger.log: draw started
on_draw_end:
- logger.log: draw ended
- lvgl.pause:
- component.update: tft_display
- delay: 60s
- lvgl.resume:
<<: !include common.yaml <<: !include common.yaml