Add webhook support for Netatmo Cameras (#20755)

Add webhook support for Netatmo Cameras
This commit is contained in:
Daniel Perna 2019-02-17 12:31:47 +01:00 committed by GitHub
parent 33b8dbe73a
commit 847711ddc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 2 deletions

View File

@ -1,26 +1,64 @@
"""Support for the Netatmo devices.""" """Support for the Netatmo devices."""
import logging import logging
import json
from datetime import timedelta from datetime import timedelta
from urllib.error import HTTPError from urllib.error import HTTPError
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, CONF_DISCOVERY) CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, CONF_DISCOVERY, CONF_URL,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle from homeassistant.util import Throttle
REQUIREMENTS = ['pyatmo==1.8'] REQUIREMENTS = ['pyatmo==1.8']
DEPENDENCIES = ['webhook']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_SECRET_KEY = 'secret_key' CONF_SECRET_KEY = 'secret_key'
CONF_WEBHOOKS = 'webhooks'
DOMAIN = 'netatmo' DOMAIN = 'netatmo'
SERVICE_ADDWEBHOOK = 'addwebhook'
SERVICE_DROPWEBHOOK = 'dropwebhook'
NETATMO_AUTH = None NETATMO_AUTH = None
NETATMO_WEBHOOK_URL = None
NETATMO_PERSONS = {}
DEFAULT_PERSON = 'Unknown'
DEFAULT_DISCOVERY = True 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_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'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10)
MIN_TIME_BETWEEN_EVENT_UPDATES = timedelta(seconds=10) MIN_TIME_BETWEEN_EVENT_UPDATES = timedelta(seconds=10)
@ -31,16 +69,23 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_SECRET_KEY): cv.string, vol.Required(CONF_SECRET_KEY): cv.string,
vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_WEBHOOKS, default=DEFAULT_WEBHOOKS): cv.boolean,
vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean, vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean,
}) })
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
SCHEMA_SERVICE_ADDWEBHOOK = vol.Schema({
vol.Optional(CONF_URL): cv.string,
})
SCHEMA_SERVICE_DROPWEBHOOK = vol.Schema({})
def setup(hass, config): def setup(hass, config):
"""Set up the Netatmo devices.""" """Set up the Netatmo devices."""
import pyatmo import pyatmo
global NETATMO_AUTH global NETATMO_AUTH, NETATMO_WEBHOOK_URL
try: try:
NETATMO_AUTH = pyatmo.ClientAuth( NETATMO_AUTH = pyatmo.ClientAuth(
config[DOMAIN][CONF_API_KEY], config[DOMAIN][CONF_SECRET_KEY], config[DOMAIN][CONF_API_KEY], config[DOMAIN][CONF_SECRET_KEY],
@ -56,9 +101,88 @@ def setup(hass, config):
for component in 'camera', 'sensor', 'binary_sensor', 'climate': for component in 'camera', 'sensor', 'binary_sensor', 'climate':
discovery.load_platform(hass, component, DOMAIN, {}, config) discovery.load_platform(hass, component, DOMAIN, {}, config)
if config[DOMAIN][CONF_WEBHOOKS]:
webhook_id = hass.components.webhook.async_generate_id()
NETATMO_WEBHOOK_URL = hass.components.webhook.async_generate_url(
webhook_id)
hass.components.webhook.async_register(
DOMAIN, 'Netatmo', webhook_id, handle_webhook)
NETATMO_AUTH.addwebhook(NETATMO_WEBHOOK_URL)
hass.bus.listen_once(
EVENT_HOMEASSISTANT_STOP, dropwebhook)
def _service_addwebhook(service):
"""Service to (re)add webhooks during runtime."""
url = service.data.get(CONF_URL)
if url is None:
url = NETATMO_WEBHOOK_URL
_LOGGER.info("Adding webhook for URL: %s", url)
NETATMO_AUTH.addwebhook(url)
hass.services.register(
DOMAIN, SERVICE_ADDWEBHOOK, _service_addwebhook,
schema=SCHEMA_SERVICE_ADDWEBHOOK)
def _service_dropwebhook(service):
"""Service to drop webhooks during runtime."""
_LOGGER.info("Dropping webhook")
NETATMO_AUTH.dropwebhook()
hass.services.register(
DOMAIN, SERVICE_DROPWEBHOOK, _service_dropwebhook,
schema=SCHEMA_SERVICE_DROPWEBHOOK)
return True return True
def dropwebhook(hass):
"""Drop the webhook subscription."""
NETATMO_AUTH.dropwebhook()
async def handle_webhook(hass, webhook_id, request):
"""Handle webhook callback."""
body = await request.text()
try:
data = json.loads(body) if body else {}
except ValueError:
return None
_LOGGER.debug("Got webhook data: %s", data)
published_data = {
ATTR_EVENT_TYPE: data.get(ATTR_EVENT_TYPE),
ATTR_HOME_NAME: data.get(ATTR_HOME_NAME),
ATTR_CAMERA_ID: data.get(ATTR_CAMERA_ID),
ATTR_MESSAGE: data.get(ATTR_MESSAGE)
}
if data.get(ATTR_EVENT_TYPE) == EVENT_PERSON:
for person in data[ATTR_PERSONS]:
published_data[ATTR_ID] = person.get(ATTR_ID)
published_data[ATTR_NAME] = NETATMO_PERSONS.get(
published_data[ATTR_ID], DEFAULT_PERSON)
published_data[ATTR_IS_KNOWN] = person.get(ATTR_IS_KNOWN)
published_data[ATTR_FACE_URL] = person.get(ATTR_FACE_URL)
hass.bus.async_fire(EVENT_BUS_PERSON, published_data)
elif data.get(ATTR_EVENT_TYPE) == EVENT_MOVEMENT:
published_data[ATTR_VIGNETTE_URL] = data.get(ATTR_VIGNETTE_URL)
published_data[ATTR_SNAPSHOT_URL] = data.get(ATTR_SNAPSHOT_URL)
hass.bus.async_fire(EVENT_BUS_MOVEMENT, published_data)
elif data.get(ATTR_EVENT_TYPE) == EVENT_HUMAN:
published_data[ATTR_VIGNETTE_URL] = data.get(ATTR_VIGNETTE_URL)
published_data[ATTR_SNAPSHOT_URL] = data.get(ATTR_SNAPSHOT_URL)
hass.bus.async_fire(EVENT_BUS_HUMAN, published_data)
elif data.get(ATTR_EVENT_TYPE) == EVENT_ANIMAL:
published_data[ATTR_VIGNETTE_URL] = data.get(ATTR_VIGNETTE_URL)
published_data[ATTR_SNAPSHOT_URL] = data.get(ATTR_SNAPSHOT_URL)
hass.bus.async_fire(EVENT_BUS_ANIMAL, published_data)
elif data.get(ATTR_EVENT_TYPE) == EVENT_VEHICLE:
hass.bus.async_fire(EVENT_BUS_VEHICLE, published_data)
published_data[ATTR_VIGNETTE_URL] = data.get(ATTR_VIGNETTE_URL)
published_data[ATTR_SNAPSHOT_URL] = data.get(ATTR_SNAPSHOT_URL)
else:
hass.bus.async_fire(EVENT_BUS_OTHER, data)
class CameraData: class CameraData:
"""Get the latest data from Netatmo.""" """Get the latest data from Netatmo."""
@ -101,6 +225,12 @@ class CameraData:
home=home, cid=cid) home=home, cid=cid)
return self.camera_type return self.camera_type
def get_persons(self):
"""Gather person data for webhooks."""
global NETATMO_PERSONS
for person_id, person_data in self.camera_data.persons.items():
NETATMO_PERSONS[person_id] = person_data.get(ATTR_PSEUDO)
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Call the Netatmo API to update the data.""" """Call the Netatmo API to update the data."""

View File

@ -40,6 +40,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
continue continue
add_entities([NetatmoCamera(data, camera_name, home, add_entities([NetatmoCamera(data, camera_name, home,
camera_type, verify_ssl)]) camera_type, verify_ssl)])
data.get_persons()
except pyatmo.NoDevice: except pyatmo.NoDevice:
return None return None

View File

@ -0,0 +1,8 @@
addwebhook:
description: Add webhook during runtime (e.g. if it has been banned).
fields:
url:
description: URL for which to add the webhook.
example: https://yourdomain.com:443/api/webhook/webhook_id
dropwebhook:
description: Drop active webhooks.