263 lines
8.6 KiB
Python
263 lines
8.6 KiB
Python
"""Support for the Netatmo devices."""
|
|
import logging
|
|
from datetime import timedelta
|
|
from urllib.error import HTTPError
|
|
|
|
import pyatmo
|
|
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."""
|
|
|
|
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."""
|
|
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)
|