diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d202486cfa..34be6e4aa2 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -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 diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ed49498176..647dd47b89 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -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 *supported_preset_modes{}; + const FixedVector *supported_preset_modes{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 26065ed644..839b0d08cc 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -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 &&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, diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index b74187eb4a..901181903a 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -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; } diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 48509e5705..e0b64aa0fa 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -1,5 +1,5 @@ -#include #include +#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 supported_preset_modes() const { return this->preset_modes_; } - /// Set the preset modes supported by the fan. - void set_supported_preset_modes(const std::set &preset_modes) { this->preset_modes_ = preset_modes; } + const FixedVector &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 &preset_modes) { + this->preset_modes_ = preset_modes; + } + /// Set the preset modes supported by the fan (from FixedVector). + template 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 &supported_preset_modes_for_api_() const { return this->preset_modes_; } + const FixedVector &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 preset_modes_{}; + FixedVector preset_modes_{}; }; } // namespace fan diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 605a9d4ef3..c059783b1e 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -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() { diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index 4234fccae3..68458d7922 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -1,8 +1,7 @@ #pragma once -#include - #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 &presets) { preset_modes_ = presets; } + void set_preset_modes(const std::initializer_list &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 preset_modes_{}; + FixedVector preset_modes_{}; void control(const fan::FanCall &call) override; void write_state_(); diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 57bd795416..9205d3592b 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -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); } diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index 6537bce3f6..60c2267b04 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -1,8 +1,7 @@ #pragma once -#include - #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 &presets) { this->preset_modes_ = presets; } - fan::FanTraits get_traits() override { return this->traits_; } + void set_preset_modes(const std::initializer_list &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 preset_modes_{}; + FixedVector preset_modes_{}; }; } // namespace speed diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp index 5f4a2ae8f7..477e2c4981 100644 --- a/esphome/components/template/fan/template_fan.cpp +++ b/esphome/components/template/fan/template_fan.cpp @@ -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); } diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h index 7f5305ca48..5b175b21a4 100644 --- a/esphome/components/template/fan/template_fan.h +++ b/esphome/components/template/fan/template_fan.h @@ -1,8 +1,7 @@ #pragma once -#include - #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 &presets) { this->preset_modes_ = presets; } - fan::FanTraits get_traits() override { return this->traits_; } + void set_preset_modes(const std::initializer_list &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 preset_modes_{}; + FixedVector preset_modes_{}; }; } // namespace template_ diff --git a/tests/components/fan/common.yaml b/tests/components/fan/common.yaml new file mode 100644 index 0000000000..55c2a656fd --- /dev/null +++ b/tests/components/fan/common.yaml @@ -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 diff --git a/tests/components/fan/test.esp8266-ard.yaml b/tests/components/fan/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/fan/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml