Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """synology_dsm coordinators."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Awaitable, Callable, Coroutine
6 from datetime import timedelta
7 import logging
8 from typing import Any, Concatenate
9 
10 from synology_dsm.api.surveillance_station.camera import SynoCamera
11 from synology_dsm.exceptions import (
12  SynologyDSMAPIErrorException,
13  SynologyDSMNotLoggedInException,
14 )
15 
16 from homeassistant.config_entries import ConfigEntry
17 from homeassistant.const import CONF_SCAN_INTERVAL
18 from homeassistant.core import HomeAssistant
19 from homeassistant.helpers.dispatcher import async_dispatcher_send
20 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
21 
22 from .common import SynoApi, raise_config_entry_auth_error
23 from .const import (
24  DEFAULT_SCAN_INTERVAL,
25  SIGNAL_CAMERA_SOURCE_CHANGED,
26  SYNOLOGY_AUTH_FAILED_EXCEPTIONS,
27  SYNOLOGY_CONNECTION_EXCEPTIONS,
28 )
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 
33 def async_re_login_on_expired[_T: SynologyDSMUpdateCoordinator[Any], **_P, _R](
34  func: Callable[Concatenate[_T, _P], Awaitable[_R]],
35 ) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, _R]]:
36  """Define a wrapper to re-login when expired."""
37 
38  async def _async_wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R:
39  for attempts in range(2):
40  try:
41  return await func(self, *args, **kwargs)
42  except SynologyDSMNotLoggedInException:
43  # If login is expired, try to login again
44  _LOGGER.debug("login is expired, try to login again")
45  try:
46  await self.api.async_login()
47  except SYNOLOGY_AUTH_FAILED_EXCEPTIONS as err:
49  if attempts == 0:
50  continue
51  except SYNOLOGY_CONNECTION_EXCEPTIONS as err:
52  raise UpdateFailed(f"Error communicating with API: {err}") from err
53 
54  raise UpdateFailed("Unknown error when communicating with API")
55 
56  return _async_wrap
57 
58 
59 class SynologyDSMUpdateCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
60  """DataUpdateCoordinator base class for synology_dsm."""
61 
62  def __init__(
63  self,
64  hass: HomeAssistant,
65  entry: ConfigEntry,
66  api: SynoApi,
67  update_interval: timedelta,
68  ) -> None:
69  """Initialize synology_dsm DataUpdateCoordinator."""
70  self.apiapi = api
71  self.entryentry = entry
72  super().__init__(
73  hass,
74  _LOGGER,
75  name=f"{entry.title} {self.__class__.__name__}",
76  update_interval=update_interval,
77  )
78 
79 
81  SynologyDSMUpdateCoordinator[dict[str, dict[str, Any]]]
82 ):
83  """DataUpdateCoordinator to gather data for a synology_dsm switch devices."""
84 
85  def __init__(
86  self,
87  hass: HomeAssistant,
88  entry: ConfigEntry,
89  api: SynoApi,
90  ) -> None:
91  """Initialize DataUpdateCoordinator for switch devices."""
92  super().__init__(hass, entry, api, timedelta(seconds=30))
93  self.versionversion: str | None = None
94 
95  async def async_setup(self) -> None:
96  """Set up the coordinator initial data."""
97  info = await self.apiapi.dsm.surveillance_station.get_info()
98  assert info is not None
99  self.versionversion = info["data"]["CMSMinVersion"]
100 
101  @async_re_login_on_expired
102  async def _async_update_data(self) -> dict[str, dict[str, Any]]:
103  """Fetch all data from api."""
104  surveillance_station = self.apiapi.surveillance_station
105  assert surveillance_station is not None
106  return {
107  "switches": {
108  "home_mode": bool(await surveillance_station.get_home_mode_status())
109  }
110  }
111 
112 
114  """DataUpdateCoordinator to gather data for a synology_dsm central device."""
115 
116  def __init__(
117  self,
118  hass: HomeAssistant,
119  entry: ConfigEntry,
120  api: SynoApi,
121  ) -> None:
122  """Initialize DataUpdateCoordinator for central device."""
123  super().__init__(
124  hass,
125  entry,
126  api,
127  timedelta(
128  minutes=entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
129  ),
130  )
131 
132  @async_re_login_on_expired
133  async def _async_update_data(self) -> None:
134  """Fetch all data from api."""
135  await self.apiapi.async_update()
136 
137 
139  SynologyDSMUpdateCoordinator[dict[str, dict[int, SynoCamera]]]
140 ):
141  """DataUpdateCoordinator to gather data for a synology_dsm cameras."""
142 
143  def __init__(
144  self,
145  hass: HomeAssistant,
146  entry: ConfigEntry,
147  api: SynoApi,
148  ) -> None:
149  """Initialize DataUpdateCoordinator for cameras."""
150  super().__init__(hass, entry, api, timedelta(seconds=30))
151 
152  @async_re_login_on_expired
153  async def _async_update_data(self) -> dict[str, dict[int, SynoCamera]]:
154  """Fetch all camera data from api."""
155  surveillance_station = self.apiapi.surveillance_station
156  assert surveillance_station is not None
157  current_data: dict[int, SynoCamera] = {
158  camera.id: camera for camera in surveillance_station.get_all_cameras()
159  }
160 
161  try:
162  await surveillance_station.update()
163  except SynologyDSMAPIErrorException as err:
164  raise UpdateFailed(f"Error communicating with API: {err}") from err
165 
166  new_data: dict[int, SynoCamera] = {
167  camera.id: camera for camera in surveillance_station.get_all_cameras()
168  }
169 
170  for cam_id, cam_data_new in new_data.items():
171  if (
172  (cam_data_current := current_data.get(cam_id)) is not None
173  and cam_data_current.live_view.rtsp != cam_data_new.live_view.rtsp
174  ):
176  self.hasshass,
177  f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{self.entry.entry_id}_{cam_id}",
178  cam_data_new.live_view.rtsp,
179  )
180 
181  return {"cameras": new_data}
None __init__(self, HomeAssistant hass, ConfigEntry entry, SynoApi api)
Definition: coordinator.py:148
None __init__(self, HomeAssistant hass, ConfigEntry entry, SynoApi api)
Definition: coordinator.py:121
None __init__(self, HomeAssistant hass, ConfigEntry entry, SynoApi api)
Definition: coordinator.py:90
None __init__(self, HomeAssistant hass, ConfigEntry entry, SynoApi api, timedelta update_interval)
Definition: coordinator.py:68
None raise_config_entry_auth_error(Exception err)
Definition: common.py:345
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193