Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Coordinator for the elmax-cloud integration."""
2 
3 from __future__ import annotations
4 
5 from asyncio import timeout
6 from datetime import timedelta
7 from logging import Logger
8 
9 from elmax_api.exceptions import (
10  ElmaxApiError,
11  ElmaxBadLoginError,
12  ElmaxBadPinError,
13  ElmaxNetworkError,
14  ElmaxPanelBusyError,
15 )
16 from elmax_api.http import Elmax, GenericElmax
17 from elmax_api.model.actuator import Actuator
18 from elmax_api.model.area import Area
19 from elmax_api.model.cover import Cover
20 from elmax_api.model.endpoint import DeviceEndpoint
21 from elmax_api.model.panel import PanelEntry, PanelStatus
22 from elmax_api.push.push import PushNotificationHandler
23 from httpx import ConnectError, ConnectTimeout
24 
25 from homeassistant.core import HomeAssistant
26 from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
27 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
28 
29 from .const import DEFAULT_TIMEOUT
30 
31 
33  """Coordinator helper to handle Elmax API polling."""
34 
35  _state_by_endpoint: dict[str, Actuator | Area | Cover | DeviceEndpoint]
36 
37  def __init__(
38  self,
39  hass: HomeAssistant,
40  logger: Logger,
41  elmax_api_client: GenericElmax,
42  panel: PanelEntry,
43  name: str,
44  update_interval: timedelta,
45  ) -> None:
46  """Instantiate the object."""
47  self._client_client = elmax_api_client
48  self._panel_entry_panel_entry = panel
49  self._state_by_endpoint_state_by_endpoint = {}
50  self._push_notification_handler_push_notification_handler = None
51  super().__init__(
52  hass=hass, logger=logger, name=name, update_interval=update_interval
53  )
54 
55  @property
56  def panel_entry(self) -> PanelEntry:
57  """Return the panel entry."""
58  return self._panel_entry_panel_entry
59 
60  def get_actuator_state(self, actuator_id: str) -> Actuator:
61  """Return state of a specific actuator."""
62  if self._state_by_endpoint_state_by_endpoint is not None:
63  return self._state_by_endpoint_state_by_endpoint[actuator_id]
64  raise HomeAssistantError("Unknown actuator")
65 
66  def get_zone_state(self, zone_id: str) -> Actuator:
67  """Return state of a specific zone."""
68  if self._state_by_endpoint_state_by_endpoint is not None:
69  return self._state_by_endpoint_state_by_endpoint[zone_id]
70  raise HomeAssistantError("Unknown zone")
71 
72  def get_area_state(self, area_id: str) -> Area:
73  """Return state of a specific area."""
74  if self._state_by_endpoint_state_by_endpoint is not None and area_id:
75  return self._state_by_endpoint_state_by_endpoint[area_id]
76  raise HomeAssistantError("Unknown area")
77 
78  def get_cover_state(self, cover_id: str) -> Cover:
79  """Return state of a specific cover."""
80  if self._state_by_endpoint_state_by_endpoint is not None:
81  return self._state_by_endpoint_state_by_endpoint[cover_id]
82  raise HomeAssistantError("Unknown cover")
83 
84  @property
85  def http_client(self):
86  """Return the current http client being used by this instance."""
87  return self._client_client
88 
89  @http_client.setter
90  def http_client(self, client: GenericElmax):
91  """Set the client library instance for Elmax API."""
92  self._client_client = client
93 
94  async def _async_update_data(self):
95  try:
96  async with timeout(DEFAULT_TIMEOUT):
97  # The following command might fail in case of the panel is offline.
98  # We handle this case in the following exception blocks.
99  status = await self._client_client.get_current_panel_status()
100 
101  except ElmaxBadPinError as err:
102  raise ConfigEntryAuthFailed("Control panel pin was refused") from err
103  except ElmaxBadLoginError as err:
104  raise ConfigEntryAuthFailed("Refused username/password/pin") from err
105  except ElmaxApiError as err:
106  raise UpdateFailed(f"Error communicating with ELMAX API: {err}") from err
107  except ElmaxPanelBusyError as err:
108  raise UpdateFailed(
109  "Communication with the panel failed, as it is currently busy"
110  ) from err
111  except (ConnectError, ConnectTimeout, ElmaxNetworkError) as err:
112  if isinstance(self._client_client, Elmax):
113  raise UpdateFailed(
114  "A communication error has occurred. "
115  "Make sure HA can reach the internet and that "
116  "your firewall allows communication with the Meross Cloud."
117  ) from err
118 
119  raise UpdateFailed(
120  "A communication error has occurred. "
121  "Make sure the panel is online and that "
122  "your firewall allows communication with it."
123  ) from err
124 
125  # Store a dictionary for fast endpoint state access
126  self._state_by_endpoint_state_by_endpoint = {k.endpoint_id: k for k in status.all_endpoints}
127 
128  # If panel supports it and a it hasn't been registered yet, register the push notification handler
129  if status.push_feature and self._push_notification_handler_push_notification_handler is None:
130  self._register_push_notification_handler_register_push_notification_handler()
131 
132  self._fire_data_update_fire_data_update(status)
133  return status
134 
135  def _fire_data_update(self, status: PanelStatus):
136  # Store a dictionary for fast endpoint state access
137  self._state_by_endpoint_state_by_endpoint = {k.endpoint_id: k for k in status.all_endpoints}
138  self.async_set_updated_dataasync_set_updated_data(status)
139 
141  ws_ep = (
142  f"{'wss' if self.http_client.base_url.scheme == 'https' else 'ws'}"
143  f"://{self.http_client.base_url.host}"
144  f":{self.http_client.base_url.port}"
145  f"{self.http_client.base_url.path}/push"
146  )
147  self._push_notification_handler_push_notification_handler = PushNotificationHandler(
148  endpoint=str(ws_ep),
149  http_client=self.http_clienthttp_clienthttp_client,
150  ssl_context=self.http_clienthttp_clienthttp_client.ssl_context,
151  )
152  self._push_notification_handler_push_notification_handler.register_push_notification_handler(
153  self._push_handler_push_handler
154  )
155  self._push_notification_handler_push_notification_handler.start(loop=self.hasshass.loop)
156 
157  async def _push_handler(self, status: PanelStatus) -> None:
158  self._fire_data_update_fire_data_update(status)
159 
160  async def async_shutdown(self) -> None:
161  """Cancel any scheduled call, and ignore new runs."""
162  if self._push_notification_handler_push_notification_handler is not None:
163  self._push_notification_handler_push_notification_handler.unregister_push_notification_handler(
164  self._push_handler_push_handler
165  )
166  self._push_notification_handler_push_notification_handler.stop()
167  self._push_notification_handler_push_notification_handler = None
168  return await super().async_shutdown()
None __init__(self, HomeAssistant hass, Logger logger, GenericElmax elmax_api_client, PanelEntry panel, str name, timedelta update_interval)
Definition: coordinator.py:45