mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Refactor netatmo webhooks (#32195)
* Start webhook implementation * Add webhook implementation * Bump pyatmo 3.2.5 * Fire event after data evaluation * Setup webhooks after components * Fix logging * Wrap non async call * Wrap non async call * Add smoke detector and door tag webhook support * Catch when webhook registration fails * Log to debug * Fix persons lookup * Add dependency * Remove false requirements * Fix requirements * Replace netatmo events by a single one * Slim down code * Clean up code * Address review vomments * Undo attribute removal * Bump pyatmo to v3.3.0 * Only create webhook id once and reuse * Store and reuse cloudhook url * Wait for hass core to be up and running * Register webhook once HA is ready * Delay webhook registration
This commit is contained in:
parent
ba0aaeeddb
commit
b9a9a92145
@ -1,27 +1,47 @@
|
||||
"""The Netatmo integration."""
|
||||
import asyncio
|
||||
import logging
|
||||
import secrets
|
||||
|
||||
import pyatmo
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import cloud
|
||||
from homeassistant.components.webhook import (
|
||||
async_register as webhook_register,
|
||||
async_unregister as webhook_unregister,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
CONF_DISCOVERY,
|
||||
CONF_USERNAME,
|
||||
CONF_WEBHOOK_ID,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
|
||||
|
||||
from . import api, config_flow
|
||||
from .const import AUTH, DATA_PERSONS, DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
|
||||
from .const import (
|
||||
AUTH,
|
||||
CONF_CLOUDHOOK_URL,
|
||||
DATA_PERSONS,
|
||||
DOMAIN,
|
||||
OAUTH2_AUTHORIZE,
|
||||
OAUTH2_TOKEN,
|
||||
)
|
||||
from .webhook import handle_webhook
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_SECRET_KEY = "secret_key"
|
||||
CONF_WEBHOOKS = "webhooks"
|
||||
|
||||
WAIT_FOR_CLOUD = 5
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
@ -79,6 +99,46 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
async def unregister_webhook(event):
|
||||
_LOGGER.debug("Unregister Netatmo webhook (%s)", entry.data[CONF_WEBHOOK_ID])
|
||||
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
|
||||
async def register_webhook(event):
|
||||
# Wait for the could integration to be ready
|
||||
await asyncio.sleep(WAIT_FOR_CLOUD)
|
||||
|
||||
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 hass.components.cloud.async_active_subscription():
|
||||
if CONF_CLOUDHOOK_URL not in entry.data:
|
||||
webhook_url = await hass.components.cloud.async_create_cloudhook(
|
||||
entry.data[CONF_WEBHOOK_ID]
|
||||
)
|
||||
data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url}
|
||||
hass.config_entries.async_update_entry(entry, data=data)
|
||||
else:
|
||||
webhook_url = entry.data[CONF_CLOUDHOOK_URL]
|
||||
else:
|
||||
webhook_url = hass.components.webhook.async_generate_url(
|
||||
entry.data[CONF_WEBHOOK_ID]
|
||||
)
|
||||
|
||||
try:
|
||||
await hass.async_add_executor_job(
|
||||
hass.data[DOMAIN][entry.entry_id][AUTH].addwebhook, webhook_url
|
||||
)
|
||||
webhook_register(
|
||||
hass, DOMAIN, "Netatmo", entry.data[CONF_WEBHOOK_ID], handle_webhook
|
||||
)
|
||||
_LOGGER.info("Register Netatmo webhook: %s", webhook_url)
|
||||
except pyatmo.ApiError as err:
|
||||
_LOGGER.error("Error during webhook registration - %s", err)
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook)
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, register_webhook)
|
||||
return True
|
||||
|
||||
|
||||
@ -95,4 +155,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
if CONF_WEBHOOK_ID in entry.data:
|
||||
await hass.async_add_executor_job(
|
||||
hass.data[DOMAIN][entry.entry_id][AUTH].dropwebhook()
|
||||
)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Cleanup when entry is removed."""
|
||||
if CONF_WEBHOOK_ID in entry.data:
|
||||
try:
|
||||
_LOGGER.debug(
|
||||
"Removing Netatmo cloudhook (%s)", entry.data[CONF_WEBHOOK_ID]
|
||||
)
|
||||
await cloud.async_delete_cloudhook(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
except cloud.CloudNotAvailable:
|
||||
pass
|
||||
|
@ -11,43 +11,29 @@ CONF_PUBLIC = "public_sensor_config"
|
||||
CAMERA_DATA = "netatmo_camera"
|
||||
HOME_DATA = "netatmo_home_data"
|
||||
|
||||
CONF_CLOUDHOOK_URL = "cloudhook_url"
|
||||
|
||||
OAUTH2_AUTHORIZE = "https://api.netatmo.com/oauth2/authorize"
|
||||
OAUTH2_TOKEN = "https://api.netatmo.com/oauth2/token"
|
||||
|
||||
DATA_PERSONS = "netatmo_persons"
|
||||
|
||||
NETATMO_WEBHOOK_URL = None
|
||||
NETATMO_EVENT = "netatmo_event"
|
||||
|
||||
DEFAULT_PERSON = "Unknown"
|
||||
DEFAULT_DISCOVERY = True
|
||||
DEFAULT_WEBHOOKS = False
|
||||
|
||||
EVENT_PERSON = "person"
|
||||
EVENT_MOVEMENT = "movement"
|
||||
EVENT_HUMAN = "human"
|
||||
EVENT_ANIMAL = "animal"
|
||||
EVENT_VEHICLE = "vehicle"
|
||||
|
||||
EVENT_BUS_PERSON = "netatmo_person"
|
||||
EVENT_BUS_MOVEMENT = "netatmo_movement"
|
||||
EVENT_BUS_HUMAN = "netatmo_human"
|
||||
EVENT_BUS_ANIMAL = "netatmo_animal"
|
||||
EVENT_BUS_VEHICLE = "netatmo_vehicle"
|
||||
EVENT_BUS_OTHER = "netatmo_other"
|
||||
|
||||
ATTR_ID = "id"
|
||||
ATTR_PSEUDO = "pseudo"
|
||||
ATTR_NAME = "name"
|
||||
ATTR_EVENT_TYPE = "event_type"
|
||||
ATTR_MESSAGE = "message"
|
||||
ATTR_CAMERA_ID = "camera_id"
|
||||
ATTR_HOME_ID = "home_id"
|
||||
ATTR_HOME_NAME = "home_name"
|
||||
ATTR_PERSONS = "persons"
|
||||
ATTR_IS_KNOWN = "is_known"
|
||||
ATTR_FACE_URL = "face_url"
|
||||
ATTR_SNAPSHOT_URL = "snapshot_url"
|
||||
ATTR_VIGNETTE_URL = "vignette_url"
|
||||
ATTR_SCHEDULE_ID = "schedule_id"
|
||||
ATTR_SCHEDULE_NAME = "schedule_name"
|
||||
|
||||
|
@ -3,7 +3,10 @@
|
||||
"name": "Netatmo",
|
||||
"documentation": "https://www.home-assistant.io/integrations/netatmo",
|
||||
"requirements": [
|
||||
"pyatmo==3.2.4"
|
||||
"pyatmo==3.3.0"
|
||||
],
|
||||
"after_dependencies": [
|
||||
"cloud"
|
||||
],
|
||||
"dependencies": [
|
||||
"webhook"
|
||||
|
65
homeassistant/components/netatmo/webhook.py
Normal file
65
homeassistant/components/netatmo/webhook.py
Normal file
@ -0,0 +1,65 @@
|
||||
"""The Netatmo integration."""
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import (
|
||||
ATTR_EVENT_TYPE,
|
||||
ATTR_FACE_URL,
|
||||
ATTR_ID,
|
||||
ATTR_IS_KNOWN,
|
||||
ATTR_NAME,
|
||||
ATTR_PERSONS,
|
||||
DATA_PERSONS,
|
||||
DEFAULT_PERSON,
|
||||
DOMAIN,
|
||||
NETATMO_EVENT,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def handle_webhook(hass, webhook_id, request):
|
||||
"""Handle webhook callback."""
|
||||
try:
|
||||
data = await request.json()
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
_LOGGER.debug("Got webhook data: %s", data)
|
||||
|
||||
event_type = data.get(ATTR_EVENT_TYPE)
|
||||
|
||||
if event_type == "outdoor":
|
||||
hass.bus.async_fire(
|
||||
event_type=NETATMO_EVENT, event_data={"type": event_type, "data": data}
|
||||
)
|
||||
for event_data in data.get("event_list"):
|
||||
async_evaluate_event(hass, event_data)
|
||||
else:
|
||||
async_evaluate_event(hass, data)
|
||||
|
||||
|
||||
@callback
|
||||
def async_evaluate_event(hass, event_data):
|
||||
"""Evaluate events from webhook."""
|
||||
event_type = event_data.get(ATTR_EVENT_TYPE)
|
||||
|
||||
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] = hass.data[DOMAIN][DATA_PERSONS].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)
|
||||
hass.bus.async_fire(
|
||||
event_type=NETATMO_EVENT,
|
||||
event_data={"type": event_type, "data": person_event_data},
|
||||
)
|
||||
else:
|
||||
hass.bus.async_fire(
|
||||
event_type=NETATMO_EVENT,
|
||||
event_data={"type": event_type, "data": event_data},
|
||||
)
|
@ -1158,7 +1158,7 @@ pyalmond==0.0.2
|
||||
pyarlo==0.2.3
|
||||
|
||||
# homeassistant.components.netatmo
|
||||
pyatmo==3.2.4
|
||||
pyatmo==3.3.0
|
||||
|
||||
# homeassistant.components.atome
|
||||
pyatome==0.1.1
|
||||
|
@ -434,7 +434,7 @@ pyalmond==0.0.2
|
||||
pyarlo==0.2.3
|
||||
|
||||
# homeassistant.components.netatmo
|
||||
pyatmo==3.2.4
|
||||
pyatmo==3.3.0
|
||||
|
||||
# homeassistant.components.blackbird
|
||||
pyblackbird==0.5
|
||||
|
Loading…
x
Reference in New Issue
Block a user