mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 00:40:21 +00:00
[lvgl] Use contextvars to improve lvgl scheduling (#19242)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e111f1130c
commit
5baad05926
@@ -0,0 +1,28 @@
|
||||
esphome:
|
||||
name: test-keyboard-before-textarea
|
||||
|
||||
esp32:
|
||||
board: lolin_c3_mini
|
||||
|
||||
spi:
|
||||
mosi_pin:
|
||||
number: GPIO2
|
||||
ignore_strapping_warning: true
|
||||
clk_pin: GPIO1
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
data_rate: 20MHz
|
||||
model: st7735
|
||||
cs_pin:
|
||||
number: GPIO8
|
||||
ignore_strapping_warning: true
|
||||
dc_pin: GPIO3
|
||||
|
||||
lvgl:
|
||||
widgets:
|
||||
- keyboard:
|
||||
id: kb
|
||||
textarea: ta
|
||||
- textarea:
|
||||
id: ta
|
||||
@@ -0,0 +1,34 @@
|
||||
esphome:
|
||||
name: test-keyboard-update-textarea
|
||||
on_boot:
|
||||
- lvgl.keyboard.update:
|
||||
id: kb
|
||||
textarea: ta2
|
||||
|
||||
esp32:
|
||||
board: lolin_c3_mini
|
||||
|
||||
spi:
|
||||
mosi_pin:
|
||||
number: GPIO2
|
||||
ignore_strapping_warning: true
|
||||
clk_pin: GPIO1
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
data_rate: 20MHz
|
||||
model: st7735
|
||||
cs_pin:
|
||||
number: GPIO8
|
||||
ignore_strapping_warning: true
|
||||
dc_pin: GPIO3
|
||||
|
||||
lvgl:
|
||||
widgets:
|
||||
- keyboard:
|
||||
id: kb
|
||||
textarea: ta1
|
||||
- textarea:
|
||||
id: ta1
|
||||
- textarea:
|
||||
id: ta2
|
||||
@@ -0,0 +1,35 @@
|
||||
esphome:
|
||||
name: test-multi-instance-theme
|
||||
|
||||
host:
|
||||
mac_address: "62:23:45:AF:B3:DD"
|
||||
|
||||
display:
|
||||
- platform: snapshot
|
||||
id: sdl0
|
||||
dimensions:
|
||||
width: 100
|
||||
height: 100
|
||||
- platform: snapshot
|
||||
id: sdl1
|
||||
dimensions:
|
||||
width: 100
|
||||
height: 100
|
||||
|
||||
lvgl:
|
||||
- id: lvgl_0
|
||||
displays: sdl0
|
||||
widgets:
|
||||
- label:
|
||||
id: label_0
|
||||
text: "First"
|
||||
|
||||
- id: lvgl_1
|
||||
displays: sdl1
|
||||
theme:
|
||||
label:
|
||||
bg_color: 0xFF0000
|
||||
widgets:
|
||||
- label:
|
||||
id: label_1
|
||||
text: "Second"
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Regression test: a keyboard: declared before its textarea: sibling must
|
||||
still get attached to it, and only after both widgets exist.
|
||||
|
||||
attach_textareas() runs as a deferred pass, after every widget (across every
|
||||
LVGL instance) is created, so it must emit the attach call after the
|
||||
keyboard's own creation statement, not inline during widget creation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.__main__ import generate_cpp_contents
|
||||
from esphome.config import read_config
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def main_cpp(request: pytest.FixtureRequest) -> str:
|
||||
config_path = (
|
||||
Path(request.fspath).parent / "config" / "keyboard_before_textarea_test.yaml"
|
||||
)
|
||||
original_path = CORE.config_path
|
||||
try:
|
||||
CORE.config_path = config_path
|
||||
CORE.config = read_config({})
|
||||
generate_cpp_contents(CORE.config)
|
||||
return CORE.cpp_main_section
|
||||
finally:
|
||||
CORE.config_path = original_path
|
||||
CORE.reset()
|
||||
|
||||
|
||||
def test_keyboard_and_textarea_are_created(main_cpp: str) -> None:
|
||||
assert "lv_keyboard_create(" in main_cpp
|
||||
assert "lv_textarea_create(" in main_cpp
|
||||
|
||||
|
||||
def test_attach_call_runs_after_keyboard_and_textarea_are_created(
|
||||
main_cpp: str,
|
||||
) -> None:
|
||||
attach_index = main_cpp.find("lv_keyboard_set_textarea(kb->obj, ta);")
|
||||
assert attach_index != -1, "keyboard was never attached to its textarea"
|
||||
assert attach_index > main_cpp.find("lv_keyboard_create(")
|
||||
assert attach_index > main_cpp.find("lv_textarea_create(")
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Regression test: lvgl.keyboard.update must be able to change which
|
||||
textarea a keyboard is attached to.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.__main__ import generate_cpp_contents
|
||||
from esphome.config import read_config
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def main_cpp(request: pytest.FixtureRequest) -> str:
|
||||
config_path = (
|
||||
Path(request.fspath).parent / "config" / "keyboard_update_textarea_test.yaml"
|
||||
)
|
||||
original_path = CORE.config_path
|
||||
try:
|
||||
CORE.config_path = config_path
|
||||
CORE.config = read_config({})
|
||||
generate_cpp_contents(CORE.config)
|
||||
return CORE.cpp_main_section
|
||||
finally:
|
||||
CORE.config_path = original_path
|
||||
CORE.reset()
|
||||
|
||||
|
||||
def test_keyboard_update_changes_textarea(main_cpp: str) -> None:
|
||||
assert "lv_keyboard_set_textarea(kb->obj, ta2);" in main_cpp
|
||||
@@ -286,7 +286,7 @@ def test_add_text_with_index_moves_before_firing_on_add(main_cpp: str) -> None:
|
||||
|
||||
|
||||
def test_add_button_with_checkable_flag(main_cpp: str) -> None:
|
||||
assert "lv_obj_t *dyn_button_VAR_ = lv_btn_create(test_list);" in main_cpp
|
||||
assert "lv_obj_t *dyn_button_VAR_ = lv_button_create(test_list);" in main_cpp
|
||||
assert (
|
||||
"lv_obj_add_flag(dyn_button_VAR_, (lv_obj_flag_t)(LV_OBJ_FLAG_CHECKABLE));"
|
||||
in main_cpp
|
||||
@@ -310,7 +310,7 @@ def test_add_nested_hierarchy_with_compound_child(main_cpp: str) -> None:
|
||||
assert (
|
||||
"lv_obj_t *dyn_label_1_VAR_ = lv_label_create(dyn_obj_VAR_);\n"
|
||||
" lv_obj_add_style(dyn_label_1_VAR_, _lv_theme_style_label_main_default, "
|
||||
"(lv_state_t)(LV_PART_MAIN));\n"
|
||||
"LV_PART_MAIN);\n"
|
||||
' lv_label_set_text(dyn_label_1_VAR_, "Nested");'
|
||||
) in main_cpp
|
||||
|
||||
@@ -321,7 +321,7 @@ def test_add_applies_theme_styles_to_dynamic_widget(main_cpp: str) -> None:
|
||||
"""
|
||||
assert (
|
||||
"lv_obj_add_style(dyn_label_1_VAR_, _lv_theme_style_label_main_default, "
|
||||
"(lv_state_t)(LV_PART_MAIN));"
|
||||
"LV_PART_MAIN);"
|
||||
) in main_cpp
|
||||
assert "LvDropdownType *dyn_dropdown_1_VAR_ = new LvDropdownType();" in main_cpp
|
||||
assert "lv_dropdown_create(dyn_obj_VAR_)" in main_cpp
|
||||
@@ -343,7 +343,7 @@ def test_add_nested_same_type_child_does_not_shadow_parent(main_cpp: str) -> Non
|
||||
assert (
|
||||
"lv_obj_t *dyn_label_2_VAR_ = lv_label_create(dyn_obj_1_VAR_);\n"
|
||||
" lv_obj_add_style(dyn_label_2_VAR_, _lv_theme_style_label_main_default, "
|
||||
"(lv_state_t)(LV_PART_MAIN));\n"
|
||||
"LV_PART_MAIN);\n"
|
||||
' lv_label_set_text(dyn_label_2_VAR_, "Grandchild");'
|
||||
) in main_cpp
|
||||
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
"""Regression test: on_add:/on_remove: containing an lvgl action must not deadlock.
|
||||
|
||||
ListType.to_code() used to build the on_add/on_remove automations directly, during
|
||||
widget creation. Every lvgl action's to_code awaits wait_for_widgets(), which only
|
||||
resolves once *all* widgets - including the list itself - have finished being
|
||||
created. Building an automation containing an lvgl action from inside that same
|
||||
widget-creation walk therefore could never complete: codegen deadlocked with
|
||||
"Circular dependency detected!". Fixed by deferring the actual build_automation()
|
||||
call to finish_list_triggers(), run after set_widgets_completed(True) - and,
|
||||
critically, before generate_triggers(), which is what processes other widgets'
|
||||
on_click etc. automations that might reference this list (e.g. via lvgl.list.add),
|
||||
and which therefore need the list's own on_add/on_remove triggers to already exist.
|
||||
"""
|
||||
"""Regression test: on_add:/on_remove: containing an lvgl action must not deadlock."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Regression test: a widget's theme style must be attached regardless of
|
||||
which LVGL instance declares theme: and which instance's widgets are built
|
||||
first.
|
||||
|
||||
theme_to_code() runs once per LVGL instance, interleaved with that instance's
|
||||
own add_widgets(). A widget built by an earlier instance, before any instance
|
||||
has declared theming for its type, must still pick up a later instance's
|
||||
theme: declaration for that type - including on the later instance's own
|
||||
widgets, which get_widget_theme_styles()'s memoisation can poison too.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.__main__ import generate_cpp_contents
|
||||
from esphome.config import read_config
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def main_cpp(request: pytest.FixtureRequest) -> str:
|
||||
config_path = (
|
||||
Path(request.fspath).parent / "config" / "multi_instance_theme_test.yaml"
|
||||
)
|
||||
original_path = CORE.config_path
|
||||
try:
|
||||
CORE.config_path = config_path
|
||||
CORE.config = read_config({})
|
||||
generate_cpp_contents(CORE.config)
|
||||
return CORE.cpp_main_section
|
||||
finally:
|
||||
CORE.config_path = original_path
|
||||
CORE.reset()
|
||||
|
||||
|
||||
def test_earlier_instance_widget_is_themed(main_cpp: str) -> None:
|
||||
assert "lv_obj_add_style(label_0, _lv_theme_style_label_main_default," in main_cpp
|
||||
|
||||
|
||||
def test_later_instance_widget_is_themed(main_cpp: str) -> None:
|
||||
assert "lv_obj_add_style(label_1, _lv_theme_style_label_main_default," in main_cpp
|
||||
Reference in New Issue
Block a user