mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 23:37:34 +00:00
[fan] Store preset mode vector on Fan entity, not FanTraits
FanTraits contained a std::vector<const char *> for preset modes. Every get_traits() call reconstructed this vector, causing heap allocations on every publish_state() and control()/perform() call. Move the vector storage to the Fan base class and have FanTraits hold a const pointer instead. Internal callers (save_state_, find_preset_mode_) search the Fan-owned vector directly, avoiding traits reconstruction. The old FanTraits setters are preserved as deprecated compatibility overloads (removed in 2026.11.0) that self-own the data, so external components continue to compile — they just get a deprecation warning and still heap-allocate until they migrate.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<const char *> preset_modes) {
|
||||
this->supported_preset_modes_ = preset_modes;
|
||||
}
|
||||
void set_supported_preset_modes(const std::vector<const char *> &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<const char *> supported_preset_modes_;
|
||||
|
||||
private:
|
||||
const char *preset_mode_{nullptr};
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
#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<const char *> &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<const char *> preset_modes) {
|
||||
this->preset_modes_ = preset_modes;
|
||||
const std::vector<const char *> &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<const char *> &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<const char *> *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<std::string> &preset_modes) = delete;
|
||||
void set_supported_preset_modes(std::initializer_list<std::string> 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<const char *> 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<const char *> &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<const char *> preset_modes_{};
|
||||
const std::vector<const char *> *preset_modes_{nullptr};
|
||||
std::vector<const char *> owned_preset_modes_{}; ///< Compat: used when old setters are called directly on traits
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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<const char *> presets) { preset_modes_ = presets; }
|
||||
void set_preset_modes(std::initializer_list<const char *> 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<const char *> preset_modes_{};
|
||||
|
||||
void control(const fan::FanCall &call) override;
|
||||
void write_state_();
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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<const char *> presets) { this->preset_modes_ = presets; }
|
||||
void set_preset_modes(std::initializer_list<const char *> 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<const char *> preset_modes_{};
|
||||
};
|
||||
|
||||
} // namespace speed
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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<const char *> presets) { this->preset_modes_ = presets; }
|
||||
void set_preset_modes(std::initializer_list<const char *> 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<const char *> preset_modes_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::template_
|
||||
|
||||
Reference in New Issue
Block a user