diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index e90371e3282..1b9fea57541 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -1,6 +1,8 @@ """Support for KNX/IP climate devices.""" from __future__ import annotations +from typing import Any, Callable, Iterable + from xknx.devices import Climate as XknxClimate from xknx.dpt.dpt_hvac_mode import HVACControllerMode, HVACOperationMode @@ -13,6 +15,12 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import CONTROLLER_MODES, DOMAIN, PRESET_MODES from .knx_entity import KnxEntity @@ -21,7 +29,12 @@ CONTROLLER_MODES_INV = {value: key for key, value in CONTROLLER_MODES.items()} PRESET_MODES_INV = {value: key for key, value in PRESET_MODES.items()} -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up climate(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -33,8 +46,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXClimate(KnxEntity, ClimateEntity): """Representation of a KNX climate device.""" - def __init__(self, device: XknxClimate): + def __init__(self, device: XknxClimate) -> None: """Initialize of a KNX climate device.""" + self._device: XknxClimate super().__init__(device) self._unit_of_measurement = TEMP_CELSIUS @@ -44,42 +58,45 @@ class KNXClimate(KnxEntity, ClimateEntity): """Return the list of supported features.""" return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - async def async_update(self): + async def async_update(self) -> None: """Request a state update from KNX bus.""" await self._device.sync() - await self._device.mode.sync() + if self._device.mode is not None: + await self._device.mode.sync() @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" return self._unit_of_measurement @property - def current_temperature(self): + def current_temperature(self) -> float | None: """Return the current temperature.""" - return self._device.temperature.value + return self._device.temperature.value # type: ignore[no-any-return] @property - def target_temperature_step(self): + def target_temperature_step(self) -> float: """Return the supported step of target temperature.""" return self._device.temperature_step @property - def target_temperature(self): + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - return self._device.target_temperature.value + return self._device.target_temperature.value # type: ignore[no-any-return] @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum temperature.""" - return self._device.target_temperature_min + temp = self._device.target_temperature_min + return temp if temp is not None else super().min_temp @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum temperature.""" - return self._device.target_temperature_max + temp = self._device.target_temperature_max + return temp if temp is not None else super().max_temp - async def async_set_temperature(self, **kwargs) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: @@ -88,11 +105,11 @@ class KNXClimate(KnxEntity, ClimateEntity): self.async_write_ha_state() @property - def hvac_mode(self) -> str | None: + def hvac_mode(self) -> str: """Return current operation ie. heat, cool, idle.""" if self._device.supports_on_off and not self._device.is_on: return HVAC_MODE_OFF - if self._device.mode.supports_controller_mode: + if self._device.mode is not None and self._device.mode.supports_controller_mode: return CONTROLLER_MODES.get( self._device.mode.controller_mode.value, HVAC_MODE_HEAT ) @@ -100,21 +117,23 @@ class KNXClimate(KnxEntity, ClimateEntity): return HVAC_MODE_HEAT @property - def hvac_modes(self) -> list[str] | None: + def hvac_modes(self) -> list[str]: """Return the list of available operation/controller modes.""" - _controller_modes = [ - CONTROLLER_MODES.get(controller_mode.value) - for controller_mode in self._device.mode.controller_modes - ] + ha_controller_modes: list[str | None] = [] + if self._device.mode is not None: + for knx_controller_mode in self._device.mode.controller_modes: + ha_controller_modes.append( + CONTROLLER_MODES.get(knx_controller_mode.value) + ) if self._device.supports_on_off: - if not _controller_modes: - _controller_modes.append(HVAC_MODE_HEAT) - _controller_modes.append(HVAC_MODE_OFF) + if not ha_controller_modes: + ha_controller_modes.append(HVAC_MODE_HEAT) + ha_controller_modes.append(HVAC_MODE_OFF) - _modes = list(set(filter(None, _controller_modes))) + hvac_modes = list(set(filter(None, ha_controller_modes))) # default to ["heat"] - return _modes if _modes else [HVAC_MODE_HEAT] + return hvac_modes if hvac_modes else [HVAC_MODE_HEAT] async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set operation mode.""" @@ -123,7 +142,10 @@ class KNXClimate(KnxEntity, ClimateEntity): else: if self._device.supports_on_off and not self._device.is_on: await self._device.turn_on() - if self._device.mode.supports_controller_mode: + if ( + self._device.mode is not None + and self._device.mode.supports_controller_mode + ): knx_controller_mode = HVACControllerMode( CONTROLLER_MODES_INV.get(hvac_mode) ) @@ -136,7 +158,7 @@ class KNXClimate(KnxEntity, ClimateEntity): Requires SUPPORT_PRESET_MODE. """ - if self._device.mode.supports_operation_mode: + if self._device.mode is not None and self._device.mode.supports_operation_mode: return PRESET_MODES.get(self._device.mode.operation_mode.value, PRESET_AWAY) return None @@ -146,16 +168,18 @@ class KNXClimate(KnxEntity, ClimateEntity): Requires SUPPORT_PRESET_MODE. """ - _presets = [ + if self._device.mode is None: + return None + + presets = [ PRESET_MODES.get(operation_mode.value) for operation_mode in self._device.mode.operation_modes ] - - return list(filter(None, _presets)) + return list(filter(None, presets)) async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - if self._device.mode.supports_operation_mode: + if self._device.mode is not None and self._device.mode.supports_operation_mode: knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode)) await self._device.mode.set_operation_mode(knx_operation_mode) self.async_write_ha_state()