[lvgl] Rework events to avoid feedback loops (#7262)

This commit is contained in:
Clyde Stubbs 2024-08-14 12:05:25 +10:00 committed by GitHub
parent a5fdcb31fc
commit a0eff08f39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 108 additions and 29 deletions

View file

@ -17,6 +17,7 @@ from .defines import (
from .lv_validation import lv_bool, lv_color, lv_image from .lv_validation import lv_bool, lv_color, lv_image
from .lvcode import ( from .lvcode import (
LVGL_COMP_ARG, LVGL_COMP_ARG,
UPDATE_EVENT,
LambdaContext, LambdaContext,
LocalVariable, LocalVariable,
LvConditional, LvConditional,
@ -30,7 +31,6 @@ from .lvcode import (
) )
from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA
from .types import ( from .types import (
LV_EVENT,
LV_STATE, LV_STATE,
LvglAction, LvglAction,
LvglCondition, LvglCondition,
@ -64,7 +64,7 @@ async def update_to_code(config, action_id, template_arg, args):
widget.type.w_type.value_property is not None widget.type.w_type.value_property is not None
and widget.type.w_type.value_property in config and widget.type.w_type.value_property in config
): ):
lv.event_send(widget.obj, LV_EVENT.VALUE_CHANGED, nullptr) lv.event_send(widget.obj, UPDATE_EVENT, nullptr)
widgets = await get_widgets(config[CONF_ID]) widgets = await get_widgets(config[CONF_ID])
return await action_to_code(widgets, do_update, action_id, template_arg, args) return await action_to_code(widgets, do_update, action_id, template_arg, args)

View file

@ -470,6 +470,7 @@ CONF_TOP_LAYER = "top_layer"
CONF_TOUCHSCREENS = "touchscreens" CONF_TOUCHSCREENS = "touchscreens"
CONF_TRANSPARENCY_KEY = "transparency_key" CONF_TRANSPARENCY_KEY = "transparency_key"
CONF_THEME = "theme" CONF_THEME = "theme"
CONF_UPDATE_ON_RELEASE = "update_on_release"
CONF_VISIBLE_ROW_COUNT = "visible_row_count" CONF_VISIBLE_ROW_COUNT = "visible_row_count"
CONF_WIDGET = "widget" CONF_WIDGET = "widget"
CONF_WIDGETS = "widgets" CONF_WIDGETS = "widgets"

View file

@ -38,7 +38,7 @@ class LVLight : public light::LightOutput {
void set_value_(lv_color_t value) { void set_value_(lv_color_t value) {
lv_led_set_color(this->obj_, value); lv_led_set_color(this->obj_, value);
lv_led_on(this->obj_); lv_led_on(this->obj_);
lv_event_send(this->obj_, lv_custom_event, nullptr); lv_event_send(this->obj_, lv_api_event, nullptr);
} }
lv_obj_t *obj_{}; lv_obj_t *obj_{};
optional<lv_color_t> initial_value_{}; optional<lv_color_t> initial_value_{};

View file

@ -29,7 +29,11 @@ LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent)
LVGL_COMP_ARG = [(LvglComponent.operator("ptr"), LVGL_COMP)] LVGL_COMP_ARG = [(LvglComponent.operator("ptr"), LVGL_COMP)]
lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr") lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr")
EVENT_ARG = [(lv_event_t_ptr, "ev")] EVENT_ARG = [(lv_event_t_ptr, "ev")]
CUSTOM_EVENT = literal("lvgl::lv_custom_event") # Two custom events; API_EVENT is fired when an entity is updated remotely by an API interaction;
# UPDATE_EVENT is fired when an entity is programmatically updated locally.
# VALUE_CHANGED is the event generated by LVGL when an entity's value changes through user interaction.
API_EVENT = literal("lvgl::lv_api_event")
UPDATE_EVENT = literal("lvgl::lv_update_event")
def get_line_marks(value) -> list: def get_line_marks(value) -> list:

View file

@ -27,7 +27,8 @@ static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
area->y2++; area->y2++;
} }
lv_event_code_t lv_custom_event; // NOLINT lv_event_code_t lv_api_event; // NOLINT
lv_event_code_t lv_update_event; // NOLINT
void LvglComponent::dump_config() { ESP_LOGCONFIG(TAG, "LVGL:"); } void LvglComponent::dump_config() { ESP_LOGCONFIG(TAG, "LVGL:"); }
void LvglComponent::set_paused(bool paused, bool show_snow) { void LvglComponent::set_paused(bool paused, bool show_snow) {
this->paused_ = paused; this->paused_ = paused;
@ -40,15 +41,18 @@ void LvglComponent::set_paused(bool paused, bool show_snow) {
} }
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) { void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) {
lv_obj_add_event_cb(obj, callback, event, this); lv_obj_add_event_cb(obj, callback, event, this);
if (event == LV_EVENT_VALUE_CHANGED) {
lv_obj_add_event_cb(obj, callback, lv_custom_event, this);
}
} }
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1,
lv_event_code_t event2) { lv_event_code_t event2) {
this->add_event_cb(obj, callback, event1); this->add_event_cb(obj, callback, event1);
this->add_event_cb(obj, callback, event2); this->add_event_cb(obj, callback, event2);
} }
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1,
lv_event_code_t event2, lv_event_code_t event3) {
this->add_event_cb(obj, callback, event1);
this->add_event_cb(obj, callback, event2);
this->add_event_cb(obj, callback, event3);
}
void LvglComponent::add_page(LvPageType *page) { void LvglComponent::add_page(LvPageType *page) {
this->pages_.push_back(page); this->pages_.push_back(page);
page->setup(this->pages_.size() - 1); page->setup(this->pages_.size() - 1);
@ -228,7 +232,8 @@ void LvglComponent::setup() {
lv_log_register_print_cb(log_cb); lv_log_register_print_cb(log_cb);
#endif #endif
lv_init(); lv_init();
lv_custom_event = static_cast<lv_event_code_t>(lv_event_register_id()); lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id());
lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id());
auto *display = this->displays_[0]; auto *display = this->displays_[0];
size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_; size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_;
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8; auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;

View file

@ -38,7 +38,8 @@
namespace esphome { namespace esphome {
namespace lvgl { namespace lvgl {
extern lv_event_code_t lv_custom_event; // NOLINT extern lv_event_code_t lv_api_event; // NOLINT
extern lv_event_code_t lv_update_event; // NOLINT
#ifdef USE_LVGL_COLOR #ifdef USE_LVGL_COLOR
inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); } inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); }
#endif // USE_LVGL_COLOR #endif // USE_LVGL_COLOR
@ -133,6 +134,8 @@ class LvglComponent : public PollingComponent {
void set_paused(bool paused, bool show_snow); void set_paused(bool paused, bool show_snow);
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event); void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event);
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2); void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2);
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2,
lv_event_code_t event3);
bool is_paused() const { return this->paused_; } bool is_paused() const { return this->paused_; }
void add_page(LvPageType *page); void add_page(LvPageType *page);
void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time); void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time);

View file

@ -3,9 +3,17 @@ from esphome.components import number
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.cpp_generator import MockObj from esphome.cpp_generator import MockObj
from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_WIDGET from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_UPDATE_ON_RELEASE, CONF_WIDGET
from ..lv_validation import animated from ..lv_validation import animated
from ..lvcode import CUSTOM_EVENT, EVENT_ARG, LambdaContext, LvContext, lv, lv_add from ..lvcode import (
API_EVENT,
EVENT_ARG,
UPDATE_EVENT,
LambdaContext,
LvContext,
lv,
lv_add,
)
from ..schemas import LVGL_SCHEMA from ..schemas import LVGL_SCHEMA
from ..types import LV_EVENT, LvNumber, lvgl_ns from ..types import LV_EVENT, LvNumber, lvgl_ns
from ..widgets import get_widgets from ..widgets import get_widgets
@ -19,6 +27,7 @@ CONFIG_SCHEMA = (
{ {
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,
} }
) )
) )
@ -39,14 +48,19 @@ async def to_code(config):
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, CUSTOM_EVENT, cg.nullptr) lv.event_send(widget.obj, API_EVENT, cg.nullptr)
async with LambdaContext(EVENT_ARG) as event: async with LambdaContext(EVENT_ARG) as event:
event.add(var.publish_state(widget.get_value())) 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(paren): async with LvContext(paren):
lv_add(var.set_control_lambda(await control.get_lambda())) lv_add(var.set_control_lambda(await control.get_lambda()))
lv_add( lv_add(
paren.add_event_cb( paren.add_event_cb(
widget.obj, await event.get_lambda(), LV_EVENT.VALUE_CHANGED widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code
) )
) )
lv_add(var.publish_state(widget.get_value())) lv_add(var.publish_state(widget.get_value()))

View file

@ -4,7 +4,15 @@ import esphome.config_validation as cv
from esphome.const import CONF_OPTIONS from esphome.const import CONF_OPTIONS
from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_WIDGET from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_WIDGET
from ..lvcode import CUSTOM_EVENT, EVENT_ARG, LambdaContext, LvContext, lv, lv_add from ..lvcode import (
API_EVENT,
EVENT_ARG,
UPDATE_EVENT,
LambdaContext,
LvContext,
lv,
lv_add,
)
from ..schemas import LVGL_SCHEMA from ..schemas import LVGL_SCHEMA
from ..types import LV_EVENT, LvSelect, lvgl_ns from ..types import LV_EVENT, LvSelect, lvgl_ns
from ..widgets import get_widgets from ..widgets import get_widgets
@ -33,7 +41,7 @@ async def to_code(config):
pub_ctx.add(selector.publish_index(widget.get_value())) pub_ctx.add(selector.publish_index(widget.get_value()))
async with LambdaContext([(cg.uint16, "v")]) as control: async with LambdaContext([(cg.uint16, "v")]) as control:
await widget.set_property("selected", "v", animated=config[CONF_ANIMATED]) await widget.set_property("selected", "v", animated=config[CONF_ANIMATED])
lv.event_send(widget.obj, CUSTOM_EVENT, cg.nullptr) lv.event_send(widget.obj, API_EVENT, cg.nullptr)
async with LvContext(paren) as ctx: async with LvContext(paren) as ctx:
lv_add(selector.set_control_lambda(await control.get_lambda())) lv_add(selector.set_control_lambda(await control.get_lambda()))
ctx.add( ctx.add(
@ -41,6 +49,7 @@ async def to_code(config):
widget.obj, widget.obj,
await pub_ctx.get_lambda(), await pub_ctx.get_lambda(),
LV_EVENT.VALUE_CHANGED, LV_EVENT.VALUE_CHANGED,
UPDATE_EVENT,
) )
) )
lv_add(selector.publish_index(widget.get_value())) lv_add(selector.publish_index(widget.get_value()))

View file

@ -3,7 +3,15 @@ from esphome.components.sensor import Sensor, new_sensor, sensor_schema
import esphome.config_validation as cv import esphome.config_validation as cv
from ..defines import CONF_LVGL_ID, CONF_WIDGET from ..defines import CONF_LVGL_ID, CONF_WIDGET
from ..lvcode import EVENT_ARG, LVGL_COMP_ARG, LambdaContext, LvContext, lv_add from ..lvcode import (
API_EVENT,
EVENT_ARG,
LVGL_COMP_ARG,
UPDATE_EVENT,
LambdaContext,
LvContext,
lv_add,
)
from ..schemas import LVGL_SCHEMA from ..schemas import LVGL_SCHEMA
from ..types import LV_EVENT, LvNumber from ..types import LV_EVENT, LvNumber
from ..widgets import Widget, get_widgets from ..widgets import Widget, get_widgets
@ -30,6 +38,10 @@ async def to_code(config):
async with LvContext(paren, LVGL_COMP_ARG): async with LvContext(paren, LVGL_COMP_ARG):
lv_add( lv_add(
paren.add_event_cb( paren.add_event_cb(
widget.obj, await lamb.get_lambda(), LV_EVENT.VALUE_CHANGED widget.obj,
await lamb.get_lambda(),
LV_EVENT.VALUE_CHANGED,
API_EVENT,
UPDATE_EVENT,
) )
) )

View file

@ -5,8 +5,9 @@ from esphome.cpp_generator import MockObj
from ..defines import CONF_LVGL_ID, CONF_WIDGET from ..defines import CONF_LVGL_ID, CONF_WIDGET
from ..lvcode import ( from ..lvcode import (
CUSTOM_EVENT, API_EVENT,
EVENT_ARG, EVENT_ARG,
UPDATE_EVENT,
LambdaContext, LambdaContext,
LvConditional, LvConditional,
LvContext, LvContext,
@ -41,7 +42,7 @@ async def to_code(config):
widget.add_state(LV_STATE.CHECKED) widget.add_state(LV_STATE.CHECKED)
cond.else_() cond.else_()
widget.clear_state(LV_STATE.CHECKED) widget.clear_state(LV_STATE.CHECKED)
lv.event_send(widget.obj, CUSTOM_EVENT, cg.nullptr) lv.event_send(widget.obj, API_EVENT, cg.nullptr)
async with LvContext(paren) as ctx: async with LvContext(paren) as ctx:
lv_add(switch.set_control_lambda(await control.get_lambda())) lv_add(switch.set_control_lambda(await control.get_lambda()))
ctx.add( ctx.add(
@ -49,6 +50,7 @@ async def to_code(config):
widget.obj, widget.obj,
await checked_ctx.get_lambda(), await checked_ctx.get_lambda(),
LV_EVENT.VALUE_CHANGED, LV_EVENT.VALUE_CHANGED,
UPDATE_EVENT,
) )
) )
lv_add(switch.publish_state(widget.get_value())) lv_add(switch.publish_state(widget.get_value()))

View file

@ -4,7 +4,15 @@ from esphome.components.text import new_text
import esphome.config_validation as cv import esphome.config_validation as cv
from ..defines import CONF_LVGL_ID, CONF_WIDGET from ..defines import CONF_LVGL_ID, CONF_WIDGET
from ..lvcode import CUSTOM_EVENT, EVENT_ARG, LambdaContext, LvContext, lv, lv_add from ..lvcode import (
API_EVENT,
EVENT_ARG,
UPDATE_EVENT,
LambdaContext,
LvContext,
lv,
lv_add,
)
from ..schemas import LVGL_SCHEMA from ..schemas import LVGL_SCHEMA
from ..types import LV_EVENT, LvText, lvgl_ns from ..types import LV_EVENT, LvText, lvgl_ns
from ..widgets import get_widgets from ..widgets import get_widgets
@ -26,14 +34,17 @@ async def to_code(config):
widget = widget[0] widget = widget[0]
async with LambdaContext([(cg.std_string, "text_value")]) as control: async with LambdaContext([(cg.std_string, "text_value")]) as control:
await widget.set_property("text", "text_value.c_str())") await widget.set_property("text", "text_value.c_str())")
lv.event_send(widget.obj, CUSTOM_EVENT, None) lv.event_send(widget.obj, API_EVENT, None)
async with LambdaContext(EVENT_ARG) as lamb: async with LambdaContext(EVENT_ARG) as lamb:
lv_add(textvar.publish_state(widget.get_value())) lv_add(textvar.publish_state(widget.get_value()))
async with LvContext(paren): async with LvContext(paren):
widget.var.set_control_lambda(await control.get_lambda()) widget.var.set_control_lambda(await control.get_lambda())
lv_add( lv_add(
paren.add_event_cb( paren.add_event_cb(
widget.obj, await lamb.get_lambda(), LV_EVENT.VALUE_CHANGED widget.obj,
await lamb.get_lambda(),
LV_EVENT.VALUE_CHANGED,
UPDATE_EVENT,
) )
) )
lv_add(textvar.publish_state(widget.get_value())) lv_add(textvar.publish_state(widget.get_value()))

View file

@ -7,7 +7,7 @@ from esphome.components.text_sensor import (
import esphome.config_validation as cv import esphome.config_validation as cv
from ..defines import CONF_LVGL_ID, CONF_WIDGET from ..defines import CONF_LVGL_ID, CONF_WIDGET
from ..lvcode import EVENT_ARG, LambdaContext, LvContext from ..lvcode import API_EVENT, EVENT_ARG, UPDATE_EVENT, LambdaContext, LvContext
from ..schemas import LVGL_SCHEMA from ..schemas import LVGL_SCHEMA
from ..types import LV_EVENT, LvText from ..types import LV_EVENT, LvText
from ..widgets import get_widgets from ..widgets import get_widgets
@ -36,5 +36,7 @@ async def to_code(config):
widget.obj, widget.obj,
await pressed_ctx.get_lambda(), await pressed_ctx.get_lambda(),
LV_EVENT.VALUE_CHANGED, LV_EVENT.VALUE_CHANGED,
API_EVENT,
UPDATE_EVENT,
) )
) )

View file

@ -11,7 +11,15 @@ from .defines import (
LV_EVENT_TRIGGERS, LV_EVENT_TRIGGERS,
literal, literal,
) )
from .lvcode import EVENT_ARG, LambdaContext, LvConditional, lv, lv_add from .lvcode import (
API_EVENT,
EVENT_ARG,
UPDATE_EVENT,
LambdaContext,
LvConditional,
lv,
lv_add,
)
from .types import LV_EVENT from .types import LV_EVENT
from .widgets import widget_map from .widgets import widget_map
@ -34,9 +42,16 @@ async def generate_triggers(lv_component):
conf = conf[0] conf = conf[0]
w.add_flag("LV_OBJ_FLAG_CLICKABLE") w.add_flag("LV_OBJ_FLAG_CLICKABLE")
event = literal("LV_EVENT_" + LV_EVENT_MAP[event[3:].upper()]) event = literal("LV_EVENT_" + LV_EVENT_MAP[event[3:].upper()])
await add_trigger(conf, event, lv_component, w) await add_trigger(conf, lv_component, w, event)
for conf in w.config.get(CONF_ON_VALUE, ()): for conf in w.config.get(CONF_ON_VALUE, ()):
await add_trigger(conf, LV_EVENT.VALUE_CHANGED, lv_component, w) await add_trigger(
conf,
lv_component,
w,
LV_EVENT.VALUE_CHANGED,
API_EVENT,
UPDATE_EVENT,
)
# Generate align to directives while we're here # Generate align to directives while we're here
if align_to := w.config.get(CONF_ALIGN_TO): if align_to := w.config.get(CONF_ALIGN_TO):
@ -47,7 +62,7 @@ async def generate_triggers(lv_component):
lv.obj_align_to(w.obj, target, align, x, y) lv.obj_align_to(w.obj, target, align, x, y)
async def add_trigger(conf, event, lv_component, w): async def add_trigger(conf, lv_component, w, *events):
tid = conf[CONF_TRIGGER_ID] tid = conf[CONF_TRIGGER_ID]
trigger = cg.new_Pvariable(tid) trigger = cg.new_Pvariable(tid)
args = w.get_args() args = w.get_args()
@ -56,4 +71,4 @@ async def add_trigger(conf, event, lv_component, w):
async with LambdaContext(EVENT_ARG, where=tid) as context: async with LambdaContext(EVENT_ARG, where=tid) as context:
with LvConditional(w.is_selected()): with LvConditional(w.is_selected()):
lv_add(trigger.trigger(value)) lv_add(trigger.trigger(value))
lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), event)) lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), *events))

View file

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