1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-16 11:03:09 +01:00
Files
core/homeassistant/components/netatmo/webhook.py
T
2026-07-08 16:21:20 +02:00

210 lines
6.2 KiB
Python

"""The Netatmo integration."""
import logging
import secrets
from typing import Any
from aiohttp.web import Request
import pyatmo
from homeassistant.components import cloud
from homeassistant.components.webhook import (
async_generate_url as webhook_generate_url,
async_register as webhook_register,
async_unregister as webhook_unregister,
)
from homeassistant.const import (
ATTR_DEVICE_ID,
ATTR_ID,
ATTR_NAME,
ATTR_PERSONS,
CONF_WEBHOOK_ID,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import (
ATTR_EVENT_TYPE,
ATTR_FACE_URL,
ATTR_HOME_ID,
ATTR_IS_KNOWN,
CONF_CLOUDHOOK_URL,
DEFAULT_PERSON,
DOMAIN,
EVENT_ID_MAP,
NETATMO_EVENT,
WEBHOOK_DEACTIVATION,
WEBHOOK_PUSH_TYPE,
)
from .coordinator import NetatmoConfigEntry, NetatmoDataHandler
_LOGGER = logging.getLogger(__name__)
SUBEVENT_TYPE_MAP = {
"outdoor": "",
"therm_mode": "",
}
async def async_handle_webhook(
hass: HomeAssistant, webhook_id: str, request: Request
) -> None:
"""Handle webhook callback."""
try:
data = await request.json()
except ValueError as err:
_LOGGER.error("Error in data: %s", err)
return
_LOGGER.debug("Got webhook data: %s", data)
entry = next(
(
entry
for entry in hass.config_entries.async_loaded_entries(DOMAIN)
if entry.data.get(CONF_WEBHOOK_ID) == webhook_id
),
None,
)
if entry is None:
return
data_handler = entry.runtime_data
event_type = data.get(ATTR_EVENT_TYPE)
if event_type in SUBEVENT_TYPE_MAP:
async_send_event(data_handler, event_type, data)
for event_data in data.get(SUBEVENT_TYPE_MAP[event_type], []):
async_evaluate_event(data_handler, event_data)
else:
async_evaluate_event(data_handler, data)
def async_evaluate_event(data_handler: NetatmoDataHandler, event_data: dict) -> None:
"""Evaluate events from webhook."""
event_type = event_data.get(ATTR_EVENT_TYPE, "None")
if event_type == "person":
for person in event_data.get(ATTR_PERSONS, {}):
person_event_data = dict(event_data)
person_event_data[ATTR_ID] = person.get(ATTR_ID)
person_event_data[ATTR_NAME] = data_handler.persons[
event_data[ATTR_HOME_ID]
].get(person_event_data[ATTR_ID], DEFAULT_PERSON)
person_event_data[ATTR_IS_KNOWN] = person.get(ATTR_IS_KNOWN)
person_event_data[ATTR_FACE_URL] = person.get(ATTR_FACE_URL)
async_send_event(data_handler, event_type, person_event_data)
else:
async_send_event(data_handler, event_type, event_data)
def async_send_event(
data_handler: NetatmoDataHandler, event_type: str, data: dict
) -> None:
"""Send events."""
hass = data_handler.hass
_LOGGER.debug("%s: %s", event_type, data)
async_dispatcher_send(
hass,
f"signal-{DOMAIN}-webhook-{event_type}",
{"type": event_type, "data": data},
)
event_data: dict[str, Any] = {
"type": event_type,
"data": data,
}
if event_type in EVENT_ID_MAP:
data_device_id = data[EVENT_ID_MAP[event_type]]
event_data[ATTR_DEVICE_ID] = data_handler.device_ids.get(data_device_id)
hass.bus.async_fire(
event_type=NETATMO_EVENT,
event_data=event_data,
)
async def async_cloudhook_generate_url(
hass: HomeAssistant, entry: NetatmoConfigEntry
) -> str:
"""Generate the full URL for a webhook_id."""
if CONF_CLOUDHOOK_URL not in entry.data:
webhook_url = await cloud.async_create_cloudhook(
hass, entry.data[CONF_WEBHOOK_ID]
)
data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url}
hass.config_entries.async_update_entry(entry, data=data)
return webhook_url
return str(entry.data[CONF_CLOUDHOOK_URL])
async def async_unregister_webhook(
hass: HomeAssistant, entry: NetatmoConfigEntry
) -> None:
"""Unregister the webhook from the Netatmo backend."""
if CONF_WEBHOOK_ID not in entry.data:
return
_LOGGER.debug("Unregister Netatmo webhook (%s)", entry.data[CONF_WEBHOOK_ID])
async_dispatcher_send(
hass,
f"signal-{DOMAIN}-webhook-None",
{"type": "None", "data": {WEBHOOK_PUSH_TYPE: WEBHOOK_DEACTIVATION}},
)
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
try:
await entry.runtime_data.auth.async_dropwebhook()
except pyatmo.ApiError:
_LOGGER.debug("No webhook to be dropped for %s", entry.data[CONF_WEBHOOK_ID])
async def async_register_webhook(
hass: HomeAssistant, entry: NetatmoConfigEntry
) -> None:
"""Register the webhook with the Netatmo backend."""
if CONF_WEBHOOK_ID not in entry.data:
data = {**entry.data, CONF_WEBHOOK_ID: secrets.token_hex()}
hass.config_entries.async_update_entry(entry, data=data)
if cloud.async_active_subscription(hass):
webhook_url = await async_cloudhook_generate_url(hass, entry)
else:
webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID])
if entry.data["auth_implementation"] == cloud.DOMAIN and not webhook_url.startswith(
"https://"
):
_LOGGER.warning(
"Webhook not registered - "
"https and port 443 is required to register the webhook"
)
return
webhook_register(
hass,
DOMAIN,
"Netatmo",
entry.data[CONF_WEBHOOK_ID],
async_handle_webhook,
)
async def _handle_stop(_: Event) -> None:
await async_unregister_webhook(hass, entry)
try:
await entry.runtime_data.auth.async_addwebhook(webhook_url)
_LOGGER.debug("Register Netatmo webhook: %s", webhook_url)
# pylint: disable-next=home-assistant-action-swallowed-exception
except pyatmo.ApiError as err:
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
_LOGGER.error("Error during webhook registration - %s", err)
else:
entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _handle_stop)
)