With all testu developed and ok
This commit is contained in:
@@ -3,7 +3,9 @@ default_config:
|
||||
logger:
|
||||
default: info
|
||||
logs:
|
||||
custom_components.versatile_thermostat: debug
|
||||
custom_components.versatile_thermostat: info
|
||||
custom_components.versatile_thermostat.underlyings: debug
|
||||
custom_components.versatile_thermostat.climate: debug
|
||||
|
||||
# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
|
||||
debugpy:
|
||||
|
||||
@@ -11,7 +11,6 @@ from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
callback,
|
||||
CoreState,
|
||||
DOMAIN as HA_DOMAIN,
|
||||
Event,
|
||||
State,
|
||||
)
|
||||
@@ -73,8 +72,6 @@ from homeassistant.const import (
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_HOME,
|
||||
STATE_NOT_HOME,
|
||||
)
|
||||
@@ -143,6 +140,9 @@ from .open_window_algorithm import WindowOpenDetectionAlgorithm
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# TODO remove this
|
||||
_LOGGER.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -259,7 +259,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self._thermostat_type = None
|
||||
self._is_over_climate = False
|
||||
# TODO should be delegated to underlying climate
|
||||
self._heater_entity_id = None
|
||||
# self._heater_entity_id = None
|
||||
# self._climate_entity_id = None
|
||||
self._underlying_climate = None
|
||||
|
||||
@@ -311,10 +311,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
else:
|
||||
_LOGGER.debug("value %s not found in Entry", value)
|
||||
|
||||
# Stop eventual cycle running
|
||||
if self._async_cancel_cycle is not None:
|
||||
self._async_cancel_cycle()
|
||||
self._async_cancel_cycle = None
|
||||
if self._window_call_cancel is not None:
|
||||
self._window_call_cancel()
|
||||
self._window_call_cancel = None
|
||||
@@ -522,10 +518,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self._total_energy = 0
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - Creation of a new VersatileThermostat entity: unique_id=%s heater_entity_id=%s",
|
||||
"%s - Creation of a new VersatileThermostat entity: unique_id=%s",
|
||||
self,
|
||||
self.unique_id,
|
||||
self._heater_entity_id,
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
@@ -632,9 +627,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
def async_remove_thermostat(self):
|
||||
"""Called when the thermostat will be removed"""
|
||||
_LOGGER.info("%s - Removing thermostat", self)
|
||||
if self._async_cancel_cycle:
|
||||
self._async_cancel_cycle()
|
||||
self._async_cancel_cycle = None
|
||||
for under in self._underlyings:
|
||||
under.remove_entity()
|
||||
|
||||
async def async_startup(self):
|
||||
"""Triggered on startup, used to get old state and set internal states accordingly"""
|
||||
@@ -687,22 +681,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self,
|
||||
)
|
||||
|
||||
if self._is_over_climate:
|
||||
climate_state = self.hass.states.get(self._climate_entity_id)
|
||||
if climate_state and climate_state.state not in (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
self._hvac_mode = climate_state.state
|
||||
need_write_state = True
|
||||
else:
|
||||
switch_state = self.hass.states.get(self._heater_entity_id)
|
||||
if switch_state and switch_state.state not in (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
self.hass.create_task(self._check_switch_initial_state())
|
||||
|
||||
if self._pmax_on:
|
||||
# try to acquire current power and power max
|
||||
current_power_state = self.hass.states.get(self._power_sensor_entity_id)
|
||||
@@ -787,6 +765,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self._prop_algorithm.calculate(
|
||||
self._target_temp, self._cur_temp, self._cur_ext_temp
|
||||
)
|
||||
|
||||
self.hass.create_task(self._check_switch_initial_state())
|
||||
self.hass.create_task(self._async_control_heating())
|
||||
|
||||
await self.get_my_previous_state()
|
||||
@@ -958,15 +938,20 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
return self._unit
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
def hvac_mode(self) -> HVACMode | None:
|
||||
"""Return current operation."""
|
||||
if self._is_over_climate and self._underlying_climate:
|
||||
return self._underlying_climate.hvac_mode
|
||||
if self._is_over_climate:
|
||||
# if one not OFF -> return it
|
||||
# else OFF
|
||||
for under in self._underlyings:
|
||||
if (action := under.hvac_mode) not in [HVACMode.OFF]:
|
||||
return action
|
||||
return HVACMode.OFF
|
||||
|
||||
return self._hvac_mode
|
||||
|
||||
@property
|
||||
def hvac_action(self):
|
||||
def hvac_action(self) -> HVACAction | None:
|
||||
"""Return the current running hvac operation if supported.
|
||||
|
||||
Need to be one of CURRENT_HVAC_*.
|
||||
@@ -1545,12 +1530,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
async def _check_switch_initial_state(self):
|
||||
"""Prevent the device from keep running if HVAC_MODE_OFF."""
|
||||
_LOGGER.debug("%s - Calling _check_switch_initial_state", self)
|
||||
if self._hvac_mode == HVACMode.OFF and self._is_device_active:
|
||||
_LOGGER.warning(
|
||||
"The climate mode is OFF, but the switch device is ON. Turning off device %s",
|
||||
self._heater_entity_id,
|
||||
)
|
||||
await self._async_underlying_entity_turn_off()
|
||||
if self.is_over_climate:
|
||||
return
|
||||
for under in self._underlyings:
|
||||
await under.check_initial_state(self._hvac_mode)
|
||||
|
||||
@callback
|
||||
def _async_switch_changed(self, event):
|
||||
@@ -1838,10 +1821,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
|
||||
async def _async_heater_turn_on(self):
|
||||
"""Turn heater toggleable device on."""
|
||||
data = {ATTR_ENTITY_ID: self._heater_entity_id}
|
||||
await self.hass.services.async_call(
|
||||
HA_DOMAIN, SERVICE_TURN_ON, data, context=self._context
|
||||
)
|
||||
# TODO should be delegated
|
||||
# data = {ATTR_ENTITY_ID: self._heater_entity_id}
|
||||
# await self.hass.services.async_call(
|
||||
# HA_DOMAIN, SERVICE_TURN_ON, data, context=self._context
|
||||
# )
|
||||
for under in self._underlyings:
|
||||
await under.turn_on()
|
||||
|
||||
async def _async_underlying_entity_turn_off(self):
|
||||
"""Turn heater toggleable device off."""
|
||||
@@ -2240,123 +2226,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
return
|
||||
|
||||
if not self._is_over_climate:
|
||||
on_time_sec: int = self._prop_algorithm.on_time_sec
|
||||
off_time_sec: int = self._prop_algorithm.off_time_sec
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - Checking new cycle. on_time_sec=%.0f, off_time_sec=%.0f, security_state=%s, preset_mode=%s",
|
||||
self,
|
||||
on_time_sec,
|
||||
off_time_sec,
|
||||
self._security_state,
|
||||
self._attr_preset_mode,
|
||||
)
|
||||
|
||||
# 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._async_cancel_cycle()
|
||||
self._async_cancel_cycle = None
|
||||
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
|
||||
|
||||
if self._hvac_mode == HVACMode.HEAT and on_time_sec > 0:
|
||||
|
||||
async def _turn_on_off_later(
|
||||
on: bool, # pylint: disable=invalid-name
|
||||
time,
|
||||
heater_action,
|
||||
next_cycle_action,
|
||||
):
|
||||
if self._async_cancel_cycle:
|
||||
self._async_cancel_cycle()
|
||||
self._async_cancel_cycle = None
|
||||
_LOGGER.debug("%s - Stopping cycle during calculation", self)
|
||||
|
||||
if self._hvac_mode == HVACMode.OFF:
|
||||
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self)
|
||||
if self._is_device_active:
|
||||
await self._async_underlying_entity_turn_off()
|
||||
return
|
||||
|
||||
if on:
|
||||
if await self.check_overpowering():
|
||||
_LOGGER.debug("%s - End of cycle (3)", self)
|
||||
return
|
||||
# Security mode could have change the on_time percent
|
||||
await self.check_security()
|
||||
time = self._prop_algorithm.on_time_sec
|
||||
|
||||
action_label = "start" if on else "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._async_control_heating()
|
||||
_LOGGER.debug("%s - End of cycle (3)", self)
|
||||
return
|
||||
|
||||
if time > 0:
|
||||
_LOGGER.info(
|
||||
"%s - %s heating for %d min %d sec",
|
||||
self,
|
||||
action_label,
|
||||
time // 60,
|
||||
time % 60,
|
||||
)
|
||||
await heater_action()
|
||||
else:
|
||||
_LOGGER.debug(
|
||||
"%s - No action on heater cause duration is 0", self
|
||||
)
|
||||
self._async_cancel_cycle = async_call_later(
|
||||
self.hass,
|
||||
time,
|
||||
next_cycle_action,
|
||||
)
|
||||
|
||||
async def _turn_on_later(_):
|
||||
await _turn_on_off_later(
|
||||
on=True,
|
||||
time=self._prop_algorithm.on_time_sec,
|
||||
heater_action=self._async_heater_turn_on,
|
||||
next_cycle_action=_turn_off_later,
|
||||
)
|
||||
self.update_custom_attributes()
|
||||
|
||||
async def _turn_off_later(_):
|
||||
await _turn_on_off_later(
|
||||
on=False,
|
||||
time=self._prop_algorithm.off_time_sec,
|
||||
heater_action=self._async_underlying_entity_turn_off,
|
||||
next_cycle_action=_turn_on_later,
|
||||
)
|
||||
# increment energy at the end of the cycle
|
||||
self.incremente_energy()
|
||||
self.update_custom_attributes()
|
||||
|
||||
await _turn_on_later(None)
|
||||
|
||||
elif self._is_device_active:
|
||||
_LOGGER.info(
|
||||
"%s - stop heating (2) for %d min %d sec",
|
||||
self,
|
||||
off_time_sec // 60,
|
||||
off_time_sec % 60,
|
||||
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,
|
||||
)
|
||||
await self._async_underlying_entity_turn_off()
|
||||
|
||||
else:
|
||||
_LOGGER.debug("%s - nothing to do", self)
|
||||
|
||||
self.update_custom_attributes()
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
""" Some common resources """
|
||||
import asyncio
|
||||
import logging
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from homeassistant.core import HomeAssistant, Event, EVENT_STATE_CHANGED, State
|
||||
@@ -74,6 +76,8 @@ FULL_4SWITCH_CONFIG = (
|
||||
| MOCK_ADVANCED_CONFIG
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MockClimate(ClimateEntity):
|
||||
"""A Mock Climate class used for Underlying climate mode"""
|
||||
@@ -195,8 +199,16 @@ def search_entity(hass: HomeAssistant, entity_id, domain) -> Entity:
|
||||
return None
|
||||
|
||||
|
||||
async def send_temperature_change_event(entity: VersatileThermostat, new_temp, date):
|
||||
async def send_temperature_change_event(
|
||||
entity: VersatileThermostat, new_temp, date, sleep=True
|
||||
):
|
||||
"""Sending a new temperature event simulating a change on temperature sensor"""
|
||||
_LOGGER.info(
|
||||
"------- Testu: sending send_temperature_change_event, new_temp=%.2f date=%s on %s",
|
||||
new_temp,
|
||||
date,
|
||||
entity,
|
||||
)
|
||||
temp_event = Event(
|
||||
EVENT_STATE_CHANGED,
|
||||
{
|
||||
@@ -208,13 +220,21 @@ async def send_temperature_change_event(entity: VersatileThermostat, new_temp, d
|
||||
)
|
||||
},
|
||||
)
|
||||
return await entity._async_temperature_changed(temp_event)
|
||||
await entity._async_temperature_changed(temp_event)
|
||||
if sleep:
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
async def send_ext_temperature_change_event(
|
||||
entity: VersatileThermostat, new_temp, date
|
||||
entity: VersatileThermostat, new_temp, date, sleep=True
|
||||
):
|
||||
"""Sending a new external temperature event simulating a change on temperature sensor"""
|
||||
_LOGGER.info(
|
||||
"------- Testu: sending send_temperature_change_event, new_temp=%.2f date=%s on %s",
|
||||
new_temp,
|
||||
date,
|
||||
entity,
|
||||
)
|
||||
temp_event = Event(
|
||||
EVENT_STATE_CHANGED,
|
||||
{
|
||||
@@ -226,11 +246,21 @@ async def send_ext_temperature_change_event(
|
||||
)
|
||||
},
|
||||
)
|
||||
return await entity._async_ext_temperature_changed(temp_event)
|
||||
await entity._async_ext_temperature_changed(temp_event)
|
||||
if sleep:
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
async def send_power_change_event(entity: VersatileThermostat, new_power, date):
|
||||
async def send_power_change_event(
|
||||
entity: VersatileThermostat, new_power, date, sleep=True
|
||||
):
|
||||
"""Sending a new power event simulating a change on power sensor"""
|
||||
_LOGGER.info(
|
||||
"------- Testu: sending send_temperature_change_event, new_power=%.2f date=%s on %s",
|
||||
new_power,
|
||||
date,
|
||||
entity,
|
||||
)
|
||||
power_event = Event(
|
||||
EVENT_STATE_CHANGED,
|
||||
{
|
||||
@@ -242,11 +272,21 @@ async def send_power_change_event(entity: VersatileThermostat, new_power, date):
|
||||
)
|
||||
},
|
||||
)
|
||||
return await entity._async_power_changed(power_event)
|
||||
await entity._async_power_changed(power_event)
|
||||
if sleep:
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
async def send_max_power_change_event(entity: VersatileThermostat, new_power_max, date):
|
||||
async def send_max_power_change_event(
|
||||
entity: VersatileThermostat, new_power_max, date, sleep=True
|
||||
):
|
||||
"""Sending a new power max event simulating a change on power max sensor"""
|
||||
_LOGGER.info(
|
||||
"------- Testu: sending send_temperature_change_event, new_power_max=%.2f date=%s on %s",
|
||||
new_power_max,
|
||||
date,
|
||||
entity,
|
||||
)
|
||||
power_event = Event(
|
||||
EVENT_STATE_CHANGED,
|
||||
{
|
||||
@@ -258,13 +298,22 @@ async def send_max_power_change_event(entity: VersatileThermostat, new_power_max
|
||||
)
|
||||
},
|
||||
)
|
||||
return await entity._async_max_power_changed(power_event)
|
||||
await entity._async_max_power_changed(power_event)
|
||||
if sleep:
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
async def send_window_change_event(
|
||||
entity: VersatileThermostat, new_state: bool, old_state: bool, date
|
||||
entity: VersatileThermostat, new_state: bool, old_state: bool, date, sleep=True
|
||||
):
|
||||
"""Sending a new window event simulating a change on the window state"""
|
||||
_LOGGER.info(
|
||||
"------- Testu: sending send_temperature_change_event, new_state=%s old_state=%s date=%s on %s",
|
||||
new_state,
|
||||
old_state,
|
||||
date,
|
||||
entity,
|
||||
)
|
||||
window_event = Event(
|
||||
EVENT_STATE_CHANGED,
|
||||
{
|
||||
@@ -283,13 +332,22 @@ async def send_window_change_event(
|
||||
},
|
||||
)
|
||||
ret = await entity._async_windows_changed(window_event)
|
||||
if sleep:
|
||||
await asyncio.sleep(0.1)
|
||||
return ret
|
||||
|
||||
|
||||
async def send_motion_change_event(
|
||||
entity: VersatileThermostat, new_state: bool, old_state: bool, date
|
||||
entity: VersatileThermostat, new_state: bool, old_state: bool, date, sleep=True
|
||||
):
|
||||
"""Sending a new motion event simulating a change on the window state"""
|
||||
_LOGGER.info(
|
||||
"------- Testu: sending send_temperature_change_event, new_state=%s old_state=%s date=%s on %s",
|
||||
new_state,
|
||||
old_state,
|
||||
date,
|
||||
entity,
|
||||
)
|
||||
motion_event = Event(
|
||||
EVENT_STATE_CHANGED,
|
||||
{
|
||||
@@ -308,13 +366,22 @@ async def send_motion_change_event(
|
||||
},
|
||||
)
|
||||
ret = await entity._async_motion_changed(motion_event)
|
||||
if sleep:
|
||||
await asyncio.sleep(0.1)
|
||||
return ret
|
||||
|
||||
|
||||
async def send_presence_change_event(
|
||||
entity: VersatileThermostat, new_state: bool, old_state: bool, date
|
||||
entity: VersatileThermostat, new_state: bool, old_state: bool, date, sleep=True
|
||||
):
|
||||
"""Sending a new presence event simulating a change on the window state"""
|
||||
_LOGGER.info(
|
||||
"------- Testu: sending send_temperature_change_event, new_state=%s old_state=%s date=%s on %s",
|
||||
new_state,
|
||||
old_state,
|
||||
date,
|
||||
entity,
|
||||
)
|
||||
presence_event = Event(
|
||||
EVENT_STATE_CHANGED,
|
||||
{
|
||||
@@ -333,6 +400,8 @@ async def send_presence_change_event(
|
||||
},
|
||||
)
|
||||
ret = await entity._async_presence_changed(presence_event)
|
||||
if sleep:
|
||||
await asyncio.sleep(0.1)
|
||||
return ret
|
||||
|
||||
|
||||
@@ -349,8 +418,18 @@ async def send_climate_change_event(
|
||||
new_hvac_action: HVACAction,
|
||||
old_hvac_action: HVACAction,
|
||||
date,
|
||||
sleep=True,
|
||||
):
|
||||
"""Sending a new climate event simulating a change on the underlying climate state"""
|
||||
_LOGGER.info(
|
||||
"------- Testu: sending send_temperature_change_event, new_hvac_mode=%s old_hvac_mode=%s new_hvac_action=%s old_hvac_action=%s date=%s on %s",
|
||||
new_hvac_mode,
|
||||
old_hvac_mode,
|
||||
new_hvac_action,
|
||||
old_hvac_action,
|
||||
date,
|
||||
entity,
|
||||
)
|
||||
climate_event = Event(
|
||||
EVENT_STATE_CHANGED,
|
||||
{
|
||||
@@ -371,4 +450,14 @@ async def send_climate_change_event(
|
||||
},
|
||||
)
|
||||
ret = await entity._async_climate_changed(climate_event)
|
||||
if sleep:
|
||||
await asyncio.sleep(0.1)
|
||||
return ret
|
||||
|
||||
|
||||
def cancel_switchs_cycles(entity: VersatileThermostat):
|
||||
"""This method will cancel all running cycle on all underlying switch entity"""
|
||||
if entity._is_over_climate:
|
||||
return
|
||||
for under in entity._underlyings:
|
||||
under._cancel_cycle()
|
||||
|
||||
@@ -241,17 +241,19 @@ async def test_bug_66(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"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.climate.VersatileThermostat._is_device_active",
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=True,
|
||||
):
|
||||
await send_temperature_change_event(entity, 15, now)
|
||||
try_window_condition = await send_window_change_event(entity, True, False, now)
|
||||
try_window_condition = await send_window_change_event(
|
||||
entity, True, False, now, False
|
||||
)
|
||||
|
||||
# simulate the call to try_window_condition
|
||||
await try_window_condition(None)
|
||||
@@ -267,13 +269,13 @@ async def test_bug_66(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=False
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = now + timedelta(minutes=1)
|
||||
@@ -290,13 +292,13 @@ async def test_bug_66(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"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.climate.VersatileThermostat._is_device_active",
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=False,
|
||||
):
|
||||
try_window_condition = await send_window_change_event(
|
||||
@@ -313,13 +315,13 @@ async def test_bug_66(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"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.climate.VersatileThermostat._is_device_active",
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = now + timedelta(minutes=2)
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
""" Test the Multiple switch management """
|
||||
import asyncio
|
||||
from unittest.mock import patch, call, ANY
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import logging
|
||||
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
async def test_one_switch_cycle(
|
||||
hass: HomeAssistant,
|
||||
skip_hass_states_is_state,
|
||||
skip_send_event,
|
||||
):
|
||||
"""Test that when multiple switch are configured the activation is distributed"""
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOver4SwitchMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOver4SwitchMockName",
|
||||
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: 8,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
"comfort_temp": 18,
|
||||
"boost_temp": 19,
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_HEATER: "switch.mock_switch1",
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SECURITY_DELAY_MIN: 5,
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.01,
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theover4switchmockname"
|
||||
)
|
||||
assert entity
|
||||
assert entity.is_over_climate is False
|
||||
|
||||
# start heating, in boost mode. We block the control_heating to avoid running a cycle
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_control_heating"
|
||||
):
|
||||
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 == 19
|
||||
assert entity.window_state is None
|
||||
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||
|
||||
# Checks that all heaters are off
|
||||
with patch(
|
||||
"homeassistant.core.StateMachine.is_state", return_value=False
|
||||
) as mock_is_state:
|
||||
assert entity._is_device_active is False
|
||||
|
||||
# Should be call for the Switch
|
||||
assert mock_is_state.call_count == 1
|
||||
|
||||
# Set temperature to a low level
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.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,
|
||||
) as mock_device_active, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.call_later",
|
||||
return_value=None,
|
||||
) as mock_call_later:
|
||||
await send_ext_temperature_change_event(entity, 5, event_timestamp)
|
||||
|
||||
# No special event
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
|
||||
# The first heater should be on but because call_later is mocked heater_on is not called
|
||||
# assert mock_heater_on.call_count == 1
|
||||
assert mock_heater_on.call_count == 0
|
||||
# There is no check if active
|
||||
assert mock_device_active.call_count == 0
|
||||
|
||||
# 4 calls dispatched along the cycle
|
||||
assert mock_call_later.call_count == 1
|
||||
mock_call_later.assert_has_calls(
|
||||
[
|
||||
call.call_later(hass, 0.0, ANY),
|
||||
]
|
||||
)
|
||||
|
||||
# Set a temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.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,
|
||||
) as mock_device_active:
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
|
||||
# No special event
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
|
||||
# The first heater should be turned on but is already on but because above we mock call_later the heater is not on. But this time it will be really on
|
||||
assert mock_heater_on.call_count == 1
|
||||
|
||||
# Set another temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
||||
await send_temperature_change_event(entity, 18.1, event_timestamp)
|
||||
|
||||
# No special event
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
|
||||
# The heater is already on cycle. So we wait that the cycle ends and no heater action is done
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert entity.underlying_entity(0)._should_relaunch_control_heating is True
|
||||
|
||||
# Simulate the relaunch
|
||||
await entity.underlying_entity(0)._turn_on_later(None)
|
||||
# wait restart
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
assert mock_heater_on.call_count == 1
|
||||
assert entity.underlying_entity(0)._should_relaunch_control_heating is False
|
||||
|
||||
# Simulate the end of heater on cycle
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
||||
await entity.underlying_entity(0)._turn_off_later(None)
|
||||
|
||||
# No special event
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_on.call_count == 0
|
||||
# The heater should be turned off this time
|
||||
assert mock_heater_off.call_count == 1
|
||||
assert entity.underlying_entity(0)._should_relaunch_control_heating is False
|
||||
|
||||
# Simulate the start of heater on cycle
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
||||
await entity.underlying_entity(0)._turn_on_later(None)
|
||||
|
||||
# No special event
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_on.call_count == 1
|
||||
# The heater should be turned off this time
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert entity.underlying_entity(0)._should_relaunch_control_heating is False
|
||||
|
||||
|
||||
async def test_multiple_switchs(
|
||||
hass: HomeAssistant,
|
||||
skip_hass_states_is_state,
|
||||
skip_send_event,
|
||||
):
|
||||
"""Test that when multiple switch are configured the activation is distributed"""
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOver4SwitchMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOver4SwitchMockName",
|
||||
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: 8,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
"comfort_temp": 18,
|
||||
"boost_temp": 19,
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_HEATER: "switch.mock_switch1",
|
||||
CONF_HEATER_2: "switch.mock_switch2",
|
||||
CONF_HEATER_3: "switch.mock_switch3",
|
||||
CONF_HEATER_4: "switch.mock_switch4",
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SECURITY_DELAY_MIN: 5,
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.01,
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theover4switchmockname"
|
||||
)
|
||||
assert entity
|
||||
assert entity.is_over_climate is False
|
||||
|
||||
# start heating, in boost mode. We block the control_heating to avoid running a cycle
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_control_heating"
|
||||
):
|
||||
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 == 19
|
||||
assert entity.window_state is None
|
||||
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||
|
||||
# Checks that all heaters are off
|
||||
with patch(
|
||||
"homeassistant.core.StateMachine.is_state", return_value=False
|
||||
) as mock_is_state:
|
||||
assert entity._is_device_active is False
|
||||
|
||||
# Should be call for all Switch
|
||||
assert mock_is_state.call_count == 4
|
||||
|
||||
# Set temperature to a low level
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.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,
|
||||
) as mock_device_active, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.call_later",
|
||||
return_value=None,
|
||||
) as mock_call_later:
|
||||
await send_ext_temperature_change_event(entity, 5, event_timestamp)
|
||||
|
||||
# No special event
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
|
||||
# The first heater should be on but because call_later is mocked heater_on is not called
|
||||
# assert mock_heater_on.call_count == 1
|
||||
assert mock_heater_on.call_count == 0
|
||||
# There is no check if active
|
||||
assert mock_device_active.call_count == 0
|
||||
|
||||
# 4 calls dispatched along the cycle
|
||||
assert mock_call_later.call_count == 4
|
||||
mock_call_later.assert_has_calls(
|
||||
[
|
||||
call.call_later(hass, 0.0, ANY),
|
||||
call.call_later(hass, 120.0, ANY),
|
||||
call.call_later(hass, 240.0, ANY),
|
||||
call.call_later(hass, 360.0, ANY),
|
||||
]
|
||||
)
|
||||
|
||||
# Set a temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.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,
|
||||
) as mock_device_active:
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
|
||||
# No special event
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
|
||||
# The first heater should be turned on but is already on but because call_later is mocked, it is only turned on here
|
||||
assert mock_heater_on.call_count == 1
|
||||
@@ -81,9 +81,9 @@ async def test_power_management_hvac_off(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off:
|
||||
await send_max_power_change_event(entity, 149, datetime.now())
|
||||
assert await entity.check_overpowering() is True
|
||||
@@ -160,9 +160,9 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off:
|
||||
await send_max_power_change_event(entity, 149, datetime.now())
|
||||
assert await entity.check_overpowering() is True
|
||||
@@ -194,9 +194,9 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off:
|
||||
await send_power_change_event(entity, 48, datetime.now())
|
||||
assert await entity.check_overpowering() is False
|
||||
@@ -278,13 +278,13 @@ async def test_power_management_energy_over_switch(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off:
|
||||
await send_temperature_change_event(entity, 15, datetime.now())
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
await send_temperature_change_event(entity, 15, datetime.now())
|
||||
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
@@ -307,9 +307,9 @@ async def test_power_management_energy_over_switch(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off:
|
||||
await send_temperature_change_event(entity, 18, datetime.now())
|
||||
assert tpi_algo.on_percent == 0.3
|
||||
@@ -329,9 +329,9 @@ async def test_power_management_energy_over_switch(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off:
|
||||
await send_temperature_change_event(entity, 20, datetime.now())
|
||||
assert tpi_algo.on_percent == 0.0
|
||||
|
||||
@@ -87,7 +87,7 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on:
|
||||
event_timestamp = now - timedelta(minutes=6)
|
||||
|
||||
@@ -134,7 +134,7 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on:
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
|
||||
@@ -149,7 +149,7 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on:
|
||||
event_timestamp = datetime.now()
|
||||
|
||||
@@ -185,4 +185,5 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
any_order=True,
|
||||
)
|
||||
|
||||
assert mock_heater_on.call_count == 0
|
||||
# Heater is now on
|
||||
assert mock_heater_on.call_count == 1
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
""" Test the normal start of a Thermostat """
|
||||
import asyncio
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -179,6 +180,8 @@ async def test_sensors_over_switch(
|
||||
)
|
||||
assert last_ext_temperature_sensor.device_class == SensorDeviceClass.TIMESTAMP
|
||||
|
||||
cancel_switchs_cycles(entity)
|
||||
|
||||
|
||||
async def test_sensors_over_climate(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -66,18 +66,16 @@ async def test_window_management_time_not_enough(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=False
|
||||
) as mock_condition:
|
||||
await send_temperature_change_event(entity, 15, datetime.now())
|
||||
try_window_condition = await send_window_change_event(
|
||||
entity, True, False, datetime.now()
|
||||
)
|
||||
# simulate the call to try_window_condition
|
||||
await try_window_condition(None)
|
||||
await send_window_change_event(entity, True, False, datetime.now())
|
||||
# simulate the call to try_window_condition. No need due to 0 WINDOW_DELAY and sleep after event is sent
|
||||
# await try_window_condition(None)
|
||||
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_on.call_count == 1
|
||||
@@ -152,9 +150,9 @@ async def test_window_management_time_enough(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
@@ -162,11 +160,7 @@ async def test_window_management_time_enough(
|
||||
return_value=True,
|
||||
):
|
||||
await send_temperature_change_event(entity, 15, datetime.now())
|
||||
try_window_condition = await send_window_change_event(
|
||||
entity, True, False, datetime.now()
|
||||
)
|
||||
# simulate the call to try_window_condition
|
||||
await try_window_condition(None)
|
||||
await send_window_change_event(entity, True, False, datetime.now())
|
||||
|
||||
assert mock_send_event.call_count == 1
|
||||
mock_send_event.assert_has_calls(
|
||||
@@ -259,9 +253,9 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
return_value=True,
|
||||
@@ -281,9 +275,9 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
return_value=True,
|
||||
@@ -316,9 +310,9 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
@@ -341,9 +335,9 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
@@ -438,9 +432,9 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
return_value=True,
|
||||
@@ -459,18 +453,32 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
return_value=True,
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
await send_temperature_change_event(entity, 18, event_timestamp, sleep=False)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 2
|
||||
# The heater turns off
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF}),
|
||||
call.send_event(
|
||||
EventType.WINDOW_AUTO_EVENT,
|
||||
{
|
||||
"type": "start",
|
||||
"cause": "slope alert",
|
||||
"curve_slope": -1.0,
|
||||
},
|
||||
),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_heater_off.call_count >= 1
|
||||
assert entity.last_temperature_slope == -1
|
||||
@@ -483,9 +491,9 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
return_value=False,
|
||||
@@ -564,9 +572,9 @@ async def test_window_auto_no_on_percent(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
return_value=True,
|
||||
@@ -586,9 +594,9 @@ async def test_window_auto_no_on_percent(
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat._is_device_active",
|
||||
return_value=True,
|
||||
|
||||
@@ -3,8 +3,10 @@ import logging
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON
|
||||
|
||||
from homeassistant.exceptions import ServiceNotFound
|
||||
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
from homeassistant.core import HomeAssistant, DOMAIN as HA_DOMAIN
|
||||
from homeassistant.core import HomeAssistant, DOMAIN as HA_DOMAIN, CALLBACK_TYPE
|
||||
from homeassistant.components.climate import (
|
||||
ClimateEntity,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
@@ -15,14 +17,19 @@ from homeassistant.components.climate import (
|
||||
SERVICE_SET_HUMIDITY,
|
||||
SERVICE_SET_SWING_MODE,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from .const import UnknownEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# TODO remove this
|
||||
_LOGGER.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
class UnderlyingEntityType(StrEnum):
|
||||
"""All underlying device type"""
|
||||
@@ -88,23 +95,54 @@ class UnderlyingEntity:
|
||||
|
||||
async def turn_off(self):
|
||||
"""Turn heater toggleable device off."""
|
||||
_LOGGER.debug("%s - Stopping underlying switch %s", self, self._entity_id)
|
||||
data = {ATTR_ENTITY_ID: self._entity_id}
|
||||
await self._hass.services.async_call(
|
||||
HA_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
data, # TODO needed ? context=self._context
|
||||
)
|
||||
_LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
|
||||
# This may fails if called after shutdown
|
||||
try:
|
||||
data = {ATTR_ENTITY_ID: self._entity_id}
|
||||
await self._hass.services.async_call(
|
||||
HA_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
data,
|
||||
)
|
||||
except ServiceNotFound as err:
|
||||
_LOGGER.error(err)
|
||||
|
||||
async def turn_on(self):
|
||||
"""Turn heater toggleable device on."""
|
||||
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
|
||||
try:
|
||||
data = {ATTR_ENTITY_ID: self._entity_id}
|
||||
await self._hass.services.async_call(
|
||||
HA_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
data,
|
||||
)
|
||||
except ServiceNotFound as err:
|
||||
_LOGGER.error(err)
|
||||
|
||||
async def set_temperature(self, temperature, max_temp, min_temp):
|
||||
"""Set the target temperature"""
|
||||
return
|
||||
|
||||
async def remove_entity(self):
|
||||
"""Remove the underlying entity"""
|
||||
return
|
||||
|
||||
# override to be able to mock the call
|
||||
def call_later(
|
||||
self, hass: HomeAssistant, delay_sec: int, called_method
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Call the method after a delay"""
|
||||
return async_call_later(hass, delay_sec, called_method)
|
||||
|
||||
|
||||
class UnderlyingSwitch(UnderlyingEntity):
|
||||
"""Represent a underlying switch"""
|
||||
|
||||
_initialDelaySec: int
|
||||
_on_time_sec: int
|
||||
_off_time_sec: int
|
||||
_hvac_mode: HVACMode
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -122,6 +160,10 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
entity_id=switch_entity_id,
|
||||
)
|
||||
self._initial_delay_sec = initial_delay_sec
|
||||
self._async_cancel_cycle = None
|
||||
self._should_relaunch_control_heating = False
|
||||
self._on_time_sec = 0
|
||||
self._off_time_sec = 0
|
||||
|
||||
@property
|
||||
def initial_delay_sec(self):
|
||||
@@ -140,6 +182,185 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
"""If the toggleable device is currently active."""
|
||||
return self._hass.states.is_state(self._entity_id, STATE_ON)
|
||||
|
||||
async def check_initial_state(self, hvac_mode: HVACMode):
|
||||
"""Prevent the heater to be on but thermostat is off"""
|
||||
if hvac_mode == HVACMode.OFF and self.is_device_active:
|
||||
_LOGGER.warning(
|
||||
"%s - The hvac mode is OFF, but the switch device is ON. Turning off device %s",
|
||||
self,
|
||||
self._entity_id,
|
||||
)
|
||||
await self.turn_off()
|
||||
|
||||
async def start_cycle(
|
||||
self,
|
||||
hvac_mode: HVACMode,
|
||||
on_time_sec: int,
|
||||
off_time_sec: int,
|
||||
force=False,
|
||||
):
|
||||
"""Starting cycle for switch"""
|
||||
_LOGGER.debug(
|
||||
"%s - Starting new cycle hvac_mode=%s on_time_sec=%d off_time_sec=%d force=%s",
|
||||
self,
|
||||
hvac_mode,
|
||||
on_time_sec,
|
||||
off_time_sec,
|
||||
force,
|
||||
)
|
||||
|
||||
self._on_time_sec = on_time_sec
|
||||
self._off_time_sec = off_time_sec
|
||||
self._hvac_mode = hvac_mode
|
||||
|
||||
# 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)
|
||||
await 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
|
||||
|
||||
# If we should heat
|
||||
if self._hvac_mode == HVACMode.HEAT and on_time_sec > 0:
|
||||
# Starts the cycle after the initial delay
|
||||
self._async_cancel_cycle = self.call_later(
|
||||
self._hass, self._initial_delay_sec, self._turn_on_later
|
||||
)
|
||||
_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) for %d min %d sec",
|
||||
self,
|
||||
off_time_sec // 60,
|
||||
off_time_sec % 60,
|
||||
)
|
||||
await self.turn_off()
|
||||
else:
|
||||
_LOGGER.debug("%s - nothing to do", self)
|
||||
|
||||
async 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 off_time_sec=%d",
|
||||
self,
|
||||
self._hvac_mode,
|
||||
self._should_relaunch_control_heating,
|
||||
self._on_time_sec,
|
||||
)
|
||||
|
||||
await 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
|
||||
|
||||
# TODO if await self.check_overpowering():
|
||||
# _LOGGER.debug("%s - End of cycle (3)", self)
|
||||
# return
|
||||
# Security mode could have change the on_time percent
|
||||
# TODO await self.check_security()
|
||||
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(
|
||||
"%s - %s heating for %d min %d sec",
|
||||
self,
|
||||
action_label,
|
||||
time // 60,
|
||||
time % 60,
|
||||
)
|
||||
await self.turn_on()
|
||||
else:
|
||||
_LOGGER.debug("%s - No action on heater cause duration is 0", self)
|
||||
self._async_cancel_cycle = self.call_later(
|
||||
self._hass,
|
||||
time,
|
||||
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 off_time_sec=%d",
|
||||
self,
|
||||
self._hvac_mode,
|
||||
self._should_relaunch_control_heating,
|
||||
self._off_time_sec,
|
||||
)
|
||||
await 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"
|
||||
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:
|
||||
_LOGGER.info(
|
||||
"%s - %s heating for %d min %d sec",
|
||||
self,
|
||||
action_label,
|
||||
time // 60,
|
||||
time % 60,
|
||||
)
|
||||
await self.turn_off()
|
||||
else:
|
||||
_LOGGER.debug("%s - No action on heater cause duration is 0", self)
|
||||
self._async_cancel_cycle = self.call_later(
|
||||
self._hass,
|
||||
time,
|
||||
self._turn_on_later,
|
||||
)
|
||||
|
||||
# increment energy at the end of the cycle
|
||||
# TODO self.incremente_energy()
|
||||
|
||||
async def remove_entity(self):
|
||||
"""Remove the entity"""
|
||||
await self._cancel_cycle()
|
||||
|
||||
|
||||
class UnderlyingClimate(UnderlyingEntity):
|
||||
"""Represent a underlying climate"""
|
||||
@@ -286,3 +507,10 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
if not self.is_initialized:
|
||||
return None
|
||||
return self._underlying_climate.hvac_action
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode:
|
||||
"""Get the hvac mode of the underlying"""
|
||||
if not self.is_initialized:
|
||||
return None
|
||||
return self._underlying_climate.hvac_mode
|
||||
|
||||
Reference in New Issue
Block a user