Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Provides the Toon DataUpdateCoordinator."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import secrets
7 
8 from aiohttp import web
9 from toonapi import Status, Toon, ToonError
10 
11 from homeassistant.components import cloud, webhook
13  async_register as webhook_register,
14  async_unregister as webhook_unregister,
15 )
16 from homeassistant.config_entries import ConfigEntry
17 from homeassistant.const import CONF_WEBHOOK_ID, EVENT_HOMEASSISTANT_STOP
18 from homeassistant.core import Event, HomeAssistant
19 from homeassistant.helpers.aiohttp_client import async_get_clientsession
20 from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
21 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
22 
23 from .const import CONF_CLOUDHOOK_URL, DEFAULT_SCAN_INTERVAL, DOMAIN
24 
25 _LOGGER = logging.getLogger(__name__)
26 
27 
29  """Class to manage fetching Toon data from single endpoint."""
30 
31  def __init__(
32  self, hass: HomeAssistant, *, entry: ConfigEntry, session: OAuth2Session
33  ) -> None:
34  """Initialize global Toon data updater."""
35  self.sessionsession = session
36  self.entryentry = entry
37 
38  async def async_token_refresh() -> str:
39  await session.async_ensure_token_valid()
40  return session.token["access_token"]
41 
42  self.toontoon = Toon(
43  token=session.token["access_token"],
44  session=async_get_clientsession(hass),
45  token_refresh_method=async_token_refresh,
46  )
47 
48  super().__init__(
49  hass, _LOGGER, name=DOMAIN, update_interval=DEFAULT_SCAN_INTERVAL
50  )
51 
52  async def register_webhook(self, event: Event | None = None) -> None:
53  """Register a webhook with Toon to get live updates."""
54  if CONF_WEBHOOK_ID not in self.entryentry.data:
55  data = {**self.entryentry.data, CONF_WEBHOOK_ID: secrets.token_hex()}
56  self.hasshass.config_entries.async_update_entry(self.entryentry, data=data)
57 
58  if cloud.async_active_subscription(self.hasshass):
59  if CONF_CLOUDHOOK_URL not in self.entryentry.data:
60  try:
61  webhook_url = await cloud.async_create_cloudhook(
62  self.hasshass, self.entryentry.data[CONF_WEBHOOK_ID]
63  )
65  webhook_url = webhook.async_generate_url(
66  self.hasshass, self.entryentry.data[CONF_WEBHOOK_ID]
67  )
68  else:
69  data = {**self.entryentry.data, CONF_CLOUDHOOK_URL: webhook_url}
70  self.hasshass.config_entries.async_update_entry(self.entryentry, data=data)
71  else:
72  webhook_url = self.entryentry.data[CONF_CLOUDHOOK_URL]
73  else:
74  webhook_url = webhook.async_generate_url(
75  self.hasshass, self.entryentry.data[CONF_WEBHOOK_ID]
76  )
77 
78  # Ensure the webhook is not registered already
79  webhook_unregister(self.hasshass, self.entryentry.data[CONF_WEBHOOK_ID])
80 
81  webhook_register(
82  self.hasshass,
83  DOMAIN,
84  "Toon",
85  self.entryentry.data[CONF_WEBHOOK_ID],
86  self.handle_webhookhandle_webhook,
87  )
88 
89  try:
90  await self.toontoon.subscribe_webhook(
91  application_id=self.entryentry.entry_id, url=webhook_url
92  )
93  _LOGGER.debug("Registered Toon webhook: %s", webhook_url)
94  except ToonError as err:
95  _LOGGER.error("Error during webhook registration - %s", err)
96 
97  self.hasshass.bus.async_listen_once(
98  EVENT_HOMEASSISTANT_STOP, self.unregister_webhookunregister_webhook
99  )
100 
101  async def handle_webhook(
102  self, hass: HomeAssistant, webhook_id: str, request: web.Request
103  ) -> None:
104  """Handle webhook callback."""
105  try:
106  data = await request.json()
107  except ValueError:
108  return
109 
110  _LOGGER.debug("Got webhook data: %s", data)
111 
112  # Webhook expired notification, re-register
113  if data.get("code") == 510:
114  await self.register_webhookregister_webhook()
115  return
116 
117  if (
118  "updateDataSet" not in data
119  or "commonName" not in data
120  or self.datadata.agreement.display_common_name != data["commonName"]
121  ):
122  _LOGGER.warning("Received invalid data from Toon webhook - %s", data)
123  return
124 
125  try:
126  await self.toontoon.update(data["updateDataSet"])
127  self.async_update_listenersasync_update_listeners()
128  except ToonError as err:
129  _LOGGER.error("Could not process data received from Toon webhook - %s", err)
130 
131  async def unregister_webhook(self, event: Event | None = None) -> None:
132  """Remove / Unregister webhook for toon."""
133  _LOGGER.debug(
134  "Unregistering Toon webhook (%s)", self.entryentry.data[CONF_WEBHOOK_ID]
135  )
136  try:
137  await self.toontoon.unsubscribe_webhook(self.entryentry.entry_id)
138  except ToonError as err:
139  _LOGGER.error("Failed unregistering Toon webhook - %s", err)
140 
141  webhook_unregister(self.hasshass, self.entryentry.data[CONF_WEBHOOK_ID])
142 
143  async def _async_update_data(self) -> Status:
144  """Fetch data from Toon."""
145  try:
146  return await self.toontoon.update()
147  except ToonError as error:
148  raise UpdateFailed(f"Invalid response from API: {error}") from error
None handle_webhook(self, HomeAssistant hass, str webhook_id, web.Request request)
Definition: coordinator.py:103
None __init__(self, HomeAssistant hass, *ConfigEntry entry, OAuth2Session session)
Definition: coordinator.py:33
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)