From 1d0df636156d5ebe2cfa65716ad97a2e013663b0 Mon Sep 17 00:00:00 2001 From: Hugo Dupras Date: Sun, 9 Oct 2016 17:45:12 +0200 Subject: [PATCH] Netatmo binary sensor (#3455) * Basic support for Netatmo welcome binary sensors Signed-off-by: Hugo D. (jabesq) * Bug fixes and optimization for Netatmo devices Signed-off-by: Hugo D. (jabesq) * pylint fixes * Bug Fixing and optimization for Netatmo devices Signed-off-by: Hugo D. (jabesq) * Add unique_id for cameras to avoid duplicate And global config to disable discovery for netatmo devices Signed-off-by: Hugo D. (jabesq) --- .../components/binary_sensor/netatmo.py | 128 ++++++++++++++++++ homeassistant/components/camera/netatmo.py | 46 ++----- homeassistant/components/netatmo.py | 43 +++++- 3 files changed, 180 insertions(+), 37 deletions(-) create mode 100644 homeassistant/components/binary_sensor/netatmo.py diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py new file mode 100644 index 00000000000..7a7f865494a --- /dev/null +++ b/homeassistant/components/binary_sensor/netatmo.py @@ -0,0 +1,128 @@ +""" +Support for the Netatmo binary sensors. + +The binary sensors based on events seen by the NetatmoCamera + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.netatmo/ +""" +import logging +import voluptuous as vol + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.netatmo import WelcomeData +from homeassistant.loader import get_component +from homeassistant.const import CONF_MONITORED_CONDITIONS +from homeassistant.helpers import config_validation as cv + +DEPENDENCIES = ["netatmo"] + +_LOGGER = logging.getLogger(__name__) + + +# These are the available sensors mapped to binary_sensor class +SENSOR_TYPES = { + "Someone known": "motion", + "Someone unknown": "motion", + "Motion": "motion", +} + +CONF_HOME = 'home' +CONF_CAMERAS = 'cameras' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_HOME): cv.string, + vol.Optional(CONF_CAMERAS, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES.keys()): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup access to Netatmo binary sensor.""" + netatmo = get_component('netatmo') + home = config.get(CONF_HOME, None) + + import lnetatmo + try: + data = WelcomeData(netatmo.NETATMO_AUTH, home) + except lnetatmo.NoDevice: + return None + + if data.get_camera_names() == []: + return None + + sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES) + + for camera_name in data.get_camera_names(): + if CONF_CAMERAS in config: + if config[CONF_CAMERAS] != [] and \ + camera_name not in config[CONF_CAMERAS]: + continue + for variable in sensors: + add_devices([WelcomeBinarySensor(data, camera_name, home, + variable)]) + + +class WelcomeBinarySensor(BinarySensorDevice): + """Represent a single binary sensor in a Netatmo Welcome device.""" + + def __init__(self, data, camera_name, home, sensor): + """Setup for access to the Netatmo camera events.""" + self._data = data + self._camera_name = camera_name + self._home = home + if home: + self._name = home + ' / ' + camera_name + else: + self._name = camera_name + self._sensor_name = sensor + self._name += ' ' + sensor + camera_id = data.welcomedata.cameraByName(camera=camera_name, + home=home)['id'] + self._unique_id = "Welcome_binary_sensor {0} - {1}".format(self._name, + camera_id) + self.update() + + @property + def name(self): + """The name of the Netatmo device and this sensor.""" + return self._name + + @property + def unique_id(self): + """Return the unique ID for this sensor.""" + return self._unique_id + + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CLASSES.""" + return SENSOR_TYPES.get(self._sensor_name) + + @property + def is_on(self): + """Return true if binary sensor is on.""" + return self._state + + def update(self): + """Request an update from the Netatmo API.""" + self._data.update() + self._data.welcomedata.updateEvent(home=self._data.home) + + if self._sensor_name == "Someone known": + self._state =\ + self._data.welcomedata.someoneKnownSeen(self._home, + self._camera_name) + elif self._sensor_name == "Someone unknown": + self._state =\ + self._data.welcomedata.someoneUnknownSeen(self._home, + self._camera_name) + elif self._sensor_name == "Motion": + self._state =\ + self._data.welcomedata.motionDetected(self._home, + self._camera_name) + else: + return None diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/camera/netatmo.py index 9069a5c6c28..259feb41a1c 100644 --- a/homeassistant/components/camera/netatmo.py +++ b/homeassistant/components/camera/netatmo.py @@ -5,12 +5,11 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/camera.netatmo/ """ import logging -from datetime import timedelta import requests import voluptuous as vol -from homeassistant.util import Throttle +from homeassistant.components.netatmo import WelcomeData from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) from homeassistant.loader import get_component from homeassistant.helpers import config_validation as cv @@ -22,8 +21,6 @@ _LOGGER = logging.getLogger(__name__) CONF_HOME = 'home' CONF_CAMERAS = 'cameras' -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HOME): cv.string, vol.Optional(CONF_CAMERAS, default=[]): @@ -43,8 +40,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return None for camera_name in data.get_camera_names(): - if config[CONF_CAMERAS] != []: - if camera_name not in config[CONF_CAMERAS]: + if CONF_CAMERAS in config: + if config[CONF_CAMERAS] != [] and \ + camera_name not in config[CONF_CAMERAS]: continue add_devices([WelcomeCamera(data, camera_name, home)]) @@ -61,6 +59,10 @@ class WelcomeCamera(Camera): self._name = home + ' / ' + camera_name else: self._name = camera_name + camera_id = data.welcomedata.cameraByName(camera=camera_name, + home=home)['id'] + self._unique_id = "Welcome_camera {0} - {1}".format(self._name, + camera_id) self._vpnurl, self._localurl = self._data.welcomedata.cameraUrls( camera=camera_name ) @@ -87,31 +89,7 @@ class WelcomeCamera(Camera): """Return the name of this Netatmo Welcome device.""" return self._name - -class WelcomeData(object): - """Get the latest data from NetAtmo.""" - - def __init__(self, auth, home=None): - """Initialize the data object.""" - self.auth = auth - self.welcomedata = None - self.camera_names = [] - self.home = home - - def get_camera_names(self): - """Return all module available on the API as a list.""" - self.update() - if not self.home: - for home in self.welcomedata.cameras: - for camera in self.welcomedata.cameras[home].values(): - self.camera_names.append(camera['name']) - else: - for camera in self.welcomedata.cameras[self.home].values(): - self.camera_names.append(camera['name']) - return self.camera_names - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Call the NetAtmo API to update the data.""" - import lnetatmo - self.welcomedata = lnetatmo.WelcomeData(self.auth) + @property + def unique_id(self): + """Return the unique ID for this sensor.""" + return self._unique_id diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py index f385940c01d..81feba85d63 100644 --- a/homeassistant/components/netatmo.py +++ b/homeassistant/components/netatmo.py @@ -5,14 +5,16 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/netatmo/ """ 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_API_KEY, CONF_PASSWORD, CONF_USERNAME, CONF_DISCOVERY) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle REQUIREMENTS = [ 'https://github.com/jabesq/netatmo-api-python/archive/' @@ -25,6 +27,9 @@ CONF_SECRET_KEY = 'secret_key' DOMAIN = 'netatmo' NETATMO_AUTH = None +DEFAULT_DISCOVERY = True + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -32,6 +37,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_SECRET_KEY): cv.string, vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean, }) }, extra=vol.ALLOW_EXTRA) @@ -50,7 +56,38 @@ def setup(hass, config): _LOGGER.error("Unable to connect to Netatmo API") return False - for component in 'camera', 'sensor': - discovery.load_platform(hass, component, DOMAIN, {}, config) + if config[DOMAIN][CONF_DISCOVERY]: + for component in 'camera', 'sensor', 'binary_sensor': + discovery.load_platform(hass, component, DOMAIN, {}, config) return True + + +class WelcomeData(object): + """Get the latest data from Netatmo.""" + + def __init__(self, auth, home=None): + """Initialize the data object.""" + self.auth = auth + self.welcomedata = None + self.camera_names = [] + self.home = home + + def get_camera_names(self): + """Return all module available on the API as a list.""" + self.camera_names = [] + self.update() + if not self.home: + for home in self.welcomedata.cameras: + for camera in self.welcomedata.cameras[home].values(): + self.camera_names.append(camera['name']) + else: + for camera in self.welcomedata.cameras[self.home].values(): + self.camera_names.append(camera['name']) + return self.camera_names + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Call the Netatmo API to update the data.""" + import lnetatmo + self.welcomedata = lnetatmo.WelcomeData(self.auth)