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:
@@ -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
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)) {}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user