Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9269240fe3 | ||
|
|
91ba2387b2 | ||
|
|
162efb4709 | ||
|
|
6a97622226 |
@@ -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
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ from .const import (
|
|||||||
CONF_USE_WINDOW_FEATURE,
|
CONF_USE_WINDOW_FEATURE,
|
||||||
CONF_THERMOSTAT_TYPE,
|
CONF_THERMOSTAT_TYPE,
|
||||||
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
||||||
|
CONF_USE_CENTRAL_BOILER_FEATURE,
|
||||||
CONF_CENTRAL_BOILER_ACTIVATION_SRV,
|
CONF_CENTRAL_BOILER_ACTIVATION_SRV,
|
||||||
CONF_CENTRAL_BOILER_DEACTIVATION_SRV,
|
CONF_CENTRAL_BOILER_DEACTIVATION_SRV,
|
||||||
overrides,
|
overrides,
|
||||||
@@ -63,10 +64,13 @@ async def async_setup_entry(
|
|||||||
name = entry.data.get(CONF_NAME)
|
name = entry.data.get(CONF_NAME)
|
||||||
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
||||||
|
|
||||||
|
entities = None
|
||||||
|
|
||||||
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||||
entities = [
|
if entry.data.get(CONF_USE_CENTRAL_BOILER_FEATURE):
|
||||||
CentralBoilerBinarySensor(hass, unique_id, name, entry.data),
|
entities = [
|
||||||
]
|
CentralBoilerBinarySensor(hass, unique_id, name, entry.data),
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
entities = [
|
entities = [
|
||||||
SecurityBinarySensor(hass, unique_id, name, entry.data),
|
SecurityBinarySensor(hass, unique_id, name, entry.data),
|
||||||
@@ -81,7 +85,8 @@ async def async_setup_entry(
|
|||||||
if entry.data.get(CONF_USE_POWER_FEATURE):
|
if entry.data.get(CONF_USE_POWER_FEATURE):
|
||||||
entities.append(OverpoweringBinarySensor(hass, unique_id, name, entry.data))
|
entities.append(OverpoweringBinarySensor(hass, unique_id, name, entry.data))
|
||||||
|
|
||||||
async_add_entities(entities, True)
|
if entities:
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
||||||
|
|||||||
@@ -242,6 +242,14 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
and infos.get(CONF_PRESENCE_SENSOR, None) is None
|
and infos.get(CONF_PRESENCE_SENSOR, None) is None
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if self._infos[CONF_USE_CENTRAL_BOILER_FEATURE] and (
|
||||||
|
not self._infos.get(CONF_CENTRAL_BOILER_ACTIVATION_SRV, False)
|
||||||
|
or len(self._infos.get(CONF_CENTRAL_BOILER_ACTIVATION_SRV)) <= 0
|
||||||
|
or not self._infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV, False)
|
||||||
|
or len(self._infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV)) <= 0
|
||||||
|
):
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
infos.get(CONF_NAME) is None
|
infos.get(CONF_NAME) is None
|
||||||
@@ -857,6 +865,9 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
if not self._infos[CONF_USE_PRESENCE_FEATURE]:
|
if not self._infos[CONF_USE_PRESENCE_FEATURE]:
|
||||||
self._infos[CONF_USE_PRESENCE_CENTRAL_CONFIG] = False
|
self._infos[CONF_USE_PRESENCE_CENTRAL_CONFIG] = False
|
||||||
self._infos[CONF_PRESENCE_SENSOR] = None
|
self._infos[CONF_PRESENCE_SENSOR] = None
|
||||||
|
if not self._infos[CONF_USE_CENTRAL_BOILER_FEATURE]:
|
||||||
|
self._infos[CONF_CENTRAL_BOILER_ACTIVATION_SRV] = None
|
||||||
|
self._infos[CONF_CENTRAL_BOILER_DEACTIVATION_SRV] = None
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Recreating entry %s due to configuration change. New config is now: %s",
|
"Recreating entry %s due to configuration change. New config is now: %s",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"ssdp": [],
|
"ssdp": [],
|
||||||
"version": "6.0.2",
|
"version": "6.0.4",
|
||||||
"zeroconf": []
|
"zeroconf": []
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,7 @@ from .const import (
|
|||||||
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
||||||
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
||||||
CONF_USE_PRESENCE_FEATURE,
|
CONF_USE_PRESENCE_FEATURE,
|
||||||
|
CONF_USE_CENTRAL_BOILER_FEATURE,
|
||||||
overrides,
|
overrides,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -153,9 +154,10 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
# For central config only
|
# For central config only
|
||||||
else:
|
else:
|
||||||
entities.append(
|
if entry.data.get(CONF_USE_CENTRAL_BOILER_FEATURE):
|
||||||
ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data)
|
entities.append(
|
||||||
)
|
ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data)
|
||||||
|
)
|
||||||
for preset in CONF_PRESETS_WITH_AC_VALUES:
|
for preset in CONF_PRESETS_WITH_AC_VALUES:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - configuring Number central, AC, non AWAY for preset %s",
|
"%s - configuring Number central, AC, non AWAY for preset %s",
|
||||||
@@ -178,7 +180,8 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities, True)
|
if len(entities) > 0:
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class ActivateBoilerThresholdNumber(
|
class ActivateBoilerThresholdNumber(
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ async def async_setup_entry(
|
|||||||
entities = [
|
entities = [
|
||||||
NbActiveDeviceForBoilerSensor(hass, unique_id, name, entry.data)
|
NbActiveDeviceForBoilerSensor(hass, unique_id, name, entry.data)
|
||||||
]
|
]
|
||||||
async_add_entities(entities, True)
|
|
||||||
else:
|
else:
|
||||||
entities = [
|
entities = [
|
||||||
LastTemperatureSensor(hass, unique_id, name, entry.data),
|
LastTemperatureSensor(hass, unique_id, name, entry.data),
|
||||||
@@ -108,6 +107,7 @@ async def async_setup_entry(
|
|||||||
RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
|
RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if entities:
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ async def test_add_a_central_config(hass: HomeAssistant, skip_hass_states_is_sta
|
|||||||
assert api.nb_active_device_for_boiler_entity is None
|
assert api.nb_active_device_for_boiler_entity is None
|
||||||
assert api.nb_active_device_for_boiler is None
|
assert api.nb_active_device_for_boiler is None
|
||||||
|
|
||||||
assert api.nb_active_device_for_boiler_threshold_entity is not None
|
assert api.nb_active_device_for_boiler_threshold_entity is None
|
||||||
assert api.nb_active_device_for_boiler_threshold == 1 # the default value
|
assert api.nb_active_device_for_boiler_threshold is None
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
|
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
|
|||||||
Reference in New Issue
Block a user