1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-20 00:35:44 +00:00
This commit is contained in:
J. Nick Koston
2025-10-31 11:01:36 -05:00
parent 4e6d74c981
commit cd3f10630b
7 changed files with 125 additions and 41 deletions

View File

@@ -12,7 +12,11 @@ void CopyFan::setup() {
this->oscillating = source_->oscillating;
this->speed = source_->speed;
this->direction = source_->direction;
this->preset_mode = source_->preset_mode;
const char *preset = source_->get_preset_mode();
if (preset != nullptr)
this->set_preset_mode_(preset);
else
this->clear_preset_mode_();
this->publish_state();
});
@@ -20,7 +24,11 @@ void CopyFan::setup() {
this->oscillating = source_->oscillating;
this->speed = source_->speed;
this->direction = source_->direction;
this->preset_mode = source_->preset_mode;
const char *preset = source_->get_preset_mode();
if (preset != nullptr)
this->set_preset_mode_(preset);
else
this->clear_preset_mode_();
this->publish_state();
}
@@ -49,7 +57,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();
}

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() {
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%
@@ -120,7 +126,7 @@ void FanRestoreState::apply(Fan &fan) {
// Use stored preset index to get preset name
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 +137,36 @@ 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) {
const char *validated = this->find_preset_mode_(preset_mode);
if (validated == nullptr) {
return false; // Preset mode not supported
}
if (this->preset_mode_ == validated) {
return false; // No change
}
this->preset_mode_ = validated;
// Keep deprecated member in sync during deprecation period
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
this->preset_mode = validated;
#pragma GCC diagnostic pop
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;
// Keep deprecated member in sync during deprecation period
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
this->preset_mode.clear();
#pragma GCC diagnostic pop
}
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 +182,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 (traits.supports_preset_modes() && preset != nullptr) {
ESP_LOGD(TAG, " Preset Mode: %s", preset);
}
this->state_callback_.call();
this->save_state_();
@@ -199,12 +236,13 @@ 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 (traits.supports_preset_modes() && 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) {
if (strcmp(mode, preset) == 0) {
state.preset_mode = i;
break;
}

View File

@@ -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 {
@@ -113,7 +112,9 @@ class Fan : public EntityBase {
/// The current direction of the fan
FanDirection direction{FanDirection::FORWARD};
// The current preset mode of the fan
std::string preset_mode{};
// Deprecated: Use get_preset_mode() instead. Will be removed in 2026.5.0
std::string preset_mode {}
__attribute__((deprecated("Use get_preset_mode() instead of .preset_mode. Will be removed in 2026.5.0")));
FanCall turn_on();
FanCall turn_off();
@@ -130,6 +131,9 @@ 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_; }
protected:
friend FanCall;
@@ -140,9 +144,19 @@ 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_;
const char *preset_mode_{nullptr};
};
} // namespace fan

View File

@@ -1,5 +1,6 @@
#pragma once
#include <cstring>
#include <vector>
#include <initializer_list>
@@ -39,6 +40,17 @@ class FanTraits {
void set_supported_preset_modes(const std::vector<const char *> &preset_modes) { this->preset_modes_ = preset_modes; }
/// 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};

View File

@@ -51,13 +51,17 @@ void HBridgeFan::dump_config() {
void HBridgeFan::control(const fan::FanCall &call) {
if (call.get_state().has_value())
this->state = *call.get_state();
if (call.get_speed().has_value())
if (call.get_speed().has_value()) {
this->speed = *call.get_speed();
// Speed manually set, clear preset mode
this->clear_preset_mode_();
}
if (call.get_oscillating().has_value())
this->oscillating = *call.get_oscillating();
if (call.get_direction().has_value())
this->direction = *call.get_direction();
this->preset_mode = call.get_preset_mode();
if (call.has_preset_mode())
this->set_preset_mode_(call.get_preset_mode());
this->write_state_();
this->publish_state();

View File

@@ -23,13 +23,17 @@ void SpeedFan::dump_config() { LOG_FAN("", "Speed Fan", this); }
void SpeedFan::control(const fan::FanCall &call) {
if (call.get_state().has_value())
this->state = *call.get_state();
if (call.get_speed().has_value())
if (call.get_speed().has_value()) {
this->speed = *call.get_speed();
// Speed manually set, clear preset mode
this->clear_preset_mode_();
}
if (call.get_oscillating().has_value())
this->oscillating = *call.get_oscillating();
if (call.get_direction().has_value())
this->direction = *call.get_direction();
this->preset_mode = call.get_preset_mode();
if (call.has_preset_mode())
this->set_preset_mode_(call.get_preset_mode());
this->write_state_();
this->publish_state();

View File

@@ -23,13 +23,17 @@ void TemplateFan::dump_config() { LOG_FAN("", "Template Fan", this); }
void TemplateFan::control(const fan::FanCall &call) {
if (call.get_state().has_value())
this->state = *call.get_state();
if (call.get_speed().has_value() && (this->speed_count_ > 0))
if (call.get_speed().has_value() && (this->speed_count_ > 0)) {
this->speed = *call.get_speed();
// Speed manually set, clear preset mode
this->clear_preset_mode_();
}
if (call.get_oscillating().has_value() && this->has_oscillating_)
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();
if (call.has_preset_mode())
this->set_preset_mode_(call.get_preset_mode());
this->publish_state();
}