"""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)