Feature 124 add inversion pilot wire (#149)
* Add inverse switch command for ThermostatOverSwitch --------- Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
@@ -54,7 +54,8 @@ from custom_components.versatile_thermostat.const import (
|
||||
CONF_AUTO_REGULATION_STRONG,
|
||||
CONF_AUTO_REGULATION_NONE,
|
||||
CONF_AUTO_REGULATION_DTEMP,
|
||||
CONF_AUTO_REGULATION_PERIOD_MIN
|
||||
CONF_AUTO_REGULATION_PERIOD_MIN,
|
||||
CONF_INVERSE_SWITCH
|
||||
)
|
||||
MOCK_TH_OVER_SWITCH_USER_CONFIG = {
|
||||
CONF_NAME: "TheOverSwitchMockName",
|
||||
@@ -101,13 +102,15 @@ MOCK_TH_OVER_CLIMATE_USER_CONFIG = {
|
||||
MOCK_TH_OVER_SWITCH_TYPE_CONFIG = {
|
||||
CONF_HEATER: "switch.mock_switch",
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_AC_MODE: False
|
||||
CONF_AC_MODE: False,
|
||||
CONF_INVERSE_SWITCH: False
|
||||
}
|
||||
|
||||
MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG = {
|
||||
CONF_HEATER: "switch.mock_air_conditioner",
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_AC_MODE: True,
|
||||
CONF_INVERSE_SWITCH: False
|
||||
}
|
||||
|
||||
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
|
||||
@@ -117,6 +120,7 @@ MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
|
||||
CONF_HEATER_4: "switch.mock_4switch3",
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_AC_MODE: False,
|
||||
CONF_INVERSE_SWITCH: False
|
||||
}
|
||||
|
||||
MOCK_TH_OVER_SWITCH_TPI_CONFIG = {
|
||||
|
||||
@@ -17,7 +17,7 @@ async def test_show_form(hass: HomeAssistant) -> None:
|
||||
# Init the API
|
||||
# hass.data["custom_components"] = None
|
||||
# loader.async_get_custom_components(hass)
|
||||
# VersatileThermostatAPI(hass)
|
||||
# BaseThermostatAPI(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
@@ -369,7 +369,7 @@ async def test_user_config_flow_over_4_switches(
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False
|
||||
}
|
||||
|
||||
TYPE_CONFIG = { # pylint: disable=wildcard-import, invalid-name
|
||||
@@ -434,6 +434,7 @@ async def test_user_config_flow_over_4_switches(
|
||||
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
|
||||
| MOCK_PRESETS_CONFIG
|
||||
| MOCK_ADVANCED_CONFIG
|
||||
| { CONF_INVERSE_SWITCH: False }
|
||||
)
|
||||
assert result["result"]
|
||||
assert result["result"].domain == DOMAIN
|
||||
|
||||
124
tests/test_inverted_switch.py
Normal file
124
tests/test_inverted_switch.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# pylint: disable=unused-argument, line-too-long, protected-access
|
||||
""" Test the Window management """
|
||||
import asyncio
|
||||
import logging
|
||||
from unittest.mock import patch, call
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from custom_components.versatile_thermostat.thermostat_switch import ThermostatOverSwitch
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_inverted_switch(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: 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,
|
||||
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
|
||||
CONF_INVERSE_SWITCH: True
|
||||
},
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.core.ServiceRegistry.async_call"
|
||||
) as mock_service_call, patch(
|
||||
"homeassistant.core.StateMachine.is_state", return_value=True # switch is On
|
||||
):
|
||||
entity: ThermostatOverSwitch = await create_thermostat(
|
||||
hass, entry, "climate.theoverswitchmockname"
|
||||
)
|
||||
assert entity
|
||||
assert entity.is_inversed
|
||||
|
||||
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.target_temperature == 21
|
||||
assert entity.is_device_active is False
|
||||
|
||||
assert mock_service_call.call_count == 0
|
||||
|
||||
# 1. Make the temperature down to activate the switch
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
), patch(
|
||||
"homeassistant.core.ServiceRegistry.async_call"
|
||||
) as mock_service_call, patch(
|
||||
"homeassistant.core.StateMachine.is_state", return_value=True # switch is Off
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
# not updated cause mocked assert entity.is_device_active is True
|
||||
|
||||
assert mock_service_call.call_count == 1
|
||||
mock_service_call.assert_has_calls([
|
||||
call.async_call('switch', SERVICE_TURN_OFF, {'entity_id': 'switch.mock_switch'}),
|
||||
])
|
||||
|
||||
# 2. Make the temperature up to deactivate the switch
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
), patch(
|
||||
"homeassistant.core.ServiceRegistry.async_call"
|
||||
) as mock_service_call, patch(
|
||||
"homeassistant.core.StateMachine.is_state", return_value=False # switch is On -> it should turned off
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
await send_temperature_change_event(entity, 25, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
# not updated cause mocked assert entity.is_device_active is False
|
||||
|
||||
# there is no change because the cycle is currenlty running.
|
||||
# we should simulate the end of the cycle to see oif underlying switch turns on
|
||||
await entity._underlyings[0].turn_off()
|
||||
|
||||
assert mock_service_call.call_count == 1
|
||||
mock_service_call.assert_has_calls([
|
||||
call.async_call('switch', SERVICE_TURN_ON, {'entity_id': 'switch.mock_switch'}),
|
||||
])
|
||||
|
||||
|
||||
|
||||
# Clean the entity
|
||||
entity.remove_thermostat()
|
||||
@@ -1,10 +1,12 @@
|
||||
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable
|
||||
|
||||
""" Test the Window management """
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from unittest.mock import patch, call, PropertyMock
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
from unittest.mock import patch
|
||||
|
||||
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
@@ -54,7 +56,7 @@ async def test_movement_management_time_not_enough(
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theoverswitchmockname"
|
||||
)
|
||||
assert entity
|
||||
@@ -251,7 +253,7 @@ async def test_movement_management_time_enough_and_presence(
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theoverswitchmockname"
|
||||
)
|
||||
assert entity
|
||||
@@ -383,7 +385,7 @@ async def test_movement_management_time_enoughand_not_presence(
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theoverswitchmockname"
|
||||
)
|
||||
assert entity
|
||||
@@ -517,7 +519,7 @@ async def test_movement_management_with_stop_during_condition(
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theoverswitchmockname"
|
||||
)
|
||||
assert entity
|
||||
@@ -597,4 +599,3 @@ async def test_movement_management_with_stop_during_condition(
|
||||
assert entity.target_temperature == 19 # Boost
|
||||
assert entity.motion_state is "on" # switch to movement on
|
||||
assert entity.presence_state is "off" # Non change
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable
|
||||
|
||||
""" Test the Multiple switch management """
|
||||
import asyncio
|
||||
from unittest.mock import patch, call, ANY
|
||||
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)
|
||||
@@ -50,7 +53,7 @@ async def test_one_switch_cycle(
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theover4switchmockname"
|
||||
)
|
||||
assert entity
|
||||
@@ -260,7 +263,7 @@ async def test_multiple_switchs(
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theover4switchmockname"
|
||||
)
|
||||
assert entity
|
||||
@@ -396,7 +399,7 @@ async def test_multiple_climates(
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theover4climatemockname"
|
||||
)
|
||||
assert entity
|
||||
@@ -496,7 +499,7 @@ async def test_multiple_climates_underlying_changes(
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theover4climatemockname"
|
||||
)
|
||||
assert entity
|
||||
|
||||
@@ -16,6 +16,7 @@ def test_pi_algorithm_basics():
|
||||
|
||||
# to reset the accumulated erro
|
||||
the_algo.set_target_temp(20)
|
||||
the_algo.reset_accumulated_error()
|
||||
|
||||
# Test the accumulator threshold effect and offset_max
|
||||
assert the_algo.calculate_regulated_temperature(10, 10) == 22 # +2
|
||||
@@ -23,8 +24,8 @@ def test_pi_algorithm_basics():
|
||||
assert the_algo.calculate_regulated_temperature(10, 10) == 22
|
||||
# Will keep infinitly 22.0
|
||||
|
||||
# to reset the accumulated erro
|
||||
the_algo.set_target_temp(20)
|
||||
# to reset the accumulated error
|
||||
the_algo.reset_accumulated_error()
|
||||
assert the_algo.calculate_regulated_temperature(18, 10) == 21.5 # +1.5
|
||||
assert the_algo.calculate_regulated_temperature(18.1, 10) == 21.6 # +1.6
|
||||
assert the_algo.calculate_regulated_temperature(18.3, 10) == 21.6 # +1.6
|
||||
@@ -104,6 +105,7 @@ def test_pi_algorithm_medium():
|
||||
|
||||
# to reset the accumulated erro
|
||||
the_algo.set_target_temp(20)
|
||||
the_algo.reset_accumulated_error()
|
||||
# Test the error acculation effect
|
||||
assert the_algo.calculate_regulated_temperature(19, 5) == 22.1
|
||||
assert the_algo.calculate_regulated_temperature(19, 5) == 22.2
|
||||
@@ -157,6 +159,7 @@ def test_pi_algorithm_strong():
|
||||
|
||||
# to reset the accumulated erro
|
||||
the_algo.set_target_temp(20)
|
||||
the_algo.reset_accumulated_error()
|
||||
# Test the error acculation effect
|
||||
assert the_algo.calculate_regulated_temperature(19, 10) == 22.8
|
||||
assert the_algo.calculate_regulated_temperature(19, 10) == 23
|
||||
|
||||
Reference in New Issue
Block a user