* First implem + tests (not finished) * With tests of calculate_shedding ok * Commit for rebase * All tests ok for central_feature_power_manager * All tests not ok * All tests ok * integrattion tests - Do startup works * enhance the overpowering algo if current_power > max_power * Change shedding calculation delay to 20 sec (vs 60 sec) * Integration tests ok * Fix overpowering is set even if other heater have on_percent = 0 * Fix too much shedding in over_climate * Add logs * Add temporal filter for calculate_shedding Add restore overpowering state at startup * Fix restore overpowering_state * Removes poweer_entity_id from vtherm non central config * Release * Add Sonoff TRVZB in creation.md * Add comment on Sonoff TRVZB Closing degree --------- Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
1004 lines
40 KiB
Python
1004 lines
40 KiB
Python
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable, too-many-lines
|
|
|
|
""" Test the Window management """
|
|
from datetime import datetime, timedelta
|
|
import logging
|
|
from unittest.mock import patch, call, AsyncMock, MagicMock, PropertyMock
|
|
|
|
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
|
from custom_components.versatile_thermostat.feature_motion_manager import (
|
|
FeatureMotionManager,
|
|
)
|
|
|
|
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
|
|
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"current_state, new_state, temp, nb_call, motion_state, is_motion_detected, preset_refresh, changed",
|
|
[
|
|
(STATE_OFF, STATE_ON, 21, 1, STATE_ON, True, PRESET_BOOST, True),
|
|
# motion is ON. So is_motion_detected is true and preset is BOOST
|
|
(STATE_OFF, STATE_ON, 21, 1, STATE_ON, True, PRESET_BOOST, True),
|
|
# current_state is ON and motion is OFF. So is_motion_detected is false and preset is ECO
|
|
(STATE_ON, STATE_OFF, 17, 1, STATE_OFF, False, PRESET_ECO, True),
|
|
],
|
|
)
|
|
async def test_motion_feature_manager_refresh(
|
|
hass: HomeAssistant,
|
|
current_state,
|
|
new_state, # new state of motion event
|
|
temp,
|
|
nb_call,
|
|
motion_state,
|
|
is_motion_detected,
|
|
preset_refresh,
|
|
changed,
|
|
):
|
|
"""Test the FeatureMotionManager class direclty"""
|
|
|
|
fake_vtherm = MagicMock(spec=BaseThermostat)
|
|
type(fake_vtherm).name = PropertyMock(return_value="the name")
|
|
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_ACTIVITY)
|
|
|
|
# 1. creation
|
|
motion_manager = FeatureMotionManager(fake_vtherm, hass)
|
|
|
|
assert motion_manager is not None
|
|
assert motion_manager.is_configured is False
|
|
assert motion_manager.is_motion_detected is False
|
|
assert motion_manager.motion_state == STATE_UNAVAILABLE
|
|
assert motion_manager.name == "the name"
|
|
|
|
assert len(motion_manager._active_listener) == 0
|
|
|
|
custom_attributes = {}
|
|
motion_manager.add_custom_attributes(custom_attributes)
|
|
assert custom_attributes["motion_sensor_entity_id"] is None
|
|
assert custom_attributes["motion_state"] == STATE_UNAVAILABLE
|
|
assert custom_attributes["is_motion_configured"] is False
|
|
assert custom_attributes["motion_preset"] is None
|
|
assert custom_attributes["no_motion_preset"] is None
|
|
assert custom_attributes["motion_delay_sec"] == 0
|
|
assert custom_attributes["motion_off_delay_sec"] == 0
|
|
|
|
# 2. post_init
|
|
motion_manager.post_init(
|
|
{
|
|
CONF_MOTION_SENSOR: "sensor.the_motion_sensor",
|
|
CONF_USE_MOTION_FEATURE: True,
|
|
CONF_MOTION_DELAY: 10,
|
|
CONF_MOTION_OFF_DELAY: 30,
|
|
CONF_MOTION_PRESET: PRESET_BOOST,
|
|
CONF_NO_MOTION_PRESET: PRESET_ECO,
|
|
}
|
|
)
|
|
|
|
assert motion_manager.is_configured is True
|
|
assert motion_manager.motion_state == STATE_UNKNOWN
|
|
assert motion_manager.is_motion_detected is False
|
|
|
|
custom_attributes = {}
|
|
motion_manager.add_custom_attributes(custom_attributes)
|
|
assert custom_attributes["motion_sensor_entity_id"] == "sensor.the_motion_sensor"
|
|
assert custom_attributes["motion_state"] == STATE_UNKNOWN
|
|
assert custom_attributes["is_motion_configured"] is True
|
|
assert custom_attributes["motion_preset"] is PRESET_BOOST
|
|
assert custom_attributes["no_motion_preset"] is PRESET_ECO
|
|
assert custom_attributes["motion_delay_sec"] == 10
|
|
assert custom_attributes["motion_off_delay_sec"] == 30
|
|
|
|
# 3. start listening
|
|
await motion_manager.start_listening()
|
|
assert motion_manager.is_configured is True
|
|
assert motion_manager.motion_state == STATE_UNKNOWN
|
|
assert motion_manager.is_motion_detected is False
|
|
|
|
assert len(motion_manager._active_listener) == 1
|
|
|
|
# 4. test refresh with the parametrized
|
|
# fmt:off
|
|
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)) as mock_get_state:
|
|
# fmt:on
|
|
# Configurer les méthodes mockées
|
|
fake_vtherm.find_preset_temp.return_value = temp
|
|
fake_vtherm.change_target_temperature = AsyncMock()
|
|
fake_vtherm.async_control_heating = AsyncMock()
|
|
fake_vtherm.recalculate = MagicMock()
|
|
|
|
# force old state for the test
|
|
motion_manager._motion_state = current_state
|
|
|
|
ret = await motion_manager.refresh_state()
|
|
assert ret == changed
|
|
assert motion_manager.is_configured is True
|
|
# in the refresh there is no delay
|
|
assert motion_manager.motion_state == new_state
|
|
assert motion_manager.is_motion_detected is is_motion_detected
|
|
|
|
assert mock_get_state.call_count == 1
|
|
|
|
assert fake_vtherm.find_preset_temp.call_count == nb_call
|
|
|
|
if nb_call == 1:
|
|
fake_vtherm.find_preset_temp.assert_has_calls(
|
|
[
|
|
call.find_preset_temp(preset_refresh),
|
|
]
|
|
)
|
|
|
|
assert fake_vtherm.change_target_temperature.call_count == nb_call
|
|
fake_vtherm.change_target_temperature.assert_has_calls(
|
|
[
|
|
call.find_preset_temp(temp),
|
|
]
|
|
)
|
|
|
|
# We do not call control_heating at startup
|
|
assert fake_vtherm.recalculate.call_count == 0
|
|
assert fake_vtherm.async_control_heating.call_count == 0
|
|
|
|
fake_vtherm.reset_mock()
|
|
|
|
# 5. Check custom_attributes
|
|
custom_attributes = {}
|
|
motion_manager.add_custom_attributes(custom_attributes)
|
|
assert custom_attributes["motion_sensor_entity_id"] == "sensor.the_motion_sensor"
|
|
assert custom_attributes["motion_state"] == new_state
|
|
assert custom_attributes["is_motion_configured"] is True
|
|
assert custom_attributes["motion_preset"] is PRESET_BOOST
|
|
assert custom_attributes["no_motion_preset"] is PRESET_ECO
|
|
assert custom_attributes["motion_delay_sec"] == 10
|
|
assert custom_attributes["motion_off_delay_sec"] == 30
|
|
|
|
motion_manager.stop_listening()
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"current_state, long_enough, new_state, temp, nb_call, motion_state, is_motion_detected, preset_event, changed",
|
|
[
|
|
(STATE_OFF, True, STATE_ON, 21, 1, STATE_ON, True, PRESET_BOOST, True),
|
|
# motion is ON but for not enough time but sensor is on at the end. So is_motion_detected is true and preset is BOOST
|
|
(STATE_OFF, False, STATE_ON, 21, 1, STATE_ON, True, PRESET_BOOST, True),
|
|
# motion is OFF for enough time. So is_motion_detected is false and preset is ECO
|
|
(STATE_ON, True, STATE_OFF, 17, 1, STATE_OFF, False, PRESET_ECO, True),
|
|
# motion is OFF for not enough time. So is_motion_detected is false and preset is ECO
|
|
(STATE_ON, False, STATE_OFF, 21, 1, STATE_ON, True, PRESET_BOOST, True),
|
|
],
|
|
)
|
|
async def test_motion_feature_manager_event(
|
|
hass: HomeAssistant,
|
|
current_state,
|
|
long_enough,
|
|
new_state, # new state of motion event
|
|
temp,
|
|
nb_call,
|
|
motion_state,
|
|
is_motion_detected,
|
|
preset_event,
|
|
changed,
|
|
):
|
|
"""Test the FeatureMotionManager class direclty"""
|
|
|
|
fake_vtherm = MagicMock(spec=BaseThermostat)
|
|
type(fake_vtherm).name = PropertyMock(return_value="the name")
|
|
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_ACTIVITY)
|
|
|
|
# 1. iniitialization creation, post_init, start_listening
|
|
motion_manager = FeatureMotionManager(fake_vtherm, hass)
|
|
motion_manager.post_init(
|
|
{
|
|
CONF_MOTION_SENSOR: "sensor.the_motion_sensor",
|
|
CONF_USE_MOTION_FEATURE: True,
|
|
CONF_MOTION_DELAY: 10,
|
|
CONF_MOTION_OFF_DELAY: 30,
|
|
CONF_MOTION_PRESET: PRESET_BOOST,
|
|
CONF_NO_MOTION_PRESET: PRESET_ECO,
|
|
}
|
|
)
|
|
await motion_manager.start_listening()
|
|
|
|
# 2. test _motion_sensor_changed with the parametrized
|
|
# fmt: off
|
|
with patch("homeassistant.helpers.condition.state", return_value=long_enough), \
|
|
patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)):
|
|
# fmt: on
|
|
fake_vtherm.find_preset_temp.return_value = temp
|
|
fake_vtherm.change_target_temperature = AsyncMock()
|
|
fake_vtherm.async_control_heating = AsyncMock()
|
|
fake_vtherm.recalculate = MagicMock()
|
|
|
|
# force old state for the test
|
|
motion_manager._motion_state = current_state
|
|
|
|
delay = await motion_manager._motion_sensor_changed(
|
|
event=Event(
|
|
event_type=EVENT_STATE_CHANGED,
|
|
data={
|
|
"entity_id": "sensor.the_motion_sensor",
|
|
"new_state": State("sensor.the_motion_sensor", new_state),
|
|
"old_state": State("sensor.the_motion_sensor", STATE_UNAVAILABLE),
|
|
}))
|
|
assert delay is not None
|
|
|
|
await delay(None)
|
|
assert motion_manager.is_configured is True
|
|
assert motion_manager.motion_state == motion_state
|
|
assert motion_manager.is_motion_detected is is_motion_detected
|
|
|
|
assert fake_vtherm.find_preset_temp.call_count == nb_call
|
|
|
|
if nb_call == 1:
|
|
fake_vtherm.find_preset_temp.assert_has_calls(
|
|
[
|
|
call.find_preset_temp(preset_event),
|
|
]
|
|
)
|
|
|
|
assert fake_vtherm.change_target_temperature.call_count == nb_call
|
|
fake_vtherm.change_target_temperature.assert_has_calls(
|
|
[
|
|
call.find_preset_temp(temp),
|
|
]
|
|
)
|
|
|
|
assert fake_vtherm.recalculate.call_count == 1
|
|
assert fake_vtherm.async_control_heating.call_count == 1
|
|
fake_vtherm.async_control_heating.assert_has_calls([
|
|
call.async_control_heating(force=True)
|
|
])
|
|
|
|
fake_vtherm.reset_mock()
|
|
|
|
# 3. Check custom_attributes
|
|
custom_attributes = {}
|
|
motion_manager.add_custom_attributes(custom_attributes)
|
|
assert custom_attributes["motion_sensor_entity_id"] == "sensor.the_motion_sensor"
|
|
assert custom_attributes["motion_state"] == motion_state
|
|
assert custom_attributes["is_motion_configured"] is True
|
|
assert custom_attributes["motion_preset"] is PRESET_BOOST
|
|
assert custom_attributes["no_motion_preset"] is PRESET_ECO
|
|
assert custom_attributes["motion_delay_sec"] == 10
|
|
assert custom_attributes["motion_off_delay_sec"] == 30
|
|
|
|
motion_manager.stop_listening()
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_motion_management_time_not_enough(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Presence management when time is not enough"""
|
|
temps = {
|
|
"frost": 10,
|
|
"eco": 17,
|
|
"comfort": 18,
|
|
"boost": 19,
|
|
"frost_away": 10,
|
|
"eco_away": 17,
|
|
"comfort_away": 18,
|
|
"boost_away": 19,
|
|
}
|
|
|
|
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,
|
|
CONF_USE_WINDOW_FEATURE: False,
|
|
CONF_USE_MOTION_FEATURE: True,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: True,
|
|
CONF_UNDERLYING_LIST: ["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_SAFETY_DELAY_MIN: 10,
|
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
|
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
|
|
CONF_MOTION_DELAY: 10, # important to not been obliged to wait
|
|
CONF_MOTION_OFF_DELAY: 30,
|
|
CONF_MOTION_PRESET: "boost",
|
|
CONF_NO_MOTION_PRESET: "comfort",
|
|
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
await set_all_climate_preset_temp(hass, entity, temps, "theoverswitchmockname")
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now: datetime = datetime.now(tz=tz)
|
|
|
|
# 1. start heating, in boost mode, when someone is present. We block the control_heating to avoid running a cycle
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
|
|
):
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_ACTIVITY)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_UNKNOWN
|
|
assert entity.presence_state is STATE_UNKNOWN
|
|
|
|
event_timestamp = now - timedelta(minutes=5)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
|
|
|
await send_presence_change_event(entity, True, False, event_timestamp)
|
|
assert entity.presence_state == STATE_ON
|
|
|
|
# 2. starts detecting motion with time not enough
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=False,
|
|
), patch(
|
|
"homeassistant.helpers.condition.state", return_value=False
|
|
) as mock_condition, patch(
|
|
"homeassistant.core.StateMachine.get",
|
|
return_value=State(
|
|
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
|
),
|
|
):
|
|
event_timestamp = now - timedelta(minutes=4)
|
|
try_condition = await send_motion_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
|
|
# Will return False -> we will stay on movement False
|
|
await try_condition(None)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 18
|
|
# state is not changed if time is not enough
|
|
assert entity.motion_state is STATE_OFF
|
|
assert entity.presence_state == STATE_ON
|
|
|
|
assert mock_send_event.call_count == 0
|
|
# Change is not confirmed
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
|
|
# starts detecting motion with time enough this time
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=False,
|
|
), patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition:
|
|
event_timestamp = now - timedelta(minutes=3)
|
|
try_condition = await send_motion_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
|
|
# Will return True -> we will switch to movement On
|
|
await try_condition(None)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because motion is detected yet
|
|
assert entity.target_temperature == 19
|
|
assert entity.motion_state == STATE_ON
|
|
assert entity.presence_state == STATE_ON
|
|
|
|
# stop detecting motion with off delay too low
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=True,
|
|
) as mock_device_active, patch(
|
|
"homeassistant.helpers.condition.state", return_value=False
|
|
) as mock_condition, patch(
|
|
"homeassistant.core.StateMachine.get",
|
|
return_value=State(
|
|
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
|
),
|
|
):
|
|
event_timestamp = now - timedelta(minutes=2)
|
|
try_condition = await send_motion_change_event(
|
|
entity, False, True, event_timestamp
|
|
)
|
|
|
|
# Will return False -> we will stay to movement On
|
|
await try_condition(None)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 19
|
|
assert entity.motion_state == STATE_ON
|
|
assert entity.presence_state == STATE_ON
|
|
|
|
assert mock_send_event.call_count == 0
|
|
# The heater must heat now
|
|
assert mock_heater_on.call_count == 1
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
|
|
# stop detecting motion with off delay enough long
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=True,
|
|
) as mock_device_active, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition:
|
|
event_timestamp = now - timedelta(minutes=1)
|
|
try_condition = await send_motion_change_event(
|
|
entity, False, True, event_timestamp
|
|
)
|
|
|
|
# Will return True -> we will switch to movement Off
|
|
await try_condition(None)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state == STATE_OFF
|
|
assert entity.presence_state == STATE_ON
|
|
|
|
assert mock_send_event.call_count == 0
|
|
# The heater must stop heating now
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 1
|
|
assert mock_send_event.call_count == 0
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_motion_management_time_enough_and_presence(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Motion management when time is not enough"""
|
|
|
|
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": 17,
|
|
"comfort_away_temp": 18,
|
|
"boost_away_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: False,
|
|
CONF_USE_MOTION_FEATURE: True,
|
|
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_SAFETY_DELAY_MIN: 5,
|
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
|
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
|
|
CONF_MOTION_DELAY: 0, # important to not been obliged to wait
|
|
CONF_MOTION_PRESET: "boost",
|
|
CONF_NO_MOTION_PRESET: "comfort",
|
|
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now: datetime = datetime.now(tz=tz)
|
|
|
|
# start heating, in boost mode. We block the control_heating to avoid running a cycle
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
|
|
):
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_ACTIVITY)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_UNKNOWN
|
|
assert entity.presence_state is STATE_UNKNOWN
|
|
|
|
event_timestamp = now - timedelta(minutes=4)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
|
|
|
await send_presence_change_event(entity, True, False, event_timestamp)
|
|
assert entity.presence_state == "on"
|
|
|
|
# starts detecting motion
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=False,
|
|
), patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
):
|
|
event_timestamp = now - timedelta(minutes=3)
|
|
await send_motion_change_event(entity, True, False, event_timestamp)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because motion is detected yet -> switch to Boost mode
|
|
assert entity.target_temperature == 19
|
|
assert entity.motion_state == STATE_ON
|
|
assert entity.presence_state == STATE_ON
|
|
assert mock_send_event.call_count == 0
|
|
# Change is confirmed. Heater should be started
|
|
assert mock_heater_on.call_count == 1
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
|
|
# stop detecting motion with confirmation of stop
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=True,
|
|
), patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
):
|
|
event_timestamp = now - timedelta(minutes=2)
|
|
await send_motion_change_event(entity, False, True, event_timestamp)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state == STATE_OFF
|
|
assert entity.presence_state == STATE_ON
|
|
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0
|
|
# Because heating is no more necessary
|
|
assert mock_heater_off.call_count == 1
|
|
assert mock_send_event.call_count == 0
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_motion_management_time_enough_and_not_presence(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Presence management when time is not enough"""
|
|
|
|
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": 17.1,
|
|
"comfort_away_temp": 18.1,
|
|
"boost_away_temp": 19.1,
|
|
CONF_USE_WINDOW_FEATURE: False,
|
|
CONF_USE_MOTION_FEATURE: True,
|
|
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_SAFETY_DELAY_MIN: 5,
|
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
|
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
|
|
CONF_MOTION_DELAY: 0, # important to not been obliged to wait
|
|
CONF_MOTION_PRESET: "boost",
|
|
CONF_NO_MOTION_PRESET: "comfort",
|
|
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now: datetime = datetime.now(tz=tz)
|
|
|
|
# start heating, in boost mode. We block the control_heating to avoid running a cycle
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
|
|
):
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_ACTIVITY)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet and presence is unknown
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_UNKNOWN
|
|
assert entity.presence_state is STATE_UNKNOWN
|
|
|
|
event_timestamp = now - timedelta(minutes=4)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
|
|
|
await send_presence_change_event(entity, False, True, event_timestamp)
|
|
assert entity.presence_state == STATE_OFF
|
|
|
|
# starts detecting motion
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=False,
|
|
), patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
):
|
|
event_timestamp = now - timedelta(minutes=3)
|
|
await send_motion_change_event(entity, True, False, event_timestamp)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because motion is detected yet -> switch to Boost away mode
|
|
assert entity.target_temperature == 19.1
|
|
assert entity.motion_state == STATE_ON
|
|
assert entity.presence_state == STATE_OFF
|
|
|
|
assert mock_send_event.call_count == 0
|
|
# Change is confirmed. Heater should be started
|
|
assert mock_heater_on.call_count == 1
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
|
|
# stop detecting motion with confirmation of stop
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=True,
|
|
), patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
):
|
|
event_timestamp = now - timedelta(minutes=2)
|
|
await send_motion_change_event(entity, False, True, event_timestamp)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 18.1
|
|
assert entity.motion_state == STATE_OFF
|
|
assert entity.presence_state == STATE_OFF
|
|
assert mock_send_event.call_count == 0
|
|
# 18.1 starts heating with a low on_percent
|
|
assert mock_heater_on.call_count == 1
|
|
assert entity.proportional_algorithm.on_percent == 0.11
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_motion_management_with_stop_during_condition(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Motion management when the movement sensor switch to off and then to on during the test condition"""
|
|
|
|
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": 17,
|
|
"comfort_away_temp": 18,
|
|
"boost_away_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: False,
|
|
CONF_USE_MOTION_FEATURE: True,
|
|
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_SAFETY_DELAY_MIN: 5,
|
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
|
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
|
|
CONF_MOTION_DELAY: 10,
|
|
CONF_MOTION_OFF_DELAY: 30,
|
|
CONF_MOTION_PRESET: "boost",
|
|
CONF_NO_MOTION_PRESET: "comfort",
|
|
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now: datetime = datetime.now(tz=tz)
|
|
|
|
# start heating, in boost mode. We block the control_heating to avoid running a cycle
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
|
|
):
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_ACTIVITY)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_UNKNOWN
|
|
assert entity.presence_state is STATE_UNKNOWN
|
|
|
|
event_timestamp = now - timedelta(minutes=6)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
|
|
|
await send_presence_change_event(entity, False, True, event_timestamp)
|
|
assert entity.presence_state == STATE_OFF
|
|
|
|
# starts detecting motion
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=True,
|
|
), patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
): # Not needed for this test
|
|
event_timestamp = now - timedelta(minutes=5)
|
|
try_condition1 = await send_motion_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
|
|
assert try_condition1 is not None
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because motion is detected yet -> switch to Boost mode
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_UNKNOWN
|
|
assert entity.presence_state == STATE_OFF
|
|
# Send a stop detection
|
|
event_timestamp = now - timedelta(minutes=4)
|
|
try_condition = await send_motion_change_event(
|
|
entity, False, True, event_timestamp
|
|
)
|
|
assert try_condition is None # The timer should not have been stopped
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_UNKNOWN
|
|
assert entity.presence_state == STATE_OFF
|
|
|
|
# Resend a start detection
|
|
event_timestamp = now - timedelta(minutes=3)
|
|
try_condition = await send_motion_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
assert (
|
|
try_condition is None
|
|
) # The timer should not have been restarted (we keep the first one)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# still no motion detected
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_UNKNOWN
|
|
assert entity.presence_state == STATE_OFF
|
|
|
|
await try_condition1(None)
|
|
# We should have switch this time
|
|
assert entity.target_temperature == 19 # Boost
|
|
assert entity.motion_state == STATE_ON # switch to movement on
|
|
assert entity.presence_state == STATE_OFF # Non change
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_motion_management_with_stop_during_condition_last_state_on(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Motion management when the movement sensor switch to off and then to on during the test condition"""
|
|
|
|
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": 17,
|
|
"comfort_away_temp": 18,
|
|
"boost_away_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_SAFETY_DELAY_MIN: 5,
|
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
|
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
|
|
CONF_MOTION_DELAY: 10,
|
|
CONF_MOTION_OFF_DELAY: 30,
|
|
CONF_MOTION_PRESET: "boost",
|
|
CONF_NO_MOTION_PRESET: "comfort",
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now: datetime = datetime.now(tz=tz)
|
|
|
|
# 0. start heating, in boost mode. We block the control_heating to avoid running a cycle
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
|
|
):
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_ACTIVITY)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_ACTIVITY
|
|
# because no motion is detected yet
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_UNKNOWN
|
|
|
|
event_timestamp = now - timedelta(minutes=6)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
|
|
|
# 1. starts detecting motion but the sensor is off
|
|
with patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=True,
|
|
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
|
"homeassistant.core.StateMachine.get",
|
|
return_value=State(
|
|
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
|
),
|
|
):
|
|
event_timestamp = now - timedelta(minutes=5)
|
|
try_condition1 = await send_motion_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
|
|
assert try_condition1 is not None
|
|
|
|
await try_condition1(None)
|
|
|
|
# because no motion is detected yet -> condition.state is False and sensor is not active
|
|
assert entity.target_temperature == 18
|
|
assert entity.motion_state is STATE_OFF
|
|
|
|
# 2. starts detecting motion but the sensor is on
|
|
with patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=True,
|
|
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
|
"homeassistant.core.StateMachine.get",
|
|
return_value=State(
|
|
entity_id="binary_sensor.mock_motion_sensor", state=STATE_ON
|
|
),
|
|
):
|
|
event_timestamp = now - timedelta(minutes=5)
|
|
try_condition1 = await send_motion_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
|
|
assert try_condition1 is not None
|
|
|
|
await try_condition1(None)
|
|
|
|
# because no motion is detected yet -> condition.state is False and sensor is not active
|
|
assert entity.target_temperature == 19
|
|
assert entity.motion_state is STATE_ON
|