[lvgl] Add direct use of mapping (#15863)

This commit is contained in:
Clyde Stubbs
2026-07-09 03:00:42 +10:00
committed by GitHub
parent bba3a9657b
commit b787281388
6 changed files with 128 additions and 15 deletions
+2
View File
@@ -739,6 +739,7 @@ CONF_GRID_ROWS = "grid_rows"
CONF_HEADER_BUTTONS = "header_buttons"
CONF_HEADER_MODE = "header_mode"
CONF_HOME = "home"
CONF_IMAGE = "image"
CONF_INDICATORS = "indicators"
CONF_INITIAL_FOCUS = "initial_focus"
CONF_SELECTED_DIGIT = "selected_digit"
@@ -752,6 +753,7 @@ CONF_LONG_PRESS_REPEAT_TIME = "long_press_repeat_time"
CONF_LVGL_ID = "lvgl_id"
CONF_LONG_MODE = "long_mode"
CONF_MAJOR_TICKS_STYLE = "major_ticks_style"
CONF_MAPPING = "mapping"
CONF_MSGBOXES = "msgboxes"
CONF_OBJ = "obj"
CONF_ONE_CHECKED = "one_checked"
+62 -8
View File
@@ -22,9 +22,12 @@ from esphome.helpers import cpp_string_escape
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import Expression, SafeExpType
from ..mapping import INDEX_TYPES, get_mapping_metadata
from . import types as ty
from .defines import (
CONF_END_VALUE,
CONF_IMAGE,
CONF_MAPPING,
CONF_START_VALUE,
CONF_TIME_FORMAT,
LV_FONTS,
@@ -375,21 +378,54 @@ def stop_value(value):
return cv.int_range(0, 255)(value)
def image_validator(value):
value = cv.requires_component("image")(value)
def _image_validator(value):
if isinstance(value, dict) and CONF_MAPPING in value:
from .schemas import MAPPING_IMAGE_SCHEMA
return MAPPING_IMAGE_SCHEMA(value)
value = cv.use_id(Image_)(value)
get_lv_images_used().add(value)
add_lv_use("label")
return value
lv_image = LValidator(
image_validator,
image.Image_.operator("ptr"),
requires="image",
)
class ImageValidator(LValidator):
def __init__(self):
super().__init__(
validator=_image_validator,
rtype=image.Image_.operator("ptr"),
requires=CONF_IMAGE,
)
async def process(
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
) -> Expression:
# Local import to avoid circular import at module level
from .lvcode import get_lambda_context_args
args = args or get_lambda_context_args()
if isinstance(value, dict) and CONF_MAPPING in value:
mapping_id = value[CONF_MAPPING]
mapping_var = await cg.get_variable(mapping_id)
metadata = get_mapping_metadata(mapping_id.id)
index = value[CONF_VALUE]
if isinstance(index, Lambda):
index = call_lambda(
await cg.process_lambda(
index, args, return_type=metadata.from_.data_type
)
)
else:
index = await metadata.from_.convert_value(index)
return mapping_var.get(index)
return await super().process(value, args)
lv_image = ImageValidator()
lv_image_list = LValidator(
cv.ensure_list(image_validator),
cv.ensure_list(_image_validator),
cg.std_vector.template(image.Image_.operator("ptr")),
requires="image",
)
@@ -440,6 +476,24 @@ class TextValidator(LValidator):
f"(std::isfinite({arg_expr}) ? {sprintf_str} : {nanval})"
)
return literal(sprintf_str)
if mapping_id := value.get(CONF_MAPPING):
mapping_var = await cg.get_variable(mapping_id)
metadata = get_mapping_metadata(mapping_id.id)
if metadata.to_ != INDEX_TYPES["string"]:
raise ValueError(
f"Mapping {mapping_id} does not map to strings, cannot use in text"
)
index = value[CONF_VALUE]
if isinstance(index, Lambda):
index = call_lambda(
await cg.process_lambda(
index, args, return_type=metadata.from_.data_type
)
)
else:
index = await metadata.from_.convert_value(index)
return mapping_var.get(index).c_str()
if time_format := value.get(CONF_TIME_FORMAT):
source = value[CONF_TIME]
if isinstance(source, Lambda):
+19
View File
@@ -3,6 +3,7 @@ from typing import Any
from esphome import config_validation as cv
from esphome.automation import Trigger, validate_automation
from esphome.components.mapping import mapping_class
from esphome.components.time import RealTimeClock
from esphome.config_validation import prepend_path
from esphome.const import (
@@ -17,6 +18,7 @@ from esphome.const import (
CONF_TEXT,
CONF_TIME,
CONF_TRIGGER_ID,
CONF_VALUE,
CONF_X,
CONF_Y,
)
@@ -31,6 +33,7 @@ from esphome.schema_extractors import (
from . import defines as df, lv_validation as lvalid
from .defines import (
CONF_EXT_CLICK_AREA,
CONF_MAPPING,
CONF_SCROLL_DIR,
CONF_SCROLL_SNAP_X,
CONF_SCROLL_SNAP_Y,
@@ -89,6 +92,20 @@ PRINTF_TEXT_SCHEMA = cv.All(
validate_printf,
)
MAPPING_TEXT_SCHEMA = cv.Schema(
{
cv.Required(CONF_MAPPING): cv.use_id(mapping_class),
cv.Required(CONF_VALUE): cv.templatable(cv.string),
}
)
MAPPING_IMAGE_SCHEMA = cv.Schema(
{
cv.Required(CONF_MAPPING): cv.use_id(mapping_class),
cv.Required(CONF_VALUE): cv.templatable(cv.string),
}
)
def _validate_text(value):
"""
@@ -100,6 +117,8 @@ def _validate_text(value):
if isinstance(value, dict):
if CONF_TIME_FORMAT in value:
return TIME_TEXT_SCHEMA(value)
if CONF_MAPPING in value:
return MAPPING_TEXT_SCHEMA(value)
return PRINTF_TEXT_SCHEMA(value)
return cv.templatable(cv.string)(value)
+15 -2
View File
@@ -1,3 +1,5 @@
from esphome.components.image import INSTANCE_TYPE as IMAGE_TYPE
from esphome.components.mapping import get_mapping_metadata
import esphome.config_validation as cv
from esphome.const import (
CONF_ANGLE,
@@ -9,7 +11,9 @@ from esphome.const import (
from ..defines import (
CONF_ANTIALIAS,
CONF_IMAGE,
CONF_MAIN,
CONF_MAPPING,
CONF_PIVOT_X,
CONF_PIVOT_Y,
CONF_SCALE,
@@ -21,8 +25,6 @@ from ..types import lv_image_t
from . import Widget, WidgetType
from .label import CONF_LABEL
CONF_IMAGE = "image"
BASE_IMG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_PIVOT_X): size,
@@ -69,5 +71,16 @@ class ImgType(WidgetType):
for prop, validator in BASE_IMG_SCHEMA.schema.items():
await w.set_property(prop, config, processor=validator)
def final_validate(self, widget, update_config, widget_config, path):
src = update_config.get(CONF_SRC)
if isinstance(src, dict) and CONF_MAPPING in src:
mapping_id = src[CONF_MAPPING]
metadata = get_mapping_metadata(mapping_id.id)
if str(metadata.to_.data_type) != str(IMAGE_TYPE):
raise cv.Invalid(
f"Mapping '{mapping_id}' does not map to an image type, but '{metadata.to_.data_type}'",
path=path + [CONF_SRC, CONF_MAPPING],
)
img_spec = ImgType()
+2 -2
View File
@@ -91,8 +91,8 @@ binary_sensor:
animation: move_right
time: 600ms
- platform: lvgl
id: button_checker
name: LVGL button
id: common_button_checker
name: Common button
widget: spin_up
on_state:
then:
+28 -3
View File
@@ -263,6 +263,9 @@ lvgl:
bg_opa: !lambda return 0.5;
- lvgl.image.update:
id: lv_image
src:
mapping: image_map
value: !lambda return round(1.0);
scale: !lambda return 512;
rotation: !lambda return 100;
pivot_x: !lambda return 20;
@@ -388,9 +391,16 @@ lvgl:
text_font: montserrat_40
border_post: true
on_press:
lvgl.label.update:
id: hello_label
text: Goodbye
- lvgl.label.update:
id: hello_label
text:
mapping: lvgl_string_map
value: !lambda return 2;
- lvgl.label.update:
id: hello_label
text:
mapping: lvgl_string_map
value: 2
on_click:
then:
- lvgl.animimg.stop: anim_img
@@ -1496,6 +1506,21 @@ image:
invert_alpha: true
transparency: alpha_channel
mapping:
- id: image_map
from: int
to: image
entries:
0: cat_image
1: dog_image
- id: lvgl_string_map
from: int
to: string
entries:
0: "First"
1: "Second"
2: "Third"
color:
- id: light_blue
hex: "3340FF"