From 56aa58780da5fe73637ed9f583fafbd0b3a26db7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 18 Aug 2024 20:27:03 +1200 Subject: [PATCH 01/13] Revert "[validation] Allow ``maybe_simple_value`` to not have default key in complex value" (#7305) --- esphome/config_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 6d6cb451d6..719cc43b31 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1864,7 +1864,7 @@ def maybe_simple_value(*validators, **kwargs): if value == SCHEMA_EXTRACT: return (validator, key) - if isinstance(value, dict): + if isinstance(value, dict) and key in value: return validator(value) return validator({key: value}) From ac9417d4694a69d843457ce3fa40f6e9c959c64a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 19 Aug 2024 08:43:23 +1000 Subject: [PATCH 02/13] [lvgl] Bug fixes (#7300) --- esphome/components/lvgl/defines.py | 32 ++++---- esphome/components/lvgl/lv_validation.py | 86 +++++++++++++-------- esphome/components/lvgl/schemas.py | 32 +++++--- esphome/components/lvgl/widgets/__init__.py | 23 +++++- tests/components/lvgl/common.yaml | 8 ++ tests/components/lvgl/lvgl-package.yaml | 58 ++++++++++++-- 6 files changed, 173 insertions(+), 66 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 8f7a973722..6a8b20b505 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -6,8 +6,8 @@ Constants already defined in esphome.const are not duplicated here and must be i from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS -from esphome.core import ID, Lambda -from esphome.cpp_generator import MockObj +from esphome.core import Lambda +from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_types import uint32 from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -22,19 +22,22 @@ def literal(arg): return arg +def call_lambda(lamb: LambdaExpression): + expr = lamb.content.strip() + if expr.startswith("return") and expr.endswith(";"): + return expr[7:][:-1] + return f"{lamb}()" + + class LValidator: """ A validator for a particular type used in LVGL. Usable in configs as a validator, also has `process()` to convert a value during code generation """ - def __init__( - self, validator, rtype, idtype=None, idexpr=None, retmapper=None, requires=None - ): + def __init__(self, validator, rtype, retmapper=None, requires=None): self.validator = validator self.rtype = rtype - self.idtype = idtype - self.idexpr = idexpr self.retmapper = retmapper self.requires = requires @@ -43,8 +46,6 @@ class LValidator: value = requires_component(self.requires)(value) if isinstance(value, cv.Lambda): return cv.returning_lambda(value) - if self.idtype is not None and isinstance(value, ID): - return cv.use_id(self.idtype)(value) return self.validator(value) async def process(self, value, args=()): @@ -52,10 +53,10 @@ class LValidator: return None if isinstance(value, Lambda): return cg.RawExpression( - f"{await cg.process_lambda(value, args, return_type=self.rtype)}()" + call_lambda( + await cg.process_lambda(value, args, return_type=self.rtype) + ) ) - if self.idtype is not None and isinstance(value, ID): - return cg.RawExpression(f"{value}->{self.idexpr}") if self.retmapper is not None: return self.retmapper(value) return cg.safe_exp(value) @@ -89,7 +90,7 @@ class LvConstant(LValidator): cv.ensure_list(self.one_of), uint32, retmapper=self.mapper ) - def mapper(self, value, args=()): + def mapper(self, value): if not isinstance(value, list): value = [value] return literal( @@ -103,7 +104,7 @@ class LvConstant(LValidator): def extend(self, *choices): """ - Extend an LVCconstant with additional choices. + Extend an LVconstant with additional choices. :param choices: The extra choices :return: A new LVConstant instance """ @@ -431,6 +432,8 @@ CONF_ONE_LINE = "one_line" CONF_ON_SELECT = "on_select" CONF_ONE_CHECKED = "one_checked" CONF_NEXT = "next" +CONF_PAD_ROW = "pad_row" +CONF_PAD_COLUMN = "pad_column" CONF_PAGE = "page" CONF_PAGE_WRAP = "page_wrap" CONF_PASSWORD_MODE = "password_mode" @@ -462,6 +465,7 @@ CONF_SKIP = "skip" CONF_SYMBOL = "symbol" CONF_TAB_ID = "tab_id" CONF_TABS = "tabs" +CONF_TIME_FORMAT = "time_format" CONF_TILE = "tile" CONF_TILE_ID = "tile_id" CONF_TILES = "tiles" diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index b351b84af6..a2be4a2abe 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,17 +1,14 @@ from typing import Union import esphome.codegen as cg -from esphome.components.binary_sensor import BinarySensor from esphome.components.color import ColorStruct from esphome.components.font import Font from esphome.components.image import Image_ -from esphome.components.sensor import Sensor -from esphome.components.text_sensor import TextSensor import esphome.config_validation as cv -from esphome.const import CONF_ARGS, CONF_COLOR, CONF_FORMAT, CONF_VALUE -from esphome.core import HexInt +from esphome.const import CONF_ARGS, CONF_COLOR, CONF_FORMAT, CONF_TIME, CONF_VALUE +from esphome.core import HexInt, Lambda from esphome.cpp_generator import MockObj -from esphome.cpp_types import uint32 +from esphome.cpp_types import ESPTime, uint32 from esphome.helpers import cpp_string_escape from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -19,9 +16,11 @@ from . import types as ty from .defines import ( CONF_END_VALUE, CONF_START_VALUE, + CONF_TIME_FORMAT, LV_FONTS, LValidator, LvConstant, + call_lambda, literal, ) from .helpers import ( @@ -110,13 +109,13 @@ def angle(value): def size_validator(value): """A size in one axis - one of "size_content", a number (pixels) or a percentage""" if value == SCHEMA_EXTRACT: - return ["size_content", "pixels", "..%"] + return ["SIZE_CONTENT", "number of pixels", "percentage"] if isinstance(value, str) and value.lower().endswith("px"): value = cv.int_(value[:-2]) if isinstance(value, str) and not value.endswith("%"): if value.upper() == "SIZE_CONTENT": return "LV_SIZE_CONTENT" - raise cv.Invalid("must be 'size_content', a pixel position or a percentage") + raise cv.Invalid("must be 'size_content', a percentage or an integer (pixels)") if isinstance(value, int): return cv.int_(value) # Will throw an exception if not a percentage. @@ -125,6 +124,15 @@ def size_validator(value): size = LValidator(size_validator, uint32, retmapper=literal) + +def pixels_validator(value): + if isinstance(value, str) and value.lower().endswith("px"): + return cv.int_(value[:-2]) + return cv.int_(value) + + +pixels = LValidator(pixels_validator, uint32, retmapper=literal) + radius_consts = LvConstant("LV_RADIUS_", "CIRCLE") @@ -167,9 +175,7 @@ lv_image = LValidator( retmapper=lambda x: lv_expr.img_from(MockObj(x)), requires="image", ) -lv_bool = LValidator( - cv.boolean, cg.bool_, BinarySensor, "get_state()", retmapper=literal -) +lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal) def lv_pct(value: Union[int, float]): @@ -185,42 +191,60 @@ def lvms_validator_(value): lv_milliseconds = LValidator( - lvms_validator_, - cg.int32, - retmapper=lambda x: x.total_milliseconds, + lvms_validator_, cg.int32, retmapper=lambda x: x.total_milliseconds ) class TextValidator(LValidator): def __init__(self): - super().__init__( - cv.string, - cg.const_char_ptr, - TextSensor, - "get_state().c_str()", - lambda s: cg.safe_exp(f"{s}"), - ) + super().__init__(cv.string, cg.std_string, lambda s: cg.safe_exp(f"{s}")) def __call__(self, value): - if isinstance(value, dict): + if isinstance(value, dict) and CONF_FORMAT in value: return value return super().__call__(value) async def process(self, value, args=()): if isinstance(value, dict): - args = [str(x) for x in value[CONF_ARGS]] - arg_expr = cg.RawExpression(",".join(args)) - format_str = cpp_string_escape(value[CONF_FORMAT]) - return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()") + if format_str := value.get(CONF_FORMAT): + args = [str(x) for x in value[CONF_ARGS]] + arg_expr = cg.RawExpression(",".join(args)) + format_str = cpp_string_escape(format_str) + return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()") + if time_format := value.get(CONF_TIME_FORMAT): + source = value[CONF_TIME] + if isinstance(source, Lambda): + time_format = cpp_string_escape(time_format) + return cg.RawExpression( + call_lambda( + await cg.process_lambda(source, args, return_type=ESPTime) + ) + + f".strftime({time_format}).c_str()" + ) + # must be an ID + source = await cg.get_variable(source) + return source.now().strftime(time_format).c_str() + if isinstance(value, Lambda): + value = call_lambda( + await cg.process_lambda(value, args, return_type=self.rtype) + ) + + # Was the lambda call reduced to a string? + if value.endswith("c_str()") or ( + value.endswith('"') and value.startswith('"') + ): + pass + else: + # Either a std::string or a lambda call returning that. We need const char* + value = f"({value}).c_str()" + return cg.RawExpression(value) return await super().process(value, args) lv_text = TextValidator() -lv_float = LValidator(cv.float_, cg.float_, Sensor, "get_state()") -lv_int = LValidator(cv.int_, cg.int_, Sensor, "get_state()") -lv_brightness = LValidator( - cv.percentage, cg.float_, Sensor, "get_state()", retmapper=lambda x: int(x * 255) -) +lv_float = LValidator(cv.float_, cg.float_) +lv_int = LValidator(cv.int_, cg.int_) +lv_brightness = LValidator(cv.percentage, cg.float_, retmapper=lambda x: int(x * 255)) def is_lv_font(font): diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index f1c7ff4df6..e9714e3b1a 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -1,5 +1,6 @@ from esphome import config_validation as cv from esphome.automation import Trigger, validate_automation +from esphome.components.time import RealTimeClock from esphome.const import ( CONF_ARGS, CONF_FORMAT, @@ -8,6 +9,7 @@ from esphome.const import ( CONF_ON_VALUE, CONF_STATE, CONF_TEXT, + CONF_TIME, CONF_TRIGGER_ID, CONF_TYPE, ) @@ -15,6 +17,7 @@ from esphome.core import TimePeriod from esphome.schema_extractors import SCHEMA_EXTRACT from . import defines as df, lv_validation as lvalid +from .defines import CONF_TIME_FORMAT from .helpers import add_lv_use, requires_component, validate_printf from .lv_validation import lv_color, lv_font, lv_image from .lvcode import LvglComponent @@ -46,7 +49,13 @@ TEXT_SCHEMA = cv.Schema( ), validate_printf, ), - lvalid.lv_text, + cv.Schema( + { + cv.Required(CONF_TIME_FORMAT): cv.string, + cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)), + } + ), + cv.templatable(cv.string), ) } ) @@ -116,15 +125,13 @@ STYLE_PROPS = { "opa_layered": lvalid.opacity, "outline_color": lvalid.lv_color, "outline_opa": lvalid.opacity, - "outline_pad": lvalid.size, - "outline_width": lvalid.size, - "pad_all": lvalid.size, - "pad_bottom": lvalid.size, - "pad_column": lvalid.size, - "pad_left": lvalid.size, - "pad_right": lvalid.size, - "pad_row": lvalid.size, - "pad_top": lvalid.size, + "outline_pad": lvalid.pixels, + "outline_width": lvalid.pixels, + "pad_all": lvalid.pixels, + "pad_bottom": lvalid.pixels, + "pad_left": lvalid.pixels, + "pad_right": lvalid.pixels, + "pad_top": lvalid.pixels, "shadow_color": lvalid.lv_color, "shadow_ofs_x": cv.int_, "shadow_ofs_y": cv.int_, @@ -304,6 +311,8 @@ LAYOUT_SCHEMA = { cv.Required(df.CONF_GRID_COLUMNS): [grid_spec], cv.Optional(df.CONF_GRID_COLUMN_ALIGN): grid_alignments, cv.Optional(df.CONF_GRID_ROW_ALIGN): grid_alignments, + cv.Optional(df.CONF_PAD_ROW): lvalid.pixels, + cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels, }, df.TYPE_FLEX: { cv.Optional( @@ -312,6 +321,8 @@ LAYOUT_SCHEMA = { cv.Optional(df.CONF_FLEX_ALIGN_MAIN, default="start"): flex_alignments, cv.Optional(df.CONF_FLEX_ALIGN_CROSS, default="start"): flex_alignments, cv.Optional(df.CONF_FLEX_ALIGN_TRACK, default="start"): flex_alignments, + cv.Optional(df.CONF_PAD_ROW): lvalid.pixels, + cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels, }, }, lower=True, @@ -338,7 +349,6 @@ DISP_BG_SCHEMA = cv.Schema( } ) - # A style schema that can include text STYLED_TEXT_SCHEMA = cv.maybe_simple_value( STYLE_SCHEMA.extend(TEXT_SCHEMA), key=CONF_TEXT diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 603de6aa3e..4abb25c61d 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -20,6 +20,8 @@ from ..defines import ( CONF_GRID_ROWS, CONF_LAYOUT, CONF_MAIN, + CONF_PAD_COLUMN, + CONF_PAD_ROW, CONF_SCROLLBAR_MODE, CONF_STYLES, CONF_WIDGETS, @@ -29,6 +31,7 @@ from ..defines import ( TYPE_FLEX, TYPE_GRID, LValidator, + call_lambda, join_enums, literal, ) @@ -273,6 +276,10 @@ async def set_obj_properties(w: Widget, config): layout_type: str = layout[CONF_TYPE] add_lv_use(layout_type) lv_obj.set_layout(w.obj, literal(f"LV_LAYOUT_{layout_type.upper()}")) + if (pad_row := layout.get(CONF_PAD_ROW)) is not None: + w.set_style(CONF_PAD_ROW, pad_row, 0) + if (pad_column := layout.get(CONF_PAD_COLUMN)) is not None: + w.set_style(CONF_PAD_COLUMN, pad_column, 0) if layout_type == TYPE_GRID: wid = config[CONF_ID] rows = [str(x) for x in layout[CONF_GRID_ROWS]] @@ -316,8 +323,13 @@ async def set_obj_properties(w: Widget, config): flag_clr = set() flag_set = set() props = parts[CONF_MAIN][CONF_DEFAULT] + lambs = {} + flag_set = set() + flag_clr = set() for prop, value in {k: v for k, v in props.items() if k in OBJ_FLAGS}.items(): - if value: + if isinstance(value, cv.Lambda): + lambs[prop] = value + elif value: flag_set.add(prop) else: flag_clr.add(prop) @@ -327,6 +339,13 @@ async def set_obj_properties(w: Widget, config): if flag_clr: clrs = join_enums(flag_clr, "LV_OBJ_FLAG_") w.clear_flag(clrs) + for key, value in lambs.items(): + lamb = await cg.process_lambda(value, [], return_type=cg.bool_) + flag = f"LV_OBJ_FLAG_{key.upper()}" + with LvConditional(call_lambda(lamb)) as cond: + w.add_flag(flag) + cond.else_() + w.clear_flag(flag) if states := config.get(CONF_STATE): adds = set() @@ -348,7 +367,7 @@ async def set_obj_properties(w: Widget, config): for key, value in lambs.items(): lamb = await cg.process_lambda(value, [], return_type=cg.bool_) state = f"LV_STATE_{key.upper()}" - with LvConditional(f"{lamb}()") as cond: + with LvConditional(call_lambda(lamb)) as cond: w.add_state(state) cond.else_() w.clear_state(state) diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index 002c7a118d..7ef7772ac9 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -127,3 +127,11 @@ binary_sensor: - platform: lvgl name: LVGL checkbox widget: checkbox_id + +wifi: + ssid: SSID + password: PASSWORD123 + +time: + platform: sntp + id: time_id diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 54022354f5..800d6eff27 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -16,8 +16,6 @@ lvgl: border_width: 0 radius: 0 pad_all: 0 - pad_row: 0 - pad_column: 0 border_color: 0x0077b3 text_color: 0xFFFFFF width: 100% @@ -55,6 +53,13 @@ lvgl: pages: - id: page1 skip: true + layout: + type: flex + pad_row: 4 + pad_column: 4px + flex_align_main: center + flex_align_cross: start + flex_align_track: end widgets: - animimg: height: 60 @@ -118,10 +123,8 @@ lvgl: outline_width: 10px pad_all: 10px pad_bottom: 10px - pad_column: 10px pad_left: 10px pad_right: 10px - pad_row: 10px pad_top: 10px shadow_color: light_blue shadow_ofs_x: 5 @@ -221,10 +224,47 @@ lvgl: - label: text: Button on_click: - lvgl.label.update: - id: hello_label - bg_color: 0x123456 - text: clicked + - lvgl.label.update: + id: hello_label + bg_color: 0x123456 + text: clicked + - lvgl.label.update: + id: hello_label + text: !lambda return "hello world"; + - lvgl.label.update: + id: hello_label + text: !lambda |- + ESP_LOGD("label", "multi-line lambda"); + return "hello world"; + - lvgl.label.update: + id: hello_label + text: !lambda 'return str_sprintf("Hello space");' + - lvgl.label.update: + id: hello_label + text: + format: "sprintf format %s" + args: ['x ? "checked" : "unchecked"'] + - lvgl.label.update: + id: hello_label + text: + time_format: "%c" + - lvgl.label.update: + id: hello_label + text: + time_format: "%c" + time: time_id + - lvgl.label.update: + id: hello_label + text: + time_format: "%c" + time: !lambda return id(time_id).now(); + - lvgl.label.update: + id: hello_label + text: + time_format: "%c" + time: !lambda |- + ESP_LOGD("label", "multi-line lambda"); + return id(time_id).now(); on_value: logger.log: format: "state now %d" @@ -396,6 +436,8 @@ lvgl: grid_row_align: end grid_rows: [25px, fr(1), content] grid_columns: [40, fr(1), fr(1)] + pad_row: 6px + pad_column: 0 widgets: - image: grid_cell_row_pos: 0 From 8b6d6fe6616f0c83c72c9ff395f74134a8956dd4 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 19 Aug 2024 00:45:10 +0200 Subject: [PATCH 03/13] [speaker] Fix header includes (#7304) --- esphome/components/speaker/speaker.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/speaker/speaker.h b/esphome/components/speaker/speaker.h index 142231881c..193049402d 100644 --- a/esphome/components/speaker/speaker.h +++ b/esphome/components/speaker/speaker.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + namespace esphome { namespace speaker { From baedd74c7a5f54a871eb413a629d6dd94c15510a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:45:22 +1200 Subject: [PATCH 04/13] [microphone] Fix header includes (#7310) --- esphome/components/microphone/microphone.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/microphone/microphone.h b/esphome/components/microphone/microphone.h index e01a10e15c..883ca97710 100644 --- a/esphome/components/microphone/microphone.h +++ b/esphome/components/microphone/microphone.h @@ -1,6 +1,9 @@ #pragma once -#include "esphome/core/entity_base.h" +#include +#include +#include +#include #include "esphome/core/helpers.h" namespace esphome { From 7464b440c078794c0dec88c3a991e2c081695855 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 18 Aug 2024 20:27:03 +1200 Subject: [PATCH 05/13] Revert "[validation] Allow ``maybe_simple_value`` to not have default key in complex value" (#7305) --- esphome/config_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 6d6cb451d6..719cc43b31 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1864,7 +1864,7 @@ def maybe_simple_value(*validators, **kwargs): if value == SCHEMA_EXTRACT: return (validator, key) - if isinstance(value, dict): + if isinstance(value, dict) and key in value: return validator(value) return validator({key: value}) From 5c7d070307c7a04e452cd641cdc3fa4baa477e18 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 19 Aug 2024 08:43:23 +1000 Subject: [PATCH 06/13] [lvgl] Bug fixes (#7300) --- esphome/components/lvgl/defines.py | 32 ++++---- esphome/components/lvgl/lv_validation.py | 86 +++++++++++++-------- esphome/components/lvgl/schemas.py | 32 +++++--- esphome/components/lvgl/widgets/__init__.py | 23 +++++- tests/components/lvgl/common.yaml | 8 ++ tests/components/lvgl/lvgl-package.yaml | 58 ++++++++++++-- 6 files changed, 173 insertions(+), 66 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 8f7a973722..6a8b20b505 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -6,8 +6,8 @@ Constants already defined in esphome.const are not duplicated here and must be i from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS -from esphome.core import ID, Lambda -from esphome.cpp_generator import MockObj +from esphome.core import Lambda +from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_types import uint32 from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -22,19 +22,22 @@ def literal(arg): return arg +def call_lambda(lamb: LambdaExpression): + expr = lamb.content.strip() + if expr.startswith("return") and expr.endswith(";"): + return expr[7:][:-1] + return f"{lamb}()" + + class LValidator: """ A validator for a particular type used in LVGL. Usable in configs as a validator, also has `process()` to convert a value during code generation """ - def __init__( - self, validator, rtype, idtype=None, idexpr=None, retmapper=None, requires=None - ): + def __init__(self, validator, rtype, retmapper=None, requires=None): self.validator = validator self.rtype = rtype - self.idtype = idtype - self.idexpr = idexpr self.retmapper = retmapper self.requires = requires @@ -43,8 +46,6 @@ class LValidator: value = requires_component(self.requires)(value) if isinstance(value, cv.Lambda): return cv.returning_lambda(value) - if self.idtype is not None and isinstance(value, ID): - return cv.use_id(self.idtype)(value) return self.validator(value) async def process(self, value, args=()): @@ -52,10 +53,10 @@ class LValidator: return None if isinstance(value, Lambda): return cg.RawExpression( - f"{await cg.process_lambda(value, args, return_type=self.rtype)}()" + call_lambda( + await cg.process_lambda(value, args, return_type=self.rtype) + ) ) - if self.idtype is not None and isinstance(value, ID): - return cg.RawExpression(f"{value}->{self.idexpr}") if self.retmapper is not None: return self.retmapper(value) return cg.safe_exp(value) @@ -89,7 +90,7 @@ class LvConstant(LValidator): cv.ensure_list(self.one_of), uint32, retmapper=self.mapper ) - def mapper(self, value, args=()): + def mapper(self, value): if not isinstance(value, list): value = [value] return literal( @@ -103,7 +104,7 @@ class LvConstant(LValidator): def extend(self, *choices): """ - Extend an LVCconstant with additional choices. + Extend an LVconstant with additional choices. :param choices: The extra choices :return: A new LVConstant instance """ @@ -431,6 +432,8 @@ CONF_ONE_LINE = "one_line" CONF_ON_SELECT = "on_select" CONF_ONE_CHECKED = "one_checked" CONF_NEXT = "next" +CONF_PAD_ROW = "pad_row" +CONF_PAD_COLUMN = "pad_column" CONF_PAGE = "page" CONF_PAGE_WRAP = "page_wrap" CONF_PASSWORD_MODE = "password_mode" @@ -462,6 +465,7 @@ CONF_SKIP = "skip" CONF_SYMBOL = "symbol" CONF_TAB_ID = "tab_id" CONF_TABS = "tabs" +CONF_TIME_FORMAT = "time_format" CONF_TILE = "tile" CONF_TILE_ID = "tile_id" CONF_TILES = "tiles" diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index b351b84af6..a2be4a2abe 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,17 +1,14 @@ from typing import Union import esphome.codegen as cg -from esphome.components.binary_sensor import BinarySensor from esphome.components.color import ColorStruct from esphome.components.font import Font from esphome.components.image import Image_ -from esphome.components.sensor import Sensor -from esphome.components.text_sensor import TextSensor import esphome.config_validation as cv -from esphome.const import CONF_ARGS, CONF_COLOR, CONF_FORMAT, CONF_VALUE -from esphome.core import HexInt +from esphome.const import CONF_ARGS, CONF_COLOR, CONF_FORMAT, CONF_TIME, CONF_VALUE +from esphome.core import HexInt, Lambda from esphome.cpp_generator import MockObj -from esphome.cpp_types import uint32 +from esphome.cpp_types import ESPTime, uint32 from esphome.helpers import cpp_string_escape from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -19,9 +16,11 @@ from . import types as ty from .defines import ( CONF_END_VALUE, CONF_START_VALUE, + CONF_TIME_FORMAT, LV_FONTS, LValidator, LvConstant, + call_lambda, literal, ) from .helpers import ( @@ -110,13 +109,13 @@ def angle(value): def size_validator(value): """A size in one axis - one of "size_content", a number (pixels) or a percentage""" if value == SCHEMA_EXTRACT: - return ["size_content", "pixels", "..%"] + return ["SIZE_CONTENT", "number of pixels", "percentage"] if isinstance(value, str) and value.lower().endswith("px"): value = cv.int_(value[:-2]) if isinstance(value, str) and not value.endswith("%"): if value.upper() == "SIZE_CONTENT": return "LV_SIZE_CONTENT" - raise cv.Invalid("must be 'size_content', a pixel position or a percentage") + raise cv.Invalid("must be 'size_content', a percentage or an integer (pixels)") if isinstance(value, int): return cv.int_(value) # Will throw an exception if not a percentage. @@ -125,6 +124,15 @@ def size_validator(value): size = LValidator(size_validator, uint32, retmapper=literal) + +def pixels_validator(value): + if isinstance(value, str) and value.lower().endswith("px"): + return cv.int_(value[:-2]) + return cv.int_(value) + + +pixels = LValidator(pixels_validator, uint32, retmapper=literal) + radius_consts = LvConstant("LV_RADIUS_", "CIRCLE") @@ -167,9 +175,7 @@ lv_image = LValidator( retmapper=lambda x: lv_expr.img_from(MockObj(x)), requires="image", ) -lv_bool = LValidator( - cv.boolean, cg.bool_, BinarySensor, "get_state()", retmapper=literal -) +lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal) def lv_pct(value: Union[int, float]): @@ -185,42 +191,60 @@ def lvms_validator_(value): lv_milliseconds = LValidator( - lvms_validator_, - cg.int32, - retmapper=lambda x: x.total_milliseconds, + lvms_validator_, cg.int32, retmapper=lambda x: x.total_milliseconds ) class TextValidator(LValidator): def __init__(self): - super().__init__( - cv.string, - cg.const_char_ptr, - TextSensor, - "get_state().c_str()", - lambda s: cg.safe_exp(f"{s}"), - ) + super().__init__(cv.string, cg.std_string, lambda s: cg.safe_exp(f"{s}")) def __call__(self, value): - if isinstance(value, dict): + if isinstance(value, dict) and CONF_FORMAT in value: return value return super().__call__(value) async def process(self, value, args=()): if isinstance(value, dict): - args = [str(x) for x in value[CONF_ARGS]] - arg_expr = cg.RawExpression(",".join(args)) - format_str = cpp_string_escape(value[CONF_FORMAT]) - return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()") + if format_str := value.get(CONF_FORMAT): + args = [str(x) for x in value[CONF_ARGS]] + arg_expr = cg.RawExpression(",".join(args)) + format_str = cpp_string_escape(format_str) + return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()") + if time_format := value.get(CONF_TIME_FORMAT): + source = value[CONF_TIME] + if isinstance(source, Lambda): + time_format = cpp_string_escape(time_format) + return cg.RawExpression( + call_lambda( + await cg.process_lambda(source, args, return_type=ESPTime) + ) + + f".strftime({time_format}).c_str()" + ) + # must be an ID + source = await cg.get_variable(source) + return source.now().strftime(time_format).c_str() + if isinstance(value, Lambda): + value = call_lambda( + await cg.process_lambda(value, args, return_type=self.rtype) + ) + + # Was the lambda call reduced to a string? + if value.endswith("c_str()") or ( + value.endswith('"') and value.startswith('"') + ): + pass + else: + # Either a std::string or a lambda call returning that. We need const char* + value = f"({value}).c_str()" + return cg.RawExpression(value) return await super().process(value, args) lv_text = TextValidator() -lv_float = LValidator(cv.float_, cg.float_, Sensor, "get_state()") -lv_int = LValidator(cv.int_, cg.int_, Sensor, "get_state()") -lv_brightness = LValidator( - cv.percentage, cg.float_, Sensor, "get_state()", retmapper=lambda x: int(x * 255) -) +lv_float = LValidator(cv.float_, cg.float_) +lv_int = LValidator(cv.int_, cg.int_) +lv_brightness = LValidator(cv.percentage, cg.float_, retmapper=lambda x: int(x * 255)) def is_lv_font(font): diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index f1c7ff4df6..e9714e3b1a 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -1,5 +1,6 @@ from esphome import config_validation as cv from esphome.automation import Trigger, validate_automation +from esphome.components.time import RealTimeClock from esphome.const import ( CONF_ARGS, CONF_FORMAT, @@ -8,6 +9,7 @@ from esphome.const import ( CONF_ON_VALUE, CONF_STATE, CONF_TEXT, + CONF_TIME, CONF_TRIGGER_ID, CONF_TYPE, ) @@ -15,6 +17,7 @@ from esphome.core import TimePeriod from esphome.schema_extractors import SCHEMA_EXTRACT from . import defines as df, lv_validation as lvalid +from .defines import CONF_TIME_FORMAT from .helpers import add_lv_use, requires_component, validate_printf from .lv_validation import lv_color, lv_font, lv_image from .lvcode import LvglComponent @@ -46,7 +49,13 @@ TEXT_SCHEMA = cv.Schema( ), validate_printf, ), - lvalid.lv_text, + cv.Schema( + { + cv.Required(CONF_TIME_FORMAT): cv.string, + cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)), + } + ), + cv.templatable(cv.string), ) } ) @@ -116,15 +125,13 @@ STYLE_PROPS = { "opa_layered": lvalid.opacity, "outline_color": lvalid.lv_color, "outline_opa": lvalid.opacity, - "outline_pad": lvalid.size, - "outline_width": lvalid.size, - "pad_all": lvalid.size, - "pad_bottom": lvalid.size, - "pad_column": lvalid.size, - "pad_left": lvalid.size, - "pad_right": lvalid.size, - "pad_row": lvalid.size, - "pad_top": lvalid.size, + "outline_pad": lvalid.pixels, + "outline_width": lvalid.pixels, + "pad_all": lvalid.pixels, + "pad_bottom": lvalid.pixels, + "pad_left": lvalid.pixels, + "pad_right": lvalid.pixels, + "pad_top": lvalid.pixels, "shadow_color": lvalid.lv_color, "shadow_ofs_x": cv.int_, "shadow_ofs_y": cv.int_, @@ -304,6 +311,8 @@ LAYOUT_SCHEMA = { cv.Required(df.CONF_GRID_COLUMNS): [grid_spec], cv.Optional(df.CONF_GRID_COLUMN_ALIGN): grid_alignments, cv.Optional(df.CONF_GRID_ROW_ALIGN): grid_alignments, + cv.Optional(df.CONF_PAD_ROW): lvalid.pixels, + cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels, }, df.TYPE_FLEX: { cv.Optional( @@ -312,6 +321,8 @@ LAYOUT_SCHEMA = { cv.Optional(df.CONF_FLEX_ALIGN_MAIN, default="start"): flex_alignments, cv.Optional(df.CONF_FLEX_ALIGN_CROSS, default="start"): flex_alignments, cv.Optional(df.CONF_FLEX_ALIGN_TRACK, default="start"): flex_alignments, + cv.Optional(df.CONF_PAD_ROW): lvalid.pixels, + cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels, }, }, lower=True, @@ -338,7 +349,6 @@ DISP_BG_SCHEMA = cv.Schema( } ) - # A style schema that can include text STYLED_TEXT_SCHEMA = cv.maybe_simple_value( STYLE_SCHEMA.extend(TEXT_SCHEMA), key=CONF_TEXT diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 603de6aa3e..4abb25c61d 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -20,6 +20,8 @@ from ..defines import ( CONF_GRID_ROWS, CONF_LAYOUT, CONF_MAIN, + CONF_PAD_COLUMN, + CONF_PAD_ROW, CONF_SCROLLBAR_MODE, CONF_STYLES, CONF_WIDGETS, @@ -29,6 +31,7 @@ from ..defines import ( TYPE_FLEX, TYPE_GRID, LValidator, + call_lambda, join_enums, literal, ) @@ -273,6 +276,10 @@ async def set_obj_properties(w: Widget, config): layout_type: str = layout[CONF_TYPE] add_lv_use(layout_type) lv_obj.set_layout(w.obj, literal(f"LV_LAYOUT_{layout_type.upper()}")) + if (pad_row := layout.get(CONF_PAD_ROW)) is not None: + w.set_style(CONF_PAD_ROW, pad_row, 0) + if (pad_column := layout.get(CONF_PAD_COLUMN)) is not None: + w.set_style(CONF_PAD_COLUMN, pad_column, 0) if layout_type == TYPE_GRID: wid = config[CONF_ID] rows = [str(x) for x in layout[CONF_GRID_ROWS]] @@ -316,8 +323,13 @@ async def set_obj_properties(w: Widget, config): flag_clr = set() flag_set = set() props = parts[CONF_MAIN][CONF_DEFAULT] + lambs = {} + flag_set = set() + flag_clr = set() for prop, value in {k: v for k, v in props.items() if k in OBJ_FLAGS}.items(): - if value: + if isinstance(value, cv.Lambda): + lambs[prop] = value + elif value: flag_set.add(prop) else: flag_clr.add(prop) @@ -327,6 +339,13 @@ async def set_obj_properties(w: Widget, config): if flag_clr: clrs = join_enums(flag_clr, "LV_OBJ_FLAG_") w.clear_flag(clrs) + for key, value in lambs.items(): + lamb = await cg.process_lambda(value, [], return_type=cg.bool_) + flag = f"LV_OBJ_FLAG_{key.upper()}" + with LvConditional(call_lambda(lamb)) as cond: + w.add_flag(flag) + cond.else_() + w.clear_flag(flag) if states := config.get(CONF_STATE): adds = set() @@ -348,7 +367,7 @@ async def set_obj_properties(w: Widget, config): for key, value in lambs.items(): lamb = await cg.process_lambda(value, [], return_type=cg.bool_) state = f"LV_STATE_{key.upper()}" - with LvConditional(f"{lamb}()") as cond: + with LvConditional(call_lambda(lamb)) as cond: w.add_state(state) cond.else_() w.clear_state(state) diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index 002c7a118d..7ef7772ac9 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -127,3 +127,11 @@ binary_sensor: - platform: lvgl name: LVGL checkbox widget: checkbox_id + +wifi: + ssid: SSID + password: PASSWORD123 + +time: + platform: sntp + id: time_id diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 54022354f5..800d6eff27 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -16,8 +16,6 @@ lvgl: border_width: 0 radius: 0 pad_all: 0 - pad_row: 0 - pad_column: 0 border_color: 0x0077b3 text_color: 0xFFFFFF width: 100% @@ -55,6 +53,13 @@ lvgl: pages: - id: page1 skip: true + layout: + type: flex + pad_row: 4 + pad_column: 4px + flex_align_main: center + flex_align_cross: start + flex_align_track: end widgets: - animimg: height: 60 @@ -118,10 +123,8 @@ lvgl: outline_width: 10px pad_all: 10px pad_bottom: 10px - pad_column: 10px pad_left: 10px pad_right: 10px - pad_row: 10px pad_top: 10px shadow_color: light_blue shadow_ofs_x: 5 @@ -221,10 +224,47 @@ lvgl: - label: text: Button on_click: - lvgl.label.update: - id: hello_label - bg_color: 0x123456 - text: clicked + - lvgl.label.update: + id: hello_label + bg_color: 0x123456 + text: clicked + - lvgl.label.update: + id: hello_label + text: !lambda return "hello world"; + - lvgl.label.update: + id: hello_label + text: !lambda |- + ESP_LOGD("label", "multi-line lambda"); + return "hello world"; + - lvgl.label.update: + id: hello_label + text: !lambda 'return str_sprintf("Hello space");' + - lvgl.label.update: + id: hello_label + text: + format: "sprintf format %s" + args: ['x ? "checked" : "unchecked"'] + - lvgl.label.update: + id: hello_label + text: + time_format: "%c" + - lvgl.label.update: + id: hello_label + text: + time_format: "%c" + time: time_id + - lvgl.label.update: + id: hello_label + text: + time_format: "%c" + time: !lambda return id(time_id).now(); + - lvgl.label.update: + id: hello_label + text: + time_format: "%c" + time: !lambda |- + ESP_LOGD("label", "multi-line lambda"); + return id(time_id).now(); on_value: logger.log: format: "state now %d" @@ -396,6 +436,8 @@ lvgl: grid_row_align: end grid_rows: [25px, fr(1), content] grid_columns: [40, fr(1), fr(1)] + pad_row: 6px + pad_column: 0 widgets: - image: grid_cell_row_pos: 0 From 0f82114e64f50a167c85374ec299c43fd2cd84ff Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 19 Aug 2024 00:45:10 +0200 Subject: [PATCH 07/13] [speaker] Fix header includes (#7304) --- esphome/components/speaker/speaker.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/speaker/speaker.h b/esphome/components/speaker/speaker.h index 142231881c..193049402d 100644 --- a/esphome/components/speaker/speaker.h +++ b/esphome/components/speaker/speaker.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + namespace esphome { namespace speaker { From c96784f59108b476597a6f09a83de358d18ef3b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:45:22 +1200 Subject: [PATCH 08/13] [microphone] Fix header includes (#7310) --- esphome/components/microphone/microphone.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/microphone/microphone.h b/esphome/components/microphone/microphone.h index e01a10e15c..883ca97710 100644 --- a/esphome/components/microphone/microphone.h +++ b/esphome/components/microphone/microphone.h @@ -1,6 +1,9 @@ #pragma once -#include "esphome/core/entity_base.h" +#include +#include +#include +#include #include "esphome/core/helpers.h" namespace esphome { From 409e84090eff4d3c8aa536e2077f036cd51cda54 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:09:59 +1200 Subject: [PATCH 09/13] Bump version to 2024.8.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 39d2ee74a1..a321ddd19f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.8.0b2" +__version__ = "2024.8.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From b425912a80aea1ed71ebf5e3515963f09f559832 Mon Sep 17 00:00:00 2001 From: Roving Ronin <108674933+Roving-Ronin@users.noreply.github.com> Date: Tue, 20 Aug 2024 07:18:06 +1000 Subject: [PATCH 10/13] Update const.py - Add missing UNIT_LITRE (#7317) --- esphome/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/const.py b/esphome/const.py index 6157ce32f7..b9c37a53a8 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1042,6 +1042,7 @@ UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR" UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh" UNIT_KILOWATT = "kW" UNIT_KILOWATT_HOURS = "kWh" +UNIT_LITRE = "L" UNIT_LUX = "lx" UNIT_METER = "m" UNIT_METER_PER_SECOND_SQUARED = "m/s²" From 1ffee9c4d2d89823346b48585a987e9ab7233a93 Mon Sep 17 00:00:00 2001 From: Ali Jafri Date: Tue, 20 Aug 2024 03:12:41 +0530 Subject: [PATCH 11/13] Fix RP2040 Neopixel flickering issue (#7307) --- .../rp2040_pio_led_strip/led_strip.cpp | 36 ++++++++++++++++--- .../rp2040_pio_led_strip/led_strip.h | 5 +++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/esphome/components/rp2040_pio_led_strip/led_strip.cpp b/esphome/components/rp2040_pio_led_strip/led_strip.cpp index 3e5e82898d..2aaa2ceb19 100644 --- a/esphome/components/rp2040_pio_led_strip/led_strip.cpp +++ b/esphome/components/rp2040_pio_led_strip/led_strip.cpp @@ -7,8 +7,10 @@ #include #include +#include #include #include +#include namespace esphome { namespace rp2040_pio_led_strip { @@ -23,6 +25,19 @@ static std::map conf_count_ = { {CHIPSET_WS2812, false}, {CHIPSET_WS2812B, false}, {CHIPSET_SK6812, false}, {CHIPSET_SM16703, false}, {CHIPSET_CUSTOM, false}, }; +static bool dma_chan_active_[12]; +static struct semaphore dma_write_complete_sem_[12]; + +// DMA interrupt service routine +void RP2040PIOLEDStripLightOutput::dma_write_complete_handler_() { + uint32_t channel = dma_hw->ints0; + for (uint dma_chan = 0; dma_chan < 12; ++dma_chan) { + if (RP2040PIOLEDStripLightOutput::dma_chan_active_[dma_chan] && (channel & (1u << dma_chan))) { + dma_hw->ints0 = (1u << dma_chan); // Clear the interrupt + sem_release(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[dma_chan]); // Handle the interrupt + } + } +} void RP2040PIOLEDStripLightOutput::setup() { ESP_LOGCONFIG(TAG, "Setting up RP2040 LED Strip..."); @@ -57,22 +72,22 @@ void RP2040PIOLEDStripLightOutput::setup() { // but there are only 4 state machines on each PIO so we can only have 4 strips per PIO uint offset = 0; - if (num_instance_[this->pio_ == pio0 ? 0 : 1] > 4) { + if (RP2040PIOLEDStripLightOutput::num_instance_[this->pio_ == pio0 ? 0 : 1] > 4) { ESP_LOGE(TAG, "Too many instances of PIO program"); this->mark_failed(); return; } // keep track of how many instances of the PIO program are running on each PIO - num_instance_[this->pio_ == pio0 ? 0 : 1]++; + RP2040PIOLEDStripLightOutput::num_instance_[this->pio_ == pio0 ? 0 : 1]++; // if there are multiple strips of the same chipset, we can reuse the same PIO program and save space if (this->conf_count_[this->chipset_]) { - offset = chipset_offsets_[this->chipset_]; + offset = RP2040PIOLEDStripLightOutput::chipset_offsets_[this->chipset_]; } else { // Load the assembled program into the PIO and get its location in the PIO's instruction memory and save it offset = pio_add_program(this->pio_, this->program_); - chipset_offsets_[this->chipset_] = offset; - conf_count_[this->chipset_] = true; + RP2040PIOLEDStripLightOutput::chipset_offsets_[this->chipset_] = offset; + RP2040PIOLEDStripLightOutput::conf_count_[this->chipset_] = true; } // Configure the state machine's PIO, and start it @@ -93,6 +108,9 @@ void RP2040PIOLEDStripLightOutput::setup() { return; } + // Mark the DMA channel as active + RP2040PIOLEDStripLightOutput::dma_chan_active_[this->dma_chan_] = true; + this->dma_config_ = dma_channel_get_default_config(this->dma_chan_); channel_config_set_transfer_data_size( &this->dma_config_, @@ -109,6 +127,13 @@ void RP2040PIOLEDStripLightOutput::setup() { false // don't start yet ); + // Initialize the semaphore for this DMA channel + sem_init(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[this->dma_chan_], 1, 1); + + irq_set_exclusive_handler(DMA_IRQ_0, dma_write_complete_handler_); // after DMA all data, raise an interrupt + dma_channel_set_irq0_enabled(this->dma_chan_, true); // map DMA channel to interrupt + irq_set_enabled(DMA_IRQ_0, true); // enable interrupt + this->init_(this->pio_, this->sm_, offset, this->pin_, this->max_refresh_rate_); } @@ -126,6 +151,7 @@ void RP2040PIOLEDStripLightOutput::write_state(light::LightState *state) { } // the bits are already in the correct order for the pio program so we can just copy the buffer using DMA + sem_acquire_blocking(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[this->dma_chan_]); dma_channel_transfer_from_buffer_now(this->dma_chan_, this->buf_, this->get_buffer_size_()); } diff --git a/esphome/components/rp2040_pio_led_strip/led_strip.h b/esphome/components/rp2040_pio_led_strip/led_strip.h index 9976842f02..7b62648974 100644 --- a/esphome/components/rp2040_pio_led_strip/led_strip.h +++ b/esphome/components/rp2040_pio_led_strip/led_strip.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace esphome { @@ -95,6 +96,8 @@ class RP2040PIOLEDStripLightOutput : public light::AddressableLight { size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); } + static void dma_write_complete_handler_(); + uint8_t *buf_{nullptr}; uint8_t *effect_data_{nullptr}; @@ -120,6 +123,8 @@ class RP2040PIOLEDStripLightOutput : public light::AddressableLight { inline static int num_instance_[2]; inline static std::map conf_count_; inline static std::map chipset_offsets_; + inline static bool dma_chan_active_[12]; + inline static struct semaphore dma_write_complete_sem_[12]; }; } // namespace rp2040_pio_led_strip From 30414667d023c18767852d12f3da7bc119c6ae1e Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Tue, 20 Aug 2024 00:22:19 +0200 Subject: [PATCH 12/13] add the ability to add more idf components to an existing setup (#7302) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 0a5dd46478..b630c7638e 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -172,6 +172,19 @@ def add_idf_component( KEY_COMPONENTS: components, KEY_SUBMODULES: submodules, } + else: + component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name] + if components is not None: + component_config[KEY_COMPONENTS] = list( + set(component_config[KEY_COMPONENTS] + components) + ) + if submodules is not None: + if component_config[KEY_SUBMODULES] is None: + component_config[KEY_SUBMODULES] = submodules + else: + component_config[KEY_SUBMODULES] = list( + set(component_config[KEY_SUBMODULES] + submodules) + ) def add_extra_script(stage: str, filename: str, path: str): From 3cbdf63f567621bf559f7cf82b05d078b56a8e28 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Tue, 20 Aug 2024 00:53:15 +0200 Subject: [PATCH 13/13] [code-quality] fix clang-tidy socket (#7285) --- esphome/components/socket/socket.cpp | 2 ++ esphome/components/socket/socket.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index 5d3528dad8..e260fce05e 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -1,4 +1,5 @@ #include "socket.h" +#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) #include #include #include @@ -74,3 +75,4 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po } } // namespace socket } // namespace esphome +#endif diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 5c12210d15..cefdb51e0d 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -5,6 +5,7 @@ #include "esphome/core/optional.h" #include "headers.h" +#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) namespace esphome { namespace socket { @@ -57,3 +58,4 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po } // namespace socket } // namespace esphome +#endif