diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 19a71959aac..7e3df90889b 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -706,8 +706,11 @@ const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) { } const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) { - return this->supported_custom_fan_modes_ ? vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len) - : nullptr; + if (this->supported_custom_fan_modes_) { + return vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len); + } + // Fallback for deprecated path: external components may set modes on ClimateTraits directly + return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len); } const char *Climate::find_custom_preset_(const char *custom_preset) { @@ -715,7 +718,11 @@ const char *Climate::find_custom_preset_(const char *custom_preset) { } const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) { - return this->supported_custom_presets_ ? vector_find(*this->supported_custom_presets_, custom_preset, len) : nullptr; + if (this->supported_custom_presets_) { + return vector_find(*this->supported_custom_presets_, custom_preset, len); + } + // Fallback for deprecated path: external components may set modes on ClimateTraits directly + return this->get_traits().find_custom_preset_(custom_preset, len); } void Climate::dump_traits_(const char *tag) { diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 4b8a67cf6f0..fdca2638c28 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -159,18 +159,18 @@ class ClimateTraits { // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_fan_modes(std::initializer_list modes) { - this->owned_custom_modes_.fan_modes = modes; - this->supported_custom_fan_modes_ = &this->owned_custom_modes_.fan_modes; + // NOLINT - intentional leak: pointer must survive copies of ClimateTraits + this->supported_custom_fan_modes_ = new std::vector(modes); // NOLINT } // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_fan_modes(const std::vector &modes) { - this->owned_custom_modes_.fan_modes = modes; - this->supported_custom_fan_modes_ = &this->owned_custom_modes_.fan_modes; + this->supported_custom_fan_modes_ = new std::vector(modes); // NOLINT } const std::vector &get_supported_custom_fan_modes() const { - return this->supported_custom_fan_modes_ ? *this->supported_custom_fan_modes_ : this->owned_custom_modes_.fan_modes; + static const std::vector EMPTY_VECTOR; + return this->supported_custom_fan_modes_ ? *this->supported_custom_fan_modes_ : EMPTY_VECTOR; } bool supports_custom_fan_mode(const char *custom_fan_mode) const { return this->supported_custom_fan_modes_ && vector_contains(*this->supported_custom_fan_modes_, custom_fan_mode); @@ -192,18 +192,17 @@ class ClimateTraits { // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_presets(std::initializer_list presets) { - this->owned_custom_modes_.presets = presets; - this->supported_custom_presets_ = &this->owned_custom_modes_.presets; + this->supported_custom_presets_ = new std::vector(presets); // NOLINT } // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_presets(const std::vector &presets) { - this->owned_custom_modes_.presets = presets; - this->supported_custom_presets_ = &this->owned_custom_modes_.presets; + this->supported_custom_presets_ = new std::vector(presets); // NOLINT } const std::vector &get_supported_custom_presets() const { - return this->supported_custom_presets_ ? *this->supported_custom_presets_ : this->owned_custom_modes_.presets; + static const std::vector EMPTY_VECTOR; + return this->supported_custom_presets_ ? *this->supported_custom_presets_ : EMPTY_VECTOR; } bool supports_custom_preset(const char *custom_preset) const { return this->supported_custom_presets_ && vector_contains(*this->supported_custom_presets_, custom_preset); @@ -309,18 +308,6 @@ class ClimateTraits { */ const std::vector *supported_custom_fan_modes_{nullptr}; const std::vector *supported_custom_presets_{nullptr}; - /** Compat storage for deprecated setters — skipped on copy to avoid overhead. - * Remove in 2026.11.0 along with the deprecated overloads. - */ - struct OwnedCustomModes { - std::vector fan_modes; - std::vector presets; - OwnedCustomModes() = default; - OwnedCustomModes(const OwnedCustomModes &) {} // NOLINT - no-op copy: compat data is not propagated - OwnedCustomModes &operator=(const OwnedCustomModes &) { return *this; } // NOLINT - OwnedCustomModes(OwnedCustomModes &&) = default; - OwnedCustomModes &operator=(OwnedCustomModes &&) = default; - } owned_custom_modes_; }; } // namespace esphome::climate