Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
0824c21841 pool for scheduler 2025-07-28 08:08:53 -10:00
13 changed files with 196 additions and 212 deletions

View File

@@ -419,7 +419,7 @@ message ListEntitiesFanResponse {
bool disabled_by_default = 9;
string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 11;
repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"];
repeated string supported_preset_modes = 12;
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
}
// Deprecated in API version 1.6 - only used in deprecated fields
@@ -500,7 +500,7 @@ message ListEntitiesLightResponse {
string name = 3;
reserved 4; // Deprecated: was string unique_id
repeated ColorMode supported_color_modes = 12 [(container_pointer) = "std::set<light::ColorMode>"];
repeated ColorMode supported_color_modes = 12;
// next four supports_* are for legacy clients, newer clients should use color modes
// Deprecated in API version 1.6
bool legacy_supports_brightness = 5 [deprecated=true];
@@ -966,7 +966,7 @@ message ListEntitiesClimateResponse {
bool supports_current_temperature = 5;
bool supports_two_point_target_temperature = 6;
repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"];
repeated ClimateMode supported_modes = 7;
float visual_min_temperature = 8;
float visual_max_temperature = 9;
float visual_target_temperature_step = 10;
@@ -975,11 +975,11 @@ message ListEntitiesClimateResponse {
// Deprecated in API version 1.5
bool legacy_supports_away = 11 [deprecated=true];
bool supports_action = 12;
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"];
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"];
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"];
repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set<climate::ClimatePreset>"];
repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"];
repeated ClimateFanMode supported_fan_modes = 13;
repeated ClimateSwingMode supported_swing_modes = 14;
repeated string supported_custom_fan_modes = 15;
repeated ClimatePreset supported_presets = 16;
repeated string supported_custom_presets = 17;
bool disabled_by_default = 18;
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 20;
@@ -1119,7 +1119,7 @@ message ListEntitiesSelectResponse {
reserved 4; // Deprecated: was string unique_id
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
repeated string options = 6 [(container_pointer) = "std::vector"];
repeated string options = 6;
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"];
@@ -1834,7 +1834,7 @@ message VoiceAssistantConfigurationResponse {
option (ifdef) = "USE_VOICE_ASSISTANT";
repeated VoiceAssistantWakeWord available_wake_words = 1;
repeated string active_wake_words = 2 [(container_pointer) = "std::vector"];
repeated string active_wake_words = 2;
uint32 max_active_wake_words = 3;
}

View File

@@ -413,7 +413,8 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
msg.supports_speed = traits.supports_speed();
msg.supports_direction = traits.supports_direction();
msg.supported_speed_count = traits.supported_speed_count();
msg.supported_preset_modes = &traits.supported_preset_modes();
for (auto const &preset : traits.supported_preset_modes())
msg.supported_preset_modes.push_back(preset);
return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
@@ -469,7 +470,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
auto *light = static_cast<light::LightState *>(entity);
ListEntitiesLightResponse msg;
auto traits = light->get_traits();
msg.supported_color_modes = &traits.get_supported_color_modes();
for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
msg.min_mireds = traits.get_min_mireds();
@@ -655,7 +657,8 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supports_current_humidity = traits.get_supports_current_humidity();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
msg.supports_target_humidity = traits.get_supports_target_humidity();
msg.supported_modes = &traits.get_supported_modes();
for (auto mode : traits.get_supported_modes())
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
@@ -663,11 +666,16 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.visual_min_humidity = traits.get_visual_min_humidity();
msg.visual_max_humidity = traits.get_visual_max_humidity();
msg.supports_action = traits.get_supports_action();
msg.supported_fan_modes = &traits.get_supported_fan_modes();
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes();
msg.supported_presets = &traits.get_supported_presets();
msg.supported_custom_presets = &traits.get_supported_custom_presets();
msg.supported_swing_modes = &traits.get_supported_swing_modes();
for (auto fan_mode : traits.get_supported_fan_modes())
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
msg.supported_custom_fan_modes.push_back(custom_fan_mode);
for (auto preset : traits.get_supported_presets())
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
for (auto const &custom_preset : traits.get_supported_custom_presets())
msg.supported_custom_presets.push_back(custom_preset);
for (auto swing_mode : traits.get_supported_swing_modes())
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -873,7 +881,8 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *select = static_cast<select::Select *>(entity);
ListEntitiesSelectResponse msg;
msg.options = &select->traits.get_options();
for (const auto &option : select->traits.get_options())
msg.options.push_back(option);
return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1187,7 +1196,9 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
resp_wake_word.trained_languages.push_back(lang);
}
}
resp.active_wake_words = &config.active_wake_words;
for (auto &wake_word_id : config.active_wake_words) {
resp.active_wake_words.push_back(wake_word_id);
}
resp.max_active_wake_words = config.max_active_wake_words;
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
}

View File

@@ -28,5 +28,4 @@ extend google.protobuf.FieldOptions {
optional string field_ifdef = 1042;
optional uint32 fixed_array_size = 50007;
optional bool no_zero_copy = 50008 [default=false];
optional string container_pointer = 50001;
}

View File

@@ -331,7 +331,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(10, this->icon_ref_);
#endif
buffer.encode_uint32(11, static_cast<uint32_t>(this->entity_category));
for (const auto &it : *this->supported_preset_modes) {
for (auto &it : this->supported_preset_modes) {
buffer.encode_string(12, it, true);
}
#ifdef USE_DEVICES
@@ -351,8 +351,8 @@ void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->icon_ref_.size());
#endif
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
if (!this->supported_preset_modes->empty()) {
for (const auto &it : *this->supported_preset_modes) {
if (!this->supported_preset_modes.empty()) {
for (const auto &it : this->supported_preset_modes) {
size.add_length_force(1, it.size());
}
}
@@ -447,7 +447,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id_ref_);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name_ref_);
for (const auto &it : *this->supported_color_modes) {
for (auto &it : this->supported_color_modes) {
buffer.encode_uint32(12, static_cast<uint32_t>(it), true);
}
buffer.encode_float(9, this->min_mireds);
@@ -468,8 +468,8 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->object_id_ref_.size());
size.add_fixed32(1, this->key);
size.add_length(1, this->name_ref_.size());
if (!this->supported_color_modes->empty()) {
for (const auto &it : *this->supported_color_modes) {
if (!this->supported_color_modes.empty()) {
for (const auto &it : this->supported_color_modes) {
size.add_uint32_force(1, static_cast<uint32_t>(it));
}
}
@@ -1064,26 +1064,26 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->name_ref_);
buffer.encode_bool(5, this->supports_current_temperature);
buffer.encode_bool(6, this->supports_two_point_target_temperature);
for (const auto &it : *this->supported_modes) {
for (auto &it : this->supported_modes) {
buffer.encode_uint32(7, static_cast<uint32_t>(it), true);
}
buffer.encode_float(8, this->visual_min_temperature);
buffer.encode_float(9, this->visual_max_temperature);
buffer.encode_float(10, this->visual_target_temperature_step);
buffer.encode_bool(12, this->supports_action);
for (const auto &it : *this->supported_fan_modes) {
for (auto &it : this->supported_fan_modes) {
buffer.encode_uint32(13, static_cast<uint32_t>(it), true);
}
for (const auto &it : *this->supported_swing_modes) {
for (auto &it : this->supported_swing_modes) {
buffer.encode_uint32(14, static_cast<uint32_t>(it), true);
}
for (const auto &it : *this->supported_custom_fan_modes) {
for (auto &it : this->supported_custom_fan_modes) {
buffer.encode_string(15, it, true);
}
for (const auto &it : *this->supported_presets) {
for (auto &it : this->supported_presets) {
buffer.encode_uint32(16, static_cast<uint32_t>(it), true);
}
for (const auto &it : *this->supported_custom_presets) {
for (auto &it : this->supported_custom_presets) {
buffer.encode_string(17, it, true);
}
buffer.encode_bool(18, this->disabled_by_default);
@@ -1106,8 +1106,8 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->name_ref_.size());
size.add_bool(1, this->supports_current_temperature);
size.add_bool(1, this->supports_two_point_target_temperature);
if (!this->supported_modes->empty()) {
for (const auto &it : *this->supported_modes) {
if (!this->supported_modes.empty()) {
for (const auto &it : this->supported_modes) {
size.add_uint32_force(1, static_cast<uint32_t>(it));
}
}
@@ -1115,28 +1115,28 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const {
size.add_float(1, this->visual_max_temperature);
size.add_float(1, this->visual_target_temperature_step);
size.add_bool(1, this->supports_action);
if (!this->supported_fan_modes->empty()) {
for (const auto &it : *this->supported_fan_modes) {
if (!this->supported_fan_modes.empty()) {
for (const auto &it : this->supported_fan_modes) {
size.add_uint32_force(1, static_cast<uint32_t>(it));
}
}
if (!this->supported_swing_modes->empty()) {
for (const auto &it : *this->supported_swing_modes) {
if (!this->supported_swing_modes.empty()) {
for (const auto &it : this->supported_swing_modes) {
size.add_uint32_force(1, static_cast<uint32_t>(it));
}
}
if (!this->supported_custom_fan_modes->empty()) {
for (const auto &it : *this->supported_custom_fan_modes) {
if (!this->supported_custom_fan_modes.empty()) {
for (const auto &it : this->supported_custom_fan_modes) {
size.add_length_force(1, it.size());
}
}
if (!this->supported_presets->empty()) {
for (const auto &it : *this->supported_presets) {
if (!this->supported_presets.empty()) {
for (const auto &it : this->supported_presets) {
size.add_uint32_force(2, static_cast<uint32_t>(it));
}
}
if (!this->supported_custom_presets->empty()) {
for (const auto &it : *this->supported_custom_presets) {
if (!this->supported_custom_presets.empty()) {
for (const auto &it : this->supported_custom_presets) {
size.add_length_force(2, it.size());
}
}
@@ -1371,7 +1371,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon_ref_);
#endif
for (const auto &it : *this->options) {
for (auto &it : this->options) {
buffer.encode_string(6, it, true);
}
buffer.encode_bool(7, this->disabled_by_default);
@@ -1387,8 +1387,8 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const {
#ifdef USE_ENTITY_ICON
size.add_length(1, this->icon_ref_.size());
#endif
if (!this->options->empty()) {
for (const auto &it : *this->options) {
if (!this->options.empty()) {
for (const auto &it : this->options) {
size.add_length_force(1, it.size());
}
}
@@ -2332,15 +2332,15 @@ void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const
for (auto &it : this->available_wake_words) {
buffer.encode_message(1, it, true);
}
for (const auto &it : *this->active_wake_words) {
for (auto &it : this->active_wake_words) {
buffer.encode_string(2, it, true);
}
buffer.encode_uint32(3, this->max_active_wake_words);
}
void VoiceAssistantConfigurationResponse::calculate_size(ProtoSize &size) const {
size.add_repeated_message(1, this->available_wake_words);
if (!this->active_wake_words->empty()) {
for (const auto &it : *this->active_wake_words) {
if (!this->active_wake_words.empty()) {
for (const auto &it : this->active_wake_words) {
size.add_length_force(1, it.size());
}
}

View File

@@ -6,9 +6,6 @@
#include "esphome/core/string_ref.h"
#include "proto.h"
#include "api_pb2_includes.h"
#include <set>
namespace esphome::api {
@@ -698,7 +695,7 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage {
bool supports_speed{false};
bool supports_direction{false};
int32_t supported_speed_count{0};
const std::set<std::string> *supported_preset_modes{};
std::vector<std::string> supported_preset_modes{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -763,7 +760,7 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_light_response"; }
#endif
const std::set<light::ColorMode> *supported_color_modes{};
std::vector<enums::ColorMode> supported_color_modes{};
float min_mireds{0.0f};
float max_mireds{0.0f};
std::vector<std::string> effects{};
@@ -1314,16 +1311,16 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
#endif
bool supports_current_temperature{false};
bool supports_two_point_target_temperature{false};
const std::set<climate::ClimateMode> *supported_modes{};
std::vector<enums::ClimateMode> supported_modes{};
float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f};
float visual_target_temperature_step{0.0f};
bool supports_action{false};
const std::set<climate::ClimateFanMode> *supported_fan_modes{};
const std::set<climate::ClimateSwingMode> *supported_swing_modes{};
const std::set<std::string> *supported_custom_fan_modes{};
const std::set<climate::ClimatePreset> *supported_presets{};
const std::set<std::string> *supported_custom_presets{};
std::vector<enums::ClimateFanMode> supported_fan_modes{};
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
std::vector<std::string> supported_custom_fan_modes{};
std::vector<enums::ClimatePreset> supported_presets{};
std::vector<std::string> supported_custom_presets{};
float visual_current_temperature_step{0.0f};
bool supports_current_humidity{false};
bool supports_target_humidity{false};
@@ -1470,7 +1467,7 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_select_response"; }
#endif
const std::vector<std::string> *options{};
std::vector<std::string> options{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2442,7 +2439,7 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage {
const char *message_name() const override { return "voice_assistant_configuration_response"; }
#endif
std::vector<VoiceAssistantWakeWord> available_wake_words{};
const std::vector<std::string> *active_wake_words{};
std::vector<std::string> active_wake_words{};
uint32_t max_active_wake_words{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;

View File

@@ -814,7 +814,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
for (const auto &it : *this->supported_preset_modes) {
for (const auto &it : this->supported_preset_modes) {
dump_field(out, "supported_preset_modes", it, 4);
}
#ifdef USE_DEVICES
@@ -857,7 +857,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
for (const auto &it : *this->supported_color_modes) {
for (const auto &it : this->supported_color_modes) {
dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4);
}
dump_field(out, "min_mireds", this->min_mireds);
@@ -1173,26 +1173,26 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
dump_field(out, "name", this->name_ref_);
dump_field(out, "supports_current_temperature", this->supports_current_temperature);
dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature);
for (const auto &it : *this->supported_modes) {
for (const auto &it : this->supported_modes) {
dump_field(out, "supported_modes", static_cast<enums::ClimateMode>(it), 4);
}
dump_field(out, "visual_min_temperature", this->visual_min_temperature);
dump_field(out, "visual_max_temperature", this->visual_max_temperature);
dump_field(out, "visual_target_temperature_step", this->visual_target_temperature_step);
dump_field(out, "supports_action", this->supports_action);
for (const auto &it : *this->supported_fan_modes) {
for (const auto &it : this->supported_fan_modes) {
dump_field(out, "supported_fan_modes", static_cast<enums::ClimateFanMode>(it), 4);
}
for (const auto &it : *this->supported_swing_modes) {
for (const auto &it : this->supported_swing_modes) {
dump_field(out, "supported_swing_modes", static_cast<enums::ClimateSwingMode>(it), 4);
}
for (const auto &it : *this->supported_custom_fan_modes) {
for (const auto &it : this->supported_custom_fan_modes) {
dump_field(out, "supported_custom_fan_modes", it, 4);
}
for (const auto &it : *this->supported_presets) {
for (const auto &it : this->supported_presets) {
dump_field(out, "supported_presets", static_cast<enums::ClimatePreset>(it), 4);
}
for (const auto &it : *this->supported_custom_presets) {
for (const auto &it : this->supported_custom_presets) {
dump_field(out, "supported_custom_presets", it, 4);
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
@@ -1305,7 +1305,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
#endif
for (const auto &it : *this->options) {
for (const auto &it : this->options) {
dump_field(out, "options", it, 4);
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
@@ -1769,7 +1769,7 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
it.dump_to(out);
out.append("\n");
}
for (const auto &it : *this->active_wake_words) {
for (const auto &it : this->active_wake_words) {
dump_field(out, "active_wake_words", it, 4);
}
dump_field(out, "max_active_wake_words", this->max_active_wake_words);

View File

@@ -1,30 +0,0 @@
#pragma once
// This file provides includes needed by the generated protobuf code
// when using pointer optimizations for component-specific types
#ifdef USE_CLIMATE
#include "esphome/components/climate/climate_mode.h"
#include "esphome/components/climate/climate_traits.h"
#endif
#ifdef USE_LIGHT
#include "esphome/components/light/light_traits.h"
#endif
#ifdef USE_FAN
#include "esphome/components/fan/fan_traits.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select_traits.h"
#endif
#ifdef USE_MEDIA_PLAYER
#include "esphome/components/media_player/media_player_traits.h"
#endif
// Standard library includes that might be needed
#include <set>
#include <vector>
#include <string>

View File

@@ -29,7 +29,7 @@ class FanTraits {
/// Set whether this fan supports changing direction
void set_direction(bool direction) { this->direction_ = direction; }
/// Return the preset modes supported by the fan.
const std::set<std::string> &supported_preset_modes() const { return this->preset_modes_; }
std::set<std::string> supported_preset_modes() const { return this->preset_modes_; }
/// Set the preset modes supported by the fan.
void set_supported_preset_modes(const std::set<std::string> &preset_modes) { this->preset_modes_ = preset_modes; }
/// Return if preset modes are supported

View File

@@ -5,7 +5,7 @@ namespace select {
void SelectTraits::set_options(std::vector<std::string> options) { this->options_ = std::move(options); }
const std::vector<std::string> &SelectTraits::get_options() const { return this->options_; }
std::vector<std::string> SelectTraits::get_options() const { return this->options_; }
} // namespace select
} // namespace esphome

View File

@@ -9,7 +9,7 @@ namespace select {
class SelectTraits {
public:
void set_options(std::vector<std::string> options);
const std::vector<std::string> &get_options() const;
std::vector<std::string> get_options() const;
protected:
std::vector<std::string> options_;

View File

@@ -76,8 +76,22 @@ 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
auto item = make_unique<SchedulerItem>();
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>();
}
item->component = component;
item->set_name(name_cstr, !is_static_string);
item->type = type;
@@ -90,16 +104,12 @@ 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_};
this->cancel_item_locked_(component, name_cstr, type);
this->defer_queue_.push_back(std::move(item));
return;
}
#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;
@@ -131,8 +141,6 @@ 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) ||
@@ -322,11 +330,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, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(), now_64,
major_dbg, last_dbg);
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);
#else /* not ESPHOME_THREAD_MULTI_ATOMICS */
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(), now_64,
this->millis_major_, this->last_millis_);
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_);
#endif /* else ESPHOME_THREAD_MULTI_ATOMICS */
// Cleanup before debug output
this->cleanup_();
@@ -358,6 +366,7 @@ void HOT Scheduler::call(uint32_t now) {
// If we have too many items to remove
if (this->to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) {
// ESP_LOGD(TAG, "Starting cleanup of %u removed items", this->to_remove_);
// We hold the lock for the entire cleanup operation because:
// 1. We're rebuilding the entire items_ list, so we need exclusive access throughout
// 2. Other threads must see either the old state or the new state, not intermediate states
@@ -367,10 +376,13 @@ void HOT Scheduler::call(uint32_t now) {
std::vector<std::unique_ptr<SchedulerItem>> valid_items;
// Move all non-removed items to valid_items
// Move all non-removed items to valid_items, recycle removed ones
for (auto &item : this->items_) {
if (!item->remove) {
valid_items.push_back(std::move(item));
} else {
// Recycle removed items
this->recycle_item_(std::move(item));
}
}
@@ -378,6 +390,7 @@ void HOT Scheduler::call(uint32_t now) {
this->items_ = std::move(valid_items);
// Rebuild the heap structure since items are no longer in heap order
std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
// ESP_LOGD(TAG, "Cleanup complete - pool size now: %zu", this->scheduler_item_pool_.size());
this->to_remove_ = 0;
}
@@ -431,6 +444,9 @@ 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));
}
}
}
@@ -480,6 +496,10 @@ 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();
}
@@ -514,18 +534,14 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
// Check all containers for matching items
#ifndef ESPHOME_THREAD_SINGLE
// Only check defer queue for timeouts (intervals never go there)
// Cancel and immediately recycle items in defer queue
if (type == SchedulerItem::TIMEOUT) {
for (auto &item : this->defer_queue_) {
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
item->remove = true;
total_cancelled++;
}
}
total_cancelled +=
this->cancel_and_recycle_from_container_(this->defer_queue_, component, name_cstr, type, match_retry);
}
#endif /* not ESPHOME_THREAD_SINGLE */
// Cancel items in the main heap
// Cancel items in the main heap (can't recycle immediately due to heap structure)
for (auto &item : this->items_) {
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
item->remove = true;
@@ -534,14 +550,8 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
}
}
// Cancel items in to_add_
for (auto &item : this->to_add_) {
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
item->remove = true;
total_cancelled++;
// Don't track removals for to_add_ items
}
}
// 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);
return total_cancelled > 0;
}
@@ -709,4 +719,22 @@ 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;
static constexpr size_t MAX_POOL_SIZE = 16;
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));
// ESP_LOGD(TAG, "Recycled SchedulerItem to pool (pool size now: %zu)", this->scheduler_item_pool_.size());
} else {
// ESP_LOGD(TAG, "Discarding SchedulerItem (pool full at %zu items)", this->scheduler_item_pool_.size());
// unique_ptr will delete the item when it goes out of scope
}
}
} // namespace esphome

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <cstring>
#include <deque>
#include <array>
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
#include <atomic>
#endif
@@ -117,11 +118,7 @@ class Scheduler {
}
// Destructor to clean up dynamic names
~SchedulerItem() {
if (name_is_dynamic) {
delete[] name_.dynamic_name;
}
}
~SchedulerItem() { clear_dynamic_name(); }
// Delete copy operations to prevent accidental copies
SchedulerItem(const SchedulerItem &) = delete;
@@ -134,13 +131,19 @@ 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
if (name_is_dynamic && name_.dynamic_name) {
delete[] name_.dynamic_name;
name_is_dynamic = false;
}
clear_dynamic_name();
if (!name) {
// nullptr case - no name provided
@@ -219,6 +222,9 @@ class Scheduler {
return item->remove || (item->component != nullptr && item->component->is_failed());
}
// Helper to recycle a SchedulerItem
void recycle_item_(std::unique_ptr<SchedulerItem> item);
// Template helper to check if any item in a container matches our criteria
template<typename Container>
bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
@@ -232,6 +238,24 @@ class Scheduler {
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_;
@@ -241,6 +265,9 @@ class Scheduler {
#endif /* ESPHOME_THREAD_SINGLE */
uint32_t to_remove_{0};
// Memory pool for recycling SchedulerItem objects
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

View File

@@ -388,7 +388,8 @@ class DoubleType(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_fixed_size_calculation(name, "add_double")
field_id_size = self.calculate_field_id_size()
return f"size.add_double({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@@ -1169,10 +1170,6 @@ class FixedArrayRepeatedType(TypeInfo):
class RepeatedTypeInfo(TypeInfo):
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
super().__init__(field)
# Check if this is a pointer field by looking for container_pointer option
self._container_type = get_field_opt(field, pb.container_pointer, "")
self._use_pointer = bool(self._container_type)
# For repeated fields, we need to get the base type info
# but we can't call create_field_type_info as it would cause recursion
# So we extract just the type creation logic
@@ -1188,14 +1185,6 @@ class RepeatedTypeInfo(TypeInfo):
@property
def cpp_type(self) -> str:
if self._use_pointer and self._container_type:
# For pointer fields, use the specified container type
# If the container type already includes the element type (e.g., std::set<climate::ClimateMode>)
# use it as-is, otherwise append the element type
if "<" in self._container_type and ">" in self._container_type:
return f"const {self._container_type}*"
else:
return f"const {self._container_type}<{self._ti.cpp_type}>*"
return f"std::vector<{self._ti.cpp_type}>"
@property
@@ -1216,9 +1205,6 @@ class RepeatedTypeInfo(TypeInfo):
@property
def decode_varint_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_varint
if content is None:
return None
@@ -1228,9 +1214,6 @@ class RepeatedTypeInfo(TypeInfo):
@property
def decode_length_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_length
if content is None and isinstance(self._ti, MessageType):
# Special handling for non-template message decoding
@@ -1243,9 +1226,6 @@ class RepeatedTypeInfo(TypeInfo):
@property
def decode_32bit_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_32bit
if content is None:
return None
@@ -1255,9 +1235,6 @@ class RepeatedTypeInfo(TypeInfo):
@property
def decode_64bit_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_64bit
if content is None:
return None
@@ -1272,31 +1249,16 @@ class RepeatedTypeInfo(TypeInfo):
@property
def encode_content(self) -> str:
if self._use_pointer:
# For pointer fields, just dereference (pointer should never be null in our use case)
o = f"for (const auto &it : *this->{self.field_name}) {{\n"
if isinstance(self._ti, EnumType):
o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n"
else:
o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n"
o += "}"
return o
o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n"
if isinstance(self._ti, EnumType):
o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n"
else:
o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n"
if isinstance(self._ti, EnumType):
o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n"
else:
o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n"
o += "}"
return o
o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n"
o += "}"
return o
@property
def dump_content(self) -> str:
if self._use_pointer:
# For pointer fields, dereference and use the existing helper
return _generate_array_dump_content(
self._ti, f"*this->{self.field_name}", self.name, is_bool=False
)
return _generate_array_dump_content(
self._ti, f"this->{self.field_name}", self.name, is_bool=self._ti_is_bool
)
@@ -1307,34 +1269,30 @@ class RepeatedTypeInfo(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
# For repeated fields, we always need to pass force=True to the underlying type's calculation
# This is because the encode method always sets force=true for repeated fields
# Handle message types separately as they use a dedicated helper
if isinstance(self._ti, MessageType):
# For repeated messages, use the dedicated helper that handles iteration internally
field_id_size = self._ti.calculate_field_id_size()
container = f"*{name}" if self._use_pointer else name
return f"size.add_repeated_message({field_id_size}, {container});"
o = f"size.add_repeated_message({field_id_size}, {name});"
return o
# For non-message types, generate size calculation with iteration
container_ref = f"*{name}" if self._use_pointer else name
empty_check = f"{name}->empty()" if self._use_pointer else f"{name}.empty()"
# For other repeated types, use the underlying type's size calculation with force=True
o = f"if (!{name}.empty()) {{\n"
o = f"if (!{empty_check}) {{\n"
# Check if this is a fixed-size type
# Check if this is a fixed-size type by seeing if it has a fixed byte count
num_bytes = self._ti.get_fixed_size_bytes()
if num_bytes is not None:
# Fixed types have constant size per element
# Fixed types have constant size per element, so we can multiply
field_id_size = self._ti.calculate_field_id_size()
# Pre-calculate the total bytes per element
bytes_per_element = field_id_size + num_bytes
size_expr = f"{name}->size()" if self._use_pointer else f"{name}.size()"
o += f" size.add_precalculated_size({size_expr} * {bytes_per_element});\n"
o += (
f" size.add_precalculated_size({name}.size() * {bytes_per_element});\n"
)
else:
# Other types need the actual value
auto_ref = "" if self._ti_is_bool else "&"
o += f" for (const auto {auto_ref}it : {container_ref}) {{\n"
o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n"
o += f" {self._ti.get_size_calculation('it', True)}\n"
o += " }\n"
o += "}"
return o
@@ -2125,7 +2083,6 @@ def main() -> None:
d = descriptor.FileDescriptorSet.FromString(proto_content)
file = d.file[0]
content = FILE_HEADER
content += """\
#pragma once
@@ -2134,12 +2091,7 @@ def main() -> None:
#include "esphome/core/string_ref.h"
#include "proto.h"
#include "api_pb2_includes.h"
#include <set>
"""
content += """
namespace esphome::api {
"""