From fc1b49e87d2066c0f3d08e58c7c8f29b321dcdfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:42:33 +1200 Subject: [PATCH 1/4] Bump ruamel-yaml from 0.18.14 to 0.18.15 (#10310) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0675115c02..2665211381 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ esphome-dashboard==20250814.0 aioesphomeapi==39.0.0 zeroconf==0.147.0 puremagic==1.30 -ruamel.yaml==0.18.14 # dashboard_import +ruamel.yaml==0.18.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==10.4.0 cairosvg==2.8.2 From 56b6dd31f13fccf83f7bd04f819257019ffdc66b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Aug 2025 21:45:13 -0500 Subject: [PATCH 2/4] [core] Eliminate heap allocation in teardown_components by using StaticVector (#10256) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/core/application.cpp | 79 +++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index d2d47fe171..dc745a2a46 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -256,30 +256,79 @@ void Application::run_powerdown_hooks() { void Application::teardown_components(uint32_t timeout_ms) { uint32_t start_time = millis(); - // Copy all components in reverse order using reverse iterators + // Use a StaticVector instead of std::vector to avoid heap allocation + // since we know the actual size at compile time + StaticVector pending_components; + + // Copy all components in reverse order // Reverse order matches the behavior of run_safe_shutdown_hooks() above and ensures // components are torn down in the opposite order of their setup_priority (which is // used to sort components during Application::setup()) - std::vector pending_components(this->components_.rbegin(), this->components_.rend()); + size_t num_components = this->components_.size(); + for (size_t i = 0; i < num_components; ++i) { + pending_components[i] = this->components_[num_components - 1 - i]; + } uint32_t now = start_time; - while (!pending_components.empty() && (now - start_time) < timeout_ms) { + size_t pending_count = num_components; + + // Teardown Algorithm + // ================== + // We iterate through pending components, calling teardown() on each. + // Components that return false (need more time) are copied forward + // in the array. Components that return true (finished) are skipped. + // + // The compaction happens in-place during iteration: + // - still_pending tracks the write position (where to put next pending component) + // - i tracks the read position (which component we're testing) + // - When teardown() returns false, we copy component[i] to component[still_pending] + // - When teardown() returns true, we just skip it (don't increment still_pending) + // + // Example with 4 components where B can teardown immediately: + // + // Start: + // pending_components: [A, B, C, D] + // pending_count: 4 ^----------^ + // + // Iteration 1: + // i=0: A needs more time → keep at pos 0 (no copy needed) + // i=1: B finished → skip + // i=2: C needs more time → copy to pos 1 + // i=3: D needs more time → copy to pos 2 + // + // After iteration 1: + // pending_components: [A, C, D | D] + // pending_count: 3 ^--------^ + // + // Iteration 2: + // i=0: A finished → skip + // i=1: C needs more time → copy to pos 0 + // i=2: D finished → skip + // + // After iteration 2: + // pending_components: [C | C, D, D] (positions 1-3 have old values) + // pending_count: 1 ^--^ + + while (pending_count > 0 && (now - start_time) < timeout_ms) { // Feed watchdog during teardown to prevent triggering this->feed_wdt(now); - // Use iterator to safely erase elements - for (auto it = pending_components.begin(); it != pending_components.end();) { - if ((*it)->teardown()) { - // Component finished teardown, erase it - it = pending_components.erase(it); - } else { - // Component still needs time - ++it; + // Process components and compact the array, keeping only those still pending + size_t still_pending = 0; + for (size_t i = 0; i < pending_count; ++i) { + if (!pending_components[i]->teardown()) { + // Component still needs time, copy it forward + if (still_pending != i) { + pending_components[still_pending] = pending_components[i]; + } + ++still_pending; } + // Component finished teardown, skip it (don't increment still_pending) } + pending_count = still_pending; // Give some time for I/O operations if components are still pending - if (!pending_components.empty()) { + if (pending_count > 0) { this->yield_with_select_(1); } @@ -287,11 +336,11 @@ void Application::teardown_components(uint32_t timeout_ms) { now = millis(); } - if (!pending_components.empty()) { + if (pending_count > 0) { // Note: At this point, connections are either disconnected or in a bad state, // so this warning will only appear via serial rather than being transmitted to clients - for (auto *component : pending_components) { - ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms", component->get_component_source(), + for (size_t i = 0; i < pending_count; ++i) { + ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms", pending_components[i]->get_component_source(), timeout_ms); } } From 2cbf4f30f97e676613da0c1cba0a9f9236268ade Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Aug 2025 21:48:04 -0500 Subject: [PATCH 3/4] [libretiny] Optimize preferences is_changed() by replacing temporary vector with unique_ptr (#10272) --- esphome/components/libretiny/preferences.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index ce4ed915c0..fc535c99b4 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -5,7 +5,7 @@ #include "esphome/core/preferences.h" #include #include -#include +#include #include namespace esphome { @@ -139,21 +139,29 @@ class LibreTinyPreferences : public ESPPreferences { } bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) { - NVSData stored_data{}; struct fdb_kv kv; fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv); if (kvp == nullptr) { ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str()); return true; } - stored_data.data.resize(kv.value_len); - fdb_blob_make(&blob, stored_data.data.data(), kv.value_len); + + // Check size first - if different, data has changed + if (kv.value_len != to_save.data.size()) { + return true; + } + + // Allocate buffer on heap to avoid stack allocation for large data + auto stored_data = std::make_unique(kv.value_len); + fdb_blob_make(&blob, stored_data.get(), kv.value_len); size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob); if (actual_len != kv.value_len) { ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len); return true; } - return to_save.data != stored_data.data; + + // Compare the actual data + return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0; } bool reset() override { From 3ff5b4773b7783065e5bcb64059f47700d56f55f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Aug 2025 21:48:40 -0500 Subject: [PATCH 4/4] [bluetooth_proxy] Mark BluetoothConnection and BluetoothProxy as final for compiler optimizations (#10280) --- esphome/components/bluetooth_proxy/bluetooth_connection.h | 2 +- esphome/components/bluetooth_proxy/bluetooth_proxy.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index a975d25d91..e5d5ff2dd6 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy { class BluetoothProxy; -class BluetoothConnection : public esp32_ble_client::BLEClientBase { +class BluetoothConnection final : public esp32_ble_client::BLEClientBase { public: void dump_config() override; void loop() override; diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index bc8d3ed762..c81c8c9532 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t { SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0, }; -class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component { +class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component { friend class BluetoothConnection; // Allow connection to update connections_free_response_ public: BluetoothProxy();