From da4e710249c47b640b8bf8786ba9e8711cef2dd7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 May 2025 20:02:14 +1200 Subject: [PATCH 01/11] [core] Add some missing includes (#8864) --- esphome/core/helpers.h | 1 + esphome/core/string_ref.h | 1 + 2 files changed, 2 insertions(+) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 7866eaa9bd..4212aeca98 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include diff --git a/esphome/core/string_ref.h b/esphome/core/string_ref.h index f3dc3a38b0..c4320107e3 100644 --- a/esphome/core/string_ref.h +++ b/esphome/core/string_ref.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include From a5f85b4437fc8442a426b2d03d29a79b22eb5135 Mon Sep 17 00:00:00 2001 From: Cossid <83468485+Cossid@users.noreply.github.com> Date: Wed, 21 May 2025 20:26:19 -0500 Subject: [PATCH 02/11] [tuya_select] - Fix datapoint config error. (#8871) --- esphome/components/tuya/select/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/select/__init__.py b/esphome/components/tuya/select/__init__.py index e5b2e36ce7..9f2b6f1e12 100644 --- a/esphome/components/tuya/select/__init__.py +++ b/esphome/components/tuya/select/__init__.py @@ -54,8 +54,8 @@ async def to_code(config): cg.add(var.set_select_mappings(list(options_map.keys()))) parent = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(parent)) - if enum_datapoint := config.get(CONF_ENUM_DATAPOINT, None) is not None: + if (enum_datapoint := config.get(CONF_ENUM_DATAPOINT, None)) is not None: cg.add(var.set_select_id(enum_datapoint, False)) - if int_datapoint := config.get(CONF_INT_DATAPOINT, None) is not None: + if (int_datapoint := config.get(CONF_INT_DATAPOINT, None)) is not None: cg.add(var.set_select_id(int_datapoint, True)) cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) From 5c54f75b7a5520e50460f2fdffead74c6dc9976a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 May 2025 13:40:53 +1200 Subject: [PATCH 03/11] [online_image] Allocate pngle manually to potentially use psram (#8354) Co-authored-by: Keith Burzinski --- esphome/components/online_image/__init__.py | 2 +- esphome/components/online_image/png_image.cpp | 22 ++++++++++++++++++- esphome/components/online_image/png_image.h | 8 ++++--- platformio.ini | 2 +- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/esphome/components/online_image/__init__.py b/esphome/components/online_image/__init__.py index 6b69bc240b..55b9037176 100644 --- a/esphome/components/online_image/__init__.py +++ b/esphome/components/online_image/__init__.py @@ -75,7 +75,7 @@ class PNGFormat(Format): def actions(self): cg.add_define("USE_ONLINE_IMAGE_PNG_SUPPORT") - cg.add_library("pngle", "1.0.2") + cg.add_library("pngle", "1.1.0") IMAGE_FORMATS = { diff --git a/esphome/components/online_image/png_image.cpp b/esphome/components/online_image/png_image.cpp index fc5fb554bf..2038d09ed0 100644 --- a/esphome/components/online_image/png_image.cpp +++ b/esphome/components/online_image/png_image.cpp @@ -34,12 +34,32 @@ static void init_callback(pngle_t *pngle, uint32_t w, uint32_t h) { * @param h The height of the rectangle to draw. * @param rgba The color to paint the rectangle in. */ -static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t rgba[4]) { +static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, const uint8_t rgba[4]) { PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle); Color color(rgba[0], rgba[1], rgba[2], rgba[3]); decoder->draw(x, y, w, h, color); } +PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) { + { + pngle_t *pngle = this->allocator_.allocate(1, PNGLE_T_SIZE); + if (!pngle) { + ESP_LOGE(TAG, "Failed to allocate memory for PNGLE engine!"); + return; + } + memset(pngle, 0, PNGLE_T_SIZE); + pngle_reset(pngle); + this->pngle_ = pngle; + } +} + +PngDecoder::~PngDecoder() { + if (this->pngle_) { + pngle_reset(this->pngle_); + this->allocator_.deallocate(this->pngle_, PNGLE_T_SIZE); + } +} + int PngDecoder::prepare(size_t download_size) { ImageDecoder::prepare(download_size); if (!this->pngle_) { diff --git a/esphome/components/online_image/png_image.h b/esphome/components/online_image/png_image.h index 39f445c588..46519f8ef4 100644 --- a/esphome/components/online_image/png_image.h +++ b/esphome/components/online_image/png_image.h @@ -1,7 +1,8 @@ #pragma once -#include "image_decoder.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "image_decoder.h" #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT #include @@ -18,13 +19,14 @@ class PngDecoder : public ImageDecoder { * * @param display The image to decode the stream into. */ - PngDecoder(OnlineImage *image) : ImageDecoder(image), pngle_(pngle_new()) {} - ~PngDecoder() override { pngle_destroy(this->pngle_); } + PngDecoder(OnlineImage *image); + ~PngDecoder() override; int prepare(size_t download_size) override; int HOT decode(uint8_t *buffer, size_t size) override; protected: + RAMAllocator allocator_; pngle_t *pngle_; }; diff --git a/platformio.ini b/platformio.ini index 292188c6fa..23ed89c262 100644 --- a/platformio.ini +++ b/platformio.ini @@ -40,7 +40,7 @@ lib_deps = wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier - kikuchan98/pngle@1.0.2 ; online_image + kikuchan98/pngle@1.1.0 ; online_image ; Using the repository directly, otherwise ESP-IDF can't use the library https://github.com/bitbank2/JPEGDEC.git#ca1e0f2 ; online_image ; This is using the repository until a new release is published to PlatformIO From b1d5ad27f3e2f4abb1bfa6d0bf2d532131f480ff Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 22 May 2025 11:49:56 +1000 Subject: [PATCH 04/11] [lvgl] Improve error messages from text validation (#8872) --- esphome/components/lvgl/schemas.py | 54 +++++++++++++++++++----------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index d0dde01421..b51b6b8a85 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -36,29 +36,43 @@ from .types import ( # this will be populated later, in __init__.py to avoid circular imports. WIDGET_TYPES: dict = {} +TIME_TEXT_SCHEMA = cv.Schema( + { + cv.Required(CONF_TIME_FORMAT): cv.string, + cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)), + } +) + +PRINTF_TEXT_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_FORMAT): cv.string, + cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), + }, + ), + validate_printf, +) + + +def _validate_text(value): + """ + Do some sanity checking of the format to get better error messages + than using cv.Any + """ + if value is None: + raise cv.Invalid("No text specified") + if isinstance(value, dict): + if CONF_TIME_FORMAT in value: + return TIME_TEXT_SCHEMA(value) + return PRINTF_TEXT_SCHEMA(value) + + return cv.templatable(cv.string)(value) + + # A schema for text properties TEXT_SCHEMA = cv.Schema( { - cv.Optional(CONF_TEXT): cv.Any( - cv.All( - cv.Schema( - { - cv.Required(CONF_FORMAT): cv.string, - cv.Optional(CONF_ARGS, default=list): cv.ensure_list( - cv.lambda_ - ), - }, - ), - validate_printf, - ), - cv.Schema( - { - cv.Required(CONF_TIME_FORMAT): cv.string, - cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)), - } - ), - cv.templatable(cv.string), - ) + cv.Optional(CONF_TEXT): _validate_text, } ) From 90e3c5bba23a073c88c5353cd4a12105b7ab182b Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 22 May 2025 14:19:16 -0500 Subject: [PATCH 05/11] [micro_wake_word] avoid duplicated detections from same event (#8877) --- esphome/components/micro_wake_word/streaming_model.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/micro_wake_word/streaming_model.cpp b/esphome/components/micro_wake_word/streaming_model.cpp index ce3d8c2e4c..38b88557e6 100644 --- a/esphome/components/micro_wake_word/streaming_model.cpp +++ b/esphome/components/micro_wake_word/streaming_model.cpp @@ -147,7 +147,11 @@ bool StreamingModel::perform_streaming_inference(const int8_t features[PREPROCES this->recent_streaming_probabilities_[this->last_n_index_] = output->data.uint8[0]; // probability; this->unprocessed_probability_status_ = true; } - this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0); + if (this->recent_streaming_probabilities_[this->last_n_index_] < this->probability_cutoff_) { + // Only increment ignore windows if less than the probability cutoff; this forces the model to "cool-off" from a + // previous detection and calling ``reset_probabilities`` so it avoids duplicate detections + this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0); + } } return true; } From e4f3a952d512c2a4142f015ffe3c18f7de7f13f8 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 22 May 2025 14:20:15 -0500 Subject: [PATCH 06/11] [speaker] ensure the pipeline returns an error state before returning its stopped (#8878) --- .../speaker/media_player/audio_pipeline.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index 60f562cc2c..ac122b6e0c 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -174,6 +174,16 @@ AudioPipelineState AudioPipeline::process_state() { } } + if ((event_bits & EventGroupBits::READER_MESSAGE_ERROR)) { + xEventGroupClearBits(this->event_group_, EventGroupBits::READER_MESSAGE_ERROR); + return AudioPipelineState::ERROR_READING; + } + + if ((event_bits & EventGroupBits::DECODER_MESSAGE_ERROR)) { + xEventGroupClearBits(this->event_group_, EventGroupBits::DECODER_MESSAGE_ERROR); + return AudioPipelineState::ERROR_DECODING; + } + if ((event_bits & EventGroupBits::READER_MESSAGE_FINISHED) && (!(event_bits & EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE) && (event_bits & EventGroupBits::DECODER_MESSAGE_FINISHED))) { @@ -203,16 +213,6 @@ AudioPipelineState AudioPipeline::process_state() { return AudioPipelineState::STOPPED; } - if ((event_bits & EventGroupBits::READER_MESSAGE_ERROR)) { - xEventGroupClearBits(this->event_group_, EventGroupBits::READER_MESSAGE_ERROR); - return AudioPipelineState::ERROR_READING; - } - - if ((event_bits & EventGroupBits::DECODER_MESSAGE_ERROR)) { - xEventGroupClearBits(this->event_group_, EventGroupBits::DECODER_MESSAGE_ERROR); - return AudioPipelineState::ERROR_DECODING; - } - if (this->pause_state_) { return AudioPipelineState::PAUSED; } From ad20825f31c860351add683982fdfa1966aa8378 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 23 May 2025 11:33:38 +1200 Subject: [PATCH 07/11] [logger] Fix options in select (#8875) Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/logger/__init__.py | 2 ++ esphome/components/logger/select/__init__.py | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 4698c1d9f1..4136480629 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -24,6 +24,7 @@ from esphome.const import ( CONF_HARDWARE_UART, CONF_ID, CONF_LEVEL, + CONF_LOGGER, CONF_LOGS, CONF_ON_MESSAGE, CONF_TAG, @@ -247,6 +248,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): baud_rate = config[CONF_BAUD_RATE] level = config[CONF_LEVEL] + CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level initial_level = LOG_LEVELS[config.get(CONF_INITIAL_LEVEL, level)] log = cg.new_Pvariable( config[CONF_ID], diff --git a/esphome/components/logger/select/__init__.py b/esphome/components/logger/select/__init__.py index b1fc881537..2e83599eb4 100644 --- a/esphome/components/logger/select/__init__.py +++ b/esphome/components/logger/select/__init__.py @@ -5,7 +5,7 @@ from esphome.const import CONF_LEVEL, CONF_LOGGER, ENTITY_CATEGORY_CONFIG, ICON_ from esphome.core import CORE from esphome.cpp_helpers import register_component, register_parented -from .. import CONF_LOGGER_ID, LOG_LEVEL_SEVERITY, Logger, logger_ns +from .. import CONF_LOGGER_ID, LOG_LEVELS, Logger, logger_ns CODEOWNERS = ["@clydebarrow"] @@ -21,9 +21,10 @@ CONFIG_SCHEMA = select.select_schema( async def to_code(config): - levels = LOG_LEVEL_SEVERITY - index = levels.index(CORE.config[CONF_LOGGER][CONF_LEVEL]) + parent = await cg.get_variable(config[CONF_LOGGER_ID]) + levels = list(LOG_LEVELS) + index = levels.index(CORE.data[CONF_LOGGER][CONF_LEVEL]) levels = levels[: index + 1] var = await select.new_select(config, options=levels) - await register_parented(var, config[CONF_LOGGER_ID]) + await register_parented(var, parent) await register_component(var, config) From e0e4ba9592db3eb810a300390b569f00e872db45 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sat, 24 May 2025 09:15:24 -0500 Subject: [PATCH 08/11] [esp32] Fix building on IDF 4 (#8892) --- esphome/components/esp32/core.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index c90d68d00e..562bcba3c2 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -15,8 +15,9 @@ #ifdef USE_ARDUINO #include #else +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) #include - +#endif void setup(); void loop(); #endif @@ -63,7 +64,13 @@ uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); } uint32_t arch_get_cpu_freq_hz() { uint32_t freq = 0; #ifdef USE_ESP_IDF +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq); +#else + rtc_cpu_freq_config_t config; + rtc_clk_cpu_freq_get_config(&config); + freq = config.freq_mhz * 1000000U; +#endif #elif defined(USE_ARDUINO) freq = ESP.getCpuFreqMHz() * 1000000; #endif From 6c08f5e343c658a7d8118b959f1a00718857ebc7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 27 May 2025 06:46:51 +1200 Subject: [PATCH 09/11] [api] Fix crash with gcc compiler on host (#8902) --- esphome/components/api/api_connection.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 27dd44ae86..b4646a2d7d 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -4,11 +4,11 @@ #include #include #include "esphome/components/network/util.h" +#include "esphome/core/application.h" #include "esphome/core/entity_base.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/version.h" -#include "esphome/core/application.h" #ifdef USE_DEEP_SLEEP #include "esphome/components/deep_sleep/deep_sleep_component.h" @@ -153,7 +153,11 @@ void APIConnection::loop() { } else { this->last_traffic_ = App.get_loop_component_start_time(); // read a packet - this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); + if (buffer.data_len > 0) { + this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); + } else { + this->read_message(0, buffer.type, nullptr); + } if (this->remove_) return; } From fdc6c4a21948c991e1f8a8aa720d9a200200cd45 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 27 May 2025 09:08:16 +1200 Subject: [PATCH 10/11] [web_server] Fix download list where external_components has a substitution value (#8911) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/dashboard/web_server.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 6196e01760..ddef2e7e2b 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -601,10 +601,12 @@ class DownloadListRequestHandler(BaseHandler): loop = asyncio.get_running_loop() try: downloads_json = await loop.run_in_executor(None, self._get, configuration) - except vol.Invalid: + except vol.Invalid as exc: + _LOGGER.exception("Error while fetching downloads", exc_info=exc) self.send_error(404) return if downloads_json is None: + _LOGGER.error("Configuration %s not found", configuration) self.send_error(404) return self.set_status(200) @@ -618,14 +620,17 @@ class DownloadListRequestHandler(BaseHandler): if storage_json is None: return None - config = yaml_util.load_yaml(settings.rel_path(configuration)) + try: + config = yaml_util.load_yaml(settings.rel_path(configuration)) - if const.CONF_EXTERNAL_COMPONENTS in config: - from esphome.components.external_components import ( - do_external_components_pass, - ) + if const.CONF_EXTERNAL_COMPONENTS in config: + from esphome.components.external_components import ( + do_external_components_pass, + ) - do_external_components_pass(config) + do_external_components_pass(config) + except vol.Invalid: + _LOGGER.info("Could not parse `external_components`, skipping") from esphome.components.esp32 import VARIANTS as ESP32_VARIANTS From 42390faf4a064a687e6227b64a15a9f957861840 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 27 May 2025 14:31:38 +1200 Subject: [PATCH 11/11] Bump version to 2025.5.1 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index d22d87bf8e..e5c3c1802d 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.5.0 +PROJECT_NUMBER = 2025.5.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 7a61c93428..2fc30beaaa 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2025.5.0" +__version__ = "2025.5.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (