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