mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
[lvgl] Rework events to avoid feedback loops (#7262)
This commit is contained in:
parent
a5fdcb31fc
commit
a0eff08f39
14 changed files with 108 additions and 29 deletions
|
@ -17,6 +17,7 @@ from .defines import (
|
|||
from .lv_validation import lv_bool, lv_color, lv_image
|
||||
from .lvcode import (
|
||||
LVGL_COMP_ARG,
|
||||
UPDATE_EVENT,
|
||||
LambdaContext,
|
||||
LocalVariable,
|
||||
LvConditional,
|
||||
|
@ -30,7 +31,6 @@ from .lvcode import (
|
|||
)
|
||||
from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA
|
||||
from .types import (
|
||||
LV_EVENT,
|
||||
LV_STATE,
|
||||
LvglAction,
|
||||
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
|
||||
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])
|
||||
return await action_to_code(widgets, do_update, action_id, template_arg, args)
|
||||
|
|
|
@ -470,6 +470,7 @@ CONF_TOP_LAYER = "top_layer"
|
|||
CONF_TOUCHSCREENS = "touchscreens"
|
||||
CONF_TRANSPARENCY_KEY = "transparency_key"
|
||||
CONF_THEME = "theme"
|
||||
CONF_UPDATE_ON_RELEASE = "update_on_release"
|
||||
CONF_VISIBLE_ROW_COUNT = "visible_row_count"
|
||||
CONF_WIDGET = "widget"
|
||||
CONF_WIDGETS = "widgets"
|
||||
|
|
|
@ -38,7 +38,7 @@ class LVLight : public light::LightOutput {
|
|||
void set_value_(lv_color_t value) {
|
||||
lv_led_set_color(this->obj_, value);
|
||||
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_{};
|
||||
optional<lv_color_t> initial_value_{};
|
||||
|
|
|
@ -29,7 +29,11 @@ LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent)
|
|||
LVGL_COMP_ARG = [(LvglComponent.operator("ptr"), LVGL_COMP)]
|
||||
lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr")
|
||||
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:
|
||||
|
|
|
@ -27,7 +27,8 @@ static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
|
|||
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::set_paused(bool paused, bool show_snow) {
|
||||
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) {
|
||||
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,
|
||||
lv_event_code_t event2) {
|
||||
this->add_event_cb(obj, callback, event1);
|
||||
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) {
|
||||
this->pages_.push_back(page);
|
||||
page->setup(this->pages_.size() - 1);
|
||||
|
@ -228,7 +232,8 @@ void LvglComponent::setup() {
|
|||
lv_log_register_print_cb(log_cb);
|
||||
#endif
|
||||
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];
|
||||
size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_;
|
||||
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
|
||||
|
|
|
@ -38,7 +38,8 @@
|
|||
namespace esphome {
|
||||
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
|
||||
inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); }
|
||||
#endif // USE_LVGL_COLOR
|
||||
|
@ -133,6 +134,8 @@ class LvglComponent : public PollingComponent {
|
|||
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 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_; }
|
||||
void add_page(LvPageType *page);
|
||||
void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time);
|
||||
|
|
|
@ -3,9 +3,17 @@ from esphome.components import number
|
|||
import esphome.config_validation as cv
|
||||
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 ..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 ..types import LV_EVENT, LvNumber, lvgl_ns
|
||||
from ..widgets import get_widgets
|
||||
|
@ -19,6 +27,7 @@ CONFIG_SCHEMA = (
|
|||
{
|
||||
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,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -39,14 +48,19 @@ async def to_code(config):
|
|||
await widget.set_property(
|
||||
"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:
|
||||
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):
|
||||
lv_add(var.set_control_lambda(await control.get_lambda()))
|
||||
lv_add(
|
||||
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()))
|
||||
|
|
|
@ -4,7 +4,15 @@ import esphome.config_validation as cv
|
|||
from esphome.const import CONF_OPTIONS
|
||||
|
||||
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 ..types import LV_EVENT, LvSelect, lvgl_ns
|
||||
from ..widgets import get_widgets
|
||||
|
@ -33,7 +41,7 @@ async def to_code(config):
|
|||
pub_ctx.add(selector.publish_index(widget.get_value()))
|
||||
async with LambdaContext([(cg.uint16, "v")]) as control:
|
||||
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:
|
||||
lv_add(selector.set_control_lambda(await control.get_lambda()))
|
||||
ctx.add(
|
||||
|
@ -41,6 +49,7 @@ async def to_code(config):
|
|||
widget.obj,
|
||||
await pub_ctx.get_lambda(),
|
||||
LV_EVENT.VALUE_CHANGED,
|
||||
UPDATE_EVENT,
|
||||
)
|
||||
)
|
||||
lv_add(selector.publish_index(widget.get_value()))
|
||||
|
|
|
@ -3,7 +3,15 @@ from esphome.components.sensor import Sensor, new_sensor, sensor_schema
|
|||
import esphome.config_validation as cv
|
||||
|
||||
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 ..types import LV_EVENT, LvNumber
|
||||
from ..widgets import Widget, get_widgets
|
||||
|
@ -30,6 +38,10 @@ async def to_code(config):
|
|||
async with LvContext(paren, LVGL_COMP_ARG):
|
||||
lv_add(
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -5,8 +5,9 @@ from esphome.cpp_generator import MockObj
|
|||
|
||||
from ..defines import CONF_LVGL_ID, CONF_WIDGET
|
||||
from ..lvcode import (
|
||||
CUSTOM_EVENT,
|
||||
API_EVENT,
|
||||
EVENT_ARG,
|
||||
UPDATE_EVENT,
|
||||
LambdaContext,
|
||||
LvConditional,
|
||||
LvContext,
|
||||
|
@ -41,7 +42,7 @@ async def to_code(config):
|
|||
widget.add_state(LV_STATE.CHECKED)
|
||||
cond.else_()
|
||||
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:
|
||||
lv_add(switch.set_control_lambda(await control.get_lambda()))
|
||||
ctx.add(
|
||||
|
@ -49,6 +50,7 @@ async def to_code(config):
|
|||
widget.obj,
|
||||
await checked_ctx.get_lambda(),
|
||||
LV_EVENT.VALUE_CHANGED,
|
||||
UPDATE_EVENT,
|
||||
)
|
||||
)
|
||||
lv_add(switch.publish_state(widget.get_value()))
|
||||
|
|
|
@ -4,7 +4,15 @@ from esphome.components.text import new_text
|
|||
import esphome.config_validation as cv
|
||||
|
||||
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 ..types import LV_EVENT, LvText, lvgl_ns
|
||||
from ..widgets import get_widgets
|
||||
|
@ -26,14 +34,17 @@ async def to_code(config):
|
|||
widget = widget[0]
|
||||
async with LambdaContext([(cg.std_string, "text_value")]) as control:
|
||||
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:
|
||||
lv_add(textvar.publish_state(widget.get_value()))
|
||||
async with LvContext(paren):
|
||||
widget.var.set_control_lambda(await control.get_lambda())
|
||||
lv_add(
|
||||
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()))
|
||||
|
|
|
@ -7,7 +7,7 @@ from esphome.components.text_sensor import (
|
|||
import esphome.config_validation as cv
|
||||
|
||||
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 ..types import LV_EVENT, LvText
|
||||
from ..widgets import get_widgets
|
||||
|
@ -36,5 +36,7 @@ async def to_code(config):
|
|||
widget.obj,
|
||||
await pressed_ctx.get_lambda(),
|
||||
LV_EVENT.VALUE_CHANGED,
|
||||
API_EVENT,
|
||||
UPDATE_EVENT,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -11,7 +11,15 @@ from .defines import (
|
|||
LV_EVENT_TRIGGERS,
|
||||
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 .widgets import widget_map
|
||||
|
||||
|
@ -34,9 +42,16 @@ async def generate_triggers(lv_component):
|
|||
conf = conf[0]
|
||||
w.add_flag("LV_OBJ_FLAG_CLICKABLE")
|
||||
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, ()):
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
async def add_trigger(conf, event, lv_component, w):
|
||||
async def add_trigger(conf, lv_component, w, *events):
|
||||
tid = conf[CONF_TRIGGER_ID]
|
||||
trigger = cg.new_Pvariable(tid)
|
||||
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:
|
||||
with LvConditional(w.is_selected()):
|
||||
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))
|
||||
|
|
|
@ -75,6 +75,7 @@ number:
|
|||
- platform: lvgl
|
||||
widget: slider_id
|
||||
name: LVGL Slider
|
||||
update_on_release: true
|
||||
- platform: lvgl
|
||||
widget: lv_arc
|
||||
id: lvgl_arc_number
|
||||
|
|
Loading…
Reference in a new issue