mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[lvgl] Revise code generation to allow early widget creation (#7611)
This commit is contained in:
		| @@ -22,7 +22,7 @@ from esphome.helpers import write_file_if_changed | |||||||
|  |  | ||||||
| from . import defines as df, helpers, lv_validation as lvalid | from . import defines as df, helpers, lv_validation as lvalid | ||||||
| from .automation import disp_update, focused_widgets, update_to_code | from .automation import disp_update, focused_widgets, update_to_code | ||||||
| from .defines import CONF_WIDGETS, add_define | from .defines import add_define | ||||||
| from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code | from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code | ||||||
| from .gradient import GRADIENT_SCHEMA, gradients_to_code | from .gradient import GRADIENT_SCHEMA, gradients_to_code | ||||||
| from .hello_world import get_hello_world | from .hello_world import get_hello_world | ||||||
| @@ -54,7 +54,7 @@ from .types import ( | |||||||
|     lv_style_t, |     lv_style_t, | ||||||
|     lvgl_ns, |     lvgl_ns, | ||||||
| ) | ) | ||||||
| from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties, styles_used | from .widgets import Widget, add_widgets, get_scr_act, set_obj_properties, styles_used | ||||||
| from .widgets.animimg import animimg_spec | from .widgets.animimg import animimg_spec | ||||||
| from .widgets.arc import arc_spec | from .widgets.arc import arc_spec | ||||||
| from .widgets.button import button_spec | from .widgets.button import button_spec | ||||||
| @@ -186,7 +186,7 @@ def final_validation(config): | |||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     cg.add_library("lvgl/lvgl", "8.4.0") |     cg.add_library("lvgl/lvgl", "8.4.0") | ||||||
|     CORE.add_define("USE_LVGL") |     cg.add_define("USE_LVGL") | ||||||
|     # suppress default enabling of extra widgets |     # suppress default enabling of extra widgets | ||||||
|     add_define("_LV_KCONFIG_PRESENT") |     add_define("_LV_KCONFIG_PRESENT") | ||||||
|     # Always enable - lots of things use it. |     # Always enable - lots of things use it. | ||||||
| @@ -200,7 +200,13 @@ async def to_code(config): | |||||||
|     add_define("LV_MEM_CUSTOM_REALLOC", "lv_custom_mem_realloc") |     add_define("LV_MEM_CUSTOM_REALLOC", "lv_custom_mem_realloc") | ||||||
|     add_define("LV_MEM_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"') |     add_define("LV_MEM_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"') | ||||||
|  |  | ||||||
|     add_define("LV_LOG_LEVEL", f"LV_LOG_LEVEL_{config[df.CONF_LOG_LEVEL]}") |     add_define( | ||||||
|  |         "LV_LOG_LEVEL", f"LV_LOG_LEVEL_{df.LV_LOG_LEVELS[config[df.CONF_LOG_LEVEL]]}" | ||||||
|  |     ) | ||||||
|  |     cg.add_define( | ||||||
|  |         "LVGL_LOG_LEVEL", | ||||||
|  |         cg.RawExpression(f"ESPHOME_LOG_LEVEL_{config[df.CONF_LOG_LEVEL]}"), | ||||||
|  |     ) | ||||||
|     add_define("LV_COLOR_DEPTH", config[df.CONF_COLOR_DEPTH]) |     add_define("LV_COLOR_DEPTH", config[df.CONF_COLOR_DEPTH]) | ||||||
|     for font in helpers.lv_fonts_used: |     for font in helpers.lv_fonts_used: | ||||||
|         add_define(f"LV_FONT_{font.upper()}") |         add_define(f"LV_FONT_{font.upper()}") | ||||||
| @@ -214,15 +220,9 @@ async def to_code(config): | |||||||
|         "LV_COLOR_CHROMA_KEY", |         "LV_COLOR_CHROMA_KEY", | ||||||
|         await lvalid.lv_color.process(config[df.CONF_TRANSPARENCY_KEY]), |         await lvalid.lv_color.process(config[df.CONF_TRANSPARENCY_KEY]), | ||||||
|     ) |     ) | ||||||
|     CORE.add_build_flag("-Isrc") |     cg.add_build_flag("-Isrc") | ||||||
|  |  | ||||||
|     cg.add_global(lvgl_ns.using) |     cg.add_global(lvgl_ns.using) | ||||||
|     lv_component = cg.new_Pvariable(config[CONF_ID]) |  | ||||||
|     await cg.register_component(lv_component, config) |  | ||||||
|     Widget.create(config[CONF_ID], lv_component, obj_spec, config) |  | ||||||
|     for display in config[df.CONF_DISPLAYS]: |  | ||||||
|         cg.add(lv_component.add_display(await cg.get_variable(display))) |  | ||||||
|  |  | ||||||
|     frac = config[CONF_BUFFER_SIZE] |     frac = config[CONF_BUFFER_SIZE] | ||||||
|     if frac >= 0.75: |     if frac >= 0.75: | ||||||
|         frac = 1 |         frac = 1 | ||||||
| @@ -232,10 +232,17 @@ async def to_code(config): | |||||||
|         frac = 4 |         frac = 4 | ||||||
|     else: |     else: | ||||||
|         frac = 8 |         frac = 8 | ||||||
|     cg.add(lv_component.set_buffer_frac(int(frac))) |     displays = [await cg.get_variable(display) for display in config[df.CONF_DISPLAYS]] | ||||||
|     cg.add(lv_component.set_full_refresh(config[df.CONF_FULL_REFRESH])) |     lv_component = cg.new_Pvariable( | ||||||
|     cg.add(lv_component.set_draw_rounding(config[df.CONF_DRAW_ROUNDING])) |         config[CONF_ID], | ||||||
|     cg.add(lv_component.set_resume_on_input(config[df.CONF_RESUME_ON_INPUT])) |         displays, | ||||||
|  |         frac, | ||||||
|  |         config[df.CONF_FULL_REFRESH], | ||||||
|  |         config[df.CONF_DRAW_ROUNDING], | ||||||
|  |         config[df.CONF_RESUME_ON_INPUT], | ||||||
|  |     ) | ||||||
|  |     await cg.register_component(lv_component, config) | ||||||
|  |     Widget.create(config[CONF_ID], lv_component, obj_spec, config) | ||||||
|  |  | ||||||
|     for font in helpers.esphome_fonts_used: |     for font in helpers.esphome_fonts_used: | ||||||
|         await cg.get_variable(font) |         await cg.get_variable(font) | ||||||
| @@ -257,6 +264,7 @@ async def to_code(config): | |||||||
|     else: |     else: | ||||||
|         add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font)) |         add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font)) | ||||||
|  |  | ||||||
|  |     lv_scr_act = get_scr_act(lv_component) | ||||||
|     async with LvContext(lv_component): |     async with LvContext(lv_component): | ||||||
|         await touchscreens_to_code(lv_component, config) |         await touchscreens_to_code(lv_component, config) | ||||||
|         await encoders_to_code(lv_component, config) |         await encoders_to_code(lv_component, config) | ||||||
| @@ -266,11 +274,9 @@ async def to_code(config): | |||||||
|         await set_obj_properties(lv_scr_act, config) |         await set_obj_properties(lv_scr_act, config) | ||||||
|         await add_widgets(lv_scr_act, config) |         await add_widgets(lv_scr_act, config) | ||||||
|         await add_pages(lv_component, config) |         await add_pages(lv_component, config) | ||||||
|         await add_top_layer(config) |         await add_top_layer(lv_component, config) | ||||||
|         await msgboxes_to_code(config) |         await msgboxes_to_code(lv_component, config) | ||||||
|         await disp_update(f"{lv_component}->get_disp()", config) |         await disp_update(lv_component.get_disp(), config) | ||||||
|     # At this point only the setup code should be generated |  | ||||||
|     assert LvContext.added_lambda_count == 1 |  | ||||||
|     # Set this directly since we are limited in how many methods can be added to the Widget class. |     # Set this directly since we are limited in how many methods can be added to the Widget class. | ||||||
|     Widget.widgets_completed = True |     Widget.widgets_completed = True | ||||||
|     async with LvContext(lv_component): |     async with LvContext(lv_component): | ||||||
| @@ -291,15 +297,15 @@ async def to_code(config): | |||||||
|             await build_automation(resume_trigger, [], conf) |             await build_automation(resume_trigger, [], conf) | ||||||
|  |  | ||||||
|     for comp in helpers.lvgl_components_required: |     for comp in helpers.lvgl_components_required: | ||||||
|         CORE.add_define(f"USE_LVGL_{comp.upper()}") |         cg.add_define(f"USE_LVGL_{comp.upper()}") | ||||||
|     if "transform_angle" in styles_used: |     if "transform_angle" in styles_used: | ||||||
|         add_define("LV_COLOR_SCREEN_TRANSP", "1") |         add_define("LV_COLOR_SCREEN_TRANSP", "1") | ||||||
|     for use in helpers.lv_uses: |     for use in helpers.lv_uses: | ||||||
|         add_define(f"LV_USE_{use.upper()}") |         add_define(f"LV_USE_{use.upper()}") | ||||||
|     lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME) |     lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME) | ||||||
|     write_file_if_changed(lv_conf_h_file, generate_lv_conf_h()) |     write_file_if_changed(lv_conf_h_file, generate_lv_conf_h()) | ||||||
|     CORE.add_build_flag("-DLV_CONF_H=1") |     cg.add_build_flag("-DLV_CONF_H=1") | ||||||
|     CORE.add_build_flag(f'-DLV_CONF_PATH="{LV_CONF_FILENAME}"') |     cg.add_build_flag(f'-DLV_CONF_PATH="{LV_CONF_FILENAME}"') | ||||||
|  |  | ||||||
|  |  | ||||||
| def display_schema(config): | def display_schema(config): | ||||||
| @@ -308,9 +314,9 @@ def display_schema(config): | |||||||
|  |  | ||||||
|  |  | ||||||
| def add_hello_world(config): | def add_hello_world(config): | ||||||
|     if CONF_WIDGETS not in config and CONF_PAGES not in config: |     if df.CONF_WIDGETS not in config and CONF_PAGES not in config: | ||||||
|         LOGGER.info("No pages or widgets configured, creating default hello_world page") |         LOGGER.info("No pages or widgets configured, creating default hello_world page") | ||||||
|         config[CONF_WIDGETS] = cv.ensure_list(WIDGET_SCHEMA)(get_hello_world()) |         config[df.CONF_WIDGETS] = cv.ensure_list(WIDGET_SCHEMA)(get_hello_world()) | ||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -329,7 +335,7 @@ CONFIG_SCHEMA = ( | |||||||
|             cv.Optional(df.CONF_DRAW_ROUNDING, default=2): cv.positive_int, |             cv.Optional(df.CONF_DRAW_ROUNDING, default=2): cv.positive_int, | ||||||
|             cv.Optional(CONF_BUFFER_SIZE, default="100%"): cv.percentage, |             cv.Optional(CONF_BUFFER_SIZE, default="100%"): cv.percentage, | ||||||
|             cv.Optional(df.CONF_LOG_LEVEL, default="WARN"): cv.one_of( |             cv.Optional(df.CONF_LOG_LEVEL, default="WARN"): cv.one_of( | ||||||
|                 *df.LOG_LEVELS, upper=True |                 *df.LV_LOG_LEVELS, upper=True | ||||||
|             ), |             ), | ||||||
|             cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of( |             cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of( | ||||||
|                 "big_endian", "little_endian" |                 "big_endian", "little_endian" | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ from esphome import automation | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ACTION, CONF_GROUP, CONF_ID, CONF_TIMEOUT | from esphome.const import CONF_ACTION, CONF_GROUP, CONF_ID, CONF_TIMEOUT | ||||||
| from esphome.cpp_generator import RawExpression, get_variable | from esphome.cpp_generator import get_variable | ||||||
| from esphome.cpp_types import nullptr | from esphome.cpp_types import nullptr | ||||||
|  |  | ||||||
| from .defines import ( | from .defines import ( | ||||||
| @@ -23,7 +23,6 @@ from .lvcode import ( | |||||||
|     UPDATE_EVENT, |     UPDATE_EVENT, | ||||||
|     LambdaContext, |     LambdaContext, | ||||||
|     LocalVariable, |     LocalVariable, | ||||||
|     LvConditional, |  | ||||||
|     LvglComponent, |     LvglComponent, | ||||||
|     ReturnStatement, |     ReturnStatement, | ||||||
|     add_line_marks, |     add_line_marks, | ||||||
| @@ -47,8 +46,8 @@ from .types import ( | |||||||
| ) | ) | ||||||
| from .widgets import ( | from .widgets import ( | ||||||
|     Widget, |     Widget, | ||||||
|  |     get_scr_act, | ||||||
|     get_widgets, |     get_widgets, | ||||||
|     lv_scr_act, |  | ||||||
|     set_obj_properties, |     set_obj_properties, | ||||||
|     wait_for_widgets, |     wait_for_widgets, | ||||||
| ) | ) | ||||||
| @@ -66,8 +65,6 @@ async def action_to_code( | |||||||
| ): | ): | ||||||
|     await wait_for_widgets() |     await wait_for_widgets() | ||||||
|     async with LambdaContext(parameters=args, where=action_id) as context: |     async with LambdaContext(parameters=args, where=action_id) as context: | ||||||
|         with LvConditional(lv_expr.is_pre_initialise()): |  | ||||||
|             context.add(RawExpression("return")) |  | ||||||
|         for widget in widgets: |         for widget in widgets: | ||||||
|             await action(widget) |             await action(widget) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) |     var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) | ||||||
| @@ -126,7 +123,7 @@ async def lvgl_is_idle(config, condition_id, template_arg, args): | |||||||
| async def disp_update(disp, config: dict): | async def disp_update(disp, config: dict): | ||||||
|     if CONF_DISP_BG_COLOR not in config and CONF_DISP_BG_IMAGE not in config: |     if CONF_DISP_BG_COLOR not in config and CONF_DISP_BG_IMAGE not in config: | ||||||
|         return |         return | ||||||
|     with LocalVariable("lv_disp_tmp", lv_disp_t, literal(disp)) as disp_temp: |     with LocalVariable("lv_disp_tmp", lv_disp_t, disp) as disp_temp: | ||||||
|         if (bg_color := config.get(CONF_DISP_BG_COLOR)) is not None: |         if (bg_color := config.get(CONF_DISP_BG_COLOR)) is not None: | ||||||
|             lv.disp_set_bg_color(disp_temp, await lv_color.process(bg_color)) |             lv.disp_set_bg_color(disp_temp, await lv_color.process(bg_color)) | ||||||
|         if bg_image := config.get(CONF_DISP_BG_IMAGE): |         if bg_image := config.get(CONF_DISP_BG_IMAGE): | ||||||
| @@ -136,15 +133,24 @@ async def disp_update(disp, config: dict): | |||||||
| @automation.register_action( | @automation.register_action( | ||||||
|     "lvgl.widget.redraw", |     "lvgl.widget.redraw", | ||||||
|     ObjUpdateAction, |     ObjUpdateAction, | ||||||
|     cv.Schema( |     cv.Any( | ||||||
|         { |         cv.maybe_simple_value( | ||||||
|             cv.Optional(CONF_ID): cv.use_id(lv_obj_t), |             { | ||||||
|             cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), |                 cv.Required(CONF_ID): cv.use_id(lv_obj_t), | ||||||
|         } |                 cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), | ||||||
|  |             }, | ||||||
|  |             key=CONF_ID, | ||||||
|  |         ), | ||||||
|  |         cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
| async def obj_invalidate_to_code(config, action_id, template_arg, args): | async def obj_invalidate_to_code(config, action_id, template_arg, args): | ||||||
|     widgets = await get_widgets(config) or [lv_scr_act] |     lv_comp = await cg.get_variable(config[CONF_LVGL_ID]) | ||||||
|  |     widgets = await get_widgets(config) or [get_scr_act(lv_comp)] | ||||||
|  |  | ||||||
|     async def do_invalidate(widget: Widget): |     async def do_invalidate(widget: Widget): | ||||||
|         lv_obj.invalidate(widget.obj) |         lv_obj.invalidate(widget.obj) | ||||||
| @@ -164,7 +170,7 @@ async def obj_invalidate_to_code(config, action_id, template_arg, args): | |||||||
| async def lvgl_update_to_code(config, action_id, template_arg, args): | async def lvgl_update_to_code(config, action_id, template_arg, args): | ||||||
|     widgets = await get_widgets(config) |     widgets = await get_widgets(config) | ||||||
|     w = widgets[0] |     w = widgets[0] | ||||||
|     disp = f"{w.obj}->get_disp()" |     disp = literal(f"{w.obj}->get_disp()") | ||||||
|     async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context: |     async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context: | ||||||
|         await disp_update(disp, config) |         await disp_update(disp, config) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) |     var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) | ||||||
|   | |||||||
| @@ -189,14 +189,14 @@ LV_ANIM = LvConstant( | |||||||
| LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER") | LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER") | ||||||
| LV_DITHER = LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF") | LV_DITHER = LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF") | ||||||
|  |  | ||||||
| LOG_LEVELS = ( | LV_LOG_LEVELS = { | ||||||
|     "TRACE", |     "VERBOSE": "TRACE", | ||||||
|     "INFO", |     "DEBUG": "TRACE", | ||||||
|     "WARN", |     "INFO": "INFO", | ||||||
|     "ERROR", |     "WARN": "WARN", | ||||||
|     "USER", |     "ERROR": "ERROR", | ||||||
|     "NONE", |     "NONE": "NONE", | ||||||
| ) | } | ||||||
|  |  | ||||||
| LV_LONG_MODES = LvConstant( | LV_LONG_MODES = LvConstant( | ||||||
|     "LV_LABEL_LONG_", |     "LV_LABEL_LONG_", | ||||||
|   | |||||||
| @@ -183,17 +183,11 @@ class LvContext(LambdaContext): | |||||||
|         super().__init__(parameters=self.args) |         super().__init__(parameters=self.args) | ||||||
|         self.lv_component = lv_component |         self.lv_component = lv_component | ||||||
|  |  | ||||||
|     async def add_init_lambda(self): |  | ||||||
|         if self.code_list: |  | ||||||
|             cg.add(self.lv_component.add_init_lambda(await self.get_lambda())) |  | ||||||
|             LvContext.added_lambda_count += 1 |  | ||||||
|  |  | ||||||
|     async def __aexit__(self, exc_type, exc_val, exc_tb): |     async def __aexit__(self, exc_type, exc_val, exc_tb): | ||||||
|         await super().__aexit__(exc_type, exc_val, exc_tb) |         await super().__aexit__(exc_type, exc_val, exc_tb) | ||||||
|         await self.add_init_lambda() |  | ||||||
|  |  | ||||||
|     def add(self, expression: Union[Expression, Statement]): |     def add(self, expression: Union[Expression, Statement]): | ||||||
|         self.code_list.append(self.indented_statement(expression)) |         cg.add(expression) | ||||||
|         return expression |         return expression | ||||||
|  |  | ||||||
|     def __call__(self, *args): |     def __call__(self, *args): | ||||||
|   | |||||||
| @@ -11,12 +11,6 @@ namespace esphome { | |||||||
| namespace lvgl { | namespace lvgl { | ||||||
| static const char *const TAG = "lvgl"; | static const char *const TAG = "lvgl"; | ||||||
|  |  | ||||||
| #if LV_USE_LOG |  | ||||||
| static void log_cb(const char *buf) { |  | ||||||
|   esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, TAG, 0, "%.*s", (int) strlen(buf) - 1, buf); |  | ||||||
| } |  | ||||||
| #endif  // LV_USE_LOG |  | ||||||
|  |  | ||||||
| static const char *const EVENT_NAMES[] = { | static const char *const EVENT_NAMES[] = { | ||||||
|     "NONE", |     "NONE", | ||||||
|     "PRESSED", |     "PRESSED", | ||||||
| @@ -383,26 +377,48 @@ void LvglComponent::write_random_() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void LvglComponent::setup() { | /** | ||||||
|   ESP_LOGCONFIG(TAG, "LVGL Setup starts"); |  * @class LvglComponent | ||||||
| #if LV_USE_LOG |  * @brief Component for rendering LVGL. | ||||||
|   lv_log_register_print_cb(log_cb); |  * | ||||||
| #endif |  * This component renders LVGL widgets on a display. Some initialisation must be done in the constructor | ||||||
|  |  * since LVGL needs to be initialised before any widgets can be created. | ||||||
|  |  * | ||||||
|  |  * @param displays a list of displays to render onto. All displays must have the same | ||||||
|  |  *                 resolution. | ||||||
|  |  * @param buffer_frac the fraction of the display resolution to use for the LVGL | ||||||
|  |  *                    draw buffer. A higher value will make animations smoother but | ||||||
|  |  *                    also increase memory usage. | ||||||
|  |  * @param full_refresh if true, the display will be fully refreshed on every frame. | ||||||
|  |  *                     If false, only changed areas will be updated. | ||||||
|  |  * @param draw_rounding the rounding to use when drawing. A value of 1 will draw | ||||||
|  |  *                      without any rounding, a value of 2 will round to the nearest | ||||||
|  |  *                      multiple of 2, and so on. | ||||||
|  |  * @param resume_on_input if true, this component will resume rendering when the user | ||||||
|  |  *                         presses a key or clicks on the screen. | ||||||
|  |  */ | ||||||
|  | LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buffer_frac, bool full_refresh, | ||||||
|  |                              int draw_rounding, bool resume_on_input) | ||||||
|  |     : draw_rounding(draw_rounding), | ||||||
|  |       displays_(std::move(displays)), | ||||||
|  |       buffer_frac_(buffer_frac), | ||||||
|  |       full_refresh_(full_refresh), | ||||||
|  |       resume_on_input_(resume_on_input) { | ||||||
|   lv_init(); |   lv_init(); | ||||||
|   lv_update_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()); |   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; | ||||||
|   auto *buf = lv_custom_mem_alloc(buf_bytes);  // NOLINT |   this->rotation = display->get_rotation(); | ||||||
|   if (buf == nullptr) { |   if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) { | ||||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR |     this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes));  // NOLINT | ||||||
|     ESP_LOGE(TAG, "Malloc failed to allocate %zu bytes", buf_bytes); |     if (this->rotate_buf_ == nullptr) | ||||||
| #endif |       return; | ||||||
|     this->mark_failed(); |  | ||||||
|     this->status_set_error("Memory allocation failure"); |  | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|  |   auto *buf = lv_custom_mem_alloc(buf_bytes);  // NOLINT | ||||||
|  |   if (buf == nullptr) | ||||||
|  |     return; | ||||||
|   lv_disp_draw_buf_init(&this->draw_buf_, buf, nullptr, buffer_pixels); |   lv_disp_draw_buf_init(&this->draw_buf_, buf, nullptr, buffer_pixels); | ||||||
|   lv_disp_drv_init(&this->disp_drv_); |   lv_disp_drv_init(&this->disp_drv_); | ||||||
|   this->disp_drv_.draw_buf = &this->draw_buf_; |   this->disp_drv_.draw_buf = &this->draw_buf_; | ||||||
| @@ -410,18 +426,7 @@ void LvglComponent::setup() { | |||||||
|   this->disp_drv_.full_refresh = this->full_refresh_; |   this->disp_drv_.full_refresh = this->full_refresh_; | ||||||
|   this->disp_drv_.flush_cb = static_flush_cb; |   this->disp_drv_.flush_cb = static_flush_cb; | ||||||
|   this->disp_drv_.rounder_cb = rounder_cb; |   this->disp_drv_.rounder_cb = rounder_cb; | ||||||
|   this->rotation = display->get_rotation(); |   // reset the display rotation since we will handle all rotations | ||||||
|   if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) { |  | ||||||
|     this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes));  // NOLINT |  | ||||||
|     if (this->rotate_buf_ == nullptr) { |  | ||||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR |  | ||||||
|       ESP_LOGE(TAG, "Malloc failed to allocate %zu bytes", buf_bytes); |  | ||||||
| #endif |  | ||||||
|       this->mark_failed(); |  | ||||||
|       this->status_set_error("Memory allocation failure"); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES); |   display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES); | ||||||
|   switch (this->rotation) { |   switch (this->rotation) { | ||||||
|     default: |     default: | ||||||
| @@ -435,12 +440,30 @@ void LvglComponent::setup() { | |||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|   this->disp_ = lv_disp_drv_register(&this->disp_drv_); |   this->disp_ = lv_disp_drv_register(&this->disp_drv_); | ||||||
|   for (const auto &v : this->init_lambdas_) | } | ||||||
|     v(this); |  | ||||||
|  | void LvglComponent::setup() { | ||||||
|  |   if (this->draw_buf_.buf1 == nullptr) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     this->status_set_error("Memory allocation failure"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "LVGL Setup starts"); | ||||||
|  | #if LV_USE_LOG | ||||||
|  |   lv_log_register_print_cb([](const char *buf) { | ||||||
|  |     auto next = strchr(buf, ')'); | ||||||
|  |     if (next != nullptr) | ||||||
|  |       buf = next + 1; | ||||||
|  |     while (isspace(*buf)) | ||||||
|  |       buf++; | ||||||
|  |     esp_log_printf_(LVGL_LOG_LEVEL, TAG, 0, "%.*s", (int) strlen(buf) - 1, buf); | ||||||
|  |   }); | ||||||
|  | #endif | ||||||
|   this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0); |   this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0); | ||||||
|   lv_disp_trig_activity(this->disp_); |   lv_disp_trig_activity(this->disp_); | ||||||
|   ESP_LOGCONFIG(TAG, "LVGL Setup complete"); |   ESP_LOGCONFIG(TAG, "LVGL Setup complete"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LvglComponent::update() { | void LvglComponent::update() { | ||||||
|   // update indicators |   // update indicators | ||||||
|   if (this->paused_) { |   if (this->paused_) { | ||||||
| @@ -455,13 +478,6 @@ void LvglComponent::loop() { | |||||||
|   } |   } | ||||||
|   lv_timer_handler_run_in_period(5); |   lv_timer_handler_run_in_period(5); | ||||||
| } | } | ||||||
| bool lv_is_pre_initialise() { |  | ||||||
|   if (!lv_is_initialized()) { |  | ||||||
|     ESP_LOGE(TAG, "LVGL call before component is initialised"); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|   return false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #ifdef USE_LVGL_ANIMIMG | #ifdef USE_LVGL_ANIMIMG | ||||||
| void lv_animimg_stop(lv_obj_t *obj) { | void lv_animimg_stop(lv_obj_t *obj) { | ||||||
|   | |||||||
| @@ -39,7 +39,6 @@ namespace lvgl { | |||||||
| extern lv_event_code_t lv_api_event;     // NOLINT | extern lv_event_code_t lv_api_event;     // NOLINT | ||||||
| extern lv_event_code_t lv_update_event;  // NOLINT | extern lv_event_code_t lv_update_event;  // NOLINT | ||||||
| extern std::string lv_event_code_name_for(uint8_t event_code); | extern std::string lv_event_code_name_for(uint8_t event_code); | ||||||
| extern bool lv_is_pre_initialise(); |  | ||||||
| #if LV_COLOR_DEPTH == 16 | #if LV_COLOR_DEPTH == 16 | ||||||
| static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565; | static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565; | ||||||
| #elif LV_COLOR_DEPTH == 32 | #elif LV_COLOR_DEPTH == 32 | ||||||
| @@ -108,6 +107,8 @@ class LvglComponent : public PollingComponent { | |||||||
|   constexpr static const char *const TAG = "lvgl"; |   constexpr static const char *const TAG = "lvgl"; | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|  |   LvglComponent(std::vector<display::Display *> displays, float buffer_frac, bool full_refresh, int draw_rounding, | ||||||
|  |                 bool resume_on_input); | ||||||
|   static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); |   static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); | ||||||
|  |  | ||||||
|   float get_setup_priority() const override { return setup_priority::PROCESSOR; } |   float get_setup_priority() const override { return setup_priority::PROCESSOR; } | ||||||
| @@ -118,13 +119,10 @@ class LvglComponent : public PollingComponent { | |||||||
|     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)); } |   void add_on_pause_callback(std::function<void(bool)> &&callback) { this->pause_callbacks_.add(std::move(callback)); } | ||||||
|   void add_display(display::Display *display) { this->displays_.push_back(display); } |  | ||||||
|   void add_init_lambda(const std::function<void(LvglComponent *)> &lamb) { this->init_lambdas_.push_back(lamb); } |  | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void set_full_refresh(bool full_refresh) { this->full_refresh_ = full_refresh; } |  | ||||||
|   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; } | ||||||
|   void set_buffer_frac(size_t frac) { this->buffer_frac_ = frac; } |  | ||||||
|   lv_disp_t *get_disp() { return this->disp_; } |   lv_disp_t *get_disp() { return this->disp_; } | ||||||
|  |   lv_obj_t *get_scr_act() { return lv_disp_get_scr_act(this->disp_); } | ||||||
|   // Pause or resume the display. |   // Pause or resume the display. | ||||||
|   // @param paused If true, pause the display. If false, resume the display. |   // @param paused If true, pause the display. If false, resume the display. | ||||||
|   // @param show_snow If true, show the snow effect when paused. |   // @param show_snow If true, show the snow effect when paused. | ||||||
| @@ -155,17 +153,19 @@ class LvglComponent : public PollingComponent { | |||||||
|   } |   } | ||||||
|   // rounding factor to align bounds of update area when drawing |   // rounding factor to align bounds of update area when drawing | ||||||
|   size_t draw_rounding{2}; |   size_t draw_rounding{2}; | ||||||
|   void set_draw_rounding(size_t rounding) { this->draw_rounding = rounding; } |  | ||||||
|   void set_resume_on_input(bool resume_on_input) { this->resume_on_input_ = resume_on_input; } |  | ||||||
|  |  | ||||||
|   // if set to true, the bounds of the update area will always start at 0,0 |  | ||||||
|   display::DisplayRotation rotation{display::DISPLAY_ROTATION_0_DEGREES}; |   display::DisplayRotation rotation{display::DISPLAY_ROTATION_0_DEGREES}; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   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}; | ||||||
|  |   bool full_refresh_{}; | ||||||
|  |   bool resume_on_input_{}; | ||||||
|  |  | ||||||
|   lv_disp_draw_buf_t draw_buf_{}; |   lv_disp_draw_buf_t draw_buf_{}; | ||||||
|   lv_disp_drv_t disp_drv_{}; |   lv_disp_drv_t disp_drv_{}; | ||||||
|   lv_disp_t *disp_{}; |   lv_disp_t *disp_{}; | ||||||
| @@ -174,14 +174,10 @@ class LvglComponent : public PollingComponent { | |||||||
|   size_t current_page_{0}; |   size_t current_page_{0}; | ||||||
|   bool show_snow_{}; |   bool show_snow_{}; | ||||||
|   bool page_wrap_{true}; |   bool page_wrap_{true}; | ||||||
|   bool resume_on_input_{}; |  | ||||||
|   std::map<lv_group_t *, lv_obj_t *> focus_marks_{}; |   std::map<lv_group_t *, lv_obj_t *> focus_marks_{}; | ||||||
|  |  | ||||||
|   std::vector<std::function<void(LvglComponent *lv_component)>> init_lambdas_; |  | ||||||
|   CallbackManager<void(uint32_t)> idle_callbacks_{}; |   CallbackManager<void(uint32_t)> idle_callbacks_{}; | ||||||
|   CallbackManager<void(bool)> pause_callbacks_{}; |   CallbackManager<void(bool)> pause_callbacks_{}; | ||||||
|   size_t buffer_frac_{1}; |  | ||||||
|   bool full_refresh_{}; |  | ||||||
|   lv_color_t *rotate_buf_{}; |   lv_color_t *rotate_buf_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,8 +17,6 @@ from .types import lv_lambda_t, lv_obj_t, lv_obj_t_ptr | |||||||
| from .widgets import Widget, add_widgets, set_obj_properties, theme_widget_map | from .widgets import Widget, add_widgets, set_obj_properties, theme_widget_map | ||||||
| from .widgets.obj import obj_spec | from .widgets.obj import obj_spec | ||||||
|  |  | ||||||
| TOP_LAYER = literal("lv_disp_get_layer_top(lv_component->get_disp())") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def styles_to_code(config): | async def styles_to_code(config): | ||||||
|     """Convert styles to C__ code.""" |     """Convert styles to C__ code.""" | ||||||
| @@ -51,9 +49,10 @@ async def theme_to_code(config): | |||||||
|             lv_assign(apply, await context.get_lambda()) |             lv_assign(apply, await context.get_lambda()) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def add_top_layer(config): | async def add_top_layer(lv_component, config): | ||||||
|  |     top_layer = lv.disp_get_layer_top(lv_component.get_disp()) | ||||||
|     if top_conf := config.get(CONF_TOP_LAYER): |     if top_conf := config.get(CONF_TOP_LAYER): | ||||||
|         with LocalVariable("top_layer", lv_obj_t, TOP_LAYER) as top_layer_obj: |         with LocalVariable("top_layer", lv_obj_t, top_layer) as top_layer_obj: | ||||||
|             top_w = Widget(top_layer_obj, obj_spec, top_conf) |             top_w = Widget(top_layer_obj, obj_spec, top_conf) | ||||||
|             await set_obj_properties(top_w, top_conf) |             await set_obj_properties(top_w, top_conf) | ||||||
|             await add_widgets(top_w, top_conf) |             await add_widgets(top_w, top_conf) | ||||||
|   | |||||||
| @@ -55,18 +55,6 @@ theme_widget_map = {} | |||||||
| styles_used = set() | styles_used = set() | ||||||
|  |  | ||||||
|  |  | ||||||
| class LvScrActType(WidgetType): |  | ||||||
|     """ |  | ||||||
|     A "widget" representing the active screen. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self): |  | ||||||
|         super().__init__("lv_scr_act()", lv_obj_t, ()) |  | ||||||
|  |  | ||||||
|     async def to_code(self, w, config: dict): |  | ||||||
|         return [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Widget: | class Widget: | ||||||
|     """ |     """ | ||||||
|     Represents a Widget. |     Represents a Widget. | ||||||
| @@ -221,6 +209,25 @@ class Widget: | |||||||
| widget_map: dict[Any, Widget] = {} | widget_map: dict[Any, Widget] = {} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LvScrActType(WidgetType): | ||||||
|  |     """ | ||||||
|  |     A "widget" representing the active screen. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__("lv_scr_act()", lv_obj_t, ()) | ||||||
|  |  | ||||||
|  |     async def to_code(self, w, config: dict): | ||||||
|  |         return [] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | lv_scr_act_spec = LvScrActType() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_scr_act(lv_comp: MockObj) -> Widget: | ||||||
|  |     return Widget.create(None, lv_comp.get_scr_act(), lv_scr_act_spec, {}) | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_widget_generator(wid): | def get_widget_generator(wid): | ||||||
|     """ |     """ | ||||||
|     Used to wait for a widget during code generation. |     Used to wait for a widget during code generation. | ||||||
| @@ -451,7 +458,3 @@ async def widget_to_code(w_cnfig, w_type: WidgetType, parent): | |||||||
|     await set_obj_properties(w, w_cnfig) |     await set_obj_properties(w, w_cnfig) | ||||||
|     await add_widgets(w, w_cnfig) |     await add_widgets(w, w_cnfig) | ||||||
|     await spec.to_code(w, w_cnfig) |     await spec.to_code(w, w_cnfig) | ||||||
|  |  | ||||||
|  |  | ||||||
| lv_scr_act_spec = LvScrActType() |  | ||||||
| lv_scr_act = Widget.create(None, literal("lv_scr_act()"), lv_scr_act_spec, {}) |  | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ from ..lvcode import ( | |||||||
|     EVENT_ARG, |     EVENT_ARG, | ||||||
|     LambdaContext, |     LambdaContext, | ||||||
|     LocalVariable, |     LocalVariable, | ||||||
|  |     lv, | ||||||
|     lv_add, |     lv_add, | ||||||
|     lv_assign, |     lv_assign, | ||||||
|     lv_expr, |     lv_expr, | ||||||
| @@ -27,7 +28,6 @@ from ..lvcode import ( | |||||||
|     lv_Pvariable, |     lv_Pvariable, | ||||||
| ) | ) | ||||||
| from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema | from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema | ||||||
| from ..styles import TOP_LAYER |  | ||||||
| from ..types import LV_EVENT, char_ptr, lv_obj_t | from ..types import LV_EVENT, char_ptr, lv_obj_t | ||||||
| from . import Widget, set_obj_properties | from . import Widget, set_obj_properties | ||||||
| from .button import button_spec | from .button import button_spec | ||||||
| @@ -59,7 +59,7 @@ MSGBOX_SCHEMA = container_schema( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def msgbox_to_code(conf): | async def msgbox_to_code(top_layer, conf): | ||||||
|     """ |     """ | ||||||
|     Construct a message box. This consists of a full-screen translucent background enclosing a centered container |     Construct a message box. This consists of a full-screen translucent background enclosing a centered container | ||||||
|     with an optional title, body, close button and a button matrix. And any other widgets the user cares to add |     with an optional title, body, close button and a button matrix. And any other widgets the user cares to add | ||||||
| @@ -101,7 +101,7 @@ async def msgbox_to_code(conf): | |||||||
|     text = await lv_text.process(conf[CONF_BODY].get(CONF_TEXT, "")) |     text = await lv_text.process(conf[CONF_BODY].get(CONF_TEXT, "")) | ||||||
|     title = await lv_text.process(conf[CONF_TITLE].get(CONF_TEXT, "")) |     title = await lv_text.process(conf[CONF_TITLE].get(CONF_TEXT, "")) | ||||||
|     close_button = conf[CONF_CLOSE_BUTTON] |     close_button = conf[CONF_CLOSE_BUTTON] | ||||||
|     lv_assign(outer, lv_expr.obj_create(TOP_LAYER)) |     lv_assign(outer, lv_expr.obj_create(top_layer)) | ||||||
|     lv_obj.set_width(outer, lv_pct(100)) |     lv_obj.set_width(outer, lv_pct(100)) | ||||||
|     lv_obj.set_height(outer, lv_pct(100)) |     lv_obj.set_height(outer, lv_pct(100)) | ||||||
|     lv_obj.set_style_bg_opa(outer, 128, 0) |     lv_obj.set_style_bg_opa(outer, 128, 0) | ||||||
| @@ -141,6 +141,7 @@ async def msgbox_to_code(conf): | |||||||
|         set_btn_data(buttonmatrix.obj, ctrl_list, width_list) |         set_btn_data(buttonmatrix.obj, ctrl_list, width_list) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def msgboxes_to_code(config): | async def msgboxes_to_code(lv_component, config): | ||||||
|  |     top_layer = lv.disp_get_layer_top(lv_component.get_disp()) | ||||||
|     for conf in config.get(CONF_MSGBOXES, ()): |     for conf in config.get(CONF_MSGBOXES, ()): | ||||||
|         await msgbox_to_code(conf) |         await msgbox_to_code(top_layer, conf) | ||||||
|   | |||||||
| @@ -12,12 +12,12 @@ substitutions: | |||||||
|   arrow_down: "\U000F004B" |   arrow_down: "\U000F004B" | ||||||
|  |  | ||||||
| lvgl: | lvgl: | ||||||
|  |   log_level: debug | ||||||
|   resume_on_input: true |   resume_on_input: true | ||||||
|   on_pause: |   on_pause: | ||||||
|     logger.log: LVGL is Paused |     logger.log: LVGL is Paused | ||||||
|   on_resume: |   on_resume: | ||||||
|     logger.log: LVGL has resumed |     logger.log: LVGL has resumed | ||||||
|   log_level: TRACE |  | ||||||
|   bg_color: light_blue |   bg_color: light_blue | ||||||
|   disp_bg_color: color_id |   disp_bg_color: color_id | ||||||
|   disp_bg_image: cat_image |   disp_bg_image: cat_image | ||||||
| @@ -125,6 +125,8 @@ lvgl: | |||||||
|       on_unload: |       on_unload: | ||||||
|         - logger.log: page unloaded |         - logger.log: page unloaded | ||||||
|         - lvgl.widget.focus: mark |         - lvgl.widget.focus: mark | ||||||
|  |         - lvgl.widget.redraw: hello_label | ||||||
|  |         - lvgl.widget.redraw: | ||||||
|       on_all_events: |       on_all_events: | ||||||
|         logger.log: |         logger.log: | ||||||
|           format: "Event %s" |           format: "Event %s" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user