diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index af34d28f7a8..124c2105bec 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -15,8 +15,14 @@ static const char *const TAG = "fan"; static const std::vector EMPTY_PRESET_MODES; // NOLINT const std::vector &FanTraits::supported_preset_modes() const { - // Compat: return empty ref when pointer is null. Remove in 2026.11.0 (change return to const vector *). - return this->preset_modes_ ? *this->preset_modes_ : EMPTY_PRESET_MODES; + if (this->preset_modes_) { + return *this->preset_modes_; + } + // Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0 (change return to const vector *). + if (!this->compat_preset_modes_.empty()) { + return this->compat_preset_modes_; + } + return EMPTY_PRESET_MODES; } // Fan direction strings indexed by FanDirection enum (0-1): FORWARD, REVERSE, plus UNKNOWN diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index b7d52aa15f1..26b34d2bc6c 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -175,6 +175,13 @@ class Fan : public EntityBase { const char *find_preset_mode_(const char *preset_mode); const char *find_preset_mode_(const char *preset_mode, size_t len); + /// Wire the Fan-owned preset modes pointer into the given traits object. + void wire_preset_modes_(FanTraits &traits) { + if (this->supported_preset_modes_) { + traits.set_supported_preset_modes(this->supported_preset_modes_); + } + } + LazyCallbackManager state_callback_{}; ESPPreferenceObject rtc_; FanRestoreMode restore_mode_; diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 0098088ae66..6a003f535fa 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -39,31 +39,32 @@ class FanTraits { // Remove before 2026.11.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) { - // NOLINT - intentional leak: pointer must survive copies of FanTraits - this->preset_modes_ = new std::vector(preset_modes); // NOLINT + // Compat: store in owned vector. Copies copy the vector (same cost as before this PR). + this->compat_preset_modes_ = preset_modes; } // Remove before 2026.11.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->preset_modes_ = new std::vector(preset_modes); // NOLINT + this->compat_preset_modes_ = preset_modes; } /// Return if preset modes are supported - bool supports_preset_modes() const { return this->preset_modes_ && !this->preset_modes_->empty(); } + bool supports_preset_modes() const { + return (this->preset_modes_ && !this->preset_modes_->empty()) || !this->compat_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); } const char *find_preset_mode(const char *preset_mode, size_t len) const { - if (preset_mode == nullptr || len == 0) - return nullptr; - if (!this->preset_modes_) { + if (preset_mode == nullptr || len == 0) { return nullptr; } - const auto &modes = *this->preset_modes_; + // Check pointer-based storage (new path) then compat owned vector (deprecated path) + const auto &modes = this->preset_modes_ ? *this->preset_modes_ : this->compat_preset_modes_; for (const char *mode : modes) { if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') { - return mode; // Return pointer from traits + return mode; } } return nullptr; @@ -75,6 +76,9 @@ class FanTraits { bool direction_{false}; int speed_count_{}; const std::vector *preset_modes_{nullptr}; + // Compat: owned storage for deprecated setters. Copies copy the vector (same cost as pre-PR). + // Remove in 2026.11.0. + std::vector compat_preset_modes_; }; } // namespace fan diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index 7511e0eb0bc..997f66ae486 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -24,7 +24,10 @@ class HBridgeFan : public Component, public fan::Fan { void setup() override; void dump_config() override; - fan::FanTraits get_traits() override { return this->traits_; } + fan::FanTraits get_traits() override { + this->wire_preset_modes_(this->traits_); + return this->traits_; + } fan::FanCall brake(); diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index 44bd1a15cb7..db96039a135 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -17,7 +17,10 @@ class SpeedFan : public Component, public fan::Fan { 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->set_supported_preset_modes(presets); } - fan::FanTraits get_traits() override { return this->traits_; } + fan::FanTraits get_traits() override { + this->wire_preset_modes_(this->traits_); + return this->traits_; + } protected: void control(const fan::FanCall &call) override; diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h index d8229ef047c..5ab6ae8c655 100644 --- a/esphome/components/template/fan/template_fan.h +++ b/esphome/components/template/fan/template_fan.h @@ -14,7 +14,10 @@ class TemplateFan final : public Component, public fan::Fan { 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->set_supported_preset_modes(presets); } - fan::FanTraits get_traits() override { return this->traits_; } + fan::FanTraits get_traits() override { + this->wire_preset_modes_(this->traits_); + return this->traits_; + } protected: void control(const fan::FanCall &call) override;