Add missing type hints to homekit_controller (#65368)

This commit is contained in:
Jc2k 2022-02-01 19:30:37 +00:00 committed by GitHub
parent aef6f49eff
commit 9f5d77e0df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 389 additions and 312 deletions

View file

@ -17,7 +17,7 @@ from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP 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.exceptions import ConfigEntryNotReady
from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
@ -30,17 +30,12 @@ from .storage import EntityMapStorage
_LOGGER = logging.getLogger(__name__) _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): class HomeKitEntity(Entity):
"""Representation of a Home Assistant HomeKit device.""" """Representation of a Home Assistant HomeKit device."""
_attr_should_poll = False _attr_should_poll = False
def __init__(self, accessory: HKDevice, devinfo): def __init__(self, accessory: HKDevice, devinfo: ConfigType) -> None:
"""Initialise a generic HomeKit device.""" """Initialise a generic HomeKit device."""
self._accessory = accessory self._accessory = accessory
self._aid = devinfo["aid"] self._aid = devinfo["aid"]
@ -67,7 +62,7 @@ class HomeKitEntity(Entity):
"""Return a Service model that this entity is attached to.""" """Return a Service model that this entity is attached to."""
return self.accessory.services.iid(self._iid) 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.""" """Entity added to hass."""
self.async_on_remove( self.async_on_remove(
self.hass.helpers.dispatcher.async_dispatcher_connect( 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_pollable_characteristics(self.pollable_characteristics)
self._accessory.add_watchable_characteristics(self.watchable_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.""" """Prepare to be removed from hass."""
self._accessory.remove_pollable_characteristics(self._aid) self._accessory.remove_pollable_characteristics(self._aid)
self._accessory.remove_watchable_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. Write characteristics to the device.
@ -101,10 +96,10 @@ class HomeKitEntity(Entity):
payload = self.service.build_update(characteristics) payload = self.service.build_update(characteristics)
return await self._accessory.put_characteristics(payload) return await self._accessory.put_characteristics(payload)
def setup(self): def setup(self) -> None:
"""Configure an entity baed on its HomeKit characteristics metadata.""" """Configure an entity based on its HomeKit characteristics metadata."""
self.pollable_characteristics = [] self.pollable_characteristics: list[tuple[int, int]] = []
self.watchable_characteristics = [] self.watchable_characteristics: list[tuple[int, int]] = []
char_types = self.get_characteristic_types() char_types = self.get_characteristic_types()
@ -118,7 +113,7 @@ class HomeKitEntity(Entity):
for char in service.characteristics.filter(char_types=char_types): for char in service.characteristics.filter(char_types=char_types):
self._setup_characteristic(char) 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.""" """Configure an entity based on a HomeKit characteristics metadata."""
# Build up a list of (aid, iid) tuples to poll on update() # Build up a list of (aid, iid) tuples to poll on update()
if CharacteristicPermissions.paired_read in char.perms: if CharacteristicPermissions.paired_read in char.perms:
@ -153,7 +148,7 @@ class HomeKitEntity(Entity):
"""Return the device info.""" """Return the device info."""
return self._accessory.device_info_for_accessory(self.accessory) 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.""" """Define the homekit characteristics the entity cares about."""
raise NotImplementedError raise NotImplementedError
@ -176,7 +171,9 @@ class CharacteristicEntity(HomeKitEntity):
the service entity. 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.""" """Initialise a generic single characteristic HomeKit entity."""
self._char = char self._char = char
super().__init__(accessory, devinfo) super().__init__(accessory, devinfo)
@ -218,7 +215,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.data[KNOWN_DEVICES] = {} hass.data[KNOWN_DEVICES] = {}
hass.data[TRIGGERS] = {} hass.data[TRIGGERS] = {}
async def _async_stop_homekit_controller(event): async def _async_stop_homekit_controller(event: Event) -> None:
await asyncio.gather( await asyncio.gather(
*( *(
connection.async_unload() connection.async_unload()

View file

@ -1,6 +1,10 @@
"""Support for Homekit Alarm Control Panel.""" """Support for Homekit Alarm Control Panel."""
from __future__ import annotations
from typing import Any
from aiohomekit.model.characteristics import CharacteristicsTypes 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 import AlarmControlPanelEntity
from homeassistant.components.alarm_control_panel.const import ( from homeassistant.components.alarm_control_panel.const import (
@ -50,7 +54,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if service.type != ServicesTypes.SECURITY_SYSTEM: if service.type != ServicesTypes.SECURITY_SYSTEM:
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
@ -63,7 +67,7 @@ async def async_setup_entry(
class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity):
"""Representation of a Homekit Alarm Control Panel.""" """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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT, CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT,
@ -72,12 +76,12 @@ class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity):
] ]
@property @property
def icon(self): def icon(self) -> str:
"""Return icon.""" """Return icon."""
return ICON return ICON
@property @property
def state(self): def state(self) -> str:
"""Return the state of the device.""" """Return the state of the device."""
return CURRENT_STATE_MAP[ return CURRENT_STATE_MAP[
self.service.value(CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT) self.service.value(CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT)
@ -88,30 +92,30 @@ class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity):
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT 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.""" """Send disarm command."""
await self.set_alarm_state(STATE_ALARM_DISARMED, code) 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.""" """Send arm command."""
await self.set_alarm_state(STATE_ALARM_ARMED_AWAY, code) 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.""" """Send stay command."""
await self.set_alarm_state(STATE_ALARM_ARMED_HOME, code) 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.""" """Send night command."""
await self.set_alarm_state(STATE_ALARM_ARMED_NIGHT, code) 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.""" """Send state command."""
await self.async_put_characteristics( await self.async_put_characteristics(
{CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: TARGET_STATE_MAP[state]} {CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: TARGET_STATE_MAP[state]}
) )
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the optional state attributes.""" """Return the optional state attributes."""
battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL) battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL)

View file

@ -1,6 +1,8 @@
"""Support for Homekit motion sensors.""" """Support for Homekit motion sensors."""
from __future__ import annotations
from aiohomekit.model.characteristics import CharacteristicsTypes 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 ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
@ -18,14 +20,14 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.MOTION _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.MOTION_DETECTED] return [CharacteristicsTypes.MOTION_DETECTED]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Has motion been detected.""" """Has motion been detected."""
return self.service.value(CharacteristicsTypes.MOTION_DETECTED) return self.service.value(CharacteristicsTypes.MOTION_DETECTED) is True
class HomeKitContactSensor(HomeKitEntity, BinarySensorEntity): class HomeKitContactSensor(HomeKitEntity, BinarySensorEntity):
@ -33,12 +35,12 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.OPENING _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.CONTACT_STATE] return [CharacteristicsTypes.CONTACT_STATE]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if the binary sensor is on/open.""" """Return true if the binary sensor is on/open."""
return self.service.value(CharacteristicsTypes.CONTACT_STATE) == 1 return self.service.value(CharacteristicsTypes.CONTACT_STATE) == 1
@ -48,12 +50,12 @@ class HomeKitSmokeSensor(HomeKitEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.SMOKE _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.SMOKE_DETECTED] return [CharacteristicsTypes.SMOKE_DETECTED]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if smoke is currently detected.""" """Return true if smoke is currently detected."""
return self.service.value(CharacteristicsTypes.SMOKE_DETECTED) == 1 return self.service.value(CharacteristicsTypes.SMOKE_DETECTED) == 1
@ -63,12 +65,12 @@ class HomeKitCarbonMonoxideSensor(HomeKitEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.GAS _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.CARBON_MONOXIDE_DETECTED] return [CharacteristicsTypes.CARBON_MONOXIDE_DETECTED]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if CO is currently detected.""" """Return true if CO is currently detected."""
return self.service.value(CharacteristicsTypes.CARBON_MONOXIDE_DETECTED) == 1 return self.service.value(CharacteristicsTypes.CARBON_MONOXIDE_DETECTED) == 1
@ -78,12 +80,12 @@ class HomeKitOccupancySensor(HomeKitEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.OCCUPANCY _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.OCCUPANCY_DETECTED] return [CharacteristicsTypes.OCCUPANCY_DETECTED]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if occupancy is currently detected.""" """Return true if occupancy is currently detected."""
return self.service.value(CharacteristicsTypes.OCCUPANCY_DETECTED) == 1 return self.service.value(CharacteristicsTypes.OCCUPANCY_DETECTED) == 1
@ -93,12 +95,12 @@ class HomeKitLeakSensor(HomeKitEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.MOISTURE _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.LEAK_DETECTED] return [CharacteristicsTypes.LEAK_DETECTED]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if a leak is detected from the binary sensor.""" """Return true if a leak is detected from the binary sensor."""
return self.service.value(CharacteristicsTypes.LEAK_DETECTED) == 1 return self.service.value(CharacteristicsTypes.LEAK_DETECTED) == 1
@ -123,7 +125,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if not (entity_class := ENTITY_TYPES.get(service.type)): if not (entity_class := ENTITY_TYPES.get(service.type)):
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}

View file

@ -19,8 +19,10 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNOWN_DEVICES, CharacteristicEntity from . import KNOWN_DEVICES, CharacteristicEntity
from .connection import HKDevice
@dataclass @dataclass
@ -64,7 +66,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_characteristic(char: Characteristic): def async_add_characteristic(char: Characteristic) -> bool:
entities = [] entities = []
info = {"aid": char.service.accessory.aid, "iid": char.service.iid} info = {"aid": char.service.accessory.aid, "iid": char.service.iid}
@ -88,16 +90,16 @@ class HomeKitButton(CharacteristicEntity, ButtonEntity):
def __init__( def __init__(
self, self,
conn, conn: HKDevice,
info, info: ConfigType,
char, char: Characteristic,
description: HomeKitButtonEntityDescription, description: HomeKitButtonEntityDescription,
): ) -> None:
"""Initialise a HomeKit button control.""" """Initialise a HomeKit button control."""
self.entity_description = description self.entity_description = description
super().__init__(conn, info, char) 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.""" """Define the homekit characteristics the entity is tracking."""
return [self._char.type] return [self._char.type]
@ -112,13 +114,13 @@ class HomeKitButton(CharacteristicEntity, ButtonEntity):
"""Press the button.""" """Press the button."""
key = self.entity_description.key key = self.entity_description.key
val = self.entity_description.write_value 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): class HomeKitEcobeeClearHoldButton(CharacteristicEntity, ButtonEntity):
"""Representation of a Button control for Ecobee clear hold request.""" """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.""" """Define the homekit characteristics the entity is tracking."""
return [] return []

View file

@ -1,6 +1,7 @@
"""Support for Homekit cameras.""" """Support for Homekit cameras."""
from __future__ import annotations from __future__ import annotations
from aiohomekit.model import Accessory
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import ServicesTypes
from homeassistant.components.camera import Camera from homeassistant.components.camera import Camera
@ -16,7 +17,7 @@ class HomeKitCamera(AccessoryEntity, Camera):
# content_type = "image/jpeg" # content_type = "image/jpeg"
def get_characteristic_types(self): def get_characteristic_types(self) -> list[str]:
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [] return []
@ -41,12 +42,12 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_accessory(accessory): def async_add_accessory(accessory: Accessory) -> bool:
stream_mgmt = accessory.services.first( stream_mgmt = accessory.services.first(
service_type=ServicesTypes.CAMERA_RTP_STREAM_MANAGEMENT service_type=ServicesTypes.CAMERA_RTP_STREAM_MANAGEMENT
) )
if not stream_mgmt: if not stream_mgmt:
return return False
info = {"aid": accessory.aid, "iid": stream_mgmt.iid} info = {"aid": accessory.aid, "iid": stream_mgmt.iid}
async_add_entities([HomeKitCamera(conn, info)], True) async_add_entities([HomeKitCamera(conn, info)], True)

View file

@ -1,5 +1,8 @@
"""Support for Homekit climate devices.""" """Support for Homekit climate devices."""
from __future__ import annotations
import logging import logging
from typing import Any
from aiohomekit.model.characteristics import ( from aiohomekit.model.characteristics import (
ActivationStateValues, ActivationStateValues,
@ -10,7 +13,7 @@ from aiohomekit.model.characteristics import (
SwingModeValues, SwingModeValues,
TargetHeaterCoolerStateValues, TargetHeaterCoolerStateValues,
) )
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from aiohomekit.utils import clamp_enum_to_char from aiohomekit.utils import clamp_enum_to_char
from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate import ClimateEntity
@ -94,7 +97,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if not (entity_class := ENTITY_TYPES.get(service.type)): if not (entity_class := ENTITY_TYPES.get(service.type)):
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
@ -107,7 +110,7 @@ async def async_setup_entry(
class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
"""Representation of a Homekit climate device.""" """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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.ACTIVE, CharacteristicsTypes.ACTIVE,
@ -119,7 +122,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
CharacteristicsTypes.TEMPERATURE_CURRENT, CharacteristicsTypes.TEMPERATURE_CURRENT,
] ]
async def async_set_temperature(self, **kwargs): async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE) temp = kwargs.get(ATTR_TEMPERATURE)
state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE)
@ -140,7 +143,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
hvac_mode, 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.""" """Set new target operation mode."""
if hvac_mode == HVAC_MODE_OFF: if hvac_mode == HVAC_MODE_OFF:
await self.async_put_characteristics( await self.async_put_characteristics(
@ -163,12 +166,12 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
) )
@property @property
def current_temperature(self): def current_temperature(self) -> float:
"""Return the current temperature.""" """Return the current temperature."""
return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT)
@property @property
def target_temperature(self): def target_temperature(self) -> float | None:
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE)
if state == TargetHeaterCoolerStateValues.COOL: if state == TargetHeaterCoolerStateValues.COOL:
@ -182,7 +185,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
return None return None
@property @property
def target_temperature_step(self): def target_temperature_step(self) -> float | None:
"""Return the supported step of target temperature.""" """Return the supported step of target temperature."""
state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE)
if state == TargetHeaterCoolerStateValues.COOL and self.service.has( if state == TargetHeaterCoolerStateValues.COOL and self.service.has(
@ -200,7 +203,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
return None return None
@property @property
def min_temp(self): def min_temp(self) -> float:
"""Return the minimum target temp.""" """Return the minimum target temp."""
state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE)
if state == TargetHeaterCoolerStateValues.COOL and self.service.has( if state == TargetHeaterCoolerStateValues.COOL and self.service.has(
@ -218,7 +221,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
return super().min_temp return super().min_temp
@property @property
def max_temp(self): def max_temp(self) -> float:
"""Return the maximum target temp.""" """Return the maximum target temp."""
state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE)
if state == TargetHeaterCoolerStateValues.COOL and self.service.has( if state == TargetHeaterCoolerStateValues.COOL and self.service.has(
@ -236,7 +239,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
return super().max_temp return super().max_temp
@property @property
def hvac_action(self): def hvac_action(self) -> str | None:
"""Return the current running hvac operation.""" """Return the current running hvac operation."""
# This characteristic describes the current mode of a device, # This characteristic describes the current mode of a device,
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit. # 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) return CURRENT_HEATER_COOLER_STATE_HOMEKIT_TO_HASS.get(value)
@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."""
# This characteristic describes the target mode # This characteristic describes the target mode
# E.g. should the device start heating a room if the temperature # E.g. should the device start heating a room if the temperature
@ -262,10 +265,10 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
): ):
return HVAC_MODE_OFF return HVAC_MODE_OFF
value = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) 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 @property
def hvac_modes(self): def hvac_modes(self) -> list[str]:
"""Return the list of available hvac operation modes.""" """Return the list of available hvac operation modes."""
valid_values = clamp_enum_to_char( valid_values = clamp_enum_to_char(
TargetHeaterCoolerStateValues, TargetHeaterCoolerStateValues,
@ -278,7 +281,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
return modes return modes
@property @property
def swing_mode(self): def swing_mode(self) -> str:
"""Return the swing setting. """Return the swing setting.
Requires SUPPORT_SWING_MODE. Requires SUPPORT_SWING_MODE.
@ -287,7 +290,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
return SWING_MODE_HOMEKIT_TO_HASS[value] return SWING_MODE_HOMEKIT_TO_HASS[value]
@property @property
def swing_modes(self): def swing_modes(self) -> list[str]:
"""Return the list of available swing modes. """Return the list of available swing modes.
Requires SUPPORT_SWING_MODE. Requires SUPPORT_SWING_MODE.
@ -305,7 +308,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
) )
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Return the list of supported features.""" """Return the list of supported features."""
features = 0 features = 0
@ -321,7 +324,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
return features return features
@property @property
def temperature_unit(self): def temperature_unit(self) -> str:
"""Return the unit of measurement.""" """Return the unit of measurement."""
return TEMP_CELSIUS return TEMP_CELSIUS
@ -329,7 +332,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity):
class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
"""Representation of a Homekit climate device.""" """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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.HEATING_COOLING_CURRENT, CharacteristicsTypes.HEATING_COOLING_CURRENT,
@ -342,12 +345,12 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET, CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET,
] ]
async def async_set_temperature(self, **kwargs): async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature.""" """Set new target temperature."""
chars = {} chars = {}
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) 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: if kwargs.get(ATTR_HVAC_MODE, mode) != mode:
mode = kwargs[ATTR_HVAC_MODE] mode = kwargs[ATTR_HVAC_MODE]
@ -359,8 +362,11 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if (mode == HVAC_MODE_HEAT_COOL) and ( if (
SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features (mode == HVAC_MODE_HEAT_COOL)
and (SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features)
and heat_temp
and cool_temp
): ):
if temp is None: if temp is None:
temp = (cool_temp + heat_temp) / 2 temp = (cool_temp + heat_temp) / 2
@ -376,13 +382,13 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
await self.async_put_characteristics(chars) 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.""" """Set new target humidity."""
await self.async_put_characteristics( await self.async_put_characteristics(
{CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: humidity} {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.""" """Set new target operation mode."""
await self.async_put_characteristics( await self.async_put_characteristics(
{ {
@ -393,12 +399,12 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
) )
@property @property
def current_temperature(self): def current_temperature(self) -> float | None:
"""Return the current temperature.""" """Return the current temperature."""
return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT)
@property @property
def target_temperature(self): def target_temperature(self) -> float | None:
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT, HVAC_MODE_COOL}) or ( 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 return None
@property @property
def target_temperature_high(self): def target_temperature_high(self) -> float | None:
"""Return the highbound target temperature we try to reach.""" """Return the highbound target temperature we try to reach."""
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and (
@ -421,7 +427,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
return None return None
@property @property
def target_temperature_low(self): def target_temperature_low(self) -> float | None:
"""Return the lowbound target temperature we try to reach.""" """Return the lowbound target temperature we try to reach."""
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and (
@ -433,7 +439,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
return None return None
@property @property
def min_temp(self): def min_temp(self) -> float:
"""Return the minimum target temp.""" """Return the minimum target temp."""
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( 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 return super().min_temp
@property @property
def max_temp(self): def max_temp(self) -> float:
"""Return the maximum target temp.""" """Return the maximum target temp."""
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( 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 return super().max_temp
@property @property
def current_humidity(self): def current_humidity(self) -> int:
"""Return the current humidity.""" """Return the current humidity."""
return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
@property @property
def target_humidity(self): def target_humidity(self) -> int:
"""Return the humidity we try to reach.""" """Return the humidity we try to reach."""
return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET) return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET)
@property @property
def min_humidity(self): def min_humidity(self) -> int:
"""Return the minimum humidity.""" """Return the minimum humidity."""
min_humidity = self.service[ min_humidity = self.service[
CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET
@ -497,7 +503,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
return super().min_humidity return super().min_humidity
@property @property
def max_humidity(self): def max_humidity(self) -> int:
"""Return the maximum humidity.""" """Return the maximum humidity."""
max_humidity = self.service[ max_humidity = self.service[
CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET
@ -507,7 +513,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
return super().max_humidity return super().max_humidity
@property @property
def hvac_action(self): def hvac_action(self) -> str | None:
"""Return the current running hvac operation.""" """Return the current running hvac operation."""
# This characteristic describes the current mode of a device, # This characteristic describes the current mode of a device,
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit. # 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) return CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
@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."""
# This characteristic describes the target mode # This characteristic describes the target mode
# E.g. should the device start heating a room if the temperature # E.g. should the device start heating a room if the temperature
# falls below the target temperature. # falls below the target temperature.
# Can be 0 - 3 (Off, Heat, Cool, Auto) # Can be 0 - 3 (Off, Heat, Cool, Auto)
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
return MODE_HOMEKIT_TO_HASS.get(value) return MODE_HOMEKIT_TO_HASS[value]
@property @property
def hvac_modes(self): def hvac_modes(self) -> list[str]:
"""Return the list of available hvac operation modes.""" """Return the list of available hvac operation modes."""
valid_values = clamp_enum_to_char( valid_values = clamp_enum_to_char(
HeatingCoolingTargetValues, HeatingCoolingTargetValues,
@ -535,7 +541,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
return [MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values] return [MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values]
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Return the list of supported features.""" """Return the list of supported features."""
features = 0 features = 0
@ -553,7 +559,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity):
return features return features
@property @property
def temperature_unit(self): def temperature_unit(self) -> str:
"""Return the unit of measurement.""" """Return the unit of measurement."""
return TEMP_CELSIUS return TEMP_CELSIUS

View file

@ -1,6 +1,9 @@
"""Config flow to configure homekit_controller.""" """Config flow to configure homekit_controller."""
from __future__ import annotations
import logging import logging
import re import re
from typing import Any
import aiohomekit import aiohomekit
from aiohomekit.exceptions import AuthenticationError 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.""" """Normalize a hkid so that it is safe to compare with other normalized hkids."""
return hkid.lower() return hkid.lower()
@callback @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.""" """Return a set of the configured hosts."""
for entry in hass.config_entries.async_entries(DOMAIN): for entry in hass.config_entries.async_entries(DOMAIN):
if entry.data.get("AccessoryPairingID") == serial: if entry.data.get("AccessoryPairingID") == serial:
return entry 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. Ensure a pin code is correctly formatted.

View file

@ -1,7 +1,11 @@
"""Helpers for managing a pairing with a HomeKit accessory or bridge.""" """Helpers for managing a pairing with a HomeKit accessory or bridge."""
from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable
import datetime import datetime
import logging import logging
from typing import Any
from aiohomekit.exceptions import ( from aiohomekit.exceptions import (
AccessoryDisconnectedError, AccessoryDisconnectedError,
@ -9,11 +13,11 @@ from aiohomekit.exceptions import (
EncryptionError, EncryptionError,
) )
from aiohomekit.model import Accessories, Accessory from aiohomekit.model import Accessories, Accessory
from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.const import ATTR_VIA_DEVICE 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 import device_registry as dr
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
@ -37,6 +41,10 @@ MAX_POLL_FAILURES_TO_DECLARE_UNAVAILABLE = 3
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
AddAccessoryCb = Callable[[Accessory], bool]
AddServiceCb = Callable[[Service], bool]
AddCharacteristicCb = Callable[[Characteristic], bool]
def valid_serial_number(serial): def valid_serial_number(serial):
"""Return if the serial number appears to be valid.""" """Return if the serial number appears to be valid."""
@ -51,7 +59,7 @@ def valid_serial_number(serial):
class HKDevice: class HKDevice:
"""HomeKit device.""" """HomeKit device."""
def __init__(self, hass, config_entry, pairing_data): def __init__(self, hass, config_entry, pairing_data) -> None:
"""Initialise a generic HomeKit device.""" """Initialise a generic HomeKit device."""
self.hass = hass self.hass = hass
@ -71,28 +79,28 @@ class HKDevice:
self.entity_map = Accessories() self.entity_map = Accessories()
# A list of callbacks that turn HK accessories into entities # 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 # 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 # 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 # 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 # 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 # 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 # a lightbulb. And we don't want to forward a config entry twice
# (triggers a Config entry already set up error) # (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 # This just tracks aid/iid pairs so we know if a HK service has been
# mapped to a HA entity. # mapped to a HA entity.
self.entities = [] self.entities: list[tuple[int, int | None, int | None]] = []
# A map of aid -> device_id # A map of aid -> device_id
# Useful when routing events to triggers # Useful when routing events to triggers
self.devices = {} self.devices: dict[int, str] = {}
self.available = False self.available = False
@ -100,13 +108,13 @@ class HKDevice:
# Current values of all characteristics homekit_controller is tracking. # Current values of all characteristics homekit_controller is tracking.
# Key is a (accessory_id, characteristic_id) tuple. # 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 # If this is set polling is active and can be disabled by calling
# this method. # 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 # Never allow concurrent polling of the same accessory or bridge
self._polling_lock = asyncio.Lock() 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 # This is set to True if we can't rely on serial numbers to be unique
self.unreliable_serial_numbers = False self.unreliable_serial_numbers = False
self.watchable_characteristics = [] self.watchable_characteristics: list[tuple[int, int]] = []
self.pairing.dispatcher_connect(self.process_new_events) 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.""" """Add (aid, iid) pairs that we need to poll."""
self.pollable_characteristics.extend(characteristics) 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.""" """Remove all pollable characteristics by accessory id."""
self.pollable_characteristics = [ self.pollable_characteristics = [
char for char in self.pollable_characteristics if char[0] != accessory_id 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.""" """Add (aid, iid) pairs that we need to poll."""
self.watchable_characteristics.extend(characteristics) self.watchable_characteristics.extend(characteristics)
self.hass.async_create_task(self.pairing.subscribe(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.""" """Remove all pollable characteristics by accessory id."""
self.watchable_characteristics = [ self.watchable_characteristics = [
char for char in self.watchable_characteristics if char[0] != accessory_id char for char in self.watchable_characteristics if char[0] != accessory_id
] ]
@callback @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.""" """Mark state of all entities on this connection when it becomes available or unavailable."""
_LOGGER.debug( _LOGGER.debug(
"Called async_set_available_state with %s for %s", available, self.unique_id "Called async_set_available_state with %s for %s", available, self.unique_id
@ -152,7 +164,7 @@ class HKDevice:
self.available = available self.available = available
self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) 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.""" """Prepare to use a paired HomeKit device in Home Assistant."""
cache = self.hass.data[ENTITY_MAP].get_map(self.unique_id) cache = self.hass.data[ENTITY_MAP].get_map(self.unique_id)
if not cache: if not cache:
@ -214,7 +226,7 @@ class HKDevice:
return device_info return device_info
@callback @callback
def async_migrate_devices(self): def async_migrate_devices(self) -> None:
"""Migrate legacy device entries from 3-tuples to 2-tuples.""" """Migrate legacy device entries from 3-tuples to 2-tuples."""
_LOGGER.debug( _LOGGER.debug(
"Migrating device registry entries for pairing %s", self.unique_id "Migrating device registry entries for pairing %s", self.unique_id
@ -246,7 +258,7 @@ class HKDevice:
(DOMAIN, IDENTIFIER_LEGACY_SERIAL_NUMBER, serial_number) (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: if not device:
continue continue
@ -286,7 +298,7 @@ class HKDevice:
) )
@callback @callback
def async_create_devices(self): def async_create_devices(self) -> None:
""" """
Build device registry entries for all accessories paired with the bridge. Build device registry entries for all accessories paired with the bridge.
@ -315,7 +327,7 @@ class HKDevice:
self.devices = devices self.devices = devices
@callback @callback
def async_detect_workarounds(self): def async_detect_workarounds(self) -> None:
"""Detect any workarounds that are needed for this pairing.""" """Detect any workarounds that are needed for this pairing."""
unreliable_serial_numbers = False unreliable_serial_numbers = False
@ -354,7 +366,7 @@ class HKDevice:
self.unreliable_serial_numbers = unreliable_serial_numbers 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. Process the entity map and load any platforms or entities that need adding.
@ -388,18 +400,18 @@ class HKDevice:
await self.async_update() 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.""" """Stop interacting with device and prepare for removal from hass."""
if self._polling_interval_remover: if self._polling_interval_remover:
self._polling_interval_remover() self._polling_interval_remover()
await self.pairing.close() 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 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.""" """Handle setup of a HomeKit accessory."""
try: try:
self.accessories = await self.pairing.list_accessories_and_characteristics() self.accessories = await self.pairing.list_accessories_and_characteristics()
@ -419,26 +431,26 @@ class HKDevice:
return True 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.""" """Add a callback to run when discovering new entities for accessories."""
self.accessory_factories.append(add_entities_cb) self.accessory_factories.append(add_entities_cb)
self._add_new_entities_for_accessory([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 accessory in self.entity_map.accessories:
for handler in handlers: for handler in handlers:
if (accessory.aid, None) in self.entities: if (accessory.aid, None, None) in self.entities:
continue continue
if handler(accessory): if handler(accessory):
self.entities.append((accessory.aid, None)) self.entities.append((accessory.aid, None, None))
break 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.""" """Add a callback to run when discovering new entities for accessories."""
self.char_factories.append(add_entities_cb) self.char_factories.append(add_entities_cb)
self._add_new_entities_for_char([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 accessory in self.entity_map.accessories:
for service in accessory.services: for service in accessory.services:
for char in service.characteristics: for char in service.characteristics:
@ -449,33 +461,33 @@ class HKDevice:
self.entities.append((accessory.aid, service.iid, char.iid)) self.entities.append((accessory.aid, service.iid, char.iid))
break 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.""" """Add a callback to run when discovering new entities for services."""
self.listeners.append(add_entities_cb) self.listeners.append(add_entities_cb)
self._add_new_entities([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.""" """Process the entity map and create HA entities."""
self._add_new_entities(self.listeners) self._add_new_entities(self.listeners)
self._add_new_entities_for_accessory(self.accessory_factories) self._add_new_entities_for_accessory(self.accessory_factories)
self._add_new_entities_for_char(self.char_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: for accessory in self.entity_map.accessories:
aid = accessory.aid aid = accessory.aid
for service in accessory.services: for service in accessory.services:
iid = service.iid iid = service.iid
if (aid, iid) in self.entities: if (aid, None, iid) in self.entities:
# Don't add the same entity again # Don't add the same entity again
continue continue
for listener in callbacks: for listener in callbacks:
if listener(service): if listener(service):
self.entities.append((aid, iid)) self.entities.append((aid, None, iid))
break break
async def async_load_platform(self, platform): async def async_load_platform(self, platform: str) -> None:
"""Load a single platform idempotently.""" """Load a single platform idempotently."""
if platform in self.platforms: if platform in self.platforms:
return return
@ -489,7 +501,7 @@ class HKDevice:
self.platforms.remove(platform) self.platforms.remove(platform)
raise raise
async def async_load_platforms(self): async def async_load_platforms(self) -> None:
"""Load any platforms needed by this HomeKit device.""" """Load any platforms needed by this HomeKit device."""
tasks = [] tasks = []
for accessory in self.entity_map.accessories: for accessory in self.entity_map.accessories:
@ -558,7 +570,7 @@ class HKDevice:
_LOGGER.debug("Finished HomeKit controller update: %s", self.unique_id) _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.""" """Process events from accessory into HA state."""
self.async_set_available_state(True) self.async_set_available_state(True)
@ -575,11 +587,11 @@ class HKDevice:
self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) 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.""" """Read latest state from homekit accessory."""
return await self.pairing.get_characteristics(*args, **kwargs) 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.""" """Control a HomeKit device state from Home Assistant."""
results = await self.pairing.put_characteristics(characteristics) results = await self.pairing.put_characteristics(characteristics)
@ -604,7 +616,7 @@ class HKDevice:
self.process_new_events(new_entity_state) self.process_new_events(new_entity_state)
@property @property
def unique_id(self): def unique_id(self) -> str:
""" """
Return a unique id for this accessory or bridge. Return a unique id for this accessory or bridge.

View file

@ -1,10 +1,15 @@
"""Support for Homekit covers.""" """Support for Homekit covers."""
from __future__ import annotations
from typing import Any
from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_POSITION, ATTR_POSITION,
ATTR_TILT_POSITION, ATTR_TILT_POSITION,
DEVICE_CLASS_GARAGE,
SUPPORT_CLOSE, SUPPORT_CLOSE,
SUPPORT_CLOSE_TILT, SUPPORT_CLOSE_TILT,
SUPPORT_OPEN, SUPPORT_OPEN,
@ -46,7 +51,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if not (entity_class := ENTITY_TYPES.get(service.type)): if not (entity_class := ENTITY_TYPES.get(service.type)):
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
@ -59,12 +64,9 @@ async def async_setup_entry(
class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity):
"""Representation of a HomeKit Garage Door.""" """Representation of a HomeKit Garage Door."""
@property _attr_device_class = DEVICE_CLASS_GARAGE
def device_class(self):
"""Define this cover as a garage door."""
return "garage"
def get_characteristic_types(self): def get_characteristic_types(self) -> list[str]:
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.DOOR_STATE_CURRENT, CharacteristicsTypes.DOOR_STATE_CURRENT,
@ -73,47 +75,47 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity):
] ]
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Flag supported features.""" """Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE return SUPPORT_OPEN | SUPPORT_CLOSE
@property @property
def _state(self): def _state(self) -> str:
"""Return the current state of the garage door.""" """Return the current state of the garage door."""
value = self.service.value(CharacteristicsTypes.DOOR_STATE_CURRENT) value = self.service.value(CharacteristicsTypes.DOOR_STATE_CURRENT)
return CURRENT_GARAGE_STATE_MAP[value] return CURRENT_GARAGE_STATE_MAP[value]
@property @property
def is_closed(self): def is_closed(self) -> bool:
"""Return true if cover is closed, else False.""" """Return true if cover is closed, else False."""
return self._state == STATE_CLOSED return self._state == STATE_CLOSED
@property @property
def is_closing(self): def is_closing(self) -> bool:
"""Return if the cover is closing or not.""" """Return if the cover is closing or not."""
return self._state == STATE_CLOSING return self._state == STATE_CLOSING
@property @property
def is_opening(self): def is_opening(self) -> bool:
"""Return if the cover is opening or not.""" """Return if the cover is opening or not."""
return self._state == STATE_OPENING return self._state == STATE_OPENING
async def async_open_cover(self, **kwargs): async def async_open_cover(self, **kwargs: Any) -> None:
"""Send open command.""" """Send open command."""
await self.set_door_state(STATE_OPEN) 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.""" """Send close command."""
await self.set_door_state(STATE_CLOSED) 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.""" """Send state command."""
await self.async_put_characteristics( await self.async_put_characteristics(
{CharacteristicsTypes.DOOR_STATE_TARGET: TARGET_GARAGE_STATE_MAP[state]} {CharacteristicsTypes.DOOR_STATE_TARGET: TARGET_GARAGE_STATE_MAP[state]}
) )
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, Any]:
"""Return the optional state attributes.""" """Return the optional state attributes."""
obstruction_detected = self.service.value( obstruction_detected = self.service.value(
CharacteristicsTypes.OBSTRUCTION_DETECTED CharacteristicsTypes.OBSTRUCTION_DETECTED
@ -124,7 +126,7 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity):
class HomeKitWindowCover(HomeKitEntity, CoverEntity): class HomeKitWindowCover(HomeKitEntity, CoverEntity):
"""Representation of a HomeKit Window or Window Covering.""" """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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.POSITION_STATE, CharacteristicsTypes.POSITION_STATE,
@ -139,7 +141,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity):
] ]
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Flag supported features.""" """Flag supported features."""
features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
@ -161,45 +163,45 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity):
return features return features
@property @property
def current_cover_position(self): def current_cover_position(self) -> int:
"""Return the current position of cover.""" """Return the current position of cover."""
return self.service.value(CharacteristicsTypes.POSITION_CURRENT) return self.service.value(CharacteristicsTypes.POSITION_CURRENT)
@property @property
def is_closed(self): def is_closed(self) -> bool:
"""Return true if cover is closed, else False.""" """Return true if cover is closed, else False."""
return self.current_cover_position == 0 return self.current_cover_position == 0
@property @property
def is_closing(self): def is_closing(self) -> bool:
"""Return if the cover is closing or not.""" """Return if the cover is closing or not."""
value = self.service.value(CharacteristicsTypes.POSITION_STATE) value = self.service.value(CharacteristicsTypes.POSITION_STATE)
state = CURRENT_WINDOW_STATE_MAP[value] state = CURRENT_WINDOW_STATE_MAP[value]
return state == STATE_CLOSING return state == STATE_CLOSING
@property @property
def is_opening(self): def is_opening(self) -> bool:
"""Return if the cover is opening or not.""" """Return if the cover is opening or not."""
value = self.service.value(CharacteristicsTypes.POSITION_STATE) value = self.service.value(CharacteristicsTypes.POSITION_STATE)
state = CURRENT_WINDOW_STATE_MAP[value] state = CURRENT_WINDOW_STATE_MAP[value]
return state == STATE_OPENING return state == STATE_OPENING
@property @property
def is_horizontal_tilt(self): def is_horizontal_tilt(self) -> bool:
"""Return True if the service has a horizontal tilt characteristic.""" """Return True if the service has a horizontal tilt characteristic."""
return ( return (
self.service.value(CharacteristicsTypes.HORIZONTAL_TILT_CURRENT) is not None self.service.value(CharacteristicsTypes.HORIZONTAL_TILT_CURRENT) is not None
) )
@property @property
def is_vertical_tilt(self): def is_vertical_tilt(self) -> bool:
"""Return True if the service has a vertical tilt characteristic.""" """Return True if the service has a vertical tilt characteristic."""
return ( return (
self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) is not None self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) is not None
) )
@property @property
def current_cover_tilt_position(self): def current_cover_tilt_position(self) -> int:
"""Return current position of cover tilt.""" """Return current position of cover tilt."""
tilt_position = self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) tilt_position = self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT)
if not tilt_position: if not tilt_position:
@ -208,26 +210,26 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity):
) )
return tilt_position return tilt_position
async def async_stop_cover(self, **kwargs): async def async_stop_cover(self, **kwargs: Any) -> None:
"""Send hold command.""" """Send hold command."""
await self.async_put_characteristics({CharacteristicsTypes.POSITION_HOLD: 1}) 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.""" """Send open command."""
await self.async_set_cover_position(position=100) 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.""" """Send close command."""
await self.async_set_cover_position(position=0) 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.""" """Send position command."""
position = kwargs[ATTR_POSITION] position = kwargs[ATTR_POSITION]
await self.async_put_characteristics( await self.async_put_characteristics(
{CharacteristicsTypes.POSITION_TARGET: position} {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.""" """Move the cover tilt to a specific position."""
tilt_position = kwargs[ATTR_TILT_POSITION] tilt_position = kwargs[ATTR_TILT_POSITION]
if self.is_vertical_tilt: if self.is_vertical_tilt:
@ -240,7 +242,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity):
) )
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, Any]:
"""Return the optional state attributes.""" """Return the optional state attributes."""
obstruction_detected = self.service.value( obstruction_detected = self.service.value(
CharacteristicsTypes.OBSTRUCTION_DETECTED CharacteristicsTypes.OBSTRUCTION_DETECTED

View file

@ -1,6 +1,10 @@
"""Support for Homekit fans.""" """Support for Homekit fans."""
from __future__ import annotations
from typing import Any
from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.components.fan import ( from homeassistant.components.fan import (
DIRECTION_FORWARD, DIRECTION_FORWARD,
@ -30,9 +34,9 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
# This must be set in subclasses to the name of a boolean characteristic # This must be set in subclasses to the name of a boolean characteristic
# that controls whether the fan is on or off. # 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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.SWING_MODE, CharacteristicsTypes.SWING_MODE,
@ -42,12 +46,12 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
] ]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self.service.value(self.on_characteristic) == 1 return self.service.value(self.on_characteristic) == 1
@property @property
def percentage(self): def percentage(self) -> int:
"""Return the current speed percentage.""" """Return the current speed percentage."""
if not self.is_on: if not self.is_on:
return 0 return 0
@ -55,19 +59,19 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
return self.service.value(CharacteristicsTypes.ROTATION_SPEED) return self.service.value(CharacteristicsTypes.ROTATION_SPEED)
@property @property
def current_direction(self): def current_direction(self) -> str:
"""Return the current direction of the fan.""" """Return the current direction of the fan."""
direction = self.service.value(CharacteristicsTypes.ROTATION_DIRECTION) direction = self.service.value(CharacteristicsTypes.ROTATION_DIRECTION)
return HK_DIRECTION_TO_HA[direction] return HK_DIRECTION_TO_HA[direction]
@property @property
def oscillating(self): def oscillating(self) -> bool:
"""Return whether or not the fan is currently oscillating.""" """Return whether or not the fan is currently oscillating."""
oscillating = self.service.value(CharacteristicsTypes.SWING_MODE) oscillating = self.service.value(CharacteristicsTypes.SWING_MODE)
return oscillating == 1 return oscillating == 1
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Flag supported features.""" """Flag supported features."""
features = 0 features = 0
@ -83,20 +87,20 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
return features return features
@property @property
def speed_count(self): def speed_count(self) -> int:
"""Speed count for the fan.""" """Speed count for the fan."""
return round( return round(
min(self.service[CharacteristicsTypes.ROTATION_SPEED].maxValue or 100, 100) min(self.service[CharacteristicsTypes.ROTATION_SPEED].maxValue or 100, 100)
/ max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0) / 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.""" """Set the direction of the fan."""
await self.async_put_characteristics( await self.async_put_characteristics(
{CharacteristicsTypes.ROTATION_DIRECTION: DIRECTION_TO_HK[direction]} {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.""" """Set the speed of the fan."""
if percentage == 0: if percentage == 0:
return await self.async_turn_off() return await self.async_turn_off()
@ -105,17 +109,21 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
{CharacteristicsTypes.ROTATION_SPEED: percentage} {CharacteristicsTypes.ROTATION_SPEED: percentage}
) )
async def async_oscillate(self, oscillating: bool): async def async_oscillate(self, oscillating: bool) -> None:
"""Oscillate the fan.""" """Oscillate the fan."""
await self.async_put_characteristics( await self.async_put_characteristics(
{CharacteristicsTypes.SWING_MODE: 1 if oscillating else 0} {CharacteristicsTypes.SWING_MODE: 1 if oscillating else 0}
) )
async def async_turn_on( 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.""" """Turn the specified fan on."""
characteristics = {} characteristics: dict[str, Any] = {}
if not self.is_on: if not self.is_on:
characteristics[self.on_characteristic] = True characteristics[self.on_characteristic] = True
@ -126,7 +134,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
if characteristics: if characteristics:
await self.async_put_characteristics(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.""" """Turn the specified fan off."""
await self.async_put_characteristics({self.on_characteristic: False}) await self.async_put_characteristics({self.on_characteristic: False})
@ -159,7 +167,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if not (entity_class := ENTITY_TYPES.get(service.type)): if not (entity_class := ENTITY_TYPES.get(service.type)):
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}

View file

@ -1,8 +1,10 @@
"""Support for HomeKit Controller humidifier.""" """Support for HomeKit Controller humidifier."""
from __future__ import annotations from __future__ import annotations
from typing import Any
from aiohomekit.model.characteristics import CharacteristicsTypes 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 import HumidifierDeviceClass, HumidifierEntity
from homeassistant.components.humidifier.const import ( from homeassistant.components.humidifier.const import (
@ -37,7 +39,7 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity):
_attr_device_class = HumidifierDeviceClass.HUMIDIFIER _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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.ACTIVE, CharacteristicsTypes.ACTIVE,
@ -47,20 +49,20 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity):
] ]
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_FLAGS | SUPPORT_MODES return SUPPORT_FLAGS | SUPPORT_MODES
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self.service.value(CharacteristicsTypes.ACTIVE) 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.""" """Turn the specified valve on."""
await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) 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.""" """Turn the specified valve off."""
await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False})
@ -138,7 +140,7 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity):
_attr_device_class = HumidifierDeviceClass.DEHUMIDIFIER _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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.ACTIVE, CharacteristicsTypes.ACTIVE,
@ -149,20 +151,20 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity):
] ]
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_FLAGS | SUPPORT_MODES return SUPPORT_FLAGS | SUPPORT_MODES
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self.service.value(CharacteristicsTypes.ACTIVE) 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.""" """Turn the specified valve on."""
await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) 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.""" """Turn the specified valve off."""
await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False})
@ -251,13 +253,13 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if service.type != ServicesTypes.HUMIDIFIER_DEHUMIDIFIER: if service.type != ServicesTypes.HUMIDIFIER_DEHUMIDIFIER:
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
entities = [] entities: list[HumidifierEntity] = []
if service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD): if service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD):
entities.append(HomeKitHumidifier(conn, info)) entities.append(HomeKitHumidifier(conn, info))

View file

@ -1,6 +1,10 @@
"""Support for Homekit lights.""" """Support for Homekit lights."""
from __future__ import annotations
from typing import Any
from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
@ -28,7 +32,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if service.type != ServicesTypes.LIGHTBULB: if service.type != ServicesTypes.LIGHTBULB:
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
@ -41,7 +45,7 @@ async def async_setup_entry(
class HomeKitLight(HomeKitEntity, LightEntity): class HomeKitLight(HomeKitEntity, LightEntity):
"""Representation of a Homekit light.""" """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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.ON, CharacteristicsTypes.ON,
@ -52,17 +56,17 @@ class HomeKitLight(HomeKitEntity, LightEntity):
] ]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self.service.value(CharacteristicsTypes.ON) return self.service.value(CharacteristicsTypes.ON)
@property @property
def brightness(self): def brightness(self) -> int:
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
return self.service.value(CharacteristicsTypes.BRIGHTNESS) * 255 / 100 return self.service.value(CharacteristicsTypes.BRIGHTNESS) * 255 / 100
@property @property
def hs_color(self): def hs_color(self) -> tuple[float, float]:
"""Return the color property.""" """Return the color property."""
return ( return (
self.service.value(CharacteristicsTypes.HUE), self.service.value(CharacteristicsTypes.HUE),
@ -70,12 +74,12 @@ class HomeKitLight(HomeKitEntity, LightEntity):
) )
@property @property
def color_temp(self): def color_temp(self) -> int:
"""Return the color temperature.""" """Return the color temperature."""
return self.service.value(CharacteristicsTypes.COLOR_TEMPERATURE) return self.service.value(CharacteristicsTypes.COLOR_TEMPERATURE)
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Flag supported features.""" """Flag supported features."""
features = 0 features = 0
@ -93,7 +97,7 @@ class HomeKitLight(HomeKitEntity, LightEntity):
return features return features
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the specified light on.""" """Turn the specified light on."""
hs_color = kwargs.get(ATTR_HS_COLOR) hs_color = kwargs.get(ATTR_HS_COLOR)
temperature = kwargs.get(ATTR_COLOR_TEMP) temperature = kwargs.get(ATTR_COLOR_TEMP)
@ -121,6 +125,6 @@ class HomeKitLight(HomeKitEntity, LightEntity):
await self.async_put_characteristics(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 light off.""" """Turn the specified light off."""
await self.async_put_characteristics({CharacteristicsTypes.ON: False}) await self.async_put_characteristics({CharacteristicsTypes.ON: False})

View file

@ -1,6 +1,10 @@
"""Support for HomeKit Controller locks.""" """Support for HomeKit Controller locks."""
from __future__ import annotations
from typing import Any
from aiohomekit.model.characteristics import CharacteristicsTypes 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.components.lock import STATE_JAMMED, LockEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -37,7 +41,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if service.type != ServicesTypes.LOCK_MECHANISM: if service.type != ServicesTypes.LOCK_MECHANISM:
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
@ -50,7 +54,7 @@ async def async_setup_entry(
class HomeKitLock(HomeKitEntity, LockEntity): class HomeKitLock(HomeKitEntity, LockEntity):
"""Representation of a HomeKit Controller Lock.""" """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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE, CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE,
@ -59,7 +63,7 @@ class HomeKitLock(HomeKitEntity, LockEntity):
] ]
@property @property
def is_locked(self): def is_locked(self) -> bool | None:
"""Return true if device is locked.""" """Return true if device is locked."""
value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE) value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE)
if CURRENT_STATE_MAP[value] == STATE_UNKNOWN: if CURRENT_STATE_MAP[value] == STATE_UNKNOWN:
@ -67,7 +71,7 @@ class HomeKitLock(HomeKitEntity, LockEntity):
return CURRENT_STATE_MAP[value] == STATE_LOCKED return CURRENT_STATE_MAP[value] == STATE_LOCKED
@property @property
def is_locking(self): def is_locking(self) -> bool:
"""Return true if device is locking.""" """Return true if device is locking."""
current_value = self.service.value( current_value = self.service.value(
CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE
@ -81,7 +85,7 @@ class HomeKitLock(HomeKitEntity, LockEntity):
) )
@property @property
def is_unlocking(self): def is_unlocking(self) -> bool:
"""Return true if device is unlocking.""" """Return true if device is unlocking."""
current_value = self.service.value( current_value = self.service.value(
CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE
@ -95,27 +99,27 @@ class HomeKitLock(HomeKitEntity, LockEntity):
) )
@property @property
def is_jammed(self): def is_jammed(self) -> bool:
"""Return true if device is jammed.""" """Return true if device is jammed."""
value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE) value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE)
return CURRENT_STATE_MAP[value] == STATE_JAMMED return CURRENT_STATE_MAP[value] == STATE_JAMMED
async def async_lock(self, **kwargs): async def async_lock(self, **kwargs: Any) -> None:
"""Lock the device.""" """Lock the device."""
await self._set_lock_state(STATE_LOCKED) await self._set_lock_state(STATE_LOCKED)
async def async_unlock(self, **kwargs): async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the device.""" """Unlock the device."""
await self._set_lock_state(STATE_UNLOCKED) 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.""" """Send state command."""
await self.async_put_characteristics( await self.async_put_characteristics(
{CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: TARGET_STATE_MAP[state]} {CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: TARGET_STATE_MAP[state]}
) )
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, Any]:
"""Return the optional state attributes.""" """Return the optional state attributes."""
attributes = {} attributes = {}

View file

@ -1,4 +1,6 @@
"""Support for HomeKit Controller Televisions.""" """Support for HomeKit Controller Televisions."""
from __future__ import annotations
import logging import logging
from aiohomekit.model.characteristics import ( from aiohomekit.model.characteristics import (
@ -7,7 +9,7 @@ from aiohomekit.model.characteristics import (
RemoteKeyValues, RemoteKeyValues,
TargetMediaStateValues, TargetMediaStateValues,
) )
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from aiohomekit.utils import clamp_enum_to_char from aiohomekit.utils import clamp_enum_to_char
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
@ -53,7 +55,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if service.type != ServicesTypes.TELEVISION: if service.type != ServicesTypes.TELEVISION:
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
@ -68,7 +70,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
_attr_device_class = MediaPlayerDeviceClass.TV _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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.ACTIVE, CharacteristicsTypes.ACTIVE,
@ -82,7 +84,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
] ]
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Flag media player features that are supported.""" """Flag media player features that are supported."""
features = 0 features = 0
@ -108,10 +110,10 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
return features return features
@property @property
def supported_media_states(self): def supported_media_states(self) -> set[TargetMediaStateValues]:
"""Mediate state flags that are supported.""" """Mediate state flags that are supported."""
if not self.service.has(CharacteristicsTypes.TARGET_MEDIA_STATE): if not self.service.has(CharacteristicsTypes.TARGET_MEDIA_STATE):
return frozenset() return set()
return clamp_enum_to_char( return clamp_enum_to_char(
TargetMediaStateValues, TargetMediaStateValues,
@ -119,17 +121,17 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
) )
@property @property
def supported_remote_keys(self): def supported_remote_keys(self) -> set[str]:
"""Remote key buttons that are supported.""" """Remote key buttons that are supported."""
if not self.service.has(CharacteristicsTypes.REMOTE_KEY): if not self.service.has(CharacteristicsTypes.REMOTE_KEY):
return frozenset() return set()
return clamp_enum_to_char( return clamp_enum_to_char(
RemoteKeyValues, self.service[CharacteristicsTypes.REMOTE_KEY] RemoteKeyValues, self.service[CharacteristicsTypes.REMOTE_KEY]
) )
@property @property
def source_list(self): def source_list(self) -> list[str]:
"""List of all input sources for this television.""" """List of all input sources for this television."""
sources = [] sources = []
@ -147,7 +149,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
return sources return sources
@property @property
def source(self): def source(self) -> str | None:
"""Name of the current input source.""" """Name of the current input source."""
active_identifier = self.service.value(CharacteristicsTypes.ACTIVE_IDENTIFIER) active_identifier = self.service.value(CharacteristicsTypes.ACTIVE_IDENTIFIER)
if not active_identifier: if not active_identifier:
@ -165,7 +167,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
return char.value return char.value
@property @property
def state(self): def state(self) -> str:
"""State of the tv.""" """State of the tv."""
active = self.service.value(CharacteristicsTypes.ACTIVE) active = self.service.value(CharacteristicsTypes.ACTIVE)
if not active: if not active:
@ -177,7 +179,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
return STATE_OK return STATE_OK
async def async_media_play(self): async def async_media_play(self) -> None:
"""Send play command.""" """Send play command."""
if self.state == STATE_PLAYING: if self.state == STATE_PLAYING:
_LOGGER.debug("Cannot play while already playing") _LOGGER.debug("Cannot play while already playing")
@ -192,7 +194,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
{CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE}
) )
async def async_media_pause(self): async def async_media_pause(self) -> None:
"""Send pause command.""" """Send pause command."""
if self.state == STATE_PAUSED: if self.state == STATE_PAUSED:
_LOGGER.debug("Cannot pause while already paused") _LOGGER.debug("Cannot pause while already paused")
@ -207,7 +209,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
{CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE}
) )
async def async_media_stop(self): async def async_media_stop(self) -> None:
"""Send stop command.""" """Send stop command."""
if self.state == STATE_IDLE: if self.state == STATE_IDLE:
_LOGGER.debug("Cannot stop when already idle") _LOGGER.debug("Cannot stop when already idle")
@ -218,7 +220,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity):
{CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.STOP} {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.""" """Switch to a different media source."""
this_accessory = self._accessory.entity_map.aid(self._aid) this_accessory = self._accessory.entity_map.aid(self._aid)
this_tv = this_accessory.services.iid(self._iid) this_tv = this_accessory.services.iid(self._iid)

View file

@ -13,8 +13,10 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNOWN_DEVICES, CharacteristicEntity from . import KNOWN_DEVICES, CharacteristicEntity
from .connection import HKDevice
NUMBER_ENTITIES: dict[str, NumberEntityDescription] = { NUMBER_ENTITIES: dict[str, NumberEntityDescription] = {
CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: NumberEntityDescription( CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: NumberEntityDescription(
@ -90,7 +92,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_characteristic(char: Characteristic): def async_add_characteristic(char: Characteristic) -> bool:
entities = [] entities = []
info = {"aid": char.service.accessory.aid, "iid": char.service.iid} info = {"aid": char.service.accessory.aid, "iid": char.service.iid}
@ -112,11 +114,11 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity):
def __init__( def __init__(
self, self,
conn, conn: HKDevice,
info, info: ConfigType,
char, char: Characteristic,
description: NumberEntityDescription, description: NumberEntityDescription,
): ) -> None:
"""Initialise a HomeKit number control.""" """Initialise a HomeKit number control."""
self.entity_description = description self.entity_description = description
super().__init__(conn, info, char) super().__init__(conn, info, char)
@ -128,7 +130,7 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity):
return f"{prefix} {self.entity_description.name}" return f"{prefix} {self.entity_description.name}"
return 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.""" """Define the homekit characteristics the entity is tracking."""
return [self._char.type] return [self._char.type]
@ -152,7 +154,7 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity):
"""Return the current characteristic value.""" """Return the current characteristic value."""
return self._char.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.""" """Set the characteristic to this value."""
await self.async_put_characteristics( await self.async_put_characteristics(
{ {
@ -164,7 +166,7 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity):
class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity): class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity):
"""Representation of a Number control for Ecobee Fan Mode request.""" """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.""" """Define the homekit characteristics the entity is tracking."""
return [self._char.type] return [self._char.type]
@ -196,7 +198,7 @@ class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity):
"""Return the current characteristic value.""" """Return the current characteristic value."""
return self._char.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.""" """Set the characteristic to this value."""
# Sending the fan mode request sometimes ends up getting ignored by ecobee # Sending the fan mode request sometimes ends up getting ignored by ecobee

View file

@ -32,7 +32,7 @@ class EcobeeModeSelect(CharacteristicEntity, SelectEntity):
return f"{name} Current Mode" return f"{name} Current Mode"
return "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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE, CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE,
@ -61,7 +61,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_characteristic(char: Characteristic): def async_add_characteristic(char: Characteristic) -> bool:
if char.type == CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE: if char.type == CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE:
info = {"aid": char.service.accessory.aid, "iid": char.service.iid} info = {"aid": char.service.accessory.aid, "iid": char.service.iid}
async_add_entities([EcobeeModeSelect(conn, info, char)]) async_add_entities([EcobeeModeSelect(conn, info, char)])

View file

@ -5,7 +5,7 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes 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 ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -28,8 +28,10 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity
from .connection import HKDevice
CO2_ICON = "mdi:molecule-co2" CO2_ICON = "mdi:molecule-co2"
@ -203,17 +205,17 @@ class HomeKitHumiditySensor(HomeKitEntity, SensorEntity):
_attr_device_class = SensorDeviceClass.HUMIDITY _attr_device_class = SensorDeviceClass.HUMIDITY
_attr_native_unit_of_measurement = PERCENTAGE _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT] return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT]
@property @property
def name(self): def name(self) -> str:
"""Return the name of the device.""" """Return the name of the device."""
return f"{super().name} Humidity" return f"{super().name} Humidity"
@property @property
def native_value(self): def native_value(self) -> float:
"""Return the current humidity.""" """Return the current humidity."""
return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
@ -224,17 +226,17 @@ class HomeKitTemperatureSensor(HomeKitEntity, SensorEntity):
_attr_device_class = SensorDeviceClass.TEMPERATURE _attr_device_class = SensorDeviceClass.TEMPERATURE
_attr_native_unit_of_measurement = TEMP_CELSIUS _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.TEMPERATURE_CURRENT] return [CharacteristicsTypes.TEMPERATURE_CURRENT]
@property @property
def name(self): def name(self) -> str:
"""Return the name of the device.""" """Return the name of the device."""
return f"{super().name} Temperature" return f"{super().name} Temperature"
@property @property
def native_value(self): def native_value(self) -> float:
"""Return the current temperature in Celsius.""" """Return the current temperature in Celsius."""
return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT)
@ -245,17 +247,17 @@ class HomeKitLightSensor(HomeKitEntity, SensorEntity):
_attr_device_class = SensorDeviceClass.ILLUMINANCE _attr_device_class = SensorDeviceClass.ILLUMINANCE
_attr_native_unit_of_measurement = LIGHT_LUX _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT] return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT]
@property @property
def name(self): def name(self) -> str:
"""Return the name of the device.""" """Return the name of the device."""
return f"{super().name} Light Level" return f"{super().name} Light Level"
@property @property
def native_value(self): def native_value(self) -> int:
"""Return the current light level in lux.""" """Return the current light level in lux."""
return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT) return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT)
@ -266,17 +268,17 @@ class HomeKitCarbonDioxideSensor(HomeKitEntity, SensorEntity):
_attr_icon = CO2_ICON _attr_icon = CO2_ICON
_attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION _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.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL] return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL]
@property @property
def name(self): def name(self) -> str:
"""Return the name of the device.""" """Return the name of the device."""
return f"{super().name} CO2" return f"{super().name} CO2"
@property @property
def native_value(self): def native_value(self) -> int:
"""Return the current CO2 level in ppm.""" """Return the current CO2 level in ppm."""
return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL) return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL)
@ -287,7 +289,7 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity):
_attr_device_class = SensorDeviceClass.BATTERY _attr_device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = PERCENTAGE _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.""" """Define the homekit characteristics the entity is tracking."""
return [ return [
CharacteristicsTypes.BATTERY_LEVEL, CharacteristicsTypes.BATTERY_LEVEL,
@ -296,12 +298,12 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity):
] ]
@property @property
def name(self): def name(self) -> str:
"""Return the name of the device.""" """Return the name of the device."""
return f"{super().name} Battery" return f"{super().name} Battery"
@property @property
def icon(self): def icon(self) -> str:
"""Return the sensor icon.""" """Return the sensor icon."""
if not self.available or self.state is None: if not self.available or self.state is None:
return "mdi:battery-unknown" return "mdi:battery-unknown"
@ -323,12 +325,12 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity):
return icon return icon
@property @property
def is_low_battery(self): def is_low_battery(self) -> bool:
"""Return true if battery level is low.""" """Return true if battery level is low."""
return self.service.value(CharacteristicsTypes.STATUS_LO_BATT) == 1 return self.service.value(CharacteristicsTypes.STATUS_LO_BATT) == 1
@property @property
def is_charging(self): def is_charging(self) -> bool:
"""Return true if currently charing.""" """Return true if currently charing."""
# 0 = not charging # 0 = not charging
# 1 = charging # 1 = charging
@ -336,7 +338,7 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity):
return self.service.value(CharacteristicsTypes.CHARGING_STATE) == 1 return self.service.value(CharacteristicsTypes.CHARGING_STATE) == 1
@property @property
def native_value(self): def native_value(self) -> int:
"""Return the current battery level percentage.""" """Return the current battery level percentage."""
return self.service.value(CharacteristicsTypes.BATTERY_LEVEL) return self.service.value(CharacteristicsTypes.BATTERY_LEVEL)
@ -356,16 +358,16 @@ class SimpleSensor(CharacteristicEntity, SensorEntity):
def __init__( def __init__(
self, self,
conn, conn: HKDevice,
info, info: ConfigType,
char, char: Characteristic,
description: HomeKitSensorEntityDescription, description: HomeKitSensorEntityDescription,
): ) -> None:
"""Initialise a secondary HomeKit characteristic sensor.""" """Initialise a secondary HomeKit characteristic sensor."""
self.entity_description = description self.entity_description = description
super().__init__(conn, info, char) 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.""" """Define the homekit characteristics the entity is tracking."""
return [self._char.type] return [self._char.type]
@ -375,7 +377,7 @@ class SimpleSensor(CharacteristicEntity, SensorEntity):
return f"{super().name} {self.entity_description.name}" return f"{super().name} {self.entity_description.name}"
@property @property
def native_value(self): def native_value(self) -> str | int | float:
"""Return the current sensor value.""" """Return the current sensor value."""
return self._char.value return self._char.value
@ -399,7 +401,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if not (entity_class := ENTITY_TYPES.get(service.type)): if not (entity_class := ENTITY_TYPES.get(service.type)):
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
@ -409,7 +411,7 @@ async def async_setup_entry(
conn.add_listener(async_add_service) conn.add_listener(async_add_service)
@callback @callback
def async_add_characteristic(char: Characteristic): def async_add_characteristic(char: Characteristic) -> bool:
if not (description := SIMPLE_SENSOR.get(char.type)): if not (description := SIMPLE_SENSOR.get(char.type)):
return False return False
if description.probe and not description.probe(char): if description.probe and not description.probe(char):

View file

@ -1,6 +1,10 @@
"""Helpers for HomeKit data stored in HA storage.""" """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 homeassistant.helpers.storage import Store
from .const import DOMAIN from .const import DOMAIN
@ -10,6 +14,19 @@ ENTITY_MAP_STORAGE_VERSION = 1
ENTITY_MAP_SAVE_DELAY = 10 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: class EntityMapStorage:
""" """
Holds a cache of entity structure data from a paired HomeKit device. Holds a cache of entity structure data from a paired HomeKit device.
@ -26,34 +43,36 @@ class EntityMapStorage:
very slow for these devices. very slow for these devices.
""" """
def __init__(self, hass): def __init__(self, hass: HomeAssistant) -> None:
"""Create a new entity map store.""" """Create a new entity map store."""
self.hass = hass self.hass = hass
self.store = Store(hass, ENTITY_MAP_STORAGE_VERSION, ENTITY_MAP_STORAGE_KEY) 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.""" """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 # There is no cached data about HomeKit devices yet
return return
self.storage_data = raw_storage.get("pairings", {}) 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.""" """Get a pairing cache item."""
return self.storage_data.get(homekit_id) return self.storage_data.get(homekit_id)
@callback @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.""" """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.storage_data[homekit_id] = data
self._async_schedule_save() self._async_schedule_save()
return data return data
@callback @callback
def async_delete_map(self, homekit_id): def async_delete_map(self, homekit_id: str) -> None:
"""Delete pairing cache.""" """Delete pairing cache."""
if homekit_id not in self.storage_data: if homekit_id not in self.storage_data:
return return
@ -62,11 +81,11 @@ class EntityMapStorage:
self._async_schedule_save() self._async_schedule_save()
@callback @callback
def _async_schedule_save(self): def _async_schedule_save(self) -> None:
"""Schedule saving the entity map cache.""" """Schedule saving the entity map cache."""
self.store.async_delay_save(self._data_to_save, ENTITY_MAP_SAVE_DELAY) self.store.async_delay_save(self._data_to_save, ENTITY_MAP_SAVE_DELAY)
@callback @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 data of entity map to store in a file."""
return {"pairings": self.storage_data} return {"pairings": self.storage_data}

View file

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from aiohomekit.model.characteristics import ( from aiohomekit.model.characteristics import (
Characteristic, Characteristic,
@ -9,15 +10,17 @@ from aiohomekit.model.characteristics import (
InUseValues, InUseValues,
IsConfiguredValues, IsConfiguredValues,
) )
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity
from .connection import HKDevice
OUTLET_IN_USE = "outlet_in_use" OUTLET_IN_USE = "outlet_in_use"
@ -53,35 +56,36 @@ SWITCH_ENTITIES: dict[str, DeclarativeSwitchEntityDescription] = {
class HomeKitSwitch(HomeKitEntity, SwitchEntity): class HomeKitSwitch(HomeKitEntity, SwitchEntity):
"""Representation of a Homekit switch.""" """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.""" """Define the homekit characteristics the entity cares about."""
return [CharacteristicsTypes.ON, CharacteristicsTypes.OUTLET_IN_USE] return [CharacteristicsTypes.ON, CharacteristicsTypes.OUTLET_IN_USE]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self.service.value(CharacteristicsTypes.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.""" """Turn the specified switch on."""
await self.async_put_characteristics({CharacteristicsTypes.ON: True}) 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.""" """Turn the specified switch off."""
await self.async_put_characteristics({CharacteristicsTypes.ON: False}) await self.async_put_characteristics({CharacteristicsTypes.ON: False})
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the optional state attributes.""" """Return the optional state attributes."""
outlet_in_use = self.service.value(CharacteristicsTypes.OUTLET_IN_USE) outlet_in_use = self.service.value(CharacteristicsTypes.OUTLET_IN_USE)
if outlet_in_use is not None: if outlet_in_use is not None:
return {OUTLET_IN_USE: outlet_in_use} return {OUTLET_IN_USE: outlet_in_use}
return None
class HomeKitValve(HomeKitEntity, SwitchEntity): class HomeKitValve(HomeKitEntity, SwitchEntity):
"""Represents a valve in an irrigation system.""" """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.""" """Define the homekit characteristics the entity cares about."""
return [ return [
CharacteristicsTypes.ACTIVE, CharacteristicsTypes.ACTIVE,
@ -90,11 +94,11 @@ class HomeKitValve(HomeKitEntity, SwitchEntity):
CharacteristicsTypes.REMAINING_DURATION, CharacteristicsTypes.REMAINING_DURATION,
] ]
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the specified valve on.""" """Turn the specified valve on."""
await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) 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.""" """Turn the specified valve off."""
await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False})
@ -104,12 +108,12 @@ class HomeKitValve(HomeKitEntity, SwitchEntity):
return "mdi:water" return "mdi:water"
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self.service.value(CharacteristicsTypes.ACTIVE) return self.service.value(CharacteristicsTypes.ACTIVE)
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, Any]:
"""Return the optional state attributes.""" """Return the optional state attributes."""
attrs = {} attrs = {}
@ -133,13 +137,13 @@ class DeclarativeCharacteristicSwitch(CharacteristicEntity, SwitchEntity):
def __init__( def __init__(
self, self,
conn, conn: HKDevice,
info, info: ConfigType,
char, char: Characteristic,
description: DeclarativeSwitchEntityDescription, description: DeclarativeSwitchEntityDescription,
): ) -> None:
"""Initialise a HomeKit switch.""" """Initialise a HomeKit switch."""
self.entity_description = description self.entity_description: DeclarativeSwitchEntityDescription = description
super().__init__(conn, info, char) super().__init__(conn, info, char)
@property @property
@ -149,22 +153,22 @@ class DeclarativeCharacteristicSwitch(CharacteristicEntity, SwitchEntity):
return f"{prefix} {self.entity_description.name}" return f"{prefix} {self.entity_description.name}"
return 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.""" """Define the homekit characteristics the entity cares about."""
return [self._char.type] return [self._char.type]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self._char.value == self.entity_description.true_value 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.""" """Turn the specified switch on."""
await self.async_put_characteristics( await self.async_put_characteristics(
{self._char.type: self.entity_description.true_value} {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.""" """Turn the specified switch off."""
await self.async_put_characteristics( await self.async_put_characteristics(
{self._char.type: self.entity_description.false_value} {self._char.type: self.entity_description.false_value}
@ -188,7 +192,7 @@ async def async_setup_entry(
conn = hass.data[KNOWN_DEVICES][hkid] conn = hass.data[KNOWN_DEVICES][hkid]
@callback @callback
def async_add_service(service): def async_add_service(service: Service) -> bool:
if not (entity_class := ENTITY_TYPES.get(service.type)): if not (entity_class := ENTITY_TYPES.get(service.type)):
return False return False
info = {"aid": service.accessory.aid, "iid": service.iid} info = {"aid": service.accessory.aid, "iid": service.iid}
@ -198,7 +202,7 @@ async def async_setup_entry(
conn.add_listener(async_add_service) conn.add_listener(async_add_service)
@callback @callback
def async_add_characteristic(char: Characteristic): def async_add_characteristic(char: Characteristic) -> bool:
if not (description := SWITCH_ENTITIES.get(char.type)): if not (description := SWITCH_ENTITIES.get(char.type)):
return False return False