Expose the keep_alive_sec attribute in HASS Developer Tools - States (#381)

* Typing: Make BaseThermostat generic on the UnderlyingEntity type

* Typing: Change the type of IntervalCaller._interval_sec from int to float

This makes the IntervalCaller class more reusable.

* Keep-alive: Expose UnderlyingSwitch.keep_alive_sec as a HASS Dev Tools attribute

Also improve a keep-alive log message.
This commit is contained in:
Paulo Ferreira de Castro
2024-03-23 10:49:09 +00:00
committed by GitHub
parent 5db7a49e75
commit 6a97622226
6 changed files with 27 additions and 12 deletions

View File

@@ -7,7 +7,7 @@ import logging
from datetime import timedelta, datetime from datetime import timedelta, datetime
from types import MappingProxyType from types import MappingProxyType
from typing import Any from typing import Any, TypeVar, Generic
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.core import ( from homeassistant.core import (
@@ -140,6 +140,7 @@ from .ema import ExponentialMovingAverage
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ConfigData = MappingProxyType[str, Any] ConfigData = MappingProxyType[str, Any]
T = TypeVar("T", bound=UnderlyingEntity)
def get_tz(hass: HomeAssistant): def get_tz(hass: HomeAssistant):
@@ -148,7 +149,7 @@ def get_tz(hass: HomeAssistant):
return dt_util.get_time_zone(hass.config.time_zone) 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.""" """Representation of a base class for all Versatile Thermostat device."""
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = (
@@ -278,7 +279,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._last_change_time = None self._last_change_time = None
self._underlyings: list[UnderlyingEntity] = [] self._underlyings: list[T] = []
self._ema_temp = None self._ema_temp = None
self._ema_algo = None self._ema_algo = None

View File

@@ -24,11 +24,16 @@ class IntervalCaller:
Convenience wrapper around Home Assistant's `async_track_time_interval` function. 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._hass = hass
self._interval_sec = interval_sec self._interval_sec = interval_sec
self._remove_handle: CALLBACK_TYPE | None = None 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): def cancel(self):
"""Cancel the regular calls to the action function.""" """Cancel the regular calls to the action function."""
if self._remove_handle: if self._remove_handle:
@@ -43,7 +48,11 @@ class IntervalCaller:
async def callback(_time: datetime): async def callback(_time: datetime):
try: try:
_LOGGER.debug("Calling keep-alive action") _LOGGER.debug(
"Calling keep-alive action '%s' (%ss interval)",
action.__name__,
self._interval_sec,
)
await action() await action()
except Exception as e: # pylint: disable=broad-exception-caught except Exception as e: # pylint: disable=broad-exception-caught
_LOGGER.error(e) _LOGGER.error(e)

View File

@@ -58,7 +58,7 @@ from .underlyings import UnderlyingClimate
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class ThermostatOverClimate(BaseThermostat): class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"""Representation of a base class for a Versatile Thermostat over a climate""" """Representation of a base class for a Versatile Thermostat over a climate"""
_auto_regulation_mode: str | None = None _auto_regulation_mode: str | None = None

View File

@@ -27,7 +27,7 @@ from .prop_algorithm import PropAlgorithm
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class ThermostatOverSwitch(BaseThermostat): class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
"""Representation of a base class for a Versatile Thermostat over a switch.""" """Representation of a base class for a Versatile Thermostat over a switch."""
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = (
@@ -136,11 +136,11 @@ class ThermostatOverSwitch(BaseThermostat):
"""Custom attributes""" """Custom attributes"""
super().update_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_over_switch"] = self.is_over_switch
self._attr_extra_state_attributes["is_inversed"] = self.is_inversed self._attr_extra_state_attributes["is_inversed"] = self.is_inversed
self._attr_extra_state_attributes["underlying_switch_0"] = self._underlyings[ self._attr_extra_state_attributes["keep_alive_sec"] = under0.keep_alive_sec
0 self._attr_extra_state_attributes["underlying_switch_0"] = under0.entity_id
].entity_id
self._attr_extra_state_attributes["underlying_switch_1"] = ( self._attr_extra_state_attributes["underlying_switch_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
) )

View File

@@ -31,7 +31,7 @@ from .underlyings import UnderlyingValve
_LOGGER = logging.getLogger(__name__) _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""" """Representation of a class for a Versatile Thermostat over a Valve"""
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = (

View File

@@ -188,7 +188,7 @@ class UnderlyingSwitch(UnderlyingEntity):
thermostat: Any, thermostat: Any,
switch_entity_id: str, switch_entity_id: str,
initial_delay_sec: int, initial_delay_sec: int,
keep_alive_sec: int, keep_alive_sec: float,
) -> None: ) -> None:
"""Initialize the underlying switch""" """Initialize the underlying switch"""
@@ -217,6 +217,11 @@ class UnderlyingSwitch(UnderlyingEntity):
"""Tells if the switch command should be inversed""" """Tells if the switch command should be inversed"""
return self._thermostat.is_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 @overrides
def startup(self): def startup(self):
super().startup() super().startup()