mirror of
https://github.com/esphome/esphome.git
synced 2025-09-13 16:52:18 +01:00
[thermostat] General clean-up, optimization, properly support "auto" mode (#10561)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
@@ -32,6 +32,7 @@ from esphome.const import (
|
||||
CONF_FAN_WITH_COOLING,
|
||||
CONF_FAN_WITH_HEATING,
|
||||
CONF_HEAT_ACTION,
|
||||
CONF_HEAT_COOL_MODE,
|
||||
CONF_HEAT_DEADBAND,
|
||||
CONF_HEAT_MODE,
|
||||
CONF_HEAT_OVERRUN,
|
||||
@@ -150,7 +151,7 @@ def generate_comparable_preset(config, name):
|
||||
def validate_thermostat(config):
|
||||
# verify corresponding action(s) exist(s) for any defined climate mode or action
|
||||
requirements = {
|
||||
CONF_AUTO_MODE: [
|
||||
CONF_HEAT_COOL_MODE: [
|
||||
CONF_COOL_ACTION,
|
||||
CONF_HEAT_ACTION,
|
||||
CONF_MIN_COOLING_OFF_TIME,
|
||||
@@ -540,6 +541,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_FAN_ONLY_MODE): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_HEAT_COOL_MODE): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_HEAT_MODE): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_OFF_MODE): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_FAN_MODE_ON_ACTION): automation.validate_automation(
|
||||
@@ -644,7 +648,6 @@ async def to_code(config):
|
||||
var = await climate.new_climate(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
|
||||
two_points_available = CONF_HEAT_ACTION in config and (
|
||||
CONF_COOL_ACTION in config
|
||||
or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config)
|
||||
@@ -739,11 +742,6 @@ async def to_code(config):
|
||||
var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION]
|
||||
)
|
||||
|
||||
if heat_cool_mode_available is True:
|
||||
cg.add(var.set_supports_heat_cool(True))
|
||||
else:
|
||||
cg.add(var.set_supports_heat_cool(False))
|
||||
|
||||
if CONF_COOL_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_cool_action_trigger(), [], config[CONF_COOL_ACTION]
|
||||
@@ -780,6 +778,7 @@ async def to_code(config):
|
||||
await automation.build_automation(
|
||||
var.get_auto_mode_trigger(), [], config[CONF_AUTO_MODE]
|
||||
)
|
||||
cg.add(var.set_supports_auto(True))
|
||||
if CONF_COOL_MODE in config:
|
||||
await automation.build_automation(
|
||||
var.get_cool_mode_trigger(), [], config[CONF_COOL_MODE]
|
||||
@@ -800,6 +799,11 @@ async def to_code(config):
|
||||
var.get_heat_mode_trigger(), [], config[CONF_HEAT_MODE]
|
||||
)
|
||||
cg.add(var.set_supports_heat(True))
|
||||
if CONF_HEAT_COOL_MODE in config:
|
||||
await automation.build_automation(
|
||||
var.get_heat_cool_mode_trigger(), [], config[CONF_HEAT_COOL_MODE]
|
||||
)
|
||||
cg.add(var.set_supports_heat_cool(True))
|
||||
if CONF_OFF_MODE in config:
|
||||
await automation.build_automation(
|
||||
var.get_off_mode_trigger(), [], config[CONF_OFF_MODE]
|
||||
|
@@ -1,4 +1,6 @@
|
||||
#include "thermostat_climate.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -64,7 +66,7 @@ void ThermostatClimate::setup() {
|
||||
|
||||
void ThermostatClimate::loop() {
|
||||
for (auto &timer : this->timer_) {
|
||||
if (timer.active && (timer.started + timer.time < millis())) {
|
||||
if (timer.active && (timer.started + timer.time < App.get_loop_component_start_time())) {
|
||||
timer.active = false;
|
||||
timer.func();
|
||||
}
|
||||
@@ -127,26 +129,35 @@ bool ThermostatClimate::hysteresis_valid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ThermostatClimate::limit_setpoints_for_heat_cool() {
|
||||
return this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
||||
(this->mode == climate::CLIMATE_MODE_AUTO && this->supports_heat_cool_);
|
||||
}
|
||||
|
||||
void ThermostatClimate::validate_target_temperature() {
|
||||
if (std::isnan(this->target_temperature)) {
|
||||
// default to the midpoint between visual min and max
|
||||
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();
|
||||
this->target_temperature = clamp(this->target_temperature, this->get_traits().get_visual_min_temperature(),
|
||||
this->get_traits().get_visual_max_temperature());
|
||||
}
|
||||
}
|
||||
|
||||
void ThermostatClimate::validate_target_temperatures() {
|
||||
if (this->supports_two_points_) {
|
||||
void ThermostatClimate::validate_target_temperatures(const bool pin_target_temperature_high) {
|
||||
if (!this->supports_two_points_) {
|
||||
this->validate_target_temperature();
|
||||
} else if (pin_target_temperature_high) {
|
||||
// if target_temperature_high is set less than target_temperature_low, move down target_temperature_low
|
||||
this->validate_target_temperature_low();
|
||||
this->validate_target_temperature_high();
|
||||
} else {
|
||||
this->validate_target_temperature();
|
||||
// if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high
|
||||
this->validate_target_temperature_high();
|
||||
this->validate_target_temperature_low();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,18 +165,13 @@ void ThermostatClimate::validate_target_temperature_low() {
|
||||
if (std::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_;
|
||||
float target_temperature_low_upper_limit =
|
||||
this->limit_setpoints_for_heat_cool()
|
||||
? clamp(this->target_temperature_high - this->set_point_minimum_differential_,
|
||||
this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
|
||||
: this->get_traits().get_visual_max_temperature();
|
||||
this->target_temperature_low = clamp(this->target_temperature_low, this->get_traits().get_visual_min_temperature(),
|
||||
target_temperature_low_upper_limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,62 +179,64 @@ void ThermostatClimate::validate_target_temperature_high() {
|
||||
if (std::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_;
|
||||
float target_temperature_high_lower_limit =
|
||||
this->limit_setpoints_for_heat_cool()
|
||||
? clamp(this->target_temperature_low + this->set_point_minimum_differential_,
|
||||
this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
|
||||
: this->get_traits().get_visual_min_temperature();
|
||||
this->target_temperature_high = clamp(this->target_temperature_high, target_temperature_high_lower_limit,
|
||||
this->get_traits().get_visual_max_temperature());
|
||||
}
|
||||
}
|
||||
|
||||
void ThermostatClimate::control(const climate::ClimateCall &call) {
|
||||
bool target_temperature_high_changed = false;
|
||||
|
||||
if (call.get_preset().has_value()) {
|
||||
// setup_complete_ blocks modifying/resetting the temps immediately after boot
|
||||
if (this->setup_complete_) {
|
||||
this->change_preset_(*call.get_preset());
|
||||
this->change_preset_(call.get_preset().value());
|
||||
} else {
|
||||
this->preset = *call.get_preset();
|
||||
this->preset = call.get_preset().value();
|
||||
}
|
||||
}
|
||||
if (call.get_custom_preset().has_value()) {
|
||||
// setup_complete_ blocks modifying/resetting the temps immediately after boot
|
||||
if (this->setup_complete_) {
|
||||
this->change_custom_preset_(*call.get_custom_preset());
|
||||
this->change_custom_preset_(call.get_custom_preset().value());
|
||||
} else {
|
||||
this->custom_preset = *call.get_custom_preset();
|
||||
this->custom_preset = call.get_custom_preset().value();
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_mode().has_value())
|
||||
this->mode = *call.get_mode();
|
||||
if (call.get_fan_mode().has_value())
|
||||
this->fan_mode = *call.get_fan_mode();
|
||||
if (call.get_swing_mode().has_value())
|
||||
this->swing_mode = *call.get_swing_mode();
|
||||
if (call.get_mode().has_value()) {
|
||||
this->mode = call.get_mode().value();
|
||||
}
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
this->fan_mode = call.get_fan_mode().value();
|
||||
}
|
||||
if (call.get_swing_mode().has_value()) {
|
||||
this->swing_mode = call.get_swing_mode().value();
|
||||
}
|
||||
if (this->supports_two_points_) {
|
||||
if (call.get_target_temperature_low().has_value()) {
|
||||
this->target_temperature_low = *call.get_target_temperature_low();
|
||||
validate_target_temperature_low();
|
||||
this->target_temperature_low = call.get_target_temperature_low().value();
|
||||
}
|
||||
if (call.get_target_temperature_high().has_value()) {
|
||||
this->target_temperature_high = *call.get_target_temperature_high();
|
||||
validate_target_temperature_high();
|
||||
target_temperature_high_changed = this->target_temperature_high != call.get_target_temperature_high().value();
|
||||
this->target_temperature_high = call.get_target_temperature_high().value();
|
||||
}
|
||||
// ensure the two set points are valid and adjust one of them if necessary
|
||||
this->validate_target_temperatures(target_temperature_high_changed ||
|
||||
(this->prev_mode_ == climate::CLIMATE_MODE_COOL));
|
||||
} else {
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
this->target_temperature = *call.get_target_temperature();
|
||||
validate_target_temperature();
|
||||
this->target_temperature = call.get_target_temperature().value();
|
||||
this->validate_target_temperature();
|
||||
}
|
||||
}
|
||||
// make any changes happen
|
||||
refresh();
|
||||
this->refresh();
|
||||
}
|
||||
|
||||
climate::ClimateTraits ThermostatClimate::traits() {
|
||||
@@ -237,47 +245,47 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
traits.set_supports_current_humidity(true);
|
||||
|
||||
if (supports_auto_)
|
||||
if (this->supports_auto_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_AUTO);
|
||||
if (supports_heat_cool_)
|
||||
if (this->supports_heat_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||
if (supports_cool_)
|
||||
if (this->supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (supports_dry_)
|
||||
if (this->supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (supports_fan_only_)
|
||||
if (this->supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
if (supports_heat_)
|
||||
if (this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
|
||||
if (supports_fan_mode_on_)
|
||||
if (this->supports_fan_mode_on_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_ON);
|
||||
if (supports_fan_mode_off_)
|
||||
if (this->supports_fan_mode_off_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_OFF);
|
||||
if (supports_fan_mode_auto_)
|
||||
if (this->supports_fan_mode_auto_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO);
|
||||
if (supports_fan_mode_low_)
|
||||
if (this->supports_fan_mode_low_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW);
|
||||
if (supports_fan_mode_medium_)
|
||||
if (this->supports_fan_mode_medium_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM);
|
||||
if (supports_fan_mode_high_)
|
||||
if (this->supports_fan_mode_high_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH);
|
||||
if (supports_fan_mode_middle_)
|
||||
if (this->supports_fan_mode_middle_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE);
|
||||
if (supports_fan_mode_focus_)
|
||||
if (this->supports_fan_mode_focus_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS);
|
||||
if (supports_fan_mode_diffuse_)
|
||||
if (this->supports_fan_mode_diffuse_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE);
|
||||
if (supports_fan_mode_quiet_)
|
||||
if (this->supports_fan_mode_quiet_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_QUIET);
|
||||
|
||||
if (supports_swing_mode_both_)
|
||||
if (this->supports_swing_mode_both_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
|
||||
if (supports_swing_mode_horizontal_)
|
||||
if (this->supports_swing_mode_horizontal_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL);
|
||||
if (supports_swing_mode_off_)
|
||||
if (this->supports_swing_mode_off_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);
|
||||
if (supports_swing_mode_vertical_)
|
||||
if (this->supports_swing_mode_vertical_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
||||
|
||||
for (auto &it : this->preset_config_) {
|
||||
@@ -300,13 +308,13 @@ climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_time
|
||||
}
|
||||
// do not change the action if an "ON" timer is running
|
||||
if ((!ignore_timers) &&
|
||||
(timer_active_(thermostat::TIMER_IDLE_ON) || timer_active_(thermostat::TIMER_COOLING_ON) ||
|
||||
timer_active_(thermostat::TIMER_FANNING_ON) || timer_active_(thermostat::TIMER_HEATING_ON))) {
|
||||
(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_COOLING_ON) ||
|
||||
this->timer_active_(thermostat::TIMER_FANNING_ON) || this->timer_active_(thermostat::TIMER_HEATING_ON))) {
|
||||
return this->action;
|
||||
}
|
||||
|
||||
// ensure set point(s) is/are valid before computing the action
|
||||
this->validate_target_temperatures();
|
||||
this->validate_target_temperatures(this->prev_mode_ == climate::CLIMATE_MODE_COOL);
|
||||
// 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
|
||||
@@ -340,6 +348,22 @@ climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_time
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
}
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
if (this->supports_two_points_) {
|
||||
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;
|
||||
}
|
||||
} else if (this->supports_cool_ && this->cooling_required_()) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
} else if (this->supports_heat_ && this->heating_required_()) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -362,7 +386,7 @@ climate::ClimateAction ThermostatClimate::compute_supplemental_action_() {
|
||||
}
|
||||
|
||||
// ensure set point(s) is/are valid before computing the action
|
||||
this->validate_target_temperatures();
|
||||
this->validate_target_temperatures(this->prev_mode_ == climate::CLIMATE_MODE_COOL);
|
||||
// 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
|
||||
@@ -653,13 +677,13 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
|
||||
this->prev_mode_trigger_->stop_action();
|
||||
this->prev_mode_trigger_ = nullptr;
|
||||
}
|
||||
Trigger<> *trig = this->auto_mode_trigger_;
|
||||
Trigger<> *trig = this->off_mode_trigger_;
|
||||
switch (mode) {
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
trig = this->off_mode_trigger_;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
trig = this->auto_mode_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
// trig = this->auto_mode_trigger_;
|
||||
trig = this->heat_cool_mode_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
trig = this->cool_mode_trigger_;
|
||||
@@ -673,11 +697,12 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
trig = this->dry_mode_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
// we cannot report an invalid mode back to HA (even if it asked for one)
|
||||
// and must assume some valid value
|
||||
mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
// trig = this->auto_mode_trigger_;
|
||||
mode = climate::CLIMATE_MODE_OFF;
|
||||
// trig = this->off_mode_trigger_;
|
||||
}
|
||||
if (trig != nullptr) {
|
||||
trig->trigger();
|
||||
@@ -685,8 +710,9 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
|
||||
this->mode = mode;
|
||||
this->prev_mode_ = mode;
|
||||
this->prev_mode_trigger_ = trig;
|
||||
if (publish_state)
|
||||
if (publish_state) {
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode, bool publish_state) {
|
||||
@@ -958,37 +984,25 @@ bool ThermostatClimate::supplemental_heating_required_() {
|
||||
(this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING));
|
||||
}
|
||||
|
||||
void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
|
||||
bool is_default_preset) {
|
||||
ESP_LOGCONFIG(TAG, " %s Is Default: %s", preset_name, YESNO(is_default_preset));
|
||||
|
||||
void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config) {
|
||||
if (this->supports_heat_) {
|
||||
if (this->supports_two_points_) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Target Temperature Low: %.1f°C", preset_name,
|
||||
config.default_temperature_low);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Target Temperature Low: %.1f°C", preset_name, config.default_temperature);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C",
|
||||
this->supports_two_points_ ? config.default_temperature_low : config.default_temperature);
|
||||
}
|
||||
if ((this->supports_cool_) || (this->supports_fan_only_)) {
|
||||
if (this->supports_two_points_) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Target Temperature High: %.1f°C", preset_name,
|
||||
config.default_temperature_high);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Target Temperature High: %.1f°C", preset_name, config.default_temperature);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C",
|
||||
this->supports_two_points_ ? config.default_temperature_high : config.default_temperature);
|
||||
}
|
||||
|
||||
if (config.mode_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Mode: %s", preset_name,
|
||||
LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
|
||||
ESP_LOGCONFIG(TAG, " Default Mode: %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
|
||||
}
|
||||
if (config.fan_mode_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Fan Mode: %s", preset_name,
|
||||
ESP_LOGCONFIG(TAG, " Default Fan Mode: %s",
|
||||
LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
|
||||
}
|
||||
if (config.swing_mode_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Swing Mode: %s", preset_name,
|
||||
ESP_LOGCONFIG(TAG, " Default Swing Mode: %s",
|
||||
LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
|
||||
}
|
||||
}
|
||||
@@ -1106,6 +1120,7 @@ ThermostatClimate::ThermostatClimate()
|
||||
heat_action_trigger_(new Trigger<>()),
|
||||
supplemental_heat_action_trigger_(new Trigger<>()),
|
||||
heat_mode_trigger_(new Trigger<>()),
|
||||
heat_cool_mode_trigger_(new Trigger<>()),
|
||||
auto_mode_trigger_(new Trigger<>()),
|
||||
idle_action_trigger_(new Trigger<>()),
|
||||
off_mode_trigger_(new Trigger<>()),
|
||||
@@ -1274,6 +1289,7 @@ Trigger<> *ThermostatClimate::get_cool_mode_trigger() const { return this->cool_
|
||||
Trigger<> *ThermostatClimate::get_dry_mode_trigger() const { return this->dry_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_only_mode_trigger() const { return this->fan_only_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_heat_mode_trigger() const { return this->heat_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_heat_cool_mode_trigger() const { return this->heat_cool_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_off_mode_trigger() const { return this->off_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_mode_on_trigger() const { return this->fan_mode_on_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_mode_off_trigger() const { return this->fan_mode_off_trigger_; }
|
||||
@@ -1295,52 +1311,55 @@ Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->p
|
||||
void ThermostatClimate::dump_config() {
|
||||
LOG_CLIMATE("", "Thermostat", this);
|
||||
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" On boot, restore from: %s\n"
|
||||
" Use Start-up Delay: %s",
|
||||
this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY",
|
||||
YESNO(this->use_startup_delay_));
|
||||
if (this->supports_two_points_) {
|
||||
ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Use Start-up Delay: %s", YESNO(this->use_startup_delay_));
|
||||
if (this->supports_cool_) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Cooling Parameters:\n"
|
||||
" Deadband: %.1f°C\n"
|
||||
" Overrun: %.1f°C",
|
||||
this->cooling_deadband_, this->cooling_overrun_);
|
||||
if ((this->supplemental_cool_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) > 0)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Supplemental Delta: %.1f°C\n"
|
||||
" Maximum Run Time: %" PRIu32 "s",
|
||||
this->supplemental_cool_delta_,
|
||||
this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) / 1000);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Overrun: %.1f°C\n"
|
||||
" Minimum Off Time: %" PRIu32 "s\n"
|
||||
" Minimum Run Time: %" PRIu32 "s",
|
||||
this->cooling_deadband_, this->cooling_overrun_,
|
||||
this->timer_duration_(thermostat::TIMER_COOLING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::TIMER_COOLING_ON) / 1000);
|
||||
if ((this->supplemental_cool_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) > 0)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Maximum Run Time: %" PRIu32 "s\n"
|
||||
" Supplemental Delta: %.1f°C",
|
||||
this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) / 1000,
|
||||
this->supplemental_cool_delta_);
|
||||
}
|
||||
}
|
||||
if (this->supports_heat_) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Heating Parameters:\n"
|
||||
" Deadband: %.1f°C\n"
|
||||
" Overrun: %.1f°C",
|
||||
this->heating_deadband_, this->heating_overrun_);
|
||||
if ((this->supplemental_heat_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) > 0)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Supplemental Delta: %.1f°C\n"
|
||||
" Maximum Run Time: %" PRIu32 "s",
|
||||
this->supplemental_heat_delta_,
|
||||
this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) / 1000);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Overrun: %.1f°C\n"
|
||||
" Minimum Off Time: %" PRIu32 "s\n"
|
||||
" Minimum Run Time: %" PRIu32 "s",
|
||||
this->heating_deadband_, this->heating_overrun_,
|
||||
this->timer_duration_(thermostat::TIMER_HEATING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::TIMER_HEATING_ON) / 1000);
|
||||
if ((this->supplemental_heat_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) > 0)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Maximum Run Time: %" PRIu32 "s\n"
|
||||
" Supplemental Delta: %.1f°C",
|
||||
this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) / 1000,
|
||||
this->supplemental_heat_delta_);
|
||||
}
|
||||
}
|
||||
if (this->supports_fan_only_) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Fanning Minimum Off Time: %" PRIu32 "s\n"
|
||||
" Fanning Minimum Run Time: %" PRIu32 "s",
|
||||
" Fan Parameters:\n"
|
||||
" Minimum Off Time: %" PRIu32 "s\n"
|
||||
" Minimum Run Time: %" PRIu32 "s",
|
||||
this->timer_duration_(thermostat::TIMER_FANNING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::TIMER_FANNING_ON) / 1000);
|
||||
}
|
||||
@@ -1351,8 +1370,8 @@ void ThermostatClimate::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %" PRIu32 "s",
|
||||
this->timer_duration_(thermostat::TIMER_FAN_MODE) / 1000);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Minimum Idle Time: %" PRIu32 "s", this->timer_[thermostat::TIMER_IDLE_ON].time / 1000);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Minimum Idle Time: %" PRIu32 "s\n"
|
||||
" Supported MODES:\n"
|
||||
" AUTO: %s\n"
|
||||
" HEAT/COOL: %s\n"
|
||||
@@ -1362,8 +1381,9 @@ void ThermostatClimate::dump_config() {
|
||||
" FAN_ONLY: %s\n"
|
||||
" FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n"
|
||||
" FAN_ONLY_COOLING: %s",
|
||||
YESNO(this->supports_auto_), YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_),
|
||||
YESNO(this->supports_cool_), YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
|
||||
this->timer_[thermostat::TIMER_IDLE_ON].time / 1000, YESNO(this->supports_auto_),
|
||||
YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_), YESNO(this->supports_cool_),
|
||||
YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
|
||||
YESNO(this->supports_fan_only_action_uses_fan_mode_timer_), YESNO(this->supports_fan_only_cooling_));
|
||||
if (this->supports_cool_) {
|
||||
ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_));
|
||||
@@ -1382,40 +1402,39 @@ void ThermostatClimate::dump_config() {
|
||||
" MIDDLE: %s\n"
|
||||
" FOCUS: %s\n"
|
||||
" DIFFUSE: %s\n"
|
||||
" QUIET: %s",
|
||||
YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
|
||||
YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
|
||||
YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
|
||||
YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
|
||||
YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_));
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" QUIET: %s\n"
|
||||
" Supported SWING MODES:\n"
|
||||
" BOTH: %s\n"
|
||||
" OFF: %s\n"
|
||||
" HORIZONTAL: %s\n"
|
||||
" VERTICAL: %s\n"
|
||||
" Supports TWO SET POINTS: %s",
|
||||
YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
|
||||
YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
|
||||
YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
|
||||
YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
|
||||
YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_),
|
||||
YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_),
|
||||
YESNO(this->supports_swing_mode_horizontal_), YESNO(this->supports_swing_mode_vertical_),
|
||||
YESNO(this->supports_two_points_));
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported PRESETS: ");
|
||||
for (auto &it : this->preset_config_) {
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first));
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true));
|
||||
this->dump_preset_config_(preset_name, it.second, it.first == this->default_preset_);
|
||||
if (!this->preset_config_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Supported PRESETS:");
|
||||
for (auto &it : this->preset_config_) {
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first));
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, it.second);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS: ");
|
||||
for (auto &it : this->custom_preset_config_) {
|
||||
const auto *preset_name = it.first.c_str();
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true));
|
||||
this->dump_preset_config_(preset_name, it.second, it.first == this->default_custom_preset_);
|
||||
if (!this->custom_preset_config_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
|
||||
for (auto &it : this->custom_preset_config_) {
|
||||
const auto *preset_name = it.first.c_str();
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_custom_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, it.second);
|
||||
}
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " On boot, restore from: %s",
|
||||
this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY");
|
||||
}
|
||||
|
||||
ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default;
|
||||
|
@@ -6,9 +6,9 @@
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace thermostat {
|
||||
@@ -24,6 +24,7 @@ enum ThermostatClimateTimerIndex : uint8_t {
|
||||
TIMER_HEATING_OFF = 7,
|
||||
TIMER_HEATING_ON = 8,
|
||||
TIMER_IDLE_ON = 9,
|
||||
TIMER_COUNT = 10,
|
||||
};
|
||||
|
||||
enum OnBootRestoreFrom : uint8_t {
|
||||
@@ -131,6 +132,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *get_dry_mode_trigger() const;
|
||||
Trigger<> *get_fan_only_mode_trigger() const;
|
||||
Trigger<> *get_heat_mode_trigger() const;
|
||||
Trigger<> *get_heat_cool_mode_trigger() const;
|
||||
Trigger<> *get_off_mode_trigger() const;
|
||||
Trigger<> *get_fan_mode_on_trigger() const;
|
||||
Trigger<> *get_fan_mode_off_trigger() const;
|
||||
@@ -163,9 +165,10 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
/// Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!)
|
||||
climate::ClimateFanMode locked_fan_mode();
|
||||
/// Set point and hysteresis validation
|
||||
bool hysteresis_valid(); // returns true if valid
|
||||
bool hysteresis_valid(); // returns true if valid
|
||||
bool limit_setpoints_for_heat_cool(); // returns true if set points should be further limited within visual range
|
||||
void validate_target_temperature();
|
||||
void validate_target_temperatures();
|
||||
void validate_target_temperatures(bool pin_target_temperature_high);
|
||||
void validate_target_temperature_low();
|
||||
void validate_target_temperature_high();
|
||||
|
||||
@@ -241,12 +244,28 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
bool supplemental_cooling_required_();
|
||||
bool supplemental_heating_required_();
|
||||
|
||||
void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
|
||||
bool is_default_preset);
|
||||
void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config);
|
||||
|
||||
/// Minimum allowable duration in seconds for action timers
|
||||
const uint8_t min_timer_duration_{1};
|
||||
|
||||
/// Store previously-known states
|
||||
///
|
||||
/// These are used to determine when a trigger/action needs to be called
|
||||
climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON};
|
||||
climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF};
|
||||
climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF};
|
||||
|
||||
/// The current supplemental action
|
||||
climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF};
|
||||
|
||||
/// Default standard preset to use on start up
|
||||
climate::ClimatePreset default_preset_{};
|
||||
|
||||
/// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior
|
||||
/// state will attempt to be restored if possible
|
||||
OnBootRestoreFrom on_boot_restore_from_{OnBootRestoreFrom::MEMORY};
|
||||
|
||||
/// Whether the controller supports auto/cooling/drying/fanning/heating.
|
||||
///
|
||||
/// A false value for any given attribute means that the controller has no such action
|
||||
@@ -362,9 +381,15 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *supplemental_heat_action_trigger_{nullptr};
|
||||
Trigger<> *heat_mode_trigger_{nullptr};
|
||||
|
||||
/// The trigger to call when the controller should switch to heat/cool mode.
|
||||
///
|
||||
/// In heat/cool mode, the controller will enable heating/cooling as necessary and switch
|
||||
/// to idle when the temperature is within the thresholds/set points.
|
||||
Trigger<> *heat_cool_mode_trigger_{nullptr};
|
||||
|
||||
/// The trigger to call when the controller should switch to auto mode.
|
||||
///
|
||||
/// In auto mode, the controller will enable heating/cooling as necessary and switch
|
||||
/// In auto mode, the controller will enable heating/cooling as supported/necessary and switch
|
||||
/// to idle when the temperature is within the thresholds/set points.
|
||||
Trigger<> *auto_mode_trigger_{nullptr};
|
||||
|
||||
@@ -438,35 +463,21 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *prev_mode_trigger_{nullptr};
|
||||
Trigger<> *prev_swing_mode_trigger_{nullptr};
|
||||
|
||||
/// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior
|
||||
/// state will attempt to be restored if possible
|
||||
OnBootRestoreFrom on_boot_restore_from_{OnBootRestoreFrom::MEMORY};
|
||||
|
||||
/// Store previously-known states
|
||||
///
|
||||
/// These are used to determine when a trigger/action needs to be called
|
||||
climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF};
|
||||
climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON};
|
||||
climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF};
|
||||
climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF};
|
||||
|
||||
/// Default standard preset to use on start up
|
||||
climate::ClimatePreset default_preset_{};
|
||||
/// Default custom preset to use on start up
|
||||
std::string default_custom_preset_{};
|
||||
|
||||
/// Climate action timers
|
||||
std::vector<ThermostatClimateTimer> timer_{
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)},
|
||||
std::array<ThermostatClimateTimer, TIMER_COUNT> timer_{
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)),
|
||||
};
|
||||
|
||||
/// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc)
|
||||
|
@@ -424,6 +424,7 @@ CONF_HEAD = "head"
|
||||
CONF_HEADING = "heading"
|
||||
CONF_HEARTBEAT = "heartbeat"
|
||||
CONF_HEAT_ACTION = "heat_action"
|
||||
CONF_HEAT_COOL_MODE = "heat_cool_mode"
|
||||
CONF_HEAT_DEADBAND = "heat_deadband"
|
||||
CONF_HEAT_MODE = "heat_mode"
|
||||
CONF_HEAT_OVERRUN = "heat_overrun"
|
||||
|
Reference in New Issue
Block a user