From 629b2e81bac0db928dea9fb23e8c70422f820d34 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 7 Mar 2017 17:26:53 -0500 Subject: [PATCH] Support for Blink Camera System (#6444) * Passing pep8, no tests yet * Fixed some issues with the request throttling * Removed ability to set throttle time because it was causing more issues than it was worth * Added blink to .coveragerc * Changed blinkpy version * Removed global var, fixed per PR requests * Added services for camera, migrated switch to binary_sensor * Added schema for service, fixed naming, removed unused function --- .coveragerc | 3 + .../components/binary_sensor/blink.py | 74 ++++++++++++++++ homeassistant/components/blink.py | 87 +++++++++++++++++++ homeassistant/components/camera/blink.py | 81 +++++++++++++++++ homeassistant/components/sensor/blink.py | 84 ++++++++++++++++++ requirements_all.txt | 3 + 6 files changed, 332 insertions(+) create mode 100644 homeassistant/components/binary_sensor/blink.py create mode 100644 homeassistant/components/blink.py create mode 100644 homeassistant/components/camera/blink.py create mode 100644 homeassistant/components/sensor/blink.py diff --git a/.coveragerc b/.coveragerc index 0a226dd147e..cd9ca93b5c8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -16,6 +16,9 @@ omit = homeassistant/components/bbb_gpio.py homeassistant/components/*/bbb_gpio.py + + homeassistant/components/blink.py + homeassistant/components/*/blink.py homeassistant/components/bloomsky.py homeassistant/components/*/bloomsky.py diff --git a/homeassistant/components/binary_sensor/blink.py b/homeassistant/components/binary_sensor/blink.py new file mode 100644 index 00000000000..8d84ffb9c90 --- /dev/null +++ b/homeassistant/components/binary_sensor/blink.py @@ -0,0 +1,74 @@ +""" +Support for Blink system camera control. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.blink/ +""" +from homeassistant.components.blink import DOMAIN +from homeassistant.components.binary_sensor import BinarySensorDevice + +DEPENDENCIES = ['blink'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the blink binary sensors.""" + if discovery_info is None: + return + + data = hass.data[DOMAIN].blink + devs = list() + for name in data.cameras: + devs.append(BlinkCameraMotionSensor(name, data)) + devs.append(BlinkSystemSensor(data)) + add_devices(devs, True) + + +class BlinkCameraMotionSensor(BinarySensorDevice): + """A representation of a Blink binary sensor.""" + + def __init__(self, name, data): + """Initialize the sensor.""" + self._name = 'blink_' + name + '_motion_enabled' + self._camera_name = name + self.data = data + self._state = self.data.cameras[self._camera_name].armed + + @property + def name(self): + """Return the name of the blink sensor.""" + return self._name + + @property + def is_on(self): + """Return the status of the sensor.""" + return self._state + + def update(self): + """Update sensor state.""" + self.data.refresh() + self._state = self.data.cameras[self._camera_name].armed + + +class BlinkSystemSensor(BinarySensorDevice): + """A representation of a Blink system sensor.""" + + def __init__(self, data): + """Initialize the sensor.""" + self._name = 'blink armed status' + self.data = data + self._state = self.data.arm + + @property + def name(self): + """Return the name of the blink sensor.""" + return self._name.replace(" ", "_") + + @property + def is_on(self): + """Return the status of the sensor.""" + return self._state + + def update(self): + """Update sensor state.""" + self.data.refresh() + self._state = self.data.arm diff --git a/homeassistant/components/blink.py b/homeassistant/components/blink.py new file mode 100644 index 00000000000..94635e2ae59 --- /dev/null +++ b/homeassistant/components/blink.py @@ -0,0 +1,87 @@ +""" +Support for Blink Home Camera System. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/blink/ +""" +import logging +import voluptuous as vol +import homeassistant.helpers.config_validation as cv +from homeassistant.const import (CONF_USERNAME, + CONF_PASSWORD, + ATTR_FRIENDLY_NAME, + ATTR_ARMED) +from homeassistant.helpers import discovery +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'blink' +REQUIREMENTS = ['blinkpy==0.4.4'] + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string + }) +}, extra=vol.ALLOW_EXTRA) + +ARM_SYSTEM_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ARMED): cv.boolean +}) + +ARM_CAMERA_SCHEMA = vol.Schema({ + vol.Required(ATTR_FRIENDLY_NAME): cv.string, + vol.Optional(ATTR_ARMED): cv.boolean +}) + +SNAP_PICTURE_SCHEMA = vol.Schema({ + vol.Required(ATTR_FRIENDLY_NAME): cv.string +}) + + +class BlinkSystem(object): + """Blink System class.""" + + def __init__(self, config_info): + """Initialize the system.""" + import blinkpy + self.blink = blinkpy.Blink(username=config_info[DOMAIN][CONF_USERNAME], + password=config_info[DOMAIN][CONF_PASSWORD]) + self.blink.setup_system() + + +def setup(hass, config): + """Setup Blink System.""" + hass.data[DOMAIN] = BlinkSystem(config) + discovery.load_platform(hass, 'camera', DOMAIN, {}, config) + discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) + discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config) + + def snap_picture(call): + """Take a picture.""" + cameras = hass.data[DOMAIN].blink.cameras + name = call.data.get(ATTR_FRIENDLY_NAME, '') + if name in cameras: + cameras[name].snap_picture() + + def arm_camera(call): + """Arm a camera.""" + cameras = hass.data[DOMAIN].blink.cameras + name = call.data.get(ATTR_FRIENDLY_NAME, '') + value = call.data.get(ATTR_ARMED, True) + if name in cameras: + cameras[name].set_motion_detect(value) + + def arm_system(call): + """Arm the system.""" + value = call.data.get(ATTR_ARMED, True) + hass.data[DOMAIN].blink.arm = value + hass.data[DOMAIN].blink.refresh() + + hass.services.register(DOMAIN, 'snap_picture', snap_picture, + schema=SNAP_PICTURE_SCHEMA) + hass.services.register(DOMAIN, 'arm_camera', arm_camera, + schema=ARM_CAMERA_SCHEMA) + hass.services.register(DOMAIN, 'arm_system', arm_system, + schema=ARM_SYSTEM_SCHEMA) + + return True diff --git a/homeassistant/components/camera/blink.py b/homeassistant/components/camera/blink.py new file mode 100644 index 00000000000..685ee5bd0fa --- /dev/null +++ b/homeassistant/components/camera/blink.py @@ -0,0 +1,81 @@ +""" +Support for Blink system camera. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.blink/ +""" +import logging + +from datetime import timedelta +import requests + +from homeassistant.components.blink import DOMAIN +from homeassistant.components.camera import Camera +from homeassistant.util import Throttle + +DEPENDENCIES = ['blink'] + +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup a Blink Camera.""" + if discovery_info is None: + return + + data = hass.data[DOMAIN].blink + devs = list() + for name in data.cameras: + devs.append(BlinkCamera(hass, config, data, name)) + + add_devices(devs) + + +class BlinkCamera(Camera): + """An implementation of a Blink Camera.""" + + def __init__(self, hass, config, data, name): + """Initialize a camera.""" + super().__init__() + self.data = data + self.hass = hass + self._name = name + self.notifications = self.data.cameras[self._name].notifications + self.response = None + + _LOGGER.info("Initialized blink camera %s", self._name) + + @property + def name(self): + """A camera name.""" + return self._name + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def request_image(self): + """An image request from Blink servers.""" + _LOGGER.info("Requesting new image from blink servers") + image_url = self.check_for_motion() + header = self.data.cameras[self._name].header + self.response = requests.get(image_url, headers=header, stream=True) + + def check_for_motion(self): + """A method to check if motion has been detected since last update.""" + self.data.refresh() + notifs = self.data.cameras[self._name].notifications + if notifs > self.notifications: + # We detected motion at some point + self.data.last_motion() + self.notifications = notifs + # returning motion image currently not working + # return self.data.cameras[self._name].motion['image'] + elif notifs < self.notifications: + self.notifications = notifs + + return self.data.camera_thumbs[self._name] + + def camera_image(self): + """Return a still image reponse from the camera.""" + self.request_image() + return self.response.content diff --git a/homeassistant/components/sensor/blink.py b/homeassistant/components/sensor/blink.py new file mode 100644 index 00000000000..738f8cb2768 --- /dev/null +++ b/homeassistant/components/sensor/blink.py @@ -0,0 +1,84 @@ +""" +Support for Blink system camera sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.blink/ +""" +import logging + +from homeassistant.components.blink import DOMAIN +from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = ['blink'] +SENSOR_TYPES = { + 'temperature': ['Temperature', TEMP_FAHRENHEIT], + 'battery': ['Battery', ''], + 'notifications': ['Notifications', ''] +} + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup a Blink sensor.""" + if discovery_info is None: + return + + data = hass.data[DOMAIN].blink + devs = list() + index = 0 + for name in data.cameras: + devs.append(BlinkSensor(name, 'temperature', index, data)) + devs.append(BlinkSensor(name, 'battery', index, data)) + devs.append(BlinkSensor(name, 'notifications', index, data)) + index += 1 + + add_devices(devs, True) + + +class BlinkSensor(Entity): + """A Blink camera sensor.""" + + def __init__(self, name, sensor_type, index, data): + """A method to initialize sensors from Blink camera.""" + self._name = 'blink_' + name + '_' + SENSOR_TYPES[sensor_type][0] + self._camera_name = name + self._type = sensor_type + self.data = data + self.index = index + self._state = None + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + + @property + def name(self): + """A method to return the name of the camera.""" + return self._name + + @property + def state(self): + """A camera's current state.""" + return self._state + + @property + def unique_id(self): + """A unique camera sensor identifier.""" + return "sensor_{}_{}".format(self._name, self.index) + + @property + def unit_of_measurement(self): + """A method to determine the unit of measurement for temperature.""" + return self._unit_of_measurement + + def update(self): + """A method to retrieve sensor data from the camera.""" + camera = self.data.cameras[self._camera_name] + if self._type == 'temperature': + self._state = camera.temperature + elif self._type == 'battery': + self._state = camera.battery + elif self._type == 'notifications': + self._state = camera.notifications + else: + self._state = None + _LOGGER.warning("Could not retrieve state from %s", self.name) diff --git a/requirements_all.txt b/requirements_all.txt index 1116e5c62e8..6e4f3912ab2 100755 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -67,6 +67,9 @@ batinfo==0.4.2 # homeassistant.components.sensor.scrape beautifulsoup4==4.5.3 +# homeassistant.components.blink +blinkpy==0.4.4 + # homeassistant.components.light.blinksticklight blinkstick==1.1.8