From 1e35c07327ca5cfc8e02f3de6444d599ac878677 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 07:37:11 -1000 Subject: [PATCH 1/4] Bump aioesphomeapi from 37.0.1 to 37.0.2 (#9738) 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 4d94ce5557..6cc821e74c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==4.9.0 click==8.1.7 esphome-dashboard==20250514.0 -aioesphomeapi==37.0.1 +aioesphomeapi==37.0.2 zeroconf==0.147.0 puremagic==1.30 ruamel.yaml==0.18.14 # dashboard_import From 7d30d1e987c600e25331d27323b4b83fdf09cbc2 Mon Sep 17 00:00:00 2001 From: DT-art1 <81360462+DT-art1@users.noreply.github.com> Date: Sun, 20 Jul 2025 22:07:56 +0200 Subject: [PATCH 2/4] [const] Move CONF_FLIP_X and CONF_FLIP_Y to ``const.py`` (#9741) --- esphome/components/max7219digit/display.py | 2 +- esphome/components/ssd1306_base/__init__.py | 5 ++--- esphome/const.py | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py index f195078c1a..fef121ff10 100644 --- a/esphome/components/max7219digit/display.py +++ b/esphome/components/max7219digit/display.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import display, spi import esphome.config_validation as cv from esphome.const import ( + CONF_FLIP_X, CONF_ID, CONF_INTENSITY, CONF_LAMBDA, @@ -14,7 +15,6 @@ CODEOWNERS = ["@rspaargaren"] DEPENDENCIES = ["spi"] CONF_ROTATE_CHIP = "rotate_chip" -CONF_FLIP_X = "flip_x" CONF_SCROLL_SPEED = "scroll_speed" CONF_SCROLL_DWELL = "scroll_dwell" CONF_SCROLL_DELAY = "scroll_delay" diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index ab2c7a5496..6633b24607 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -6,6 +6,8 @@ from esphome.const import ( CONF_BRIGHTNESS, CONF_CONTRAST, CONF_EXTERNAL_VCC, + CONF_FLIP_X, + CONF_FLIP_Y, CONF_INVERT, CONF_LAMBDA, CONF_MODEL, @@ -18,9 +20,6 @@ ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base") SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer) SSD1306Model = ssd1306_base_ns.enum("SSD1306Model") -CONF_FLIP_X = "flip_x" -CONF_FLIP_Y = "flip_y" - MODELS = { "SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32, "SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64, diff --git a/esphome/const.py b/esphome/const.py index 39578a1fcf..7da19a8c1b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -375,6 +375,8 @@ CONF_FINGER_ID = "finger_id" CONF_FINGERPRINT_COUNT = "fingerprint_count" CONF_FLASH_LENGTH = "flash_length" CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length" +CONF_FLIP_X = "flip_x" +CONF_FLIP_Y = "flip_y" CONF_FLOW = "flow" CONF_FLOW_CONTROL_PIN = "flow_control_pin" CONF_FONT = "font" From 109eae26a7a62467979c313fbc4268fca78ca8aa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 12:09:11 -1000 Subject: [PATCH 3/4] [core] Refactor scheduler to eliminate hidden side effects in empty_() method --- esphome/core/scheduler.cpp | 27 +++++++++++++++++++-------- esphome/core/scheduler.h | 19 +++++++------------ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 7a0c08e1f0..4778ad61a1 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -219,10 +219,16 @@ bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) optional HOT Scheduler::next_schedule_in(uint32_t now) { // IMPORTANT: This method should only be called from the main thread (loop task). - // It calls empty_() and accesses items_[0] without holding a lock, which is only + // It performs cleanup and accesses items_[0] without holding a lock, which is only // safe when called from the main thread. Other threads must not call this method. - if (this->empty_()) + + // Cleanup removed items first + size_t item_count = this->cleanup_(); + + // If no items, return empty optional + if (item_count == 0) return {}; + auto &item = this->items_[0]; // Convert the fresh timestamp from caller (usually Application::loop()) to 64-bit const auto now_64 = this->millis_64_(now); // 'now' from parameter - fresh from caller @@ -281,7 +287,9 @@ void HOT Scheduler::call(uint32_t now) { ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now_64, this->millis_major_, this->last_millis_); #endif - while (!this->empty_()) { + // Cleanup before debug output + this->cleanup_(); + while (!this->items_.empty()) { std::unique_ptr item; { LockGuard guard{this->lock_}; @@ -332,7 +340,9 @@ void HOT Scheduler::call(uint32_t now) { this->to_remove_ = 0; } - while (!this->empty_()) { + // Cleanup removed items before processing + this->cleanup_(); + while (!this->items_.empty()) { // use scoping to indicate visibility of `item` variable { // Don't copy-by value yet @@ -398,8 +408,8 @@ void HOT Scheduler::process_to_add() { } this->to_add_.clear(); } -void HOT Scheduler::cleanup_() { - // Fast path: if nothing to remove, just return +size_t HOT Scheduler::cleanup_() { + // Fast path: if nothing to remove, just return the current size // Reading to_remove_ without lock is safe because: // 1. We only call this from the main thread during call() // 2. If it's 0, there's definitely nothing to cleanup @@ -407,7 +417,7 @@ void HOT Scheduler::cleanup_() { // 4. Not all platforms support atomics, so we accept this race in favor of performance // 5. The worst case is a one-loop-iteration delay in cleanup, which is harmless if (this->to_remove_ == 0) - return; + return this->items_.size(); // We must hold the lock for the entire cleanup operation because: // 1. We're modifying items_ (via pop_raw_) which requires exclusive access @@ -421,10 +431,11 @@ void HOT Scheduler::cleanup_() { while (!this->items_.empty()) { auto &item = this->items_[0]; if (!item->remove) - return; + break; this->to_remove_--; this->pop_raw_(); } + return this->items_.size(); } void HOT Scheduler::pop_raw_() { std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 64df2f2bb0..41e9bc9a44 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -57,6 +57,9 @@ class Scheduler { // Calculate when the next scheduled item should run // @param now Fresh timestamp from millis() - must not be stale/cached + // Returns the time in milliseconds until the next scheduled item, or nullopt if no items + // This method performs cleanup of removed items before checking the schedule + // IMPORTANT: This method should only be called from the main thread (loop task). optional next_schedule_in(uint32_t now); // Execute all scheduled items that are ready @@ -146,7 +149,10 @@ class Scheduler { uint32_t delay, std::function func); uint64_t millis_64_(uint32_t now); - void cleanup_(); + // Cleanup logically deleted items from the scheduler + // Returns the number of items remaining after cleanup + // IMPORTANT: This method should only be called from the main thread (loop task). + size_t cleanup_(); void pop_raw_(); private: @@ -190,17 +196,6 @@ class Scheduler { return item->remove || (item->component != nullptr && item->component->is_failed()); } - // Check if the scheduler has no items. - // IMPORTANT: This method should only be called from the main thread (loop task). - // It performs cleanup of removed items and checks if the queue is empty. - // The items_.empty() check at the end is done without a lock for performance, - // which is safe because this is only called from the main thread while other - // threads only add items (never remove them). - bool empty_() { - this->cleanup_(); - return this->items_.empty(); - } - Mutex lock_; std::vector> items_; std::vector> to_add_; From f6b989bd9aa3e3e8f89b5f29e53274b0be856ccd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 12:15:16 -1000 Subject: [PATCH 4/4] cleanup --- esphome/core/scheduler.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 4778ad61a1..05b83a1d0a 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -222,11 +222,8 @@ optional HOT Scheduler::next_schedule_in(uint32_t now) { // It performs cleanup and accesses items_[0] without holding a lock, which is only // safe when called from the main thread. Other threads must not call this method. - // Cleanup removed items first - size_t item_count = this->cleanup_(); - // If no items, return empty optional - if (item_count == 0) + if (this->cleanup_() == 0) return {}; auto &item = this->items_[0];