From 2c0558fe238cebaaa4734a18e8f9cba2bad661fd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Jun 2025 11:25:30 -0500 Subject: [PATCH 1/4] Update test_scheduler_string_test.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/integration/test_scheduler_string_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_scheduler_string_test.py b/tests/integration/test_scheduler_string_test.py index 2953278367..c94a291497 100644 --- a/tests/integration/test_scheduler_string_test.py +++ b/tests/integration/test_scheduler_string_test.py @@ -111,7 +111,7 @@ async def test_scheduler_string_test( try: await asyncio.wait_for(static_interval_fired.wait(), timeout=1.0) except asyncio.TimeoutError: - pytest.fail("Static interval did not fire within 1 seconds") + pytest.fail("Static interval did not fire within 1 second") try: await asyncio.wait_for(static_interval_cancelled.wait(), timeout=2.0) From 25ebddfa1cd6953f79eddd7010c27ac8fdc000c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Jun 2025 11:25:36 -0500 Subject: [PATCH 2/4] Update test_scheduler_string_test.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/integration/test_scheduler_string_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_scheduler_string_test.py b/tests/integration/test_scheduler_string_test.py index c94a291497..301fd1eae3 100644 --- a/tests/integration/test_scheduler_string_test.py +++ b/tests/integration/test_scheduler_string_test.py @@ -127,7 +127,7 @@ async def test_scheduler_string_test( try: await asyncio.wait_for(dynamic_timeout_fired.wait(), timeout=1.0) except asyncio.TimeoutError: - pytest.fail("Dynamic timeout did not fire within 1 seconds") + pytest.fail("Dynamic timeout did not fire within 1 second") try: await asyncio.wait_for(dynamic_interval_fired.wait(), timeout=1.5) From 5718c0f5b87e4b305c317ee20e0a7ebe473da11b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Jun 2025 11:25:42 -0500 Subject: [PATCH 3/4] Update test_scheduler_string_test.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/integration/test_scheduler_string_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_scheduler_string_test.py b/tests/integration/test_scheduler_string_test.py index 301fd1eae3..670af6e22d 100644 --- a/tests/integration/test_scheduler_string_test.py +++ b/tests/integration/test_scheduler_string_test.py @@ -138,7 +138,7 @@ async def test_scheduler_string_test( try: await asyncio.wait_for(cancel_test_done.wait(), timeout=1.0) except asyncio.TimeoutError: - pytest.fail("Cancel test did not complete within 1 seconds") + pytest.fail("Cancel test did not complete within 1 second") # Wait for final results try: From 7100c22dc4cdc68ee7ab54e29f90d00be01096ef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Jun 2025 15:47:10 -0500 Subject: [PATCH 4/4] address copilot comments --- esphome/core/component.cpp | 8 ++++++++ esphome/core/component.h | 2 ++ esphome/core/scheduler.cpp | 34 +++++++++++++++++++++++++++++++--- esphome/core/scheduler.h | 7 +++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index a1645219b1..a415b78cff 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -68,6 +68,10 @@ bool Component::cancel_interval(const std::string &name) { // NOLINT return App.scheduler.cancel_interval(this, name); } +bool Component::cancel_interval(const char *name) { // NOLINT + return App.scheduler.cancel_interval(this, name); +} + void Component::set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, float backoff_increase_factor) { // NOLINT App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); @@ -89,6 +93,10 @@ bool Component::cancel_timeout(const std::string &name) { // NOLINT return App.scheduler.cancel_timeout(this, name); } +bool Component::cancel_timeout(const char *name) { // NOLINT + return App.scheduler.cancel_timeout(this, name); +} + void Component::call_loop() { this->loop(); } void Component::call_setup() { this->setup(); } void Component::call_dump_config() { diff --git a/esphome/core/component.h b/esphome/core/component.h index 900db27e29..5b37deeb68 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -284,6 +284,7 @@ class Component { * @return Whether an interval functions was deleted. */ bool cancel_interval(const std::string &name); // NOLINT + bool cancel_interval(const char *name); // NOLINT /** Set an retry function with a unique name. Empty name means no cancelling possible. * @@ -368,6 +369,7 @@ class Component { * @return Whether a timeout functions was deleted. */ bool cancel_timeout(const std::string &name); // NOLINT + bool cancel_timeout(const char *name); // NOLINT /** Defer a callback to the next loop() call. * diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 67fb87f58d..5c01b4f3f4 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -7,6 +7,7 @@ #include "esphome/core/log.h" #include #include +#include namespace esphome { @@ -124,6 +125,9 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name) { return this->cancel_item_(component, name, SchedulerItem::TIMEOUT); } +bool HOT Scheduler::cancel_timeout(Component *component, const char *name) { + return this->cancel_item_(component, name, SchedulerItem::TIMEOUT); +} void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, std::function func) { this->set_timer_common_(component, SchedulerItem::INTERVAL, false, &name, interval, std::move(func)); @@ -136,6 +140,9 @@ void HOT Scheduler::set_interval(Component *component, const char *name, uint32_ bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) { return this->cancel_item_(component, name, SchedulerItem::INTERVAL); } +bool HOT Scheduler::cancel_interval(Component *component, const char *name) { + return this->cancel_item_(component, name, SchedulerItem::INTERVAL); +} struct RetryArgs { std::function func; @@ -357,13 +364,25 @@ void HOT Scheduler::push_(std::unique_ptr item) { LockGuard guard{this->lock_}; this->to_add_.push_back(std::move(item)); } -bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) { +// Common implementation for cancel operations +bool HOT Scheduler::cancel_item_common_(Component *component, bool is_static_string, const void *name_ptr, + SchedulerItem::Type type) { + // Get the name as const char* + const char *name_cstr = + is_static_string ? static_cast(name_ptr) : static_cast(name_ptr)->c_str(); + + // Handle null or empty names + if (name_cstr == nullptr) + return false; + // obtain lock because this function iterates and can be called from non-loop task context LockGuard guard{this->lock_}; bool ret = false; + for (auto &it : this->items_) { const char *item_name = it->get_name(); - if (it->component == component && item_name != nullptr && name == item_name && it->type == type && !it->remove) { + if (it->component == component && item_name != nullptr && strcmp(name_cstr, item_name) == 0 && it->type == type && + !it->remove) { to_remove_++; it->remove = true; ret = true; @@ -371,7 +390,7 @@ bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, } for (auto &it : this->to_add_) { const char *item_name = it->get_name(); - if (it->component == component && item_name != nullptr && name == item_name && it->type == type) { + if (it->component == component && item_name != nullptr && strcmp(name_cstr, item_name) == 0 && it->type == type) { it->remove = true; ret = true; } @@ -379,6 +398,15 @@ bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, return ret; } + +bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) { + return this->cancel_item_common_(component, false, &name, type); +} + +bool HOT Scheduler::cancel_item_(Component *component, const char *name, SchedulerItem::Type type) { + return this->cancel_item_common_(component, true, name, type); +} + uint64_t Scheduler::millis_() { // Get the current 32-bit millis value const uint32_t now = millis(); diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 84a460292d..a64968932e 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -28,6 +28,7 @@ class Scheduler { void set_timeout(Component *component, const char *name, uint32_t timeout, std::function func); bool cancel_timeout(Component *component, const std::string &name); + bool cancel_timeout(Component *component, const char *name); void set_interval(Component *component, const std::string &name, uint32_t interval, std::function func); @@ -44,6 +45,7 @@ class Scheduler { void set_interval(Component *component, const char *name, uint32_t interval, std::function func); bool cancel_interval(Component *component, const std::string &name); + bool cancel_interval(Component *component, const char *name); void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, std::function func, float backoff_increase_factor = 1.0f); bool cancel_retry(Component *component, const std::string &name); @@ -137,7 +139,12 @@ class Scheduler { void cleanup_(); void pop_raw_(); void push_(std::unique_ptr item); + // Common implementation for cancel operations + bool cancel_item_common_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type); + bool cancel_item_(Component *component, const std::string &name, SchedulerItem::Type type); + bool cancel_item_(Component *component, const char *name, SchedulerItem::Type type); + bool empty_() { this->cleanup_(); return this->items_.empty();