From 36eab00eac174bb04a6940c39ce7ff4e5afbb327 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Aug 2025 12:14:17 -1000 Subject: [PATCH 1/4] preen --- esphome/core/application.cpp | 54 ++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 05fa85c112..85200d8d6c 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -34,6 +34,38 @@ namespace esphome { static const char *const TAG = "app"; +// Helper function for insertion sort of components by setup priority +// Using insertion sort instead of std::stable_sort saves ~1.3KB of flash +// by avoiding template instantiations (std::rotate, std::stable_sort, lambdas) +static void insertion_sort_by_setup_priority(Component **components, size_t size) { + for (size_t i = 1; i < size; i++) { + Component *key = components[i]; + float key_priority = key->get_actual_setup_priority(); + int32_t j = i - 1; + + while (j >= 0 && components[j]->get_actual_setup_priority() < key_priority) { + components[j + 1] = components[j]; + j--; + } + components[j + 1] = key; + } +} + +// Helper function for insertion sort of components by loop priority +static void insertion_sort_by_loop_priority(Component **components, size_t size) { + for (size_t i = 1; i < size; i++) { + Component *key = components[i]; + float key_priority = key->get_loop_priority(); + int32_t j = i - 1; + + while (j >= 0 && components[j]->get_loop_priority() < key_priority) { + components[j + 1] = components[j]; + j--; + } + components[j + 1] = key; + } +} + void Application::register_component_(Component *comp) { if (comp == nullptr) { ESP_LOGW(TAG, "Tried to register null component!"); @@ -51,9 +83,9 @@ void Application::register_component_(Component *comp) { void Application::setup() { ESP_LOGI(TAG, "Running through setup()"); ESP_LOGV(TAG, "Sorting components by setup priority"); - std::stable_sort(this->components_.begin(), this->components_.end(), [](const Component *a, const Component *b) { - return a->get_actual_setup_priority() > b->get_actual_setup_priority(); - }); + + // Sort by setup priority using our helper function + insertion_sort_by_setup_priority(this->components_.data(), this->components_.size()); // Initialize looping_components_ early so enable_pending_loops_() works during setup this->calculate_looping_components_(); @@ -69,20 +101,8 @@ void Application::setup() { if (component->can_proceed()) continue; - // Using insertion sort instead of std::stable_sort saves ~1.3KB of flash - // by avoiding std::rotate, std::stable_sort, and lambda template instantiations. - // Insertion sort is efficient for small arrays and maintains stability - for (int32_t j = 1; j <= static_cast(i); j++) { - Component *key = this->components_[j]; - float key_priority = key->get_loop_priority(); - int32_t k = j - 1; - - while (k >= 0 && this->components_[k]->get_loop_priority() < key_priority) { - this->components_[k + 1] = this->components_[k]; - k--; - } - this->components_[k + 1] = key; - } + // Sort components 0 through i by loop priority + insertion_sort_by_loop_priority(this->components_.data(), i + 1); do { uint8_t new_app_state = STATUS_LED_WARNING; From fd442cc4856574b6b3d2ef00b72abfea81bb11e9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 08:21:54 +1000 Subject: [PATCH 2/4] [syslog] Fix RFC3164 timestamp compliance for single-digit days (#10034) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/syslog/esphome_syslog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index e322a6951d..71468fa932 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -35,7 +35,7 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level]; } int pri = this->facility_ * 8 + severity; - auto timestamp = this->time_->now().strftime("%b %d %H:%M:%S"); + auto timestamp = this->time_->now().strftime("%b %e %H:%M:%S"); size_t len = message_len; // remove color formatting if (this->strip_ && message[0] == 0x1B && len > 11) { From 40dcee594bdc39af5ee48ede24ecc51ca7c1f324 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Aug 2025 12:27:54 -1000 Subject: [PATCH 3/4] preen --- esphome/core/application.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 85200d8d6c..e5103802db 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -37,12 +37,15 @@ static const char *const TAG = "app"; // Helper function for insertion sort of components by setup priority // Using insertion sort instead of std::stable_sort saves ~1.3KB of flash // by avoiding template instantiations (std::rotate, std::stable_sort, lambdas) +// IMPORTANT: This sort is stable (preserves relative order of equal elements), +// which is necessary to maintain user-defined component order for same priority static void insertion_sort_by_setup_priority(Component **components, size_t size) { for (size_t i = 1; i < size; i++) { Component *key = components[i]; float key_priority = key->get_actual_setup_priority(); int32_t j = i - 1; + // Using '<' (not '<=') ensures stability - equal priority components keep their order while (j >= 0 && components[j]->get_actual_setup_priority() < key_priority) { components[j + 1] = components[j]; j--; @@ -52,12 +55,15 @@ static void insertion_sort_by_setup_priority(Component **components, size_t size } // Helper function for insertion sort of components by loop priority +// IMPORTANT: This sort is stable (preserves relative order of equal elements), +// which is required when components are re-sorted during setup() if they block static void insertion_sort_by_loop_priority(Component **components, size_t size) { for (size_t i = 1; i < size; i++) { Component *key = components[i]; float key_priority = key->get_loop_priority(); int32_t j = i - 1; + // Using '<' (not '<=') ensures stability - equal priority components keep their order while (j >= 0 && components[j]->get_loop_priority() < key_priority) { components[j + 1] = components[j]; j--; From 9c76847acacd9a550aea36f3ad4bf01d6741d153 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Aug 2025 12:45:44 -1000 Subject: [PATCH 4/4] [wifi] Replace std::stable_sort with insertion sort to save 2.4KB flash --- esphome/components/wifi/wifi_component.cpp | 74 +++++++++++++++------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 98f75894f4..e7ca3629a5 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -505,6 +505,54 @@ void WiFiComponent::start_scanning() { this->state_ = WIFI_COMPONENT_STATE_STA_SCANNING; } +// Helper function for WiFi scan result comparison +// Returns true if 'a' should be placed before 'b' in the sorted order +static bool wifi_scan_result_is_better(const WiFiScanResult &a, const WiFiScanResult &b) { + // Matching networks always come before non-matching + if (a.get_matches() && !b.get_matches()) + return true; + if (!a.get_matches() && b.get_matches()) + return false; + + if (a.get_matches() && b.get_matches()) { + // For APs with the same SSID, always prefer stronger signal + // This helps with mesh networks and multiple APs + if (a.get_ssid() == b.get_ssid()) { + return a.get_rssi() > b.get_rssi(); + } + + // For different SSIDs, check priority first + if (a.get_priority() != b.get_priority()) + return a.get_priority() > b.get_priority(); + // If priorities are equal, prefer stronger signal + return a.get_rssi() > b.get_rssi(); + } + + // Both don't match - sort by signal strength + return a.get_rssi() > b.get_rssi(); +} + +// Helper function for insertion sort of WiFi scan results +// Using insertion sort instead of std::stable_sort saves flash memory +// by avoiding template instantiations (std::rotate, std::stable_sort, lambdas) +// IMPORTANT: This sort is stable (preserves relative order of equal elements) +static void insertion_sort_scan_results(std::vector &results) { + const size_t size = results.size(); + for (size_t i = 1; i < size; i++) { + // Make a copy to avoid issues with move semantics during comparison + WiFiScanResult key = results[i]; + int32_t j = i - 1; + + // Move elements that are worse than key to the right + // For stability, we only move if key is strictly better than results[j] + while (j >= 0 && wifi_scan_result_is_better(key, results[j])) { + results[j + 1] = results[j]; + j--; + } + results[j + 1] = key; + } +} + void WiFiComponent::check_scanning_finished() { if (!this->scan_done_) { if (millis() - this->action_started_ > 30000) { @@ -535,30 +583,8 @@ void WiFiComponent::check_scanning_finished() { } } - std::stable_sort(this->scan_result_.begin(), this->scan_result_.end(), - [](const WiFiScanResult &a, const WiFiScanResult &b) { - // return true if a is better than b - if (a.get_matches() && !b.get_matches()) - return true; - if (!a.get_matches() && b.get_matches()) - return false; - - if (a.get_matches() && b.get_matches()) { - // For APs with the same SSID, always prefer stronger signal - // This helps with mesh networks and multiple APs - if (a.get_ssid() == b.get_ssid()) { - return a.get_rssi() > b.get_rssi(); - } - - // For different SSIDs, check priority first - if (a.get_priority() != b.get_priority()) - return a.get_priority() > b.get_priority(); - // If priorities are equal, prefer stronger signal - return a.get_rssi() > b.get_rssi(); - } - - return a.get_rssi() > b.get_rssi(); - }); + // Sort scan results using insertion sort for better memory efficiency + insertion_sort_scan_results(this->scan_result_); for (auto &res : this->scan_result_) { char bssid_s[18];