Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Homekit device discovery."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import contextlib
7 import logging
8 
9 import aiohomekit
10 from aiohomekit.const import (
11  BLE_TRANSPORT_SUPPORTED,
12  COAP_TRANSPORT_SUPPORTED,
13  IP_TRANSPORT_SUPPORTED,
14 )
15 from aiohomekit.exceptions import (
16  AccessoryDisconnectedError,
17  AccessoryNotFoundError,
18  EncryptionError,
19 )
20 
21 from homeassistant.config_entries import ConfigEntry
22 from homeassistant.const import ATTR_IDENTIFIERS, EVENT_HOMEASSISTANT_STOP
23 from homeassistant.core import Event, HomeAssistant
24 from homeassistant.exceptions import ConfigEntryNotReady
25 from homeassistant.helpers import config_validation as cv, device_registry as dr
26 from homeassistant.helpers.typing import ConfigType
27 from homeassistant.util.async_ import create_eager_task
28 
29 from .config_flow import normalize_hkid
30 from .connection import HKDevice
31 from .const import DOMAIN, KNOWN_DEVICES
32 from .utils import async_get_controller
33 
34 # Ensure all the controllers get imported in the executor
35 # since they are loaded late.
36 if BLE_TRANSPORT_SUPPORTED:
37  from aiohomekit.controller import ble # noqa: F401
38 if COAP_TRANSPORT_SUPPORTED:
39  from aiohomekit.controller import coap # noqa: F401
40 if IP_TRANSPORT_SUPPORTED:
41  from aiohomekit.controller import ip # noqa: F401
42 
43 _LOGGER = logging.getLogger(__name__)
44 
45 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
46 
47 
48 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
49  """Set up a HomeKit connection on a config entry."""
50  conn = HKDevice(hass, entry, entry.data)
51  hass.data[KNOWN_DEVICES][conn.unique_id] = conn
52 
53  # For backwards compat
54  if entry.unique_id is None:
55  hass.config_entries.async_update_entry(
56  entry, unique_id=normalize_hkid(conn.unique_id)
57  )
58 
59  try:
60  await conn.async_setup()
61  except (
62  TimeoutError,
63  AccessoryNotFoundError,
64  EncryptionError,
65  AccessoryDisconnectedError,
66  ) as ex:
67  del hass.data[KNOWN_DEVICES][conn.unique_id]
68  with contextlib.suppress(TimeoutError):
69  await conn.pairing.close()
70  raise ConfigEntryNotReady from ex
71 
72  return True
73 
74 
75 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
76  """Set up for Homekit devices."""
77  await async_get_controller(hass)
78 
79  hass.data[KNOWN_DEVICES] = {}
80 
81  async def _async_stop_homekit_controller(event: Event) -> None:
82  await asyncio.gather(
83  *(
84  create_eager_task(connection.async_unload())
85  for connection in hass.data[KNOWN_DEVICES].values()
86  )
87  )
88 
89  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_homekit_controller)
90 
91  return True
92 
93 
94 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
95  """Disconnect from HomeKit devices before unloading entry."""
96  hkid = entry.data["AccessoryPairingID"]
97 
98  if hkid in hass.data[KNOWN_DEVICES]:
99  connection: HKDevice = hass.data[KNOWN_DEVICES][hkid]
100  await connection.async_unload()
101 
102  return True
103 
104 
105 async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
106  """Cleanup caches before removing config entry."""
107  hkid = entry.data["AccessoryPairingID"]
108 
109  controller = await async_get_controller(hass)
110 
111  # Remove the pairing on the device, making the device discoverable again.
112  # Don't reuse any objects in hass.data as they are already unloaded
113  controller.load_pairing(hkid, dict(entry.data))
114  try:
115  await controller.remove_pairing(hkid)
116  except aiohomekit.AccessoryDisconnectedError:
117  _LOGGER.warning(
118  (
119  "Accessory %s was removed from HomeAssistant but was not reachable "
120  "to properly unpair. It may need resetting before you can use it with "
121  "HomeKit again"
122  ),
123  entry.title,
124  )
125 
126 
128  hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
129 ) -> bool:
130  """Remove homekit_controller config entry from a device."""
131  hkid = config_entry.data["AccessoryPairingID"]
132  connection: HKDevice = hass.data[KNOWN_DEVICES][hkid]
133  return not device_entry.identifiers.intersection(
134  identifier
135  for accessory in connection.entity_map.accessories
136  for identifier in connection.device_info_for_accessory(accessory)[
137  ATTR_IDENTIFIERS
138  ]
139  )
Controller async_get_controller(HomeAssistant hass)
Definition: utils.py:47
None async_remove_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:105
bool async_remove_config_entry_device(HomeAssistant hass, ConfigEntry config_entry, dr.DeviceEntry device_entry)
Definition: __init__.py:129
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:94
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:48
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:75