From 7c259d2f6426c835c98cc6fbe27b8ebe07f609af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 26 Mar 2026 15:39:29 -1000 Subject: [PATCH] Simplify save_state_ compat path, move CopyFan preset copy to setup() - save_state_(): split into two clear branches instead of conditional FanTraits construction trick - CopyFan: copy source preset modes once in setup() instead of on every get_traits() call --- esphome/components/copy/fan/copy_fan.cpp | 10 +++++++-- esphome/components/fan/fan.cpp | 26 ++++++++++++++++-------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp index e7d01827aa..bdaa35c467 100644 --- a/esphome/components/copy/fan/copy_fan.cpp +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -7,6 +7,12 @@ namespace copy { static const char *const TAG = "copy.fan"; void CopyFan::setup() { + // Copy preset modes once from source fan — stored on Fan base class + auto source_traits = source_->get_traits(); + if (source_traits.supports_preset_modes()) { + this->set_supported_preset_modes(source_traits.supported_preset_modes()); + } + source_->add_on_state_callback([this]() { this->copy_state_from_source_(); this->publish_state(); @@ -39,8 +45,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()); - // Preset modes are wired from source fan's storage via get_traits() - this->set_supported_preset_modes(base.supported_preset_modes()); + // Preset modes are set once in setup() and wired via wire_preset_modes_() + this->wire_preset_modes_(traits); return traits; } diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index fb9654da5b..563c0eff13 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -297,15 +297,23 @@ void Fan::save_state_() { state.preset_mode = FanRestoreState::NO_PRESET; if (this->has_preset_mode()) { - // Use Fan-owned vector, or fall back to traits for deprecated path. - // Keep traits alive so the reference to compat_preset_modes_ doesn't dangle. - auto traits = this->supported_preset_modes_ ? FanTraits() : this->get_traits(); - const auto &preset_modes = - this->supported_preset_modes_ ? *this->supported_preset_modes_ : traits.supported_preset_modes(); - for (size_t i = 0; i < preset_modes.size(); i++) { - if (preset_modes[i] == this->preset_mode_) { - state.preset_mode = i; - break; + if (this->supported_preset_modes_) { + // New path: search Fan-owned vector directly + 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; + } + } + } else { + // Compat: fall back to traits for deprecated path. Remove in 2026.11.0. + auto traits = this->get_traits(); + const auto &preset_modes = traits.supported_preset_modes(); + for (size_t i = 0; i < preset_modes.size(); i++) { + if (preset_modes[i] == this->preset_mode_) { + state.preset_mode = i; + break; + } } } }