Add typing to deCONZ Climate and Cover platforms (#59610)

This commit is contained in:
Robert Svensson 2021-11-17 15:08:37 +01:00 committed by GitHub
parent edbe54c346
commit 29e0ef604e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 35 deletions

View file

@ -1,6 +1,9 @@
"""Support for deCONZ climate devices.""" """Support for deCONZ climate devices."""
from __future__ import annotations from __future__ import annotations
from collections.abc import ValuesView
from typing import Any
from pydeconz.sensor import ( from pydeconz.sensor import (
THERMOSTAT_FAN_MODE_AUTO, THERMOSTAT_FAN_MODE_AUTO,
THERMOSTAT_FAN_MODE_HIGH, THERMOSTAT_FAN_MODE_HIGH,
@ -42,13 +45,15 @@ from homeassistant.components.climate.const import (
SUPPORT_PRESET_MODE, SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_LOCKED, ATTR_OFFSET, ATTR_VALVE from .const import ATTR_LOCKED, ATTR_OFFSET, ATTR_VALVE
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry from .gateway import DeconzGateway, get_gateway_from_config_entry
DECONZ_FAN_SMART = "smart" DECONZ_FAN_SMART = "smart"
@ -89,7 +94,11 @@ PRESET_MODE_TO_DECONZ = {
DECONZ_TO_PRESET_MODE = {value: key for key, value in PRESET_MODE_TO_DECONZ.items()} DECONZ_TO_PRESET_MODE = {value: key for key, value in PRESET_MODE_TO_DECONZ.items()}
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the deCONZ climate devices. """Set up the deCONZ climate devices.
Thermostats are based on the same device class as sensors in deCONZ. Thermostats are based on the same device class as sensors in deCONZ.
@ -98,9 +107,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
gateway.entities[DOMAIN] = set() gateway.entities[DOMAIN] = set()
@callback @callback
def async_add_climate(sensors=gateway.api.sensors.values()): def async_add_climate(
sensors: list[Thermostat]
| ValuesView[Thermostat] = gateway.api.sensors.values(),
) -> None:
"""Add climate devices from deCONZ.""" """Add climate devices from deCONZ."""
entities = [] entities: list[DeconzThermostat] = []
for sensor in sensors: for sensor in sensors:
@ -131,9 +143,11 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
"""Representation of a deCONZ thermostat.""" """Representation of a deCONZ thermostat."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Thermostat
_attr_temperature_unit = TEMP_CELSIUS _attr_temperature_unit = TEMP_CELSIUS
def __init__(self, device, gateway): def __init__(self, device: Thermostat, gateway: DeconzGateway) -> None:
"""Set up thermostat device.""" """Set up thermostat device."""
super().__init__(device, gateway) super().__init__(device, gateway)
@ -167,7 +181,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
) )
@property @property
def fan_modes(self) -> list: def fan_modes(self) -> list[str]:
"""Return the list of available fan operation modes.""" """Return the list of available fan operation modes."""
return list(FAN_MODE_TO_DECONZ) return list(FAN_MODE_TO_DECONZ)
@ -181,7 +195,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
# HVAC control # HVAC control
@property @property
def hvac_mode(self): def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode. """Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*. Need to be one of HVAC_MODE_*.
@ -192,7 +206,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
) )
@property @property
def hvac_modes(self) -> list: def hvac_modes(self) -> list[str]:
"""Return the list of available hvac operation modes.""" """Return the list of available hvac operation modes."""
return list(self._hvac_mode_to_deconz) return list(self._hvac_mode_to_deconz)
@ -231,16 +245,20 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
@property @property
def current_temperature(self) -> float: def current_temperature(self) -> float:
"""Return the current temperature.""" """Return the current temperature."""
return self._device.temperature return self._device.temperature # type: ignore[no-any-return]
@property @property
def target_temperature(self) -> float: def target_temperature(self) -> float | None:
"""Return the target temperature.""" """Return the target temperature."""
if self._device.mode == THERMOSTAT_MODE_COOL: if self._device.mode == THERMOSTAT_MODE_COOL and self._device.cooling_setpoint:
return self._device.cooling_setpoint return self._device.cooling_setpoint # type: ignore[no-any-return]
return self._device.heating_setpoint
async def async_set_temperature(self, **kwargs): if self._device.heating_setpoint:
return self._device.heating_setpoint # type: ignore[no-any-return]
return None
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
if ATTR_TEMPERATURE not in kwargs: if ATTR_TEMPERATURE not in kwargs:
raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}") raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}")
@ -252,7 +270,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
await self._device.set_config(**data) await self._device.set_config(**data)
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, bool | int]:
"""Return the state attributes of the thermostat.""" """Return the state attributes of the thermostat."""
attr = {} attr = {}

View file

@ -1,5 +1,10 @@
"""Support for deCONZ covers.""" """Support for deCONZ covers."""
from __future__ import annotations
from collections.abc import ValuesView
from typing import Any
from pydeconz.light import Cover from pydeconz.light import Cover
from homeassistant.components.cover import ( from homeassistant.components.cover import (
@ -18,11 +23,13 @@ from homeassistant.components.cover import (
SUPPORT_STOP_TILT, SUPPORT_STOP_TILT,
CoverEntity, CoverEntity,
) )
from homeassistant.core import callback from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry from .gateway import DeconzGateway, get_gateway_from_config_entry
DEVICE_CLASS = { DEVICE_CLASS = {
"Level controllable output": DEVICE_CLASS_DAMPER, "Level controllable output": DEVICE_CLASS_DAMPER,
@ -31,13 +38,19 @@ DEVICE_CLASS = {
} }
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up covers for deCONZ component.""" """Set up covers for deCONZ component."""
gateway = get_gateway_from_config_entry(hass, config_entry) gateway = get_gateway_from_config_entry(hass, config_entry)
gateway.entities[DOMAIN] = set() gateway.entities[DOMAIN] = set()
@callback @callback
def async_add_cover(lights=gateway.api.lights.values()): def async_add_cover(
lights: list[Cover] | ValuesView[Cover] = gateway.api.lights.values(),
) -> None:
"""Add cover from deCONZ.""" """Add cover from deCONZ."""
entities = [] entities = []
@ -66,8 +79,9 @@ class DeconzCover(DeconzDevice, CoverEntity):
"""Representation of a deCONZ cover.""" """Representation of a deCONZ cover."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Cover
def __init__(self, device, gateway): def __init__(self, device: Cover, gateway: DeconzGateway) -> None:
"""Set up cover device.""" """Set up cover device."""
super().__init__(device, gateway) super().__init__(device, gateway)
@ -85,52 +99,52 @@ class DeconzCover(DeconzDevice, CoverEntity):
self._attr_device_class = DEVICE_CLASS.get(self._device.type) self._attr_device_class = DEVICE_CLASS.get(self._device.type)
@property @property
def current_cover_position(self): def current_cover_position(self) -> int:
"""Return the current position of the cover.""" """Return the current position of the cover."""
return 100 - self._device.lift return 100 - self._device.lift # type: ignore[no-any-return]
@property @property
def is_closed(self): def is_closed(self) -> bool:
"""Return if the cover is closed.""" """Return if the cover is closed."""
return not self._device.is_open return not self._device.is_open
async def async_set_cover_position(self, **kwargs): async def async_set_cover_position(self, **kwargs: int) -> None:
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
position = 100 - kwargs[ATTR_POSITION] position = 100 - kwargs[ATTR_POSITION]
await self._device.set_position(lift=position) await self._device.set_position(lift=position)
async def async_open_cover(self, **kwargs): async def async_open_cover(self, **kwargs: Any) -> None:
"""Open cover.""" """Open cover."""
await self._device.open() await self._device.open()
async def async_close_cover(self, **kwargs): async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover.""" """Close cover."""
await self._device.close() await self._device.close()
async def async_stop_cover(self, **kwargs): async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop cover.""" """Stop cover."""
await self._device.stop() await self._device.stop()
@property @property
def current_cover_tilt_position(self): def current_cover_tilt_position(self) -> int | None:
"""Return the current tilt position of the cover.""" """Return the current tilt position of the cover."""
if self._device.tilt is not None: if self._device.tilt is not None:
return 100 - self._device.tilt return 100 - self._device.tilt # type: ignore[no-any-return]
return None return None
async def async_set_cover_tilt_position(self, **kwargs): async def async_set_cover_tilt_position(self, **kwargs: int) -> None:
"""Tilt the cover to a specific position.""" """Tilt the cover to a specific position."""
position = 100 - kwargs[ATTR_TILT_POSITION] position = 100 - kwargs[ATTR_TILT_POSITION]
await self._device.set_position(tilt=position) await self._device.set_position(tilt=position)
async def async_open_cover_tilt(self, **kwargs): async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Open cover tilt.""" """Open cover tilt."""
await self._device.set_position(tilt=0) await self._device.set_position(tilt=0)
async def async_close_cover_tilt(self, **kwargs): async def async_close_cover_tilt(self, **kwargs: Any) -> None:
"""Close cover tilt.""" """Close cover tilt."""
await self._device.set_position(tilt=100) await self._device.set_position(tilt=100)
async def async_stop_cover_tilt(self, **kwargs): async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
"""Stop cover tilt.""" """Stop cover tilt."""
await self._device.stop() await self._device.stop()

View file

@ -347,7 +347,7 @@ async def test_climate_device_with_cooling_support(
"0": { "0": {
"config": { "config": {
"battery": 25, "battery": 25,
"coolsetpoint": None, "coolsetpoint": 1111,
"fanmode": None, "fanmode": None,
"heatsetpoint": 2222, "heatsetpoint": 2222,
"mode": "heat", "mode": "heat",
@ -398,8 +398,10 @@ async def test_climate_device_with_cooling_support(
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done()
assert hass.states.get("climate.zen_01").state == HVAC_MODE_COOL assert hass.states.get("climate.zen_01").state == HVAC_MODE_COOL
assert hass.states.get("climate.zen_01").attributes["temperature"] == 11.1
# Verify service calls # Verify service calls