Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 32 additions & 16 deletions homeassistant/components/generic_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_ACTIVITY,
PRESET_AWAY,
PRESET_COMFORT,
PRESET_HOME,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed other implementations of the climate component but does it make sense?
It seems confusing to me to have the PRESET_HOME and None because I understand that they are the same use case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely different. Consider None like manual or hold. None is the only preset you can't set.

PRESET_NONE,
PRESET_SLEEP,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
Expand Down Expand Up @@ -64,10 +68,20 @@
CONF_HOT_TOLERANCE = "hot_tolerance"
CONF_KEEP_ALIVE = "keep_alive"
CONF_INITIAL_HVAC_MODE = "initial_hvac_mode"
CONF_AWAY_TEMP = "away_temp"
CONF_PRECISION = "precision"
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE

CONF_PRESETS = {
p: f"{p}_temp"
for p in (
PRESET_AWAY,
PRESET_COMFORT,
PRESET_HOME,
PRESET_SLEEP,
PRESET_ACTIVITY,
)
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HEATER): cv.entity_id,
Expand All @@ -84,13 +98,12 @@
vol.Optional(CONF_INITIAL_HVAC_MODE): vol.In(
[HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]
),
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float),
vol.Optional(CONF_PRECISION): vol.In(
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]
),
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
).extend({vol.Optional(v): vol.Coerce(float) for (k, v) in CONF_PRESETS.items()})


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
Expand All @@ -110,7 +123,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
keep_alive = config.get(CONF_KEEP_ALIVE)
initial_hvac_mode = config.get(CONF_INITIAL_HVAC_MODE)
away_temp = config.get(CONF_AWAY_TEMP)
presets = {
key: config[value] for key, value in CONF_PRESETS.items() if value in config
}
precision = config.get(CONF_PRECISION)
unit = hass.config.units.temperature_unit
unique_id = config.get(CONF_UNIQUE_ID)
Expand All @@ -130,7 +145,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
hot_tolerance,
keep_alive,
initial_hvac_mode,
away_temp,
presets,
precision,
unit,
unique_id,
Expand All @@ -156,7 +171,7 @@ def __init__(
hot_tolerance,
keep_alive,
initial_hvac_mode,
away_temp,
presets,
precision,
unit,
unique_id,
Expand All @@ -171,7 +186,7 @@ def __init__(
self._hot_tolerance = hot_tolerance
self._keep_alive = keep_alive
self._hvac_mode = initial_hvac_mode
self._saved_target_temp = target_temp or away_temp
self._saved_target_temp = target_temp or next(iter(presets.values()), None)
self._temp_precision = precision
if self.ac_mode:
self._hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
Expand All @@ -187,12 +202,12 @@ def __init__(
self._unit = unit
self._unique_id = unique_id
self._support_flags = SUPPORT_FLAGS
if away_temp:
if len(presets):
self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE
self._attr_preset_modes = [PRESET_NONE, PRESET_AWAY]
self._attr_preset_modes = [PRESET_NONE] + list(presets.keys())
else:
self._attr_preset_modes = [PRESET_NONE]
self._away_temp = away_temp
self._presets = presets

async def async_added_to_hass(self):
"""Run when entity about to be added."""
Expand Down Expand Up @@ -530,14 +545,15 @@ async def async_set_preset_mode(self, preset_mode: str):
if preset_mode == self._attr_preset_mode:
# I don't think we need to call async_write_ha_state if we didn't change the state
return
if preset_mode == PRESET_AWAY:
self._attr_preset_mode = PRESET_AWAY
self._saved_target_temp = self._target_temp
self._target_temp = self._away_temp
await self._async_control_heating(force=True)
elif preset_mode == PRESET_NONE:
if preset_mode == PRESET_NONE:
self._attr_preset_mode = PRESET_NONE
self._target_temp = self._saved_target_temp
await self._async_control_heating(force=True)
else:
if self._attr_preset_mode == PRESET_NONE:
self._saved_target_temp = self._target_temp
self._attr_preset_mode = preset_mode
self._target_temp = self._presets[preset_mode]
await self._async_control_heating(force=True)

self.async_write_ha_state()
67 changes: 55 additions & 12 deletions tests/components/generic_thermostat/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_ACTIVITY,
PRESET_AWAY,
PRESET_COMFORT,
PRESET_HOME,
PRESET_NONE,
PRESET_SLEEP,
)
from homeassistant.components.generic_thermostat import (
DOMAIN as GENERIC_THERMOSTAT_DOMAIN,
Expand Down Expand Up @@ -209,6 +213,10 @@ async def setup_comp_2(hass):
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"away_temp": 16,
"sleep_temp": 17,
"home_temp": 19,
"comfort_temp": 20,
"activity_temp": 21,
"initial_hvac_mode": HVAC_MODE_HEAT,
}
},
Expand Down Expand Up @@ -288,38 +296,73 @@ async def test_set_target_temp(hass, setup_comp_2):
assert state.attributes.get("temperature") == 30.0


async def test_set_away_mode(hass, setup_comp_2):
@pytest.mark.parametrize(
"preset,temp",
[
(PRESET_NONE, 23),
(PRESET_AWAY, 16),
(PRESET_COMFORT, 20),
(PRESET_HOME, 19),
(PRESET_SLEEP, 17),
(PRESET_ACTIVITY, 21),
],
)
async def test_set_away_mode(hass, setup_comp_2, preset, temp):
"""Test the setting away mode."""
await common.async_set_temperature(hass, 23)
await common.async_set_preset_mode(hass, PRESET_AWAY)
await common.async_set_preset_mode(hass, preset)
state = hass.states.get(ENTITY)
assert state.attributes.get("temperature") == 16


async def test_set_away_mode_and_restore_prev_temp(hass, setup_comp_2):
assert state.attributes.get("temperature") == temp


@pytest.mark.parametrize(
"preset,temp",
[
(PRESET_NONE, 23),
(PRESET_AWAY, 16),
(PRESET_COMFORT, 20),
(PRESET_HOME, 19),
(PRESET_SLEEP, 17),
(PRESET_ACTIVITY, 21),
],
)
async def test_set_away_mode_and_restore_prev_temp(hass, setup_comp_2, preset, temp):
"""Test the setting and removing away mode.

Verify original temperature is restored.
"""
await common.async_set_temperature(hass, 23)
await common.async_set_preset_mode(hass, PRESET_AWAY)
await common.async_set_preset_mode(hass, preset)
state = hass.states.get(ENTITY)
assert state.attributes.get("temperature") == 16
assert state.attributes.get("temperature") == temp
await common.async_set_preset_mode(hass, PRESET_NONE)
state = hass.states.get(ENTITY)
assert state.attributes.get("temperature") == 23


async def test_set_away_mode_twice_and_restore_prev_temp(hass, setup_comp_2):
@pytest.mark.parametrize(
"preset,temp",
[
(PRESET_NONE, 23),
(PRESET_AWAY, 16),
(PRESET_COMFORT, 20),
(PRESET_HOME, 19),
(PRESET_SLEEP, 17),
(PRESET_ACTIVITY, 21),
],
)
async def test_set_away_mode_twice_and_restore_prev_temp(
hass, setup_comp_2, preset, temp
):
"""Test the setting away mode twice in a row.

Verify original temperature is restored.
"""
await common.async_set_temperature(hass, 23)
await common.async_set_preset_mode(hass, PRESET_AWAY)
await common.async_set_preset_mode(hass, PRESET_AWAY)
await common.async_set_preset_mode(hass, preset)
await common.async_set_preset_mode(hass, preset)
state = hass.states.get(ENTITY)
assert state.attributes.get("temperature") == 16
assert state.attributes.get("temperature") == temp
await common.async_set_preset_mode(hass, PRESET_NONE)
state = hass.states.get(ENTITY)
assert state.attributes.get("temperature") == 23
Expand Down