From e9aecb5fe8f33c3bb13ae54a85884b9057c63405 Mon Sep 17 00:00:00 2001 From: Leonardo Rivera Date: Thu, 24 Sep 2026 09:56:11 -0300 Subject: [PATCH] [climate_ir] Store the supported modes in one mask (#19580) --- esphome/components/climate_ir/climate_ir.cpp | 18 +++-------- esphome/components/climate_ir/climate_ir.h | 32 ++++++++++++------- esphome/components/mitsubishi/mitsubishi.cpp | 2 +- esphome/components/mitsubishi/mitsubishi.h | 8 ++--- .../components/climate_ir/climate_ir_test.cpp | 29 ++++++++++++++++- 5 files changed, 59 insertions(+), 30 deletions(-) diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index ca639c7de0..47143e6e60 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -13,17 +13,7 @@ climate::ClimateTraits ClimateIR::traits() { if (this->humidity_sensor_ != nullptr) { traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY); } - traits.set_supported_modes({climate::CLIMATE_MODE_OFF}); - if (this->supports_cool_) - traits.add_supported_mode(climate::CLIMATE_MODE_COOL); - if (this->supports_heat_) - traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); - if (this->supports_heat_cool_) - traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); - if (this->supports_dry_) - traits.add_supported_mode(climate::CLIMATE_MODE_DRY); - if (this->supports_fan_only_) - traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + traits.set_supported_modes(this->modes_); traits.set_visual_min_temperature(this->minimum_temperature_); traits.set_visual_max_temperature(this->maximum_temperature_); @@ -98,8 +88,10 @@ void ClimateIR::dump_config() { " Supports HEAT: %s\n" " Supports COOL: %s\n" " Supports HEAT_COOL: %s", - this->minimum_temperature_, this->maximum_temperature_, YESNO(this->supports_heat_), - YESNO(this->supports_cool_), YESNO(this->supports_heat_cool_)); + this->minimum_temperature_, this->maximum_temperature_, + YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT)), + YESNO(this->modes_.count(climate::CLIMATE_MODE_COOL)), + YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT_COOL))); } } // namespace esphome::climate_ir diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h index d65ed5d621..060564b4ba 100644 --- a/esphome/components/climate_ir/climate_ir.h +++ b/esphome/components/climate_ir/climate_ir.h @@ -30,8 +30,10 @@ class ClimateIR : public Component, this->minimum_temperature_ = minimum_temperature; this->maximum_temperature_ = maximum_temperature; this->temperature_step_ = temperature_step; - this->supports_dry_ = supports_dry; - this->supports_fan_only_ = supports_fan_only; + if (supports_dry) + this->modes_.insert(climate::CLIMATE_MODE_DRY); + if (supports_fan_only) + this->modes_.insert(climate::CLIMATE_MODE_FAN_ONLY); this->fan_modes_ = fan_modes; this->swing_modes_ = swing_modes; this->presets_ = presets; @@ -39,9 +41,11 @@ class ClimateIR : public Component, void setup() override; void dump_config() override; - void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } - void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } - void set_supports_heat_cool(bool supports_heat_cool) { this->supports_heat_cool_ = supports_heat_cool; } + void set_supports_cool(bool supports_cool) { this->set_mode_supported_(climate::CLIMATE_MODE_COOL, supports_cool); } + void set_supports_heat(bool supports_heat) { this->set_mode_supported_(climate::CLIMATE_MODE_HEAT, supports_heat); } + void set_supports_heat_cool(bool supports_heat_cool) { + this->set_mode_supported_(climate::CLIMATE_MODE_HEAT_COOL, supports_heat_cool); + } void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; } @@ -59,12 +63,18 @@ class ClimateIR : public Component, // Dummy implement on_receive so implementation is optional for inheritors bool on_receive(remote_base::RemoteReceiveData data) override { return false; }; - bool supports_cool_{true}; - bool supports_heat_{true}; - // Default (supports_cool && supports_heat) is resolved during code generation. - bool supports_heat_cool_{true}; - bool supports_dry_{false}; - bool supports_fan_only_{false}; + ESPHOME_ALWAYS_INLINE void set_mode_supported_(climate::ClimateMode mode, bool supported) { + if (supported) { + this->modes_.insert(mode); + } else { + this->modes_.erase(mode); + } + } + + // The HEAT_COOL default (supports_cool && supports_heat) is resolved during code generation. + static constexpr climate::ClimateModeMask DEFAULT_MODES{climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, + climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_HEAT_COOL}; + climate::ClimateModeMask modes_{DEFAULT_MODES}; climate::ClimateFanModeMask fan_modes_{}; climate::ClimateSwingModeMask swing_modes_{}; climate::ClimatePresetMask presets_{}; diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index d7c4e7f1a1..9cb355fe9c 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -102,7 +102,7 @@ void MitsubishiClimate::transmit_state() { default: remote_state[6] = MITSUBISHI_MODE_COOL; remote_state[8] = MITSUBISHI_MODE_A_COOL; - if (this->supports_heat_) { + if (this->modes_.count(climate::CLIMATE_MODE_HEAT)) { remote_state[6] = MITSUBISHI_MODE_HEAT; remote_state[8] = MITSUBISHI_MODE_A_HEAT; } diff --git a/esphome/components/mitsubishi/mitsubishi.h b/esphome/components/mitsubishi/mitsubishi.h index 5b61beac4e..269d39b06b 100644 --- a/esphome/components/mitsubishi/mitsubishi.h +++ b/esphome/components/mitsubishi/mitsubishi.h @@ -51,10 +51,10 @@ class MitsubishiClimate final : public climate_ir::ClimateIR { {climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST, climate::CLIMATE_PRESET_SLEEP}) {} - void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } - void set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } - void set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; } - void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } + void set_supports_dry(bool supports_dry) { this->set_mode_supported_(climate::CLIMATE_MODE_DRY, supports_dry); } + void set_supports_fan_only(bool supports_fan_only) { + this->set_mode_supported_(climate::CLIMATE_MODE_FAN_ONLY, supports_fan_only); + } void set_fan_mode(SetFanMode fan_mode) { this->fan_mode_ = fan_mode; diff --git a/tests/components/climate_ir/climate_ir_test.cpp b/tests/components/climate_ir/climate_ir_test.cpp index 479e1f1b45..25e894a360 100644 --- a/tests/components/climate_ir/climate_ir_test.cpp +++ b/tests/components/climate_ir/climate_ir_test.cpp @@ -5,7 +5,8 @@ namespace esphome::climate_ir::testing { class TestClimateIR : public ClimateIR { public: - TestClimateIR() : ClimateIR(16.0f, 30.0f) {} + explicit TestClimateIR(bool supports_dry = false, bool supports_fan_only = false) + : ClimateIR(16.0f, 30.0f, 1.0f, supports_dry, supports_fan_only) {} using ClimateIR::traits; @@ -53,4 +54,30 @@ TEST(ClimateIRTest, HeatAndCoolModesFollowTheirOwnFlags) { EXPECT_TRUE(traits.supports_mode(climate::CLIMATE_MODE_OFF)); } +TEST(ClimateIRTest, DefaultModes) { + TestClimateIR climate; + auto traits = climate.traits(); + EXPECT_TRUE(traits.supports_mode(climate::CLIMATE_MODE_OFF)); + EXPECT_TRUE(traits.supports_mode(climate::CLIMATE_MODE_COOL)); + EXPECT_TRUE(traits.supports_mode(climate::CLIMATE_MODE_HEAT)); + EXPECT_TRUE(traits.supports_mode(climate::CLIMATE_MODE_HEAT_COOL)); + EXPECT_FALSE(traits.supports_mode(climate::CLIMATE_MODE_DRY)); + EXPECT_FALSE(traits.supports_mode(climate::CLIMATE_MODE_FAN_ONLY)); +} + +TEST(ClimateIRTest, DryAndFanOnlyFromConstructor) { + TestClimateIR climate(true, true); + auto traits = climate.traits(); + EXPECT_TRUE(traits.supports_mode(climate::CLIMATE_MODE_DRY)); + EXPECT_TRUE(traits.supports_mode(climate::CLIMATE_MODE_FAN_ONLY)); +} + +TEST(ClimateIRTest, SetterCanTurnAModeBackOn) { + TestClimateIR climate; + climate.set_supports_cool(false); + EXPECT_FALSE(climate.traits().supports_mode(climate::CLIMATE_MODE_COOL)); + climate.set_supports_cool(true); + EXPECT_TRUE(climate.traits().supports_mode(climate::CLIMATE_MODE_COOL)); +} + } // namespace esphome::climate_ir::testing