Files
versatile_thermostat/tests/test_binary_sensors.py
Jean-Marc Collin 0c8d80f378 Issue #168 - Adds slow auto-new_slow_regulation (#170)
Issue #169 - Adds support for Versatile Thermostat UI Card

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2023-11-06 12:31:45 +01:00

518 lines
19 KiB
Python

# pylint: disable=wildcard-import, unused-wildcard-import, unused-argument, line-too-long
""" Test the normal start of a Thermostat """
from unittest.mock import patch
from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACMode
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.binary_sensor import (
SecurityBinarySensor,
OverpoweringBinarySensor,
WindowBinarySensor,
MotionBinarySensor,
PresenceBinarySensor,
)
from .commons import *
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_security_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
): # pylint: disable=unused-argument
"""Test the security binary sensors in thermostat type"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
},
)
entity: BaseThermostat = await create_thermostat (
hass, entry, "climate.theoverswitchmockname"
)
assert entity
security_binary_sensor: SecurityBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_security_state", "binary_sensor"
)
assert security_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Security should be disabled
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
assert security_binary_sensor.state == STATE_OFF
assert security_binary_sensor.device_class == BinarySensorDeviceClass.SAFETY
# Set temperature in the past
event_timestamp = now - timedelta(minutes=6)
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
await send_temperature_change_event(entity, 15, event_timestamp)
assert entity.security_state is True
# Simulate the event reception
await security_binary_sensor.async_my_climate_changed()
assert security_binary_sensor.state == STATE_ON
# set temperature now
await send_temperature_change_event(entity, 15, now)
assert entity.security_state is False
# Simulate the event reception
await security_binary_sensor.async_my_climate_changed()
assert security_binary_sensor.state == STATE_OFF
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_overpowering_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the overpowering binary sensors in thermostat type"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
CONF_PRESET_POWER: 12,
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
overpowering_binary_sensor: OverpoweringBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_overpowering_state", "binary_sensor"
)
assert overpowering_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert await entity.check_overpowering() is False
assert entity.overpowering_state is None
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state is STATE_OFF
assert overpowering_binary_sensor.device_class == BinarySensorDeviceClass.POWER
await send_power_change_event(entity, 100, now)
await send_max_power_change_event(entity, 150, now)
assert await entity.check_overpowering() is True
assert entity.overpowering_state is True
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_ON
# set max power to a low value
await send_max_power_change_event(entity, 201, now)
assert await entity.check_overpowering() is False
assert entity.overpowering_state is False
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_OFF
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the window binary sensors in thermostat type"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
window_binary_sensor: WindowBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_window_state", "binary_sensor"
)
assert window_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.window_state is STATE_OFF
await window_binary_sensor.async_my_climate_changed()
assert window_binary_sensor.state is STATE_OFF
assert window_binary_sensor.device_class == BinarySensorDeviceClass.WINDOW
# Open the window
with patch("homeassistant.helpers.condition.state", return_value=True):
try_window_condition = await send_window_change_event(entity, True, False, now)
# simulate the call to try_window_condition
await try_window_condition(None)
assert entity.window_state is STATE_ON
# Simulate the event reception
await window_binary_sensor.async_my_climate_changed()
assert window_binary_sensor.state == STATE_ON
# close the window
with patch("homeassistant.helpers.condition.state", return_value=True):
try_window_condition = await send_window_change_event(entity, False, True, now)
# simulate the call to try_window_condition
await try_window_condition(None)
assert entity.window_state is STATE_OFF
# Simulate the event reception
await window_binary_sensor.async_my_climate_changed()
assert window_binary_sensor.state == STATE_OFF
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_motion_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the motion binary sensors in thermostat type"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: True,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 0, # important to not been obliged to wait
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
motion_binary_sensor: MotionBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_motion_state", "binary_sensor"
)
assert motion_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.motion_state is None
await motion_binary_sensor.async_my_climate_changed()
assert motion_binary_sensor.state is STATE_OFF
assert motion_binary_sensor.device_class == BinarySensorDeviceClass.MOTION
# Detect motion
with patch("homeassistant.helpers.condition.state", return_value=True):
try_motion_condition = await send_motion_change_event(entity, True, False, now)
# simulate the call to try_window_condition
await try_motion_condition(None)
assert entity.motion_state is STATE_ON
# Simulate the event reception
await motion_binary_sensor.async_my_climate_changed()
assert motion_binary_sensor.state == STATE_ON
# Undetect motion
with patch("homeassistant.helpers.condition.state", return_value=True):
try_motion_condition = await send_motion_change_event(entity, False, True, now)
# simulate the call to try_motion_condition
await try_motion_condition(None)
assert entity.motion_state is STATE_OFF
# Simulate the event reception
await motion_binary_sensor.async_my_climate_changed()
assert motion_binary_sensor.state == STATE_OFF
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_presence_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the presence binary sensors in thermostat type"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
"eco_away_temp": 12,
"comfort_away_temp": 13,
"boost_away_temp": 14,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: True,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
presence_binary_sensor: PresenceBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_presence_state", "binary_sensor"
)
assert presence_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.presence_state is None
await presence_binary_sensor.async_my_climate_changed()
assert presence_binary_sensor.state is STATE_OFF
assert presence_binary_sensor.device_class == BinarySensorDeviceClass.PRESENCE
# Detect motion
await send_presence_change_event(entity, True, False, now)
assert entity.presence_state is STATE_ON
# Simulate the event reception
await presence_binary_sensor.async_my_climate_changed()
assert presence_binary_sensor.state == STATE_ON
# Undetect motion
await send_presence_change_event(entity, False, True, now)
assert entity.presence_state is STATE_OFF
# Simulate the event reception
await presence_binary_sensor.async_my_climate_changed()
assert presence_binary_sensor.state == STATE_OFF
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_binary_sensors_over_climate_minimal(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the binary sensors with thermostat over climate type"""
the_mock_underlying = MagicMockClimate()
with patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
return_value=the_mock_underlying,
):
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverClimateMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverclimatemockname"
)
assert entity
assert entity.is_over_climate
security_binary_sensor: SecurityBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_security_state", "binary_sensor"
)
assert security_binary_sensor is not None
overpowering_binary_sensor: OverpoweringBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_overpowering_state", "binary_sensor"
)
assert overpowering_binary_sensor is None
window_binary_sensor: WindowBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_window_state", "binary_sensor"
)
assert window_binary_sensor is None
motion_binary_sensor: MotionBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_motion_state", "binary_sensor"
)
assert motion_binary_sensor is None
presence_binary_sensor: PresenceBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_presence_state", "binary_sensor"
)
assert presence_binary_sensor is None