From 9f5d77e0df957c20a2af574d706140786f0a551a Mon Sep 17 00:00:00 2001 From: Jc2k Date: Tue, 1 Feb 2022 19:30:37 +0000 Subject: [PATCH] Add missing type hints to homekit_controller (#65368) --- .../components/homekit_controller/__init__.py | 33 +++--- .../homekit_controller/alarm_control_panel.py | 26 +++-- .../homekit_controller/binary_sensor.py | 32 +++--- .../components/homekit_controller/button.py | 18 ++-- .../components/homekit_controller/camera.py | 7 +- .../components/homekit_controller/climate.py | 88 ++++++++------- .../homekit_controller/config_flow.py | 10 +- .../homekit_controller/connection.py | 102 ++++++++++-------- .../components/homekit_controller/cover.py | 64 +++++------ .../components/homekit_controller/fan.py | 42 +++++--- .../homekit_controller/humidifier.py | 28 ++--- .../components/homekit_controller/light.py | 24 +++-- .../components/homekit_controller/lock.py | 26 +++-- .../homekit_controller/media_player.py | 32 +++--- .../components/homekit_controller/number.py | 20 ++-- .../components/homekit_controller/select.py | 4 +- .../components/homekit_controller/sensor.py | 56 +++++----- .../components/homekit_controller/storage.py | 41 +++++-- .../components/homekit_controller/switch.py | 48 +++++---- 19 files changed, 389 insertions(+), 312 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index ed65a83a1e7..974cb5e1dfc 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -17,7 +17,7 @@ from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.typing import ConfigType @@ -30,17 +30,12 @@ from .storage import EntityMapStorage _LOGGER = logging.getLogger(__name__) -def escape_characteristic_name(char_name): - """Escape any dash or dots in a characteristics name.""" - return char_name.replace("-", "_").replace(".", "_") - - class HomeKitEntity(Entity): """Representation of a Home Assistant HomeKit device.""" _attr_should_poll = False - def __init__(self, accessory: HKDevice, devinfo): + def __init__(self, accessory: HKDevice, devinfo: ConfigType) -> None: """Initialise a generic HomeKit device.""" self._accessory = accessory self._aid = devinfo["aid"] @@ -67,7 +62,7 @@ class HomeKitEntity(Entity): """Return a Service model that this entity is attached to.""" return self.accessory.services.iid(self._iid) - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Entity added to hass.""" self.async_on_remove( self.hass.helpers.dispatcher.async_dispatcher_connect( @@ -78,12 +73,12 @@ class HomeKitEntity(Entity): self._accessory.add_pollable_characteristics(self.pollable_characteristics) self._accessory.add_watchable_characteristics(self.watchable_characteristics) - async def async_will_remove_from_hass(self): + async def async_will_remove_from_hass(self) -> None: """Prepare to be removed from hass.""" self._accessory.remove_pollable_characteristics(self._aid) self._accessory.remove_watchable_characteristics(self._aid) - async def async_put_characteristics(self, characteristics: dict[str, Any]): + async def async_put_characteristics(self, characteristics: dict[str, Any]) -> None: """ Write characteristics to the device. @@ -101,10 +96,10 @@ class HomeKitEntity(Entity): payload = self.service.build_update(characteristics) return await self._accessory.put_characteristics(payload) - def setup(self): - """Configure an entity baed on its HomeKit characteristics metadata.""" - self.pollable_characteristics = [] - self.watchable_characteristics = [] + def setup(self) -> None: + """Configure an entity based on its HomeKit characteristics metadata.""" + self.pollable_characteristics: list[tuple[int, int]] = [] + self.watchable_characteristics: list[tuple[int, int]] = [] char_types = self.get_characteristic_types() @@ -118,7 +113,7 @@ class HomeKitEntity(Entity): for char in service.characteristics.filter(char_types=char_types): self._setup_characteristic(char) - def _setup_characteristic(self, char: Characteristic): + def _setup_characteristic(self, char: Characteristic) -> None: """Configure an entity based on a HomeKit characteristics metadata.""" # Build up a list of (aid, iid) tuples to poll on update() if CharacteristicPermissions.paired_read in char.perms: @@ -153,7 +148,7 @@ class HomeKitEntity(Entity): """Return the device info.""" return self._accessory.device_info_for_accessory(self.accessory) - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" raise NotImplementedError @@ -176,7 +171,9 @@ class CharacteristicEntity(HomeKitEntity): the service entity. """ - def __init__(self, accessory, devinfo, char): + def __init__( + self, accessory: HKDevice, devinfo: ConfigType, char: Characteristic + ) -> None: """Initialise a generic single characteristic HomeKit entity.""" self._char = char super().__init__(accessory, devinfo) @@ -218,7 +215,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.data[KNOWN_DEVICES] = {} hass.data[TRIGGERS] = {} - async def _async_stop_homekit_controller(event): + async def _async_stop_homekit_controller(event: Event) -> None: await asyncio.gather( *( connection.async_unload() diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index e7d9bf11c32..0194036db4e 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -1,6 +1,10 @@ """Support for Homekit Alarm Control Panel.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import ( @@ -50,7 +54,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.SECURITY_SYSTEM: return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -63,7 +67,7 @@ async def async_setup_entry( class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): """Representation of a Homekit Alarm Control Panel.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT, @@ -72,12 +76,12 @@ class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): ] @property - def icon(self): + def icon(self) -> str: """Return icon.""" return ICON @property - def state(self): + def state(self) -> str: """Return the state of the device.""" return CURRENT_STATE_MAP[ self.service.value(CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT) @@ -88,30 +92,30 @@ class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): """Return the list of supported features.""" return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT - async def async_alarm_disarm(self, code=None): + async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" await self.set_alarm_state(STATE_ALARM_DISARMED, code) - async def async_alarm_arm_away(self, code=None): + async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm command.""" await self.set_alarm_state(STATE_ALARM_ARMED_AWAY, code) - async def async_alarm_arm_home(self, code=None): + async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send stay command.""" await self.set_alarm_state(STATE_ALARM_ARMED_HOME, code) - async def async_alarm_arm_night(self, code=None): + async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send night command.""" await self.set_alarm_state(STATE_ALARM_ARMED_NIGHT, code) - async def set_alarm_state(self, state, code=None): + async def set_alarm_state(self, state: str, code: str | None = None) -> None: """Send state command.""" await self.async_put_characteristics( {CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: TARGET_STATE_MAP[state]} ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the optional state attributes.""" battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL) diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index 09c42bf0547..91c1c47d47e 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -1,6 +1,8 @@ """Support for Homekit motion sensors.""" +from __future__ import annotations + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -18,14 +20,14 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.MOTION - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.MOTION_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Has motion been detected.""" - return self.service.value(CharacteristicsTypes.MOTION_DETECTED) + return self.service.value(CharacteristicsTypes.MOTION_DETECTED) is True class HomeKitContactSensor(HomeKitEntity, BinarySensorEntity): @@ -33,12 +35,12 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.OPENING - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.CONTACT_STATE] @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on/open.""" return self.service.value(CharacteristicsTypes.CONTACT_STATE) == 1 @@ -48,12 +50,12 @@ class HomeKitSmokeSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.SMOKE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.SMOKE_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Return true if smoke is currently detected.""" return self.service.value(CharacteristicsTypes.SMOKE_DETECTED) == 1 @@ -63,12 +65,12 @@ class HomeKitCarbonMonoxideSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.GAS - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.CARBON_MONOXIDE_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Return true if CO is currently detected.""" return self.service.value(CharacteristicsTypes.CARBON_MONOXIDE_DETECTED) == 1 @@ -78,12 +80,12 @@ class HomeKitOccupancySensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.OCCUPANCY - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.OCCUPANCY_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Return true if occupancy is currently detected.""" return self.service.value(CharacteristicsTypes.OCCUPANCY_DETECTED) == 1 @@ -93,12 +95,12 @@ class HomeKitLeakSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.MOISTURE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.LEAK_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Return true if a leak is detected from the binary sensor.""" return self.service.value(CharacteristicsTypes.LEAK_DETECTED) == 1 @@ -123,7 +125,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} diff --git a/homeassistant/components/homekit_controller/button.py b/homeassistant/components/homekit_controller/button.py index 8c8b1e616c9..7d2c737b509 100644 --- a/homeassistant/components/homekit_controller/button.py +++ b/homeassistant/components/homekit_controller/button.py @@ -19,8 +19,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType from . import KNOWN_DEVICES, CharacteristicEntity +from .connection import HKDevice @dataclass @@ -64,7 +66,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: entities = [] info = {"aid": char.service.accessory.aid, "iid": char.service.iid} @@ -88,16 +90,16 @@ class HomeKitButton(CharacteristicEntity, ButtonEntity): def __init__( self, - conn, - info, - char, + conn: HKDevice, + info: ConfigType, + char: Characteristic, description: HomeKitButtonEntityDescription, - ): + ) -> None: """Initialise a HomeKit button control.""" self.entity_description = description super().__init__(conn, info, char) - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [self._char.type] @@ -112,13 +114,13 @@ class HomeKitButton(CharacteristicEntity, ButtonEntity): """Press the button.""" key = self.entity_description.key val = self.entity_description.write_value - return await self.async_put_characteristics({key: val}) + await self.async_put_characteristics({key: val}) class HomeKitEcobeeClearHoldButton(CharacteristicEntity, ButtonEntity): """Representation of a Button control for Ecobee clear hold request.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [] diff --git a/homeassistant/components/homekit_controller/camera.py b/homeassistant/components/homekit_controller/camera.py index 63b06cce338..0ffa0a22f4d 100644 --- a/homeassistant/components/homekit_controller/camera.py +++ b/homeassistant/components/homekit_controller/camera.py @@ -1,6 +1,7 @@ """Support for Homekit cameras.""" from __future__ import annotations +from aiohomekit.model import Accessory from aiohomekit.model.services import ServicesTypes from homeassistant.components.camera import Camera @@ -16,7 +17,7 @@ class HomeKitCamera(AccessoryEntity, Camera): # content_type = "image/jpeg" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [] @@ -41,12 +42,12 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_accessory(accessory): + def async_add_accessory(accessory: Accessory) -> bool: stream_mgmt = accessory.services.first( service_type=ServicesTypes.CAMERA_RTP_STREAM_MANAGEMENT ) if not stream_mgmt: - return + return False info = {"aid": accessory.aid, "iid": stream_mgmt.iid} async_add_entities([HomeKitCamera(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index aa807f3e901..e8179b0bc6b 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -1,5 +1,8 @@ """Support for Homekit climate devices.""" +from __future__ import annotations + import logging +from typing import Any from aiohomekit.model.characteristics import ( ActivationStateValues, @@ -10,7 +13,7 @@ from aiohomekit.model.characteristics import ( SwingModeValues, TargetHeaterCoolerStateValues, ) -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.utils import clamp_enum_to_char from homeassistant.components.climate import ClimateEntity @@ -94,7 +97,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -107,7 +110,7 @@ async def async_setup_entry( class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): """Representation of a Homekit climate device.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -119,7 +122,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): CharacteristicsTypes.TEMPERATURE_CURRENT, ] - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) @@ -140,7 +143,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): hvac_mode, ) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target operation mode.""" if hvac_mode == HVAC_MODE_OFF: await self.async_put_characteristics( @@ -163,12 +166,12 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): ) @property - def current_temperature(self): + def current_temperature(self) -> float: """Return the current temperature.""" return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) @property - def target_temperature(self): + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL: @@ -182,7 +185,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return None @property - def target_temperature_step(self): + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL and self.service.has( @@ -200,7 +203,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return None @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum target temp.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL and self.service.has( @@ -218,7 +221,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return super().min_temp @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum target temp.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL and self.service.has( @@ -236,7 +239,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return super().max_temp @property - def hvac_action(self): + def hvac_action(self) -> str | None: """Return the current running hvac operation.""" # This characteristic describes the current mode of a device, # e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit. @@ -250,7 +253,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return CURRENT_HEATER_COOLER_STATE_HOMEKIT_TO_HASS.get(value) @property - def hvac_mode(self): + def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" # This characteristic describes the target mode # E.g. should the device start heating a room if the temperature @@ -262,10 +265,10 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): ): return HVAC_MODE_OFF value = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) - return TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS.get(value) + return TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS[value] @property - def hvac_modes(self): + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" valid_values = clamp_enum_to_char( TargetHeaterCoolerStateValues, @@ -278,7 +281,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return modes @property - def swing_mode(self): + def swing_mode(self) -> str: """Return the swing setting. Requires SUPPORT_SWING_MODE. @@ -287,7 +290,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return SWING_MODE_HOMEKIT_TO_HASS[value] @property - def swing_modes(self): + def swing_modes(self) -> list[str]: """Return the list of available swing modes. Requires SUPPORT_SWING_MODE. @@ -305,7 +308,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): ) @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" features = 0 @@ -321,7 +324,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return features @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" return TEMP_CELSIUS @@ -329,7 +332,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): """Representation of a Homekit climate device.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.HEATING_COOLING_CURRENT, @@ -342,12 +345,12 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET, ] - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" chars = {} value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - mode = MODE_HOMEKIT_TO_HASS.get(value) + mode = MODE_HOMEKIT_TO_HASS[value] if kwargs.get(ATTR_HVAC_MODE, mode) != mode: mode = kwargs[ATTR_HVAC_MODE] @@ -359,8 +362,11 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) - if (mode == HVAC_MODE_HEAT_COOL) and ( - SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + if ( + (mode == HVAC_MODE_HEAT_COOL) + and (SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features) + and heat_temp + and cool_temp ): if temp is None: temp = (cool_temp + heat_temp) / 2 @@ -376,13 +382,13 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): await self.async_put_characteristics(chars) - async def async_set_humidity(self, humidity): + async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" await self.async_put_characteristics( {CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: humidity} ) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target operation mode.""" await self.async_put_characteristics( { @@ -393,12 +399,12 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): ) @property - def current_temperature(self): + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) @property - def target_temperature(self): + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT, HVAC_MODE_COOL}) or ( @@ -409,7 +415,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return None @property - def target_temperature_high(self): + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( @@ -421,7 +427,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return None @property - def target_temperature_low(self): + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( @@ -433,7 +439,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return None @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum target temp.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( @@ -455,7 +461,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return super().min_temp @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum target temp.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( @@ -477,17 +483,17 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return super().max_temp @property - def current_humidity(self): + def current_humidity(self) -> int: """Return the current humidity.""" return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) @property - def target_humidity(self): + def target_humidity(self) -> int: """Return the humidity we try to reach.""" return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET) @property - def min_humidity(self): + def min_humidity(self) -> int: """Return the minimum humidity.""" min_humidity = self.service[ CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET @@ -497,7 +503,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return super().min_humidity @property - def max_humidity(self): + def max_humidity(self) -> int: """Return the maximum humidity.""" max_humidity = self.service[ CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET @@ -507,7 +513,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return super().max_humidity @property - def hvac_action(self): + def hvac_action(self) -> str | None: """Return the current running hvac operation.""" # This characteristic describes the current mode of a device, # e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit. @@ -516,17 +522,17 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return CURRENT_MODE_HOMEKIT_TO_HASS.get(value) @property - def hvac_mode(self): + def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" # This characteristic describes the target mode # E.g. should the device start heating a room if the temperature # falls below the target temperature. # Can be 0 - 3 (Off, Heat, Cool, Auto) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - return MODE_HOMEKIT_TO_HASS.get(value) + return MODE_HOMEKIT_TO_HASS[value] @property - def hvac_modes(self): + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" valid_values = clamp_enum_to_char( HeatingCoolingTargetValues, @@ -535,7 +541,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return [MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values] @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" features = 0 @@ -553,7 +559,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return features @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" return TEMP_CELSIUS diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 6a2200b69eb..54d265a5f4a 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -1,6 +1,9 @@ """Config flow to configure homekit_controller.""" +from __future__ import annotations + import logging import re +from typing import Any import aiohomekit from aiohomekit.exceptions import AuthenticationError @@ -55,20 +58,21 @@ INSECURE_CODES = { } -def normalize_hkid(hkid): +def normalize_hkid(hkid: str) -> str: """Normalize a hkid so that it is safe to compare with other normalized hkids.""" return hkid.lower() @callback -def find_existing_host(hass, serial): +def find_existing_host(hass, serial: str) -> config_entries.ConfigEntry | None: """Return a set of the configured hosts.""" for entry in hass.config_entries.async_entries(DOMAIN): if entry.data.get("AccessoryPairingID") == serial: return entry + return None -def ensure_pin_format(pin, allow_insecure_setup_codes=None): +def ensure_pin_format(pin: str, allow_insecure_setup_codes: Any = None) -> str: """ Ensure a pin code is correctly formatted. diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 7b5f7114170..8bd5351906c 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -1,7 +1,11 @@ """Helpers for managing a pairing with a HomeKit accessory or bridge.""" +from __future__ import annotations + import asyncio +from collections.abc import Callable import datetime import logging +from typing import Any from aiohomekit.exceptions import ( AccessoryDisconnectedError, @@ -9,11 +13,11 @@ from aiohomekit.exceptions import ( EncryptionError, ) from aiohomekit.model import Accessories, Accessory -from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.const import ATTR_VIA_DEVICE -from homeassistant.core import callback +from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.event import async_track_time_interval @@ -37,6 +41,10 @@ MAX_POLL_FAILURES_TO_DECLARE_UNAVAILABLE = 3 _LOGGER = logging.getLogger(__name__) +AddAccessoryCb = Callable[[Accessory], bool] +AddServiceCb = Callable[[Service], bool] +AddCharacteristicCb = Callable[[Characteristic], bool] + def valid_serial_number(serial): """Return if the serial number appears to be valid.""" @@ -51,7 +59,7 @@ def valid_serial_number(serial): class HKDevice: """HomeKit device.""" - def __init__(self, hass, config_entry, pairing_data): + def __init__(self, hass, config_entry, pairing_data) -> None: """Initialise a generic HomeKit device.""" self.hass = hass @@ -71,28 +79,28 @@ class HKDevice: self.entity_map = Accessories() # A list of callbacks that turn HK accessories into entities - self.accessory_factories = [] + self.accessory_factories: list[AddAccessoryCb] = [] # A list of callbacks that turn HK service metadata into entities - self.listeners = [] + self.listeners: list[AddServiceCb] = [] # A list of callbacks that turn HK characteristics into entities - self.char_factories = [] + self.char_factories: list[AddCharacteristicCb] = [] # The platorms we have forwarded the config entry so far. If a new # accessory is added to a bridge we may have to load additional # platforms. We don't want to load all platforms up front if its just # a lightbulb. And we don't want to forward a config entry twice # (triggers a Config entry already set up error) - self.platforms = set() + self.platforms: set[str] = set() # This just tracks aid/iid pairs so we know if a HK service has been # mapped to a HA entity. - self.entities = [] + self.entities: list[tuple[int, int | None, int | None]] = [] # A map of aid -> device_id # Useful when routing events to triggers - self.devices = {} + self.devices: dict[int, str] = {} self.available = False @@ -100,13 +108,13 @@ class HKDevice: # Current values of all characteristics homekit_controller is tracking. # Key is a (accessory_id, characteristic_id) tuple. - self.current_state = {} + self.current_state: dict[tuple[int, int], Any] = {} - self.pollable_characteristics = [] + self.pollable_characteristics: list[tuple[int, int]] = [] # If this is set polling is active and can be disabled by calling # this method. - self._polling_interval_remover = None + self._polling_interval_remover: CALLBACK_TYPE | None = None # Never allow concurrent polling of the same accessory or bridge self._polling_lock = asyncio.Lock() @@ -116,33 +124,37 @@ class HKDevice: # This is set to True if we can't rely on serial numbers to be unique self.unreliable_serial_numbers = False - self.watchable_characteristics = [] + self.watchable_characteristics: list[tuple[int, int]] = [] self.pairing.dispatcher_connect(self.process_new_events) - def add_pollable_characteristics(self, characteristics): + def add_pollable_characteristics( + self, characteristics: list[tuple[int, int]] + ) -> None: """Add (aid, iid) pairs that we need to poll.""" self.pollable_characteristics.extend(characteristics) - def remove_pollable_characteristics(self, accessory_id): + def remove_pollable_characteristics(self, accessory_id: int) -> None: """Remove all pollable characteristics by accessory id.""" self.pollable_characteristics = [ char for char in self.pollable_characteristics if char[0] != accessory_id ] - def add_watchable_characteristics(self, characteristics): + def add_watchable_characteristics( + self, characteristics: list[tuple[int, int]] + ) -> None: """Add (aid, iid) pairs that we need to poll.""" self.watchable_characteristics.extend(characteristics) self.hass.async_create_task(self.pairing.subscribe(characteristics)) - def remove_watchable_characteristics(self, accessory_id): + def remove_watchable_characteristics(self, accessory_id: int) -> None: """Remove all pollable characteristics by accessory id.""" self.watchable_characteristics = [ char for char in self.watchable_characteristics if char[0] != accessory_id ] @callback - def async_set_available_state(self, available): + def async_set_available_state(self, available: bool) -> None: """Mark state of all entities on this connection when it becomes available or unavailable.""" _LOGGER.debug( "Called async_set_available_state with %s for %s", available, self.unique_id @@ -152,7 +164,7 @@ class HKDevice: self.available = available self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) - async def async_setup(self): + async def async_setup(self) -> bool: """Prepare to use a paired HomeKit device in Home Assistant.""" cache = self.hass.data[ENTITY_MAP].get_map(self.unique_id) if not cache: @@ -214,7 +226,7 @@ class HKDevice: return device_info @callback - def async_migrate_devices(self): + def async_migrate_devices(self) -> None: """Migrate legacy device entries from 3-tuples to 2-tuples.""" _LOGGER.debug( "Migrating device registry entries for pairing %s", self.unique_id @@ -246,7 +258,7 @@ class HKDevice: (DOMAIN, IDENTIFIER_LEGACY_SERIAL_NUMBER, serial_number) ) - device = device_registry.async_get_device(identifiers=identifiers) + device = device_registry.async_get_device(identifiers=identifiers) # type: ignore[arg-type] if not device: continue @@ -286,7 +298,7 @@ class HKDevice: ) @callback - def async_create_devices(self): + def async_create_devices(self) -> None: """ Build device registry entries for all accessories paired with the bridge. @@ -315,7 +327,7 @@ class HKDevice: self.devices = devices @callback - def async_detect_workarounds(self): + def async_detect_workarounds(self) -> None: """Detect any workarounds that are needed for this pairing.""" unreliable_serial_numbers = False @@ -354,7 +366,7 @@ class HKDevice: self.unreliable_serial_numbers = unreliable_serial_numbers - async def async_process_entity_map(self): + async def async_process_entity_map(self) -> None: """ Process the entity map and load any platforms or entities that need adding. @@ -388,18 +400,18 @@ class HKDevice: await self.async_update() - async def async_unload(self): + async def async_unload(self) -> None: """Stop interacting with device and prepare for removal from hass.""" if self._polling_interval_remover: self._polling_interval_remover() await self.pairing.close() - return await self.hass.config_entries.async_unload_platforms( + await self.hass.config_entries.async_unload_platforms( self.config_entry, self.platforms ) - async def async_refresh_entity_map(self, config_num): + async def async_refresh_entity_map(self, config_num: int) -> bool: """Handle setup of a HomeKit accessory.""" try: self.accessories = await self.pairing.list_accessories_and_characteristics() @@ -419,26 +431,26 @@ class HKDevice: return True - def add_accessory_factory(self, add_entities_cb): + def add_accessory_factory(self, add_entities_cb) -> None: """Add a callback to run when discovering new entities for accessories.""" self.accessory_factories.append(add_entities_cb) self._add_new_entities_for_accessory([add_entities_cb]) - def _add_new_entities_for_accessory(self, handlers): + def _add_new_entities_for_accessory(self, handlers) -> None: for accessory in self.entity_map.accessories: for handler in handlers: - if (accessory.aid, None) in self.entities: + if (accessory.aid, None, None) in self.entities: continue if handler(accessory): - self.entities.append((accessory.aid, None)) + self.entities.append((accessory.aid, None, None)) break - def add_char_factory(self, add_entities_cb): + def add_char_factory(self, add_entities_cb) -> None: """Add a callback to run when discovering new entities for accessories.""" self.char_factories.append(add_entities_cb) self._add_new_entities_for_char([add_entities_cb]) - def _add_new_entities_for_char(self, handlers): + def _add_new_entities_for_char(self, handlers) -> None: for accessory in self.entity_map.accessories: for service in accessory.services: for char in service.characteristics: @@ -449,33 +461,33 @@ class HKDevice: self.entities.append((accessory.aid, service.iid, char.iid)) break - def add_listener(self, add_entities_cb): + def add_listener(self, add_entities_cb) -> None: """Add a callback to run when discovering new entities for services.""" self.listeners.append(add_entities_cb) self._add_new_entities([add_entities_cb]) - def add_entities(self): + def add_entities(self) -> None: """Process the entity map and create HA entities.""" self._add_new_entities(self.listeners) self._add_new_entities_for_accessory(self.accessory_factories) self._add_new_entities_for_char(self.char_factories) - def _add_new_entities(self, callbacks): + def _add_new_entities(self, callbacks) -> None: for accessory in self.entity_map.accessories: aid = accessory.aid for service in accessory.services: iid = service.iid - if (aid, iid) in self.entities: + if (aid, None, iid) in self.entities: # Don't add the same entity again continue for listener in callbacks: if listener(service): - self.entities.append((aid, iid)) + self.entities.append((aid, None, iid)) break - async def async_load_platform(self, platform): + async def async_load_platform(self, platform: str) -> None: """Load a single platform idempotently.""" if platform in self.platforms: return @@ -489,7 +501,7 @@ class HKDevice: self.platforms.remove(platform) raise - async def async_load_platforms(self): + async def async_load_platforms(self) -> None: """Load any platforms needed by this HomeKit device.""" tasks = [] for accessory in self.entity_map.accessories: @@ -558,7 +570,7 @@ class HKDevice: _LOGGER.debug("Finished HomeKit controller update: %s", self.unique_id) - def process_new_events(self, new_values_dict): + def process_new_events(self, new_values_dict) -> None: """Process events from accessory into HA state.""" self.async_set_available_state(True) @@ -575,11 +587,11 @@ class HKDevice: self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) - async def get_characteristics(self, *args, **kwargs): + async def get_characteristics(self, *args, **kwargs) -> dict[str, Any]: """Read latest state from homekit accessory.""" return await self.pairing.get_characteristics(*args, **kwargs) - async def put_characteristics(self, characteristics): + async def put_characteristics(self, characteristics) -> None: """Control a HomeKit device state from Home Assistant.""" results = await self.pairing.put_characteristics(characteristics) @@ -604,7 +616,7 @@ class HKDevice: self.process_new_events(new_entity_state) @property - def unique_id(self): + def unique_id(self) -> str: """ Return a unique id for this accessory or bridge. diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index bb6bab4a495..a1aa8dbd807 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -1,10 +1,15 @@ """Support for Homekit covers.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, + DEVICE_CLASS_GARAGE, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, SUPPORT_OPEN, @@ -46,7 +51,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -59,12 +64,9 @@ async def async_setup_entry( class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): """Representation of a HomeKit Garage Door.""" - @property - def device_class(self): - """Define this cover as a garage door.""" - return "garage" + _attr_device_class = DEVICE_CLASS_GARAGE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.DOOR_STATE_CURRENT, @@ -73,47 +75,47 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): ] @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE @property - def _state(self): + def _state(self) -> str: """Return the current state of the garage door.""" value = self.service.value(CharacteristicsTypes.DOOR_STATE_CURRENT) return CURRENT_GARAGE_STATE_MAP[value] @property - def is_closed(self): + def is_closed(self) -> bool: """Return true if cover is closed, else False.""" return self._state == STATE_CLOSED @property - def is_closing(self): + def is_closing(self) -> bool: """Return if the cover is closing or not.""" return self._state == STATE_CLOSING @property - def is_opening(self): + def is_opening(self) -> bool: """Return if the cover is opening or not.""" return self._state == STATE_OPENING - async def async_open_cover(self, **kwargs): + async def async_open_cover(self, **kwargs: Any) -> None: """Send open command.""" await self.set_door_state(STATE_OPEN) - async def async_close_cover(self, **kwargs): + async def async_close_cover(self, **kwargs: Any) -> None: """Send close command.""" await self.set_door_state(STATE_CLOSED) - async def set_door_state(self, state): + async def set_door_state(self, state: str) -> None: """Send state command.""" await self.async_put_characteristics( {CharacteristicsTypes.DOOR_STATE_TARGET: TARGET_GARAGE_STATE_MAP[state]} ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED @@ -124,7 +126,7 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): class HomeKitWindowCover(HomeKitEntity, CoverEntity): """Representation of a HomeKit Window or Window Covering.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.POSITION_STATE, @@ -139,7 +141,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): ] @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION @@ -161,45 +163,45 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): return features @property - def current_cover_position(self): + def current_cover_position(self) -> int: """Return the current position of cover.""" return self.service.value(CharacteristicsTypes.POSITION_CURRENT) @property - def is_closed(self): + def is_closed(self) -> bool: """Return true if cover is closed, else False.""" return self.current_cover_position == 0 @property - def is_closing(self): + def is_closing(self) -> bool: """Return if the cover is closing or not.""" value = self.service.value(CharacteristicsTypes.POSITION_STATE) state = CURRENT_WINDOW_STATE_MAP[value] return state == STATE_CLOSING @property - def is_opening(self): + def is_opening(self) -> bool: """Return if the cover is opening or not.""" value = self.service.value(CharacteristicsTypes.POSITION_STATE) state = CURRENT_WINDOW_STATE_MAP[value] return state == STATE_OPENING @property - def is_horizontal_tilt(self): + def is_horizontal_tilt(self) -> bool: """Return True if the service has a horizontal tilt characteristic.""" return ( self.service.value(CharacteristicsTypes.HORIZONTAL_TILT_CURRENT) is not None ) @property - def is_vertical_tilt(self): + def is_vertical_tilt(self) -> bool: """Return True if the service has a vertical tilt characteristic.""" return ( self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) is not None ) @property - def current_cover_tilt_position(self): + def current_cover_tilt_position(self) -> int: """Return current position of cover tilt.""" tilt_position = self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) if not tilt_position: @@ -208,26 +210,26 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): ) return tilt_position - async def async_stop_cover(self, **kwargs): + async def async_stop_cover(self, **kwargs: Any) -> None: """Send hold command.""" await self.async_put_characteristics({CharacteristicsTypes.POSITION_HOLD: 1}) - async def async_open_cover(self, **kwargs): + async def async_open_cover(self, **kwargs: Any) -> None: """Send open command.""" await self.async_set_cover_position(position=100) - async def async_close_cover(self, **kwargs): + async def async_close_cover(self, **kwargs: Any) -> None: """Send close command.""" await self.async_set_cover_position(position=0) - async def async_set_cover_position(self, **kwargs): + async def async_set_cover_position(self, **kwargs: Any) -> None: """Send position command.""" position = kwargs[ATTR_POSITION] await self.async_put_characteristics( {CharacteristicsTypes.POSITION_TARGET: position} ) - async def async_set_cover_tilt_position(self, **kwargs): + async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" tilt_position = kwargs[ATTR_TILT_POSITION] if self.is_vertical_tilt: @@ -240,7 +242,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index 0b6f03b18e3..71d71ce469f 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -1,6 +1,10 @@ """Support for Homekit fans.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.fan import ( DIRECTION_FORWARD, @@ -30,9 +34,9 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): # This must be set in subclasses to the name of a boolean characteristic # that controls whether the fan is on or off. - on_characteristic = None + on_characteristic: str - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.SWING_MODE, @@ -42,12 +46,12 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): ] @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(self.on_characteristic) == 1 @property - def percentage(self): + def percentage(self) -> int: """Return the current speed percentage.""" if not self.is_on: return 0 @@ -55,19 +59,19 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): return self.service.value(CharacteristicsTypes.ROTATION_SPEED) @property - def current_direction(self): + def current_direction(self) -> str: """Return the current direction of the fan.""" direction = self.service.value(CharacteristicsTypes.ROTATION_DIRECTION) return HK_DIRECTION_TO_HA[direction] @property - def oscillating(self): + def oscillating(self) -> bool: """Return whether or not the fan is currently oscillating.""" oscillating = self.service.value(CharacteristicsTypes.SWING_MODE) return oscillating == 1 @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" features = 0 @@ -83,20 +87,20 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): return features @property - def speed_count(self): + def speed_count(self) -> int: """Speed count for the fan.""" return round( min(self.service[CharacteristicsTypes.ROTATION_SPEED].maxValue or 100, 100) / max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0) ) - async def async_set_direction(self, direction): + async def async_set_direction(self, direction: str) -> None: """Set the direction of the fan.""" await self.async_put_characteristics( {CharacteristicsTypes.ROTATION_DIRECTION: DIRECTION_TO_HK[direction]} ) - async def async_set_percentage(self, percentage): + async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan.""" if percentage == 0: return await self.async_turn_off() @@ -105,17 +109,21 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): {CharacteristicsTypes.ROTATION_SPEED: percentage} ) - async def async_oscillate(self, oscillating: bool): + async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" await self.async_put_characteristics( {CharacteristicsTypes.SWING_MODE: 1 if oscillating else 0} ) async def async_turn_on( - self, speed=None, percentage=None, preset_mode=None, **kwargs - ): + self, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, + ) -> None: """Turn the specified fan on.""" - characteristics = {} + characteristics: dict[str, Any] = {} if not self.is_on: characteristics[self.on_characteristic] = True @@ -126,7 +134,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): if characteristics: await self.async_put_characteristics(characteristics) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified fan off.""" await self.async_put_characteristics({self.on_characteristic: False}) @@ -159,7 +167,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index bb46c5b2dec..bc922dd4ec1 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -1,8 +1,10 @@ """Support for HomeKit Controller humidifier.""" from __future__ import annotations +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.humidifier import HumidifierDeviceClass, HumidifierEntity from homeassistant.components.humidifier.const import ( @@ -37,7 +39,7 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): _attr_device_class = HumidifierDeviceClass.HUMIDIFIER - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -47,20 +49,20 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): ] @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" return SUPPORT_FLAGS | SUPPORT_MODES @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ACTIVE) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified valve on.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified valve off.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @@ -138,7 +140,7 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): _attr_device_class = HumidifierDeviceClass.DEHUMIDIFIER - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -149,20 +151,20 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): ] @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" return SUPPORT_FLAGS | SUPPORT_MODES @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ACTIVE) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified valve on.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified valve off.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @@ -251,13 +253,13 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.HUMIDIFIER_DEHUMIDIFIER: return False info = {"aid": service.accessory.aid, "iid": service.iid} - entities = [] + entities: list[HumidifierEntity] = [] if service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD): entities.append(HomeKitHumidifier(conn, info)) diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index 2e2d60750d8..2b82f27022b 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -1,6 +1,10 @@ """Support for Homekit lights.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -28,7 +32,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.LIGHTBULB: return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -41,7 +45,7 @@ async def async_setup_entry( class HomeKitLight(HomeKitEntity, LightEntity): """Representation of a Homekit light.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ON, @@ -52,17 +56,17 @@ class HomeKitLight(HomeKitEntity, LightEntity): ] @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ON) @property - def brightness(self): + def brightness(self) -> int: """Return the brightness of this light between 0..255.""" return self.service.value(CharacteristicsTypes.BRIGHTNESS) * 255 / 100 @property - def hs_color(self): + def hs_color(self) -> tuple[float, float]: """Return the color property.""" return ( self.service.value(CharacteristicsTypes.HUE), @@ -70,12 +74,12 @@ class HomeKitLight(HomeKitEntity, LightEntity): ) @property - def color_temp(self): + def color_temp(self) -> int: """Return the color temperature.""" return self.service.value(CharacteristicsTypes.COLOR_TEMPERATURE) @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" features = 0 @@ -93,7 +97,7 @@ class HomeKitLight(HomeKitEntity, LightEntity): return features - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified light on.""" hs_color = kwargs.get(ATTR_HS_COLOR) temperature = kwargs.get(ATTR_COLOR_TEMP) @@ -121,6 +125,6 @@ class HomeKitLight(HomeKitEntity, LightEntity): await self.async_put_characteristics(characteristics) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified light off.""" await self.async_put_characteristics({CharacteristicsTypes.ON: False}) diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index a3248e3fa02..248bb93a68f 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -1,6 +1,10 @@ """Support for HomeKit Controller locks.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.lock import STATE_JAMMED, LockEntity from homeassistant.config_entries import ConfigEntry @@ -37,7 +41,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.LOCK_MECHANISM: return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -50,7 +54,7 @@ async def async_setup_entry( class HomeKitLock(HomeKitEntity, LockEntity): """Representation of a HomeKit Controller Lock.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE, @@ -59,7 +63,7 @@ class HomeKitLock(HomeKitEntity, LockEntity): ] @property - def is_locked(self): + def is_locked(self) -> bool | None: """Return true if device is locked.""" value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE) if CURRENT_STATE_MAP[value] == STATE_UNKNOWN: @@ -67,7 +71,7 @@ class HomeKitLock(HomeKitEntity, LockEntity): return CURRENT_STATE_MAP[value] == STATE_LOCKED @property - def is_locking(self): + def is_locking(self) -> bool: """Return true if device is locking.""" current_value = self.service.value( CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE @@ -81,7 +85,7 @@ class HomeKitLock(HomeKitEntity, LockEntity): ) @property - def is_unlocking(self): + def is_unlocking(self) -> bool: """Return true if device is unlocking.""" current_value = self.service.value( CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE @@ -95,27 +99,27 @@ class HomeKitLock(HomeKitEntity, LockEntity): ) @property - def is_jammed(self): + def is_jammed(self) -> bool: """Return true if device is jammed.""" value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE) return CURRENT_STATE_MAP[value] == STATE_JAMMED - async def async_lock(self, **kwargs): + async def async_lock(self, **kwargs: Any) -> None: """Lock the device.""" await self._set_lock_state(STATE_LOCKED) - async def async_unlock(self, **kwargs): + async def async_unlock(self, **kwargs: Any) -> None: """Unlock the device.""" await self._set_lock_state(STATE_UNLOCKED) - async def _set_lock_state(self, state): + async def _set_lock_state(self, state: str) -> None: """Send state command.""" await self.async_put_characteristics( {CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: TARGET_STATE_MAP[state]} ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" attributes = {} diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index dda763c4ae5..7318343b643 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -1,4 +1,6 @@ """Support for HomeKit Controller Televisions.""" +from __future__ import annotations + import logging from aiohomekit.model.characteristics import ( @@ -7,7 +9,7 @@ from aiohomekit.model.characteristics import ( RemoteKeyValues, TargetMediaStateValues, ) -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.utils import clamp_enum_to_char from homeassistant.components.media_player import ( @@ -53,7 +55,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.TELEVISION: return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -68,7 +70,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): _attr_device_class = MediaPlayerDeviceClass.TV - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -82,7 +84,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): ] @property - def supported_features(self): + def supported_features(self) -> int: """Flag media player features that are supported.""" features = 0 @@ -108,10 +110,10 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): return features @property - def supported_media_states(self): + def supported_media_states(self) -> set[TargetMediaStateValues]: """Mediate state flags that are supported.""" if not self.service.has(CharacteristicsTypes.TARGET_MEDIA_STATE): - return frozenset() + return set() return clamp_enum_to_char( TargetMediaStateValues, @@ -119,17 +121,17 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): ) @property - def supported_remote_keys(self): + def supported_remote_keys(self) -> set[str]: """Remote key buttons that are supported.""" if not self.service.has(CharacteristicsTypes.REMOTE_KEY): - return frozenset() + return set() return clamp_enum_to_char( RemoteKeyValues, self.service[CharacteristicsTypes.REMOTE_KEY] ) @property - def source_list(self): + def source_list(self) -> list[str]: """List of all input sources for this television.""" sources = [] @@ -147,7 +149,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): return sources @property - def source(self): + def source(self) -> str | None: """Name of the current input source.""" active_identifier = self.service.value(CharacteristicsTypes.ACTIVE_IDENTIFIER) if not active_identifier: @@ -165,7 +167,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): return char.value @property - def state(self): + def state(self) -> str: """State of the tv.""" active = self.service.value(CharacteristicsTypes.ACTIVE) if not active: @@ -177,7 +179,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): return STATE_OK - async def async_media_play(self): + async def async_media_play(self) -> None: """Send play command.""" if self.state == STATE_PLAYING: _LOGGER.debug("Cannot play while already playing") @@ -192,7 +194,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} ) - async def async_media_pause(self): + async def async_media_pause(self) -> None: """Send pause command.""" if self.state == STATE_PAUSED: _LOGGER.debug("Cannot pause while already paused") @@ -207,7 +209,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} ) - async def async_media_stop(self): + async def async_media_stop(self) -> None: """Send stop command.""" if self.state == STATE_IDLE: _LOGGER.debug("Cannot stop when already idle") @@ -218,7 +220,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.STOP} ) - async def async_select_source(self, source): + async def async_select_source(self, source: str) -> None: """Switch to a different media source.""" this_accessory = self._accessory.entity_map.aid(self._aid) this_tv = this_accessory.services.iid(self._iid) diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index 9c28bc8ebdd..a473e30fe6d 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -13,8 +13,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType from . import KNOWN_DEVICES, CharacteristicEntity +from .connection import HKDevice NUMBER_ENTITIES: dict[str, NumberEntityDescription] = { CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: NumberEntityDescription( @@ -90,7 +92,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: entities = [] info = {"aid": char.service.accessory.aid, "iid": char.service.iid} @@ -112,11 +114,11 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): def __init__( self, - conn, - info, - char, + conn: HKDevice, + info: ConfigType, + char: Characteristic, description: NumberEntityDescription, - ): + ) -> None: """Initialise a HomeKit number control.""" self.entity_description = description super().__init__(conn, info, char) @@ -128,7 +130,7 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): return f"{prefix} {self.entity_description.name}" return self.entity_description.name - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [self._char.type] @@ -152,7 +154,7 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): """Return the current characteristic value.""" return self._char.value - async def async_set_value(self, value: float): + async def async_set_value(self, value: float) -> None: """Set the characteristic to this value.""" await self.async_put_characteristics( { @@ -164,7 +166,7 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity): """Representation of a Number control for Ecobee Fan Mode request.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [self._char.type] @@ -196,7 +198,7 @@ class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity): """Return the current characteristic value.""" return self._char.value - async def async_set_value(self, value: float): + async def async_set_value(self, value: float) -> None: """Set the characteristic to this value.""" # Sending the fan mode request sometimes ends up getting ignored by ecobee diff --git a/homeassistant/components/homekit_controller/select.py b/homeassistant/components/homekit_controller/select.py index 82ae3aff691..681f24b9ab8 100644 --- a/homeassistant/components/homekit_controller/select.py +++ b/homeassistant/components/homekit_controller/select.py @@ -32,7 +32,7 @@ class EcobeeModeSelect(CharacteristicEntity, SelectEntity): return f"{name} Current Mode" return "Current Mode" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE, @@ -61,7 +61,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: if char.type == CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE: info = {"aid": char.service.accessory.aid, "iid": char.service.iid} async_add_entities([EcobeeModeSelect(conn, info, char)]) diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 13491c8ee03..b7d7b8005ed 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -5,7 +5,7 @@ from collections.abc import Callable from dataclasses import dataclass from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.sensor import ( SensorDeviceClass, @@ -28,8 +28,10 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity +from .connection import HKDevice CO2_ICON = "mdi:molecule-co2" @@ -203,17 +205,17 @@ class HomeKitHumiditySensor(HomeKitEntity, SensorEntity): _attr_device_class = SensorDeviceClass.HUMIDITY _attr_native_unit_of_measurement = PERCENTAGE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} Humidity" @property - def native_value(self): + def native_value(self) -> float: """Return the current humidity.""" return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) @@ -224,17 +226,17 @@ class HomeKitTemperatureSensor(HomeKitEntity, SensorEntity): _attr_device_class = SensorDeviceClass.TEMPERATURE _attr_native_unit_of_measurement = TEMP_CELSIUS - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.TEMPERATURE_CURRENT] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} Temperature" @property - def native_value(self): + def native_value(self) -> float: """Return the current temperature in Celsius.""" return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) @@ -245,17 +247,17 @@ class HomeKitLightSensor(HomeKitEntity, SensorEntity): _attr_device_class = SensorDeviceClass.ILLUMINANCE _attr_native_unit_of_measurement = LIGHT_LUX - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} Light Level" @property - def native_value(self): + def native_value(self) -> int: """Return the current light level in lux.""" return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT) @@ -266,17 +268,17 @@ class HomeKitCarbonDioxideSensor(HomeKitEntity, SensorEntity): _attr_icon = CO2_ICON _attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} CO2" @property - def native_value(self): + def native_value(self) -> int: """Return the current CO2 level in ppm.""" return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL) @@ -287,7 +289,7 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity): _attr_device_class = SensorDeviceClass.BATTERY _attr_native_unit_of_measurement = PERCENTAGE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [ CharacteristicsTypes.BATTERY_LEVEL, @@ -296,12 +298,12 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity): ] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} Battery" @property - def icon(self): + def icon(self) -> str: """Return the sensor icon.""" if not self.available or self.state is None: return "mdi:battery-unknown" @@ -323,12 +325,12 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity): return icon @property - def is_low_battery(self): + def is_low_battery(self) -> bool: """Return true if battery level is low.""" return self.service.value(CharacteristicsTypes.STATUS_LO_BATT) == 1 @property - def is_charging(self): + def is_charging(self) -> bool: """Return true if currently charing.""" # 0 = not charging # 1 = charging @@ -336,7 +338,7 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity): return self.service.value(CharacteristicsTypes.CHARGING_STATE) == 1 @property - def native_value(self): + def native_value(self) -> int: """Return the current battery level percentage.""" return self.service.value(CharacteristicsTypes.BATTERY_LEVEL) @@ -356,16 +358,16 @@ class SimpleSensor(CharacteristicEntity, SensorEntity): def __init__( self, - conn, - info, - char, + conn: HKDevice, + info: ConfigType, + char: Characteristic, description: HomeKitSensorEntityDescription, - ): + ) -> None: """Initialise a secondary HomeKit characteristic sensor.""" self.entity_description = description super().__init__(conn, info, char) - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [self._char.type] @@ -375,7 +377,7 @@ class SimpleSensor(CharacteristicEntity, SensorEntity): return f"{super().name} {self.entity_description.name}" @property - def native_value(self): + def native_value(self) -> str | int | float: """Return the current sensor value.""" return self._char.value @@ -399,7 +401,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -409,7 +411,7 @@ async def async_setup_entry( conn.add_listener(async_add_service) @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: if not (description := SIMPLE_SENSOR.get(char.type)): return False if description.probe and not description.probe(char): diff --git a/homeassistant/components/homekit_controller/storage.py b/homeassistant/components/homekit_controller/storage.py index 4d512fbbc5d..fc6970f078a 100644 --- a/homeassistant/components/homekit_controller/storage.py +++ b/homeassistant/components/homekit_controller/storage.py @@ -1,6 +1,10 @@ """Helpers for HomeKit data stored in HA storage.""" -from homeassistant.core import callback +from __future__ import annotations + +from typing import Any, TypedDict, cast + +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.storage import Store from .const import DOMAIN @@ -10,6 +14,19 @@ ENTITY_MAP_STORAGE_VERSION = 1 ENTITY_MAP_SAVE_DELAY = 10 +class Pairing(TypedDict): + """A versioned map of entity metadata as presented by aiohomekit.""" + + config_num: int + accessories: list[Any] + + +class StorageLayout(TypedDict): + """Cached pairing metadata needed by aiohomekit.""" + + pairings: dict[str, Pairing] + + class EntityMapStorage: """ Holds a cache of entity structure data from a paired HomeKit device. @@ -26,34 +43,36 @@ class EntityMapStorage: very slow for these devices. """ - def __init__(self, hass): + def __init__(self, hass: HomeAssistant) -> None: """Create a new entity map store.""" self.hass = hass self.store = Store(hass, ENTITY_MAP_STORAGE_VERSION, ENTITY_MAP_STORAGE_KEY) - self.storage_data = {} + self.storage_data: dict[str, Pairing] = {} - async def async_initialize(self): + async def async_initialize(self) -> None: """Get the pairing cache data.""" - if not (raw_storage := await self.store.async_load()): + if not (raw_storage := cast(StorageLayout, await self.store.async_load())): # There is no cached data about HomeKit devices yet return self.storage_data = raw_storage.get("pairings", {}) - def get_map(self, homekit_id): + def get_map(self, homekit_id) -> Pairing | None: """Get a pairing cache item.""" return self.storage_data.get(homekit_id) @callback - def async_create_or_update_map(self, homekit_id, config_num, accessories): + def async_create_or_update_map( + self, homekit_id: str, config_num: int, accessories: list[Any] + ) -> Pairing: """Create a new pairing cache.""" - data = {"config_num": config_num, "accessories": accessories} + data = Pairing(config_num=config_num, accessories=accessories) self.storage_data[homekit_id] = data self._async_schedule_save() return data @callback - def async_delete_map(self, homekit_id): + def async_delete_map(self, homekit_id: str) -> None: """Delete pairing cache.""" if homekit_id not in self.storage_data: return @@ -62,11 +81,11 @@ class EntityMapStorage: self._async_schedule_save() @callback - def _async_schedule_save(self): + def _async_schedule_save(self) -> None: """Schedule saving the entity map cache.""" self.store.async_delay_save(self._data_to_save, ENTITY_MAP_SAVE_DELAY) @callback - def _data_to_save(self): + def _data_to_save(self) -> dict[str, Any]: """Return data of entity map to store in a file.""" return {"pairings": self.storage_data} diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index c681ca4d288..07d0e21e59f 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -2,6 +2,7 @@ from __future__ import annotations from dataclasses import dataclass +from typing import Any from aiohomekit.model.characteristics import ( Characteristic, @@ -9,15 +10,17 @@ from aiohomekit.model.characteristics import ( InUseValues, IsConfiguredValues, ) -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity +from .connection import HKDevice OUTLET_IN_USE = "outlet_in_use" @@ -53,35 +56,36 @@ SWITCH_ENTITIES: dict[str, DeclarativeSwitchEntityDescription] = { class HomeKitSwitch(HomeKitEntity, SwitchEntity): """Representation of a Homekit switch.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [CharacteristicsTypes.ON, CharacteristicsTypes.OUTLET_IN_USE] @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ON) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified switch on.""" await self.async_put_characteristics({CharacteristicsTypes.ON: True}) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified switch off.""" await self.async_put_characteristics({CharacteristicsTypes.ON: False}) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the optional state attributes.""" outlet_in_use = self.service.value(CharacteristicsTypes.OUTLET_IN_USE) if outlet_in_use is not None: return {OUTLET_IN_USE: outlet_in_use} + return None class HomeKitValve(HomeKitEntity, SwitchEntity): """Represents a valve in an irrigation system.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -90,11 +94,11 @@ class HomeKitValve(HomeKitEntity, SwitchEntity): CharacteristicsTypes.REMAINING_DURATION, ] - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified valve on.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified valve off.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @@ -104,12 +108,12 @@ class HomeKitValve(HomeKitEntity, SwitchEntity): return "mdi:water" @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ACTIVE) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" attrs = {} @@ -133,13 +137,13 @@ class DeclarativeCharacteristicSwitch(CharacteristicEntity, SwitchEntity): def __init__( self, - conn, - info, - char, + conn: HKDevice, + info: ConfigType, + char: Characteristic, description: DeclarativeSwitchEntityDescription, - ): + ) -> None: """Initialise a HomeKit switch.""" - self.entity_description = description + self.entity_description: DeclarativeSwitchEntityDescription = description super().__init__(conn, info, char) @property @@ -149,22 +153,22 @@ class DeclarativeCharacteristicSwitch(CharacteristicEntity, SwitchEntity): return f"{prefix} {self.entity_description.name}" return self.entity_description.name - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [self._char.type] @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self._char.value == self.entity_description.true_value - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified switch on.""" await self.async_put_characteristics( {self._char.type: self.entity_description.true_value} ) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified switch off.""" await self.async_put_characteristics( {self._char.type: self.entity_description.false_value} @@ -188,7 +192,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -198,7 +202,7 @@ async def async_setup_entry( conn.add_listener(async_add_service) @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: if not (description := SWITCH_ENTITIES.get(char.type)): return False