Skip to content

Commit

Permalink
Only compute homekit_controller accessory_info when entity is added o…
Browse files Browse the repository at this point in the history
…r config changes (home-assistant#102145)
  • Loading branch information
bdraco committed Oct 17, 2023
1 parent e6895b5 commit d8e541a
Show file tree
Hide file tree
Showing 6 changed files with 642 additions and 33 deletions.
41 changes: 31 additions & 10 deletions homeassistant/components/homekit_controller/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import asyncio
from collections.abc import Callable, Iterable
from datetime import datetime, timedelta
from functools import partial
import logging
from operator import attrgetter
from types import MappingProxyType
Expand Down Expand Up @@ -144,6 +145,7 @@ def __init__(
)

self._availability_callbacks: set[CALLBACK_TYPE] = set()
self._config_changed_callbacks: set[CALLBACK_TYPE] = set()
self._subscriptions: dict[tuple[int, int], set[CALLBACK_TYPE]] = {}

@property
Expand Down Expand Up @@ -605,6 +607,8 @@ async def async_update_new_accessories_state(self) -> None:
await self.async_process_entity_map()
if self.watchable_characteristics:
await self.pairing.subscribe(self.watchable_characteristics)
for callback_ in self._config_changed_callbacks:
callback_()
await self.async_update()
await self.async_add_new_entities()

Expand Down Expand Up @@ -805,31 +809,48 @@ def process_new_events(
for callback_ in to_callback:
callback_()

@callback
def _remove_characteristics_callback(
self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE
) -> None:
"""Remove a characteristics callback."""
for aid_iid in characteristics:
self._subscriptions[aid_iid].remove(callback_)
if not self._subscriptions[aid_iid]:
del self._subscriptions[aid_iid]

@callback
def async_subscribe(
self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE
) -> CALLBACK_TYPE:
"""Add characteristics to the watch list."""
for aid_iid in characteristics:
self._subscriptions.setdefault(aid_iid, set()).add(callback_)
return partial(
self._remove_characteristics_callback, characteristics, callback_
)

def _unsub():
for aid_iid in characteristics:
self._subscriptions[aid_iid].remove(callback_)
if not self._subscriptions[aid_iid]:
del self._subscriptions[aid_iid]

return _unsub
@callback
def _remove_availability_callback(self, callback_: CALLBACK_TYPE) -> None:
"""Remove an availability callback."""
self._availability_callbacks.remove(callback_)

@callback
def async_subscribe_availability(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE:
"""Add characteristics to the watch list."""
self._availability_callbacks.add(callback_)
return partial(self._remove_availability_callback, callback_)

def _unsub():
self._availability_callbacks.remove(callback_)
@callback
def _remove_config_changed_callback(self, callback_: CALLBACK_TYPE) -> None:
"""Remove an availability callback."""
self._config_changed_callbacks.remove(callback_)

return _unsub
@callback
def async_subscribe_config_changed(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE:
"""Subscribe to config of the accessory being changed aka c# changes."""
self._config_changed_callbacks.add(callback_)
return partial(self._remove_config_changed_callback, callback_)

async def get_characteristics(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
"""Read latest state from homekit accessory."""
Expand Down
42 changes: 22 additions & 20 deletions homeassistant/components/homekit_controller/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

from typing import Any

from aiohomekit.model import Accessory
from aiohomekit.model.characteristics import (
EVENT_CHARACTERISTICS,
Characteristic,
CharacteristicPermissions,
CharacteristicsTypes,
)
from aiohomekit.model.services import Service, ServicesTypes
from aiohomekit.model.services import ServicesTypes

from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType
Expand All @@ -32,41 +32,43 @@ def __init__(self, accessory: HKDevice, devinfo: ConfigType) -> None:
self._iid = devinfo["iid"]
self._char_name: str | None = None
self.all_characteristics: set[tuple[int, int]] = set()
self._async_set_accessory_and_service()
self.setup()

super().__init__()

@property
def accessory(self) -> Accessory:
"""Return an Accessory model that this entity is attached to."""
return self._accessory.entity_map.aid(self._aid)

@property
def accessory_info(self) -> Service:
"""Information about the make and model of an accessory."""
return self.accessory.services.first(
@callback
def _async_set_accessory_and_service(self) -> None:
"""Set the accessory and service for this entity."""
accessory = self._accessory
self.accessory = accessory.entity_map.aid(self._aid)
self.service = self.accessory.services.iid(self._iid)
self.accessory_info = self.accessory.services.first(
service_type=ServicesTypes.ACCESSORY_INFORMATION
)

@property
def service(self) -> Service:
"""Return a Service model that this entity is attached to."""
return self.accessory.services.iid(self._iid)
@callback
def _async_config_changed(self) -> None:
"""Handle accessory discovery changes."""
self._async_set_accessory_and_service()
self.async_write_ha_state()

async def async_added_to_hass(self) -> None:
"""Entity added to hass."""
accessory = self._accessory
self.async_on_remove(
self._accessory.async_subscribe(
accessory.async_subscribe(
self.all_characteristics, self._async_write_ha_state
)
)
self.async_on_remove(
self._accessory.async_subscribe_availability(self._async_write_ha_state)
accessory.async_subscribe_availability(self._async_write_ha_state)
)
self._accessory.add_pollable_characteristics(self.pollable_characteristics)
await self._accessory.add_watchable_characteristics(
self.watchable_characteristics
self.async_on_remove(
accessory.async_subscribe_config_changed(self._async_config_changed)
)
accessory.add_pollable_characteristics(self.pollable_characteristics)
await accessory.add_watchable_characteristics(self.watchable_characteristics)

async def async_will_remove_from_hass(self) -> None:
"""Prepare to be removed from hass."""
Expand Down
7 changes: 4 additions & 3 deletions tests/components/homekit_controller/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import Any, Final
from unittest import mock

from aiohomekit.controller.abstract import AbstractPairing
from aiohomekit.hkjson import loads as hkloads
from aiohomekit.model import (
Accessories,
Expand Down Expand Up @@ -180,7 +181,7 @@ async def time_changed(hass, seconds):
await hass.async_block_till_done()


async def setup_accessories_from_file(hass, path):
async def setup_accessories_from_file(hass: HomeAssistant, path: str) -> Accessories:
"""Load an collection of accessory defs from JSON data."""
accessories_fixture = await hass.async_add_executor_job(
load_fixture, os.path.join("homekit_controller", path)
Expand Down Expand Up @@ -242,11 +243,11 @@ async def setup_test_accessories_with_controller(
return config_entry, pairing


async def device_config_changed(hass, accessories):
async def device_config_changed(hass: HomeAssistant, accessories: Accessories):
"""Discover new devices added to Home Assistant at runtime."""
# Update the accessories our FakePairing knows about
controller = hass.data[CONTROLLER]
pairing = controller.pairings["00:00:00:00:00:00"]
pairing: AbstractPairing = controller.pairings["00:00:00:00:00:00"]

accessories_obj = Accessories()
for accessory in accessories:
Expand Down
Loading

0 comments on commit d8e541a

Please sign in to comment.