From 93c555ae873dbd29ac29f18ace4615fef6c482eb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 22 Oct 2025 13:18:14 -1000 Subject: [PATCH] reset --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 3 +- esphome/components/api/api_pb2.h | 2 +- esphome/components/copy/fan/copy_fan.cpp | 12 +---- esphome/components/copy/fan/copy_fan.h | 2 - esphome/components/fan/automation.h | 5 +-- esphome/components/fan/fan.cpp | 44 ++++++------------- esphome/components/fan/fan_traits.h | 35 ++++++++++----- .../components/hbridge/fan/hbridge_fan.cpp | 6 +-- esphome/components/hbridge/fan/hbridge_fan.h | 10 +++-- esphome/components/speed/fan/speed_fan.cpp | 7 ++- esphome/components/speed/fan/speed_fan.h | 10 +++-- .../components/template/fan/template_fan.cpp | 8 ++-- .../components/template/fan/template_fan.h | 10 +++-- esphome/components/web_server/web_server.cpp | 2 +- 15 files changed, 72 insertions(+), 86 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 34be6e4aa2..d202486cfa 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -425,7 +425,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) = "FixedVector"]; + repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"]; uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"]; } // Deprecated in API version 1.6 - only used in deprecated fields diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 970b6d29f4..7c135946f8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -423,8 +423,7 @@ 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(); - if (traits.supports_preset_modes()) - msg.supported_preset_modes = &traits.supported_preset_modes(); + msg.supported_preset_modes = &traits.supported_preset_modes_for_api_(); return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::fan_command(const FanCommandRequest &msg) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 647dd47b89..ed49498176 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -725,7 +725,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage { bool supports_speed{false}; bool supports_direction{false}; int32_t supported_speed_count{0}; - const FixedVector *supported_preset_modes{}; + const std::set *supported_preset_modes{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp index 9ec4d8f973..15a7f5e025 100644 --- a/esphome/components/copy/fan/copy_fan.cpp +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -35,17 +35,7 @@ fan::FanTraits CopyFan::get_traits() { traits.set_speed(base.supports_speed()); traits.set_supported_speed_count(base.supported_speed_count()); traits.set_direction(base.supports_direction()); - - // Copy preset modes from source to avoid dangling pointer to temporary - if (base.supports_preset_modes()) { - const auto &source_modes = base.supported_preset_modes(); - this->preset_modes_.init(source_modes.size()); - for (const auto &mode : source_modes) { - this->preset_modes_.push_back(mode); - } - traits.set_supported_preset_modes(&this->preset_modes_); - } - + traits.set_supported_preset_modes(base.supported_preset_modes()); return traits; } diff --git a/esphome/components/copy/fan/copy_fan.h b/esphome/components/copy/fan/copy_fan.h index 78134c6890..b474975bc4 100644 --- a/esphome/components/copy/fan/copy_fan.h +++ b/esphome/components/copy/fan/copy_fan.h @@ -1,7 +1,6 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/helpers.h" #include "esphome/components/fan/fan.h" namespace esphome { @@ -20,7 +19,6 @@ class CopyFan : public fan::Fan, public Component { ; fan::Fan *source_; - FixedVector preset_modes_{}; }; } // namespace copy diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index cf04362477..90661c307c 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -58,11 +58,10 @@ template class CycleSpeedAction : public Action { void play(Ts... x) override { // check to see if fan supports speeds and is on - auto traits = this->state_->get_traits(); - if (traits.supported_speed_count()) { + if (this->state_->get_traits().supported_speed_count()) { if (this->state_->state) { int speed = this->state_->speed + 1; - int supported_speed_count = traits.supported_speed_count(); + int supported_speed_count = this->state_->get_traits().supported_speed_count(); bool off_speed_cycle = no_off_cycle_.value(x...); if (speed > supported_speed_count && off_speed_cycle) { // was running at max speed, off speed cycle enabled, so turn off diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 774cf59e23..26065ed644 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -50,18 +50,8 @@ void FanCall::validate_() { } if (!this->preset_mode_.empty()) { - bool found = false; - if (traits.supports_preset_modes()) { - const auto &preset_modes = traits.supported_preset_modes(); - // Linear search is efficient for small preset mode lists (typically 2-5 items) - for (const auto &mode : preset_modes) { - if (mode == this->preset_mode_) { - found = true; - break; - } - } - } - if (!found) { + const auto &preset_modes = traits.supported_preset_modes(); + if (preset_modes.find(this->preset_mode_) == preset_modes.end()) { ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), this->preset_mode_.c_str()); this->preset_mode_.clear(); } @@ -102,12 +92,11 @@ FanCall FanRestoreState::to_call(Fan &fan) { call.set_speed(this->speed); call.set_direction(this->direction); - auto traits = fan.get_traits(); - if (traits.supports_preset_modes()) { + if (fan.get_traits().supports_preset_modes()) { // Use stored preset index to get preset name - const auto &preset_modes = traits.supported_preset_modes(); + const auto &preset_modes = fan.get_traits().supported_preset_modes(); if (this->preset_mode < preset_modes.size()) { - call.set_preset_mode(preset_modes[this->preset_mode]); + call.set_preset_mode(*std::next(preset_modes.begin(), this->preset_mode)); } } return call; @@ -118,12 +107,11 @@ void FanRestoreState::apply(Fan &fan) { fan.speed = this->speed; fan.direction = this->direction; - auto traits = fan.get_traits(); - if (traits.supports_preset_modes()) { + if (fan.get_traits().supports_preset_modes()) { // Use stored preset index to get preset name - const auto &preset_modes = traits.supported_preset_modes(); + const auto &preset_modes = fan.get_traits().supported_preset_modes(); if (this->preset_mode < preset_modes.size()) { - fan.preset_mode = preset_modes[this->preset_mode]; + fan.preset_mode = *std::next(preset_modes.begin(), this->preset_mode); } } fan.publish_state(); @@ -200,16 +188,12 @@ void Fan::save_state_() { state.speed = this->speed; state.direction = this->direction; - auto traits = this->get_traits(); - if (traits.supports_preset_modes() && !this->preset_mode.empty()) { - const auto &preset_modes = traits.supported_preset_modes(); - // Store index of current preset mode - linear search is efficient for small lists - for (size_t i = 0; i < preset_modes.size(); i++) { - if (preset_modes[i] == this->preset_mode) { - state.preset_mode = i; - break; - } - } + if (this->get_traits().supports_preset_modes() && !this->preset_mode.empty()) { + const auto &preset_modes = this->get_traits().supported_preset_modes(); + // Store index of current preset mode + auto preset_iterator = preset_modes.find(this->preset_mode); + if (preset_iterator != preset_modes.end()) + state.preset_mode = std::distance(preset_modes.begin(), preset_iterator); } this->rtc_.save(&state); diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 5c2a0eb355..48509e5705 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -1,8 +1,16 @@ +#include +#include + #pragma once -#include "esphome/core/helpers.h" - namespace esphome { + +#ifdef USE_API +namespace api { +class APIConnection; +} // namespace api +#endif + namespace fan { class FanTraits { @@ -10,12 +18,6 @@ class FanTraits { FanTraits() = default; FanTraits(bool oscillation, bool speed, bool direction, int speed_count) : oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {} - FanTraits(bool oscillation, bool speed, bool direction, int speed_count, const FixedVector *preset_modes) - : oscillation_(oscillation), - speed_(speed), - direction_(direction), - speed_count_(speed_count), - preset_modes_(preset_modes) {} /// Return if this fan supports oscillation. bool supports_oscillation() const { return this->oscillation_; } @@ -34,18 +36,27 @@ 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 FixedVector &supported_preset_modes() const { return *this->preset_modes_; } + std::set supported_preset_modes() const { return this->preset_modes_; } /// Set the preset modes supported by the fan. - void set_supported_preset_modes(const FixedVector *preset_modes) { this->preset_modes_ = preset_modes; } + void set_supported_preset_modes(const std::set &preset_modes) { this->preset_modes_ = preset_modes; } /// Return if preset modes are supported - bool supports_preset_modes() const { return this->preset_modes_ != nullptr && !this->preset_modes_->empty(); } + bool supports_preset_modes() const { return !this->preset_modes_.empty(); } protected: +#ifdef USE_API + // The API connection is a friend class to access internal methods + friend class api::APIConnection; + // This method returns a reference to the internal preset modes set. + // It is used by the API to avoid copying data when encoding messages. + // Warning: Do not use this method outside of the API connection code. + // It returns a reference to internal data that can be invalidated. + const std::set &supported_preset_modes_for_api_() const { return this->preset_modes_; } +#endif bool oscillation_{false}; bool speed_{false}; bool direction_{false}; int speed_count_{}; - const FixedVector *preset_modes_{nullptr}; + std::set preset_modes_{}; }; } // namespace fan diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 6971e11cf6..605a9d4ef3 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -33,10 +33,10 @@ void HBridgeFan::setup() { restore->apply(*this); this->write_state_(); } -} -fan::FanTraits HBridgeFan::get_traits() { - return fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_, &this->preset_modes_); + // Construct traits + this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); + this->traits_.set_supported_preset_modes(this->preset_modes_); } void HBridgeFan::dump_config() { diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index e4b075f759..4234fccae3 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -1,7 +1,8 @@ #pragma once +#include + #include "esphome/core/automation.h" -#include "esphome/core/helpers.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" #include "esphome/components/fan/fan.h" @@ -21,11 +22,11 @@ class HBridgeFan : public Component, public fan::Fan { void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; } void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; } void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; } - void set_preset_modes(const std::initializer_list &presets) { this->preset_modes_ = presets; } + void set_preset_modes(const std::set &presets) { preset_modes_ = presets; } void setup() override; void dump_config() override; - fan::FanTraits get_traits() override; + fan::FanTraits get_traits() override { return this->traits_; } fan::FanCall brake(); @@ -36,7 +37,8 @@ class HBridgeFan : public Component, public fan::Fan { output::BinaryOutput *oscillating_{nullptr}; int speed_count_{}; DecayMode decay_mode_{DECAY_MODE_SLOW}; - FixedVector preset_modes_{}; + fan::FanTraits traits_; + std::set preset_modes_{}; void control(const fan::FanCall &call) override; void write_state_(); diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 081588286f..57bd795416 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -12,11 +12,10 @@ void SpeedFan::setup() { restore->apply(*this); this->write_state_(); } -} -fan::FanTraits SpeedFan::get_traits() { - return fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_, - &this->preset_modes_); + // Construct traits + this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); + this->traits_.set_supported_preset_modes(this->preset_modes_); } void SpeedFan::dump_config() { LOG_FAN("", "Speed Fan", this); } diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index baf0fe30f0..6537bce3f6 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -1,7 +1,8 @@ #pragma once +#include + #include "esphome/core/component.h" -#include "esphome/core/helpers.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" #include "esphome/components/fan/fan.h" @@ -17,8 +18,8 @@ class SpeedFan : public Component, public fan::Fan { void set_output(output::FloatOutput *output) { this->output_ = output; } void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } - void set_preset_modes(const std::initializer_list &presets) { this->preset_modes_ = presets; } - fan::FanTraits get_traits() override; + void set_preset_modes(const std::set &presets) { this->preset_modes_ = presets; } + fan::FanTraits get_traits() override { return this->traits_; } protected: void control(const fan::FanCall &call) override; @@ -28,7 +29,8 @@ class SpeedFan : public Component, public fan::Fan { output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *direction_{nullptr}; int speed_count_{}; - FixedVector preset_modes_{}; + fan::FanTraits traits_; + std::set preset_modes_{}; }; } // namespace speed diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp index 94891e6a72..5f4a2ae8f7 100644 --- a/esphome/components/template/fan/template_fan.cpp +++ b/esphome/components/template/fan/template_fan.cpp @@ -11,11 +11,11 @@ void TemplateFan::setup() { if (restore.has_value()) { restore->apply(*this); } -} -fan::FanTraits TemplateFan::get_traits() { - return fan::FanTraits(this->has_oscillating_, this->speed_count_ > 0, this->has_direction_, this->speed_count_, - &this->preset_modes_); + // Construct traits + this->traits_ = + fan::FanTraits(this->has_oscillating_, this->speed_count_ > 0, this->has_direction_, this->speed_count_); + this->traits_.set_supported_preset_modes(this->preset_modes_); } void TemplateFan::dump_config() { LOG_FAN("", "Template Fan", this); } diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h index affb313a2e..7f5305ca48 100644 --- a/esphome/components/template/fan/template_fan.h +++ b/esphome/components/template/fan/template_fan.h @@ -1,7 +1,8 @@ #pragma once +#include + #include "esphome/core/component.h" -#include "esphome/core/helpers.h" #include "esphome/components/fan/fan.h" namespace esphome { @@ -15,8 +16,8 @@ class TemplateFan : public Component, public fan::Fan { void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; } void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; } void set_speed_count(int count) { this->speed_count_ = count; } - void set_preset_modes(const std::initializer_list &presets) { this->preset_modes_ = presets; } - fan::FanTraits get_traits() override; + void set_preset_modes(const std::set &presets) { this->preset_modes_ = presets; } + fan::FanTraits get_traits() override { return this->traits_; } protected: void control(const fan::FanCall &call) override; @@ -24,7 +25,8 @@ class TemplateFan : public Component, public fan::Fan { bool has_oscillating_{false}; bool has_direction_{false}; int speed_count_{0}; - FixedVector preset_modes_{}; + fan::FanTraits traits_; + std::set preset_modes_{}; }; } // namespace template_ diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e84bb67aba..1d08ef5a35 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -723,7 +723,7 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { root["speed_level"] = obj->speed; root["speed_count"] = traits.supported_speed_count(); } - if (traits.supports_oscillation()) + if (obj->get_traits().supports_oscillation()) root["oscillation"] = obj->oscillating; if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj);