Add valve change tests (ko)
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"""
|
||||
|
||||
Reference in New Issue
Block a user