mirror of
https://github.com/esphome/esphome.git
synced 2025-11-17 23:35:47 +00:00
[fan] Remove duplicate preset mode storage to save RAM (#11632)
This commit is contained in:
@@ -410,8 +410,8 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
|
||||
}
|
||||
if (traits.supports_direction())
|
||||
msg.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
if (traits.supports_preset_modes())
|
||||
msg.set_preset_mode(StringRef(fan->preset_mode));
|
||||
if (traits.supports_preset_modes() && fan->has_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);
|
||||
}
|
||||
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
|
||||
@@ -12,7 +12,7 @@ void CopyFan::setup() {
|
||||
this->oscillating = source_->oscillating;
|
||||
this->speed = source_->speed;
|
||||
this->direction = source_->direction;
|
||||
this->preset_mode = source_->preset_mode;
|
||||
this->set_preset_mode_(source_->get_preset_mode());
|
||||
this->publish_state();
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ void CopyFan::setup() {
|
||||
this->oscillating = source_->oscillating;
|
||||
this->speed = source_->speed;
|
||||
this->direction = source_->direction;
|
||||
this->preset_mode = source_->preset_mode;
|
||||
this->set_preset_mode_(source_->get_preset_mode());
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ void CopyFan::control(const fan::FanCall &call) {
|
||||
call2.set_speed(*call.get_speed());
|
||||
if (call.get_direction().has_value())
|
||||
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.perform();
|
||||
}
|
||||
|
||||
@@ -212,18 +212,19 @@ class FanPresetSetTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *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_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
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:
|
||||
std::string last_preset_mode_;
|
||||
const char *last_preset_mode_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
||||
@@ -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() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str());
|
||||
this->validate_();
|
||||
@@ -32,8 +53,8 @@ void FanCall::perform() {
|
||||
if (this->direction_.has_value()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
|
||||
}
|
||||
if (!this->preset_mode_.empty()) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_.c_str());
|
||||
if (this->has_preset_mode()) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
}
|
||||
this->parent_.control(*this);
|
||||
}
|
||||
@@ -46,30 +67,15 @@ void FanCall::validate_() {
|
||||
|
||||
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
|
||||
// "Manually setting a speed must disable any set preset mode"
|
||||
this->preset_mode_.clear();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
this->preset_mode_ = nullptr;
|
||||
}
|
||||
|
||||
// when turning on...
|
||||
if (!this->parent_.state && this->binary_state_.has_value() &&
|
||||
*this->binary_state_
|
||||
// ..,and no preset mode will be active...
|
||||
&& this->preset_mode_.empty() &&
|
||||
this->parent_.preset_mode.empty()
|
||||
&& !this->has_preset_mode() &&
|
||||
this->parent_.get_preset_mode() == nullptr
|
||||
// ...and neither current nor new speed is available...
|
||||
&& traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) {
|
||||
// ...set speed to 100%
|
||||
@@ -117,12 +123,13 @@ void FanRestoreState::apply(Fan &fan) {
|
||||
|
||||
auto traits = fan.get_traits();
|
||||
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();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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::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::publish_state() {
|
||||
auto traits = this->get_traits();
|
||||
@@ -146,8 +176,9 @@ void Fan::publish_state() {
|
||||
if (traits.supports_direction()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
|
||||
}
|
||||
if (traits.supports_preset_modes() && !this->preset_mode.empty()) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode.c_str());
|
||||
const char *preset = this->get_preset_mode();
|
||||
if (preset != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", preset);
|
||||
}
|
||||
this->state_callback_.call();
|
||||
this->save_state_();
|
||||
@@ -199,16 +230,15 @@ void Fan::save_state_() {
|
||||
state.speed = this->speed;
|
||||
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();
|
||||
// Store index of current preset mode
|
||||
size_t i = 0;
|
||||
for (const auto &mode : preset_modes) {
|
||||
if (strcmp(mode, this->preset_mode.c_str()) == 0) {
|
||||
// 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] == preset) {
|
||||
state.preset_mode = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,11 +70,10 @@ class FanCall {
|
||||
return *this;
|
||||
}
|
||||
optional<FanDirection> get_direction() const { return this->direction_; }
|
||||
FanCall &set_preset_mode(const std::string &preset_mode) {
|
||||
this->preset_mode_ = preset_mode;
|
||||
return *this;
|
||||
}
|
||||
std::string get_preset_mode() const { return this->preset_mode_; }
|
||||
FanCall &set_preset_mode(const std::string &preset_mode);
|
||||
FanCall &set_preset_mode(const char *preset_mode);
|
||||
const char *get_preset_mode() const { return this->preset_mode_; }
|
||||
bool has_preset_mode() const { return this->preset_mode_ != nullptr; }
|
||||
|
||||
void perform();
|
||||
|
||||
@@ -86,7 +85,7 @@ class FanCall {
|
||||
optional<bool> oscillating_;
|
||||
optional<int> speed_;
|
||||
optional<FanDirection> direction_{};
|
||||
std::string preset_mode_{};
|
||||
const char *preset_mode_{nullptr}; // Pointer to string in traits (after validation)
|
||||
};
|
||||
|
||||
struct FanRestoreState {
|
||||
@@ -112,8 +111,6 @@ class Fan : public EntityBase {
|
||||
int speed{0};
|
||||
/// The current direction of the fan
|
||||
FanDirection direction{FanDirection::FORWARD};
|
||||
// The current preset mode of the fan
|
||||
std::string preset_mode{};
|
||||
|
||||
FanCall turn_on();
|
||||
FanCall turn_off();
|
||||
@@ -130,8 +127,15 @@ class Fan : public EntityBase {
|
||||
/// Set the restore mode of this fan.
|
||||
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:
|
||||
friend FanCall;
|
||||
friend struct FanRestoreState;
|
||||
|
||||
virtual void control(const FanCall &call) = 0;
|
||||
|
||||
@@ -140,9 +144,21 @@ class Fan : public EntityBase {
|
||||
|
||||
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_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
FanRestoreMode restore_mode_;
|
||||
|
||||
private:
|
||||
const char *preset_mode_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
@@ -44,6 +45,17 @@ class FanTraits {
|
||||
|
||||
/// Return if preset modes are supported
|
||||
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:
|
||||
bool oscillation_{false};
|
||||
|
||||
@@ -57,7 +57,7 @@ void HBridgeFan::control(const fan::FanCall &call) {
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
this->preset_mode = call.get_preset_mode();
|
||||
this->set_preset_mode_(call.get_preset_mode());
|
||||
|
||||
this->write_state_();
|
||||
this->publish_state();
|
||||
|
||||
@@ -29,7 +29,7 @@ void SpeedFan::control(const fan::FanCall &call) {
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
this->preset_mode = call.get_preset_mode();
|
||||
this->set_preset_mode_(call.get_preset_mode());
|
||||
|
||||
this->write_state_();
|
||||
this->publish_state();
|
||||
|
||||
@@ -29,7 +29,7 @@ void TemplateFan::control(const fan::FanCall &call) {
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value() && this->has_direction_)
|
||||
this->direction = *call.get_direction();
|
||||
this->preset_mode = call.get_preset_mode();
|
||||
this->set_preset_mode_(call.get_preset_mode());
|
||||
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user