1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-27 21:23:48 +00:00

[fan] Use FixedVector for preset modes, preserve config order (breaking)

This commit is contained in:
J. Nick Koston
2025-10-22 11:03:32 -10:00
parent 1c67a61945
commit f559fad4fc
13 changed files with 70 additions and 38 deletions

View File

@@ -425,7 +425,7 @@ message ListEntitiesFanResponse {
bool disabled_by_default = 9;
string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 11;
repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"];
repeated string supported_preset_modes = 12 [(container_pointer) = "FixedVector"];
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
}
// Deprecated in API version 1.6 - only used in deprecated fields

View File

@@ -725,7 +725,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage {
bool supports_speed{false};
bool supports_direction{false};
int32_t supported_speed_count{0};
const std::set<std::string> *supported_preset_modes{};
const FixedVector<std::string> *supported_preset_modes{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -39,7 +39,7 @@ void FanCall::perform() {
}
void FanCall::validate_() {
auto traits = this->parent_.get_traits();
const auto &traits = this->parent_.get_traits();
if (this->speed_.has_value()) {
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
@@ -51,7 +51,15 @@ void FanCall::validate_() {
if (!this->preset_mode_.empty()) {
const auto &preset_modes = traits.supported_preset_modes();
if (preset_modes.find(this->preset_mode_) == preset_modes.end()) {
// Linear search is efficient for small preset mode lists (typically 2-5 items)
bool found = false;
for (const auto &mode : preset_modes) {
if (mode == this->preset_mode_) {
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();
}
@@ -96,7 +104,7 @@ FanCall FanRestoreState::to_call(Fan &fan) {
// Use stored preset index to get preset name
const auto &preset_modes = fan.get_traits().supported_preset_modes();
if (this->preset_mode < preset_modes.size()) {
call.set_preset_mode(*std::next(preset_modes.begin(), this->preset_mode));
call.set_preset_mode(preset_modes[this->preset_mode]);
}
}
return call;
@@ -111,7 +119,7 @@ void FanRestoreState::apply(Fan &fan) {
// Use stored preset index to get preset name
const auto &preset_modes = fan.get_traits().supported_preset_modes();
if (this->preset_mode < preset_modes.size()) {
fan.preset_mode = *std::next(preset_modes.begin(), this->preset_mode);
fan.preset_mode = preset_modes[this->preset_mode];
}
}
fan.publish_state();
@@ -124,7 +132,7 @@ FanCall Fan::make_call() { return FanCall(*this); }
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();
const auto &traits = this->get_traits();
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
ESP_LOGD(TAG, " State: %s", ONOFF(this->state));
@@ -190,17 +198,20 @@ void Fan::save_state_() {
if (this->get_traits().supports_preset_modes() && !this->preset_mode.empty()) {
const auto &preset_modes = this->get_traits().supported_preset_modes();
// Store index of current preset mode
auto preset_iterator = preset_modes.find(this->preset_mode);
if (preset_iterator != preset_modes.end())
state.preset_mode = std::distance(preset_modes.begin(), preset_iterator);
// Store index of current preset mode - linear search is efficient for small lists
for (size_t i = 0; i < preset_modes.size(); i++) {
if (preset_modes[i] == this->preset_mode) {
state.preset_mode = i;
break;
}
}
}
this->rtc_.save(&state);
}
void Fan::dump_traits_(const char *tag, const char *prefix) {
auto traits = this->get_traits();
const auto &traits = this->get_traits();
if (traits.supports_speed()) {
ESP_LOGCONFIG(tag,

View File

@@ -127,7 +127,7 @@ class Fan : public EntityBase {
void publish_state();
virtual FanTraits get_traits() = 0;
virtual const FanTraits &get_traits() = 0;
/// Set the restore mode of this fan.
void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }

View File

@@ -1,5 +1,5 @@
#include <set>
#include <utility>
#include "esphome/core/helpers.h"
#pragma once
@@ -36,9 +36,18 @@ 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.
std::set<std::string> supported_preset_modes() const { return this->preset_modes_; }
/// Set the preset modes supported by the fan.
void set_supported_preset_modes(const std::set<std::string> &preset_modes) { this->preset_modes_ = preset_modes; }
const FixedVector<std::string> &supported_preset_modes() const { return this->preset_modes_; }
/// Set the preset modes supported by the fan (from initializer list).
void set_supported_preset_modes(const std::initializer_list<std::string> &preset_modes) {
this->preset_modes_ = preset_modes;
}
/// Set the preset modes supported by the fan (from FixedVector).
template<typename T> void set_supported_preset_modes(const T &preset_modes) {
this->preset_modes_.init(preset_modes.size());
for (const auto &mode : preset_modes) {
this->preset_modes_.push_back(mode);
}
}
/// Return if preset modes are supported
bool supports_preset_modes() const { return !this->preset_modes_.empty(); }
@@ -46,17 +55,17 @@ class FanTraits {
#ifdef USE_API
// The API connection is a friend class to access internal methods
friend class api::APIConnection;
// This method returns a reference to the internal preset modes set.
// This method returns a reference to the internal preset modes.
// It is used by the API to avoid copying data when encoding messages.
// Warning: Do not use this method outside of the API connection code.
// It returns a reference to internal data that can be invalidated.
const std::set<std::string> &supported_preset_modes_for_api_() const { return this->preset_modes_; }
const FixedVector<std::string> &supported_preset_modes_for_api_() const { return this->preset_modes_; }
#endif
bool oscillation_{false};
bool speed_{false};
bool direction_{false};
int speed_count_{};
std::set<std::string> preset_modes_{};
FixedVector<std::string> preset_modes_{};
};
} // namespace fan

View File

@@ -36,7 +36,8 @@ void HBridgeFan::setup() {
// Construct traits
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
this->traits_.set_supported_preset_modes(this->preset_modes_);
if (!this->preset_modes_.empty())
this->traits_.set_supported_preset_modes(this->preset_modes_);
}
void HBridgeFan::dump_config() {

View File

@@ -1,8 +1,7 @@
#pragma once
#include <set>
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/output/float_output.h"
#include "esphome/components/fan/fan.h"
@@ -22,11 +21,11 @@ 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(const std::set<std::string> &presets) { preset_modes_ = presets; }
void set_preset_modes(const std::initializer_list<std::string> &presets) { preset_modes_ = presets; }
void setup() override;
void dump_config() override;
fan::FanTraits get_traits() override { return this->traits_; }
const fan::FanTraits &get_traits() override { return this->traits_; }
fan::FanCall brake();
@@ -38,7 +37,7 @@ class HBridgeFan : public Component, public fan::Fan {
int speed_count_{};
DecayMode decay_mode_{DECAY_MODE_SLOW};
fan::FanTraits traits_;
std::set<std::string> preset_modes_{};
FixedVector<std::string> preset_modes_{};
void control(const fan::FanCall &call) override;
void write_state_();

View File

@@ -15,7 +15,8 @@ void SpeedFan::setup() {
// Construct traits
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_);
this->traits_.set_supported_preset_modes(this->preset_modes_);
if (!this->preset_modes_.empty())
this->traits_.set_supported_preset_modes(this->preset_modes_);
}
void SpeedFan::dump_config() { LOG_FAN("", "Speed Fan", this); }

View File

@@ -1,8 +1,7 @@
#pragma once
#include <set>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/output/float_output.h"
#include "esphome/components/fan/fan.h"
@@ -18,8 +17,8 @@ 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(const std::set<std::string> &presets) { this->preset_modes_ = presets; }
fan::FanTraits get_traits() override { return this->traits_; }
void set_preset_modes(const std::initializer_list<std::string> &presets) { this->preset_modes_ = presets; }
const fan::FanTraits &get_traits() override { return this->traits_; }
protected:
void control(const fan::FanCall &call) override;
@@ -30,7 +29,7 @@ class SpeedFan : public Component, public fan::Fan {
output::BinaryOutput *direction_{nullptr};
int speed_count_{};
fan::FanTraits traits_;
std::set<std::string> preset_modes_{};
FixedVector<std::string> preset_modes_{};
};
} // namespace speed

View File

@@ -15,7 +15,8 @@ void TemplateFan::setup() {
// Construct traits
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_);
if (!this->preset_modes_.empty())
this->traits_.set_supported_preset_modes(this->preset_modes_);
}
void TemplateFan::dump_config() { LOG_FAN("", "Template Fan", this); }

View File

@@ -1,8 +1,7 @@
#pragma once
#include <set>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/fan/fan.h"
namespace esphome {
@@ -16,8 +15,8 @@ class TemplateFan : 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(const std::set<std::string> &presets) { this->preset_modes_ = presets; }
fan::FanTraits get_traits() override { return this->traits_; }
void set_preset_modes(const std::initializer_list<std::string> &presets) { this->preset_modes_ = presets; }
const fan::FanTraits &get_traits() override { return this->traits_; }
protected:
void control(const fan::FanCall &call) override;
@@ -26,7 +25,7 @@ class TemplateFan : public Component, public fan::Fan {
bool has_direction_{false};
int speed_count_{0};
fan::FanTraits traits_;
std::set<std::string> preset_modes_{};
FixedVector<std::string> preset_modes_{};
};
} // namespace template_

View File

@@ -0,0 +1,11 @@
fan:
- platform: template
id: test_fan
name: "Test Fan"
preset_modes:
- Eco
- Sleep
- Turbo
has_oscillating: true
has_direction: true
speed_count: 3

View File

@@ -0,0 +1 @@
<<: !include common.yaml