Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Coordinator for the nuki component."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections import defaultdict
7 from datetime import timedelta
8 import logging
9 
10 from pynuki import NukiBridge, NukiLock, NukiOpener
11 from pynuki.bridge import InvalidCredentialsException
12 from pynuki.device import NukiDevice
13 from requests.exceptions import RequestException
14 
15 from homeassistant.const import Platform
16 from homeassistant.core import HomeAssistant
17 from homeassistant.helpers import entity_registry as er
18 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
19 
20 from .const import DOMAIN, ERROR_STATES
21 from .helpers import parse_id
22 
23 _LOGGER = logging.getLogger(__name__)
24 
25 UPDATE_INTERVAL = timedelta(seconds=30)
26 
27 
29  """Data Update Coordinator for the Nuki integration."""
30 
31  def __init__(
32  self,
33  hass: HomeAssistant,
34  bridge: NukiBridge,
35  locks: list[NukiLock],
36  openers: list[NukiOpener],
37  ) -> None:
38  """Initialize my coordinator."""
39  super().__init__(
40  hass,
41  _LOGGER,
42  # Name of the data. For logging purposes.
43  name="nuki devices",
44  # Polling interval. Will only be polled if there are subscribers.
45  update_interval=UPDATE_INTERVAL,
46  )
47  self.bridgebridge = bridge
48  self.lockslocks = locks
49  self.openersopeners = openers
50 
51  @property
52  def bridge_id(self):
53  """Return the parsed id of the Nuki bridge."""
54  return parse_id(self.bridgebridge.info()["ids"]["hardwareId"])
55 
56  async def _async_update_data(self) -> None:
57  """Fetch data from Nuki bridge."""
58  try:
59  # Note: TimeoutError and aiohttp.ClientError are already
60  # handled by the data update coordinator.
61  async with asyncio.timeout(10):
62  events = await self.hasshass.async_add_executor_job(
63  self.update_devicesupdate_devices, self.lockslocks + self.openersopeners
64  )
65  except InvalidCredentialsException as err:
66  raise UpdateFailed(f"Invalid credentials for Bridge: {err}") from err
67  except RequestException as err:
68  raise UpdateFailed(f"Error communicating with Bridge: {err}") from err
69 
70  ent_reg = er.async_get(self.hasshass)
71  for event, device_ids in events.items():
72  for device_id in device_ids:
73  entity_id = ent_reg.async_get_entity_id(
74  Platform.LOCK, DOMAIN, device_id
75  )
76  event_data = {
77  "entity_id": entity_id,
78  "type": event,
79  }
80  self.hasshass.bus.async_fire("nuki_event", event_data)
81 
82  def update_devices(self, devices: list[NukiDevice]) -> dict[str, set[str]]:
83  """Update the Nuki devices.
84 
85  Returns:
86  A dict with the events to be fired. The event type is the key and the device ids are the value
87 
88  """
89 
90  events: dict[str, set[str]] = defaultdict(set)
91 
92  for device in devices:
93  for level in (False, True):
94  try:
95  if isinstance(device, NukiOpener):
96  last_ring_action_state = device.ring_action_state
97 
98  device.update(level)
99 
100  if not last_ring_action_state and device.ring_action_state:
101  events["ring"].add(device.nuki_id)
102  else:
103  device.update(level)
104  except RequestException:
105  continue
106 
107  if device.state not in ERROR_STATES:
108  break
109 
110  return events
None __init__(self, HomeAssistant hass, NukiBridge bridge, list[NukiLock] locks, list[NukiOpener] openers)
Definition: coordinator.py:37
dict[str, set[str]] update_devices(self, list[NukiDevice] devices)
Definition: coordinator.py:82
bool add(self, _T matcher)
Definition: match.py:185