Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Telldus Live."""
2 
3 import asyncio
4 from functools import partial
5 import logging
6 
7 from tellduslive import DIM, TURNON, UP, Session
8 import voluptuous as vol
9 
10 from homeassistant import config_entries
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.const import CONF_HOST, CONF_SCAN_INTERVAL
13 from homeassistant.core import HomeAssistant
14 from homeassistant.helpers import config_validation as cv, device_registry as dr
15 from homeassistant.helpers.dispatcher import async_dispatcher_send
16 from homeassistant.helpers.event import async_call_later
17 from homeassistant.helpers.typing import ConfigType
18 
19 from .const import (
20  DOMAIN,
21  KEY_SCAN_INTERVAL,
22  KEY_SESSION,
23  MIN_UPDATE_INTERVAL,
24  NOT_SO_PRIVATE_KEY,
25  PUBLIC_KEY,
26  SCAN_INTERVAL,
27  SIGNAL_UPDATE_ENTITY,
28  TELLDUS_DISCOVERY_NEW,
29 )
30 
31 APPLICATION_NAME = "Home Assistant"
32 
33 _LOGGER = logging.getLogger(__name__)
34 
35 CONFIG_SCHEMA = vol.Schema(
36  {
37  DOMAIN: vol.Schema(
38  {
39  vol.Optional(CONF_HOST, default=DOMAIN): cv.string,
40  vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): vol.All(
41  cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)
42  ),
43  }
44  )
45  },
46  extra=vol.ALLOW_EXTRA,
47 )
48 
49 DATA_CONFIG_ENTRY_LOCK = "tellduslive_config_entry_lock"
50 CONFIG_ENTRY_IS_SETUP = "telldus_config_entry_is_setup"
51 
52 NEW_CLIENT_TASK = "telldus_new_client_task"
53 INTERVAL_TRACKER = f"{DOMAIN}_INTERVAL"
54 
55 
56 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
57  """Create a tellduslive session."""
58  conf = entry.data[KEY_SESSION]
59 
60  if CONF_HOST in conf:
61  # Session(**conf) does blocking IO when
62  # communicating with local devices.
63  session = await hass.async_add_executor_job(partial(Session, **conf))
64  else:
65  session = Session(
66  PUBLIC_KEY, NOT_SO_PRIVATE_KEY, application=APPLICATION_NAME, **conf
67  )
68 
69  if not session.is_authorized:
70  _LOGGER.error("Authentication Error")
71  return False
72 
73  hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock()
74  hass.data[CONFIG_ENTRY_IS_SETUP] = set()
75  hass.data[NEW_CLIENT_TASK] = hass.loop.create_task(
76  async_new_client(hass, session, entry)
77  )
78 
79  return True
80 
81 
82 async def async_new_client(hass, session, entry):
83  """Add the hubs associated with the current client to device_registry."""
84  interval = entry.data[KEY_SCAN_INTERVAL]
85  _LOGGER.debug("Update interval %s seconds", interval)
86  client = TelldusLiveClient(hass, entry, session, interval)
87  hass.data[DOMAIN] = client
88  dev_reg = dr.async_get(hass)
89  for hub in await client.async_get_hubs():
90  _LOGGER.debug("Connected hub %s", hub["name"])
91  dev_reg.async_get_or_create(
92  config_entry_id=entry.entry_id,
93  identifiers={(DOMAIN, hub["id"])},
94  manufacturer="Telldus",
95  name=hub["name"],
96  model=hub["type"],
97  sw_version=hub["version"],
98  )
99  await client.update()
100 
101 
102 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
103  """Set up the Telldus Live component."""
104  if DOMAIN not in config:
105  return True
106 
107  hass.async_create_task(
108  hass.config_entries.flow.async_init(
109  DOMAIN,
110  context={"source": config_entries.SOURCE_IMPORT},
111  data={
112  CONF_HOST: config[DOMAIN].get(CONF_HOST),
113  KEY_SCAN_INTERVAL: config[DOMAIN][CONF_SCAN_INTERVAL],
114  },
115  )
116  )
117  return True
118 
119 
120 async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
121  """Unload a config entry."""
122  if not hass.data[NEW_CLIENT_TASK].done():
123  hass.data[NEW_CLIENT_TASK].cancel()
124  interval_tracker = hass.data.pop(INTERVAL_TRACKER)
125  interval_tracker()
126  unload_ok = await hass.config_entries.async_unload_platforms(
127  config_entry, CONFIG_ENTRY_IS_SETUP
128  )
129  del hass.data[DOMAIN]
130  del hass.data[DATA_CONFIG_ENTRY_LOCK]
131  del hass.data[CONFIG_ENTRY_IS_SETUP]
132  return unload_ok
133 
134 
136  """Get the latest data and update the states."""
137 
138  def __init__(self, hass, config_entry, session, interval):
139  """Initialize the Tellus data object."""
140  self._known_devices_known_devices = set()
141  self._device_infos_device_infos = {}
142 
143  self._hass_hass = hass
144  self._config_entry_config_entry = config_entry
145  self._client_client = session
146  self._interval_interval = interval
147 
148  async def async_get_hubs(self):
149  """Return hubs registered for the user."""
150  clients = await self._hass_hass.async_add_executor_job(self._client_client.get_clients)
151  return clients or []
152 
153  def device_info(self, device_id):
154  """Return device info."""
155  return self._device_infos_device_infos.get(device_id)
156 
157  @staticmethod
158  def identify_device(device):
159  """Find out what type of HA component to create."""
160  if device.is_sensor:
161  return "sensor"
162 
163  if device.methods & DIM:
164  return "light"
165  if device.methods & UP:
166  return "cover"
167  if device.methods & TURNON:
168  return "switch"
169  if device.methods == 0:
170  return "binary_sensor"
171  _LOGGER.warning("Unidentified device type (methods: %d)", device.methods)
172  return "switch"
173 
174  async def _discover(self, device_id):
175  """Discover the component."""
176  device = self._client_client.device(device_id)
177  component = self.identify_deviceidentify_device(device)
178  self._device_infos_device_infos.update(
179  {device_id: await self._hass_hass.async_add_executor_job(device.info)}
180  )
181  async with self._hass_hass.data[DATA_CONFIG_ENTRY_LOCK]:
182  if component not in self._hass_hass.data[CONFIG_ENTRY_IS_SETUP]:
183  await self._hass_hass.config_entries.async_forward_entry_setups(
184  self._config_entry_config_entry, [component]
185  )
186  self._hass_hass.data[CONFIG_ENTRY_IS_SETUP].add(component)
187  device_ids = []
188  if device.is_sensor:
189  device_ids.extend(
190  (device.device_id, item.name, item.scale) for item in device.items
191  )
192  else:
193  device_ids.append(device_id)
194  for _id in device_ids:
196  self._hass_hass, TELLDUS_DISCOVERY_NEW.format(component, DOMAIN), _id
197  )
198 
199  async def update(self, *args):
200  """Periodically poll the servers for current state."""
201  try:
202  if not await self._hass_hass.async_add_executor_job(self._client_client.update):
203  _LOGGER.warning("Failed request")
204  return
205  dev_ids = {dev.device_id for dev in self._client_client.devices}
206  new_devices = dev_ids - self._known_devices_known_devices
207  # just await each discover as `gather` use up all HTTPAdapter pools
208  for d_id in new_devices:
209  await self._discover_discover(d_id)
210  self._known_devices_known_devices |= new_devices
211  async_dispatcher_send(self._hass_hass, SIGNAL_UPDATE_ENTITY)
212  finally:
213  self._hass_hass.data[INTERVAL_TRACKER] = async_call_later(
214  self._hass_hass, self._interval_interval, self.updateupdate
215  )
216 
217  def device(self, device_id):
218  """Return device representation."""
219  return self._client_client.device(device_id)
220 
221  def is_available(self, device_id):
222  """Return device availability."""
223  return device_id in self._client_client.device_ids
def __init__(self, hass, config_entry, session, interval)
Definition: __init__.py:138
bool add(self, _T matcher)
Definition: match.py:185
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
def async_new_client(hass, session, entry)
Definition: __init__.py:82
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:56
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:120
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:102
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
Definition: event.py:1597