Compare commits
7 Commits
5.4.0-beta
...
5.4.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76382ebb35 | ||
|
|
90f9a0e1e3 | ||
|
|
ed977b53cd | ||
|
|
5d453393f8 | ||
|
|
d2f2ab7804 | ||
|
|
b0b6d0478d | ||
|
|
f8a2c9baa9 |
11
.github/workflows/testus.yaml
vendored
11
.github/workflows/testus.yaml
vendored
@@ -42,9 +42,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate HTML Coverage Report
|
- name: Generate HTML Coverage Report
|
||||||
run: coverage html
|
run: coverage html
|
||||||
|
# - name: Deploy to GitHub Pages
|
||||||
- name: Deploy to GitHub Pages
|
# uses: peaceiris/actions-gh-pages@v3
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
# with:
|
||||||
with:
|
# github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
# publish_dir: ./htmlcov
|
||||||
publish_dir: ./htmlcov
|
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ Certains thermostat de type TRV sont réputés incompatibles avec le Versatile T
|
|||||||
2. Les thermostats « Homematic » (et éventuellement Homematic IP) sont connus pour rencontrer des problèmes avec le Versatile Thermostat en raison des limitations du protocole RF sous-jacent. Ce problème se produit particulièrement lorsque vous essayez de contrôler plusieurs thermostats Homematic à la fois dans une seule instance de VTherm. Afin de réduire la charge du cycle de service, vous pouvez par ex. regroupez les thermostats avec des procédures spécifiques à Homematic (par exemple en utilisant un thermostat mural) et laissez Versatile Thermostat contrôler uniquement le thermostat mural directement. Une autre option consiste à contrôler un seul thermostat et à propager les changements de mode CVC et de température par un automatisme,
|
2. Les thermostats « Homematic » (et éventuellement Homematic IP) sont connus pour rencontrer des problèmes avec le Versatile Thermostat en raison des limitations du protocole RF sous-jacent. Ce problème se produit particulièrement lorsque vous essayez de contrôler plusieurs thermostats Homematic à la fois dans une seule instance de VTherm. Afin de réduire la charge du cycle de service, vous pouvez par ex. regroupez les thermostats avec des procédures spécifiques à Homematic (par exemple en utilisant un thermostat mural) et laissez Versatile Thermostat contrôler uniquement le thermostat mural directement. Une autre option consiste à contrôler un seul thermostat et à propager les changements de mode CVC et de température par un automatisme,
|
||||||
3. les thermostats de type Heatzy qui ne supportent pas les commandes de type set_temperature
|
3. les thermostats de type Heatzy qui ne supportent pas les commandes de type set_temperature
|
||||||
4. les thermostats de type Rointe ont tendance a se réveiller tout seul. Le reste fonctionne normalement.
|
4. les thermostats de type Rointe ont tendance a se réveiller tout seul. Le reste fonctionne normalement.
|
||||||
|
5. les TRV de type Aqara SRTS-A01 qui n'ont pas le retour d'état `hvac_action` permettant de savoir si elle chauffe ou pas. Donc les retours d'état sont faussés, le reste à l'air fonctionnel.
|
||||||
|
|
||||||
# Pourquoi une nouvelle implémentation du thermostat ?
|
# Pourquoi une nouvelle implémentation du thermostat ?
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ Some TRV type thermostats are known to be incompatible with the Versatile Thermo
|
|||||||
2. "Homematic" (and possible Homematic IP) thermostats are known to have problems with Versatile Thermostats because of limitations of the underlying RF protocol. This problem especially occurs when trying to control several Homematic thermostats at once in one Versatile Thermostat instance. In order to reduce duty cycle load, you may e.g. group thermostats with Homematic-specific procedures (e.g. using a wall thermostat) and let Versatile Thermostat only control the wall thermostat directly. Another option is to control only one thermostat and propagate the changes in HVAC mode and temperature by an automation.
|
2. "Homematic" (and possible Homematic IP) thermostats are known to have problems with Versatile Thermostats because of limitations of the underlying RF protocol. This problem especially occurs when trying to control several Homematic thermostats at once in one Versatile Thermostat instance. In order to reduce duty cycle load, you may e.g. group thermostats with Homematic-specific procedures (e.g. using a wall thermostat) and let Versatile Thermostat only control the wall thermostat directly. Another option is to control only one thermostat and propagate the changes in HVAC mode and temperature by an automation.
|
||||||
3. Thermostat of type Heatzy which doesn't supports the set_temperature command.
|
3. Thermostat of type Heatzy which doesn't supports the set_temperature command.
|
||||||
4. Thermostats of type Rointe tends to awake alone even if VTherm turns it off. Others functions works fine.
|
4. Thermostats of type Rointe tends to awake alone even if VTherm turns it off. Others functions works fine.
|
||||||
|
5. TRV of type Aqara SRTS-A01 which doesn't have the return state `hvac_action` allowing to know if it is heating or not. So return states are not available. Others features, seems to work normally.
|
||||||
|
|
||||||
# Why another thermostat implementation ?
|
# Why another thermostat implementation ?
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import math
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
|
from types import MappingProxyType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
@@ -20,10 +22,12 @@ from homeassistant.components.climate import ClimateEntity
|
|||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
||||||
|
from homeassistant.helpers.typing import EventType as HASSEventType
|
||||||
|
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
async_call_later,
|
async_call_later,
|
||||||
|
EventStateChangedData,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.exceptions import ConditionError
|
from homeassistant.exceptions import ConditionError
|
||||||
@@ -134,6 +138,7 @@ from .open_window_algorithm import WindowOpenDetectionAlgorithm
|
|||||||
from .ema import ExponentialMovingAverage
|
from .ema import ExponentialMovingAverage
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
ConfigData = MappingProxyType[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def get_tz(hass: HomeAssistant):
|
def get_tz(hass: HomeAssistant):
|
||||||
@@ -197,7 +202,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
unique_id: str,
|
||||||
|
name: str,
|
||||||
|
entry_infos: ConfigData,
|
||||||
|
):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -262,7 +273,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
self._last_change_time = None
|
self._last_change_time = None
|
||||||
|
|
||||||
self._underlyings = []
|
self._underlyings: list[UnderlyingEntity] = []
|
||||||
|
|
||||||
self._ema_temp = None
|
self._ema_temp = None
|
||||||
self._ema_algo = None
|
self._ema_algo = None
|
||||||
@@ -276,7 +287,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
self.post_init(entry_infos)
|
self.post_init(entry_infos)
|
||||||
|
|
||||||
def clean_central_config_doublon(self, config_entry, central_config) -> dict:
|
def clean_central_config_doublon(
|
||||||
|
self, config_entry: ConfigData, central_config: ConfigEntry | None
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""Removes all values from config with are concerned by central_config"""
|
"""Removes all values from config with are concerned by central_config"""
|
||||||
|
|
||||||
def clean_one(cfg, schema: vol.Schema):
|
def clean_one(cfg, schema: vol.Schema):
|
||||||
@@ -322,7 +335,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
return entry_infos
|
return entry_infos
|
||||||
|
|
||||||
def post_init(self, config_entry):
|
def post_init(self, config_entry: ConfigData):
|
||||||
"""Finish the initialization of the thermostast"""
|
"""Finish the initialization of the thermostast"""
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@@ -345,7 +358,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._attr_target_temperature_step = step
|
self._attr_target_temperature_step = step
|
||||||
|
|
||||||
# convert entry_infos into usable attributes
|
# convert entry_infos into usable attributes
|
||||||
presets = {}
|
presets: dict[str, Any] = {}
|
||||||
items = CONF_PRESETS_WITH_AC.items() if self._ac_mode else CONF_PRESETS.items()
|
items = CONF_PRESETS_WITH_AC.items() if self._ac_mode else CONF_PRESETS.items()
|
||||||
for key, value in items:
|
for key, value in items:
|
||||||
_LOGGER.debug("looking for key=%s, value=%s", key, value)
|
_LOGGER.debug("looking for key=%s, value=%s", key, value)
|
||||||
@@ -357,7 +370,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._attr_max_temp if self._ac_mode else self._attr_min_temp
|
self._attr_max_temp if self._ac_mode else self._attr_min_temp
|
||||||
)
|
)
|
||||||
|
|
||||||
presets_away = {}
|
presets_away: dict[str, Any] = {}
|
||||||
items = (
|
items = (
|
||||||
CONF_PRESETS_AWAY_WITH_AC.items()
|
CONF_PRESETS_AWAY_WITH_AC.items()
|
||||||
if self._ac_mode
|
if self._ac_mode
|
||||||
@@ -805,7 +818,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
def init_underlyings(self):
|
def init_underlyings(self):
|
||||||
"""Initialize all underlyings. Should be overriden if necessary"""
|
"""Initialize all underlyings. Should be overriden if necessary"""
|
||||||
|
|
||||||
def restore_specific_previous_state(self, old_state):
|
def restore_specific_previous_state(self, old_state: State):
|
||||||
"""Should be overriden in each specific thermostat
|
"""Should be overriden in each specific thermostat
|
||||||
if a specific previous state or attribute should be
|
if a specific previous state or attribute should be
|
||||||
restored
|
restored
|
||||||
@@ -886,7 +899,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._hvac_mode,
|
self._hvac_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return f"VersatileThermostat-{self.name}"
|
return f"VersatileThermostat-{self.name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -916,19 +929,19 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self) -> str:
|
||||||
return self._unique_id
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_modes(self):
|
def hvac_modes(self) -> list[HVACMode]:
|
||||||
"""List of available operation modes."""
|
"""List of available operation modes."""
|
||||||
return self._hvac_list
|
return self._hvac_list
|
||||||
|
|
||||||
@@ -1016,17 +1029,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
return self._is_used_by_central_boiler
|
return self._is_used_by_central_boiler
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self) -> float | None:
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
return self._target_temp
|
return self._target_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self) -> ClimateEntityFeature:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return self._support_flags
|
return self._support_flags
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_device_active(self):
|
def is_device_active(self) -> bool:
|
||||||
"""Returns true if one underlying is active"""
|
"""Returns true if one underlying is active"""
|
||||||
for under in self._underlyings:
|
for under in self._underlyings:
|
||||||
if under.is_device_active:
|
if under.is_device_active:
|
||||||
@@ -1034,7 +1047,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self) -> float | None:
|
||||||
"""Return the sensor temperature."""
|
"""Return the sensor temperature."""
|
||||||
return self._cur_temp
|
return self._cur_temp
|
||||||
|
|
||||||
@@ -1203,7 +1216,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"""Turn auxiliary heater off."""
|
"""Turn auxiliary heater off."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode, need_control_heating=True):
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode, need_control_heating=True):
|
||||||
"""Set new target hvac mode."""
|
"""Set new target hvac mode."""
|
||||||
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
|
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
|
||||||
|
|
||||||
@@ -1239,7 +1252,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
|
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def async_set_preset_mode(self, preset_mode, overwrite_saved_preset=True):
|
async def async_set_preset_mode(
|
||||||
|
self, preset_mode: str, overwrite_saved_preset=True
|
||||||
|
):
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
await self._async_set_preset_mode_internal(
|
await self._async_set_preset_mode_internal(
|
||||||
preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset
|
preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset
|
||||||
@@ -1247,7 +1262,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
await self.async_control_heating(force=True)
|
await self.async_control_heating(force=True)
|
||||||
|
|
||||||
async def _async_set_preset_mode_internal(
|
async def _async_set_preset_mode_internal(
|
||||||
self, preset_mode, force=False, overwrite_saved_preset=True
|
self, preset_mode: str, force=False, overwrite_saved_preset=True
|
||||||
):
|
):
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
_LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force)
|
_LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force)
|
||||||
@@ -1296,13 +1311,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
||||||
|
|
||||||
def reset_last_change_time(
|
def reset_last_change_time(
|
||||||
self, old_preset_mode=None
|
self, old_preset_mode: str | None = None
|
||||||
): # pylint: disable=unused-argument
|
): # pylint: disable=unused-argument
|
||||||
"""Reset to now the last change time"""
|
"""Reset to now the last change time"""
|
||||||
self._last_change_time = datetime.now(tz=self._current_tz)
|
self._last_change_time = datetime.now(tz=self._current_tz)
|
||||||
_LOGGER.debug("%s - last_change_time is now %s", self, self._last_change_time)
|
_LOGGER.debug("%s - last_change_time is now %s", self, self._last_change_time)
|
||||||
|
|
||||||
def reset_last_temperature_time(self, old_preset_mode=None):
|
def reset_last_temperature_time(self, old_preset_mode: str | None = None):
|
||||||
"""Reset to now the last temperature time if conditions are satisfied"""
|
"""Reset to now the last temperature time if conditions are satisfied"""
|
||||||
if (
|
if (
|
||||||
self._attr_preset_mode not in HIDDEN_PRESETS
|
self._attr_preset_mode not in HIDDEN_PRESETS
|
||||||
@@ -1312,7 +1327,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._last_ext_temperature_measure
|
self._last_ext_temperature_measure
|
||||||
) = datetime.now(tz=self._current_tz)
|
) = datetime.now(tz=self._current_tz)
|
||||||
|
|
||||||
def find_preset_temp(self, preset_mode):
|
def find_preset_temp(self, preset_mode: str):
|
||||||
"""Find the right temperature of a preset considering the presence if configured"""
|
"""Find the right temperature of a preset considering the presence if configured"""
|
||||||
if preset_mode is None or preset_mode == "none":
|
if preset_mode is None or preset_mode == "none":
|
||||||
return (
|
return (
|
||||||
@@ -1348,11 +1363,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
else:
|
else:
|
||||||
return self._presets_away[self.get_preset_away_name(preset_mode)]
|
return self._presets_away[self.get_preset_away_name(preset_mode)]
|
||||||
|
|
||||||
def get_preset_away_name(self, preset_mode):
|
def get_preset_away_name(self, preset_mode: str) -> str:
|
||||||
"""Get the preset name in away mode (when presence is off)"""
|
"""Get the preset name in away mode (when presence is off)"""
|
||||||
return preset_mode + PRESET_AWAY_SUFFIX
|
return preset_mode + PRESET_AWAY_SUFFIX
|
||||||
|
|
||||||
async def async_set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode: str):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
_LOGGER.info("%s - Set fan mode: %s", self, fan_mode)
|
_LOGGER.info("%s - Set fan mode: %s", self, fan_mode)
|
||||||
return
|
return
|
||||||
@@ -1362,7 +1377,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
_LOGGER.info("%s - Set fan mode: %s", self, humidity)
|
_LOGGER.info("%s - Set fan mode: %s", self, humidity)
|
||||||
return
|
return
|
||||||
|
|
||||||
async def async_set_swing_mode(self, swing_mode):
|
async def async_set_swing_mode(self, swing_mode: str):
|
||||||
"""Set new target swing operation."""
|
"""Set new target swing operation."""
|
||||||
_LOGGER.info("%s - Set fan mode: %s", self, swing_mode)
|
_LOGGER.info("%s - Set fan mode: %s", self, swing_mode)
|
||||||
return
|
return
|
||||||
@@ -1379,14 +1394,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self.reset_last_change_time()
|
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):
|
async def _async_internal_set_temperature(self, temperature: float):
|
||||||
"""Set the target temperature and the target temperature of underlying climate if any
|
"""Set the target temperature and the target temperature of underlying climate if any
|
||||||
For testing purpose you can pass an event_timestamp.
|
For testing purpose you can pass an event_timestamp.
|
||||||
"""
|
"""
|
||||||
self._target_temp = temperature
|
self._target_temp = temperature
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_state_date_or_now(self, state: State):
|
def get_state_date_or_now(self, state: State) -> datetime:
|
||||||
"""Extract the last_changed state from State or return now if not available"""
|
"""Extract the last_changed state from State or return now if not available"""
|
||||||
return (
|
return (
|
||||||
state.last_changed.astimezone(self._current_tz)
|
state.last_changed.astimezone(self._current_tz)
|
||||||
@@ -1394,7 +1409,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
else datetime.now(tz=self._current_tz)
|
else datetime.now(tz=self._current_tz)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_last_updated_date_or_now(self, state: State):
|
def get_last_updated_date_or_now(self, state: State) -> datetime:
|
||||||
"""Extract the last_changed state from State or return now if not available"""
|
"""Extract the last_changed state from State or return now if not available"""
|
||||||
return (
|
return (
|
||||||
state.last_updated.astimezone(self._current_tz)
|
state.last_updated.astimezone(self._current_tz)
|
||||||
@@ -1679,7 +1694,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
_LOGGER.error("Unable to update external temperature from sensor: %s", ex)
|
_LOGGER.error("Unable to update external temperature from sensor: %s", ex)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def _async_power_changed(self, event):
|
async def _async_power_changed(self, event: HASSEventType[EventStateChangedData]):
|
||||||
"""Handle power changes."""
|
"""Handle power changes."""
|
||||||
_LOGGER.debug("Thermostat %s - Receive new Power event", self.name)
|
_LOGGER.debug("Thermostat %s - Receive new Power event", self.name)
|
||||||
_LOGGER.debug(event)
|
_LOGGER.debug(event)
|
||||||
@@ -1705,7 +1720,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
|
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def _async_max_power_changed(self, event):
|
async def _async_max_power_changed(
|
||||||
|
self, event: HASSEventType[EventStateChangedData]
|
||||||
|
):
|
||||||
"""Handle power max changes."""
|
"""Handle power max changes."""
|
||||||
_LOGGER.debug("Thermostat %s - Receive new Power Max event", self.name)
|
_LOGGER.debug("Thermostat %s - Receive new Power Max event", self.name)
|
||||||
_LOGGER.debug(event)
|
_LOGGER.debug(event)
|
||||||
@@ -1730,7 +1747,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
|
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def _async_presence_changed(self, event):
|
async def _async_presence_changed(
|
||||||
|
self, event: HASSEventType[EventStateChangedData]
|
||||||
|
):
|
||||||
"""Handle presence changes."""
|
"""Handle presence changes."""
|
||||||
new_state = event.data.get("new_state")
|
new_state = event.data.get("new_state")
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@@ -1746,7 +1765,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
await self._async_update_presence(new_state.state)
|
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):
|
async def _async_update_presence(self, new_state: str):
|
||||||
_LOGGER.info("%s - Updating presence. New state is %s", self, new_state)
|
_LOGGER.info("%s - Updating presence. New state is %s", self, new_state)
|
||||||
self._presence_state = (
|
self._presence_state = (
|
||||||
STATE_ON if new_state in (STATE_ON, STATE_HOME) else STATE_OFF
|
STATE_ON if new_state in (STATE_ON, STATE_HOME) else STATE_OFF
|
||||||
@@ -1859,7 +1878,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.window_bypass_state or not self.is_window_auto_enabled:
|
if self.window_bypass_state or not self.is_window_auto_enabled:
|
||||||
_LOGGER.info(
|
_LOGGER.debug(
|
||||||
"%s - Window auto event is ignored because bypass is ON or window auto detection is disabled",
|
"%s - Window auto event is ignored because bypass is ON or window auto detection is disabled",
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
@@ -2044,7 +2063,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
return self._overpowering_state
|
return self._overpowering_state
|
||||||
|
|
||||||
async def check_central_mode(self, new_central_mode, old_central_mode) -> None:
|
async def check_central_mode(
|
||||||
|
self, new_central_mode: str | None, old_central_mode: str | None
|
||||||
|
):
|
||||||
"""Take into account a central mode change"""
|
"""Take into account a central mode change"""
|
||||||
if not self.is_controlled_by_central_mode:
|
if not self.is_controlled_by_central_mode:
|
||||||
self._last_central_mode = None
|
self._last_central_mode = None
|
||||||
@@ -2334,7 +2355,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
else: # default is to turn_off
|
else: # default is to turn_off
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
|
||||||
async def async_control_heating(self, force=False, _=None):
|
async def async_control_heating(self, force=False, _=None) -> bool:
|
||||||
"""The main function used to run the calculation at each cycle"""
|
"""The main function used to run the calculation at each cycle"""
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@@ -2402,7 +2423,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
def update_custom_attributes(self):
|
def update_custom_attributes(self):
|
||||||
"""Update the custom extra attributes for the entity"""
|
"""Update the custom extra attributes for the entity"""
|
||||||
|
|
||||||
self._attr_extra_state_attributes: dict(str, str) = {
|
self._attr_extra_state_attributes: dict[str, Any] = {
|
||||||
"is_on": self.is_on,
|
"is_on": self.is_on,
|
||||||
"hvac_action": self.hvac_action,
|
"hvac_action": self.hvac_action,
|
||||||
"hvac_mode": self.hvac_mode,
|
"hvac_mode": self.hvac_mode,
|
||||||
@@ -2483,7 +2504,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"""
|
"""
|
||||||
_LOGGER.info("%s - The config entry have been updated")
|
_LOGGER.info("%s - The config entry have been updated")
|
||||||
|
|
||||||
async def service_set_presence(self, presence):
|
async def service_set_presence(self, presence: str):
|
||||||
"""Called by a service call:
|
"""Called by a service call:
|
||||||
service: versatile_thermostat.set_presence
|
service: versatile_thermostat.set_presence
|
||||||
data:
|
data:
|
||||||
@@ -2496,7 +2517,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
await self.async_control_heating(force=True)
|
await self.async_control_heating(force=True)
|
||||||
|
|
||||||
async def service_set_preset_temperature(
|
async def service_set_preset_temperature(
|
||||||
self, preset, temperature=None, temperature_away=None
|
self,
|
||||||
|
preset: str,
|
||||||
|
temperature: float | None = None,
|
||||||
|
temperature_away: float | None = None,
|
||||||
):
|
):
|
||||||
"""Called by a service call:
|
"""Called by a service call:
|
||||||
service: versatile_thermostat.set_preset_temperature
|
service: versatile_thermostat.set_preset_temperature
|
||||||
@@ -2534,7 +2558,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
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):
|
async def service_set_security(
|
||||||
|
self,
|
||||||
|
delay_min: int | None,
|
||||||
|
min_on_percent: float | None,
|
||||||
|
default_on_percent: float | None,
|
||||||
|
):
|
||||||
"""Called by a service call:
|
"""Called by a service call:
|
||||||
service: versatile_thermostat.set_security
|
service: versatile_thermostat.set_security
|
||||||
data:
|
data:
|
||||||
@@ -2564,7 +2593,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
await self.async_control_heating()
|
await self.async_control_heating()
|
||||||
self.update_custom_attributes()
|
self.update_custom_attributes()
|
||||||
|
|
||||||
async def service_set_window_bypass_state(self, window_bypass):
|
async def service_set_window_bypass_state(self, window_bypass: bool):
|
||||||
"""Called by a service call:
|
"""Called by a service call:
|
||||||
service: versatile_thermostat.set_window_bypass
|
service: versatile_thermostat.set_window_bypass
|
||||||
data:
|
data:
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
|
|||||||
]
|
]
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
|
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
|
||||||
vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=0.5): vol.Coerce(float),
|
vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=10): vol.Coerce(float),
|
||||||
vol.Optional(CONF_AUTO_REGULATION_PERIOD_MIN, default=5): cv.positive_int,
|
vol.Optional(CONF_AUTO_REGULATION_PERIOD_MIN, default=5): cv.positive_int,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ from homeassistant.helpers.restore_state import RestoreEntity
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
|
||||||
|
from custom_components.versatile_thermostat.base_thermostat import (
|
||||||
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
BaseThermostat,
|
||||||
|
ConfigData,
|
||||||
|
)
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
DEVICE_MANUFACTURER,
|
DEVICE_MANUFACTURER,
|
||||||
@@ -57,7 +59,9 @@ async def async_setup_entry(
|
|||||||
class CentralModeSelect(SelectEntity, RestoreEntity):
|
class CentralModeSelect(SelectEntity, RestoreEntity):
|
||||||
"""Representation of the central mode choice"""
|
"""Representation of the central mode choice"""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
|
||||||
|
):
|
||||||
"""Initialize the energy sensor"""
|
"""Initialize the energy sensor"""
|
||||||
self._config_id = unique_id
|
self._config_id = unique_id
|
||||||
self._device_name = entry_infos.get(CONF_NAME)
|
self._device_name = entry_infos.get(CONF_NAME)
|
||||||
@@ -67,7 +71,7 @@ class CentralModeSelect(SelectEntity, RestoreEntity):
|
|||||||
self._attr_current_option = CENTRAL_MODE_AUTO
|
self._attr_current_option = CENTRAL_MODE_AUTO
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self) -> str | None:
|
def icon(self) -> str:
|
||||||
return "mdi:form-select"
|
return "mdi:form-select"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -116,7 +120,7 @@ class CentralModeSelect(SelectEntity, RestoreEntity):
|
|||||||
self._attr_current_option = option
|
self._attr_current_option = option
|
||||||
await self.notify_central_mode_change(old_central_mode=old_option)
|
await self.notify_central_mode_change(old_central_mode=old_option)
|
||||||
|
|
||||||
async def notify_central_mode_change(self, old_central_mode=None):
|
async def notify_central_mode_change(self, old_central_mode: str | None = None):
|
||||||
"""Notify all VTherm that the central_mode have change"""
|
"""Notify all VTherm that the central_mode have change"""
|
||||||
# Update all VTherm states
|
# Update all VTherm states
|
||||||
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
|
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
|
||||||
@@ -130,5 +134,5 @@ class CentralModeSelect(SelectEntity, RestoreEntity):
|
|||||||
self._attr_current_option, old_central_mode
|
self._attr_current_option, old_central_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return f"VersatileThermostat-{self.name}"
|
return f"VersatileThermostat-{self.name}"
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ class ValveOpenPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
|
|||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
||||||
"""Initialize the energy sensor"""
|
"""Initialize the energy sensor"""
|
||||||
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
||||||
self._attr_name = "Vave open percent"
|
self._attr_name = "Valve open percent"
|
||||||
self._attr_unique_id = f"{self._device_name}_valve_open_percent"
|
self._attr_unique_id = f"{self._device_name}_valve_open_percent"
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, State, callback
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
async_track_time_interval,
|
async_track_time_interval,
|
||||||
|
EventStateChangedData,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.typing import EventType as HASSEventType
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
HVACAction,
|
HVACAction,
|
||||||
HVACMode,
|
HVACMode,
|
||||||
@@ -16,7 +17,7 @@ from homeassistant.components.climate import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .commons import NowClass, round_to_nearest
|
from .commons import NowClass, round_to_nearest
|
||||||
from .base_thermostat import BaseThermostat
|
from .base_thermostat import BaseThermostat, ConfigData
|
||||||
from .pi_algorithm import PITemperatureRegulator
|
from .pi_algorithm import PITemperatureRegulator
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@@ -59,19 +60,19 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class ThermostatOverClimate(BaseThermostat):
|
class ThermostatOverClimate(BaseThermostat):
|
||||||
"""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
|
_auto_regulation_mode: str | None = None
|
||||||
_regulation_algo = None
|
_regulation_algo = None
|
||||||
_regulated_target_temp: float = None
|
_regulated_target_temp: float | None = None
|
||||||
_auto_regulation_dtemp: float = None
|
_auto_regulation_dtemp: float | None = None
|
||||||
_auto_regulation_period_min: int = None
|
_auto_regulation_period_min: int | None = None
|
||||||
_last_regulation_change: datetime = None
|
_last_regulation_change: datetime | None = None
|
||||||
# The fan mode configured in configEntry
|
# The fan mode configured in configEntry
|
||||||
_auto_fan_mode: str = None
|
_auto_fan_mode: str | None = None
|
||||||
# The current fan mode (could be change by service call)
|
# The current fan mode (could be change by service call)
|
||||||
_current_auto_fan_mode: str = None
|
_current_auto_fan_mode: str | None = None
|
||||||
# The fan_mode name depending of the current_mode
|
# The fan_mode name depending of the current_mode
|
||||||
_auto_activated_fan_mode: str = None
|
_auto_activated_fan_mode: str | None = None
|
||||||
_auto_deactivated_fan_mode: str = None
|
_auto_deactivated_fan_mode: str | None = None
|
||||||
|
|
||||||
_entity_component_unrecorded_attributes = (
|
_entity_component_unrecorded_attributes = (
|
||||||
BaseThermostat._entity_component_unrecorded_attributes.union(
|
BaseThermostat._entity_component_unrecorded_attributes.union(
|
||||||
@@ -94,7 +95,9 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
|
||||||
|
):
|
||||||
"""Initialize the thermostat over switch."""
|
"""Initialize the thermostat over switch."""
|
||||||
# super.__init__ calls post_init at the end. So it must be called after regulation initialization
|
# super.__init__ calls post_init at the end. So it must be called after regulation initialization
|
||||||
super().__init__(hass, unique_id, name, entry_infos)
|
super().__init__(hass, unique_id, name, entry_infos)
|
||||||
@@ -127,7 +130,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
return HVACAction.OFF
|
return HVACAction.OFF
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def _async_internal_set_temperature(self, temperature):
|
async def _async_internal_set_temperature(self, temperature: float):
|
||||||
"""Set the target temperature and the target temperature of underlying climate if any"""
|
"""Set the target temperature and the target temperature of underlying climate if any"""
|
||||||
await super()._async_internal_set_temperature(temperature)
|
await super()._async_internal_set_temperature(temperature)
|
||||||
|
|
||||||
@@ -239,7 +242,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
await self.async_set_fan_mode(self._auto_deactivated_fan_mode)
|
await self.async_set_fan_mode(self._auto_deactivated_fan_mode)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def post_init(self, config_entry):
|
def post_init(self, config_entry: ConfigData):
|
||||||
"""Initialize the Thermostat"""
|
"""Initialize the Thermostat"""
|
||||||
|
|
||||||
super().post_init(config_entry)
|
super().post_init(config_entry)
|
||||||
@@ -281,7 +284,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
else CONF_AUTO_FAN_NONE
|
else CONF_AUTO_FAN_NONE
|
||||||
)
|
)
|
||||||
|
|
||||||
def choose_auto_regulation_mode(self, auto_regulation_mode):
|
def choose_auto_regulation_mode(self, auto_regulation_mode: str):
|
||||||
"""Choose or change the regulation mode"""
|
"""Choose or change the regulation mode"""
|
||||||
self._auto_regulation_mode = auto_regulation_mode
|
self._auto_regulation_mode = auto_regulation_mode
|
||||||
if self._auto_regulation_mode == CONF_AUTO_REGULATION_LIGHT:
|
if self._auto_regulation_mode == CONF_AUTO_REGULATION_LIGHT:
|
||||||
@@ -357,7 +360,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
self.target_temperature, 0, 0, 0, 0, 0.1, 0
|
self.target_temperature, 0, 0, 0, 0, 0.1, 0
|
||||||
)
|
)
|
||||||
|
|
||||||
def choose_auto_fan_mode(self, auto_fan_mode):
|
def choose_auto_fan_mode(self, auto_fan_mode: str):
|
||||||
"""Choose the correct fan mode depending of the underlying capacities and the configuration"""
|
"""Choose the correct fan mode depending of the underlying capacities and the configuration"""
|
||||||
|
|
||||||
self._current_auto_fan_mode = auto_fan_mode
|
self._current_auto_fan_mode = auto_fan_mode
|
||||||
@@ -369,7 +372,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
self._auto_activated_fan_mode = self._auto_deactivated_fan_mode = None
|
self._auto_activated_fan_mode = self._auto_deactivated_fan_mode = None
|
||||||
return
|
return
|
||||||
|
|
||||||
def find_fan_mode(fan_modes, fan_mode) -> str:
|
def find_fan_mode(fan_modes: list[str], fan_mode: str) -> str | None:
|
||||||
"""Return the fan_mode if it exist of None if not"""
|
"""Return the fan_mode if it exist of None if not"""
|
||||||
try:
|
try:
|
||||||
return fan_mode if fan_modes.index(fan_mode) >= 0 else None
|
return fan_mode if fan_modes.index(fan_mode) >= 0 else None
|
||||||
@@ -427,10 +430,11 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# init auto_regulation_mode
|
# init auto_regulation_mode
|
||||||
self.choose_auto_regulation_mode(self._auto_regulation_mode)
|
# Issue 325 - do only once (in post_init and not here)
|
||||||
|
# self.choose_auto_regulation_mode(self._auto_regulation_mode)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def restore_specific_previous_state(self, old_state):
|
def restore_specific_previous_state(self, old_state: State):
|
||||||
"""Restore my specific attributes from previous state"""
|
"""Restore my specific attributes from previous state"""
|
||||||
old_error = old_state.attributes.get("regulation_accumulated_error")
|
old_error = old_state.attributes.get("regulation_accumulated_error")
|
||||||
if old_error:
|
if old_error:
|
||||||
@@ -542,7 +546,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def _async_climate_changed(self, event):
|
async def _async_climate_changed(self, event: HASSEventType[EventStateChangedData]):
|
||||||
"""Handle unerdlying climate state changes.
|
"""Handle unerdlying climate state changes.
|
||||||
This method takes the underlying values and update the VTherm with them.
|
This method takes the underlying values and update the VTherm with them.
|
||||||
To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received
|
To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received
|
||||||
@@ -552,7 +556,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
which is important for feedaback and which cannot generates loops.
|
which is important for feedaback and which cannot generates loops.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def end_climate_changed(changes):
|
async def end_climate_changed(changes: bool):
|
||||||
"""To end the event management"""
|
"""To end the event management"""
|
||||||
if changes:
|
if changes:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
@@ -745,7 +749,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
await end_climate_changed(changes)
|
await end_climate_changed(changes)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def async_control_heating(self, force=False, _=None):
|
async def async_control_heating(self, force=False, _=None) -> bool:
|
||||||
"""The main function used to run the calculation at each cycle"""
|
"""The main function used to run the calculation at each cycle"""
|
||||||
ret = await super().async_control_heating(force, _)
|
ret = await super().async_control_heating(force, _)
|
||||||
|
|
||||||
@@ -757,27 +761,27 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_regulation_mode(self):
|
def auto_regulation_mode(self) -> str | None:
|
||||||
"""Get the regulation mode"""
|
"""Get the regulation mode"""
|
||||||
return self._auto_regulation_mode
|
return self._auto_regulation_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_fan_mode(self):
|
def auto_fan_mode(self) -> str | None:
|
||||||
"""Get the auto fan mode"""
|
"""Get the auto fan mode"""
|
||||||
return self._auto_fan_mode
|
return self._auto_fan_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def regulated_target_temp(self):
|
def regulated_target_temp(self) -> float | None:
|
||||||
"""Get the regulated target temperature"""
|
"""Get the regulated target temperature"""
|
||||||
return self._regulated_target_temp
|
return self._regulated_target_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_regulated(self):
|
def is_regulated(self) -> bool:
|
||||||
"""Check if the ThermostatOverClimate is regulated"""
|
"""Check if the ThermostatOverClimate is regulated"""
|
||||||
return self.auto_regulation_mode != CONF_AUTO_REGULATION_NONE
|
return self.auto_regulation_mode != CONF_AUTO_REGULATION_NONE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_modes(self):
|
def hvac_modes(self) -> list[HVACMode]:
|
||||||
"""List of available operation modes."""
|
"""List of available operation modes."""
|
||||||
if self.underlying_entity(0):
|
if self.underlying_entity(0):
|
||||||
return self.underlying_entity(0).hvac_modes
|
return self.underlying_entity(0).hvac_modes
|
||||||
@@ -944,7 +948,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
await under.async_turn_aux_heat_off()
|
await under.async_turn_aux_heat_off()
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def async_set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode: str):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
_LOGGER.info("%s - Set fan mode: %s", self, fan_mode)
|
_LOGGER.info("%s - Set fan mode: %s", self, fan_mode)
|
||||||
if fan_mode is None:
|
if fan_mode is None:
|
||||||
@@ -977,7 +981,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
self._swing_mode = swing_mode
|
self._swing_mode = swing_mode
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def service_set_auto_regulation_mode(self, auto_regulation_mode):
|
async def service_set_auto_regulation_mode(self, auto_regulation_mode: str):
|
||||||
"""Called by a service call:
|
"""Called by a service call:
|
||||||
service: versatile_thermostat.set_auto_regulation_mode
|
service: versatile_thermostat.set_auto_regulation_mode
|
||||||
data:
|
data:
|
||||||
@@ -1006,7 +1010,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
await self._send_regulated_temperature()
|
await self._send_regulated_temperature()
|
||||||
self.update_custom_attributes()
|
self.update_custom_attributes()
|
||||||
|
|
||||||
async def service_set_auto_fan_mode(self, auto_fan_mode):
|
async def service_set_auto_fan_mode(self, auto_fan_mode: str):
|
||||||
"""Called by a service call:
|
"""Called by a service call:
|
||||||
service: versatile_thermostat.set_auto_fan_mode
|
service: versatile_thermostat.set_auto_fan_mode
|
||||||
data:
|
data:
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
""" A climate over switch classe """
|
""" A climate over switch classe """
|
||||||
import logging
|
import logging
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.event import async_track_state_change_event
|
from homeassistant.helpers.event import (
|
||||||
|
async_track_state_change_event,
|
||||||
|
EventStateChangedData,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.typing import EventType as HASSEventType
|
||||||
from homeassistant.components.climate import HVACMode
|
from homeassistant.components.climate import HVACMode
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@@ -15,7 +19,7 @@ from .const import (
|
|||||||
overrides,
|
overrides,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .base_thermostat import BaseThermostat
|
from .base_thermostat import BaseThermostat, ConfigData
|
||||||
from .underlyings import UnderlyingSwitch
|
from .underlyings import UnderlyingSwitch
|
||||||
from .prop_algorithm import PropAlgorithm
|
from .prop_algorithm import PropAlgorithm
|
||||||
|
|
||||||
@@ -51,7 +55,7 @@ class ThermostatOverSwitch(BaseThermostat):
|
|||||||
# def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
|
# def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
|
||||||
# """Initialize the thermostat over switch."""
|
# """Initialize the thermostat over switch."""
|
||||||
# super().__init__(hass, unique_id, name, config_entry)
|
# super().__init__(hass, unique_id, name, config_entry)
|
||||||
_is_inversed: bool = None
|
_is_inversed: bool | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_over_switch(self) -> bool:
|
def is_over_switch(self) -> bool:
|
||||||
@@ -72,7 +76,7 @@ class ThermostatOverSwitch(BaseThermostat):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def post_init(self, config_entry):
|
def post_init(self, config_entry: ConfigData):
|
||||||
"""Initialize the Thermostat"""
|
"""Initialize the Thermostat"""
|
||||||
|
|
||||||
super().post_init(config_entry)
|
super().post_init(config_entry)
|
||||||
@@ -200,7 +204,7 @@ class ThermostatOverSwitch(BaseThermostat):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_switch_changed(self, event):
|
def _async_switch_changed(self, event: HASSEventType[EventStateChangedData]):
|
||||||
"""Handle heater switch state changes."""
|
"""Handle heater switch state changes."""
|
||||||
new_state = event.data.get("new_state")
|
new_state = event.data.get("new_state")
|
||||||
old_state = event.data.get("old_state")
|
old_state = event.data.get("old_state")
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ from datetime import timedelta, datetime
|
|||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
async_track_time_interval,
|
async_track_time_interval,
|
||||||
|
EventStateChangedData,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.typing import EventType as HASSEventType
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.components.climate import HVACMode
|
from homeassistant.components.climate import HVACMode
|
||||||
|
|
||||||
from .base_thermostat import BaseThermostat
|
from .base_thermostat import BaseThermostat, ConfigData
|
||||||
from .prop_algorithm import PropAlgorithm
|
from .prop_algorithm import PropAlgorithm
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@@ -55,12 +57,14 @@ class ThermostatOverValve(BaseThermostat):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id: str, name: str, config_entry: ConfigData
|
||||||
|
):
|
||||||
"""Initialize the thermostat over switch."""
|
"""Initialize the thermostat over switch."""
|
||||||
self._valve_open_percent: int = 0
|
self._valve_open_percent: int = 0
|
||||||
self._last_calculation_timestamp: datetime = None
|
self._last_calculation_timestamp: datetime | None = None
|
||||||
self._auto_regulation_dpercent: float = None
|
self._auto_regulation_dpercent: float | None = None
|
||||||
self._auto_regulation_period_min: int = None
|
self._auto_regulation_period_min: int | None = None
|
||||||
|
|
||||||
# Call to super must be done after initialization because it calls post_init at the end
|
# Call to super must be done after initialization because it calls post_init at the end
|
||||||
super().__init__(hass, unique_id, name, config_entry)
|
super().__init__(hass, unique_id, name, config_entry)
|
||||||
@@ -79,7 +83,7 @@ class ThermostatOverValve(BaseThermostat):
|
|||||||
return self._valve_open_percent
|
return self._valve_open_percent
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def post_init(self, config_entry):
|
def post_init(self, config_entry: ConfigData):
|
||||||
"""Initialize the Thermostat"""
|
"""Initialize the Thermostat"""
|
||||||
|
|
||||||
super().post_init(config_entry)
|
super().post_init(config_entry)
|
||||||
@@ -144,7 +148,7 @@ class ThermostatOverValve(BaseThermostat):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def _async_valve_changed(self, event):
|
async def _async_valve_changed(self, event: HASSEventType[EventStateChangedData]):
|
||||||
"""Handle unerdlying valve state changes.
|
"""Handle unerdlying valve state changes.
|
||||||
This method just log the change. It changes nothing to avoid loops.
|
This method just log the change. It changes nothing to avoid loops.
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user