Skip to content

Commit

Permalink
Enable strict typing for generic_thermostat (home-assistant#108024)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p authored Jan 15, 2024
1 parent 9bca09a commit 84038fb
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 50 deletions.
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ homeassistant.components.fronius.*
homeassistant.components.frontend.*
homeassistant.components.fully_kiosk.*
homeassistant.components.generic_hygrostat.*
homeassistant.components.generic_thermostat.*
homeassistant.components.geo_location.*
homeassistant.components.geocaching.*
homeassistant.components.gios.*
Expand Down
106 changes: 56 additions & 50 deletions homeassistant/components/generic_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import asyncio
from datetime import datetime, timedelta
import logging
import math
from typing import Any
Expand Down Expand Up @@ -36,10 +37,12 @@
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfTemperature,
)
from homeassistant.core import (
DOMAIN as HA_DOMAIN,
CoreState,
Event,
HomeAssistant,
State,
callback,
Expand Down Expand Up @@ -126,25 +129,25 @@ async def async_setup_platform(

await async_setup_reload_service(hass, DOMAIN, PLATFORMS)

name = config.get(CONF_NAME)
heater_entity_id = config.get(CONF_HEATER)
sensor_entity_id = config.get(CONF_SENSOR)
min_temp = config.get(CONF_MIN_TEMP)
max_temp = config.get(CONF_MAX_TEMP)
target_temp = config.get(CONF_TARGET_TEMP)
ac_mode = config.get(CONF_AC_MODE)
min_cycle_duration = config.get(CONF_MIN_DUR)
cold_tolerance = config.get(CONF_COLD_TOLERANCE)
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
keep_alive = config.get(CONF_KEEP_ALIVE)
initial_hvac_mode = config.get(CONF_INITIAL_HVAC_MODE)
presets = {
name: str = config[CONF_NAME]
heater_entity_id: str = config[CONF_HEATER]
sensor_entity_id: str = config[CONF_SENSOR]
min_temp: float | None = config.get(CONF_MIN_TEMP)
max_temp: float | None = config.get(CONF_MAX_TEMP)
target_temp: float | None = config.get(CONF_TARGET_TEMP)
ac_mode: bool | None = config.get(CONF_AC_MODE)
min_cycle_duration: timedelta | None = config.get(CONF_MIN_DUR)
cold_tolerance: float = config[CONF_COLD_TOLERANCE]
hot_tolerance: float = config[CONF_HOT_TOLERANCE]
keep_alive: timedelta | None = config.get(CONF_KEEP_ALIVE)
initial_hvac_mode: HVACMode | None = config.get(CONF_INITIAL_HVAC_MODE)
presets: dict[str, float] = {
key: config[value] for key, value in CONF_PRESETS.items() if value in config
}
precision = config.get(CONF_PRECISION)
target_temperature_step = config.get(CONF_TEMP_STEP)
precision: float | None = config.get(CONF_PRECISION)
target_temperature_step: float | None = config.get(CONF_TEMP_STEP)
unit = hass.config.units.temperature_unit
unique_id = config.get(CONF_UNIQUE_ID)
unique_id: str | None = config.get(CONF_UNIQUE_ID)

async_add_entities(
[
Expand Down Expand Up @@ -178,24 +181,24 @@ class GenericThermostat(ClimateEntity, RestoreEntity):

def __init__(
self,
name,
heater_entity_id,
sensor_entity_id,
min_temp,
max_temp,
target_temp,
ac_mode,
min_cycle_duration,
cold_tolerance,
hot_tolerance,
keep_alive,
initial_hvac_mode,
presets,
precision,
target_temperature_step,
unit,
unique_id,
):
name: str,
heater_entity_id: str,
sensor_entity_id: str,
min_temp: float | None,
max_temp: float | None,
target_temp: float | None,
ac_mode: bool | None,
min_cycle_duration: timedelta | None,
cold_tolerance: float,
hot_tolerance: float,
keep_alive: timedelta | None,
initial_hvac_mode: HVACMode | None,
presets: dict[str, float],
precision: float | None,
target_temperature_step: float | None,
unit: UnitOfTemperature,
unique_id: str | None,
) -> None:
"""Initialize the thermostat."""
self._attr_name = name
self.heater_entity_id = heater_entity_id
Expand All @@ -214,7 +217,7 @@ def __init__(
else:
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
self._active = False
self._cur_temp = None
self._cur_temp: float | None = None
self._temp_lock = asyncio.Lock()
self._min_temp = min_temp
self._max_temp = max_temp
Expand Down Expand Up @@ -254,7 +257,7 @@ async def async_added_to_hass(self) -> None:
)

@callback
def _async_startup(*_):
def _async_startup(_: Event | None = None) -> None:
"""Init on startup."""
sensor_state = self.hass.states.get(self.sensor_entity_id)
if sensor_state and sensor_state.state not in (
Expand Down Expand Up @@ -297,7 +300,7 @@ def _async_startup(*_):
):
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
if not self._hvac_mode and old_state.state:
self._hvac_mode = old_state.state
self._hvac_mode = HVACMode(old_state.state)

else:
# No previous state, try and restore defaults
Expand All @@ -315,32 +318,32 @@ def _async_startup(*_):
self._hvac_mode = HVACMode.OFF

@property
def precision(self):
def precision(self) -> float:
"""Return the precision of the system."""
if self._temp_precision is not None:
return self._temp_precision
return super().precision

@property
def target_temperature_step(self):
def target_temperature_step(self) -> float:
"""Return the supported step of target temperature."""
if self._temp_target_temperature_step is not None:
return self._temp_target_temperature_step
# if a target_temperature_step is not defined, fallback to equal the precision
return self.precision

@property
def current_temperature(self):
def current_temperature(self) -> float | None:
"""Return the sensor temperature."""
return self._cur_temp

@property
def hvac_mode(self):
def hvac_mode(self) -> HVACMode | None:
"""Return current operation."""
return self._hvac_mode

@property
def hvac_action(self):
def hvac_action(self) -> HVACAction:
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
Expand All @@ -354,7 +357,7 @@ def hvac_action(self):
return HVACAction.HEATING

@property
def target_temperature(self):
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
return self._target_temp

Expand Down Expand Up @@ -385,7 +388,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None:
self.async_write_ha_state()

@property
def min_temp(self):
def min_temp(self) -> float:
"""Return the minimum temperature."""
if self._min_temp is not None:
return self._min_temp
Expand All @@ -394,7 +397,7 @@ def min_temp(self):
return super().min_temp

@property
def max_temp(self):
def max_temp(self) -> float:
"""Return the maximum temperature."""
if self._max_temp is not None:
return self._max_temp
Expand All @@ -414,7 +417,7 @@ async def _async_sensor_changed(
await self._async_control_heating()
self.async_write_ha_state()

async def _check_switch_initial_state(self):
async def _check_switch_initial_state(self) -> None:
"""Prevent the device from keep running if HVACMode.OFF."""
if self._hvac_mode == HVACMode.OFF and self._is_device_active:
_LOGGER.warning(
Expand Down Expand Up @@ -448,7 +451,9 @@ def _async_update_temp(self, state: State) -> None:
except ValueError as ex:
_LOGGER.error("Unable to update from sensor: %s", ex)

async def _async_control_heating(self, time=None, force=False):
async def _async_control_heating(
self, time: datetime | None = None, force: bool = False
) -> None:
"""Check if we need to turn heating on or off."""
async with self._temp_lock:
if not self._active and None not in (
Expand Down Expand Up @@ -490,6 +495,7 @@ async def _async_control_heating(self, time=None, force=False):
if not long_enough:
return

assert self._cur_temp is not None and self._target_temp is not None
too_cold = self._target_temp >= self._cur_temp + self._cold_tolerance
too_hot = self._cur_temp >= self._target_temp + self._hot_tolerance
if self._is_device_active:
Expand All @@ -514,21 +520,21 @@ async def _async_control_heating(self, time=None, force=False):
await self._async_heater_turn_off()

@property
def _is_device_active(self):
def _is_device_active(self) -> bool | None:
"""If the toggleable device is currently active."""
if not self.hass.states.get(self.heater_entity_id):
return None

return self.hass.states.is_state(self.heater_entity_id, STATE_ON)

async def _async_heater_turn_on(self):
async def _async_heater_turn_on(self) -> None:
"""Turn heater toggleable device on."""
data = {ATTR_ENTITY_ID: self.heater_entity_id}
await self.hass.services.async_call(
HA_DOMAIN, SERVICE_TURN_ON, data, context=self._context
)

async def _async_heater_turn_off(self):
async def _async_heater_turn_off(self) -> None:
"""Turn heater toggleable device off."""
data = {ATTR_ENTITY_ID: self.heater_entity_id}
await self.hass.services.async_call(
Expand Down
10 changes: 10 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,16 @@ disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true

[mypy-homeassistant.components.generic_thermostat.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true

[mypy-homeassistant.components.geo_location.*]
check_untyped_defs = true
disallow_incomplete_defs = true
Expand Down

0 comments on commit 84038fb

Please sign in to comment.