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