hass-core/tests/components/generic_thermostat/test_climate.py
javicalle e798f415a4
Wait for switch startup in generic_thermostat (#45253)
* Better status control on restore

* Better status control on restore

* fix code coverage

* Rollback hvac_mode initialization

I think I have better understood the handling of the `hvac_mode`.
I change the approach. Now the thermostat doesn't initialize until the switch is available.

* fix pyupgrade

* fix black

* Delete test_turn_on_while_restarting

HVAC mode should not be modified by the switch.
IMHO, this test does not make sense because if the switch is turned on the thermostat is not turning on (and not changing HVAC_MODE)

* Re add turn off if HVAC is off

If HVAC_MODE is off thermostat will not control heater switch. This can be because `initial_hvac_mode`, because state defaults to or because old_state.
IMHO it is preferable to be excessively cautious.

* Update climate.py

* Change warning message

* Fix black

* Fix black
2021-03-19 15:42:45 +01:00

1435 lines
45 KiB
Python

"""The tests for the generic_thermostat."""
import datetime
from os import path
from unittest.mock import patch
import pytest
import pytz
import voluptuous as vol
from homeassistant import config as hass_config
from homeassistant.components import input_boolean, switch
from homeassistant.components.climate.const import (
ATTR_PRESET_MODE,
DOMAIN,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_NONE,
)
from homeassistant.components.generic_thermostat import (
DOMAIN as GENERIC_THERMOSTAT_DOMAIN,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
SERVICE_RELOAD,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
import homeassistant.core as ha
from homeassistant.core import DOMAIN as HASS_DOMAIN, CoreState, State, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util.unit_system import METRIC_SYSTEM
from tests.common import (
assert_setup_component,
async_fire_time_changed,
mock_restore_cache,
)
from tests.components.climate import common
ENTITY = "climate.test"
ENT_SENSOR = "sensor.test"
ENT_SWITCH = "switch.test"
HEAT_ENTITY = "climate.test_heat"
COOL_ENTITY = "climate.test_cool"
ATTR_AWAY_MODE = "away_mode"
MIN_TEMP = 3.0
MAX_TEMP = 65.0
TARGET_TEMP = 42.0
COLD_TOLERANCE = 0.5
HOT_TOLERANCE = 0.5
async def test_setup_missing_conf(hass):
"""Test set up heat_control with missing config values."""
config = {
"platform": "generic_thermostat",
"name": "test",
"target_sensor": ENT_SENSOR,
}
with assert_setup_component(0):
await async_setup_component(hass, "climate", {"climate": config})
async def test_valid_conf(hass):
"""Test set up generic_thermostat with valid config values."""
assert await async_setup_component(
hass,
"climate",
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
}
},
)
@pytest.fixture
async def setup_comp_1(hass):
"""Initialize components."""
hass.config.units = METRIC_SYSTEM
assert await async_setup_component(hass, "homeassistant", {})
await hass.async_block_till_done()
async def test_heater_input_boolean(hass, setup_comp_1):
"""Test heater switching input_boolean."""
heater_switch = "input_boolean.test"
assert await async_setup_component(
hass, input_boolean.DOMAIN, {"input_boolean": {"test": None}}
)
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"heater": heater_switch,
"target_sensor": ENT_SENSOR,
"initial_hvac_mode": HVAC_MODE_HEAT,
}
},
)
await hass.async_block_till_done()
assert STATE_OFF == hass.states.get(heater_switch).state
_setup_sensor(hass, 18)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 23)
assert STATE_ON == hass.states.get(heater_switch).state
async def test_heater_switch(hass, setup_comp_1):
"""Test heater switching test switch."""
platform = getattr(hass.components, "test.switch")
platform.init()
switch_1 = platform.ENTITIES[1]
assert await async_setup_component(
hass, switch.DOMAIN, {"switch": {"platform": "test"}}
)
await hass.async_block_till_done()
heater_switch = switch_1.entity_id
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"heater": heater_switch,
"target_sensor": ENT_SENSOR,
"initial_hvac_mode": HVAC_MODE_HEAT,
}
},
)
await hass.async_block_till_done()
assert STATE_OFF == hass.states.get(heater_switch).state
_setup_sensor(hass, 18)
await common.async_set_temperature(hass, 23)
await hass.async_block_till_done()
assert STATE_ON == hass.states.get(heater_switch).state
async def test_unique_id(hass, setup_comp_1):
"""Test heater switching input_boolean."""
unique_id = "some_unique_id"
_setup_sensor(hass, 18)
_setup_switch(hass, True)
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"unique_id": unique_id,
}
},
)
await hass.async_block_till_done()
entity_registry = er.async_get(hass)
entry = entity_registry.async_get(ENTITY)
assert entry
assert entry.unique_id == unique_id
def _setup_sensor(hass, temp):
"""Set up the test sensor."""
hass.states.async_set(ENT_SENSOR, temp)
@pytest.fixture
async def setup_comp_2(hass):
"""Initialize components."""
hass.config.units = METRIC_SYSTEM
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 2,
"hot_tolerance": 4,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"away_temp": 16,
"initial_hvac_mode": HVAC_MODE_HEAT,
}
},
)
await hass.async_block_till_done()
async def test_setup_defaults_to_unknown(hass):
"""Test the setting of defaults to unknown."""
hass.config.units = METRIC_SYSTEM
await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 2,
"hot_tolerance": 4,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"away_temp": 16,
}
},
)
await hass.async_block_till_done()
assert HVAC_MODE_OFF == hass.states.get(ENTITY).state
async def test_setup_gets_current_temp_from_sensor(hass):
"""Test that current temperature is updated on entity addition."""
hass.config.units = METRIC_SYSTEM
_setup_sensor(hass, 18)
await hass.async_block_till_done()
await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 2,
"hot_tolerance": 4,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"away_temp": 16,
}
},
)
await hass.async_block_till_done()
assert hass.states.get(ENTITY).attributes["current_temperature"] == 18
async def test_default_setup_params(hass, setup_comp_2):
"""Test the setup with default parameters."""
state = hass.states.get(ENTITY)
assert 7 == state.attributes.get("min_temp")
assert 35 == state.attributes.get("max_temp")
assert 7 == state.attributes.get("temperature")
async def test_get_hvac_modes(hass, setup_comp_2):
"""Test that the operation list returns the correct modes."""
state = hass.states.get(ENTITY)
modes = state.attributes.get("hvac_modes")
assert [HVAC_MODE_HEAT, HVAC_MODE_OFF] == modes
async def test_set_target_temp(hass, setup_comp_2):
"""Test the setting of the target temperature."""
await common.async_set_temperature(hass, 30)
state = hass.states.get(ENTITY)
assert 30.0 == state.attributes.get("temperature")
with pytest.raises(vol.Invalid):
await common.async_set_temperature(hass, None)
state = hass.states.get(ENTITY)
assert 30.0 == state.attributes.get("temperature")
async def test_set_away_mode(hass, setup_comp_2):
"""Test the setting away mode."""
await common.async_set_temperature(hass, 23)
await common.async_set_preset_mode(hass, PRESET_AWAY)
state = hass.states.get(ENTITY)
assert 16 == state.attributes.get("temperature")
async def test_set_away_mode_and_restore_prev_temp(hass, setup_comp_2):
"""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)
state = hass.states.get(ENTITY)
assert 16 == state.attributes.get("temperature")
await common.async_set_preset_mode(hass, PRESET_NONE)
state = hass.states.get(ENTITY)
assert 23 == state.attributes.get("temperature")
async def test_set_away_mode_twice_and_restore_prev_temp(hass, setup_comp_2):
"""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)
state = hass.states.get(ENTITY)
assert 16 == state.attributes.get("temperature")
await common.async_set_preset_mode(hass, PRESET_NONE)
state = hass.states.get(ENTITY)
assert 23 == state.attributes.get("temperature")
async def test_sensor_bad_value(hass, setup_comp_2):
"""Test sensor that have None as state."""
state = hass.states.get(ENTITY)
temp = state.attributes.get("current_temperature")
_setup_sensor(hass, None)
await hass.async_block_till_done()
state = hass.states.get(ENTITY)
assert temp == state.attributes.get("current_temperature")
async def test_sensor_unknown(hass):
"""Test when target sensor is Unknown."""
hass.states.async_set("sensor.unknown", STATE_UNKNOWN)
assert await async_setup_component(
hass,
"climate",
{
"climate": {
"platform": "generic_thermostat",
"name": "unknown",
"heater": ENT_SWITCH,
"target_sensor": "sensor.unknown",
}
},
)
await hass.async_block_till_done()
state = hass.states.get("climate.unknown")
assert state.attributes.get("current_temperature") is None
async def test_sensor_unavailable(hass):
"""Test when target sensor is Unavailable."""
hass.states.async_set("sensor.unavailable", STATE_UNAVAILABLE)
assert await async_setup_component(
hass,
"climate",
{
"climate": {
"platform": "generic_thermostat",
"name": "unavailable",
"heater": ENT_SWITCH,
"target_sensor": "sensor.unavailable",
}
},
)
await hass.async_block_till_done()
state = hass.states.get("climate.unavailable")
assert state.attributes.get("current_temperature") is None
async def test_set_target_temp_heater_on(hass, setup_comp_2):
"""Test if target temperature turn heater on."""
calls = _setup_switch(hass, False)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 30)
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_set_target_temp_heater_off(hass, setup_comp_2):
"""Test if target temperature turn heater off."""
calls = _setup_switch(hass, True)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 25)
assert 2 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_heater_on_within_tolerance(hass, setup_comp_2):
"""Test if temperature change doesn't turn on within tolerance."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 29)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_heater_on_outside_tolerance(hass, setup_comp_2):
"""Test if temperature change turn heater on outside cold tolerance."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 27)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_heater_off_within_tolerance(hass, setup_comp_2):
"""Test if temperature change doesn't turn off within tolerance."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 33)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_heater_off_outside_tolerance(hass, setup_comp_2):
"""Test if temperature change turn heater off outside hot tolerance."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 35)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_running_when_hvac_mode_is_off(hass, setup_comp_2):
"""Test that the switch turns off when enabled is set False."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_no_state_change_when_hvac_mode_off(hass, setup_comp_2):
"""Test that the switch doesn't turn on when enabled is False."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 30)
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_hvac_mode_heat(hass, setup_comp_2):
"""Test change mode from OFF to HEAT.
Switch turns on when temp below setpoint and mode changes.
"""
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
calls = _setup_switch(hass, False)
await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT)
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
def _setup_switch(hass, is_on):
"""Set up the test switch."""
hass.states.async_set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
calls = []
@callback
def log_call(call):
"""Log service calls."""
calls.append(call)
hass.services.async_register(ha.DOMAIN, SERVICE_TURN_ON, log_call)
hass.services.async_register(ha.DOMAIN, SERVICE_TURN_OFF, log_call)
return calls
@pytest.fixture
async def setup_comp_3(hass):
"""Initialize components."""
hass.config.temperature_unit = TEMP_CELSIUS
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 2,
"hot_tolerance": 4,
"away_temp": 30,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"ac_mode": True,
"initial_hvac_mode": HVAC_MODE_COOL,
}
},
)
await hass.async_block_till_done()
async def test_set_target_temp_ac_off(hass, setup_comp_3):
"""Test if target temperature turn ac off."""
calls = _setup_switch(hass, True)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 30)
assert 2 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_turn_away_mode_on_cooling(hass, setup_comp_3):
"""Test the setting away mode when cooling."""
_setup_switch(hass, True)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 19)
await common.async_set_preset_mode(hass, PRESET_AWAY)
state = hass.states.get(ENTITY)
assert 30 == state.attributes.get("temperature")
async def test_hvac_mode_cool(hass, setup_comp_3):
"""Test change mode from OFF to COOL.
Switch turns on when temp below setpoint and mode changes.
"""
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
calls = _setup_switch(hass, False)
await common.async_set_hvac_mode(hass, HVAC_MODE_COOL)
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_set_target_temp_ac_on(hass, setup_comp_3):
"""Test if target temperature turn ac on."""
calls = _setup_switch(hass, False)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 25)
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_ac_off_within_tolerance(hass, setup_comp_3):
"""Test if temperature change doesn't turn ac off within tolerance."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 29.8)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_set_temp_change_ac_off_outside_tolerance(hass, setup_comp_3):
"""Test if temperature change turn ac off."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 27)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_ac_on_within_tolerance(hass, setup_comp_3):
"""Test if temperature change doesn't turn ac on within tolerance."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 25.2)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_ac_on_outside_tolerance(hass, setup_comp_3):
"""Test if temperature change turn ac on."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_running_when_operating_mode_is_off_2(hass, setup_comp_3):
"""Test that the switch turns off when enabled is set False."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_no_state_change_when_operation_mode_off_2(hass, setup_comp_3):
"""Test that the switch doesn't turn on when enabled is False."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 30)
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
_setup_sensor(hass, 35)
await hass.async_block_till_done()
assert 0 == len(calls)
@pytest.fixture
async def setup_comp_4(hass):
"""Initialize components."""
hass.config.temperature_unit = TEMP_CELSIUS
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 0.3,
"hot_tolerance": 0.3,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"ac_mode": True,
"min_cycle_duration": datetime.timedelta(minutes=10),
"initial_hvac_mode": HVAC_MODE_COOL,
}
},
)
await hass.async_block_till_done()
async def test_temp_change_ac_trigger_on_not_long_enough(hass, setup_comp_4):
"""Test if temperature change turn ac on."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_ac_trigger_on_long_enough(hass, setup_comp_4):
"""Test if temperature change turn ac on."""
fake_changed = datetime.datetime(
1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc
)
with patch(
"homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed
):
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_ac_trigger_off_not_long_enough(hass, setup_comp_4):
"""Test if temperature change turn ac on."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_ac_trigger_off_long_enough(hass, setup_comp_4):
"""Test if temperature change turn ac on."""
fake_changed = datetime.datetime(
1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc
)
with patch(
"homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed
):
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_mode_change_ac_trigger_off_not_long_enough(hass, setup_comp_4):
"""Test if mode change turns ac off despite minimum cycle."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 0 == len(calls)
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
assert 1 == len(calls)
call = calls[0]
assert "homeassistant" == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_mode_change_ac_trigger_on_not_long_enough(hass, setup_comp_4):
"""Test if mode change turns ac on despite minimum cycle."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 0 == len(calls)
await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT)
assert 1 == len(calls)
call = calls[0]
assert "homeassistant" == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
@pytest.fixture
async def setup_comp_5(hass):
"""Initialize components."""
hass.config.temperature_unit = TEMP_CELSIUS
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 0.3,
"hot_tolerance": 0.3,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"ac_mode": True,
"min_cycle_duration": datetime.timedelta(minutes=10),
"initial_hvac_mode": HVAC_MODE_COOL,
}
},
)
await hass.async_block_till_done()
async def test_temp_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5):
"""Test if temperature change turn ac on."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_ac_trigger_on_long_enough_2(hass, setup_comp_5):
"""Test if temperature change turn ac on."""
fake_changed = datetime.datetime(
1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc
)
with patch(
"homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed
):
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5):
"""Test if temperature change turn ac on."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_ac_trigger_off_long_enough_2(hass, setup_comp_5):
"""Test if temperature change turn ac on."""
fake_changed = datetime.datetime(
1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc
)
with patch(
"homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed
):
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_mode_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5):
"""Test if mode change turns ac off despite minimum cycle."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 0 == len(calls)
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
assert 1 == len(calls)
call = calls[0]
assert "homeassistant" == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_mode_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5):
"""Test if mode change turns ac on despite minimum cycle."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 0 == len(calls)
await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT)
assert 1 == len(calls)
call = calls[0]
assert "homeassistant" == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
@pytest.fixture
async def setup_comp_6(hass):
"""Initialize components."""
hass.config.temperature_unit = TEMP_CELSIUS
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 0.3,
"hot_tolerance": 0.3,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"min_cycle_duration": datetime.timedelta(minutes=10),
"initial_hvac_mode": HVAC_MODE_HEAT,
}
},
)
await hass.async_block_till_done()
async def test_temp_change_heater_trigger_off_not_long_enough(hass, setup_comp_6):
"""Test if temp change doesn't turn heater off because of time."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_heater_trigger_on_not_long_enough(hass, setup_comp_6):
"""Test if temp change doesn't turn heater on because of time."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6):
"""Test if temperature change turn heater on after min cycle."""
fake_changed = datetime.datetime(
1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc
)
with patch(
"homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed
):
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_heater_trigger_off_long_enough(hass, setup_comp_6):
"""Test if temperature change turn heater off after min cycle."""
fake_changed = datetime.datetime(
1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc
)
with patch(
"homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed
):
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_mode_change_heater_trigger_off_not_long_enough(hass, setup_comp_6):
"""Test if mode change turns heater off despite minimum cycle."""
calls = _setup_switch(hass, True)
await common.async_set_temperature(hass, 25)
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert 0 == len(calls)
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF)
assert 1 == len(calls)
call = calls[0]
assert "homeassistant" == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_mode_change_heater_trigger_on_not_long_enough(hass, setup_comp_6):
"""Test if mode change turns heater on despite minimum cycle."""
calls = _setup_switch(hass, False)
await common.async_set_temperature(hass, 30)
_setup_sensor(hass, 25)
await hass.async_block_till_done()
assert 0 == len(calls)
await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT)
assert 1 == len(calls)
call = calls[0]
assert "homeassistant" == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
@pytest.fixture
async def setup_comp_7(hass):
"""Initialize components."""
hass.config.temperature_unit = TEMP_CELSIUS
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 0.3,
"hot_tolerance": 0.3,
"heater": ENT_SWITCH,
"target_temp": 25,
"target_sensor": ENT_SENSOR,
"ac_mode": True,
"min_cycle_duration": datetime.timedelta(minutes=15),
"keep_alive": datetime.timedelta(minutes=10),
"initial_hvac_mode": HVAC_MODE_COOL,
}
},
)
await hass.async_block_till_done()
async def test_temp_change_ac_trigger_on_long_enough_3(hass, setup_comp_7):
"""Test if turn on signal is sent at keep-alive intervals."""
calls = _setup_switch(hass, True)
await hass.async_block_till_done()
_setup_sensor(hass, 30)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 25)
test_time = datetime.datetime.now(pytz.UTC)
async_fire_time_changed(hass, test_time)
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5))
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10))
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_ac_trigger_off_long_enough_3(hass, setup_comp_7):
"""Test if turn on signal is sent at keep-alive intervals."""
calls = _setup_switch(hass, False)
await hass.async_block_till_done()
_setup_sensor(hass, 20)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 25)
test_time = datetime.datetime.now(pytz.UTC)
async_fire_time_changed(hass, test_time)
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5))
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10))
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
@pytest.fixture
async def setup_comp_8(hass):
"""Initialize components."""
hass.config.temperature_unit = TEMP_CELSIUS
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 0.3,
"hot_tolerance": 0.3,
"target_temp": 25,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"min_cycle_duration": datetime.timedelta(minutes=15),
"keep_alive": datetime.timedelta(minutes=10),
"initial_hvac_mode": HVAC_MODE_HEAT,
}
},
)
await hass.async_block_till_done()
async def test_temp_change_heater_trigger_on_long_enough_2(hass, setup_comp_8):
"""Test if turn on signal is sent at keep-alive intervals."""
calls = _setup_switch(hass, True)
await hass.async_block_till_done()
_setup_sensor(hass, 20)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 25)
test_time = datetime.datetime.now(pytz.UTC)
async_fire_time_changed(hass, test_time)
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5))
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10))
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_ON == call.service
assert ENT_SWITCH == call.data["entity_id"]
async def test_temp_change_heater_trigger_off_long_enough_2(hass, setup_comp_8):
"""Test if turn on signal is sent at keep-alive intervals."""
calls = _setup_switch(hass, False)
await hass.async_block_till_done()
_setup_sensor(hass, 30)
await hass.async_block_till_done()
await common.async_set_temperature(hass, 25)
test_time = datetime.datetime.now(pytz.UTC)
async_fire_time_changed(hass, test_time)
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5))
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10))
await hass.async_block_till_done()
assert 1 == len(calls)
call = calls[0]
assert HASS_DOMAIN == call.domain
assert SERVICE_TURN_OFF == call.service
assert ENT_SWITCH == call.data["entity_id"]
@pytest.fixture
async def setup_comp_9(hass):
"""Initialize components."""
hass.config.temperature_unit = TEMP_FAHRENHEIT
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 0.3,
"hot_tolerance": 0.3,
"target_temp": 25,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"min_cycle_duration": datetime.timedelta(minutes=15),
"keep_alive": datetime.timedelta(minutes=10),
"precision": 0.1,
}
},
)
await hass.async_block_till_done()
async def test_precision(hass, setup_comp_9):
"""Test that setting precision to tenths works as intended."""
await common.async_set_temperature(hass, 23.27)
state = hass.states.get(ENTITY)
assert 23.3 == state.attributes.get("temperature")
async def test_custom_setup_params(hass):
"""Test the setup with custom parameters."""
result = await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"min_temp": MIN_TEMP,
"max_temp": MAX_TEMP,
"target_temp": TARGET_TEMP,
}
},
)
assert result
await hass.async_block_till_done()
state = hass.states.get(ENTITY)
assert state.attributes.get("min_temp") == MIN_TEMP
assert state.attributes.get("max_temp") == MAX_TEMP
assert state.attributes.get("temperature") == TARGET_TEMP
async def test_restore_state(hass):
"""Ensure states are restored on startup."""
mock_restore_cache(
hass,
(
State(
"climate.test_thermostat",
HVAC_MODE_OFF,
{ATTR_TEMPERATURE: "20", ATTR_PRESET_MODE: PRESET_AWAY},
),
),
)
hass.state = CoreState.starting
await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test_thermostat",
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"away_temp": 14,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("climate.test_thermostat")
assert state.attributes[ATTR_TEMPERATURE] == 20
assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY
assert state.state == HVAC_MODE_OFF
async def test_no_restore_state(hass):
"""Ensure states are restored on startup if they exist.
Allows for graceful reboot.
"""
mock_restore_cache(
hass,
(
State(
"climate.test_thermostat",
HVAC_MODE_OFF,
{ATTR_TEMPERATURE: "20", ATTR_PRESET_MODE: PRESET_AWAY},
),
),
)
hass.state = CoreState.starting
await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test_thermostat",
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"target_temp": 22,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("climate.test_thermostat")
assert state.attributes[ATTR_TEMPERATURE] == 22
assert state.state == HVAC_MODE_OFF
async def test_initial_hvac_off_force_heater_off(hass):
"""Ensure that restored state is coherent with real situation.
'initial_hvac_mode: off' will force HVAC status, but we must be sure
that heater don't keep on.
"""
# switch is on
calls = _setup_switch(hass, True)
assert hass.states.get(ENT_SWITCH).state == STATE_ON
_setup_sensor(hass, 16)
await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test_thermostat",
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"target_temp": 20,
"initial_hvac_mode": HVAC_MODE_OFF,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("climate.test_thermostat")
# 'initial_hvac_mode' will force state but must prevent heather keep working
assert state.state == HVAC_MODE_OFF
# heater must be switched off
assert len(calls) == 1
call = calls[0]
assert call.domain == HASS_DOMAIN
assert call.service == SERVICE_TURN_OFF
assert call.data["entity_id"] == ENT_SWITCH
async def test_restore_will_turn_off_(hass):
"""Ensure that restored state is coherent with real situation.
Thermostat status must trigger heater event if temp raises the target .
"""
heater_switch = "input_boolean.test"
mock_restore_cache(
hass,
(
State(
"climate.test_thermostat",
HVAC_MODE_HEAT,
{ATTR_TEMPERATURE: "18", ATTR_PRESET_MODE: PRESET_NONE},
),
State(heater_switch, STATE_ON, {}),
),
)
hass.state = CoreState.starting
assert await async_setup_component(
hass, input_boolean.DOMAIN, {"input_boolean": {"test": None}}
)
await hass.async_block_till_done()
assert hass.states.get(heater_switch).state == STATE_ON
_setup_sensor(hass, 22)
await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test_thermostat",
"heater": heater_switch,
"target_sensor": ENT_SENSOR,
"target_temp": 20,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("climate.test_thermostat")
assert state.attributes[ATTR_TEMPERATURE] == 20
assert state.state == HVAC_MODE_HEAT
assert hass.states.get(heater_switch).state == STATE_ON
async def test_restore_state_uncoherence_case(hass):
"""
Test restore from a strange state.
- Turn the generic thermostat off
- Restart HA and restore state from DB
"""
_mock_restore_cache(hass, temperature=20)
calls = _setup_switch(hass, False)
_setup_sensor(hass, 15)
await _setup_climate(hass)
await hass.async_block_till_done()
state = hass.states.get(ENTITY)
assert 20 == state.attributes[ATTR_TEMPERATURE]
assert HVAC_MODE_OFF == state.state
assert 0 == len(calls)
calls = _setup_switch(hass, False)
await hass.async_block_till_done()
state = hass.states.get(ENTITY)
assert HVAC_MODE_OFF == state.state
async def _setup_climate(hass):
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"cold_tolerance": 2,
"hot_tolerance": 4,
"away_temp": 30,
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"ac_mode": True,
}
},
)
def _mock_restore_cache(hass, temperature=20, hvac_mode=HVAC_MODE_OFF):
mock_restore_cache(
hass,
(
State(
ENTITY,
hvac_mode,
{ATTR_TEMPERATURE: str(temperature), ATTR_PRESET_MODE: PRESET_AWAY},
),
),
)
async def test_reload(hass):
"""Test we can reload."""
assert await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test",
"heater": "switch.any",
"target_sensor": "sensor.any",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
assert hass.states.get("climate.test") is not None
yaml_path = path.join(
_get_fixtures_base_path(),
"fixtures",
"generic_thermostat/configuration.yaml",
)
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
await hass.services.async_call(
GENERIC_THERMOSTAT_DOMAIN,
SERVICE_RELOAD,
{},
blocking=True,
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
assert hass.states.get("climate.test") is None
assert hass.states.get("climate.reload")
def _get_fixtures_base_path():
return path.dirname(path.dirname(path.dirname(__file__)))