Add valve change tests (ko)

This commit is contained in:
Jean-Marc Collin
2023-10-27 06:57:40 +00:00
parent 7afa67336b
commit e7c39f144b
11 changed files with 187 additions and 203 deletions

View File

@@ -732,17 +732,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
return f"VersatileThermostat-{self.name}"
@property
def is_over_climate(self):
def is_over_climate(self) -> bool:
""" True if the Thermostat is over_climate"""
return False
@property
def is_over_switch(self):
def is_over_switch(self) -> bool:
""" True if the Thermostat is over_switch"""
return False
@property
def is_over_valve(self):
def is_over_valve(self) -> bool:
""" True if the Thermostat is over_valve"""
return False
@@ -853,7 +853,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"""
if self._hvac_mode == HVACMode.OFF:
return HVACAction.OFF
if not self._is_device_active:
if not self.is_device_active:
return HVACAction.IDLE
if self._hvac_mode == HVACMode.COOL:
return HVACAction.COOLING
@@ -873,7 +873,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
return self._support_flags
@property
def _is_device_active(self):
def is_device_active(self):
"""Returns true if one underlying is active"""
for under in self._underlyings:
if under.is_device_active:
@@ -1093,7 +1093,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self._async_set_preset_mode_internal(self._attr_preset_mode, True)
if need_control_heating and sub_need_control_heating:
await self._async_control_heating(force=True)
await self.async_control_heating(force=True)
# Ensure we update the current operation after changing the mode
self.reset_last_temperature_time()
@@ -1107,7 +1107,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
async def async_set_preset_mode(self, preset_mode):
"""Set new preset mode."""
await self._async_set_preset_mode_internal(preset_mode)
await self._async_control_heating(force=True)
await self.async_control_heating(force=True)
async def _async_set_preset_mode_internal(self, preset_mode, force=False):
"""Set new preset mode."""
@@ -1240,7 +1240,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._attr_preset_mode = PRESET_NONE
self.recalculate()
self.reset_last_change_time()
await self._async_control_heating(force=True)
await self.async_control_heating(force=True)
async def _async_internal_set_temperature(self, temperature):
"""Set the target temperature and the target temperature of underlying climate if any"""
@@ -1290,7 +1290,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self._async_update_temp(new_state)
self.recalculate()
await self._async_control_heating(force=False)
await self.async_control_heating(force=False)
async def _async_ext_temperature_changed(self, event: Event):
"""Handle external temperature opf the sensor changes."""
@@ -1305,7 +1305,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self._async_update_ext_temp(new_state)
self.recalculate()
await self._async_control_heating(force=False)
await self.async_control_heating(force=False)
@callback
async def _async_windows_changed(self, event):
@@ -1431,7 +1431,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self.find_preset_temp(new_preset)
)
self.recalculate()
await self._async_control_heating(force=True)
await self.async_control_heating(force=True)
self._motion_call_cancel = None
im_on = self._motion_state == STATE_ON
@@ -1519,7 +1519,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
if changes:
self.async_write_ha_state()
self.update_custom_attributes()
await self._async_control_heating()
await self.async_control_heating()
new_state = event.data.get("new_state")
_LOGGER.debug("%s - _async_climate_changed new_state is %s", self, new_state)
@@ -1746,7 +1746,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._current_power = current_power
if self._attr_preset_mode == PRESET_POWER:
await self._async_control_heating()
await self.async_control_heating()
except ValueError as ex:
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
@@ -1771,7 +1771,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
raise ValueError(f"Sensor has illegal state {new_state.state}")
self._current_power_max = current_power_max
if self._attr_preset_mode == PRESET_POWER:
await self._async_control_heating()
await self.async_control_heating()
except ValueError as ex:
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
@@ -1791,7 +1791,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
return
await self._async_update_presence(new_state.state)
await self._async_control_heating(force=True)
await self.async_control_heating(force=True)
async def _async_update_presence(self, new_state):
_LOGGER.debug("%s - Updating presence. New state is %s", self, new_state)
@@ -2237,7 +2237,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
return shouldBeInSecurity
async def _async_control_heating(self, force=False, _=None):
async def async_control_heating(self, force=False, _=None):
"""The main function used to run the calculation at each cycle"""
_LOGGER.debug(
@@ -2278,18 +2278,18 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
if self._hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF)", self)
# A security to force stop heater if still active
if self._is_device_active:
if self.is_device_active:
await self._async_underlying_entity_turn_off()
return True
if not self.is_over_climate:
for under in self._underlyings:
await under.start_cycle(
self._hvac_mode,
self._prop_algorithm.on_time_sec,
self._prop_algorithm.off_time_sec,
force,
)
for under in self._underlyings:
await under.start_cycle(
self._hvac_mode,
self._prop_algorithm.on_time_sec if self._prop_algorithm else None,
self._prop_algorithm.off_time_sec if self._prop_algorithm else None,
self._prop_algorithm.on_percent if self._prop_algorithm else None,
force,
)
self.update_custom_attributes()
return True
@@ -2329,6 +2329,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._total_energy,
)
# TODO implement this with overrides
def update_custom_attributes(self):
"""Update the custom extra attributes for the entity"""
@@ -2459,7 +2460,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"""
_LOGGER.info("%s - Calling service_set_presence, presence: %s", self, presence)
await self._async_update_presence(presence)
await self._async_control_heating(force=True)
await self.async_control_heating(force=True)
async def service_set_preset_temperature(
self, preset, temperature=None, temperature_away=None
@@ -2498,7 +2499,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self._async_set_preset_mode_internal(
preset.rstrip(PRESET_AC_SUFFIX), force=True
)
await self._async_control_heating(force=True)
await self.async_control_heating(force=True)
async def service_set_security(self, delay_min, min_on_percent, default_on_percent):
"""Called by a service call:
@@ -2527,7 +2528,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
if self._prop_algorithm and self._security_state:
self._prop_algorithm.set_security(self._security_default_on_percent)
await self._async_control_heating()
await self.async_control_heating()
self.update_custom_attributes()
def send_event(self, event_type: EventType, data: dict):

View File

@@ -227,3 +227,14 @@ class UnknownEntity(HomeAssistantError):
class WindowOpenDetectionMethod(HomeAssistantError):
"""Error to indicate there is an error in the window open detection method given."""
class overrides: # pylint: disable=invalid-name
""" An annotation to inform overrides """
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
return self.func.__get__(instance, owner)
def __call__(self, *args, **kwargs):
raise RuntimeError(f"Method {self.func.__name__} should have been overridden")

View File

@@ -23,7 +23,7 @@ class ThermostatOverClimate(BaseThermostat):
super().__init__(hass, unique_id, name, entry_infos)
@property
def is_over_climate(self):
def is_over_climate(self) -> bool:
""" True if the Thermostat is over_climate"""
return True
@@ -93,7 +93,7 @@ class ThermostatOverClimate(BaseThermostat):
self.async_on_remove(
async_track_time_interval(
self.hass,
self._async_control_heating,
self.async_control_heating,
interval=timedelta(minutes=self._cycle_min),
)
)

View File

@@ -24,7 +24,7 @@ class ThermostatOverSwitch(BaseThermostat):
super().__init__(hass, unique_id, name, entry_infos)
@property
def is_over_switch(self):
def is_over_switch(self) -> bool:
""" True if the Thermostat is over_switch"""
return True
@@ -65,4 +65,4 @@ class ThermostatOverSwitch(BaseThermostat):
)
)
self.hass.create_task(self._async_control_heating())
self.hass.create_task(self.async_control_heating())

View File

@@ -24,10 +24,15 @@ class ThermostatOverValve(BaseThermostat):
super().__init__(hass, unique_id, name, entry_infos)
@property
def is_over_valve(self):
def is_over_valve(self) -> bool:
""" True if the Thermostat is over_valve"""
return True
@property
def valve_open_percent(self) -> int:
""" Gives the percentage of valve needed"""
return round(max(0, min(self.proportional_algorithm.on_percent, 1)) * 100)
def post_init(self, entry_infos):
""" Initialize the Thermostat"""
@@ -40,7 +45,7 @@ class ThermostatOverValve(BaseThermostat):
if entry_infos.get(CONF_VALVE_4):
lst_valves.append(entry_infos.get(CONF_VALVE_4))
for valve in enumerate(lst_valves):
for _, valve in enumerate(lst_valves):
self._underlyings.append(
UnderlyingValve(
hass=self._hass,
@@ -68,7 +73,7 @@ class ThermostatOverValve(BaseThermostat):
self.async_on_remove(
async_track_time_interval(
self.hass,
self._async_control_heating,
self.async_control_heating,
interval=timedelta(minutes=self._cycle_min),
)
)
@@ -90,7 +95,7 @@ class ThermostatOverValve(BaseThermostat):
if changes:
self.async_write_ha_state()
self.update_custom_attributes()
await self._async_control_heating()
await self.async_control_heating()
new_state = event.data.get("new_state")
_LOGGER.debug("%s - _async_climate_changed new_state is %s", self, new_state)

View File

@@ -24,10 +24,13 @@ from homeassistant.components.climate import (
SERVICE_TURN_ON,
SERVICE_SET_TEMPERATURE,
)
from homeassistant.components.number import SERVICE_SET_VALUE
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_call_later
from .const import UnknownEntity
from .const import UnknownEntity, overrides
_LOGGER = logging.getLogger(__name__)
@@ -160,6 +163,19 @@ class UnderlyingEntity:
"""Call the method after a delay"""
return async_call_later(hass, delay_sec, called_method)
async def start_cycle(
self,
hvac_mode: HVACMode,
on_time_sec: int,
off_time_sec: int,
on_percent: int,
force=False,
):
"""Starting cycle for switch"""
def _cancel_cycle(self):
""" Stops an eventual cycle """
class UnderlyingSwitch(UnderlyingEntity):
"""Represent a underlying switch"""
@@ -215,11 +231,13 @@ class UnderlyingSwitch(UnderlyingEntity):
"""If the toggleable device is currently active."""
return self._hass.states.is_state(self._entity_id, STATE_ON)
@overrides
async def start_cycle(
self,
hvac_mode: HVACMode,
on_time_sec: int,
off_time_sec: int,
on_percent: int,
force=False,
):
"""Starting cycle for switch"""
@@ -270,6 +288,7 @@ class UnderlyingSwitch(UnderlyingEntity):
else:
_LOGGER.debug("%s - nothing to do", self)
@overrides
def _cancel_cycle(self):
"""Cancel the cycle"""
if self._async_cancel_cycle:
@@ -303,15 +322,6 @@ class UnderlyingSwitch(UnderlyingEntity):
time = self._on_time_sec
action_label = "start"
# if self._should_relaunch_control_heating:
# _LOGGER.debug("Don't %s cause a cycle have to be relaunch", action_label)
# self._should_relaunch_control_heating = False
# # self.hass.create_task(self._async_control_heating())
# await self.start_cycle(
# self._hvac_mode, self._on_time_sec, self._off_time_sec
# )
# _LOGGER.debug("%s - End of cycle (3)", self)
# return
if time > 0:
_LOGGER.info(
@@ -348,16 +358,6 @@ class UnderlyingSwitch(UnderlyingEntity):
return
action_label = "stop"
# if self._should_relaunch_control_heating:
# _LOGGER.debug("Don't %s cause a cycle have to be relaunch", action_label)
# self._should_relaunch_control_heating = False
# # self.hass.create_task(self._async_control_heating())
# await self.start_cycle(
# self._hvac_mode, self._on_time_sec, self._off_time_sec
# )
# _LOGGER.debug("%s - End of cycle (3)", self)
# return
time = self._off_time_sec
if time > 0:
@@ -636,7 +636,7 @@ class UnderlyingValve(UnderlyingEntity):
"""Represent a underlying switch"""
_hvac_mode: HVACMode
# The percentage of opening the valve
# This is the percentage of opening int integer (from 0 to 100)
_percent_open: int
def __init__(
@@ -656,7 +656,7 @@ class UnderlyingValve(UnderlyingEntity):
self._async_cancel_cycle = None
self._should_relaunch_control_heating = False
self._hvac_mode = None
self._percent_open = 0
self._percent_open = self._thermostat.valve_open_percent
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
"""Set the HVACmode. Returns true if something have change"""
@@ -680,132 +680,35 @@ class UnderlyingValve(UnderlyingEntity):
except Exception: # pylint: disable=broad-exception-caught
return False
@overrides
async def start_cycle(
self,
hvac_mode: HVACMode,
_1,
_2,
_3,
force=False,
):
"""Starting cycle for switch"""
_LOGGER.debug(
"%s - Starting new cycle hvac_mode=%s percent_open=%d force=%s",
self,
hvac_mode,
self._percent_open,
force,
)
"""We use this function to change the on_percent"""
caped_val = self._thermostat.valve_open_percent
if not force and self._percent_open == caped_val:
# No changes
return
self._hvac_mode = hvac_mode
self._percent_open = caped_val
# Send the new command to valve via a service call
# Cancel eventual previous cycle if any
if self._async_cancel_cycle is not None:
if force:
_LOGGER.debug("%s - we force a new cycle", self)
self._cancel_cycle()
else:
_LOGGER.debug(
"%s - A previous cycle is alredy running and no force -> waits for its end",
self,
)
# self._should_relaunch_control_heating = True
_LOGGER.debug("%s - End of cycle (2)", self)
return
try:
_LOGGER.info("%s - Setting valve ouverture percent to %s", self, self._percent_open)
# If we should heat, starts the cycle with delay
if self._hvac_mode in [HVACMode.HEAT, HVACMode.COOL] and self._percent_open > 0:
# Starts the cycle after the initial delay
self._async_cancel_cycle = self.call_later(
self._hass, 0, self._turn_on_later
data = { "value": self._percent_open }
await self._hass.services.async_call(
HA_DOMAIN,
SERVICE_SET_VALUE,
data,
)
_LOGGER.debug("%s - _async_cancel_cycle=%s", self, self._async_cancel_cycle)
# if we not heat but device is active
elif self.is_device_active:
_LOGGER.info(
"%s - stop heating (2)",
self,
)
await self.turn_off()
else:
_LOGGER.debug("%s - nothing to do", self)
def _cancel_cycle(self):
"""Cancel the cycle"""
if self._async_cancel_cycle:
self._async_cancel_cycle()
self._async_cancel_cycle = None
_LOGGER.debug("%s - Stopping cycle during calculation", self)
async def _turn_on_later(self, _):
"""Turn the heater on after a delay"""
_LOGGER.debug(
"%s - calling turn_on_later hvac_mode=%s, should_relaunch_later=%s",
self,
self._hvac_mode,
self._should_relaunch_control_heating,
)
self._cancel_cycle()
if self._hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self)
if self.is_device_active:
await self.turn_off()
return
if await self._thermostat.check_overpowering():
_LOGGER.debug("%s - End of cycle (3)", self)
return
# Security mode could have change the on_time percent
await self._thermostat.check_security()
action_label = "start"
_LOGGER.info(
"%s - %s heating",
self,
action_label,
)
await self.turn_on()
self._async_cancel_cycle = self.call_later(
self._hass,
0,
self._turn_off_later,
)
async def _turn_off_later(self, _):
"""Turn the heater off and call the next cycle after the delay"""
_LOGGER.debug(
"%s - calling turn_off_later hvac_mode=%s, should_relaunch_later=%s",
self,
self._hvac_mode,
self._should_relaunch_control_heating,
)
self._cancel_cycle()
if self._hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self)
if self.is_device_active:
await self.turn_off()
return
action_label = "stop"
_LOGGER.info(
"%s - %s heating",
self,
action_label
)
await self.turn_off()
self._async_cancel_cycle = self.call_later(
self._hass,
0,
self._turn_on_later
)
# increment energy at the end of the cycle
self._thermostat.incremente_energy()
except ServiceNotFound as err:
_LOGGER.error(err)
def remove_entity(self):
"""Remove the entity after stopping its cycle"""

View File

@@ -82,7 +82,7 @@ def skip_hass_states_get_fixture():
def skip_control_heating_fixture():
"""Skip the control_heating of VersatileThermostat"""
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat._async_control_heating"
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
):
yield

View File

@@ -62,9 +62,9 @@ async def test_bug_56(
# Should not failed
entity.update_custom_attributes()
# try to call _async_control_heating
# try to call async_control_heating
try:
ret = await entity._async_control_heating()
ret = await entity.async_control_heating()
# an exception should be send
assert ret is False
except Exception: # pylint: disable=broad-exception-caught
@@ -75,9 +75,9 @@ async def test_bug_56(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
return_value=the_mock_underlying, # dont find the underlying climate
):
# try to call _async_control_heating
# try to call async_control_heating
try:
await entity._async_control_heating()
await entity.async_control_heating()
except UnknownEntity:
assert False
except Exception: # pylint: disable=broad-exception-caught

View File

@@ -64,7 +64,7 @@ async def test_movement_management_time_not_enough(
# 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"
"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)
@@ -261,7 +261,7 @@ async def test_movement_management_time_enough_and_presence(
# 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"
"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)
@@ -393,7 +393,7 @@ async def test_movement_management_time_enoughand_not_presence(
# 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"
"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)
@@ -527,7 +527,7 @@ async def test_movement_management_with_stop_during_condition(
# 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"
"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)

View File

@@ -58,7 +58,7 @@ async def test_one_switch_cycle(
# 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"
"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_BOOST)
@@ -75,7 +75,7 @@ async def test_one_switch_cycle(
with patch(
"homeassistant.core.StateMachine.is_state", return_value=False
) as mock_is_state:
assert entity._is_device_active is False # pylint: disable=protected-access
assert entity.is_device_active is False # pylint: disable=protected-access
# Should be call for the Switch
assert mock_is_state.call_count == 1
@@ -269,7 +269,7 @@ async def test_multiple_switchs(
# 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"
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
@@ -285,7 +285,7 @@ async def test_multiple_switchs(
await send_temperature_change_event(entity, 15, event_timestamp)
# Checks that all climates are off
assert entity._is_device_active is False # pylint: disable=protected-access
assert entity.is_device_active is False # pylint: disable=protected-access
# Should be call for all Switch
assert mock_underlying_set_hvac_mode.call_count == 4
@@ -405,7 +405,7 @@ async def test_multiple_climates(
# 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"
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
@@ -427,11 +427,11 @@ async def test_multiple_climates(
call.set_hvac_mode(HVACMode.HEAT),
]
)
assert entity._is_device_active is False # pylint: disable=protected-access
assert entity.is_device_active is False # pylint: disable=protected-access
# Stop 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"
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
@@ -452,7 +452,7 @@ async def test_multiple_climates(
call.set_hvac_mode(HVACMode.OFF),
]
)
assert entity._is_device_active is False # pylint: disable=protected-access
assert entity.is_device_active is False # pylint: disable=protected-access
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -505,7 +505,7 @@ async def test_multiple_climates_underlying_changes(
# 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"
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
@@ -527,11 +527,11 @@ async def test_multiple_climates_underlying_changes(
call.set_hvac_mode(HVACMode.HEAT),
]
)
assert entity._is_device_active is False # pylint: disable=protected-access
assert entity.is_device_active is False # pylint: disable=protected-access
# Stop heating on one underlying climate
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat._async_control_heating"
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
@@ -554,11 +554,11 @@ async def test_multiple_climates_underlying_changes(
]
)
assert entity.hvac_mode == HVACMode.OFF
assert entity._is_device_active is False # pylint: disable=protected-access
assert entity.is_device_active is False # pylint: disable=protected-access
# Start heating on one underlying climate
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat._async_control_heating"
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode, patch(
@@ -587,4 +587,4 @@ async def test_multiple_climates_underlying_changes(
)
assert entity.hvac_mode == HVACMode.HEAT
assert entity.hvac_action == HVACAction.IDLE
assert entity._is_device_active is False # pylint: disable=protected-access
assert entity.is_device_active is False # pylint: disable=protected-access

View File

@@ -1,3 +1,5 @@
# pylint: disable=line-too-long
""" Test the normal start of a Switch AC Thermostat """
from unittest.mock import patch, call
from datetime import datetime, timedelta
@@ -11,7 +13,6 @@ from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DO
from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.thermostat_valve import ThermostatOverValve
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
@@ -84,7 +85,7 @@ async def test_over_valve_full_start(hass: HomeAssistant, skip_hass_states_is_st
return entity
# The name is in the CONF and not the title of the entry
entity: BaseThermostat = find_my_entity("climate.theovervalvemockname")
entity: ThermostatOverValve = find_my_entity("climate.theovervalvemockname")
assert entity
assert isinstance(entity, ThermostatOverValve)
@@ -114,7 +115,6 @@ async def test_over_valve_full_start(hass: HomeAssistant, skip_hass_states_is_st
# should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT
assert mock_send_event.call_count == 2
mock_send_event.assert_has_calls(
[
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_NONE}),
@@ -125,9 +125,73 @@ async def test_over_valve_full_start(hass: HomeAssistant, skip_hass_states_is_st
]
)
# Set the HVACMode to HEAT, with manual preset and target_temp to 18 before receiving temperature
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
# Select a hvacmode, presence and preset
await entity.async_set_hvac_mode(HVACMode.COOL)
assert entity.hvac_mode is HVACMode.COOL
await entity.async_set_hvac_mode(HVACMode.HEAT)
#
assert entity.hvac_mode is HVACMode.HEAT
# No heating now
assert entity.valve_open_percent == 0
assert entity.hvac_action == HVACAction.IDLE
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.HVAC_MODE_EVENT,
{"hvac_mode": HVACMode.HEAT},
),
]
)
# set manual target temp
await entity.async_set_temperature(temperature=18)
assert entity.preset_mode == PRESET_NONE # Manual mode
assert entity.target_temperature == 18
# Nothing have changed cause we don't have room and external temperature
assert mock_send_event.call_count == 1
# Set temperature and external temperature
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingValve.is_device_active", return_value=True
):
# Change temperature
event_timestamp = now - timedelta(minutes=10)
await send_temperature_change_event(entity, 15, datetime.now())
await send_ext_temperature_change_event(entity, 10, datetime.now())
# Should heating strongly now
assert entity.valve_open_percent == 98
assert entity.is_device_active is True
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count == 2
mock_service_call.assert_has_calls([
call.async_call(
HA_DOMAIN,
SERVICE_SET_VALUE,
{
"value": 90
}
),
call.async_call(
HA_DOMAIN,
SERVICE_SET_VALUE,
{
"value": 98
}
)
])
assert mock_send_event.call_count == 0
# ICI
event_timestamp = now - timedelta(minutes=4)
await send_presence_change_event(entity, True, False, event_timestamp)