mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	[climate] Replace std::set with FiniteSetMask for trait storage (#11466)
This commit is contained in:
		| @@ -989,7 +989,7 @@ message ListEntitiesClimateResponse { | |||||||
|  |  | ||||||
|   bool supports_current_temperature = 5; // Deprecated: use feature_flags |   bool supports_current_temperature = 5; // Deprecated: use feature_flags | ||||||
|   bool supports_two_point_target_temperature = 6; // Deprecated: use feature_flags |   bool supports_two_point_target_temperature = 6; // Deprecated: use feature_flags | ||||||
|   repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"]; |   repeated ClimateMode supported_modes = 7 [(container_pointer_no_template) = "climate::ClimateModeMask"]; | ||||||
|   float visual_min_temperature = 8; |   float visual_min_temperature = 8; | ||||||
|   float visual_max_temperature = 9; |   float visual_max_temperature = 9; | ||||||
|   float visual_target_temperature_step = 10; |   float visual_target_temperature_step = 10; | ||||||
| @@ -998,11 +998,11 @@ message ListEntitiesClimateResponse { | |||||||
|   // Deprecated in API version 1.5 |   // Deprecated in API version 1.5 | ||||||
|   bool legacy_supports_away = 11 [deprecated=true]; |   bool legacy_supports_away = 11 [deprecated=true]; | ||||||
|   bool supports_action = 12; // Deprecated: use feature_flags |   bool supports_action = 12; // Deprecated: use feature_flags | ||||||
|   repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"]; |   repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer_no_template) = "climate::ClimateFanModeMask"]; | ||||||
|   repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"]; |   repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer_no_template) = "climate::ClimateSwingModeMask"]; | ||||||
|   repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"]; |   repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::vector"]; | ||||||
|   repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set<climate::ClimatePreset>"]; |   repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"]; | ||||||
|   repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"]; |   repeated string supported_custom_presets = 17 [(container_pointer) = "std::vector"]; | ||||||
|   bool disabled_by_default = 18; |   bool disabled_by_default = 18; | ||||||
|   string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"]; |   string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"]; | ||||||
|   EntityCategory entity_category = 20; |   EntityCategory entity_category = 20; | ||||||
|   | |||||||
| @@ -669,18 +669,18 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection | |||||||
|   msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION); |   msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION); | ||||||
|   // Current feature flags and other supported parameters |   // Current feature flags and other supported parameters | ||||||
|   msg.feature_flags = traits.get_feature_flags(); |   msg.feature_flags = traits.get_feature_flags(); | ||||||
|   msg.supported_modes = &traits.get_supported_modes_for_api_(); |   msg.supported_modes = &traits.get_supported_modes(); | ||||||
|   msg.visual_min_temperature = traits.get_visual_min_temperature(); |   msg.visual_min_temperature = traits.get_visual_min_temperature(); | ||||||
|   msg.visual_max_temperature = traits.get_visual_max_temperature(); |   msg.visual_max_temperature = traits.get_visual_max_temperature(); | ||||||
|   msg.visual_target_temperature_step = traits.get_visual_target_temperature_step(); |   msg.visual_target_temperature_step = traits.get_visual_target_temperature_step(); | ||||||
|   msg.visual_current_temperature_step = traits.get_visual_current_temperature_step(); |   msg.visual_current_temperature_step = traits.get_visual_current_temperature_step(); | ||||||
|   msg.visual_min_humidity = traits.get_visual_min_humidity(); |   msg.visual_min_humidity = traits.get_visual_min_humidity(); | ||||||
|   msg.visual_max_humidity = traits.get_visual_max_humidity(); |   msg.visual_max_humidity = traits.get_visual_max_humidity(); | ||||||
|   msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_(); |   msg.supported_fan_modes = &traits.get_supported_fan_modes(); | ||||||
|   msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_(); |   msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes(); | ||||||
|   msg.supported_presets = &traits.get_supported_presets_for_api_(); |   msg.supported_presets = &traits.get_supported_presets(); | ||||||
|   msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_(); |   msg.supported_custom_presets = &traits.get_supported_custom_presets(); | ||||||
|   msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_(); |   msg.supported_swing_modes = &traits.get_supported_swing_modes(); | ||||||
|   return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, |   return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, | ||||||
|                                      is_single); |                                      is_single); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1377,16 +1377,16 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage { | |||||||
| #endif | #endif | ||||||
|   bool supports_current_temperature{false}; |   bool supports_current_temperature{false}; | ||||||
|   bool supports_two_point_target_temperature{false}; |   bool supports_two_point_target_temperature{false}; | ||||||
|   const std::set<climate::ClimateMode> *supported_modes{}; |   const climate::ClimateModeMask *supported_modes{}; | ||||||
|   float visual_min_temperature{0.0f}; |   float visual_min_temperature{0.0f}; | ||||||
|   float visual_max_temperature{0.0f}; |   float visual_max_temperature{0.0f}; | ||||||
|   float visual_target_temperature_step{0.0f}; |   float visual_target_temperature_step{0.0f}; | ||||||
|   bool supports_action{false}; |   bool supports_action{false}; | ||||||
|   const std::set<climate::ClimateFanMode> *supported_fan_modes{}; |   const climate::ClimateFanModeMask *supported_fan_modes{}; | ||||||
|   const std::set<climate::ClimateSwingMode> *supported_swing_modes{}; |   const climate::ClimateSwingModeMask *supported_swing_modes{}; | ||||||
|   const std::set<std::string> *supported_custom_fan_modes{}; |   const std::vector<std::string> *supported_custom_fan_modes{}; | ||||||
|   const std::set<climate::ClimatePreset> *supported_presets{}; |   const climate::ClimatePresetMask *supported_presets{}; | ||||||
|   const std::set<std::string> *supported_custom_presets{}; |   const std::vector<std::string> *supported_custom_presets{}; | ||||||
|   float visual_current_temperature_step{0.0f}; |   float visual_current_temperature_step{0.0f}; | ||||||
|   bool supports_current_humidity{false}; |   bool supports_current_humidity{false}; | ||||||
|   bool supports_target_humidity{false}; |   bool supports_target_humidity{false}; | ||||||
|   | |||||||
| @@ -99,9 +99,8 @@ enum BedjetCommand : uint8_t { | |||||||
|  |  | ||||||
| static const uint8_t BEDJET_FAN_SPEED_COUNT = 20; | static const uint8_t BEDJET_FAN_SPEED_COUNT = 20; | ||||||
|  |  | ||||||
| static const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; | static constexpr const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; | ||||||
| static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; | static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; | ||||||
| static const std::set<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_; |  | ||||||
|  |  | ||||||
| }  // namespace bedjet | }  // namespace bedjet | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     // It would be better if we had a slider for the fan modes. |     // It would be better if we had a slider for the fan modes. | ||||||
|     traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES_SET); |     traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES); | ||||||
|     traits.set_supported_presets({ |     traits.set_supported_presets({ | ||||||
|         // If we support NONE, then have to decide what happens if the user switches to it (turn off?) |         // If we support NONE, then have to decide what happens if the user switches to it (turn off?) | ||||||
|         // climate::CLIMATE_PRESET_NONE, |         // climate::CLIMATE_PRESET_NONE, | ||||||
|   | |||||||
| @@ -385,7 +385,7 @@ void Climate::save_state_() { | |||||||
|   if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { |   if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { | ||||||
|     state.uses_custom_fan_mode = true; |     state.uses_custom_fan_mode = true; | ||||||
|     const auto &supported = traits.get_supported_custom_fan_modes(); |     const auto &supported = traits.get_supported_custom_fan_modes(); | ||||||
|     // std::set has consistent order (lexicographic for strings) |     // std::vector maintains insertion order | ||||||
|     size_t i = 0; |     size_t i = 0; | ||||||
|     for (const auto &mode : supported) { |     for (const auto &mode : supported) { | ||||||
|       if (mode == custom_fan_mode) { |       if (mode == custom_fan_mode) { | ||||||
| @@ -402,7 +402,7 @@ void Climate::save_state_() { | |||||||
|   if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { |   if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { | ||||||
|     state.uses_custom_preset = true; |     state.uses_custom_preset = true; | ||||||
|     const auto &supported = traits.get_supported_custom_presets(); |     const auto &supported = traits.get_supported_custom_presets(); | ||||||
|     // std::set has consistent order (lexicographic for strings) |     // std::vector maintains insertion order | ||||||
|     size_t i = 0; |     size_t i = 0; | ||||||
|     for (const auto &preset : supported) { |     for (const auto &preset : supported) { | ||||||
|       if (preset == custom_preset) { |       if (preset == custom_preset) { | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ namespace esphome { | |||||||
| namespace climate { | namespace climate { | ||||||
|  |  | ||||||
| /// Enum for all modes a climate device can be in. | /// Enum for all modes a climate device can be in. | ||||||
|  | /// NOTE: If adding values, update ClimateModeMask in climate_traits.h to use the new last value | ||||||
| enum ClimateMode : uint8_t { | enum ClimateMode : uint8_t { | ||||||
|   /// The climate device is off |   /// The climate device is off | ||||||
|   CLIMATE_MODE_OFF = 0, |   CLIMATE_MODE_OFF = 0, | ||||||
| @@ -24,7 +25,7 @@ enum ClimateMode : uint8_t { | |||||||
|    * For example, the target temperature can be adjusted based on a schedule, or learned behavior. |    * For example, the target temperature can be adjusted based on a schedule, or learned behavior. | ||||||
|    * The target temperature can't be adjusted when in this mode. |    * The target temperature can't be adjusted when in this mode. | ||||||
|    */ |    */ | ||||||
|   CLIMATE_MODE_AUTO = 6 |   CLIMATE_MODE_AUTO = 6  // Update ClimateModeMask in climate_traits.h if adding values after this | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Enum for the current action of the climate device. Values match those of ClimateMode. | /// Enum for the current action of the climate device. Values match those of ClimateMode. | ||||||
| @@ -43,6 +44,7 @@ enum ClimateAction : uint8_t { | |||||||
|   CLIMATE_ACTION_FAN = 6, |   CLIMATE_ACTION_FAN = 6, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /// NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value | ||||||
| enum ClimateFanMode : uint8_t { | enum ClimateFanMode : uint8_t { | ||||||
|   /// The fan mode is set to On |   /// The fan mode is set to On | ||||||
|   CLIMATE_FAN_ON = 0, |   CLIMATE_FAN_ON = 0, | ||||||
| @@ -63,10 +65,11 @@ enum ClimateFanMode : uint8_t { | |||||||
|   /// The fan mode is set to Diffuse |   /// The fan mode is set to Diffuse | ||||||
|   CLIMATE_FAN_DIFFUSE = 8, |   CLIMATE_FAN_DIFFUSE = 8, | ||||||
|   /// The fan mode is set to Quiet |   /// The fan mode is set to Quiet | ||||||
|   CLIMATE_FAN_QUIET = 9, |   CLIMATE_FAN_QUIET = 9,  // Update ClimateFanModeMask in climate_traits.h if adding values after this | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Enum for all modes a climate swing can be in | /// Enum for all modes a climate swing can be in | ||||||
|  | /// NOTE: If adding values, update ClimateSwingModeMask in climate_traits.h to use the new last value | ||||||
| enum ClimateSwingMode : uint8_t { | enum ClimateSwingMode : uint8_t { | ||||||
|   /// The swing mode is set to Off |   /// The swing mode is set to Off | ||||||
|   CLIMATE_SWING_OFF = 0, |   CLIMATE_SWING_OFF = 0, | ||||||
| @@ -75,10 +78,11 @@ enum ClimateSwingMode : uint8_t { | |||||||
|   /// The fan mode is set to Vertical |   /// The fan mode is set to Vertical | ||||||
|   CLIMATE_SWING_VERTICAL = 2, |   CLIMATE_SWING_VERTICAL = 2, | ||||||
|   /// The fan mode is set to Horizontal |   /// The fan mode is set to Horizontal | ||||||
|   CLIMATE_SWING_HORIZONTAL = 3, |   CLIMATE_SWING_HORIZONTAL = 3,  // Update ClimateSwingModeMask in climate_traits.h if adding values after this | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Enum for all preset modes | /// Enum for all preset modes | ||||||
|  | /// NOTE: If adding values, update ClimatePresetMask in climate_traits.h to use the new last value | ||||||
| enum ClimatePreset : uint8_t { | enum ClimatePreset : uint8_t { | ||||||
|   /// No preset is active |   /// No preset is active | ||||||
|   CLIMATE_PRESET_NONE = 0, |   CLIMATE_PRESET_NONE = 0, | ||||||
| @@ -95,7 +99,7 @@ enum ClimatePreset : uint8_t { | |||||||
|   /// Device is prepared for sleep |   /// Device is prepared for sleep | ||||||
|   CLIMATE_PRESET_SLEEP = 6, |   CLIMATE_PRESET_SLEEP = 6, | ||||||
|   /// Device is reacting to activity (e.g., movement sensors) |   /// Device is reacting to activity (e.g., movement sensors) | ||||||
|   CLIMATE_PRESET_ACTIVITY = 7, |   CLIMATE_PRESET_ACTIVITY = 7,  // Update ClimatePresetMask in climate_traits.h if adding values after this | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum ClimateFeature : uint32_t { | enum ClimateFeature : uint32_t { | ||||||
|   | |||||||
| @@ -1,19 +1,33 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <set> | #include <vector> | ||||||
| #include "climate_mode.h" | #include "climate_mode.h" | ||||||
|  | #include "esphome/core/finite_set_mask.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
|  |  | ||||||
| #ifdef USE_API |  | ||||||
| namespace api { |  | ||||||
| class APIConnection; |  | ||||||
| }  // namespace api |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace climate { | namespace climate { | ||||||
|  |  | ||||||
|  | // Type aliases for climate enum bitmasks | ||||||
|  | // These replace std::set<EnumType> to eliminate red-black tree overhead | ||||||
|  | // For contiguous enums starting at 0, DefaultBitPolicy provides 1:1 mapping (enum value = bit position) | ||||||
|  | // Bitmask size is automatically calculated from the last enum value | ||||||
|  | using ClimateModeMask = FiniteSetMask<ClimateMode, DefaultBitPolicy<ClimateMode, CLIMATE_MODE_AUTO + 1>>; | ||||||
|  | using ClimateFanModeMask = FiniteSetMask<ClimateFanMode, DefaultBitPolicy<ClimateFanMode, CLIMATE_FAN_QUIET + 1>>; | ||||||
|  | using ClimateSwingModeMask = | ||||||
|  |     FiniteSetMask<ClimateSwingMode, DefaultBitPolicy<ClimateSwingMode, CLIMATE_SWING_HORIZONTAL + 1>>; | ||||||
|  | using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimatePreset, CLIMATE_PRESET_ACTIVITY + 1>>; | ||||||
|  |  | ||||||
|  | // Lightweight linear search for small vectors (1-20 items) | ||||||
|  | // Avoids std::find template overhead | ||||||
|  | template<typename T> inline bool vector_contains(const std::vector<T> &vec, const T &value) { | ||||||
|  |   for (const auto &item : vec) { | ||||||
|  |     if (item == value) | ||||||
|  |       return true; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** This class contains all static data for climate devices. | /** This class contains all static data for climate devices. | ||||||
|  * |  * | ||||||
|  * All climate devices must support these features: |  * All climate devices must support these features: | ||||||
| @@ -107,48 +121,60 @@ class ClimateTraits { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); } |   void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; } | ||||||
|   void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); } |   void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); } | ||||||
|   bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); } |   bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); } | ||||||
|   const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; } |   const ClimateModeMask &get_supported_modes() const { return this->supported_modes_; } | ||||||
|  |  | ||||||
|   void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); } |   void set_supported_fan_modes(ClimateFanModeMask modes) { this->supported_fan_modes_ = modes; } | ||||||
|   void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); } |   void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); } | ||||||
|   void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); } |   void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.push_back(mode); } | ||||||
|   bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); } |   bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); } | ||||||
|   bool get_supports_fan_modes() const { |   bool get_supports_fan_modes() const { | ||||||
|     return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty(); |     return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty(); | ||||||
|   } |   } | ||||||
|   const std::set<ClimateFanMode> &get_supported_fan_modes() const { return this->supported_fan_modes_; } |   const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; } | ||||||
|  |  | ||||||
|   void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) { |   void set_supported_custom_fan_modes(std::vector<std::string> supported_custom_fan_modes) { | ||||||
|     this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); |     this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); | ||||||
|   } |   } | ||||||
|   const std::set<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; } |   void set_supported_custom_fan_modes(std::initializer_list<std::string> modes) { | ||||||
|  |     this->supported_custom_fan_modes_ = modes; | ||||||
|  |   } | ||||||
|  |   template<size_t N> void set_supported_custom_fan_modes(const char *const (&modes)[N]) { | ||||||
|  |     this->supported_custom_fan_modes_.assign(modes, modes + N); | ||||||
|  |   } | ||||||
|  |   const std::vector<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; } | ||||||
|   bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { |   bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { | ||||||
|     return this->supported_custom_fan_modes_.count(custom_fan_mode); |     return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); } |   void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; } | ||||||
|   void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); } |   void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); } | ||||||
|   void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.insert(preset); } |   void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.push_back(preset); } | ||||||
|   bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); } |   bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); } | ||||||
|   bool get_supports_presets() const { return !this->supported_presets_.empty(); } |   bool get_supports_presets() const { return !this->supported_presets_.empty(); } | ||||||
|   const std::set<climate::ClimatePreset> &get_supported_presets() const { return this->supported_presets_; } |   const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; } | ||||||
|  |  | ||||||
|   void set_supported_custom_presets(std::set<std::string> supported_custom_presets) { |   void set_supported_custom_presets(std::vector<std::string> supported_custom_presets) { | ||||||
|     this->supported_custom_presets_ = std::move(supported_custom_presets); |     this->supported_custom_presets_ = std::move(supported_custom_presets); | ||||||
|   } |   } | ||||||
|   const std::set<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; } |   void set_supported_custom_presets(std::initializer_list<std::string> presets) { | ||||||
|  |     this->supported_custom_presets_ = presets; | ||||||
|  |   } | ||||||
|  |   template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) { | ||||||
|  |     this->supported_custom_presets_.assign(presets, presets + N); | ||||||
|  |   } | ||||||
|  |   const std::vector<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; } | ||||||
|   bool supports_custom_preset(const std::string &custom_preset) const { |   bool supports_custom_preset(const std::string &custom_preset) const { | ||||||
|     return this->supported_custom_presets_.count(custom_preset); |     return vector_contains(this->supported_custom_presets_, custom_preset); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); } |   void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; } | ||||||
|   void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); } |   void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); } | ||||||
|   bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); } |   bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); } | ||||||
|   bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); } |   bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); } | ||||||
|   const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return this->supported_swing_modes_; } |   const ClimateSwingModeMask &get_supported_swing_modes() const { return this->supported_swing_modes_; } | ||||||
|  |  | ||||||
|   float get_visual_min_temperature() const { return this->visual_min_temperature_; } |   float get_visual_min_temperature() const { return this->visual_min_temperature_; } | ||||||
|   void set_visual_min_temperature(float visual_min_temperature) { |   void set_visual_min_temperature(float visual_min_temperature) { | ||||||
| @@ -179,23 +205,6 @@ class ClimateTraits { | |||||||
|   void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; } |   void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| #ifdef USE_API |  | ||||||
|   // The API connection is a friend class to access internal methods |  | ||||||
|   friend class api::APIConnection; |  | ||||||
|   // These methods return references to internal data structures. |  | ||||||
|   // They are used by the API to avoid copying data when encoding messages. |  | ||||||
|   // Warning: Do not use these methods outside of the API connection code. |  | ||||||
|   // They return references to internal data that can be invalidated. |  | ||||||
|   const std::set<ClimateMode> &get_supported_modes_for_api_() const { return this->supported_modes_; } |  | ||||||
|   const std::set<ClimateFanMode> &get_supported_fan_modes_for_api_() const { return this->supported_fan_modes_; } |  | ||||||
|   const std::set<std::string> &get_supported_custom_fan_modes_for_api_() const { |  | ||||||
|     return this->supported_custom_fan_modes_; |  | ||||||
|   } |  | ||||||
|   const std::set<climate::ClimatePreset> &get_supported_presets_for_api_() const { return this->supported_presets_; } |  | ||||||
|   const std::set<std::string> &get_supported_custom_presets_for_api_() const { return this->supported_custom_presets_; } |  | ||||||
|   const std::set<ClimateSwingMode> &get_supported_swing_modes_for_api_() const { return this->supported_swing_modes_; } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   void set_mode_support_(climate::ClimateMode mode, bool supported) { |   void set_mode_support_(climate::ClimateMode mode, bool supported) { | ||||||
|     if (supported) { |     if (supported) { | ||||||
|       this->supported_modes_.insert(mode); |       this->supported_modes_.insert(mode); | ||||||
| @@ -226,12 +235,12 @@ class ClimateTraits { | |||||||
|   float visual_min_humidity_{30}; |   float visual_min_humidity_{30}; | ||||||
|   float visual_max_humidity_{99}; |   float visual_max_humidity_{99}; | ||||||
|  |  | ||||||
|   std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF}; |   climate::ClimateModeMask supported_modes_{climate::CLIMATE_MODE_OFF}; | ||||||
|   std::set<climate::ClimateFanMode> supported_fan_modes_; |   climate::ClimateFanModeMask supported_fan_modes_; | ||||||
|   std::set<climate::ClimateSwingMode> supported_swing_modes_; |   climate::ClimateSwingModeMask supported_swing_modes_; | ||||||
|   std::set<climate::ClimatePreset> supported_presets_; |   climate::ClimatePresetMask supported_presets_; | ||||||
|   std::set<std::string> supported_custom_fan_modes_; |   std::vector<std::string> supported_custom_fan_modes_; | ||||||
|   std::set<std::string> supported_custom_presets_; |   std::vector<std::string> supported_custom_presets_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace climate | }  // namespace climate | ||||||
|   | |||||||
| @@ -24,16 +24,18 @@ class ClimateIR : public Component, | |||||||
|                   public remote_base::RemoteTransmittable { |                   public remote_base::RemoteTransmittable { | ||||||
|  public: |  public: | ||||||
|   ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, |   ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, | ||||||
|             bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {}, |             bool supports_dry = false, bool supports_fan_only = false, | ||||||
|             std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) { |             climate::ClimateFanModeMask fan_modes = climate::ClimateFanModeMask(), | ||||||
|  |             climate::ClimateSwingModeMask swing_modes = climate::ClimateSwingModeMask(), | ||||||
|  |             climate::ClimatePresetMask presets = climate::ClimatePresetMask()) { | ||||||
|     this->minimum_temperature_ = minimum_temperature; |     this->minimum_temperature_ = minimum_temperature; | ||||||
|     this->maximum_temperature_ = maximum_temperature; |     this->maximum_temperature_ = maximum_temperature; | ||||||
|     this->temperature_step_ = temperature_step; |     this->temperature_step_ = temperature_step; | ||||||
|     this->supports_dry_ = supports_dry; |     this->supports_dry_ = supports_dry; | ||||||
|     this->supports_fan_only_ = supports_fan_only; |     this->supports_fan_only_ = supports_fan_only; | ||||||
|     this->fan_modes_ = std::move(fan_modes); |     this->fan_modes_ = fan_modes; | ||||||
|     this->swing_modes_ = std::move(swing_modes); |     this->swing_modes_ = swing_modes; | ||||||
|     this->presets_ = std::move(presets); |     this->presets_ = presets; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -60,9 +62,9 @@ class ClimateIR : public Component, | |||||||
|   bool supports_heat_{true}; |   bool supports_heat_{true}; | ||||||
|   bool supports_dry_{false}; |   bool supports_dry_{false}; | ||||||
|   bool supports_fan_only_{false}; |   bool supports_fan_only_{false}; | ||||||
|   std::set<climate::ClimateFanMode> fan_modes_ = {}; |   climate::ClimateFanModeMask fan_modes_{}; | ||||||
|   std::set<climate::ClimateSwingMode> swing_modes_ = {}; |   climate::ClimateSwingModeMask swing_modes_{}; | ||||||
|   std::set<climate::ClimatePreset> presets_ = {}; |   climate::ClimatePresetMask presets_{}; | ||||||
|  |  | ||||||
|   sensor::Sensor *sensor_{nullptr}; |   sensor::Sensor *sensor_{nullptr}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -171,7 +171,7 @@ void HaierClimateBase::toggle_power() { | |||||||
|       PendingAction({ActionRequest::TOGGLE_POWER, esphome::optional<haier_protocol::HaierMessage>()}); |       PendingAction({ActionRequest::TOGGLE_POWER, esphome::optional<haier_protocol::HaierMessage>()}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) { | void HaierClimateBase::set_supported_swing_modes(climate::ClimateSwingModeMask modes) { | ||||||
|   this->traits_.set_supported_swing_modes(modes); |   this->traits_.set_supported_swing_modes(modes); | ||||||
|   if (!modes.empty()) |   if (!modes.empty()) | ||||||
|     this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); |     this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); | ||||||
| @@ -179,13 +179,13 @@ void HaierClimateBase::set_supported_swing_modes(const std::set<climate::Climate | |||||||
|  |  | ||||||
| void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); } | void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); } | ||||||
|  |  | ||||||
| void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) { | void HaierClimateBase::set_supported_modes(climate::ClimateModeMask modes) { | ||||||
|   this->traits_.set_supported_modes(modes); |   this->traits_.set_supported_modes(modes); | ||||||
|   this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF);        // Always available |   this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF);        // Always available | ||||||
|   this->traits_.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);  // Always available |   this->traits_.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);  // Always available | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePreset> &presets) { | void HaierClimateBase::set_supported_presets(climate::ClimatePresetMask presets) { | ||||||
|   this->traits_.set_supported_presets(presets); |   this->traits_.set_supported_presets(presets); | ||||||
|   if (!presets.empty()) |   if (!presets.empty()) | ||||||
|     this->traits_.add_supported_preset(climate::CLIMATE_PRESET_NONE); |     this->traits_.add_supported_preset(climate::CLIMATE_PRESET_NONE); | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <set> |  | ||||||
| #include "esphome/components/climate/climate.h" | #include "esphome/components/climate/climate.h" | ||||||
| #include "esphome/components/uart/uart.h" | #include "esphome/components/uart/uart.h" | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| @@ -60,9 +59,9 @@ class HaierClimateBase : public esphome::Component, | |||||||
|   void send_power_off_command(); |   void send_power_off_command(); | ||||||
|   void toggle_power(); |   void toggle_power(); | ||||||
|   void reset_protocol() { this->reset_protocol_request_ = true; }; |   void reset_protocol() { this->reset_protocol_request_ = true; }; | ||||||
|   void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes); |   void set_supported_modes(esphome::climate::ClimateModeMask modes); | ||||||
|   void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes); |   void set_supported_swing_modes(esphome::climate::ClimateSwingModeMask modes); | ||||||
|   void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets); |   void set_supported_presets(esphome::climate::ClimatePresetMask presets); | ||||||
|   bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; }; |   bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; }; | ||||||
|   size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; |   size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; | ||||||
|   size_t read_array(uint8_t *data, size_t len) noexcept override { |   size_t read_array(uint8_t *data, size_t len) noexcept override { | ||||||
|   | |||||||
| @@ -1033,9 +1033,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|   { |   { | ||||||
|     // Swing mode |     // Swing mode | ||||||
|     ClimateSwingMode old_swing_mode = this->swing_mode; |     ClimateSwingMode old_swing_mode = this->swing_mode; | ||||||
|     const std::set<ClimateSwingMode> &swing_modes = traits_.get_supported_swing_modes(); |     const auto &swing_modes = traits_.get_supported_swing_modes(); | ||||||
|     bool vertical_swing_supported = swing_modes.find(CLIMATE_SWING_VERTICAL) != swing_modes.end(); |     bool vertical_swing_supported = swing_modes.count(CLIMATE_SWING_VERTICAL); | ||||||
|     bool horizontal_swing_supported = swing_modes.find(CLIMATE_SWING_HORIZONTAL) != swing_modes.end(); |     bool horizontal_swing_supported = swing_modes.count(CLIMATE_SWING_HORIZONTAL); | ||||||
|     if (horizontal_swing_supported && |     if (horizontal_swing_supported && | ||||||
|         (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) { |         (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) { | ||||||
|       if (vertical_swing_supported && |       if (vertical_swing_supported && | ||||||
| @@ -1218,13 +1218,13 @@ void HonClimate::fill_control_messages_queue_() { | |||||||
|                                                 (uint8_t) hon_protocol::DataParameters::QUIET_MODE, |                                                 (uint8_t) hon_protocol::DataParameters::QUIET_MODE, | ||||||
|                                             quiet_mode_buf, 2); |                                             quiet_mode_buf, 2); | ||||||
|     } |     } | ||||||
|     if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) { |     if ((fast_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_BOOST)) { | ||||||
|       this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, |       this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, | ||||||
|                                             (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + |                                             (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|                                                 (uint8_t) hon_protocol::DataParameters::FAST_MODE, |                                                 (uint8_t) hon_protocol::DataParameters::FAST_MODE, | ||||||
|                                             fast_mode_buf, 2); |                                             fast_mode_buf, 2); | ||||||
|     } |     } | ||||||
|     if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) { |     if ((away_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_AWAY)) { | ||||||
|       this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, |       this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, | ||||||
|                                             (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + |                                             (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|                                                 (uint8_t) hon_protocol::DataParameters::TEN_DEGREE, |                                                 (uint8_t) hon_protocol::DataParameters::TEN_DEGREE, | ||||||
|   | |||||||
| @@ -97,12 +97,11 @@ const float TEMP_MAX = 100;  // Celsius | |||||||
| class HeatpumpIRClimate : public climate_ir::ClimateIR { | class HeatpumpIRClimate : public climate_ir::ClimateIR { | ||||||
|  public: |  public: | ||||||
|   HeatpumpIRClimate() |   HeatpumpIRClimate() | ||||||
|       : climate_ir::ClimateIR( |       : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true, | ||||||
|             TEMP_MIN, TEMP_MAX, 1.0f, true, true, |                               {climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, | ||||||
|             std::set<climate::ClimateFanMode>{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, |                                climate::CLIMATE_FAN_AUTO}, | ||||||
|                                               climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_AUTO}, |                               {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL, | ||||||
|             std::set<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL, |                                climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {} | ||||||
|                                                 climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {} |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void set_protocol(Protocol protocol) { this->protocol_ = protocol; } |   void set_protocol(Protocol protocol) { this->protocol_ = protocol; } | ||||||
|   void set_horizontal_default(HorizontalDirection horizontal_direction) { |   void set_horizontal_default(HorizontalDirection horizontal_direction) { | ||||||
|   | |||||||
| @@ -19,6 +19,9 @@ using climate::ClimateTraits; | |||||||
| using climate::ClimateMode; | using climate::ClimateMode; | ||||||
| using climate::ClimateSwingMode; | using climate::ClimateSwingMode; | ||||||
| using climate::ClimateFanMode; | using climate::ClimateFanMode; | ||||||
|  | using climate::ClimateModeMask; | ||||||
|  | using climate::ClimateSwingModeMask; | ||||||
|  | using climate::ClimatePresetMask; | ||||||
|  |  | ||||||
| class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate { | class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate { | ||||||
|  public: |  public: | ||||||
| @@ -40,20 +43,20 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, | |||||||
|   void do_power_on() { this->base_.setPowerState(true); } |   void do_power_on() { this->base_.setPowerState(true); } | ||||||
|   void do_power_off() { this->base_.setPowerState(false); } |   void do_power_off() { this->base_.setPowerState(false); } | ||||||
|   void do_power_toggle() { this->base_.setPowerState(this->mode == ClimateMode::CLIMATE_MODE_OFF); } |   void do_power_toggle() { this->base_.setPowerState(this->mode == ClimateMode::CLIMATE_MODE_OFF); } | ||||||
|   void set_supported_modes(const std::set<ClimateMode> &modes) { this->supported_modes_ = modes; } |   void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; } | ||||||
|   void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { this->supported_swing_modes_ = modes; } |   void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; } | ||||||
|   void set_supported_presets(const std::set<ClimatePreset> &presets) { this->supported_presets_ = presets; } |   void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; } | ||||||
|   void set_custom_presets(const std::set<std::string> &presets) { this->supported_custom_presets_ = presets; } |   void set_custom_presets(const std::vector<std::string> &presets) { this->supported_custom_presets_ = presets; } | ||||||
|   void set_custom_fan_modes(const std::set<std::string> &modes) { this->supported_custom_fan_modes_ = modes; } |   void set_custom_fan_modes(const std::vector<std::string> &modes) { this->supported_custom_fan_modes_ = modes; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void control(const ClimateCall &call) override; |   void control(const ClimateCall &call) override; | ||||||
|   ClimateTraits traits() override; |   ClimateTraits traits() override; | ||||||
|   std::set<ClimateMode> supported_modes_{}; |   ClimateModeMask supported_modes_{}; | ||||||
|   std::set<ClimateSwingMode> supported_swing_modes_{}; |   ClimateSwingModeMask supported_swing_modes_{}; | ||||||
|   std::set<ClimatePreset> supported_presets_{}; |   ClimatePresetMask supported_presets_{}; | ||||||
|   std::set<std::string> supported_custom_presets_{}; |   std::vector<std::string> supported_custom_presets_{}; | ||||||
|   std::set<std::string> supported_custom_fan_modes_{}; |   std::vector<std::string> supported_custom_fan_modes_{}; | ||||||
|   Sensor *outdoor_sensor_{nullptr}; |   Sensor *outdoor_sensor_{nullptr}; | ||||||
|   Sensor *humidity_sensor_{nullptr}; |   Sensor *humidity_sensor_{nullptr}; | ||||||
|   Sensor *power_sensor_{nullptr}; |   Sensor *power_sensor_{nullptr}; | ||||||
|   | |||||||
| @@ -40,6 +40,10 @@ enum OnBootRestoreFrom : uint8_t { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| struct ThermostatClimateTimer { | struct ThermostatClimateTimer { | ||||||
|  |   ThermostatClimateTimer() = default; | ||||||
|  |   ThermostatClimateTimer(bool active, uint32_t time, uint32_t started, std::function<void()> func) | ||||||
|  |       : active(active), time(time), started(started), func(std::move(func)) {} | ||||||
|  |  | ||||||
|   bool active; |   bool active; | ||||||
|   uint32_t time; |   uint32_t time; | ||||||
|   uint32_t started; |   uint32_t started; | ||||||
|   | |||||||
| @@ -405,7 +405,7 @@ void ToshibaClimate::setup() { | |||||||
|   this->swing_modes_ = this->toshiba_swing_modes_(); |   this->swing_modes_ = this->toshiba_swing_modes_(); | ||||||
|  |  | ||||||
|   // Ensure swing mode is always initialized to a valid value |   // Ensure swing mode is always initialized to a valid value | ||||||
|   if (this->swing_modes_.empty() || this->swing_modes_.find(this->swing_mode) == this->swing_modes_.end()) { |   if (this->swing_modes_.empty() || !this->swing_modes_.count(this->swing_mode)) { | ||||||
|     // No swing support for this model or current swing mode not supported, reset to OFF |     // No swing support for this model or current swing mode not supported, reset to OFF | ||||||
|     this->swing_mode = climate::CLIMATE_SWING_OFF; |     this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -71,10 +71,10 @@ class ToshibaClimate : public climate_ir::ClimateIR { | |||||||
|       return TOSHIBA_RAS_2819T_TEMP_C_MAX; |       return TOSHIBA_RAS_2819T_TEMP_C_MAX; | ||||||
|     return TOSHIBA_GENERIC_TEMP_C_MAX;  // Default to GENERIC for unknown models |     return TOSHIBA_GENERIC_TEMP_C_MAX;  // Default to GENERIC for unknown models | ||||||
|   } |   } | ||||||
|   std::set<climate::ClimateSwingMode> toshiba_swing_modes_() { |   climate::ClimateSwingModeMask toshiba_swing_modes_() { | ||||||
|     return (this->model_ == MODEL_GENERIC) |     return (this->model_ == MODEL_GENERIC) | ||||||
|                ? std::set<climate::ClimateSwingMode>{} |                ? climate::ClimateSwingModeMask() | ||||||
|                : std::set<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}; |                : climate::ClimateSwingModeMask{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}; | ||||||
|   } |   } | ||||||
|   void encode_(remote_base::RemoteTransmitData *data, const uint8_t *message, uint8_t nbytes, uint8_t repeat); |   void encode_(remote_base::RemoteTransmitData *data, const uint8_t *message, uint8_t nbytes, uint8_t repeat); | ||||||
|   bool decode_(remote_base::RemoteReceiveData *data, uint8_t *message, uint8_t nbytes); |   bool decode_(remote_base::RemoteReceiveData *data, uint8_t *message, uint8_t nbytes); | ||||||
|   | |||||||
| @@ -312,18 +312,12 @@ climate::ClimateTraits TuyaClimate::traits() { | |||||||
|     traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); |     traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); | ||||||
|   } |   } | ||||||
|   if (this->swing_vertical_id_.has_value() && this->swing_horizontal_id_.has_value()) { |   if (this->swing_vertical_id_.has_value() && this->swing_horizontal_id_.has_value()) { | ||||||
|     std::set<climate::ClimateSwingMode> supported_swing_modes = { |     traits.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, | ||||||
|         climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, |                                       climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL}); | ||||||
|         climate::CLIMATE_SWING_HORIZONTAL}; |  | ||||||
|     traits.set_supported_swing_modes(std::move(supported_swing_modes)); |  | ||||||
|   } else if (this->swing_vertical_id_.has_value()) { |   } else if (this->swing_vertical_id_.has_value()) { | ||||||
|     std::set<climate::ClimateSwingMode> supported_swing_modes = {climate::CLIMATE_SWING_OFF, |     traits.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}); | ||||||
|                                                                  climate::CLIMATE_SWING_VERTICAL}; |  | ||||||
|     traits.set_supported_swing_modes(std::move(supported_swing_modes)); |  | ||||||
|   } else if (this->swing_horizontal_id_.has_value()) { |   } else if (this->swing_horizontal_id_.has_value()) { | ||||||
|     std::set<climate::ClimateSwingMode> supported_swing_modes = {climate::CLIMATE_SWING_OFF, |     traits.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}); | ||||||
|                                                                  climate::CLIMATE_SWING_HORIZONTAL}; |  | ||||||
|     traits.set_supported_swing_modes(std::move(supported_swing_modes)); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (fan_speed_id_) { |   if (fan_speed_id_) { | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ class InitialStateHelper: | |||||||
|         helper = InitialStateHelper(entities) |         helper = InitialStateHelper(entities) | ||||||
|         client.subscribe_states(helper.on_state_wrapper(user_callback)) |         client.subscribe_states(helper.on_state_wrapper(user_callback)) | ||||||
|         await helper.wait_for_initial_states() |         await helper.wait_for_initial_states() | ||||||
|  |         # Access initial states via helper.initial_states[key] | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, entities: list[EntityInfo]) -> None: |     def __init__(self, entities: list[EntityInfo]) -> None: | ||||||
| @@ -63,6 +64,8 @@ class InitialStateHelper: | |||||||
|         self._entities_by_id = { |         self._entities_by_id = { | ||||||
|             (entity.device_id, entity.key): entity for entity in entities |             (entity.device_id, entity.key): entity for entity in entities | ||||||
|         } |         } | ||||||
|  |         # Store initial states by key for test access | ||||||
|  |         self.initial_states: dict[int, EntityState] = {} | ||||||
|  |  | ||||||
|         # Log all entities |         # Log all entities | ||||||
|         _LOGGER.debug( |         _LOGGER.debug( | ||||||
| @@ -127,6 +130,9 @@ class InitialStateHelper: | |||||||
|  |  | ||||||
|             # If this entity is waiting for initial state |             # If this entity is waiting for initial state | ||||||
|             if entity_id in self._wait_initial_states: |             if entity_id in self._wait_initial_states: | ||||||
|  |                 # Store the initial state for test access | ||||||
|  |                 self.initial_states[state.key] = state | ||||||
|  |  | ||||||
|                 # Remove from waiting set |                 # Remove from waiting set | ||||||
|                 self._wait_initial_states.discard(entity_id) |                 self._wait_initial_states.discard(entity_id) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,12 +2,11 @@ | |||||||
|  |  | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| import asyncio |  | ||||||
|  |  | ||||||
| import aioesphomeapi | import aioesphomeapi | ||||||
| from aioesphomeapi import ClimateAction, ClimateMode, ClimatePreset, EntityState | from aioesphomeapi import ClimateAction, ClimateInfo, ClimateMode, ClimatePreset | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
|  | from .state_utils import InitialStateHelper | ||||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -18,26 +17,27 @@ async def test_host_mode_climate_basic_state( | |||||||
|     api_client_connected: APIClientConnectedFactory, |     api_client_connected: APIClientConnectedFactory, | ||||||
| ) -> None: | ) -> None: | ||||||
|     """Test basic climate state reporting.""" |     """Test basic climate state reporting.""" | ||||||
|     loop = asyncio.get_running_loop() |  | ||||||
|     async with run_compiled(yaml_config), api_client_connected() as client: |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|         states: dict[int, EntityState] = {} |         # Get entities and set up state synchronization | ||||||
|         climate_future: asyncio.Future[EntityState] = loop.create_future() |         entities, services = await client.list_entities_services() | ||||||
|  |         initial_state_helper = InitialStateHelper(entities) | ||||||
|  |         climate_infos = [e for e in entities if isinstance(e, ClimateInfo)] | ||||||
|  |         assert len(climate_infos) >= 1, "Expected at least 1 climate entity" | ||||||
|  |  | ||||||
|         def on_state(state: EntityState) -> None: |         # Subscribe with the wrapper (no-op callback since we just want initial states) | ||||||
|             states[state.key] = state |         client.subscribe_states(initial_state_helper.on_state_wrapper(lambda _: None)) | ||||||
|             if ( |  | ||||||
|                 isinstance(state, aioesphomeapi.ClimateState) |  | ||||||
|                 and not climate_future.done() |  | ||||||
|             ): |  | ||||||
|                 climate_future.set_result(state) |  | ||||||
|  |  | ||||||
|         client.subscribe_states(on_state) |  | ||||||
|  |  | ||||||
|  |         # Wait for all initial states to be broadcast | ||||||
|         try: |         try: | ||||||
|             climate_state = await asyncio.wait_for(climate_future, timeout=5.0) |             await initial_state_helper.wait_for_initial_states() | ||||||
|         except TimeoutError: |         except TimeoutError: | ||||||
|             pytest.fail("Climate state not received within 5 seconds") |             pytest.fail("Timeout waiting for initial states") | ||||||
|  |  | ||||||
|  |         # Get the climate entity and its initial state | ||||||
|  |         test_climate = climate_infos[0] | ||||||
|  |         climate_state = initial_state_helper.initial_states.get(test_climate.key) | ||||||
|  |  | ||||||
|  |         assert climate_state is not None, "Climate initial state not found" | ||||||
|         assert isinstance(climate_state, aioesphomeapi.ClimateState) |         assert isinstance(climate_state, aioesphomeapi.ClimateState) | ||||||
|         assert climate_state.mode == ClimateMode.OFF |         assert climate_state.mode == ClimateMode.OFF | ||||||
|         assert climate_state.action == ClimateAction.OFF |         assert climate_state.action == ClimateAction.OFF | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user