From 81f6750211cd98a7595d0a6320cd4d7760e161ff Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:27:08 +1100 Subject: [PATCH] [lvgl] Bugfixes #3 (#7472) --- esphome/components/image/image.cpp | 44 ++++++++++++++++ esphome/components/image/image.h | 19 ++++++- esphome/components/lvgl/__init__.py | 4 +- esphome/components/lvgl/defines.py | 1 + esphome/components/lvgl/lv_validation.py | 3 +- esphome/components/lvgl/lvgl_esphome.cpp | 43 --------------- esphome/components/lvgl/lvgl_esphome.h | 4 -- esphome/components/lvgl/widgets/__init__.py | 2 + esphome/components/lvgl/widgets/animimg.py | 7 ++- esphome/components/lvgl/widgets/meter.py | 27 ++++++---- tests/components/image/test.esp32-ard.yaml | 4 +- tests/components/image/test.esp32-c3-ard.yaml | 4 +- tests/components/image/test.esp32-c3-idf.yaml | 4 +- tests/components/image/test.esp32-idf.yaml | 4 +- tests/components/lvgl/lvgl-package.yaml | 52 +++++++++++++++++++ tests/components/lvgl/test.esp32-idf.yaml | 2 +- 16 files changed, 148 insertions(+), 76 deletions(-) diff --git a/esphome/components/image/image.cpp b/esphome/components/image/image.cpp index 0ddb8110cb..ded4c60d25 100644 --- a/esphome/components/image/image.cpp +++ b/esphome/components/image/image.cpp @@ -79,6 +79,50 @@ Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const { return color_off; } } +#ifdef USE_LVGL +lv_img_dsc_t *Image::get_lv_img_dsc() { + // lazily construct lvgl image_dsc. + if (this->dsc_.data != this->data_start_) { + this->dsc_.data = this->data_start_; + this->dsc_.header.always_zero = 0; + this->dsc_.header.reserved = 0; + this->dsc_.header.w = this->width_; + this->dsc_.header.h = this->height_; + this->dsc_.data_size = image_type_to_width_stride(this->dsc_.header.w * this->dsc_.header.h, this->get_type()); + switch (this->get_type()) { + case IMAGE_TYPE_BINARY: + this->dsc_.header.cf = LV_IMG_CF_ALPHA_1BIT; + break; + + case IMAGE_TYPE_GRAYSCALE: + this->dsc_.header.cf = LV_IMG_CF_ALPHA_8BIT; + break; + + case IMAGE_TYPE_RGB24: + this->dsc_.header.cf = LV_IMG_CF_RGB888; + break; + + case IMAGE_TYPE_RGB565: +#if LV_COLOR_DEPTH == 16 + this->dsc_.header.cf = this->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR; +#else + this->dsc_.header.cf = LV_IMG_CF_RGB565; +#endif + break; + + case image::IMAGE_TYPE_RGBA: +#if LV_COLOR_DEPTH == 32 + this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR; +#else + this->dsc_.header.cf = LV_IMG_CF_RGBA8888; +#endif + break; + } + } + return &this->dsc_; +} +#endif // USE_LVGL + bool Image::get_binary_pixel_(int x, int y) const { const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t pos = x + y * width_8; diff --git a/esphome/components/image/image.h b/esphome/components/image/image.h index 5f1f50a134..ae5a7a814d 100644 --- a/esphome/components/image/image.h +++ b/esphome/components/image/image.h @@ -1,6 +1,15 @@ #pragma once #include "esphome/core/color.h" -#include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display.h" + +#ifdef USE_LVGL +// required for clang-tidy +#ifndef LV_CONF_H +#define LV_CONF_SKIP 1 // NOLINT +#endif // LV_CONF_H + +#include +#endif // USE_LVGL namespace esphome { namespace image { @@ -37,7 +46,7 @@ class Image : public display::BaseImage { Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const; int get_width() const override; int get_height() const override; - const uint8_t *get_data_start() { return this->data_start_; } + const uint8_t *get_data_start() const { return this->data_start_; } ImageType get_type() const; void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; @@ -45,6 +54,9 @@ class Image : public display::BaseImage { void set_transparency(bool transparent) { transparent_ = transparent; } bool has_transparency() const { return transparent_; } +#ifdef USE_LVGL + lv_img_dsc_t *get_lv_img_dsc(); +#endif protected: bool get_binary_pixel_(int x, int y) const; Color get_rgb24_pixel_(int x, int y) const; @@ -57,6 +69,9 @@ class Image : public display::BaseImage { ImageType type_; const uint8_t *data_start_; bool transparent_; +#ifdef USE_LVGL + lv_img_dsc_t dsc_{}; +#endif }; } // namespace image diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index a3a6f7ddaf..ce3843567b 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -53,7 +53,7 @@ from .types import ( lv_style_t, lvgl_ns, ) -from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties +from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties, styles_used from .widgets.animimg import animimg_spec from .widgets.arc import arc_spec from .widgets.button import button_spec @@ -280,6 +280,8 @@ async def to_code(config): for comp in helpers.lvgl_components_required: CORE.add_define(f"USE_LVGL_{comp.upper()}") + if "transform_angle" in styles_used: + add_define("LV_COLOR_SCREEN_TRANSP", "1") for use in helpers.lv_uses: add_define(f"LV_USE_{use.upper()}") lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 3db49d26a4..02f726e49c 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -452,6 +452,7 @@ CONF_OFFSET_Y = "offset_y" CONF_ONE_CHECKED = "one_checked" CONF_ONE_LINE = "one_line" CONF_ON_SELECT = "on_select" +CONF_OPA = "opa" CONF_NEXT = "next" CONF_PAD_ROW = "pad_row" CONF_PAD_COLUMN = "pad_column" diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 3dee0189fb..bd98319fd3 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -31,7 +31,6 @@ from .defines import ( literal, ) from .helpers import esphome_fonts_used, lv_fonts_used, requires_component -from .lvcode import lv_expr from .types import lv_font_t, lv_gradient_t, lv_img_t opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") @@ -330,7 +329,7 @@ def image_validator(value): lv_image = LValidator( image_validator, lv_img_t, - retmapper=lambda x: lv_expr.img_from(MockObj(x)), + retmapper=lambda x: MockObj(x, "->").get_lv_img_dsc(), requires="image", ) lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal) diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 89c9828740..b63fb0dab8 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -356,49 +356,6 @@ bool lv_is_pre_initialise() { return false; } -#ifdef USE_LVGL_IMAGE -lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc) { - if (img_dsc == nullptr) - img_dsc = new lv_img_dsc_t(); // NOLINT - img_dsc->header.always_zero = 0; - img_dsc->header.reserved = 0; - img_dsc->header.w = src->get_width(); - img_dsc->header.h = src->get_height(); - img_dsc->data = src->get_data_start(); - img_dsc->data_size = image_type_to_width_stride(img_dsc->header.w * img_dsc->header.h, src->get_type()); - switch (src->get_type()) { - case image::IMAGE_TYPE_BINARY: - img_dsc->header.cf = LV_IMG_CF_ALPHA_1BIT; - break; - - case image::IMAGE_TYPE_GRAYSCALE: - img_dsc->header.cf = LV_IMG_CF_ALPHA_8BIT; - break; - - case image::IMAGE_TYPE_RGB24: - img_dsc->header.cf = LV_IMG_CF_RGB888; - break; - - case image::IMAGE_TYPE_RGB565: -#if LV_COLOR_DEPTH == 16 - img_dsc->header.cf = src->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR; -#else - img_dsc->header.cf = LV_IMG_CF_RGB565; -#endif - break; - - case image::IMAGE_TYPE_RGBA: -#if LV_COLOR_DEPTH == 32 - img_dsc->header.cf = LV_IMG_CF_TRUE_COLOR; -#else - img_dsc->header.cf = LV_IMG_CF_RGBA8888; -#endif - break; - } - return img_dsc; -} -#endif // USE_LVGL_IMAGE - #ifdef USE_LVGL_ANIMIMG void lv_animimg_stop(lv_obj_t *obj) { auto *animg = (lv_animimg_t *) obj; diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index d5cff51de2..0c3738bd1f 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -102,10 +102,6 @@ class FontEngine { lv_font_t lv_font_{}; }; #endif // USE_LVGL_FONT -#ifdef USE_LVGL_IMAGE -lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc = nullptr); -#endif // USE_LVGL_IMAGE - #ifdef USE_LVGL_ANIMIMG void lv_animimg_stop(lv_obj_t *obj); #endif // USE_LVGL_ANIMIMG diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index e093cebd16..533ffdea55 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -52,6 +52,7 @@ from ..types import LV_STATE, LvType, WidgetType, lv_coord_t, lv_obj_t, lv_obj_t EVENT_LAMB = "event_lamb__" theme_widget_map = {} +styles_used = set() class LvScrActType(WidgetType): @@ -158,6 +159,7 @@ class Widget: def set_style(self, prop, value, state): if value is None: return + styles_used.add(prop) lv.call(f"obj_set_style_{prop}", self.obj, value, state) def __type_base(self): diff --git a/esphome/components/lvgl/widgets/animimg.py b/esphome/components/lvgl/widgets/animimg.py index a973ca0702..3b20008c3d 100644 --- a/esphome/components/lvgl/widgets/animimg.py +++ b/esphome/components/lvgl/widgets/animimg.py @@ -2,13 +2,12 @@ from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_DURATION, CONF_ID -from esphome.cpp_generator import MockObj from ..automation import action_to_code from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC from ..helpers import lvgl_components_required from ..lv_validation import lv_image, lv_milliseconds -from ..lvcode import lv, lv_expr +from ..lvcode import lv from ..types import LvType, ObjUpdateAction, void_ptr from . import Widget, WidgetType, get_widgets from .img import CONF_IMAGE @@ -63,7 +62,7 @@ class AnimimgType(WidgetType): if CONF_SRC in config: for x in config[CONF_SRC]: await cg.get_variable(x) - srcs = [lv_expr.img_from(MockObj(x)) for x in config[CONF_SRC]] + srcs = [await lv_image.process(x) for x in config[CONF_SRC]] src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs) count = len(config[CONF_SRC]) lv.animimg_set_src(w.obj, src_id, count) @@ -73,7 +72,7 @@ class AnimimgType(WidgetType): lv.animimg_start(w.obj) def get_uses(self): - return CONF_IMAGE, CONF_LABEL + return "img", CONF_IMAGE, CONF_LABEL animimg_spec = AnimimgType() diff --git a/esphome/components/lvgl/widgets/meter.py b/esphome/components/lvgl/widgets/meter.py index 36f6643022..bc455ccebc 100644 --- a/esphome/components/lvgl/widgets/meter.py +++ b/esphome/components/lvgl/widgets/meter.py @@ -20,6 +20,7 @@ from ..defines import ( CONF_END_VALUE, CONF_INDICATOR, CONF_MAIN, + CONF_OPA, CONF_PIVOT_X, CONF_PIVOT_Y, CONF_SRC, @@ -35,10 +36,11 @@ from ..lv_validation import ( lv_color, lv_float, lv_image, + opacity, requires_component, size, ) -from ..lvcode import LocalVariable, lv, lv_assign, lv_expr +from ..lvcode import LocalVariable, lv, lv_assign, lv_expr, lv_obj from ..types import LvType, ObjUpdateAction from . import Widget, WidgetType, get_widgets from .arc import CONF_ARC @@ -76,6 +78,7 @@ INDICATOR_LINE_SCHEMA = cv.Schema( cv.Optional(CONF_COLOR, default=0): lv_color, cv.Optional(CONF_R_MOD, default=0): size, cv.Optional(CONF_VALUE): lv_float, + cv.Optional(CONF_OPA): opacity, } ) INDICATOR_IMG_SCHEMA = cv.Schema( @@ -84,6 +87,7 @@ INDICATOR_IMG_SCHEMA = cv.Schema( cv.Required(CONF_PIVOT_X): pixels, cv.Required(CONF_PIVOT_Y): pixels, cv.Optional(CONF_VALUE): lv_float, + cv.Optional(CONF_OPA): opacity, } ) INDICATOR_ARC_SCHEMA = cv.Schema( @@ -94,6 +98,7 @@ INDICATOR_ARC_SCHEMA = cv.Schema( cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float, cv.Optional(CONF_END_VALUE): lv_float, + cv.Optional(CONF_OPA): opacity, } ) INDICATOR_TICKS_SCHEMA = cv.Schema( @@ -218,9 +223,7 @@ class MeterType(WidgetType): for indicator in scale_conf.get(CONF_INDICATORS, ()): (t, v) = next(iter(indicator.items())) iid = v[CONF_ID] - ivar = cg.new_variable( - iid, cg.nullptr, type_=lv_meter_indicator_t_ptr - ) + ivar = cg.Pvariable(iid, cg.nullptr, type_=lv_meter_indicator_t) # Enable getting the meter to which this belongs. wid = Widget.create(iid, var, obj_spec, v) wid.obj = ivar @@ -268,9 +271,7 @@ class MeterType(WidgetType): v[CONF_PIVOT_Y], ), ) - start_value = await get_start_value(v) - end_value = await get_end_value(v) - set_indicator_values(var, ivar, start_value, end_value) + await set_indicator_values(var, ivar, v) meter_spec = MeterType() @@ -285,21 +286,22 @@ meter_spec = MeterType() cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float, cv.Optional(CONF_END_VALUE): lv_float, + cv.Optional(CONF_OPA): opacity, } ), ) async def indicator_update_to_code(config, action_id, template_arg, args): widget = await get_widgets(config) - start_value = await get_start_value(config) - end_value = await get_end_value(config) async def set_value(w: Widget): - set_indicator_values(w.var, w.obj, start_value, end_value) + await set_indicator_values(w.var, w.obj, config) return await action_to_code(widget, set_value, action_id, template_arg, args) -def set_indicator_values(meter, indicator, start_value, end_value): +async def set_indicator_values(meter, indicator, config): + start_value = await get_start_value(config) + end_value = await get_end_value(config) if start_value is not None: if end_value is None: lv.meter_set_indicator_value(meter, indicator, start_value) @@ -307,3 +309,6 @@ def set_indicator_values(meter, indicator, start_value, end_value): lv.meter_set_indicator_start_value(meter, indicator, start_value) if end_value is not None: lv.meter_set_indicator_end_value(meter, indicator, end_value) + if opa := config.get(CONF_OPA): + lv_assign(indicator.opa, await opacity.process(opa)) + lv_obj.invalidate(meter) diff --git a/tests/components/image/test.esp32-ard.yaml b/tests/components/image/test.esp32-ard.yaml index 34c7914976..9dd44d177f 100644 --- a/tests/components/image/test.esp32-ard.yaml +++ b/tests/components/image/test.esp32-ard.yaml @@ -2,13 +2,13 @@ spi: - id: spi_main_lcd clk_pin: 16 mosi_pin: 17 - miso_pin: 15 + miso_pin: 32 display: - platform: ili9xxx id: main_lcd model: ili9342 - cs_pin: 12 + cs_pin: 14 dc_pin: 13 reset_pin: 21 invert_colors: true diff --git a/tests/components/image/test.esp32-c3-ard.yaml b/tests/components/image/test.esp32-c3-ard.yaml index 91ff0a0579..c0b2779773 100644 --- a/tests/components/image/test.esp32-c3-ard.yaml +++ b/tests/components/image/test.esp32-c3-ard.yaml @@ -8,8 +8,8 @@ display: - platform: ili9xxx id: main_lcd model: ili9342 - cs_pin: 8 - dc_pin: 9 + cs_pin: 3 + dc_pin: 11 reset_pin: 10 invert_colors: true diff --git a/tests/components/image/test.esp32-c3-idf.yaml b/tests/components/image/test.esp32-c3-idf.yaml index 91ff0a0579..c0b2779773 100644 --- a/tests/components/image/test.esp32-c3-idf.yaml +++ b/tests/components/image/test.esp32-c3-idf.yaml @@ -8,8 +8,8 @@ display: - platform: ili9xxx id: main_lcd model: ili9342 - cs_pin: 8 - dc_pin: 9 + cs_pin: 3 + dc_pin: 11 reset_pin: 10 invert_colors: true diff --git a/tests/components/image/test.esp32-idf.yaml b/tests/components/image/test.esp32-idf.yaml index 34c7914976..e903afea1f 100644 --- a/tests/components/image/test.esp32-idf.yaml +++ b/tests/components/image/test.esp32-idf.yaml @@ -2,13 +2,13 @@ spi: - id: spi_main_lcd clk_pin: 16 mosi_pin: 17 - miso_pin: 15 + miso_pin: 18 display: - platform: ili9xxx id: main_lcd model: ili9342 - cs_pin: 12 + cs_pin: 19 dc_pin: 13 reset_pin: 21 invert_colors: true diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 2b72f31770..6f79a1f810 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -608,6 +608,58 @@ lvgl: id: tabview_id index: 0 animated: true + - meter: + height: 200px + width: 200px + indicator: + bg_color: 0xFF + radius: 0 + bg_opa: TRANSP + text_color: 0xFFFFFF + scales: + - ticks: + width: 1 + count: 61 + length: 20 + color: 0xFFFFFF + range_from: 0 + range_to: 60 + angle_range: 360 + rotation: 270 + indicators: + - line: + opa: 50% + id: minute_hand + color: 0xFF0000 + r_mod: -1 + width: 3 + - + angle_range: 330 + rotation: 300 + range_from: 1 + range_to: 12 + ticks: + width: 1 + count: 12 + length: 1 + major: + stride: 1 + width: 4 + length: 8 + color: 0xC0C0C0 + label_gap: 6 + - angle_range: 360 + rotation: 270 + range_from: 0 + range_to: 720 + indicators: + - line: + id: hour_hand + value: 180 + width: 4 + color: 0xA0A0A0 + r_mod: -20 + font: - file: "gfonts://Roboto" id: space16 diff --git a/tests/components/lvgl/test.esp32-idf.yaml b/tests/components/lvgl/test.esp32-idf.yaml index 927d72d15c..05a1f243ed 100644 --- a/tests/components/lvgl/test.esp32-idf.yaml +++ b/tests/components/lvgl/test.esp32-idf.yaml @@ -10,7 +10,7 @@ sensor: - platform: rotary_encoder name: "Rotary Encoder" id: encoder - pin_a: 2 + pin_a: 3 pin_b: 1 internal: true