Home Assistant Unofficial Reference 2024.12.1
entity_loader.py
Go to the documentation of this file.
1 """UniFi Network entity loader.
2 
3 Central point to load entities for the different platforms.
4 Make sure expected clients are available for platforms.
5 """
6 
7 from __future__ import annotations
8 
9 import asyncio
10 from collections.abc import Callable, Coroutine, Sequence
11 from datetime import timedelta
12 from functools import partial
13 from typing import TYPE_CHECKING, Any
14 
15 from aiounifi.interfaces.api_handlers import ItemEvent
16 
17 from homeassistant.const import Platform
18 from homeassistant.core import callback
19 from homeassistant.helpers import entity_registry as er
20 from homeassistant.helpers.dispatcher import async_dispatcher_connect
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
23 
24 from ..const import LOGGER, UNIFI_WIRELESS_CLIENTS
25 from ..entity import UnifiEntity, UnifiEntityDescription
26 
27 if TYPE_CHECKING:
28  from .hub import UnifiHub
29 
30 CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1)
31 POLL_INTERVAL = timedelta(seconds=10)
32 
33 
35  """UniFi Network integration handling platforms for entity registration."""
36 
37  def __init__(self, hub: UnifiHub) -> None:
38  """Initialize the UniFi entity loader."""
39  self.hubhub = hub
40  self.api_updatersapi_updaters = (
41  hub.api.clients.update,
42  hub.api.clients_all.update,
43  hub.api.devices.update,
44  hub.api.dpi_apps.update,
45  hub.api.dpi_groups.update,
46  hub.api.port_forwarding.update,
47  hub.api.sites.update,
48  hub.api.system_information.update,
49  hub.api.traffic_rules.update,
50  hub.api.wlans.update,
51  )
52  self.polling_api_updaterspolling_api_updaters = (hub.api.traffic_rules.update,)
53  self.wireless_clientswireless_clients = hub.hass.data[UNIFI_WIRELESS_CLIENTS]
54 
55  self._dataUpdateCoordinator_dataUpdateCoordinator = DataUpdateCoordinator(
56  hub.hass,
57  LOGGER,
58  name="Unifi entity poller",
59  update_method=self._update_pollable_api_data_update_pollable_api_data,
60  update_interval=POLL_INTERVAL,
61  )
62 
63  self._update_listener_update_listener = self._dataUpdateCoordinator_dataUpdateCoordinator.async_add_listener(
64  update_callback=lambda: None
65  )
66 
67  self.platforms: list[
68  tuple[
69  AddEntitiesCallback,
70  type[UnifiEntity],
71  tuple[UnifiEntityDescription, ...],
72  bool,
73  ]
74  ] = []
75 
76  self.known_objects: set[tuple[str, str]] = set()
77  """Tuples of entity description key and object ID of loaded entities."""
78 
79  async def initialize(self) -> None:
80  """Initialize API data and extra client support."""
81  await self._refresh_api_data_refresh_api_data()
82  self._restore_inactive_clients_restore_inactive_clients()
83  self.wireless_clientswireless_clients.update_clients(set(self.hubhub.api.clients.values()))
84 
85  async def _refresh_data(
86  self, updaters: Sequence[Callable[[], Coroutine[Any, Any, None]]]
87  ) -> None:
88  results = await asyncio.gather(
89  *[update() for update in updaters],
90  return_exceptions=True,
91  )
92  for result in results:
93  if result is not None:
94  LOGGER.warning("Exception on update %s", result)
95 
96  async def _update_pollable_api_data(self) -> None:
97  """Refresh API data for pollable updaters."""
98  await self._refresh_data_refresh_data(self.polling_api_updaterspolling_api_updaters)
99 
100  async def _refresh_api_data(self) -> None:
101  """Refresh API data from network application."""
102  await self._refresh_data_refresh_data(self.api_updatersapi_updaters)
103 
104  @callback
105  def _restore_inactive_clients(self) -> None:
106  """Restore inactive clients.
107 
108  Provide inactive clients to device tracker and switch platform.
109  """
110  config = self.hubhub.config
111  entity_registry = er.async_get(self.hubhub.hass)
112  macs: list[str] = [
113  entry.unique_id.split("-", 1)[1]
114  for entry in er.async_entries_for_config_entry(
115  entity_registry, config.entry.entry_id
116  )
117  if entry.domain == Platform.DEVICE_TRACKER and "-" in entry.unique_id
118  ]
119  api = self.hubhub.api
120  for mac in config.option_supported_clients + config.option_block_clients + macs:
121  if mac not in api.clients and mac in api.clients_all:
122  api.clients.process_raw([dict(api.clients_all[mac].raw)])
123 
124  @callback
126  self,
127  async_add_entities: AddEntitiesCallback,
128  entity_class: type[UnifiEntity],
129  descriptions: tuple[UnifiEntityDescription, ...],
130  requires_admin: bool = False,
131  ) -> None:
132  """Register UniFi entity platforms."""
133  self.platforms.append(
134  (async_add_entities, entity_class, descriptions, requires_admin)
135  )
136 
137  @callback
138  def load_entities(self) -> None:
139  """Load entities into the registered UniFi platforms."""
140  for (
141  async_add_entities,
142  entity_class,
143  descriptions,
144  requires_admin,
145  ) in self.platforms:
146  if requires_admin and not self.hubhub.is_admin:
147  continue
148  self._load_entities_load_entities(entity_class, descriptions, async_add_entities)
149 
150  @callback
152  self, description: UnifiEntityDescription, obj_id: str
153  ) -> bool:
154  """Validate if entity is allowed and supported before creating it."""
155  return bool(
156  (description.key, obj_id) not in self.known_objects
157  and description.allowed_fn(self.hubhub, obj_id)
158  and description.supported_fn(self.hubhub, obj_id)
159  )
160 
161  @callback
163  self,
164  unifi_platform_entity: type[UnifiEntity],
165  descriptions: tuple[UnifiEntityDescription, ...],
166  async_add_entities: AddEntitiesCallback,
167  ) -> None:
168  """Load entities and subscribe for future entities."""
169 
170  @callback
171  def add_unifi_entities() -> None:
172  """Add currently known UniFi entities."""
174  unifi_platform_entity(obj_id, self.hubhub, description)
175  for description in descriptions
176  for obj_id in description.api_handler_fn(self.hubhub.api)
177  if self._should_add_entity_should_add_entity(description, obj_id)
178  )
179 
180  add_unifi_entities()
181 
182  self.hubhub.config.entry.async_on_unload(
184  self.hubhub.hass,
185  self.hubhub.signal_options_update,
186  add_unifi_entities,
187  )
188  )
189 
190  # Subscribe for future entities
191 
192  @callback
193  def create_unifi_entity(
194  description: UnifiEntityDescription, event: ItemEvent, obj_id: str
195  ) -> None:
196  """Create new UniFi entity on event."""
197  if self._should_add_entity_should_add_entity(description, obj_id):
199  [unifi_platform_entity(obj_id, self.hubhub, description)]
200  )
201 
202  for description in descriptions:
203  description.api_handler_fn(self.hubhub.api).subscribe(
204  partial(create_unifi_entity, description), ItemEvent.ADDED
205  )
None _refresh_data(self, Sequence[Callable[[], Coroutine[Any, Any, None]]] updaters)
None _load_entities(self, type[UnifiEntity] unifi_platform_entity, tuple[UnifiEntityDescription,...] descriptions, AddEntitiesCallback async_add_entities)
bool _should_add_entity(self, UnifiEntityDescription description, str obj_id)
None register_platform(self, AddEntitiesCallback async_add_entities, type[UnifiEntity] entity_class, tuple[UnifiEntityDescription,...] descriptions, bool requires_admin=False)
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
Callable[[], None] subscribe(HomeAssistant hass, str topic, MessageCallbackType msg_callback, int qos=DEFAULT_QOS, str encoding="utf-8")
Definition: client.py:247
None async_add_listener(HomeAssistant hass, Callable[[], None] listener)
Definition: __init__.py:82
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103