Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for TP-Link LTE modems."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import logging
7 from typing import Any
8 
9 import aiohttp
10 import attr
11 import tp_connected
12 import voluptuous as vol
13 
14 from homeassistant.const import (
15  CONF_HOST,
16  CONF_NAME,
17  CONF_PASSWORD,
18  CONF_RECIPIENT,
19  EVENT_HOMEASSISTANT_STOP,
20  Platform,
21 )
22 from homeassistant.core import Event, HomeAssistant, callback
23 from homeassistant.helpers import config_validation as cv, discovery
24 from homeassistant.helpers.aiohttp_client import async_create_clientsession
25 from homeassistant.helpers.typing import ConfigType
26 
27 _LOGGER = logging.getLogger(__name__)
28 
29 DOMAIN = "tplink_lte"
30 DATA_KEY = "tplink_lte"
31 
32 CONF_NOTIFY = "notify"
33 
34 _NOTIFY_SCHEMA = vol.Schema(
35  {
36  vol.Optional(CONF_NAME): cv.string,
37  vol.Optional(CONF_RECIPIENT): vol.All(cv.ensure_list, [cv.string]),
38  }
39 )
40 
41 CONFIG_SCHEMA = vol.Schema(
42  {
43  DOMAIN: vol.All(
44  cv.ensure_list,
45  [
46  vol.Schema(
47  {
48  vol.Required(CONF_HOST): cv.string,
49  vol.Required(CONF_PASSWORD): cv.string,
50  vol.Optional(CONF_NOTIFY): vol.All(
51  cv.ensure_list, [_NOTIFY_SCHEMA]
52  ),
53  }
54  )
55  ],
56  )
57  },
58  extra=vol.ALLOW_EXTRA,
59 )
60 
61 
62 @attr.s
63 class ModemData:
64  """Class for modem state."""
65 
66  host: str = attr.ib()
67  modem: tp_connected.Modem = attr.ib()
68 
69  connected: bool = attr.ib(init=False, default=True)
70 
71 
72 @attr.s
73 class LTEData:
74  """Shared state."""
75 
76  websession: aiohttp.ClientSession = attr.ib()
77  modem_data: dict[str, ModemData] = attr.ib(init=False, factory=dict)
78 
79  def get_modem_data(self, config: dict[str, Any]) -> ModemData | None:
80  """Get the requested or the only modem_data value."""
81  if CONF_HOST in config:
82  return self.modem_data.get(config[CONF_HOST])
83  if len(self.modem_data) == 1:
84  return next(iter(self.modem_data.values()))
85 
86  return None
87 
88 
89 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
90  """Set up TP-Link LTE component."""
91  if DATA_KEY not in hass.data:
92  websession = async_create_clientsession(
93  hass, cookie_jar=aiohttp.CookieJar(unsafe=True)
94  )
95  hass.data[DATA_KEY] = LTEData(websession)
96 
97  domain_config = config.get(DOMAIN, [])
98 
99  tasks = [_setup_lte(hass, conf) for conf in domain_config]
100  if tasks:
101  await asyncio.gather(*tasks)
102 
103  for conf in domain_config:
104  for notify_conf in conf.get(CONF_NOTIFY, []):
105  hass.async_create_task(
106  discovery.async_load_platform(
107  hass, Platform.NOTIFY, DOMAIN, notify_conf, config
108  )
109  )
110 
111  return True
112 
113 
114 async def _setup_lte(
115  hass: HomeAssistant, lte_config: dict[str, Any], delay: int = 0
116 ) -> None:
117  """Set up a TP-Link LTE modem."""
118 
119  host: str = lte_config[CONF_HOST]
120  password: str = lte_config[CONF_PASSWORD]
121 
122  lte_data: LTEData = hass.data[DATA_KEY]
123  modem = tp_connected.Modem(hostname=host, websession=lte_data.websession)
124 
125  modem_data = ModemData(host, modem)
126 
127  try:
128  await _login(hass, modem_data, password)
129  except tp_connected.Error:
130  retry_task = hass.loop.create_task(_retry_login(hass, modem_data, password))
131 
132  @callback
133  def cleanup_retry(event: Event) -> None:
134  """Clean up retry task resources."""
135  if not retry_task.done():
136  retry_task.cancel()
137 
138  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_retry)
139 
140 
141 async def _login(hass: HomeAssistant, modem_data: ModemData, password: str) -> None:
142  """Log in and complete setup."""
143  await modem_data.modem.login(password=password)
144  modem_data.connected = True
145  lte_data: LTEData = hass.data[DATA_KEY]
146  lte_data.modem_data[modem_data.host] = modem_data
147 
148  async def cleanup(event: Event) -> None:
149  """Clean up resources."""
150  await modem_data.modem.logout()
151 
152  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup)
153 
154 
155 async def _retry_login(
156  hass: HomeAssistant, modem_data: ModemData, password: str
157 ) -> None:
158  """Sleep and retry setup."""
159 
160  _LOGGER.warning("Could not connect to %s. Will keep trying", modem_data.host)
161 
162  modem_data.connected = False
163  delay = 15
164 
165  while not modem_data.connected:
166  await asyncio.sleep(delay)
167 
168  try:
169  await _login(hass, modem_data, password)
170  _LOGGER.warning("Connected to %s", modem_data.host)
171  except tp_connected.Error:
172  delay = min(2 * delay, 300)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
aiohttp.ClientSession async_create_clientsession()
Definition: coordinator.py:51