mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 01:07:45 +01:00
Thermostat fixes+updates 1 (#2032)
Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
parent
c9062599df
commit
0a32321c85
4 changed files with 107 additions and 17 deletions
|
@ -7,6 +7,7 @@ from esphome.const import (
|
|||
CONF_AWAY_CONFIG,
|
||||
CONF_COOL_ACTION,
|
||||
CONF_COOL_MODE,
|
||||
CONF_DEFAULT_MODE,
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH,
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_LOW,
|
||||
CONF_DRY_ACTION,
|
||||
|
@ -33,10 +34,12 @@ from esphome.const import (
|
|||
CONF_SWING_HORIZONTAL_ACTION,
|
||||
CONF_SWING_OFF_ACTION,
|
||||
CONF_SWING_VERTICAL_ACTION,
|
||||
CONF_TARGET_TEMPERATURE_CHANGE_ACTION,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
|
||||
climate_ns = cg.esphome_ns.namespace("climate")
|
||||
thermostat_ns = cg.esphome_ns.namespace("thermostat")
|
||||
ThermostatClimate = thermostat_ns.class_(
|
||||
"ThermostatClimate", climate.Climate, cg.Component
|
||||
|
@ -44,6 +47,17 @@ ThermostatClimate = thermostat_ns.class_(
|
|||
ThermostatClimateTargetTempConfig = thermostat_ns.struct(
|
||||
"ThermostatClimateTargetTempConfig"
|
||||
)
|
||||
ClimateMode = climate_ns.enum("ClimateMode")
|
||||
CLIMATE_MODES = {
|
||||
"OFF": ClimateMode.CLIMATE_MODE_OFF,
|
||||
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
|
||||
"COOL": ClimateMode.CLIMATE_MODE_COOL,
|
||||
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
|
||||
"DRY": ClimateMode.CLIMATE_MODE_DRY,
|
||||
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
|
||||
"AUTO": ClimateMode.CLIMATE_MODE_AUTO,
|
||||
}
|
||||
validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
|
||||
|
||||
|
||||
def validate_thermostat(config):
|
||||
|
@ -141,6 +155,21 @@ def validate_thermostat(config):
|
|||
CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION
|
||||
)
|
||||
)
|
||||
# verify default climate mode is valid given above configuration
|
||||
default_mode = config[CONF_DEFAULT_MODE]
|
||||
requirements = {
|
||||
"HEAT_COOL": [CONF_COOL_ACTION, CONF_HEAT_ACTION],
|
||||
"COOL": [CONF_COOL_ACTION],
|
||||
"HEAT": [CONF_HEAT_ACTION],
|
||||
"DRY": [CONF_DRY_ACTION],
|
||||
"FAN_ONLY": [CONF_FAN_ONLY_ACTION],
|
||||
"AUTO": [CONF_COOL_ACTION, CONF_HEAT_ACTION],
|
||||
}.get(default_mode, [])
|
||||
for req in requirements:
|
||||
if req not in config:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_DEFAULT_MODE} is set to {default_mode} but {req} is not present in the configuration"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
@ -204,6 +233,12 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_SWING_VERTICAL_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_TARGET_TEMPERATURE_CHANGE_ACTION
|
||||
): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_DEFAULT_MODE, default="OFF"): cv.templatable(
|
||||
validate_climate_mode
|
||||
),
|
||||
cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
||||
cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||
cv.Optional(CONF_HYSTERESIS, default=0.5): cv.temperature,
|
||||
|
@ -233,6 +268,7 @@ 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_sensor(sens))
|
||||
cg.add(var.set_hysteresis(config[CONF_HYSTERESIS]))
|
||||
|
||||
|
@ -380,6 +416,12 @@ async def to_code(config):
|
|||
config[CONF_SWING_VERTICAL_ACTION],
|
||||
)
|
||||
cg.add(var.set_supports_swing_mode_vertical(True))
|
||||
if CONF_TARGET_TEMPERATURE_CHANGE_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_temperature_change_trigger(),
|
||||
[],
|
||||
config[CONF_TARGET_TEMPERATURE_CHANGE_ACTION],
|
||||
)
|
||||
|
||||
if CONF_AWAY_CONFIG in config:
|
||||
away = config[CONF_AWAY_CONFIG]
|
||||
|
|
|
@ -21,7 +21,7 @@ void ThermostatClimate::setup() {
|
|||
restore->to_call(this).perform();
|
||||
} else {
|
||||
// restore from defaults, change_away handles temps for us
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
this->mode = this->default_mode_;
|
||||
this->change_away_(false);
|
||||
}
|
||||
// refresh the climate action based on the restored settings
|
||||
|
@ -35,9 +35,18 @@ void ThermostatClimate::refresh() {
|
|||
this->switch_to_action_(compute_action_());
|
||||
this->switch_to_fan_mode_(this->fan_mode.value());
|
||||
this->switch_to_swing_mode_(this->swing_mode);
|
||||
this->check_temperature_change_trigger_();
|
||||
this->publish_state();
|
||||
}
|
||||
void ThermostatClimate::control(const climate::ClimateCall &call) {
|
||||
if (call.get_preset().has_value()) {
|
||||
// setup_complete_ blocks modifying/resetting the temps immediately after boot
|
||||
if (this->setup_complete_) {
|
||||
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
|
||||
} else {
|
||||
this->preset = *call.get_preset();
|
||||
}
|
||||
}
|
||||
if (call.get_mode().has_value())
|
||||
this->mode = *call.get_mode();
|
||||
if (call.get_fan_mode().has_value())
|
||||
|
@ -50,15 +59,6 @@ void ThermostatClimate::control(const climate::ClimateCall &call) {
|
|||
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();
|
||||
if (call.get_preset().has_value()) {
|
||||
// setup_complete_ blocks modifying/resetting the temps immediately after boot
|
||||
if (this->setup_complete_) {
|
||||
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
|
||||
} else {
|
||||
this->preset = *call.get_preset();
|
||||
;
|
||||
}
|
||||
}
|
||||
// set point validation
|
||||
if (this->supports_two_points_) {
|
||||
if (this->target_temperature_low < this->get_traits().get_visual_min_temperature())
|
||||
|
@ -128,7 +128,16 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
|||
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) {
|
||||
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_))
|
||||
|
@ -153,9 +162,6 @@ climate::ClimateAction ThermostatClimate::compute_action_() {
|
|||
case climate::CLIMATE_MODE_DRY:
|
||||
target_action = climate::CLIMATE_ACTION_DRYING;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
target_action = climate::CLIMATE_ACTION_OFF;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
|
@ -200,9 +206,6 @@ climate::ClimateAction ThermostatClimate::compute_action_() {
|
|||
case climate::CLIMATE_MODE_DRY:
|
||||
target_action = climate::CLIMATE_ACTION_DRYING;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
target_action = climate::CLIMATE_ACTION_OFF;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
if (this->supports_cool_) {
|
||||
if (this->current_temperature > this->target_temperature + this->hysteresis_)
|
||||
|
@ -410,6 +413,30 @@ 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
|
||||
if ((this->prev_target_temperature_low_ == this->target_temperature_low) &&
|
||||
(this->prev_target_temperature_high_ == this->target_temperature_high) && this->setup_complete_) {
|
||||
return; // nothing changed, no reason to trigger
|
||||
} else {
|
||||
// save the new temperatures so we can check them again later; the trigger will fire below
|
||||
this->prev_target_temperature_low_ = this->target_temperature_low;
|
||||
this->prev_target_temperature_high_ = this->target_temperature_high;
|
||||
}
|
||||
} else {
|
||||
if ((this->prev_target_temperature_ == this->target_temperature) && this->setup_complete_) {
|
||||
return; // nothing changed, no reason to trigger
|
||||
} else {
|
||||
// save the new temperature so we can check it again later; the trigger will fire below
|
||||
this->prev_target_temperature_ = this->target_temperature;
|
||||
}
|
||||
}
|
||||
// trigger the action
|
||||
Trigger<> *trig = this->temperature_change_trigger_;
|
||||
assert(trig != nullptr);
|
||||
trig->trigger();
|
||||
}
|
||||
void ThermostatClimate::change_away_(bool away) {
|
||||
if (!away) {
|
||||
if (this->supports_two_points_) {
|
||||
|
@ -457,7 +484,9 @@ ThermostatClimate::ThermostatClimate()
|
|||
swing_mode_both_trigger_(new Trigger<>()),
|
||||
swing_mode_off_trigger_(new Trigger<>()),
|
||||
swing_mode_horizontal_trigger_(new Trigger<>()),
|
||||
swing_mode_vertical_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_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) {
|
||||
|
@ -534,6 +563,7 @@ Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() const { return this-
|
|||
Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this->swing_mode_off_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; }
|
||||
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_) {
|
||||
|
|
|
@ -26,6 +26,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
|||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_default_mode(climate::ClimateMode default_mode);
|
||||
void set_hysteresis(float hysteresis);
|
||||
void set_sensor(sensor::Sensor *sensor);
|
||||
void set_supports_auto(bool supports_auto);
|
||||
|
@ -76,6 +77,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
|||
Trigger<> *get_swing_mode_horizontal_trigger() const;
|
||||
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();
|
||||
/// Call triggers based on updated climate states (modes/actions)
|
||||
|
@ -106,6 +108,9 @@ class ThermostatClimate : public climate::Climate, public Component {
|
|||
/// Switch the climate device to the given climate swing mode.
|
||||
void switch_to_swing_mode_(climate::ClimateSwingMode swing_mode);
|
||||
|
||||
/// Check if the temperature change trigger should be called.
|
||||
void check_temperature_change_trigger_();
|
||||
|
||||
/// The sensor used for getting the current temperature
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
|
||||
|
@ -242,6 +247,9 @@ class ThermostatClimate : public climate::Climate, public Component {
|
|||
/// The trigger to call when the controller should switch the swing mode to "vertical".
|
||||
Trigger<> *swing_mode_vertical_trigger_{nullptr};
|
||||
|
||||
/// The trigger to call when the target temperature(s) change(es).
|
||||
Trigger<> *temperature_change_trigger_{nullptr};
|
||||
|
||||
/// A reference to the trigger that was previously active.
|
||||
///
|
||||
/// This is so that the previous trigger can be stopped before enabling a new one
|
||||
|
@ -256,8 +264,16 @@ class ThermostatClimate : public climate::Climate, public Component {
|
|||
/// 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::ClimateMode default_mode_{climate::CLIMATE_MODE_OFF};
|
||||
climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF};
|
||||
|
||||
/// Store previously-known temperatures
|
||||
///
|
||||
/// These are used to determine when the temperature change trigger/action needs to be called
|
||||
float prev_target_temperature_{NAN};
|
||||
float prev_target_temperature_low_{NAN};
|
||||
float prev_target_temperature_high_{NAN};
|
||||
|
||||
/// Temperature data for normal/home and away modes
|
||||
ThermostatClimateTargetTempConfig normal_config_{};
|
||||
ThermostatClimateTargetTempConfig away_config_{};
|
||||
|
|
|
@ -159,6 +159,7 @@ CONF_DAYS_OF_WEEK = "days_of_week"
|
|||
CONF_DC_PIN = "dc_pin"
|
||||
CONF_DEBOUNCE = "debounce"
|
||||
CONF_DECELERATION = "deceleration"
|
||||
CONF_DEFAULT_MODE = "default_mode"
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high"
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low"
|
||||
CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length"
|
||||
|
@ -572,6 +573,7 @@ CONF_TABLET = "tablet"
|
|||
CONF_TAG = "tag"
|
||||
CONF_TARGET = "target"
|
||||
CONF_TARGET_TEMPERATURE = "target_temperature"
|
||||
CONF_TARGET_TEMPERATURE_CHANGE_ACTION = "target_temperature_change_action"
|
||||
CONF_TARGET_TEMPERATURE_HIGH = "target_temperature_high"
|
||||
CONF_TARGET_TEMPERATURE_LOW = "target_temperature_low"
|
||||
CONF_TEMPERATURE = "temperature"
|
||||
|
|
Loading…
Reference in a new issue