[lvgl] Bugfixes #3 (#7472)

This commit is contained in:
Clyde Stubbs 2024-10-07 11:27:08 +11:00 committed by GitHub
parent cbc03aae80
commit 81f6750211
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 148 additions and 76 deletions

View file

@ -79,6 +79,50 @@ Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
return color_off; 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 { bool Image::get_binary_pixel_(int x, int y) const {
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
const uint32_t pos = x + y * width_8; const uint32_t pos = x + y * width_8;

View file

@ -1,6 +1,15 @@
#pragma once #pragma once
#include "esphome/core/color.h" #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 <lvgl.h>
#endif // USE_LVGL
namespace esphome { namespace esphome {
namespace image { 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; 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_width() const override;
int get_height() 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; ImageType get_type() const;
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; 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; } void set_transparency(bool transparent) { transparent_ = transparent; }
bool has_transparency() const { return transparent_; } bool has_transparency() const { return transparent_; }
#ifdef USE_LVGL
lv_img_dsc_t *get_lv_img_dsc();
#endif
protected: protected:
bool get_binary_pixel_(int x, int y) const; bool get_binary_pixel_(int x, int y) const;
Color get_rgb24_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_; ImageType type_;
const uint8_t *data_start_; const uint8_t *data_start_;
bool transparent_; bool transparent_;
#ifdef USE_LVGL
lv_img_dsc_t dsc_{};
#endif
}; };
} // namespace image } // namespace image

View file

@ -53,7 +53,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 from .widgets import Widget, add_widgets, lv_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
@ -280,6 +280,8 @@ async def to_code(config):
for comp in helpers.lvgl_components_required: for comp in helpers.lvgl_components_required:
CORE.add_define(f"USE_LVGL_{comp.upper()}") 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: 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)

View file

@ -452,6 +452,7 @@ CONF_OFFSET_Y = "offset_y"
CONF_ONE_CHECKED = "one_checked" CONF_ONE_CHECKED = "one_checked"
CONF_ONE_LINE = "one_line" CONF_ONE_LINE = "one_line"
CONF_ON_SELECT = "on_select" CONF_ON_SELECT = "on_select"
CONF_OPA = "opa"
CONF_NEXT = "next" CONF_NEXT = "next"
CONF_PAD_ROW = "pad_row" CONF_PAD_ROW = "pad_row"
CONF_PAD_COLUMN = "pad_column" CONF_PAD_COLUMN = "pad_column"

View file

@ -31,7 +31,6 @@ from .defines import (
literal, literal,
) )
from .helpers import esphome_fonts_used, lv_fonts_used, requires_component 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 from .types import lv_font_t, lv_gradient_t, lv_img_t
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
@ -330,7 +329,7 @@ def image_validator(value):
lv_image = LValidator( lv_image = LValidator(
image_validator, image_validator,
lv_img_t, lv_img_t,
retmapper=lambda x: lv_expr.img_from(MockObj(x)), retmapper=lambda x: MockObj(x, "->").get_lv_img_dsc(),
requires="image", requires="image",
) )
lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal) lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal)

View file

@ -356,49 +356,6 @@ bool lv_is_pre_initialise() {
return false; 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 #ifdef USE_LVGL_ANIMIMG
void lv_animimg_stop(lv_obj_t *obj) { void lv_animimg_stop(lv_obj_t *obj) {
auto *animg = (lv_animimg_t *) obj; auto *animg = (lv_animimg_t *) obj;

View file

@ -102,10 +102,6 @@ class FontEngine {
lv_font_t lv_font_{}; lv_font_t lv_font_{};
}; };
#endif // USE_LVGL_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 #ifdef USE_LVGL_ANIMIMG
void lv_animimg_stop(lv_obj_t *obj); void lv_animimg_stop(lv_obj_t *obj);
#endif // USE_LVGL_ANIMIMG #endif // USE_LVGL_ANIMIMG

View file

@ -52,6 +52,7 @@ from ..types import LV_STATE, LvType, WidgetType, lv_coord_t, lv_obj_t, lv_obj_t
EVENT_LAMB = "event_lamb__" EVENT_LAMB = "event_lamb__"
theme_widget_map = {} theme_widget_map = {}
styles_used = set()
class LvScrActType(WidgetType): class LvScrActType(WidgetType):
@ -158,6 +159,7 @@ class Widget:
def set_style(self, prop, value, state): def set_style(self, prop, value, state):
if value is None: if value is None:
return return
styles_used.add(prop)
lv.call(f"obj_set_style_{prop}", self.obj, value, state) lv.call(f"obj_set_style_{prop}", self.obj, value, state)
def __type_base(self): def __type_base(self):

View file

@ -2,13 +2,12 @@ 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_DURATION, CONF_ID from esphome.const import CONF_DURATION, CONF_ID
from esphome.cpp_generator import MockObj
from ..automation import action_to_code from ..automation import action_to_code
from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC
from ..helpers import lvgl_components_required from ..helpers import lvgl_components_required
from ..lv_validation import lv_image, lv_milliseconds 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 ..types import LvType, ObjUpdateAction, void_ptr
from . import Widget, WidgetType, get_widgets from . import Widget, WidgetType, get_widgets
from .img import CONF_IMAGE from .img import CONF_IMAGE
@ -63,7 +62,7 @@ class AnimimgType(WidgetType):
if CONF_SRC in config: if CONF_SRC in config:
for x in config[CONF_SRC]: for x in config[CONF_SRC]:
await cg.get_variable(x) 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) src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs)
count = len(config[CONF_SRC]) count = len(config[CONF_SRC])
lv.animimg_set_src(w.obj, src_id, count) lv.animimg_set_src(w.obj, src_id, count)
@ -73,7 +72,7 @@ class AnimimgType(WidgetType):
lv.animimg_start(w.obj) lv.animimg_start(w.obj)
def get_uses(self): def get_uses(self):
return CONF_IMAGE, CONF_LABEL return "img", CONF_IMAGE, CONF_LABEL
animimg_spec = AnimimgType() animimg_spec = AnimimgType()

View file

@ -20,6 +20,7 @@ from ..defines import (
CONF_END_VALUE, CONF_END_VALUE,
CONF_INDICATOR, CONF_INDICATOR,
CONF_MAIN, CONF_MAIN,
CONF_OPA,
CONF_PIVOT_X, CONF_PIVOT_X,
CONF_PIVOT_Y, CONF_PIVOT_Y,
CONF_SRC, CONF_SRC,
@ -35,10 +36,11 @@ from ..lv_validation import (
lv_color, lv_color,
lv_float, lv_float,
lv_image, lv_image,
opacity,
requires_component, requires_component,
size, 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 ..types import LvType, ObjUpdateAction
from . import Widget, WidgetType, get_widgets from . import Widget, WidgetType, get_widgets
from .arc import CONF_ARC 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_COLOR, default=0): lv_color,
cv.Optional(CONF_R_MOD, default=0): size, cv.Optional(CONF_R_MOD, default=0): size,
cv.Optional(CONF_VALUE): lv_float, cv.Optional(CONF_VALUE): lv_float,
cv.Optional(CONF_OPA): opacity,
} }
) )
INDICATOR_IMG_SCHEMA = cv.Schema( INDICATOR_IMG_SCHEMA = cv.Schema(
@ -84,6 +87,7 @@ INDICATOR_IMG_SCHEMA = cv.Schema(
cv.Required(CONF_PIVOT_X): pixels, cv.Required(CONF_PIVOT_X): pixels,
cv.Required(CONF_PIVOT_Y): pixels, cv.Required(CONF_PIVOT_Y): pixels,
cv.Optional(CONF_VALUE): lv_float, cv.Optional(CONF_VALUE): lv_float,
cv.Optional(CONF_OPA): opacity,
} }
) )
INDICATOR_ARC_SCHEMA = cv.Schema( 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_VALUE, CONF_VALUE): lv_float,
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
cv.Optional(CONF_END_VALUE): lv_float, cv.Optional(CONF_END_VALUE): lv_float,
cv.Optional(CONF_OPA): opacity,
} }
) )
INDICATOR_TICKS_SCHEMA = cv.Schema( INDICATOR_TICKS_SCHEMA = cv.Schema(
@ -218,9 +223,7 @@ class MeterType(WidgetType):
for indicator in scale_conf.get(CONF_INDICATORS, ()): for indicator in scale_conf.get(CONF_INDICATORS, ()):
(t, v) = next(iter(indicator.items())) (t, v) = next(iter(indicator.items()))
iid = v[CONF_ID] iid = v[CONF_ID]
ivar = cg.new_variable( ivar = cg.Pvariable(iid, cg.nullptr, type_=lv_meter_indicator_t)
iid, cg.nullptr, type_=lv_meter_indicator_t_ptr
)
# Enable getting the meter to which this belongs. # Enable getting the meter to which this belongs.
wid = Widget.create(iid, var, obj_spec, v) wid = Widget.create(iid, var, obj_spec, v)
wid.obj = ivar wid.obj = ivar
@ -268,9 +271,7 @@ class MeterType(WidgetType):
v[CONF_PIVOT_Y], v[CONF_PIVOT_Y],
), ),
) )
start_value = await get_start_value(v) await set_indicator_values(var, ivar, v)
end_value = await get_end_value(v)
set_indicator_values(var, ivar, start_value, end_value)
meter_spec = MeterType() meter_spec = MeterType()
@ -285,21 +286,22 @@ meter_spec = MeterType()
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
cv.Optional(CONF_END_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): async def indicator_update_to_code(config, action_id, template_arg, args):
widget = await get_widgets(config) 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): 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) 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 start_value is not None:
if end_value is None: if end_value is None:
lv.meter_set_indicator_value(meter, indicator, start_value) 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) lv.meter_set_indicator_start_value(meter, indicator, start_value)
if end_value is not None: if end_value is not None:
lv.meter_set_indicator_end_value(meter, indicator, end_value) 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)

View file

@ -2,13 +2,13 @@ spi:
- id: spi_main_lcd - id: spi_main_lcd
clk_pin: 16 clk_pin: 16
mosi_pin: 17 mosi_pin: 17
miso_pin: 15 miso_pin: 32
display: display:
- platform: ili9xxx - platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 12 cs_pin: 14
dc_pin: 13 dc_pin: 13
reset_pin: 21 reset_pin: 21
invert_colors: true invert_colors: true

View file

@ -8,8 +8,8 @@ display:
- platform: ili9xxx - platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 8 cs_pin: 3
dc_pin: 9 dc_pin: 11
reset_pin: 10 reset_pin: 10
invert_colors: true invert_colors: true

View file

@ -8,8 +8,8 @@ display:
- platform: ili9xxx - platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 8 cs_pin: 3
dc_pin: 9 dc_pin: 11
reset_pin: 10 reset_pin: 10
invert_colors: true invert_colors: true

View file

@ -2,13 +2,13 @@ spi:
- id: spi_main_lcd - id: spi_main_lcd
clk_pin: 16 clk_pin: 16
mosi_pin: 17 mosi_pin: 17
miso_pin: 15 miso_pin: 18
display: display:
- platform: ili9xxx - platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 12 cs_pin: 19
dc_pin: 13 dc_pin: 13
reset_pin: 21 reset_pin: 21
invert_colors: true invert_colors: true

View file

@ -608,6 +608,58 @@ lvgl:
id: tabview_id id: tabview_id
index: 0 index: 0
animated: true 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: font:
- file: "gfonts://Roboto" - file: "gfonts://Roboto"
id: space16 id: space16

View file

@ -10,7 +10,7 @@ sensor:
- platform: rotary_encoder - platform: rotary_encoder
name: "Rotary Encoder" name: "Rotary Encoder"
id: encoder id: encoder
pin_a: 2 pin_a: 3
pin_b: 1 pin_b: 1
internal: true internal: true