diff --git a/custom_components/versatile_thermostat/base_thermostat.py b/custom_components/versatile_thermostat/base_thermostat.py index c9619ec..43b1fcd 100644 --- a/custom_components/versatile_thermostat/base_thermostat.py +++ b/custom_components/versatile_thermostat/base_thermostat.py @@ -7,7 +7,7 @@ import logging from datetime import timedelta, datetime from types import MappingProxyType -from typing import Any +from typing import Any, TypeVar, Generic from homeassistant.util import dt as dt_util from homeassistant.core import ( @@ -140,6 +140,7 @@ from .ema import ExponentialMovingAverage _LOGGER = logging.getLogger(__name__) ConfigData = MappingProxyType[str, Any] +T = TypeVar("T", bound=UnderlyingEntity) def get_tz(hass: HomeAssistant): @@ -148,7 +149,7 @@ def get_tz(hass: HomeAssistant): return dt_util.get_time_zone(hass.config.time_zone) -class BaseThermostat(ClimateEntity, RestoreEntity): +class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): """Representation of a base class for all Versatile Thermostat device.""" _entity_component_unrecorded_attributes = ( @@ -278,7 +279,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity): self._last_change_time = None - self._underlyings: list[UnderlyingEntity] = [] + self._underlyings: list[T] = [] self._ema_temp = None self._ema_algo = None diff --git a/custom_components/versatile_thermostat/keep_alive.py b/custom_components/versatile_thermostat/keep_alive.py index a4af483..1457cdf 100644 --- a/custom_components/versatile_thermostat/keep_alive.py +++ b/custom_components/versatile_thermostat/keep_alive.py @@ -24,11 +24,16 @@ class IntervalCaller: Convenience wrapper around Home Assistant's `async_track_time_interval` function. """ - def __init__(self, hass: HomeAssistant, interval_sec: int) -> None: + def __init__(self, hass: HomeAssistant, interval_sec: float) -> None: self._hass = hass self._interval_sec = interval_sec self._remove_handle: CALLBACK_TYPE | None = None + @property + def interval_sec(self) -> float: + """Return the calling interval in seconds.""" + return self._interval_sec + def cancel(self): """Cancel the regular calls to the action function.""" if self._remove_handle: @@ -43,7 +48,11 @@ class IntervalCaller: async def callback(_time: datetime): try: - _LOGGER.debug("Calling keep-alive action") + _LOGGER.debug( + "Calling keep-alive action '%s' (%ss interval)", + action.__name__, + self._interval_sec, + ) await action() except Exception as e: # pylint: disable=broad-exception-caught _LOGGER.error(e) diff --git a/custom_components/versatile_thermostat/thermostat_climate.py b/custom_components/versatile_thermostat/thermostat_climate.py index 1b2b36f..47ed4e7 100644 --- a/custom_components/versatile_thermostat/thermostat_climate.py +++ b/custom_components/versatile_thermostat/thermostat_climate.py @@ -58,7 +58,7 @@ from .underlyings import UnderlyingClimate _LOGGER = logging.getLogger(__name__) -class ThermostatOverClimate(BaseThermostat): +class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]): """Representation of a base class for a Versatile Thermostat over a climate""" _auto_regulation_mode: str | None = None diff --git a/custom_components/versatile_thermostat/thermostat_switch.py b/custom_components/versatile_thermostat/thermostat_switch.py index f069161..ae2a389 100644 --- a/custom_components/versatile_thermostat/thermostat_switch.py +++ b/custom_components/versatile_thermostat/thermostat_switch.py @@ -27,7 +27,7 @@ from .prop_algorithm import PropAlgorithm _LOGGER = logging.getLogger(__name__) -class ThermostatOverSwitch(BaseThermostat): +class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]): """Representation of a base class for a Versatile Thermostat over a switch.""" _entity_component_unrecorded_attributes = ( @@ -136,11 +136,11 @@ class ThermostatOverSwitch(BaseThermostat): """Custom attributes""" super().update_custom_attributes() + under0: UnderlyingSwitch = self._underlyings[0] self._attr_extra_state_attributes["is_over_switch"] = self.is_over_switch self._attr_extra_state_attributes["is_inversed"] = self.is_inversed - self._attr_extra_state_attributes["underlying_switch_0"] = self._underlyings[ - 0 - ].entity_id + self._attr_extra_state_attributes["keep_alive_sec"] = under0.keep_alive_sec + self._attr_extra_state_attributes["underlying_switch_0"] = under0.entity_id self._attr_extra_state_attributes["underlying_switch_1"] = ( self._underlyings[1].entity_id if len(self._underlyings) > 1 else None ) diff --git a/custom_components/versatile_thermostat/thermostat_valve.py b/custom_components/versatile_thermostat/thermostat_valve.py index f6e19b5..f08db4f 100644 --- a/custom_components/versatile_thermostat/thermostat_valve.py +++ b/custom_components/versatile_thermostat/thermostat_valve.py @@ -31,7 +31,7 @@ from .underlyings import UnderlyingValve _LOGGER = logging.getLogger(__name__) -class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method +class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=abstract-method """Representation of a class for a Versatile Thermostat over a Valve""" _entity_component_unrecorded_attributes = ( diff --git a/custom_components/versatile_thermostat/underlyings.py b/custom_components/versatile_thermostat/underlyings.py index 6a78512..860138e 100644 --- a/custom_components/versatile_thermostat/underlyings.py +++ b/custom_components/versatile_thermostat/underlyings.py @@ -188,7 +188,7 @@ class UnderlyingSwitch(UnderlyingEntity): thermostat: Any, switch_entity_id: str, initial_delay_sec: int, - keep_alive_sec: int, + keep_alive_sec: float, ) -> None: """Initialize the underlying switch""" @@ -217,6 +217,11 @@ class UnderlyingSwitch(UnderlyingEntity): """Tells if the switch command should be inversed""" return self._thermostat.is_inversed + @property + def keep_alive_sec(self) -> float: + """Return the switch keep-alive interval in seconds.""" + return self._keep_alive.interval_sec + @overrides def startup(self): super().startup()