Add more type hints in the thermostat classes and selected files (#364)

This commit is contained in:
Paulo Ferreira de Castro
2024-01-26 09:51:25 +00:00
committed by GitHub
parent 5d453393f8
commit ed977b53cd
5 changed files with 131 additions and 87 deletions

View File

@@ -6,6 +6,8 @@ import math
import logging
from datetime import timedelta, datetime
from types import MappingProxyType
from typing import Any
from homeassistant.util import dt as dt_util
from homeassistant.core import (
@@ -20,10 +22,12 @@ from homeassistant.components.climate import ClimateEntity
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.typing import EventType as HASSEventType
from homeassistant.helpers.event import (
async_track_state_change_event,
async_call_later,
EventStateChangedData,
)
from homeassistant.exceptions import ConditionError
@@ -134,6 +138,7 @@ from .open_window_algorithm import WindowOpenDetectionAlgorithm
from .ema import ExponentialMovingAverage
_LOGGER = logging.getLogger(__name__)
ConfigData = MappingProxyType[str, Any]
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."""
super().__init__()
@@ -262,7 +273,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._last_change_time = None
self._underlyings = []
self._underlyings: list[UnderlyingEntity] = []
self._ema_temp = None
self._ema_algo = None
@@ -276,7 +287,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
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"""
def clean_one(cfg, schema: vol.Schema):
@@ -322,7 +335,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
return entry_infos
def post_init(self, config_entry):
def post_init(self, config_entry: ConfigData):
"""Finish the initialization of the thermostast"""
_LOGGER.info(
@@ -345,7 +358,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._attr_target_temperature_step = step
# 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()
for key, value in items:
_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
)
presets_away = {}
presets_away: dict[str, Any] = {}
items = (
CONF_PRESETS_AWAY_WITH_AC.items()
if self._ac_mode
@@ -805,7 +818,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
def init_underlyings(self):
"""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
if a specific previous state or attribute should be
restored
@@ -886,7 +899,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._hvac_mode,
)
def __str__(self):
def __str__(self) -> str:
return f"VersatileThermostat-{self.name}"
@property
@@ -916,19 +929,19 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
)
@property
def unique_id(self):
def unique_id(self) -> str:
return self._unique_id
@property
def should_poll(self):
def should_poll(self) -> bool:
return False
@property
def name(self):
def name(self) -> str:
return self._name
@property
def hvac_modes(self):
def hvac_modes(self) -> list[HVACMode]:
"""List of available operation modes."""
return self._hvac_list
@@ -1016,17 +1029,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
return self._is_used_by_central_boiler
@property
def target_temperature(self):
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
return self._target_temp
@property
def supported_features(self):
def supported_features(self) -> ClimateEntityFeature:
"""Return the list of supported features."""
return self._support_flags
@property
def is_device_active(self):
def is_device_active(self) -> bool:
"""Returns true if one underlying is active"""
for under in self._underlyings:
if under.is_device_active:
@@ -1034,7 +1047,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
return False
@property
def current_temperature(self):
def current_temperature(self) -> float | None:
"""Return the sensor temperature."""
return self._cur_temp
@@ -1203,7 +1216,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"""Turn auxiliary heater off."""
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."""
_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})
@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."""
await self._async_set_preset_mode_internal(
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)
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."""
_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})
def reset_last_change_time(
self, old_preset_mode=None
self, old_preset_mode: str | None = None
): # pylint: disable=unused-argument
"""Reset to now the last change time"""
self._last_change_time = datetime.now(tz=self._current_tz)
_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"""
if (
self._attr_preset_mode not in HIDDEN_PRESETS
@@ -1312,7 +1327,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._last_ext_temperature_measure
) = 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"""
if preset_mode is None or preset_mode == "none":
return (
@@ -1348,11 +1363,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
else:
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)"""
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."""
_LOGGER.info("%s - Set fan mode: %s", self, fan_mode)
return
@@ -1362,7 +1377,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
_LOGGER.info("%s - Set fan mode: %s", self, humidity)
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."""
_LOGGER.info("%s - Set fan mode: %s", self, swing_mode)
return
@@ -1379,14 +1394,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self.reset_last_change_time()
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
For testing purpose you can pass an event_timestamp.
"""
self._target_temp = temperature
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"""
return (
state.last_changed.astimezone(self._current_tz)
@@ -1394,7 +1409,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
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"""
return (
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)
@callback
async def _async_power_changed(self, event):
async def _async_power_changed(self, event: HASSEventType[EventStateChangedData]):
"""Handle power changes."""
_LOGGER.debug("Thermostat %s - Receive new Power event", self.name)
_LOGGER.debug(event)
@@ -1705,7 +1720,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
@callback
async def _async_max_power_changed(self, event):
async def _async_max_power_changed(
self, event: HASSEventType[EventStateChangedData]
):
"""Handle power max changes."""
_LOGGER.debug("Thermostat %s - Receive new Power Max event", self.name)
_LOGGER.debug(event)
@@ -1730,7 +1747,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
@callback
async def _async_presence_changed(self, event):
async def _async_presence_changed(
self, event: HASSEventType[EventStateChangedData]
):
"""Handle presence changes."""
new_state = event.data.get("new_state")
_LOGGER.info(
@@ -1746,7 +1765,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self._async_update_presence(new_state.state)
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)
self._presence_state = (
STATE_ON if new_state in (STATE_ON, STATE_HOME) else STATE_OFF
@@ -2044,7 +2063,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
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"""
if not self.is_controlled_by_central_mode:
self._last_central_mode = None
@@ -2334,7 +2355,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
else: # default is to turn_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"""
_LOGGER.debug(
@@ -2402,7 +2423,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
def update_custom_attributes(self):
"""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,
"hvac_action": self.hvac_action,
"hvac_mode": self.hvac_mode,
@@ -2483,7 +2504,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"""
_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:
service: versatile_thermostat.set_presence
data:
@@ -2496,7 +2517,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self.async_control_heating(force=True)
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:
service: versatile_thermostat.set_preset_temperature
@@ -2534,7 +2558,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
)
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:
service: versatile_thermostat.set_security
data:
@@ -2564,7 +2593,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self.async_control_heating()
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:
service: versatile_thermostat.set_window_bypass
data: