mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +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."""
|
"""The Netatmo integration."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
import pyatmo
|
||||||
import voluptuous as vol
|
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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_CLIENT_ID,
|
CONF_CLIENT_ID,
|
||||||
CONF_CLIENT_SECRET,
|
CONF_CLIENT_SECRET,
|
||||||
CONF_DISCOVERY,
|
CONF_DISCOVERY,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
|
CONF_WEBHOOK_ID,
|
||||||
|
EVENT_HOMEASSISTANT_START,
|
||||||
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
|
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
|
||||||
|
|
||||||
from . import api, config_flow
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_SECRET_KEY = "secret_key"
|
CONF_SECRET_KEY = "secret_key"
|
||||||
CONF_WEBHOOKS = "webhooks"
|
CONF_WEBHOOKS = "webhooks"
|
||||||
|
|
||||||
|
WAIT_FOR_CLOUD = 5
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: 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)
|
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -95,4 +155,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
if unload_ok:
|
if unload_ok:
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
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
|
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"
|
CAMERA_DATA = "netatmo_camera"
|
||||||
HOME_DATA = "netatmo_home_data"
|
HOME_DATA = "netatmo_home_data"
|
||||||
|
|
||||||
|
CONF_CLOUDHOOK_URL = "cloudhook_url"
|
||||||
|
|
||||||
OAUTH2_AUTHORIZE = "https://api.netatmo.com/oauth2/authorize"
|
OAUTH2_AUTHORIZE = "https://api.netatmo.com/oauth2/authorize"
|
||||||
OAUTH2_TOKEN = "https://api.netatmo.com/oauth2/token"
|
OAUTH2_TOKEN = "https://api.netatmo.com/oauth2/token"
|
||||||
|
|
||||||
DATA_PERSONS = "netatmo_persons"
|
DATA_PERSONS = "netatmo_persons"
|
||||||
|
|
||||||
NETATMO_WEBHOOK_URL = None
|
NETATMO_WEBHOOK_URL = None
|
||||||
|
NETATMO_EVENT = "netatmo_event"
|
||||||
|
|
||||||
DEFAULT_PERSON = "Unknown"
|
DEFAULT_PERSON = "Unknown"
|
||||||
DEFAULT_DISCOVERY = True
|
DEFAULT_DISCOVERY = True
|
||||||
DEFAULT_WEBHOOKS = False
|
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_ID = "id"
|
||||||
ATTR_PSEUDO = "pseudo"
|
ATTR_PSEUDO = "pseudo"
|
||||||
ATTR_NAME = "name"
|
ATTR_NAME = "name"
|
||||||
ATTR_EVENT_TYPE = "event_type"
|
ATTR_EVENT_TYPE = "event_type"
|
||||||
ATTR_MESSAGE = "message"
|
|
||||||
ATTR_CAMERA_ID = "camera_id"
|
|
||||||
ATTR_HOME_ID = "home_id"
|
ATTR_HOME_ID = "home_id"
|
||||||
ATTR_HOME_NAME = "home_name"
|
ATTR_HOME_NAME = "home_name"
|
||||||
ATTR_PERSONS = "persons"
|
ATTR_PERSONS = "persons"
|
||||||
ATTR_IS_KNOWN = "is_known"
|
ATTR_IS_KNOWN = "is_known"
|
||||||
ATTR_FACE_URL = "face_url"
|
ATTR_FACE_URL = "face_url"
|
||||||
ATTR_SNAPSHOT_URL = "snapshot_url"
|
|
||||||
ATTR_VIGNETTE_URL = "vignette_url"
|
|
||||||
ATTR_SCHEDULE_ID = "schedule_id"
|
ATTR_SCHEDULE_ID = "schedule_id"
|
||||||
ATTR_SCHEDULE_NAME = "schedule_name"
|
ATTR_SCHEDULE_NAME = "schedule_name"
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
"name": "Netatmo",
|
"name": "Netatmo",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/netatmo",
|
"documentation": "https://www.home-assistant.io/integrations/netatmo",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"pyatmo==3.2.4"
|
"pyatmo==3.3.0"
|
||||||
|
],
|
||||||
|
"after_dependencies": [
|
||||||
|
"cloud"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"webhook"
|
"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
|
pyarlo==0.2.3
|
||||||
|
|
||||||
# homeassistant.components.netatmo
|
# homeassistant.components.netatmo
|
||||||
pyatmo==3.2.4
|
pyatmo==3.3.0
|
||||||
|
|
||||||
# homeassistant.components.atome
|
# homeassistant.components.atome
|
||||||
pyatome==0.1.1
|
pyatome==0.1.1
|
||||||
|
@ -434,7 +434,7 @@ pyalmond==0.0.2
|
|||||||
pyarlo==0.2.3
|
pyarlo==0.2.3
|
||||||
|
|
||||||
# homeassistant.components.netatmo
|
# homeassistant.components.netatmo
|
||||||
pyatmo==3.2.4
|
pyatmo==3.3.0
|
||||||
|
|
||||||
# homeassistant.components.blackbird
|
# homeassistant.components.blackbird
|
||||||
pyblackbird==0.5
|
pyblackbird==0.5
|
||||||
|
Loading…
x
Reference in New Issue
Block a user