mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-29 22:24:26 +00:00 
			
		
		
		
	Thermostat enhancements and code clean-up (#2073)
This commit is contained in:
		| @@ -6,7 +6,9 @@ from esphome.const import ( | ||||
|     CONF_AUTO_MODE, | ||||
|     CONF_AWAY_CONFIG, | ||||
|     CONF_COOL_ACTION, | ||||
|     CONF_COOL_DEADBAND, | ||||
|     CONF_COOL_MODE, | ||||
|     CONF_COOL_OVERRUN, | ||||
|     CONF_DEFAULT_MODE, | ||||
|     CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, | ||||
|     CONF_DEFAULT_TARGET_TEMPERATURE_LOW, | ||||
| @@ -22,14 +24,18 @@ from esphome.const import ( | ||||
|     CONF_FAN_MODE_FOCUS_ACTION, | ||||
|     CONF_FAN_MODE_DIFFUSE_ACTION, | ||||
|     CONF_FAN_ONLY_ACTION, | ||||
|     CONF_FAN_ONLY_COOLING, | ||||
|     CONF_FAN_ONLY_MODE, | ||||
|     CONF_HEAT_ACTION, | ||||
|     CONF_HEAT_DEADBAND, | ||||
|     CONF_HEAT_MODE, | ||||
|     CONF_HEAT_OVERRUN, | ||||
|     CONF_HYSTERESIS, | ||||
|     CONF_ID, | ||||
|     CONF_IDLE_ACTION, | ||||
|     CONF_OFF_MODE, | ||||
|     CONF_SENSOR, | ||||
|     CONF_SET_POINT_MINIMUM_DIFFERENTIAL, | ||||
|     CONF_SWING_BOTH_ACTION, | ||||
|     CONF_SWING_HORIZONTAL_ACTION, | ||||
|     CONF_SWING_OFF_ACTION, | ||||
| @@ -62,99 +68,59 @@ validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) | ||||
|  | ||||
| def validate_thermostat(config): | ||||
|     # verify corresponding climate action action exists for any defined climate mode action | ||||
|     if CONF_COOL_MODE in config and CONF_COOL_ACTION not in config: | ||||
|         raise cv.Invalid( | ||||
|             "{} must be defined to use {}".format(CONF_COOL_ACTION, CONF_COOL_MODE) | ||||
|         ) | ||||
|     if CONF_DRY_MODE in config and CONF_DRY_ACTION not in config: | ||||
|         raise cv.Invalid( | ||||
|             "{} must be defined to use {}".format(CONF_DRY_ACTION, CONF_DRY_MODE) | ||||
|         ) | ||||
|     if CONF_FAN_ONLY_MODE in config and CONF_FAN_ONLY_ACTION not in config: | ||||
|         raise cv.Invalid( | ||||
|             "{} must be defined to use {}".format( | ||||
|                 CONF_FAN_ONLY_ACTION, CONF_FAN_ONLY_MODE | ||||
|             ) | ||||
|         ) | ||||
|     if CONF_HEAT_MODE in config and CONF_HEAT_ACTION not in config: | ||||
|         raise cv.Invalid( | ||||
|             "{} must be defined to use {}".format(CONF_HEAT_ACTION, CONF_HEAT_MODE) | ||||
|         ) | ||||
|     # verify corresponding default target temperature exists when a given climate action exists | ||||
|     if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH not in config and ( | ||||
|         CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config | ||||
|     ): | ||||
|         raise cv.Invalid( | ||||
|             "{} must be defined when using {} or {}".format( | ||||
|                 CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, | ||||
|     requirements = { | ||||
|         CONF_AUTO_MODE: [CONF_COOL_ACTION, CONF_HEAT_ACTION], | ||||
|         CONF_COOL_MODE: [CONF_COOL_ACTION], | ||||
|         CONF_DRY_MODE: [CONF_DRY_ACTION], | ||||
|         CONF_FAN_ONLY_MODE: [CONF_FAN_ONLY_ACTION], | ||||
|         CONF_HEAT_MODE: [CONF_HEAT_ACTION], | ||||
|     } | ||||
|     for config_mode, req_actions in requirements.items(): | ||||
|         for req_action in req_actions: | ||||
|             if config_mode in config and req_action not in config: | ||||
|                 raise cv.Invalid(f"{req_action} must be defined to use {config_mode}") | ||||
|  | ||||
|     # determine validation requirements based on fan_only_cooling setting | ||||
|     if config[CONF_FAN_ONLY_COOLING] is True: | ||||
|         requirements = { | ||||
|             CONF_DEFAULT_TARGET_TEMPERATURE_HIGH: [ | ||||
|                 CONF_COOL_ACTION, | ||||
|                 CONF_FAN_ONLY_ACTION, | ||||
|             ) | ||||
|         ) | ||||
|     if CONF_DEFAULT_TARGET_TEMPERATURE_LOW not in config and CONF_HEAT_ACTION in config: | ||||
|         raise cv.Invalid( | ||||
|             "{} must be defined when using {}".format( | ||||
|                 CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION | ||||
|             ) | ||||
|         ) | ||||
|     # if a given climate action is NOT defined, it should not have a default target temperature | ||||
|     if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config and ( | ||||
|         CONF_COOL_ACTION not in config and CONF_FAN_ONLY_ACTION not in config | ||||
|     ): | ||||
|         raise cv.Invalid( | ||||
|             "{} is defined with no {}".format( | ||||
|                 CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_COOL_ACTION | ||||
|             ) | ||||
|         ) | ||||
|     if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config and CONF_HEAT_ACTION not in config: | ||||
|         raise cv.Invalid( | ||||
|             "{} is defined with no {}".format( | ||||
|                 CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION | ||||
|             ) | ||||
|         ) | ||||
|             ], | ||||
|             CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION], | ||||
|         } | ||||
|     else: | ||||
|         requirements = { | ||||
|             CONF_DEFAULT_TARGET_TEMPERATURE_HIGH: [CONF_COOL_ACTION], | ||||
|             CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION], | ||||
|         } | ||||
|  | ||||
|     for config_temp, req_actions in requirements.items(): | ||||
|         for req_action in req_actions: | ||||
|             # verify corresponding default target temperature exists when a given climate action exists | ||||
|             if config_temp not in config and req_action in config: | ||||
|                 raise cv.Invalid( | ||||
|                     f"{config_temp} must be defined when using {req_action}" | ||||
|                 ) | ||||
|             # if a given climate action is NOT defined, it should not have a default target temperature | ||||
|             if config_temp in config and req_action not in config: | ||||
|                 raise cv.Invalid(f"{config_temp} is defined with no {req_action}") | ||||
|  | ||||
|     if CONF_AWAY_CONFIG in config: | ||||
|         away = config[CONF_AWAY_CONFIG] | ||||
|         # verify corresponding default target temperature exists when a given climate action exists | ||||
|         if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH not in away and ( | ||||
|             CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config | ||||
|         ): | ||||
|             raise cv.Invalid( | ||||
|                 "{} must be defined in away configuration when using {} or {}".format( | ||||
|                     CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, | ||||
|                     CONF_COOL_ACTION, | ||||
|                     CONF_FAN_ONLY_ACTION, | ||||
|                 ) | ||||
|             ) | ||||
|         if ( | ||||
|             CONF_DEFAULT_TARGET_TEMPERATURE_LOW not in away | ||||
|             and CONF_HEAT_ACTION in config | ||||
|         ): | ||||
|             raise cv.Invalid( | ||||
|                 "{} must be defined in away configuration when using {}".format( | ||||
|                     CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION | ||||
|                 ) | ||||
|             ) | ||||
|         # if a given climate action is NOT defined, it should not have a default target temperature | ||||
|         if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away and ( | ||||
|             CONF_COOL_ACTION not in config and CONF_FAN_ONLY_ACTION not in config | ||||
|         ): | ||||
|             raise cv.Invalid( | ||||
|                 "{} is defined in away configuration with no {} or {}".format( | ||||
|                     CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, | ||||
|                     CONF_COOL_ACTION, | ||||
|                     CONF_FAN_ONLY_ACTION, | ||||
|                 ) | ||||
|             ) | ||||
|         if ( | ||||
|             CONF_DEFAULT_TARGET_TEMPERATURE_LOW in away | ||||
|             and CONF_HEAT_ACTION not in config | ||||
|         ): | ||||
|             raise cv.Invalid( | ||||
|                 "{} is defined in away configuration with no {}".format( | ||||
|                     CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION | ||||
|                 ) | ||||
|             ) | ||||
|         for config_temp, req_actions in requirements.items(): | ||||
|             for req_action in req_actions: | ||||
|                 # verify corresponding default target temperature exists when a given climate action exists | ||||
|                 if config_temp not in away and req_action in config: | ||||
|                     raise cv.Invalid( | ||||
|                         f"{config_temp} must be defined in away configuration when using {req_action}" | ||||
|                     ) | ||||
|                 # if a given climate action is NOT defined, it should not have a default target temperature | ||||
|                 if config_temp in away and req_action not in config: | ||||
|                     raise cv.Invalid( | ||||
|                         f"{config_temp} is defined in away configuration with no {req_action}" | ||||
|                     ) | ||||
|  | ||||
|     # verify default climate mode is valid given above configuration | ||||
|     default_mode = config[CONF_DEFAULT_MODE] | ||||
|     requirements = { | ||||
| @@ -241,7 +207,15 @@ CONFIG_SCHEMA = cv.All( | ||||
|             ), | ||||
|             cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, | ||||
|             cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, | ||||
|             cv.Optional( | ||||
|                 CONF_SET_POINT_MINIMUM_DIFFERENTIAL, default=0.5 | ||||
|             ): cv.temperature, | ||||
|             cv.Optional(CONF_COOL_DEADBAND): cv.temperature, | ||||
|             cv.Optional(CONF_COOL_OVERRUN): cv.temperature, | ||||
|             cv.Optional(CONF_HEAT_DEADBAND): cv.temperature, | ||||
|             cv.Optional(CONF_HEAT_OVERRUN): cv.temperature, | ||||
|             cv.Optional(CONF_HYSTERESIS, default=0.5): cv.temperature, | ||||
|             cv.Optional(CONF_FAN_ONLY_COOLING, default=False): cv.boolean, | ||||
|             cv.Optional(CONF_AWAY_CONFIG): cv.Schema( | ||||
|                 { | ||||
|                     cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, | ||||
| @@ -269,8 +243,32 @@ async def to_code(config): | ||||
|  | ||||
|     sens = await cg.get_variable(config[CONF_SENSOR]) | ||||
|     cg.add(var.set_default_mode(config[CONF_DEFAULT_MODE])) | ||||
|     cg.add( | ||||
|         var.set_set_point_minimum_differential( | ||||
|             config[CONF_SET_POINT_MINIMUM_DIFFERENTIAL] | ||||
|         ) | ||||
|     ) | ||||
|     cg.add(var.set_sensor(sens)) | ||||
|     cg.add(var.set_hysteresis(config[CONF_HYSTERESIS])) | ||||
|  | ||||
|     if CONF_COOL_DEADBAND in config: | ||||
|         cg.add(var.set_cool_deadband(config[CONF_COOL_DEADBAND])) | ||||
|     else: | ||||
|         cg.add(var.set_cool_deadband(config[CONF_HYSTERESIS])) | ||||
|  | ||||
|     if CONF_COOL_OVERRUN in config: | ||||
|         cg.add(var.set_cool_overrun(config[CONF_COOL_OVERRUN])) | ||||
|     else: | ||||
|         cg.add(var.set_cool_overrun(config[CONF_HYSTERESIS])) | ||||
|  | ||||
|     if CONF_HEAT_DEADBAND in config: | ||||
|         cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND])) | ||||
|     else: | ||||
|         cg.add(var.set_heat_deadband(config[CONF_HYSTERESIS])) | ||||
|  | ||||
|     if CONF_HEAT_OVERRUN in config: | ||||
|         cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN])) | ||||
|     else: | ||||
|         cg.add(var.set_heat_overrun(config[CONF_HYSTERESIS])) | ||||
|  | ||||
|     if two_points_available is True: | ||||
|         cg.add(var.set_supports_two_points(True)) | ||||
| @@ -288,6 +286,7 @@ async def to_code(config): | ||||
|         normal_config = ThermostatClimateTargetTempConfig( | ||||
|             config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW] | ||||
|         ) | ||||
|     cg.add(var.set_supports_fan_only_cooling(config[CONF_FAN_ONLY_COOLING])) | ||||
|     cg.add(var.set_normal_config(normal_config)) | ||||
|  | ||||
|     await automation.build_automation( | ||||
|   | ||||
| @@ -7,6 +7,7 @@ namespace thermostat { | ||||
| static const char *const TAG = "thermostat.climate"; | ||||
|  | ||||
| void ThermostatClimate::setup() { | ||||
|   // add a callback so that whenever the sensor state changes we can take action | ||||
|   this->sensor_->add_on_state_callback([this](float state) { | ||||
|     this->current_temperature = state; | ||||
|     // required action may have changed, recompute, refresh | ||||
| @@ -29,7 +30,12 @@ void ThermostatClimate::setup() { | ||||
|   this->setup_complete_ = true; | ||||
|   this->publish_state(); | ||||
| } | ||||
| float ThermostatClimate::hysteresis() { return this->hysteresis_; } | ||||
|  | ||||
| float ThermostatClimate::cool_deadband() { return this->cool_deadband_; } | ||||
| float ThermostatClimate::cool_overrun() { return this->cool_overrun_; } | ||||
| float ThermostatClimate::heat_deadband() { return this->heat_deadband_; } | ||||
| float ThermostatClimate::heat_overrun() { return this->heat_overrun_; } | ||||
|  | ||||
| void ThermostatClimate::refresh() { | ||||
|   this->switch_to_mode_(this->mode); | ||||
|   this->switch_to_action_(compute_action_()); | ||||
| @@ -38,6 +44,77 @@ void ThermostatClimate::refresh() { | ||||
|   this->check_temperature_change_trigger_(); | ||||
|   this->publish_state(); | ||||
| } | ||||
|  | ||||
| bool ThermostatClimate::hysteresis_valid() { | ||||
|   if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && | ||||
|       (isnan(this->cool_deadband_) || isnan(this->cool_overrun_))) | ||||
|     return false; | ||||
|  | ||||
|   if (this->supports_heat_ && (isnan(this->heat_deadband_) || isnan(this->heat_overrun_))) | ||||
|     return false; | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::validate_target_temperature() { | ||||
|   if (isnan(this->target_temperature)) { | ||||
|     this->target_temperature = | ||||
|         ((this->get_traits().get_visual_max_temperature() - this->get_traits().get_visual_min_temperature()) / 2) + | ||||
|         this->get_traits().get_visual_min_temperature(); | ||||
|   } else { | ||||
|     // target_temperature must be between the visual minimum and the visual maximum | ||||
|     if (this->target_temperature < this->get_traits().get_visual_min_temperature()) | ||||
|       this->target_temperature = this->get_traits().get_visual_min_temperature(); | ||||
|     if (this->target_temperature > this->get_traits().get_visual_max_temperature()) | ||||
|       this->target_temperature = this->get_traits().get_visual_max_temperature(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::validate_target_temperatures() { | ||||
|   if (this->supports_two_points_) { | ||||
|     validate_target_temperature_low(); | ||||
|     validate_target_temperature_high(); | ||||
|   } else { | ||||
|     validate_target_temperature(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::validate_target_temperature_low() { | ||||
|   if (isnan(this->target_temperature_low)) { | ||||
|     this->target_temperature_low = this->get_traits().get_visual_min_temperature(); | ||||
|   } else { | ||||
|     // target_temperature_low must not be lower than the visual minimum | ||||
|     if (this->target_temperature_low < this->get_traits().get_visual_min_temperature()) | ||||
|       this->target_temperature_low = this->get_traits().get_visual_min_temperature(); | ||||
|     // target_temperature_low must not be greater than the visual maximum minus set_point_minimum_differential_ | ||||
|     if (this->target_temperature_low > | ||||
|         this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_) | ||||
|       this->target_temperature_low = | ||||
|           this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_; | ||||
|     // if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high | ||||
|     if (this->target_temperature_low > this->target_temperature_high - this->set_point_minimum_differential_) | ||||
|       this->target_temperature_high = this->target_temperature_low + this->set_point_minimum_differential_; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::validate_target_temperature_high() { | ||||
|   if (isnan(this->target_temperature_high)) { | ||||
|     this->target_temperature_high = this->get_traits().get_visual_max_temperature(); | ||||
|   } else { | ||||
|     // target_temperature_high must not be lower than the visual maximum | ||||
|     if (this->target_temperature_high > this->get_traits().get_visual_max_temperature()) | ||||
|       this->target_temperature_high = this->get_traits().get_visual_max_temperature(); | ||||
|     // target_temperature_high must not be lower than the visual minimum plus set_point_minimum_differential_ | ||||
|     if (this->target_temperature_high < | ||||
|         this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_) | ||||
|       this->target_temperature_high = | ||||
|           this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_; | ||||
|     // if target_temperature_high is set less than target_temperature_low, move down target_temperature_low | ||||
|     if (this->target_temperature_high < this->target_temperature_low + this->set_point_minimum_differential_) | ||||
|       this->target_temperature_low = this->target_temperature_high - this->set_point_minimum_differential_; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::control(const climate::ClimateCall &call) { | ||||
|   if (call.get_preset().has_value()) { | ||||
|     // setup_complete_ blocks modifying/resetting the temps immediately after boot | ||||
| @@ -53,29 +130,25 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { | ||||
|     this->fan_mode = *call.get_fan_mode(); | ||||
|   if (call.get_swing_mode().has_value()) | ||||
|     this->swing_mode = *call.get_swing_mode(); | ||||
|   if (call.get_target_temperature().has_value()) | ||||
|     this->target_temperature = *call.get_target_temperature(); | ||||
|   if (call.get_target_temperature_low().has_value()) | ||||
|     this->target_temperature_low = *call.get_target_temperature_low(); | ||||
|   if (call.get_target_temperature_high().has_value()) | ||||
|     this->target_temperature_high = *call.get_target_temperature_high(); | ||||
|   // set point validation | ||||
|   if (this->supports_two_points_) { | ||||
|     if (this->target_temperature_low < this->get_traits().get_visual_min_temperature()) | ||||
|       this->target_temperature_low = this->get_traits().get_visual_min_temperature(); | ||||
|     if (this->target_temperature_high > this->get_traits().get_visual_max_temperature()) | ||||
|       this->target_temperature_high = this->get_traits().get_visual_max_temperature(); | ||||
|     if (this->target_temperature_high < this->target_temperature_low) | ||||
|       this->target_temperature_high = this->target_temperature_low; | ||||
|     if (call.get_target_temperature_low().has_value()) { | ||||
|       this->target_temperature_low = *call.get_target_temperature_low(); | ||||
|       validate_target_temperature_low(); | ||||
|     } | ||||
|     if (call.get_target_temperature_high().has_value()) { | ||||
|       this->target_temperature_high = *call.get_target_temperature_high(); | ||||
|       validate_target_temperature_high(); | ||||
|     } | ||||
|   } else { | ||||
|     if (this->target_temperature < this->get_traits().get_visual_min_temperature()) | ||||
|       this->target_temperature = this->get_traits().get_visual_min_temperature(); | ||||
|     if (this->target_temperature > this->get_traits().get_visual_max_temperature()) | ||||
|       this->target_temperature = this->get_traits().get_visual_max_temperature(); | ||||
|     if (call.get_target_temperature().has_value()) { | ||||
|       this->target_temperature = *call.get_target_temperature(); | ||||
|       validate_target_temperature(); | ||||
|     } | ||||
|   } | ||||
|   // make any changes happen | ||||
|   refresh(); | ||||
| } | ||||
|  | ||||
| climate::ClimateTraits ThermostatClimate::traits() { | ||||
|   auto traits = climate::ClimateTraits(); | ||||
|   traits.set_supports_current_temperature(true); | ||||
| @@ -127,114 +200,54 @@ climate::ClimateTraits ThermostatClimate::traits() { | ||||
|   traits.set_supports_action(true); | ||||
|   return traits; | ||||
| } | ||||
|  | ||||
| climate::ClimateAction ThermostatClimate::compute_action_() { | ||||
|   // we need to know the current climate action before anything else happens here | ||||
|   climate::ClimateAction target_action = this->action; | ||||
|   // if the climate mode is OFF then the climate action must be OFF | ||||
|   if (this->mode == climate::CLIMATE_MODE_OFF) { | ||||
|   auto target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|   // if any hysteresis values or current_temperature is not valid, we go to OFF; | ||||
|   if (isnan(this->current_temperature) || !this->hysteresis_valid()) { | ||||
|     return climate::CLIMATE_ACTION_OFF; | ||||
|   } else if (this->action == climate::CLIMATE_ACTION_OFF) { | ||||
|     // ...but if the climate mode is NOT OFF then the climate action must not be OFF | ||||
|     target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|   } | ||||
|  | ||||
|   if (this->supports_two_points_) { | ||||
|     if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || | ||||
|         isnan(this->target_temperature_high) || isnan(this->hysteresis_)) | ||||
|       // if any control parameters are nan, go to OFF action (not IDLE!) | ||||
|       return climate::CLIMATE_ACTION_OFF; | ||||
|  | ||||
|     if (((this->action == climate::CLIMATE_ACTION_FAN) && (this->mode != climate::CLIMATE_MODE_FAN_ONLY)) || | ||||
|         ((this->action == climate::CLIMATE_ACTION_DRYING) && (this->mode != climate::CLIMATE_MODE_DRY))) { | ||||
|       target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|     } | ||||
|  | ||||
|     switch (this->mode) { | ||||
|       case climate::CLIMATE_MODE_FAN_ONLY: | ||||
|         if (this->supports_fan_only_) { | ||||
|           if (this->current_temperature > this->target_temperature_high + this->hysteresis_) | ||||
|             target_action = climate::CLIMATE_ACTION_FAN; | ||||
|           else if (this->current_temperature < this->target_temperature_high - this->hysteresis_) | ||||
|             if (this->action == climate::CLIMATE_ACTION_FAN) | ||||
|               target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|         } | ||||
|         break; | ||||
|       case climate::CLIMATE_MODE_DRY: | ||||
|         target_action = climate::CLIMATE_ACTION_DRYING; | ||||
|         break; | ||||
|       case climate::CLIMATE_MODE_HEAT_COOL: | ||||
|       case climate::CLIMATE_MODE_COOL: | ||||
|       case climate::CLIMATE_MODE_HEAT: | ||||
|         if (this->supports_cool_) { | ||||
|           if (this->current_temperature > this->target_temperature_high + this->hysteresis_) | ||||
|             target_action = climate::CLIMATE_ACTION_COOLING; | ||||
|           else if (this->current_temperature < this->target_temperature_high - this->hysteresis_) | ||||
|             if (this->action == climate::CLIMATE_ACTION_COOLING) | ||||
|               target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|         } | ||||
|         if (this->supports_heat_) { | ||||
|           if (this->current_temperature < this->target_temperature_low - this->hysteresis_) | ||||
|             target_action = climate::CLIMATE_ACTION_HEATING; | ||||
|           else if (this->current_temperature > this->target_temperature_low + this->hysteresis_) | ||||
|             if (this->action == climate::CLIMATE_ACTION_HEATING) | ||||
|               target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|         } | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|   } else { | ||||
|     if (isnan(this->current_temperature) || isnan(this->target_temperature) || isnan(this->hysteresis_)) | ||||
|       // if any control parameters are nan, go to OFF action (not IDLE!) | ||||
|       return climate::CLIMATE_ACTION_OFF; | ||||
|  | ||||
|     if (((this->action == climate::CLIMATE_ACTION_FAN) && (this->mode != climate::CLIMATE_MODE_FAN_ONLY)) || | ||||
|         ((this->action == climate::CLIMATE_ACTION_DRYING) && (this->mode != climate::CLIMATE_MODE_DRY))) { | ||||
|       target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|     } | ||||
|  | ||||
|     switch (this->mode) { | ||||
|       case climate::CLIMATE_MODE_FAN_ONLY: | ||||
|         if (this->supports_fan_only_) { | ||||
|           if (this->current_temperature > this->target_temperature + this->hysteresis_) | ||||
|             target_action = climate::CLIMATE_ACTION_FAN; | ||||
|           else if (this->current_temperature < this->target_temperature - this->hysteresis_) | ||||
|             if (this->action == climate::CLIMATE_ACTION_FAN) | ||||
|               target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|         } | ||||
|         break; | ||||
|       case climate::CLIMATE_MODE_DRY: | ||||
|         target_action = climate::CLIMATE_ACTION_DRYING; | ||||
|         break; | ||||
|       case climate::CLIMATE_MODE_COOL: | ||||
|         if (this->supports_cool_) { | ||||
|           if (this->current_temperature > this->target_temperature + this->hysteresis_) | ||||
|             target_action = climate::CLIMATE_ACTION_COOLING; | ||||
|           else if (this->current_temperature < this->target_temperature - this->hysteresis_) | ||||
|             if (this->action == climate::CLIMATE_ACTION_COOLING) | ||||
|               target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|         } | ||||
|       case climate::CLIMATE_MODE_HEAT: | ||||
|         if (this->supports_heat_) { | ||||
|           if (this->current_temperature < this->target_temperature - this->hysteresis_) | ||||
|             target_action = climate::CLIMATE_ACTION_HEATING; | ||||
|           else if (this->current_temperature > this->target_temperature + this->hysteresis_) | ||||
|             if (this->action == climate::CLIMATE_ACTION_HEATING) | ||||
|               target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|         } | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|   // ensure set point(s) is/are valid before computing the action | ||||
|   this->validate_target_temperatures(); | ||||
|   // everything has been validated so we can now safely compute the action | ||||
|   switch (this->mode) { | ||||
|     // if the climate mode is OFF then the climate action must be OFF | ||||
|     case climate::CLIMATE_MODE_OFF: | ||||
|       target_action = climate::CLIMATE_ACTION_OFF; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_FAN_ONLY: | ||||
|       if (this->fanning_required_()) | ||||
|         target_action = climate::CLIMATE_ACTION_FAN; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_DRY: | ||||
|       target_action = climate::CLIMATE_ACTION_DRYING; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_HEAT_COOL: | ||||
|       if (this->cooling_required_() && this->heating_required_()) { | ||||
|         // this is bad and should never happen, so just stop. | ||||
|         // target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|       } else if (this->cooling_required_()) { | ||||
|         target_action = climate::CLIMATE_ACTION_COOLING; | ||||
|       } else if (this->heating_required_()) { | ||||
|         target_action = climate::CLIMATE_ACTION_HEATING; | ||||
|       } | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_COOL: | ||||
|       if (this->cooling_required_()) { | ||||
|         target_action = climate::CLIMATE_ACTION_COOLING; | ||||
|       } | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_HEAT: | ||||
|       if (this->heating_required_()) { | ||||
|         target_action = climate::CLIMATE_ACTION_HEATING; | ||||
|       } | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
|   // do not switch to an action that isn't enabled per the active climate mode | ||||
|   if ((this->mode == climate::CLIMATE_MODE_COOL) && (target_action == climate::CLIMATE_ACTION_HEATING)) | ||||
|     target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|   if ((this->mode == climate::CLIMATE_MODE_HEAT) && (target_action == climate::CLIMATE_ACTION_COOLING)) | ||||
|     target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|  | ||||
|   return target_action; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { | ||||
|   // setup_complete_ helps us ensure an action is called immediately after boot | ||||
|   if ((action == this->action) && this->setup_complete_) | ||||
| @@ -284,6 +297,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { | ||||
|   this->action = action; | ||||
|   this->prev_action_trigger_ = trig; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { | ||||
|   // setup_complete_ helps us ensure an action is called immediately after boot | ||||
|   if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) | ||||
| @@ -335,6 +349,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { | ||||
|   this->prev_fan_mode_ = fan_mode; | ||||
|   this->prev_fan_mode_trigger_ = trig; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { | ||||
|   // setup_complete_ helps us ensure an action is called immediately after boot | ||||
|   if ((mode == this->prev_mode_) && this->setup_complete_) | ||||
| @@ -377,6 +392,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { | ||||
|   this->prev_mode_ = mode; | ||||
|   this->prev_mode_trigger_ = trig; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode) { | ||||
|   // setup_complete_ helps us ensure an action is called immediately after boot | ||||
|   if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) | ||||
| @@ -413,6 +429,7 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo | ||||
|   this->prev_swing_mode_ = swing_mode; | ||||
|   this->prev_swing_mode_trigger_ = trig; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::check_temperature_change_trigger_() { | ||||
|   if (this->supports_two_points_) { | ||||
|     // setup_complete_ helps us ensure an action is called immediately after boot | ||||
| @@ -437,6 +454,70 @@ void ThermostatClimate::check_temperature_change_trigger_() { | ||||
|   assert(trig != nullptr); | ||||
|   trig->trigger(); | ||||
| } | ||||
|  | ||||
| bool ThermostatClimate::cooling_required_() { | ||||
|   auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature; | ||||
|  | ||||
|   if (this->supports_cool_) { | ||||
|     if (this->current_temperature > (temperature + this->cool_deadband_)) { | ||||
|       // if the current temperature exceeds the target + deadband, cooling is required | ||||
|       return true; | ||||
|     } else if (this->current_temperature < (temperature - this->cool_overrun_)) { | ||||
|       // if the current temperature is less than the target - overrun, cooling should stop | ||||
|       return false; | ||||
|     } else { | ||||
|       // if we get here, the current temperature is between target + deadband and target - overrun, | ||||
|       //  so the action should not change unless it conflicts with the current mode | ||||
|       return (this->action == climate::CLIMATE_ACTION_COOLING) && | ||||
|              ((this->mode == climate::CLIMATE_MODE_HEAT_COOL) || (this->mode == climate::CLIMATE_MODE_COOL)); | ||||
|     } | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool ThermostatClimate::fanning_required_() { | ||||
|   auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature; | ||||
|  | ||||
|   if (this->supports_fan_only_) { | ||||
|     if (this->supports_fan_only_cooling_) { | ||||
|       if (this->current_temperature > (temperature + this->cool_deadband_)) { | ||||
|         // if the current temperature exceeds the target + deadband, fanning is required | ||||
|         return true; | ||||
|       } else if (this->current_temperature < (temperature - this->cool_overrun_)) { | ||||
|         // if the current temperature is less than the target - overrun, fanning should stop | ||||
|         return false; | ||||
|       } else { | ||||
|         // if we get here, the current temperature is between target + deadband and target - overrun, | ||||
|         //  so the action should not change unless it conflicts with the current mode | ||||
|         return (this->action == climate::CLIMATE_ACTION_FAN) && (this->mode == climate::CLIMATE_MODE_FAN_ONLY); | ||||
|       } | ||||
|     } else { | ||||
|       return true; | ||||
|     } | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool ThermostatClimate::heating_required_() { | ||||
|   auto temperature = this->supports_two_points_ ? this->target_temperature_low : this->target_temperature; | ||||
|  | ||||
|   if (this->supports_heat_) { | ||||
|     if (this->current_temperature < temperature - this->heat_deadband_) { | ||||
|       // if the current temperature is below the target - deadband, heating is required | ||||
|       return true; | ||||
|     } else if (this->current_temperature > temperature + this->heat_overrun_) { | ||||
|       // if the current temperature is above the target + overrun, heating should stop | ||||
|       return false; | ||||
|     } else { | ||||
|       // if we get here, the current temperature is between target - deadband and target + overrun, | ||||
|       //  so the action should not change unless it conflicts with the current mode | ||||
|       return (this->action == climate::CLIMATE_ACTION_HEATING) && | ||||
|              ((this->mode == climate::CLIMATE_MODE_HEAT_COOL) || (this->mode == climate::CLIMATE_MODE_HEAT)); | ||||
|     } | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::change_away_(bool away) { | ||||
|   if (!away) { | ||||
|     if (this->supports_two_points_) { | ||||
| @@ -453,13 +534,16 @@ void ThermostatClimate::change_away_(bool away) { | ||||
|   } | ||||
|   this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) { | ||||
|   this->normal_config_ = normal_config; | ||||
| } | ||||
|  | ||||
| void ThermostatClimate::set_away_config(const ThermostatClimateTargetTempConfig &away_config) { | ||||
|   this->supports_away_ = true; | ||||
|   this->away_config_ = away_config; | ||||
| } | ||||
|  | ||||
| ThermostatClimate::ThermostatClimate() | ||||
|     : cool_action_trigger_(new Trigger<>()), | ||||
|       cool_mode_trigger_(new Trigger<>()), | ||||
| @@ -486,8 +570,15 @@ ThermostatClimate::ThermostatClimate() | ||||
|       swing_mode_horizontal_trigger_(new Trigger<>()), | ||||
|       swing_mode_vertical_trigger_(new Trigger<>()), | ||||
|       temperature_change_trigger_(new Trigger<>()) {} | ||||
|  | ||||
| void ThermostatClimate::set_default_mode(climate::ClimateMode default_mode) { this->default_mode_ = default_mode; } | ||||
| void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; } | ||||
| void ThermostatClimate::set_set_point_minimum_differential(float differential) { | ||||
|   this->set_point_minimum_differential_ = differential; | ||||
| } | ||||
| void ThermostatClimate::set_cool_deadband(float deadband) { this->cool_deadband_ = deadband; } | ||||
| void ThermostatClimate::set_cool_overrun(float overrun) { this->cool_overrun_ = overrun; } | ||||
| void ThermostatClimate::set_heat_deadband(float deadband) { this->heat_deadband_ = deadband; } | ||||
| void ThermostatClimate::set_heat_overrun(float overrun) { this->heat_overrun_ = overrun; } | ||||
| void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } | ||||
| void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { | ||||
|   this->supports_heat_cool_ = supports_heat_cool; | ||||
| @@ -496,6 +587,9 @@ void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_a | ||||
| void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } | ||||
| void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } | ||||
| void ThermostatClimate::set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; } | ||||
| void ThermostatClimate::set_supports_fan_only_cooling(bool supports_fan_only_cooling) { | ||||
|   this->supports_fan_only_cooling_ = supports_fan_only_cooling; | ||||
| } | ||||
| void ThermostatClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } | ||||
| void ThermostatClimate::set_supports_fan_mode_on(bool supports_fan_mode_on) { | ||||
|   this->supports_fan_mode_on_ = supports_fan_mode_on; | ||||
| @@ -539,6 +633,7 @@ void ThermostatClimate::set_supports_swing_mode_vertical(bool supports_swing_mod | ||||
| void ThermostatClimate::set_supports_two_points(bool supports_two_points) { | ||||
|   this->supports_two_points_ = supports_two_points; | ||||
| } | ||||
|  | ||||
| Trigger<> *ThermostatClimate::get_cool_action_trigger() const { return this->cool_action_trigger_; } | ||||
| Trigger<> *ThermostatClimate::get_dry_action_trigger() const { return this->dry_action_trigger_; } | ||||
| Trigger<> *ThermostatClimate::get_fan_only_action_trigger() const { return this->fan_only_action_trigger_; } | ||||
| @@ -564,6 +659,7 @@ Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this-> | ||||
| Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; } | ||||
| Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() const { return this->swing_mode_vertical_trigger_; } | ||||
| Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return this->temperature_change_trigger_; } | ||||
|  | ||||
| void ThermostatClimate::dump_config() { | ||||
|   LOG_CLIMATE("", "Thermostat", this); | ||||
|   if (this->supports_heat_) { | ||||
| @@ -578,12 +674,17 @@ void ThermostatClimate::dump_config() { | ||||
|     else | ||||
|       ESP_LOGCONFIG(TAG, "  Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature); | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Hysteresis: %.1f°C", this->hysteresis_); | ||||
|   ESP_LOGCONFIG(TAG, "  Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); | ||||
|   ESP_LOGCONFIG(TAG, "  Cool Deadband: %.1f°C", this->cool_deadband_); | ||||
|   ESP_LOGCONFIG(TAG, "  Cool Overrun: %.1f°C", this->cool_overrun_); | ||||
|   ESP_LOGCONFIG(TAG, "  Heat Deadband: %.1f°C", this->heat_deadband_); | ||||
|   ESP_LOGCONFIG(TAG, "  Heat Overrun: %.1f°C", this->heat_overrun_); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports AUTO: %s", YESNO(this->supports_auto_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports COOL: %s", YESNO(this->supports_cool_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports DRY: %s", YESNO(this->supports_dry_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports FAN_ONLY_COOLING: %s", YESNO(this->supports_fan_only_cooling_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports HEAT: %s", YESNO(this->supports_heat_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports FAN MODE ON: %s", YESNO(this->supports_fan_mode_on_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Supports FAN MODE OFF: %s", YESNO(this->supports_fan_mode_off_)); | ||||
| @@ -619,8 +720,10 @@ void ThermostatClimate::dump_config() { | ||||
| } | ||||
|  | ||||
| ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default; | ||||
|  | ||||
| ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float default_temperature) | ||||
|     : default_temperature(default_temperature) {} | ||||
|  | ||||
| ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float default_temperature_low, | ||||
|                                                                      float default_temperature_high) | ||||
|     : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {} | ||||
|   | ||||
| @@ -17,7 +17,10 @@ struct ThermostatClimateTargetTempConfig { | ||||
|   float default_temperature{NAN}; | ||||
|   float default_temperature_low{NAN}; | ||||
|   float default_temperature_high{NAN}; | ||||
|   float hysteresis{NAN}; | ||||
|   float cool_deadband_{NAN}; | ||||
|   float cool_overrun_{NAN}; | ||||
|   float heat_deadband_{NAN}; | ||||
|   float heat_overrun_{NAN}; | ||||
| }; | ||||
|  | ||||
| class ThermostatClimate : public climate::Climate, public Component { | ||||
| @@ -27,13 +30,18 @@ class ThermostatClimate : public climate::Climate, public Component { | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void set_default_mode(climate::ClimateMode default_mode); | ||||
|   void set_hysteresis(float hysteresis); | ||||
|   void set_set_point_minimum_differential(float differential); | ||||
|   void set_cool_deadband(float deadband); | ||||
|   void set_cool_overrun(float overrun); | ||||
|   void set_heat_deadband(float deadband); | ||||
|   void set_heat_overrun(float overrun); | ||||
|   void set_sensor(sensor::Sensor *sensor); | ||||
|   void set_supports_auto(bool supports_auto); | ||||
|   void set_supports_heat_cool(bool supports_heat_cool); | ||||
|   void set_supports_cool(bool supports_cool); | ||||
|   void set_supports_dry(bool supports_dry); | ||||
|   void set_supports_fan_only(bool supports_fan_only); | ||||
|   void set_supports_fan_only_cooling(bool supports_fan_only_cooling); | ||||
|   void set_supports_heat(bool supports_heat); | ||||
|   void set_supports_fan_mode_on(bool supports_fan_mode_on); | ||||
|   void set_supports_fan_mode_off(bool supports_fan_mode_off); | ||||
| @@ -78,10 +86,19 @@ class ThermostatClimate : public climate::Climate, public Component { | ||||
|   Trigger<> *get_swing_mode_off_trigger() const; | ||||
|   Trigger<> *get_swing_mode_vertical_trigger() const; | ||||
|   Trigger<> *get_temperature_change_trigger() const; | ||||
|   /// Get current hysteresis value | ||||
|   float hysteresis(); | ||||
|   /// Get current hysteresis values | ||||
|   float cool_deadband(); | ||||
|   float cool_overrun(); | ||||
|   float heat_deadband(); | ||||
|   float heat_overrun(); | ||||
|   /// Call triggers based on updated climate states (modes/actions) | ||||
|   void refresh(); | ||||
|   /// Set point and hysteresis validation | ||||
|   bool hysteresis_valid();  // returns true if valid | ||||
|   void validate_target_temperature(); | ||||
|   void validate_target_temperatures(); | ||||
|   void validate_target_temperature_low(); | ||||
|   void validate_target_temperature_high(); | ||||
|  | ||||
|  protected: | ||||
|   /// Override control to change settings of the climate device. | ||||
| @@ -111,6 +128,11 @@ class ThermostatClimate : public climate::Climate, public Component { | ||||
|   /// Check if the temperature change trigger should be called. | ||||
|   void check_temperature_change_trigger_(); | ||||
|  | ||||
|   /// Check if cooling/fanning/heating actions are required; returns true if so | ||||
|   bool cooling_required_(); | ||||
|   bool fanning_required_(); | ||||
|   bool heating_required_(); | ||||
|  | ||||
|   /// The sensor used for getting the current temperature | ||||
|   sensor::Sensor *sensor_{nullptr}; | ||||
|  | ||||
| @@ -124,6 +146,8 @@ class ThermostatClimate : public climate::Climate, public Component { | ||||
|   bool supports_dry_{false}; | ||||
|   bool supports_fan_only_{false}; | ||||
|   bool supports_heat_{false}; | ||||
|   /// Special flag -- enables fan to be switched based on target_temperature_high | ||||
|   bool supports_fan_only_cooling_{false}; | ||||
|  | ||||
|   /// Whether the controller supports turning on or off just the fan. | ||||
|   /// | ||||
| @@ -278,8 +302,14 @@ class ThermostatClimate : public climate::Climate, public Component { | ||||
|   ThermostatClimateTargetTempConfig normal_config_{}; | ||||
|   ThermostatClimateTargetTempConfig away_config_{}; | ||||
|  | ||||
|   /// Hysteresis value used for computing climate actions | ||||
|   float hysteresis_{0}; | ||||
|   /// Minimum differential required between set points | ||||
|   float set_point_minimum_differential_{0}; | ||||
|  | ||||
|   /// Hysteresis values used for computing climate actions | ||||
|   float cool_deadband_{0}; | ||||
|   float cool_overrun_{0}; | ||||
|   float heat_deadband_{0}; | ||||
|   float heat_overrun_{0}; | ||||
|  | ||||
|   /// setup_complete_ blocks modifying/resetting the temps immediately after boot | ||||
|   bool setup_complete_{false}; | ||||
|   | ||||
| @@ -134,7 +134,9 @@ CONF_CONDITION_ID = "condition_id" | ||||
| CONF_CONDUCTIVITY = "conductivity" | ||||
| CONF_CONTRAST = "contrast" | ||||
| CONF_COOL_ACTION = "cool_action" | ||||
| CONF_COOL_DEADBAND = "cool_deadband" | ||||
| CONF_COOL_MODE = "cool_mode" | ||||
| CONF_COOL_OVERRUN = "cool_overrun" | ||||
| CONF_COUNT = "count" | ||||
| CONF_COUNT_MODE = "count_mode" | ||||
| CONF_COURSE = "course" | ||||
| @@ -219,6 +221,7 @@ 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_ONLY_ACTION = "fan_only_action" | ||||
| CONF_FAN_ONLY_COOLING = "fan_only_cooling" | ||||
| CONF_FAN_ONLY_MODE = "fan_only_mode" | ||||
| CONF_FAST_CONNECT = "fast_connect" | ||||
| CONF_FILE = "file" | ||||
| @@ -248,7 +251,9 @@ CONF_GROUP = "group" | ||||
| CONF_HARDWARE_UART = "hardware_uart" | ||||
| CONF_HEARTBEAT = "heartbeat" | ||||
| CONF_HEAT_ACTION = "heat_action" | ||||
| CONF_HEAT_DEADBAND = "heat_deadband" | ||||
| CONF_HEAT_MODE = "heat_mode" | ||||
| CONF_HEAT_OVERRUN = "heat_overrun" | ||||
| CONF_HEATER = "heater" | ||||
| CONF_HEIGHT = "height" | ||||
| CONF_HIDDEN = "hidden" | ||||
| @@ -541,6 +546,7 @@ CONF_SERVERS = "servers" | ||||
| CONF_SERVICE = "service" | ||||
| CONF_SERVICE_UUID = "service_uuid" | ||||
| CONF_SERVICES = "services" | ||||
| CONF_SET_POINT_MINIMUM_DIFFERENTIAL = "set_point_minimum_differential" | ||||
| CONF_SETUP_MODE = "setup_mode" | ||||
| CONF_SETUP_PRIORITY = "setup_priority" | ||||
| CONF_SHUNT_RESISTANCE = "shunt_resistance" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user