Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Init the tedee component."""
2 
3 from collections.abc import Awaitable, Callable
4 from http import HTTPStatus
5 import logging
6 from typing import Any
7 
8 from aiohttp.hdrs import METH_POST
9 from aiohttp.web import Request, Response
10 from aiotedee.exception import TedeeDataUpdateException, TedeeWebhookException
11 
12 from homeassistant.components.http import HomeAssistantView
14  async_generate_id as webhook_generate_id,
15  async_generate_url as webhook_generate_url,
16  async_register as webhook_register,
17  async_unregister as webhook_unregister,
18 )
19 from homeassistant.config_entries import ConfigEntry
20 from homeassistant.const import CONF_WEBHOOK_ID, EVENT_HOMEASSISTANT_STOP, Platform
21 from homeassistant.core import HomeAssistant
22 from homeassistant.helpers import device_registry as dr
23 from homeassistant.helpers.network import get_url
24 
25 from .const import DOMAIN, NAME
26 from .coordinator import TedeeApiCoordinator, TedeeConfigEntry
27 
28 PLATFORMS = [
29  Platform.BINARY_SENSOR,
30  Platform.LOCK,
31  Platform.SENSOR,
32 ]
33 
34 _LOGGER = logging.getLogger(__name__)
35 
36 
37 async def async_setup_entry(hass: HomeAssistant, entry: TedeeConfigEntry) -> bool:
38  """Integration setup."""
39 
40  coordinator = TedeeApiCoordinator(hass, entry)
41 
42  await coordinator.async_config_entry_first_refresh()
43 
44  device_registry = dr.async_get(hass)
45  device_registry.async_get_or_create(
46  config_entry_id=entry.entry_id,
47  identifiers={(DOMAIN, coordinator.bridge.serial)},
48  manufacturer="Tedee",
49  name=coordinator.bridge.name,
50  model="Bridge",
51  serial_number=coordinator.bridge.serial,
52  )
53 
54  entry.runtime_data = coordinator
55 
56  async def unregister_webhook(_: Any) -> None:
57  await coordinator.async_unregister_webhook()
58  webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
59 
60  async def register_webhook() -> None:
61  instance_url = get_url(hass, allow_ip=True, allow_external=False)
62  # first make sure we don't have leftover callbacks to the same instance
63  try:
64  await coordinator.tedee_client.cleanup_webhooks_by_host(instance_url)
65  except (TedeeDataUpdateException, TedeeWebhookException) as ex:
66  _LOGGER.warning("Failed to cleanup Tedee webhooks by host: %s", ex)
67 
68  webhook_url = webhook_generate_url(
69  hass, entry.data[CONF_WEBHOOK_ID], allow_external=False, allow_ip=True
70  )
71  webhook_name = "Tedee"
72  if entry.title != NAME:
73  webhook_name = f"{NAME} {entry.title}"
74 
75  webhook_register(
76  hass,
77  DOMAIN,
78  webhook_name,
79  entry.data[CONF_WEBHOOK_ID],
80  get_webhook_handler(coordinator),
81  allowed_methods=[METH_POST],
82  )
83  _LOGGER.debug("Registered Tedee webhook at hass: %s", webhook_url)
84 
85  try:
86  await coordinator.async_register_webhook(webhook_url)
87  except TedeeWebhookException:
88  _LOGGER.exception("Failed to register Tedee webhook from bridge")
89  else:
90  entry.async_on_unload(
91  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook)
92  )
93 
94  entry.async_create_background_task(
95  hass, register_webhook(), "tedee_register_webhook"
96  )
97  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
98 
99  return True
100 
101 
102 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
103  """Unload a config entry."""
104  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
105 
106 
108  coordinator: TedeeApiCoordinator,
109 ) -> Callable[[HomeAssistant, str, Request], Awaitable[Response | None]]:
110  """Return webhook handler."""
111 
112  async def async_webhook_handler(
113  hass: HomeAssistant, webhook_id: str, request: Request
114  ) -> Response | None:
115  # Handle http post calls to the path.
116  if not request.body_exists:
117  return HomeAssistantView.json(
118  result="No Body", status_code=HTTPStatus.BAD_REQUEST
119  )
120 
121  body = await request.json()
122  try:
123  coordinator.webhook_received(body)
124  except TedeeWebhookException as ex:
125  return HomeAssistantView.json(
126  result=str(ex), status_code=HTTPStatus.BAD_REQUEST
127  )
128 
129  return HomeAssistantView.json(result="OK", status_code=HTTPStatus.OK)
130 
131  return async_webhook_handler
132 
133 
134 async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
135  """Migrate old entry."""
136  if config_entry.version > 1:
137  # This means the user has downgraded from a future version
138  return False
139 
140  version = config_entry.version
141  minor_version = config_entry.minor_version
142 
143  if version == 1 and minor_version == 1:
144  _LOGGER.debug(
145  "Migrating Tedee config entry from version %s.%s", version, minor_version
146  )
147  data = {**config_entry.data, CONF_WEBHOOK_ID: webhook_generate_id()}
148  hass.config_entries.async_update_entry(config_entry, data=data, minor_version=2)
149  _LOGGER.debug("Migration to version 1.2 successful")
150  return True
bool async_setup_entry(HomeAssistant hass, TedeeConfigEntry entry)
Definition: __init__.py:37
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:102
bool async_migrate_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:134
Callable[[HomeAssistant, str, Request], Awaitable[Response|None]] get_webhook_handler(TedeeApiCoordinator coordinator)
Definition: __init__.py:109
str get_url(HomeAssistant hass, *bool require_current_request=False, bool require_ssl=False, bool require_standard_port=False, bool require_cloud=False, bool allow_internal=True, bool allow_external=True, bool allow_cloud=True, bool|None allow_ip=None, bool|None prefer_external=None, bool prefer_cloud=False)
Definition: network.py:131