From 05ab49a6152cb46a877dcbf851b1127c764725a8 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Tue, 7 Mar 2023 04:19:49 +0000 Subject: [PATCH] climate: add on_control callbacks (#4511) This lets downstream components respond to climate configuration changes, which take place through ClimateCall objects, without also being notified every time the state changes, which happens every time the input sensor announces a new value. FIXES https://github.com/esphome/feature-requests/issues/2136 --- esphome/components/climate/__init__.py | 7 +++++++ esphome/components/climate/automation.h | 7 +++++++ esphome/components/climate/climate.cpp | 5 +++++ esphome/components/climate/climate.h | 9 +++++++++ esphome/const.py | 1 + tests/test1.yaml | 2 ++ 6 files changed, 31 insertions(+) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 709d0d12ed..4a16c3fb7d 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -20,6 +20,7 @@ from esphome.const import ( CONF_MODE, CONF_MODE_COMMAND_TOPIC, CONF_MODE_STATE_TOPIC, + CONF_ON_CONTROL, CONF_ON_STATE, CONF_PRESET, CONF_PRESET_COMMAND_TOPIC, @@ -127,6 +128,7 @@ def single_visual_temperature(value): # Actions ControlAction = climate_ns.class_("ControlAction", automation.Action) StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template()) +ControlTrigger = climate_ns.class_("ControlTrigger", automation.Trigger.template()) VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any( single_visual_temperature, @@ -203,6 +205,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA). cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.publish_topic ), + cv.Optional(CONF_ON_CONTROL): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger), + } + ), cv.Optional(CONF_ON_STATE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index 3145358dab..9b06563eb4 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -42,6 +42,13 @@ template class ControlAction : public Action { Climate *climate_; }; +class ControlTrigger : public Trigger<> { + public: + ControlTrigger(Climate *climate) { + climate->add_on_control_callback([this]() { this->trigger(); }); + } +}; + class StateTrigger : public Trigger<> { public: StateTrigger(Climate *climate) { diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index b80fe640c8..37572ae913 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -44,6 +44,7 @@ void ClimateCall::perform() { if (this->target_temperature_high_.has_value()) { ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); } + this->parent_->control_callback_.call(); this->parent_->control(*this); } void ClimateCall::validate_() { @@ -317,6 +318,10 @@ void Climate::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +void Climate::add_on_control_callback(std::function &&callback) { + this->control_callback_.add(std::move(callback)); +} + // Random 32bit value; If this changes existing restore preferences are invalidated static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 8cc260abbe..520036f718 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -219,6 +219,14 @@ class Climate : public EntityBase { */ void add_on_state_callback(std::function &&callback); + /** + * Add a callback for the climate device configuration; each time the configuration parameters of a climate device + * is updated (using perform() of a ClimateCall), this callback will be called, before any on_state callback. + * + * @param callback The callback to call. + */ + void add_on_control_callback(std::function &&callback); + /** Make a climate device control call, this is used to control the climate device, see the ClimateCall description * for more info. * @return A new ClimateCall instance targeting this climate device. @@ -285,6 +293,7 @@ class Climate : public EntityBase { void dump_traits_(const char *tag); CallbackManager state_callback_{}; + CallbackManager control_callback_{}; ESPPreferenceObject rtc_; optional visual_min_temperature_override_{}; optional visual_max_temperature_override_{}; diff --git a/esphome/const.py b/esphome/const.py index d821a76b75..0ab9dd54bf 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -447,6 +447,7 @@ CONF_ON_BLE_SERVICE_DATA_ADVERTISE = "on_ble_service_data_advertise" CONF_ON_BOOT = "on_boot" CONF_ON_CLICK = "on_click" CONF_ON_CONNECT = "on_connect" +CONF_ON_CONTROL = "on_control" CONF_ON_DISCONNECT = "on_disconnect" CONF_ON_DOUBLE_CLICK = "on_double_click" CONF_ON_ENROLLMENT_DONE = "on_enrollment_done" diff --git a/tests/test1.yaml b/tests/test1.yaml index d43490c8cc..a554d45771 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2052,6 +2052,8 @@ climate: name: Midea IR use_fahrenheit: true - platform: midea + on_control: + logger.log: Control message received! on_state: logger.log: State changed! id: midea_unit