diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp index 14c600d71f..e7d01827aa 100644 --- a/esphome/components/copy/fan/copy_fan.cpp +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -39,7 +39,8 @@ 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()); - traits.set_supported_preset_modes(base.supported_preset_modes()); + // Preset modes are wired from source fan's storage via get_traits() + this->set_supported_preset_modes(base.supported_preset_modes()); return traits; } diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index dc7a75018c..659abf70a9 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -148,7 +148,13 @@ const char *Fan::find_preset_mode_(const char *preset_mode) { } const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) { - return this->get_traits().find_preset_mode(preset_mode, len); + if (preset_mode == nullptr || len == 0) + return nullptr; + for (const char *mode : this->supported_preset_modes_) { + if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') + return mode; + } + return nullptr; } bool Fan::set_preset_mode_(const char *preset_mode, size_t len) { @@ -261,8 +267,6 @@ void Fan::save_state_() { return; } - auto traits = this->get_traits(); - FanRestoreState state{}; state.state = this->state; state.oscillating = this->oscillating; @@ -271,10 +275,9 @@ void Fan::save_state_() { state.preset_mode = FanRestoreState::NO_PRESET; if (this->has_preset_mode()) { - const auto &preset_modes = traits.supported_preset_modes(); - // Find index of current preset mode (pointer comparison is safe since preset is from traits) - for (size_t i = 0; i < preset_modes.size(); i++) { - if (preset_modes[i] == this->preset_mode_) { + // Find index of current preset mode (pointer comparison is safe since preset is from our vector) + for (size_t i = 0; i < this->supported_preset_modes_.size(); i++) { + if (this->supported_preset_modes_[i] == this->preset_mode_) { state.preset_mode = i; break; } diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index e7b3681e32..42999d16f7 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -130,6 +130,14 @@ class Fan : public EntityBase { virtual FanTraits get_traits() = 0; + /// Set the supported preset modes (stored on Fan, referenced by FanTraits via pointer). + void set_supported_preset_modes(std::initializer_list preset_modes) { + this->supported_preset_modes_ = preset_modes; + } + void set_supported_preset_modes(const std::vector &preset_modes) { + this->supported_preset_modes_ = preset_modes; + } + /// Set the restore mode of this fan. void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } @@ -171,6 +179,9 @@ class Fan : public EntityBase { ESPPreferenceObject rtc_; FanRestoreMode restore_mode_; + /// Preset mode storage — owned by Fan, referenced by FanTraits via pointer. + std::vector supported_preset_modes_; + private: const char *preset_mode_{nullptr}; }; diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index c0c5f34c50..f8202f36ce 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -3,6 +3,7 @@ #include #include #include +#include "esphome/core/helpers.h" namespace esphome { @@ -31,20 +32,29 @@ 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::vector &supported_preset_modes() const { return this->preset_modes_; } - /// Set the preset modes supported by the fan (from initializer list). - void set_supported_preset_modes(std::initializer_list preset_modes) { - this->preset_modes_ = preset_modes; + const std::vector &supported_preset_modes() const { + return this->preset_modes_ ? *this->preset_modes_ : this->owned_preset_modes_; } - /// Set the preset modes supported by the fan (from vector). - void set_supported_preset_modes(const std::vector &preset_modes) { this->preset_modes_ = preset_modes; } + /// Set the preset modes pointer (points to vector owned by Fan base class). + void set_supported_preset_modes(const std::vector *preset_modes) { this->preset_modes_ = preset_modes; } - // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages - void set_supported_preset_modes(const std::vector &preset_modes) = delete; - void set_supported_preset_modes(std::initializer_list preset_modes) = delete; + // Remove before 2027.1.0 + ESPDEPRECATED("Call set_supported_preset_modes() on the Fan entity instead. Removed in 2026.11.0", "2026.5.0") + void set_supported_preset_modes(std::initializer_list preset_modes) { + this->owned_preset_modes_ = preset_modes; + this->preset_modes_ = &this->owned_preset_modes_; + } + // Remove before 2027.1.0 + ESPDEPRECATED("Call set_supported_preset_modes() on the Fan entity instead. Removed in 2026.11.0", "2026.5.0") + void set_supported_preset_modes(const std::vector &preset_modes) { + this->owned_preset_modes_ = preset_modes; + this->preset_modes_ = &this->owned_preset_modes_; + } /// Return if preset modes are supported - bool supports_preset_modes() const { return !this->preset_modes_.empty(); } + bool supports_preset_modes() const { + return (this->preset_modes_ && !this->preset_modes_->empty()) || !this->owned_preset_modes_.empty(); + } /// Find and return the matching preset mode pointer from supported modes, or nullptr if not found. const char *find_preset_mode(const char *preset_mode) const { return this->find_preset_mode(preset_mode, preset_mode ? strlen(preset_mode) : 0); @@ -52,7 +62,8 @@ class FanTraits { const char *find_preset_mode(const char *preset_mode, size_t len) const { if (preset_mode == nullptr || len == 0) return nullptr; - for (const char *mode : this->preset_modes_) { + const auto &modes = this->preset_modes_ ? *this->preset_modes_ : this->owned_preset_modes_; + for (const char *mode : modes) { if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') { return mode; // Return pointer from traits } @@ -65,7 +76,8 @@ class FanTraits { bool speed_{false}; bool direction_{false}; int speed_count_{}; - std::vector preset_modes_{}; + const std::vector *preset_modes_{nullptr}; + std::vector owned_preset_modes_{}; ///< Compat: used when old setters are called directly on traits }; } // namespace fan diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 89c162eebf..d548128b99 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -30,7 +30,6 @@ fan::FanCall HBridgeFan::brake() { void HBridgeFan::setup() { // Construct traits before restore so preset modes can be looked up by index this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); - this->traits_.set_supported_preset_modes(this->preset_modes_); auto restore = this->restore_state_(); if (restore.has_value()) { diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index ec1e8ada0e..7511e0eb0b 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -20,7 +20,7 @@ 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(std::initializer_list presets) { preset_modes_ = presets; } + void set_preset_modes(std::initializer_list presets) { this->set_supported_preset_modes(presets); } void setup() override; void dump_config() override; @@ -36,7 +36,6 @@ class HBridgeFan : public Component, public fan::Fan { int speed_count_{}; DecayMode decay_mode_{DECAY_MODE_SLOW}; fan::FanTraits traits_; - std::vector 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 d45237c467..eaa8a55858 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -9,7 +9,6 @@ static const char *const TAG = "speed.fan"; void SpeedFan::setup() { // Construct traits before restore so preset modes can be looked up by index this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); - this->traits_.set_supported_preset_modes(this->preset_modes_); auto restore = this->restore_state_(); if (restore.has_value()) { diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index e9a389e0f3..44bd1a15cb 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -16,7 +16,7 @@ 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(std::initializer_list presets) { this->preset_modes_ = presets; } + void set_preset_modes(std::initializer_list presets) { this->set_supported_preset_modes(presets); } fan::FanTraits get_traits() override { return this->traits_; } protected: @@ -28,7 +28,6 @@ class SpeedFan : public Component, public fan::Fan { output::BinaryOutput *direction_{nullptr}; int speed_count_{}; fan::FanTraits traits_; - std::vector preset_modes_{}; }; } // namespace speed diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp index 46a5cba9bb..431be84654 100644 --- a/esphome/components/template/fan/template_fan.cpp +++ b/esphome/components/template/fan/template_fan.cpp @@ -9,7 +9,6 @@ void TemplateFan::setup() { // Construct traits before restore so preset modes can be looked up by index 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_); auto restore = this->restore_state_(); if (restore.has_value()) { diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h index b7e1d4ab5a..d8229ef047 100644 --- a/esphome/components/template/fan/template_fan.h +++ b/esphome/components/template/fan/template_fan.h @@ -13,7 +13,7 @@ class TemplateFan final : 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(std::initializer_list presets) { this->preset_modes_ = presets; } + void set_preset_modes(std::initializer_list presets) { this->set_supported_preset_modes(presets); } fan::FanTraits get_traits() override { return this->traits_; } protected: @@ -23,7 +23,6 @@ class TemplateFan final : public Component, public fan::Fan { bool has_direction_{false}; int speed_count_{0}; fan::FanTraits traits_; - std::vector preset_modes_{}; }; } // namespace esphome::template_