Home Assistant Unofficial Reference 2024.12.1
utils.py
Go to the documentation of this file.
1 """Helper functions for the homekit_controller component."""
2 
3 from functools import lru_cache
4 from typing import cast
5 
6 from aiohomekit import Controller
7 
8 from homeassistant.components import bluetooth, zeroconf
9 from homeassistant.const import EVENT_HOMEASSISTANT_STOP
10 from homeassistant.core import Event, HomeAssistant
11 
12 from .const import CONTROLLER
13 from .storage import async_get_entity_storage
14 
15 type IidTuple = tuple[int, int | None, int | None]
16 
17 
18 def unique_id_to_iids(unique_id: str) -> IidTuple | None:
19  """Convert a unique_id to a tuple of accessory id, service iid and characteristic iid.
20 
21  Depending on the field in the accessory map that is referenced, some of these may be None.
22 
23  Returns None if this unique_id doesn't follow the homekit_controller scheme and is invalid.
24  """
25  try:
26  match unique_id.split("_"):
27  case (unique_id, aid, sid, cid):
28  return (int(aid), int(sid), int(cid))
29  case (unique_id, aid, sid):
30  return (int(aid), int(sid), None)
31  case (unique_id, aid):
32  return (int(aid), None, None)
33  except ValueError:
34  # One of the int conversions failed - this can't be a valid homekit_controller unique id
35  # Fall through and return None
36  pass
37 
38  return None
39 
40 
41 @lru_cache
42 def folded_name(name: str) -> str:
43  """Return a name that is used for matching a similar string."""
44  return name.casefold().replace(" ", "")
45 
46 
47 async def async_get_controller(hass: HomeAssistant) -> Controller:
48  """Get or create an aiohomekit Controller instance."""
49  if existing := hass.data.get(CONTROLLER):
50  return cast(Controller, existing)
51 
52  async_zeroconf_instance = await zeroconf.async_get_async_instance(hass)
53 
54  char_cache = await async_get_entity_storage(hass)
55 
56  # In theory another call to async_get_controller could have run while we were
57  # trying to get the zeroconf instance. So we check again to make sure we
58  # don't leak a Controller instance here.
59  if existing := hass.data.get(CONTROLLER):
60  return cast(Controller, existing)
61 
62  bleak_scanner_instance = bluetooth.async_get_scanner(hass)
63 
64  controller = Controller(
65  async_zeroconf_instance=async_zeroconf_instance,
66  bleak_scanner_instance=bleak_scanner_instance, # type: ignore[arg-type]
67  char_cache=char_cache,
68  )
69 
70  hass.data[CONTROLLER] = controller
71 
72  async def _async_stop_homekit_controller(event: Event) -> None:
73  # Pop first so that in theory another controller /could/ start
74  # While this one was shutting down
75  hass.data.pop(CONTROLLER, None)
76  await controller.async_stop()
77 
78  # Right now _async_stop_homekit_controller is only called on HA exiting
79  # So we don't have to worry about leaking a callback here.
80  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_homekit_controller)
81 
82  await controller.async_start()
83 
84  return controller
EntityMapStorage async_get_entity_storage(HomeAssistant hass)
Definition: storage.py:103
Controller async_get_controller(HomeAssistant hass)
Definition: utils.py:47
IidTuple|None unique_id_to_iids(str unique_id)
Definition: utils.py:18