From dfafc41ce618d7378fdba0ba66bd8d867e6d96ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Wed, 8 Feb 2023 23:28:16 +0100 Subject: [PATCH] climate: add support for quiet fan mode (#3609) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 1 + esphome/components/climate/__init__.py | 1 + esphome/components/climate/climate.cpp | 2 ++ esphome/components/climate/climate_mode.cpp | 2 ++ esphome/components/climate/climate_mode.h | 2 ++ esphome/components/climate/climate_traits.h | 2 +- esphome/components/demo/demo_climate.h | 1 + esphome/components/mqtt/mqtt_climate.cpp | 5 +++++ esphome/components/thermostat/climate.py | 14 +++++++++++++- .../components/thermostat/thermostat_climate.cpp | 15 ++++++++++++++- .../components/thermostat/thermostat_climate.h | 6 ++++++ esphome/const.py | 1 + tests/test3.yaml | 10 ++++++---- 15 files changed, 58 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 013bb123f9..ffb3bcb07e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -787,6 +787,7 @@ enum ClimateFanMode { CLIMATE_FAN_MIDDLE = 6; CLIMATE_FAN_FOCUS = 7; CLIMATE_FAN_DIFFUSE = 8; + CLIMATE_FAN_QUIET = 9; } enum ClimateSwingMode { CLIMATE_SWING_OFF = 0; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 67c011e76c..9df05d2978 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -235,6 +235,8 @@ template<> const char *proto_enum_to_string(enums::Climat return "CLIMATE_FAN_FOCUS"; case enums::CLIMATE_FAN_DIFFUSE: return "CLIMATE_FAN_DIFFUSE"; + case enums::CLIMATE_FAN_QUIET: + return "CLIMATE_FAN_QUIET"; default: return "UNKNOWN"; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 3b804f1de7..2db1c6fafa 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -99,6 +99,7 @@ enum ClimateFanMode : uint32_t { CLIMATE_FAN_MIDDLE = 6, CLIMATE_FAN_FOCUS = 7, CLIMATE_FAN_DIFFUSE = 8, + CLIMATE_FAN_QUIET = 9, }; enum ClimateSwingMode : uint32_t { CLIMATE_SWING_OFF = 0, diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 8a3cd38444..b83b57002a 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -73,6 +73,7 @@ CLIMATE_FAN_MODES = { "MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE, "FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS, "DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE, + "QUIET": ClimateFanMode.CLIMATE_FAN_QUIET, } validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 512dbdd6dd..e1611d2fa9 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -174,6 +174,8 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { this->set_fan_mode(CLIMATE_FAN_FOCUS); } else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) { this->set_fan_mode(CLIMATE_FAN_DIFFUSE); + } else if (str_equals_case_insensitive(fan_mode, "QUIET")) { + this->set_fan_mode(CLIMATE_FAN_QUIET); } else { if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) { this->custom_fan_mode_ = fan_mode; diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index e46159a750..794f45ccd6 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -62,6 +62,8 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) { return LOG_STR("FOCUS"); case climate::CLIMATE_FAN_DIFFUSE: return LOG_STR("DIFFUSE"); + case climate::CLIMATE_FAN_QUIET: + return LOG_STR("QUIET"); default: return LOG_STR("UNKNOWN"); } diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 139400a08a..c5245812c7 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -62,6 +62,8 @@ enum ClimateFanMode : uint8_t { CLIMATE_FAN_FOCUS = 7, /// The fan mode is set to Diffuse CLIMATE_FAN_DIFFUSE = 8, + /// The fan mode is set to Quiet + CLIMATE_FAN_QUIET = 9, }; /// Enum for all modes a climate swing can be in diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 3ec51bc3c2..9da9bb7374 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -28,7 +28,7 @@ namespace climate { * - supports action - if the climate device supports reporting the active * current action of the device with the action property. * - supports fan modes - optionally, if it has a fan which can be configured in different ways: - * - on, off, auto, high, medium, low, middle, focus, diffuse + * - on, off, auto, high, medium, low, middle, focus, diffuse, quiet * - supports swing modes - optionally, if it has a swing which can be configured in different ways: * - off, both, vertical, horizontal * diff --git a/esphome/components/demo/demo_climate.h b/esphome/components/demo/demo_climate.h index 0cf48dd4ee..1ba80aabf5 100644 --- a/esphome/components/demo/demo_climate.h +++ b/esphome/components/demo/demo_climate.h @@ -111,6 +111,7 @@ class DemoClimate : public climate::Climate, public Component { climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE, + climate::CLIMATE_FAN_QUIET, }); traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"}); traits.set_supported_swing_modes({ diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 7c3c414b3a..349c185fcc 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -102,6 +102,8 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo fan_modes.add("focus"); if (traits.supports_fan_mode(CLIMATE_FAN_DIFFUSE)) fan_modes.add("diffuse"); + if (traits.supports_fan_mode(CLIMATE_FAN_QUIET)) + fan_modes.add("quiet"); for (const auto &fan_mode : traits.get_supported_custom_fan_modes()) fan_modes.add(fan_mode); } @@ -328,6 +330,9 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_FAN_DIFFUSE: payload = "diffuse"; break; + case CLIMATE_FAN_QUIET: + payload = "quiet"; + break; } } if (this->device_->custom_fan_mode.has_value()) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 8aa61dbb93..9a57f6a337 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -24,6 +24,7 @@ from esphome.const import ( CONF_FAN_MODE_MIDDLE_ACTION, CONF_FAN_MODE_FOCUS_ACTION, CONF_FAN_MODE_DIFFUSE_ACTION, + CONF_FAN_MODE_QUIET_ACTION, CONF_FAN_ONLY_ACTION, CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER, CONF_FAN_ONLY_COOLING, @@ -273,6 +274,7 @@ def validate_thermostat(config): CONF_FAN_MODE_MIDDLE_ACTION, CONF_FAN_MODE_FOCUS_ACTION, CONF_FAN_MODE_DIFFUSE_ACTION, + CONF_FAN_MODE_QUIET_ACTION, ], } for req_config_item, config_triggers in requirements.items(): @@ -413,6 +415,7 @@ def validate_thermostat(config): "MIDDLE": [CONF_FAN_MODE_MIDDLE_ACTION], "FOCUS": [CONF_FAN_MODE_FOCUS_ACTION], "DIFFUSE": [CONF_FAN_MODE_DIFFUSE_ACTION], + "QUIET": [CONF_FAN_MODE_QUIET_ACTION], } for preset_config in config[CONF_PRESET]: @@ -500,12 +503,13 @@ def validate_thermostat(config): CONF_FAN_MODE_MIDDLE_ACTION, CONF_FAN_MODE_FOCUS_ACTION, CONF_FAN_MODE_DIFFUSE_ACTION, + CONF_FAN_MODE_QUIET_ACTION, ] for config_req_action in requirements: if config_req_action in config: return config raise cv.Invalid( - f"At least one of {CONF_FAN_MODE_ON_ACTION}, {CONF_FAN_MODE_OFF_ACTION}, {CONF_FAN_MODE_AUTO_ACTION}, {CONF_FAN_MODE_LOW_ACTION}, {CONF_FAN_MODE_MEDIUM_ACTION}, {CONF_FAN_MODE_HIGH_ACTION}, {CONF_FAN_MODE_MIDDLE_ACTION}, {CONF_FAN_MODE_FOCUS_ACTION}, {CONF_FAN_MODE_DIFFUSE_ACTION} must be defined to use {CONF_MIN_FAN_MODE_SWITCHING_TIME}" + f"At least one of {CONF_FAN_MODE_ON_ACTION}, {CONF_FAN_MODE_OFF_ACTION}, {CONF_FAN_MODE_AUTO_ACTION}, {CONF_FAN_MODE_LOW_ACTION}, {CONF_FAN_MODE_MEDIUM_ACTION}, {CONF_FAN_MODE_HIGH_ACTION}, {CONF_FAN_MODE_MIDDLE_ACTION}, {CONF_FAN_MODE_FOCUS_ACTION}, {CONF_FAN_MODE_DIFFUSE_ACTION}, {CONF_FAN_MODE_QUIET_ACTION} must be defined to use {CONF_MIN_FAN_MODE_SWITCHING_TIME}" ) return config @@ -563,6 +567,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_FAN_MODE_DIFFUSE_ACTION): automation.validate_automation( single=True ), + cv.Optional(CONF_FAN_MODE_QUIET_ACTION): automation.validate_automation( + single=True + ), cv.Optional(CONF_SWING_BOTH_ACTION): automation.validate_automation( single=True ), @@ -836,6 +843,11 @@ async def to_code(config): var.get_fan_mode_diffuse_trigger(), [], config[CONF_FAN_MODE_DIFFUSE_ACTION] ) cg.add(var.set_supports_fan_mode_diffuse(True)) + if CONF_FAN_MODE_QUIET_ACTION in config: + await automation.build_automation( + var.get_fan_mode_quiet_trigger(), [], config[CONF_FAN_MODE_QUIET_ACTION] + ) + cg.add(var.set_supports_fan_mode_quiet(True)) if CONF_SWING_BOTH_ACTION in config: await automation.build_automation( var.get_swing_mode_both_trigger(), [], config[CONF_SWING_BOTH_ACTION] diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 1fbbbe022c..51da663a0c 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -247,6 +247,8 @@ climate::ClimateTraits ThermostatClimate::traits() { traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS); if (supports_fan_mode_diffuse_) traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE); + if (supports_fan_mode_quiet_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_QUIET); if (supports_swing_mode_both_) traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); @@ -594,6 +596,10 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo trig = this->fan_mode_diffuse_trigger_; ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode"); break; + case climate::CLIMATE_FAN_QUIET: + trig = this->fan_mode_quiet_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_QUIET mode"); + break; default: // we cannot report an invalid mode back to HA (even if it asked for one) // and must assume some valid value @@ -1093,6 +1099,7 @@ ThermostatClimate::ThermostatClimate() fan_mode_middle_trigger_(new Trigger<>()), fan_mode_focus_trigger_(new Trigger<>()), fan_mode_diffuse_trigger_(new Trigger<>()), + fan_mode_quiet_trigger_(new Trigger<>()), swing_mode_both_trigger_(new Trigger<>()), swing_mode_off_trigger_(new Trigger<>()), swing_mode_horizontal_trigger_(new Trigger<>()), @@ -1208,6 +1215,9 @@ void ThermostatClimate::set_supports_fan_mode_focus(bool supports_fan_mode_focus void ThermostatClimate::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) { this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse; } +void ThermostatClimate::set_supports_fan_mode_quiet(bool supports_fan_mode_quiet) { + this->supports_fan_mode_quiet_ = supports_fan_mode_quiet; +} void ThermostatClimate::set_supports_swing_mode_both(bool supports_swing_mode_both) { this->supports_swing_mode_both_ = supports_swing_mode_both; } @@ -1250,6 +1260,7 @@ Trigger<> *ThermostatClimate::get_fan_mode_high_trigger() const { return this->f Trigger<> *ThermostatClimate::get_fan_mode_middle_trigger() const { return this->fan_mode_middle_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_focus_trigger() const { return this->fan_mode_focus_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_diffuse_trigger() const { return this->fan_mode_diffuse_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_quiet_trigger() const { return this->fan_mode_quiet_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() const { return this->swing_mode_both_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this->swing_mode_off_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; } @@ -1294,7 +1305,8 @@ void ThermostatClimate::dump_config() { } if (this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ || this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ || - this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_) { + this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_ || + this->supports_fan_mode_quiet_) { ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %us", this->timer_duration_(thermostat::TIMER_FAN_MODE) / 1000); } @@ -1323,6 +1335,7 @@ void ThermostatClimate::dump_config() { ESP_LOGCONFIG(TAG, " Supports FAN MODE MIDDLE: %s", YESNO(this->supports_fan_mode_middle_)); ESP_LOGCONFIG(TAG, " Supports FAN MODE FOCUS: %s", YESNO(this->supports_fan_mode_focus_)); ESP_LOGCONFIG(TAG, " Supports FAN MODE DIFFUSE: %s", YESNO(this->supports_fan_mode_diffuse_)); + ESP_LOGCONFIG(TAG, " Supports FAN MODE QUIET: %s", YESNO(this->supports_fan_mode_quiet_)); ESP_LOGCONFIG(TAG, " Supports SWING MODE BOTH: %s", YESNO(this->supports_swing_mode_both_)); ESP_LOGCONFIG(TAG, " Supports SWING MODE OFF: %s", YESNO(this->supports_swing_mode_off_)); ESP_LOGCONFIG(TAG, " Supports SWING MODE HORIZONTAL: %s", YESNO(this->supports_swing_mode_horizontal_)); diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 124f95de33..677b4ad324 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -101,6 +101,7 @@ class ThermostatClimate : public climate::Climate, public Component { void set_supports_fan_mode_middle(bool supports_fan_mode_middle); void set_supports_fan_mode_focus(bool supports_fan_mode_focus); void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse); + void set_supports_fan_mode_quiet(bool supports_fan_mode_quiet); void set_supports_swing_mode_both(bool supports_swing_mode_both); void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal); void set_supports_swing_mode_off(bool supports_swing_mode_off); @@ -132,6 +133,7 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *get_fan_mode_middle_trigger() const; Trigger<> *get_fan_mode_focus_trigger() const; Trigger<> *get_fan_mode_diffuse_trigger() const; + Trigger<> *get_fan_mode_quiet_trigger() const; Trigger<> *get_swing_mode_both_trigger() const; Trigger<> *get_swing_mode_horizontal_trigger() const; Trigger<> *get_swing_mode_off_trigger() const; @@ -277,6 +279,7 @@ class ThermostatClimate : public climate::Climate, public Component { bool supports_fan_mode_middle_{false}; bool supports_fan_mode_focus_{false}; bool supports_fan_mode_diffuse_{false}; + bool supports_fan_mode_quiet_{false}; /// Whether the controller supports various swing modes. /// @@ -372,6 +375,9 @@ class ThermostatClimate : public climate::Climate, public Component { /// The trigger to call when the controller should switch the fan to "diffuse" position. Trigger<> *fan_mode_diffuse_trigger_{nullptr}; + /// The trigger to call when the controller should switch the fan to "quiet" position. + Trigger<> *fan_mode_quiet_trigger_{nullptr}; + /// The trigger to call when the controller should switch the swing mode to "both". Trigger<> *swing_mode_both_trigger_{nullptr}; diff --git a/esphome/const.py b/esphome/const.py index dde5e9016a..802f4fd000 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -237,6 +237,7 @@ CONF_FAN_MODE_MEDIUM_ACTION = "fan_mode_medium_action" CONF_FAN_MODE_MIDDLE_ACTION = "fan_mode_middle_action" CONF_FAN_MODE_OFF_ACTION = "fan_mode_off_action" CONF_FAN_MODE_ON_ACTION = "fan_mode_on_action" +CONF_FAN_MODE_QUIET_ACTION = "fan_mode_quiet_action" CONF_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic" CONF_FAN_ONLY_ACTION = "fan_only_action" CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER = "fan_only_action_uses_fan_mode_timer" diff --git a/tests/test3.yaml b/tests/test3.yaml index 6578def72d..4827b7cbcd 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1136,14 +1136,16 @@ climate: - switch.turn_on: gpio_switch1 fan_mode_diffuse_action: - switch.turn_on: gpio_switch2 + fan_mode_quiet_action: + - switch.turn_on: gpio_switch1 swing_off_action: - - switch.turn_on: gpio_switch1 + - switch.turn_on: gpio_switch2 swing_horizontal_action: - - switch.turn_on: gpio_switch2 - swing_vertical_action: - switch.turn_on: gpio_switch1 - swing_both_action: + swing_vertical_action: - switch.turn_on: gpio_switch2 + swing_both_action: + - switch.turn_on: gpio_switch1 startup_delay: true supplemental_cooling_delta: 2.0 cool_deadband: 0.5