Thermostat remove deprecated config (#3643)

* Raise errors for all the now deprecated options

* Fix CONF_DEFAULT_PRESET detection

* Stop attempting to set the non-existent normal_config

* Add support for default presets

* Fix correct detection of Two Point temperature mode

* Fix lint issues

* Fix tests

* Generate correct yaml for equivalent configurations

* Remove debug code

* Only set default preset if the thermostat does not have state to restore

* Add restore_default_preset_on_boot option
If set to True then the default_preset will be applied on every boot. If False (Default) state will be restored from memory as per prior versions

* Apply lint suggestions

* Switch from restore_default_preset_on_boot to an enum for startup_behavior
This gives better self-documentation as well as the option for extending to other options down the track

* Lint fixes

* Rename startup_behavior to on_boot_restore_from
This removes any issues with different English locales

* Fix comparable_preset yaml output alignment

* Add dump of on_boot_restore_from setting

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
Michael Davidson
2022-09-26 12:59:04 +10:00
committed by GitHub
parent ce2e161b08
commit 8095db6715
4 changed files with 161 additions and 69 deletions
+100 -47
View File
@@ -69,6 +69,8 @@ from esphome.const import (
)
CONF_PRESET_CHANGE = "preset_change"
CONF_DEFAULT_PRESET = "default_preset"
CONF_ON_BOOT_RESTORE_FROM = "on_boot_restore_from"
CODEOWNERS = ["@kbx81"]
@@ -80,6 +82,13 @@ ThermostatClimate = thermostat_ns.class_(
ThermostatClimateTargetTempConfig = thermostat_ns.struct(
"ThermostatClimateTargetTempConfig"
)
OnBootRestoreFrom = thermostat_ns.enum("OnBootRestoreFrom")
ON_BOOT_RESTORE_FROM = {
"MEMORY": OnBootRestoreFrom.MEMORY,
"DEFAULT_PRESET": OnBootRestoreFrom.DEFAULT_PRESET,
}
validate_on_boot_restore_from = cv.enum(ON_BOOT_RESTORE_FROM, upper=True)
ClimateMode = climate_ns.enum("ClimateMode")
CLIMATE_MODES = {
"OFF": ClimateMode.CLIMATE_MODE_OFF,
@@ -125,6 +134,17 @@ def validate_temperature_preset(preset, root_config, name, requirements):
)
def generate_comparable_preset(config, name):
comparable_preset = f"{CONF_PRESET}:\n" f" - {CONF_NAME}: {name}\n"
if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_LOW}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]}\n"
if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_HIGH}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]}\n"
return comparable_preset
def validate_thermostat(config):
# verify corresponding action(s) exist(s) for any defined climate mode or action
requirements = {
@@ -277,13 +297,32 @@ def validate_thermostat(config):
CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION],
}
# Validate temperature requirements for default configuraation
validate_temperature_preset(config, config, "default", requirements)
# Legacy high/low configs
if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
comparable_preset = generate_comparable_preset(config, "Your new preset")
# Validate temperature requirements for away configuration
raise cv.Invalid(
f"{CONF_DEFAULT_TARGET_TEMPERATURE_LOW} is no longer valid. Please switch to using a preset for an equivalent experience.\nEquivalent configuration:\n\n"
f"{comparable_preset}"
)
if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
comparable_preset = generate_comparable_preset(config, "Your new preset")
raise cv.Invalid(
f"{CONF_DEFAULT_TARGET_TEMPERATURE_HIGH} is no longer valid. Please switch to using a preset for an equivalent experience.\nEquivalent configuration:\n\n"
f"{comparable_preset}"
)
# Legacy away mode - raise an error instructing the user to switch to presets
if CONF_AWAY_CONFIG in config:
away = config[CONF_AWAY_CONFIG]
validate_temperature_preset(away, config, "away", requirements)
comparable_preset = generate_comparable_preset(config[CONF_AWAY_CONFIG], "Away")
raise cv.Invalid(
f"{CONF_AWAY_CONFIG} is no longer valid. Please switch to using a preset named "
"Away"
" for an equivalent experience.\nEquivalent configuration:\n\n"
f"{comparable_preset}"
)
# Validate temperature requirements for presets
if CONF_PRESET in config:
@@ -292,7 +331,12 @@ def validate_thermostat(config):
preset_config, config, preset_config[CONF_NAME], requirements
)
# Verify default climate mode is valid given above configuration
# Warn about using the removed CONF_DEFAULT_MODE and advise users
if CONF_DEFAULT_MODE in config and config[CONF_DEFAULT_MODE] is not None:
raise cv.Invalid(
f"{CONF_DEFAULT_MODE} is no longer valid. Please switch to using presets and specify a {CONF_DEFAULT_PRESET}."
)
default_mode = config[CONF_DEFAULT_MODE]
requirements = {
"HEAT_COOL": [CONF_COOL_ACTION, CONF_HEAT_ACTION],
@@ -403,6 +447,38 @@ def validate_thermostat(config):
f"{CONF_SWING_MODE} is set to {swing_mode} for {preset_config[CONF_NAME]} but {req} is not present in the configuration"
)
# If a default preset is requested then ensure that preset is defined
if CONF_DEFAULT_PRESET in config:
default_preset = config[CONF_DEFAULT_PRESET]
if CONF_PRESET not in config:
raise cv.Invalid(
f"{CONF_DEFAULT_PRESET} is specified but no presets are defined"
)
presets = config[CONF_PRESET]
found_preset = False
for preset in presets:
if preset[CONF_NAME] == default_preset:
found_preset = True
break
if found_preset is False:
raise cv.Invalid(
f"{CONF_DEFAULT_PRESET} set to '{default_preset}' but no such preset has been defined. Available presets: {[preset[CONF_NAME] for preset in presets]}"
)
# If restoring default preset on boot is true then ensure we have a default preset
if (
CONF_ON_BOOT_RESTORE_FROM in config
and config[CONF_ON_BOOT_RESTORE_FROM] is OnBootRestoreFrom.DEFAULT_PRESET
):
if CONF_DEFAULT_PRESET not in config:
raise cv.Invalid(
f"{CONF_DEFAULT_PRESET} must be defined to use {CONF_ON_BOOT_RESTORE_FROM} in DEFAULT_PRESET mode"
)
if config[CONF_FAN_WITH_COOLING] is True and CONF_FAN_ONLY_ACTION not in config:
raise cv.Invalid(
f"{CONF_FAN_ONLY_ACTION} must be defined to use {CONF_FAN_WITH_COOLING}"
@@ -502,9 +578,8 @@ CONFIG_SCHEMA = cv.All(
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_MODE, default=None): cv.valid,
cv.Optional(CONF_DEFAULT_PRESET): cv.templatable(cv.string),
cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
cv.Optional(
@@ -542,6 +617,7 @@ CONFIG_SCHEMA = cv.All(
}
),
cv.Optional(CONF_PRESET): cv.ensure_list(PRESET_CONFIG_SCHEMA),
cv.Optional(CONF_ON_BOOT_RESTORE_FROM): validate_on_boot_restore_from,
cv.Optional(CONF_PRESET_CHANGE): automation.validate_automation(
single=True
),
@@ -564,9 +640,10 @@ async def to_code(config):
CONF_COOL_ACTION in config
or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config)
)
if two_points_available:
cg.add(var.set_supports_two_points(True))
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]
@@ -579,23 +656,6 @@ async def to_code(config):
cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND]))
cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN]))
if two_points_available is True:
cg.add(var.set_supports_two_points(True))
normal_config = ThermostatClimateTargetTempConfig(
config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
)
elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
cg.add(var.set_supports_two_points(False))
normal_config = ThermostatClimateTargetTempConfig(
config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
)
elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
cg.add(var.set_supports_two_points(False))
normal_config = ThermostatClimateTargetTempConfig(
config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]
)
if CONF_MAX_COOLING_RUN_TIME in config:
cg.add(
var.set_cooling_maximum_run_time_in_sec(config[CONF_MAX_COOLING_RUN_TIME])
@@ -661,7 +721,6 @@ async def to_code(config):
cg.add(var.set_supports_fan_with_heating(config[CONF_FAN_WITH_HEATING]))
cg.add(var.set_use_startup_delay(config[CONF_STARTUP_DELAY]))
cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_HOME, normal_config))
await automation.build_automation(
var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION]
@@ -808,27 +867,8 @@ async def to_code(config):
config[CONF_TARGET_TEMPERATURE_CHANGE_ACTION],
)
if CONF_AWAY_CONFIG in config:
away = config[CONF_AWAY_CONFIG]
if two_points_available is True:
away_config = ThermostatClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
)
elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away:
away_config = ThermostatClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
)
elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in away:
away_config = ThermostatClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]
)
cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_AWAY, away_config))
if CONF_PRESET in config:
for preset_config in config[CONF_PRESET]:
name = preset_config[CONF_NAME]
standard_preset = None
if name.upper() in climate.CLIMATE_PRESETS:
@@ -872,6 +912,19 @@ async def to_code(config):
else:
cg.add(var.set_custom_preset_config(name, preset_target_variable))
if CONF_DEFAULT_PRESET in config:
default_preset_name = config[CONF_DEFAULT_PRESET]
# if the name is a built in preset use the appropriate naming format
if default_preset_name.upper() in climate.CLIMATE_PRESETS:
climate_preset = climate.CLIMATE_PRESETS[default_preset_name.upper()]
cg.add(var.set_default_preset(climate_preset))
else:
cg.add(var.set_default_preset(default_preset_name))
if CONF_ON_BOOT_RESTORE_FROM in config:
cg.add(var.set_on_boot_restore_from(config[CONF_ON_BOOT_RESTORE_FROM]))
if CONF_PRESET_CHANGE in config:
await automation.build_automation(
var.get_preset_change_trigger(), [], config[CONF_PRESET_CHANGE]