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"""