Window bypass - add testu and color theme for sensor

This commit is contained in:
Jean-Marc Collin
2023-10-30 08:22:06 +00:00
parent 2786a6e5ae
commit dc89e011c7
7 changed files with 271 additions and 16 deletions

View File

@@ -219,3 +219,4 @@ frontend:
state-binary_sensor-window-on-color: "rgb(156, 39, 176)" state-binary_sensor-window-on-color: "rgb(156, 39, 176)"
state-binary_sensor-motion-on-color: "rgb(156, 39, 176)" state-binary_sensor-motion-on-color: "rgb(156, 39, 176)"
state-binary_sensor-presence-on-color: "lightgreen" state-binary_sensor-presence-on-color: "lightgreen"
state-binary_sensor-running-on-color: "orange"

View File

@@ -518,6 +518,7 @@ frontend:
state-binary_sensor-window-on-color: "rgb(156, 39, 176)" state-binary_sensor-window-on-color: "rgb(156, 39, 176)"
state-binary_sensor-motion-on-color: "rgb(156, 39, 176)" state-binary_sensor-motion-on-color: "rgb(156, 39, 176)"
state-binary_sensor-presence-on-color: "lightgreen" state-binary_sensor-presence-on-color: "lightgreen"
state-binary_sensor-running-on-color: "orange"
``` ```
et choisissez le thème ```versatile_thermostat_theme``` dans la configuration du panel. Vous obtiendrez quelque-chose qui va ressembler à ça : et choisissez le thème ```versatile_thermostat_theme``` dans la configuration du panel. Vous obtiendrez quelque-chose qui va ressembler à ça :

View File

@@ -505,6 +505,7 @@ frontend:
state-binary_sensor-window-on-color: "rgb(156, 39, 176)" state-binary_sensor-window-on-color: "rgb(156, 39, 176)"
state-binary_sensor-motion-on-color: "rgb(156, 39, 176)" state-binary_sensor-motion-on-color: "rgb(156, 39, 176)"
state-binary_sensor-presence-on-color: "lightgreen" state-binary_sensor-presence-on-color: "lightgreen"
state-binary_sensor-running-on-color: "orange"
``` ```
and choose the ```versatile_thermostat_theme``` theme in the panel configuration. You will get something that will look like this: and choose the ```versatile_thermostat_theme``` theme in the panel configuration. You will get something that will look like this:

View File

@@ -1324,8 +1324,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
#PR - Adding Window ByPass #PR - Adding Window ByPass
_LOGGER.debug("%s - Window ByPass is : %s", self, self._window_bypass_state) _LOGGER.debug("%s - Window ByPass is : %s", self, self._window_bypass_state)
if self._window_bypass_state: if self._window_bypass_state:
_LOGGER.info("Window ByPass is activated. Ignore window event") _LOGGER.info("%s - Window ByPass is activated. Ignore window event", self)
self.update_custom_attributes()
return return
if self._window_state == STATE_OFF: if self._window_state == STATE_OFF:
@@ -1706,6 +1705,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self, self,
slope if slope is not None else 0.0, slope if slope is not None else 0.0,
) )
if self.window_bypass_state:
_LOGGER.info("%s - Window auto event is ignored because bypass is ON", self)
return
if ( if (
self._window_auto_algo.is_window_open_detected() self._window_auto_algo.is_window_open_detected()
and self._window_auto_state is False and self._window_auto_state is False

View File

@@ -326,7 +326,7 @@
"thermostat_type": { "thermostat_type": {
"options": { "options": {
"thermostat_over_switch": "Termostat nad spínačom", "thermostat_over_switch": "Termostat nad spínačom",
"thermostat_over_climate": "Termostat nad iným termostatom" "thermostat_over_climate": "Termostat nad iným termostatom",
"thermostat_over_valve": "Thermostat over a valve" "thermostat_over_valve": "Thermostat over a valve"
} }
} }
@@ -346,4 +346,4 @@
} }
} }
} }
} }

View File

@@ -1,9 +1,11 @@
# pylint: disable=unused-argument, line-too-long
""" Test the OpenWindow algorithm """ """ Test the OpenWindow algorithm """
from datetime import datetime, timedelta from datetime import datetime, timedelta
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from custom_components.versatile_thermostat.open_window_algorithm import WindowOpenDetectionAlgorithm from custom_components.versatile_thermostat.open_window_algorithm import WindowOpenDetectionAlgorithm
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
async def test_open_window_algo( async def test_open_window_algo(
hass: HomeAssistant, hass: HomeAssistant,

View File

@@ -1,10 +1,12 @@
# pylint: disable=unused-argument, line-too-long, protected-access
""" Test the Window management """ """ Test the Window management """
import asyncio import asyncio
import logging
from unittest.mock import patch, call, PropertyMock from unittest.mock import patch, call, PropertyMock
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@@ -47,7 +49,7 @@ async def test_window_management_time_not_enough(
}, },
) )
entity: VersatileThermostat = await create_thermostat( entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname" hass, entry, "climate.theoverswitchmockname"
) )
assert entity assert entity
@@ -135,7 +137,7 @@ async def test_window_management_time_enough(
}, },
) )
entity: VersatileThermostat = await create_thermostat( entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname" hass, entry, "climate.theoverswitchmockname"
) )
assert entity assert entity
@@ -274,7 +276,7 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
}, },
) )
entity: VersatileThermostat = await create_thermostat( entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname" hass, entry, "climate.theoverswitchmockname"
) )
assert entity assert entity
@@ -424,7 +426,7 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True]) @pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_state): async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the Power management""" """Test the Window auto management"""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@@ -458,7 +460,7 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
}, },
) )
entity: VersatileThermostat = await create_thermostat( entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname" hass, entry, "climate.theoverswitchmockname"
) )
assert entity assert entity
@@ -603,7 +605,7 @@ async def test_window_auto_no_on_percent(
}, },
) )
entity: VersatileThermostat = await create_thermostat( entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname" hass, entry, "climate.theoverswitchmockname"
) )
assert entity assert entity
@@ -711,7 +713,7 @@ async def test_window_bypass(
}, },
) )
entity: VersatileThermostat = await create_thermostat( entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname" hass, entry, "climate.theoverswitchmockname"
) )
assert entity assert entity
@@ -746,8 +748,11 @@ async def test_window_bypass(
assert mock_heater_off.call_count == 0 assert mock_heater_off.call_count == 0
assert mock_send_event.call_count == 0 assert mock_send_event.call_count == 0
#Set Window ByPass to true # Set Window ByPass to true
entity._window_bypass_state = True await entity.service_set_window_bypass_state(True)
assert entity.window_bypass_state is True
# entity._window_bypass_state = True
# Open the window, condition of time is satisfied, check the thermostat and heater turns off # Open the window, condition of time is satisfied, check the thermostat and heater turns off
with patch( with patch(
@@ -802,5 +807,246 @@ async def test_window_bypass(
assert entity.hvac_mode is HVACMode.HEAT assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
# Clean the entity
entity.remove_thermostat()
#PR - Adding Window bypass for window auto algorithm
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_auto_bypass(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the Window auto management"""
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": 21,
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_AUTO_OPEN_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
tz = get_tz(hass) # pylint: disable=invalid-name
now = datetime.now(tz)
tpi_algo = entity._prop_algorithm
assert tpi_algo
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.target_temperature == 21
assert entity.window_state is None
# Make the temperature down
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,
):
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 19, event_timestamp)
# The heater turns on
assert mock_heater_on.call_count == 1
assert entity.last_temperature_slope is None
assert entity._window_auto_algo.is_window_open_detected() is False
assert entity._window_auto_algo.is_window_close_detected() is False
assert entity.hvac_mode is HVACMode.HEAT
# send one degre down in one minute with window bypass on
await entity.service_set_window_bypass_state(True)
assert entity.window_bypass_state is True
# entity._window_bypass_state = True
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,
):
event_timestamp = now - timedelta(minutes=3)
await send_temperature_change_event(entity, 18, event_timestamp, sleep=False)
# No change should have been done
assert mock_send_event.call_count == 0
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 0
assert entity.last_temperature_slope == -1
assert entity._window_auto_algo.is_window_open_detected() is True
assert entity._window_auto_algo.is_window_close_detected() is False
assert entity.window_auto_state == STATE_OFF
assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity
entity.remove_thermostat()
#PR - Adding Window bypass AFTER detection have been done should reactivate the heater
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_bypass_reactivate(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the Window management when window is open and then bypass is set to on"""
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
tpi_algo = entity._prop_algorithm
assert tpi_algo
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.target_temperature == 19
assert entity.window_state is None
# change temperature to force turning on the heater
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,
):
await send_temperature_change_event(entity, 15, datetime.now())
# Heater shoud turn-on
assert mock_heater_on.call_count >= 1
assert mock_heater_off.call_count == 0
assert mock_send_event.call_count == 0
# Open the window, condition of time is satisfied, check the thermostat and heater turns off
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(
"homeassistant.helpers.condition.state", return_value=True
) as mock_condition, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True,
):
await send_window_change_event(entity, True, False, datetime.now())
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})]
)
# Heater should not be on
assert mock_heater_on.call_count == 0
# One call in set_hvac_mode turn_off and one call in the control_heating for security
assert mock_heater_off.call_count == 2
assert mock_condition.call_count == 1
assert entity.hvac_mode is HVACMode.OFF
assert entity.window_state == STATE_ON
# Call the set bypass service to set bypass ON
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,
):
await entity.service_set_window_bypass_state(True)
assert entity.window_state == STATE_ON
assert entity.preset_mode is PRESET_BOOST
assert entity.hvac_mode is HVACMode.HEAT
# assert mock_heater_on.call_count == 1
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
),
],
any_order=False,
)
# Clean the entity # Clean the entity
entity.remove_thermostat() entity.remove_thermostat()