248 lines
8.6 KiB
Python
248 lines
8.6 KiB
Python
"""Support for the Netatmo devices."""
|
|
import logging
|
|
from datetime import timedelta
|
|
from urllib.error import HTTPError
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.const import (
|
|
CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, CONF_DISCOVERY, CONF_URL,
|
|
EVENT_HOMEASSISTANT_STOP)
|
|
from homeassistant.helpers import discovery
|
|
import homeassistant.helpers.config_validation as cv
|
|
from homeassistant.util import Throttle
|
|
|
|
from .const import DOMAIN, DATA_NETATMO_AUTH
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DATA_PERSONS = 'netatmo_persons'
|
|
DATA_WEBHOOK_URL = 'netatmo_webhook_url'
|
|
|
|
CONF_SECRET_KEY = 'secret_key'
|
|
CONF_WEBHOOKS = 'webhooks'
|
|
|
|
SERVICE_ADDWEBHOOK = 'addwebhook'
|
|
SERVICE_DROPWEBHOOK = 'dropwebhook'
|
|
|
|
NETATMO_AUTH = None
|
|
NETATMO_WEBHOOK_URL = None
|
|
|
|
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_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=5)
|
|
MIN_TIME_BETWEEN_EVENT_UPDATES = timedelta(seconds=5)
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
DOMAIN: vol.Schema({
|
|
vol.Required(CONF_API_KEY): cv.string,
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
vol.Required(CONF_SECRET_KEY): 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,
|
|
})
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
SCHEMA_SERVICE_ADDWEBHOOK = vol.Schema({
|
|
vol.Optional(CONF_URL): cv.string,
|
|
})
|
|
|
|
SCHEMA_SERVICE_DROPWEBHOOK = vol.Schema({})
|
|
|
|
|
|
def setup(hass, config):
|
|
"""Set up the Netatmo devices."""
|
|
import pyatmo
|
|
|
|
hass.data[DATA_PERSONS] = {}
|
|
try:
|
|
auth = pyatmo.ClientAuth(
|
|
config[DOMAIN][CONF_API_KEY], config[DOMAIN][CONF_SECRET_KEY],
|
|
config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD],
|
|
'read_station read_camera access_camera '
|
|
'read_thermostat write_thermostat '
|
|
'read_presence access_presence read_homecoach')
|
|
except HTTPError:
|
|
_LOGGER.error("Unable to connect to Netatmo API")
|
|
return False
|
|
|
|
# Store config to be used during entry setup
|
|
hass.data[DATA_NETATMO_AUTH] = auth
|
|
|
|
if config[DOMAIN][CONF_DISCOVERY]:
|
|
for component in 'camera', 'sensor', 'binary_sensor', 'climate':
|
|
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
|
|
|
if config[DOMAIN][CONF_WEBHOOKS]:
|
|
webhook_id = hass.components.webhook.async_generate_id()
|
|
hass.data[
|
|
DATA_WEBHOOK_URL] = hass.components.webhook.async_generate_url(
|
|
webhook_id)
|
|
hass.components.webhook.async_register(
|
|
DOMAIN, 'Netatmo', webhook_id, handle_webhook)
|
|
auth.addwebhook(hass.data[DATA_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 = hass.data[DATA_WEBHOOK_URL]
|
|
_LOGGER.info("Adding webhook for URL: %s", url)
|
|
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")
|
|
auth.dropwebhook()
|
|
|
|
hass.services.register(
|
|
DOMAIN, SERVICE_DROPWEBHOOK, _service_dropwebhook,
|
|
schema=SCHEMA_SERVICE_DROPWEBHOOK)
|
|
|
|
return True
|
|
|
|
|
|
def dropwebhook(hass):
|
|
"""Drop the webhook subscription."""
|
|
auth = hass.data[DATA_NETATMO_AUTH]
|
|
auth.dropwebhook()
|
|
|
|
|
|
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)
|
|
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] = hass.data[DATA_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:
|
|
"""Get the latest data from Netatmo."""
|
|
|
|
def __init__(self, hass, auth, home=None):
|
|
"""Initialize the data object."""
|
|
self._hass = hass
|
|
self.auth = auth
|
|
self.camera_data = None
|
|
self.camera_names = []
|
|
self.module_names = []
|
|
self.home = home
|
|
self.camera_type = None
|
|
|
|
def get_camera_names(self):
|
|
"""Return all camera available on the API as a list."""
|
|
self.camera_names = []
|
|
self.update()
|
|
if not self.home:
|
|
for home in self.camera_data.cameras:
|
|
for camera in self.camera_data.cameras[home].values():
|
|
self.camera_names.append(camera['name'])
|
|
else:
|
|
for camera in self.camera_data.cameras[self.home].values():
|
|
self.camera_names.append(camera['name'])
|
|
return self.camera_names
|
|
|
|
def get_module_names(self, camera_name):
|
|
"""Return all module available on the API as a list."""
|
|
self.module_names = []
|
|
self.update()
|
|
cam_id = self.camera_data.cameraByName(camera=camera_name,
|
|
home=self.home)['id']
|
|
for module in self.camera_data.modules.values():
|
|
if cam_id == module['cam_id']:
|
|
self.module_names.append(module['name'])
|
|
return self.module_names
|
|
|
|
def get_camera_type(self, camera=None, home=None, cid=None):
|
|
"""Return camera type for a camera, cid has preference over camera."""
|
|
self.camera_type = self.camera_data.cameraType(camera=camera,
|
|
home=home, cid=cid)
|
|
return self.camera_type
|
|
|
|
def get_persons(self):
|
|
"""Gather person data for webhooks."""
|
|
for person_id, person_data in self.camera_data.persons.items():
|
|
self._hass.data[DATA_PERSONS][person_id] = person_data.get(
|
|
ATTR_PSEUDO)
|
|
|
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
|
def update(self):
|
|
"""Call the Netatmo API to update the data."""
|
|
import pyatmo
|
|
self.camera_data = pyatmo.CameraData(self.auth, size=100)
|
|
|
|
@Throttle(MIN_TIME_BETWEEN_EVENT_UPDATES)
|
|
def update_event(self):
|
|
"""Call the Netatmo API to update the events."""
|
|
self.camera_data.updateEvent(
|
|
home=self.home, cameratype=self.camera_type)
|