Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Component to embed Aqualink devices."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Awaitable, Callable, Coroutine
6 from datetime import datetime
7 from functools import wraps
8 import logging
9 from typing import Any, Concatenate
10 
11 import httpx
12 from iaqualink.client import AqualinkClient
13 from iaqualink.device import (
14  AqualinkBinarySensor,
15  AqualinkLight,
16  AqualinkSensor,
17  AqualinkSwitch,
18  AqualinkThermostat,
19 )
20 from iaqualink.exception import AqualinkServiceException
21 
22 from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
23 from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
24 from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
25 from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
26 from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
27 from homeassistant.config_entries import ConfigEntry
28 from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
29 from homeassistant.core import HomeAssistant
30 from homeassistant.exceptions import ConfigEntryNotReady
31 from homeassistant.helpers.dispatcher import async_dispatcher_send
32 from homeassistant.helpers.event import async_track_time_interval
33 from homeassistant.helpers.httpx_client import get_async_client
34 
35 from .const import DOMAIN, UPDATE_INTERVAL
36 from .entity import AqualinkEntity
37 
38 _LOGGER = logging.getLogger(__name__)
39 
40 ATTR_CONFIG = "config"
41 PARALLEL_UPDATES = 0
42 
43 PLATFORMS = [
44  Platform.BINARY_SENSOR,
45  Platform.CLIMATE,
46  Platform.LIGHT,
47  Platform.SENSOR,
48  Platform.SWITCH,
49 ]
50 
51 
52 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
53  """Set up Aqualink from a config entry."""
54  username = entry.data[CONF_USERNAME]
55  password = entry.data[CONF_PASSWORD]
56 
57  hass.data.setdefault(DOMAIN, {})
58 
59  # These will contain the initialized devices
60  binary_sensors = hass.data[DOMAIN][BINARY_SENSOR_DOMAIN] = []
61  climates = hass.data[DOMAIN][CLIMATE_DOMAIN] = []
62  lights = hass.data[DOMAIN][LIGHT_DOMAIN] = []
63  sensors = hass.data[DOMAIN][SENSOR_DOMAIN] = []
64  switches = hass.data[DOMAIN][SWITCH_DOMAIN] = []
65 
66  aqualink = AqualinkClient(username, password, httpx_client=get_async_client(hass))
67  try:
68  await aqualink.login()
69  except AqualinkServiceException as login_exception:
70  _LOGGER.error("Failed to login: %s", login_exception)
71  await aqualink.close()
72  return False
73  except (TimeoutError, httpx.HTTPError) as aio_exception:
74  await aqualink.close()
75  raise ConfigEntryNotReady(
76  f"Error while attempting login: {aio_exception}"
77  ) from aio_exception
78 
79  try:
80  systems = await aqualink.get_systems()
81  except AqualinkServiceException as svc_exception:
82  await aqualink.close()
83  raise ConfigEntryNotReady(
84  f"Error while attempting to retrieve systems list: {svc_exception}"
85  ) from svc_exception
86 
87  systems = list(systems.values())
88  if not systems:
89  _LOGGER.error("No systems detected or supported")
90  await aqualink.close()
91  return False
92 
93  for system in systems:
94  try:
95  devices = await system.get_devices()
96  except AqualinkServiceException as svc_exception:
97  await aqualink.close()
98  raise ConfigEntryNotReady(
99  f"Error while attempting to retrieve devices list: {svc_exception}"
100  ) from svc_exception
101 
102  for dev in devices.values():
103  if isinstance(dev, AqualinkThermostat):
104  climates += [dev]
105  elif isinstance(dev, AqualinkLight):
106  lights += [dev]
107  elif isinstance(dev, AqualinkSwitch):
108  switches += [dev]
109  elif isinstance(dev, AqualinkBinarySensor):
110  binary_sensors += [dev]
111  elif isinstance(dev, AqualinkSensor):
112  sensors += [dev]
113 
114  platforms = []
115  if binary_sensors:
116  _LOGGER.debug("Got %s binary sensors: %s", len(binary_sensors), binary_sensors)
117  platforms.append(Platform.BINARY_SENSOR)
118  if climates:
119  _LOGGER.debug("Got %s climates: %s", len(climates), climates)
120  platforms.append(Platform.CLIMATE)
121  if lights:
122  _LOGGER.debug("Got %s lights: %s", len(lights), lights)
123  platforms.append(Platform.LIGHT)
124  if sensors:
125  _LOGGER.debug("Got %s sensors: %s", len(sensors), sensors)
126  platforms.append(Platform.SENSOR)
127  if switches:
128  _LOGGER.debug("Got %s switches: %s", len(switches), switches)
129  platforms.append(Platform.SWITCH)
130 
131  hass.data[DOMAIN]["client"] = aqualink
132 
133  await hass.config_entries.async_forward_entry_setups(entry, platforms)
134 
135  async def _async_systems_update(_: datetime) -> None:
136  """Refresh internal state for all systems."""
137  for system in systems:
138  prev = system.online
139 
140  try:
141  await system.update()
142  except (AqualinkServiceException, httpx.HTTPError) as svc_exception:
143  if prev is not None:
144  _LOGGER.warning(
145  "Failed to refresh system %s state: %s",
146  system.serial,
147  svc_exception,
148  )
149  await system.aqualink.close()
150  else:
151  cur = system.online
152  if cur and not prev:
153  _LOGGER.warning("System %s reconnected to iAqualink", system.serial)
154 
155  async_dispatcher_send(hass, DOMAIN)
156 
157  entry.async_on_unload(
158  async_track_time_interval(hass, _async_systems_update, UPDATE_INTERVAL)
159  )
160 
161  return True
162 
163 
164 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
165  """Unload a config entry."""
166  aqualink = hass.data[DOMAIN]["client"]
167  await aqualink.close()
168 
169  platforms_to_unload = [
170  platform for platform in PLATFORMS if platform in hass.data[DOMAIN]
171  ]
172 
173  del hass.data[DOMAIN]
174 
175  return await hass.config_entries.async_unload_platforms(entry, platforms_to_unload)
176 
177 
178 def refresh_system[_AqualinkEntityT: AqualinkEntity, **_P](
179  func: Callable[Concatenate[_AqualinkEntityT, _P], Awaitable[Any]],
180 ) -> Callable[Concatenate[_AqualinkEntityT, _P], Coroutine[Any, Any, None]]:
181  """Force update all entities after state change."""
182 
183  @wraps(func)
184  async def wrapper(
185  self: _AqualinkEntityT, *args: _P.args, **kwargs: _P.kwargs
186  ) -> None:
187  """Call decorated function and send update signal to all entities."""
188  await func(self, *args, **kwargs)
189  async_dispatcher_send(self.hass, DOMAIN)
190 
191  return wrapper
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)
Definition: event.py:1679
httpx.AsyncClient get_async_client(HomeAssistant hass, bool verify_ssl=True)
Definition: httpx_client.py:41