mirror of
https://github.com/esphome/esphome.git
synced 2025-09-02 15:30:26 +00:00
Compare commits
1 Commits
scheduler_
...
get_device
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4da18133f4 |
@@ -15,8 +15,8 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi
|
||||
|
||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||
|
||||
if (!obj->get_device_class().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
|
||||
if (!obj->get_device_class_ref().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,8 +19,8 @@ const extern float COVER_CLOSED;
|
||||
if (traits_.get_is_assumed_state()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
||||
} \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
if (!(obj)->get_device_class_ref().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
@@ -16,8 +16,8 @@ namespace event {
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
if (!(obj)->get_device_class_ref().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
@@ -22,8 +22,8 @@ void log_number(const char *tag, const char *prefix, const char *type, Number *o
|
||||
ESP_LOGCONFIG(tag, "%s Unit of Measurement: '%s'", prefix, obj->traits.get_unit_of_measurement().c_str());
|
||||
}
|
||||
|
||||
if (!obj->traits.get_device_class().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->traits.get_device_class().c_str());
|
||||
if (!obj->traits.get_device_class_ref().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->traits.get_device_class_ref().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,8 +20,8 @@ void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *o
|
||||
prefix, type, obj->get_name().c_str(), prefix, state_class_to_string(obj->get_state_class()).c_str(),
|
||||
prefix, obj->get_unit_of_measurement().c_str(), prefix, obj->get_accuracy_decimals());
|
||||
|
||||
if (!obj->get_device_class().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
|
||||
if (!obj->get_device_class_ref().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
||||
}
|
||||
|
||||
if (!obj->get_icon().empty()) {
|
||||
|
@@ -100,8 +100,8 @@ void log_switch(const char *tag, const char *prefix, const char *type, Switch *o
|
||||
if (obj->is_inverted()) {
|
||||
ESP_LOGCONFIG(tag, "%s Inverted: YES", prefix);
|
||||
}
|
||||
if (!obj->get_device_class().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
|
||||
if (!obj->get_device_class_ref().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@ namespace text_sensor {
|
||||
#define LOG_TEXT_SENSOR(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
if (!(obj)->get_device_class_ref().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
|
||||
} \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
|
@@ -19,8 +19,8 @@ const extern float VALVE_CLOSED;
|
||||
if (traits_.get_is_assumed_state()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
||||
} \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
if (!(obj)->get_device_class_ref().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
@@ -79,22 +79,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
return;
|
||||
}
|
||||
|
||||
// Get fresh timestamp BEFORE taking lock - millis_64_ may need to acquire lock itself
|
||||
const uint64_t now = this->millis_64_(millis());
|
||||
|
||||
// Take lock early to protect scheduler_item_pool_ access
|
||||
LockGuard guard{this->lock_};
|
||||
|
||||
// Create and populate the scheduler item
|
||||
std::unique_ptr<SchedulerItem> item;
|
||||
if (!this->scheduler_item_pool_.empty()) {
|
||||
// Reuse from pool
|
||||
item = std::move(this->scheduler_item_pool_.back());
|
||||
this->scheduler_item_pool_.pop_back();
|
||||
} else {
|
||||
// Allocate new if pool is empty
|
||||
item = make_unique<SchedulerItem>();
|
||||
}
|
||||
auto item = make_unique<SchedulerItem>();
|
||||
item->component = component;
|
||||
item->set_name(name_cstr, !is_static_string);
|
||||
item->type = type;
|
||||
@@ -113,6 +99,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
// Single-core platforms don't need thread-safe defer handling
|
||||
if (delay == 0 && type == SchedulerItem::TIMEOUT) {
|
||||
// Put in defer queue for guaranteed FIFO execution
|
||||
LockGuard guard{this->lock_};
|
||||
if (!skip_cancel) {
|
||||
this->cancel_item_locked_(component, name_cstr, type);
|
||||
}
|
||||
@@ -121,6 +108,9 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
}
|
||||
#endif /* not ESPHOME_THREAD_SINGLE */
|
||||
|
||||
// Get fresh timestamp for new timer/interval - ensures accurate scheduling
|
||||
const auto now = this->millis_64_(millis()); // Fresh millis() call
|
||||
|
||||
// Type-specific setup
|
||||
if (type == SchedulerItem::INTERVAL) {
|
||||
item->interval = delay;
|
||||
@@ -152,6 +142,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
}
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
|
||||
LockGuard guard{this->lock_};
|
||||
|
||||
// For retries, check if there's a cancelled timeout first
|
||||
if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT &&
|
||||
(has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) ||
|
||||
@@ -343,11 +335,11 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||
const auto last_dbg = this->last_millis_.load(std::memory_order_relaxed);
|
||||
const auto major_dbg = this->millis_major_.load(std::memory_order_relaxed);
|
||||
ESP_LOGD(TAG, "Items: count=%zu, pool=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(),
|
||||
this->scheduler_item_pool_.size(), now_64, major_dbg, last_dbg);
|
||||
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(), now_64,
|
||||
major_dbg, last_dbg);
|
||||
#else /* not ESPHOME_THREAD_MULTI_ATOMICS */
|
||||
ESP_LOGD(TAG, "Items: count=%zu, pool=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(),
|
||||
this->scheduler_item_pool_.size(), now_64, this->millis_major_, this->last_millis_);
|
||||
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(), now_64,
|
||||
this->millis_major_, this->last_millis_);
|
||||
#endif /* else ESPHOME_THREAD_MULTI_ATOMICS */
|
||||
// Cleanup before debug output
|
||||
this->cleanup_();
|
||||
@@ -388,13 +380,10 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
|
||||
std::vector<std::unique_ptr<SchedulerItem>> valid_items;
|
||||
|
||||
// Move all non-removed items to valid_items, recycle removed ones
|
||||
// Move all non-removed items to valid_items
|
||||
for (auto &item : this->items_) {
|
||||
if (!is_item_removed_(item.get())) {
|
||||
if (!item->remove) {
|
||||
valid_items.push_back(std::move(item));
|
||||
} else {
|
||||
// Recycle removed items
|
||||
this->recycle_item_(std::move(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,9 +469,6 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
// Add new item directly to to_add_
|
||||
// since we have the lock held
|
||||
this->to_add_.push_back(std::move(item));
|
||||
} else {
|
||||
// Timeout completed - recycle it
|
||||
this->recycle_item_(std::move(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,10 +518,6 @@ size_t HOT Scheduler::cleanup_() {
|
||||
}
|
||||
void HOT Scheduler::pop_raw_() {
|
||||
std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
|
||||
|
||||
// Instead of destroying, recycle the item
|
||||
this->recycle_item_(std::move(this->items_.back()));
|
||||
|
||||
this->items_.pop_back();
|
||||
}
|
||||
|
||||
@@ -570,14 +552,18 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
||||
|
||||
// Check all containers for matching items
|
||||
#ifndef ESPHOME_THREAD_SINGLE
|
||||
// Cancel and immediately recycle items in defer queue
|
||||
// Only check defer queue for timeouts (intervals never go there)
|
||||
if (type == SchedulerItem::TIMEOUT) {
|
||||
total_cancelled +=
|
||||
this->cancel_and_recycle_from_container_(this->defer_queue_, component, name_cstr, type, match_retry);
|
||||
for (auto &item : this->defer_queue_) {
|
||||
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||
this->mark_item_removed_(item.get());
|
||||
total_cancelled++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* not ESPHOME_THREAD_SINGLE */
|
||||
|
||||
// Cancel items in the main heap (can't recycle immediately due to heap structure)
|
||||
// Cancel items in the main heap
|
||||
for (auto &item : this->items_) {
|
||||
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||
this->mark_item_removed_(item.get());
|
||||
@@ -586,8 +572,14 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel and immediately recycle items in to_add_ since they're not in heap yet
|
||||
total_cancelled += this->cancel_and_recycle_from_container_(this->to_add_, component, name_cstr, type, match_retry);
|
||||
// Cancel items in to_add_
|
||||
for (auto &item : this->to_add_) {
|
||||
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||
this->mark_item_removed_(item.get());
|
||||
total_cancelled++;
|
||||
// Don't track removals for to_add_ items
|
||||
}
|
||||
}
|
||||
|
||||
return total_cancelled > 0;
|
||||
}
|
||||
@@ -755,24 +747,4 @@ bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr<SchedulerItem> &a,
|
||||
return a->next_execution_ > b->next_execution_;
|
||||
}
|
||||
|
||||
void Scheduler::recycle_item_(std::unique_ptr<SchedulerItem> item) {
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
// Pool size of 8 is a balance between memory usage and performance:
|
||||
// - Small enough to not waste memory on simple configs (1-2 timers)
|
||||
// - Large enough to handle complex setups with multiple sensors/components
|
||||
// - Prevents system-wide stalls from heap allocation/deallocation that can
|
||||
// disrupt task synchronization and cause dropped events
|
||||
static constexpr size_t MAX_POOL_SIZE = 8;
|
||||
if (this->scheduler_item_pool_.size() < MAX_POOL_SIZE) {
|
||||
// Clear callback to release captured resources
|
||||
item->callback = nullptr;
|
||||
// Clear dynamic name if any
|
||||
item->clear_dynamic_name();
|
||||
this->scheduler_item_pool_.push_back(std::move(item));
|
||||
}
|
||||
// else: unique_ptr will delete the item when it goes out of scope
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
@@ -5,7 +5,6 @@
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <array>
|
||||
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||
#include <atomic>
|
||||
#endif
|
||||
@@ -143,7 +142,11 @@ class Scheduler {
|
||||
}
|
||||
|
||||
// Destructor to clean up dynamic names
|
||||
~SchedulerItem() { clear_dynamic_name(); }
|
||||
~SchedulerItem() {
|
||||
if (name_is_dynamic) {
|
||||
delete[] name_.dynamic_name;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete copy operations to prevent accidental copies
|
||||
SchedulerItem(const SchedulerItem &) = delete;
|
||||
@@ -156,19 +159,13 @@ class Scheduler {
|
||||
// Helper to get the name regardless of storage type
|
||||
const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; }
|
||||
|
||||
// Helper to clear dynamic name if allocated
|
||||
void clear_dynamic_name() {
|
||||
if (name_is_dynamic && name_.dynamic_name) {
|
||||
delete[] name_.dynamic_name;
|
||||
name_.dynamic_name = nullptr;
|
||||
name_is_dynamic = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to set name with proper ownership
|
||||
void set_name(const char *name, bool make_copy = false) {
|
||||
// Clean up old dynamic name if any
|
||||
clear_dynamic_name();
|
||||
if (name_is_dynamic && name_.dynamic_name) {
|
||||
delete[] name_.dynamic_name;
|
||||
name_is_dynamic = false;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
// nullptr case - no name provided
|
||||
@@ -243,13 +240,10 @@ class Scheduler {
|
||||
void execute_item_(SchedulerItem *item, uint32_t now);
|
||||
|
||||
// Helper to check if item should be skipped
|
||||
bool should_skip_item_(SchedulerItem *item) const {
|
||||
return is_item_removed_(item) || (item->component != nullptr && item->component->is_failed());
|
||||
bool should_skip_item_(const SchedulerItem *item) const {
|
||||
return item->remove || (item->component != nullptr && item->component->is_failed());
|
||||
}
|
||||
|
||||
// Helper to recycle a SchedulerItem
|
||||
void recycle_item_(std::unique_ptr<SchedulerItem> item);
|
||||
|
||||
// Helper to check if item is marked for removal (platform-specific)
|
||||
// Returns true if item should be skipped, handles platform-specific synchronization
|
||||
// For ESPHOME_THREAD_MULTI_NO_ATOMICS platforms, the caller must hold the scheduler lock before calling this
|
||||
@@ -286,33 +280,14 @@ class Scheduler {
|
||||
bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
|
||||
bool match_retry) const {
|
||||
for (const auto &item : container) {
|
||||
if (is_item_removed_(item.get()) &&
|
||||
this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
|
||||
/* skip_removed= */ false)) {
|
||||
if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
|
||||
/* skip_removed= */ false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Template helper to cancel and recycle items from a container
|
||||
template<typename Container>
|
||||
size_t cancel_and_recycle_from_container_(Container &container, Component *component, const char *name_cstr,
|
||||
SchedulerItem::Type type, bool match_retry) {
|
||||
size_t cancelled = 0;
|
||||
for (auto it = container.begin(); it != container.end();) {
|
||||
if (this->matches_item_(*it, component, name_cstr, type, match_retry)) {
|
||||
// Recycle the cancelled item immediately
|
||||
this->recycle_item_(std::move(*it));
|
||||
it = container.erase(it);
|
||||
cancelled++;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
Mutex lock_;
|
||||
std::vector<std::unique_ptr<SchedulerItem>> items_;
|
||||
std::vector<std::unique_ptr<SchedulerItem>> to_add_;
|
||||
@@ -322,16 +297,6 @@ class Scheduler {
|
||||
#endif /* ESPHOME_THREAD_SINGLE */
|
||||
uint32_t to_remove_{0};
|
||||
|
||||
// Memory pool for recycling SchedulerItem objects to reduce heap churn.
|
||||
// Design decisions:
|
||||
// - std::vector is used instead of a fixed array because many systems only need 1-2 scheduler items
|
||||
// - The vector grows dynamically up to MAX_POOL_SIZE (8) only when needed, saving memory on simple setups
|
||||
// - This approach balances memory efficiency for simple configs with performance for complex ones
|
||||
// - The pool significantly reduces heap fragmentation which is critical because heap allocation/deallocation
|
||||
// can stall the entire system, causing timing issues and dropped events for any components that need
|
||||
// to synchronize between tasks (see https://github.com/esphome/backlog/issues/52)
|
||||
std::vector<std::unique_ptr<SchedulerItem>> scheduler_item_pool_;
|
||||
|
||||
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||
/*
|
||||
* Multi-threaded platforms with atomic support: last_millis_ needs atomic for lock-free updates
|
||||
|
@@ -203,13 +203,27 @@ void ESPTime::recalc_timestamp_local() {
|
||||
}
|
||||
|
||||
int32_t ESPTime::timezone_offset() {
|
||||
int32_t offset = 0;
|
||||
time_t now = ::time(nullptr);
|
||||
struct tm local_tm = *::localtime(&now);
|
||||
local_tm.tm_isdst = 0; // Cause mktime to ignore daylight saving time because we want to include it in the offset.
|
||||
time_t local_time = mktime(&local_tm);
|
||||
struct tm utc_tm = *::gmtime(&now);
|
||||
time_t utc_time = mktime(&utc_tm);
|
||||
return static_cast<int32_t>(local_time - utc_time);
|
||||
auto local = ESPTime::from_epoch_local(now);
|
||||
auto utc = ESPTime::from_epoch_utc(now);
|
||||
bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year;
|
||||
|
||||
if (utc.minute > local.minute) {
|
||||
local.minute += 60;
|
||||
local.hour -= 1;
|
||||
}
|
||||
offset += (local.minute - utc.minute) * 60;
|
||||
|
||||
if (negative) {
|
||||
offset -= (utc.hour - local.hour) * 3600;
|
||||
} else {
|
||||
if (utc.hour > local.hour) {
|
||||
local.hour += 24;
|
||||
}
|
||||
offset += (local.hour - utc.hour) * 3600;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool ESPTime::operator<(const ESPTime &other) const { return this->timestamp < other.timestamp; }
|
||||
|
Reference in New Issue
Block a user