1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-18 15:55:46 +00:00

[fan] Remove duplicate preset mode storage to save RAM (#11632)

This commit is contained in:
J. Nick Koston
2025-11-04 15:55:37 -06:00
committed by GitHub
parent 531b27582a
commit 885508775f
9 changed files with 109 additions and 50 deletions

View File

@@ -410,8 +410,8 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
} }
if (traits.supports_direction()) if (traits.supports_direction())
msg.direction = static_cast<enums::FanDirection>(fan->direction); msg.direction = static_cast<enums::FanDirection>(fan->direction);
if (traits.supports_preset_modes()) if (traits.supports_preset_modes() && fan->has_preset_mode())
msg.set_preset_mode(StringRef(fan->preset_mode)); msg.set_preset_mode(StringRef(fan->get_preset_mode()));
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,

View File

@@ -12,7 +12,7 @@ void CopyFan::setup() {
this->oscillating = source_->oscillating; this->oscillating = source_->oscillating;
this->speed = source_->speed; this->speed = source_->speed;
this->direction = source_->direction; this->direction = source_->direction;
this->preset_mode = source_->preset_mode; this->set_preset_mode_(source_->get_preset_mode());
this->publish_state(); this->publish_state();
}); });
@@ -20,7 +20,7 @@ void CopyFan::setup() {
this->oscillating = source_->oscillating; this->oscillating = source_->oscillating;
this->speed = source_->speed; this->speed = source_->speed;
this->direction = source_->direction; this->direction = source_->direction;
this->preset_mode = source_->preset_mode; this->set_preset_mode_(source_->get_preset_mode());
this->publish_state(); this->publish_state();
} }
@@ -49,7 +49,7 @@ void CopyFan::control(const fan::FanCall &call) {
call2.set_speed(*call.get_speed()); call2.set_speed(*call.get_speed());
if (call.get_direction().has_value()) if (call.get_direction().has_value())
call2.set_direction(*call.get_direction()); call2.set_direction(*call.get_direction());
if (!call.get_preset_mode().empty()) if (call.has_preset_mode())
call2.set_preset_mode(call.get_preset_mode()); call2.set_preset_mode(call.get_preset_mode());
call2.perform(); call2.perform();
} }

View File

@@ -212,18 +212,19 @@ class FanPresetSetTrigger : public Trigger<std::string> {
public: public:
FanPresetSetTrigger(Fan *state) { FanPresetSetTrigger(Fan *state) {
state->add_on_state_callback([this, state]() { state->add_on_state_callback([this, state]() {
auto preset_mode = state->preset_mode; const auto *preset_mode = state->get_preset_mode();
auto should_trigger = preset_mode != this->last_preset_mode_; auto should_trigger = preset_mode != this->last_preset_mode_;
this->last_preset_mode_ = preset_mode; this->last_preset_mode_ = preset_mode;
if (should_trigger) { if (should_trigger) {
this->trigger(preset_mode); // Trigger with empty string when nullptr to maintain backward compatibility
this->trigger(preset_mode != nullptr ? preset_mode : "");
} }
}); });
this->last_preset_mode_ = state->preset_mode; this->last_preset_mode_ = state->get_preset_mode();
} }
protected: protected:
std::string last_preset_mode_; const char *last_preset_mode_{nullptr};
}; };
} // namespace fan } // namespace fan

View File

@@ -17,6 +17,27 @@ const LogString *fan_direction_to_string(FanDirection direction) {
} }
} }
FanCall &FanCall::set_preset_mode(const std::string &preset_mode) { return this->set_preset_mode(preset_mode.c_str()); }
FanCall &FanCall::set_preset_mode(const char *preset_mode) {
if (preset_mode == nullptr || strlen(preset_mode) == 0) {
this->preset_mode_ = nullptr;
return *this;
}
// Find and validate pointer from traits immediately
auto traits = this->parent_.get_traits();
const char *validated_mode = traits.find_preset_mode(preset_mode);
if (validated_mode != nullptr) {
this->preset_mode_ = validated_mode; // Store pointer from traits
} else {
// Preset mode not found in traits - log warning and don't set
ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), preset_mode);
this->preset_mode_ = nullptr;
}
return *this;
}
void FanCall::perform() { void FanCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str()); ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str());
this->validate_(); this->validate_();
@@ -32,8 +53,8 @@ void FanCall::perform() {
if (this->direction_.has_value()) { if (this->direction_.has_value()) {
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_))); ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
} }
if (!this->preset_mode_.empty()) { if (this->has_preset_mode()) {
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_.c_str()); ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
} }
this->parent_.control(*this); this->parent_.control(*this);
} }
@@ -46,30 +67,15 @@ void FanCall::validate_() {
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes // https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
// "Manually setting a speed must disable any set preset mode" // "Manually setting a speed must disable any set preset mode"
this->preset_mode_.clear(); this->preset_mode_ = nullptr;
}
if (!this->preset_mode_.empty()) {
const auto &preset_modes = traits.supported_preset_modes();
bool found = false;
for (const auto &mode : preset_modes) {
if (strcmp(mode, this->preset_mode_.c_str()) == 0) {
found = true;
break;
}
}
if (!found) {
ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), this->preset_mode_.c_str());
this->preset_mode_.clear();
}
} }
// when turning on... // when turning on...
if (!this->parent_.state && this->binary_state_.has_value() && if (!this->parent_.state && this->binary_state_.has_value() &&
*this->binary_state_ *this->binary_state_
// ..,and no preset mode will be active... // ..,and no preset mode will be active...
&& this->preset_mode_.empty() && && !this->has_preset_mode() &&
this->parent_.preset_mode.empty() this->parent_.get_preset_mode() == nullptr
// ...and neither current nor new speed is available... // ...and neither current nor new speed is available...
&& traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) { && traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) {
// ...set speed to 100% // ...set speed to 100%
@@ -117,12 +123,13 @@ void FanRestoreState::apply(Fan &fan) {
auto traits = fan.get_traits(); auto traits = fan.get_traits();
if (traits.supports_preset_modes()) { if (traits.supports_preset_modes()) {
// Use stored preset index to get preset name // Use stored preset index to get preset name from traits
const auto &preset_modes = traits.supported_preset_modes(); const auto &preset_modes = traits.supported_preset_modes();
if (this->preset_mode < preset_modes.size()) { if (this->preset_mode < preset_modes.size()) {
fan.preset_mode = preset_modes[this->preset_mode]; fan.set_preset_mode_(preset_modes[this->preset_mode]);
} }
} }
fan.publish_state(); fan.publish_state();
} }
@@ -131,6 +138,29 @@ FanCall Fan::turn_off() { return this->make_call().set_state(false); }
FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } FanCall Fan::toggle() { return this->make_call().set_state(!this->state); }
FanCall Fan::make_call() { return FanCall(*this); } FanCall Fan::make_call() { return FanCall(*this); }
const char *Fan::find_preset_mode_(const char *preset_mode) { return this->get_traits().find_preset_mode(preset_mode); }
bool Fan::set_preset_mode_(const char *preset_mode) {
if (preset_mode == nullptr) {
// Treat nullptr as clearing the preset mode
if (this->preset_mode_ == nullptr) {
return false; // No change
}
this->clear_preset_mode_();
return true;
}
const char *validated = this->find_preset_mode_(preset_mode);
if (validated == nullptr || this->preset_mode_ == validated) {
return false; // Preset mode not supported or no change
}
this->preset_mode_ = validated;
return true;
}
bool Fan::set_preset_mode_(const std::string &preset_mode) { return this->set_preset_mode_(preset_mode.c_str()); }
void Fan::clear_preset_mode_() { this->preset_mode_ = nullptr; }
void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); } void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
void Fan::publish_state() { void Fan::publish_state() {
auto traits = this->get_traits(); auto traits = this->get_traits();
@@ -146,8 +176,9 @@ void Fan::publish_state() {
if (traits.supports_direction()) { if (traits.supports_direction()) {
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction))); ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
} }
if (traits.supports_preset_modes() && !this->preset_mode.empty()) { const char *preset = this->get_preset_mode();
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode.c_str()); if (preset != nullptr) {
ESP_LOGD(TAG, " Preset Mode: %s", preset);
} }
this->state_callback_.call(); this->state_callback_.call();
this->save_state_(); this->save_state_();
@@ -199,16 +230,15 @@ void Fan::save_state_() {
state.speed = this->speed; state.speed = this->speed;
state.direction = this->direction; state.direction = this->direction;
if (traits.supports_preset_modes() && !this->preset_mode.empty()) { const char *preset = this->get_preset_mode();
if (preset != nullptr) {
const auto &preset_modes = traits.supported_preset_modes(); const auto &preset_modes = traits.supported_preset_modes();
// Store index of current preset mode // Find index of current preset mode (pointer comparison is safe since preset is from traits)
size_t i = 0; for (size_t i = 0; i < preset_modes.size(); i++) {
for (const auto &mode : preset_modes) { if (preset_modes[i] == preset) {
if (strcmp(mode, this->preset_mode.c_str()) == 0) {
state.preset_mode = i; state.preset_mode = i;
break; break;
} }
i++;
} }
} }

View File

@@ -70,11 +70,10 @@ class FanCall {
return *this; return *this;
} }
optional<FanDirection> get_direction() const { return this->direction_; } optional<FanDirection> get_direction() const { return this->direction_; }
FanCall &set_preset_mode(const std::string &preset_mode) { FanCall &set_preset_mode(const std::string &preset_mode);
this->preset_mode_ = preset_mode; FanCall &set_preset_mode(const char *preset_mode);
return *this; const char *get_preset_mode() const { return this->preset_mode_; }
} bool has_preset_mode() const { return this->preset_mode_ != nullptr; }
std::string get_preset_mode() const { return this->preset_mode_; }
void perform(); void perform();
@@ -86,7 +85,7 @@ class FanCall {
optional<bool> oscillating_; optional<bool> oscillating_;
optional<int> speed_; optional<int> speed_;
optional<FanDirection> direction_{}; optional<FanDirection> direction_{};
std::string preset_mode_{}; const char *preset_mode_{nullptr}; // Pointer to string in traits (after validation)
}; };
struct FanRestoreState { struct FanRestoreState {
@@ -112,8 +111,6 @@ class Fan : public EntityBase {
int speed{0}; int speed{0};
/// The current direction of the fan /// The current direction of the fan
FanDirection direction{FanDirection::FORWARD}; FanDirection direction{FanDirection::FORWARD};
// The current preset mode of the fan
std::string preset_mode{};
FanCall turn_on(); FanCall turn_on();
FanCall turn_off(); FanCall turn_off();
@@ -130,8 +127,15 @@ class Fan : public EntityBase {
/// Set the restore mode of this fan. /// Set the restore mode of this fan.
void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
/// Get the current preset mode (returns pointer to string stored in traits, or nullptr if not set)
const char *get_preset_mode() const { return this->preset_mode_; }
/// Check if a preset mode is currently active
bool has_preset_mode() const { return this->preset_mode_ != nullptr; }
protected: protected:
friend FanCall; friend FanCall;
friend struct FanRestoreState;
virtual void control(const FanCall &call) = 0; virtual void control(const FanCall &call) = 0;
@@ -140,9 +144,21 @@ class Fan : public EntityBase {
void dump_traits_(const char *tag, const char *prefix); void dump_traits_(const char *tag, const char *prefix);
/// Set the preset mode (finds and stores pointer from traits). Returns true if changed.
bool set_preset_mode_(const char *preset_mode);
/// Set the preset mode (finds and stores pointer from traits). Returns true if changed.
bool set_preset_mode_(const std::string &preset_mode);
/// Clear the preset mode
void clear_preset_mode_();
/// Find and return the matching preset mode pointer from traits, or nullptr if not found.
const char *find_preset_mode_(const char *preset_mode);
CallbackManager<void()> state_callback_{}; CallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
FanRestoreMode restore_mode_; FanRestoreMode restore_mode_;
private:
const char *preset_mode_{nullptr};
}; };
} // namespace fan } // namespace fan

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstring>
#include <vector> #include <vector>
#include <initializer_list> #include <initializer_list>
@@ -44,6 +45,17 @@ class FanTraits {
/// Return if preset modes are supported /// 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_.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 {
if (preset_mode == nullptr)
return nullptr;
for (const char *mode : this->preset_modes_) {
if (strcmp(mode, preset_mode) == 0) {
return mode; // Return pointer from traits
}
}
return nullptr;
}
protected: protected:
bool oscillation_{false}; bool oscillation_{false};

View File

@@ -57,7 +57,7 @@ void HBridgeFan::control(const fan::FanCall &call) {
this->oscillating = *call.get_oscillating(); this->oscillating = *call.get_oscillating();
if (call.get_direction().has_value()) if (call.get_direction().has_value())
this->direction = *call.get_direction(); this->direction = *call.get_direction();
this->preset_mode = call.get_preset_mode(); this->set_preset_mode_(call.get_preset_mode());
this->write_state_(); this->write_state_();
this->publish_state(); this->publish_state();

View File

@@ -29,7 +29,7 @@ void SpeedFan::control(const fan::FanCall &call) {
this->oscillating = *call.get_oscillating(); this->oscillating = *call.get_oscillating();
if (call.get_direction().has_value()) if (call.get_direction().has_value())
this->direction = *call.get_direction(); this->direction = *call.get_direction();
this->preset_mode = call.get_preset_mode(); this->set_preset_mode_(call.get_preset_mode());
this->write_state_(); this->write_state_();
this->publish_state(); this->publish_state();

View File

@@ -29,7 +29,7 @@ void TemplateFan::control(const fan::FanCall &call) {
this->oscillating = *call.get_oscillating(); this->oscillating = *call.get_oscillating();
if (call.get_direction().has_value() && this->has_direction_) if (call.get_direction().has_value() && this->has_direction_)
this->direction = *call.get_direction(); this->direction = *call.get_direction();
this->preset_mode = call.get_preset_mode(); this->set_preset_mode_(call.get_preset_mode());
this->publish_state(); this->publish_state();
} }