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:
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user