diff --git a/.coveragerc b/.coveragerc index b02b4d1257978224e3832f46636e046d5369157c..3163b5f723c21b78c42ddc8a96e205ecdd0d96d0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -20,6 +20,9 @@ omit = homeassistant/components/android_ip_webcam.py homeassistant/components/*/android_ip_webcam.py + homeassistant/components/arlo.py + homeassistant/components/*/arlo.py + homeassistant/components/axis.py homeassistant/components/*/axis.py diff --git a/homeassistant/components/arlo.py b/homeassistant/components/arlo.py new file mode 100644 index 0000000000000000000000000000000000000000..feb772092372c2540c36caa64c7309e50029da86 --- /dev/null +++ b/homeassistant/components/arlo.py @@ -0,0 +1,60 @@ +""" +This component provides basic support for Netgear Arlo IP cameras. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/arlo/ +""" +import logging +import voluptuous as vol +from homeassistant.helpers import config_validation as cv + +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +import homeassistant.loader as loader + +from requests.exceptions import HTTPError, ConnectTimeout + +REQUIREMENTS = ['pyarlo==0.0.4'] + +_LOGGER = logging.getLogger(__name__) + +CONF_ATTRIBUTION = 'Data provided by arlo.netgear.com' + +DOMAIN = 'arlo' + +DEFAULT_BRAND = 'Netgear Arlo' + +NOTIFICATION_ID = 'arlo_notification' +NOTIFICATION_TITLE = 'Arlo Camera Setup' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up an Arlo component.""" + conf = config[DOMAIN] + username = conf.get(CONF_USERNAME) + password = conf.get(CONF_PASSWORD) + + persistent_notification = loader.get_component('persistent_notification') + try: + from pyarlo import PyArlo + + arlo = PyArlo(username, password, preload=False) + if not arlo.is_connected: + return False + hass.data['arlo'] = arlo + except (ConnectTimeout, HTTPError) as ex: + _LOGGER.error("Unable to connect to Netgar Arlo: %s", str(ex)) + persistent_notification.create( + hass, 'Error: {}<br />' + 'You will need to restart hass after fixing.' + ''.format(ex), + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID) + return False + return True diff --git a/homeassistant/components/camera/arlo.py b/homeassistant/components/camera/arlo.py new file mode 100644 index 0000000000000000000000000000000000000000..16688370b0764679afb43de8b1c07cbe7284efa4 --- /dev/null +++ b/homeassistant/components/camera/arlo.py @@ -0,0 +1,92 @@ +""" +This component provides basic support for Netgear Arlo IP cameras. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.arlo/ +""" +import asyncio +import logging +import voluptuous as vol + +from homeassistant.helpers import config_validation as cv +from homeassistant.components.arlo import DEFAULT_BRAND + +from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) +from homeassistant.components.ffmpeg import DATA_FFMPEG +from homeassistant.helpers.aiohttp_client import ( + async_aiohttp_proxy_stream) + +DEPENDENCIES = ['arlo', 'ffmpeg'] + +_LOGGER = logging.getLogger(__name__) + +CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_FFMPEG_ARGUMENTS): + cv.string, +}) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up an Arlo IP Camera.""" + arlo = hass.data.get('arlo') + if not arlo: + return False + + cameras = [] + for camera in arlo.cameras: + cameras.append(ArloCam(hass, camera, config)) + + async_add_devices(cameras, True) + return True + + +class ArloCam(Camera): + """An implementation of a Netgear Arlo IP camera.""" + + def __init__(self, hass, camera, device_info): + """Initialize an Arlo camera.""" + super().__init__() + + self._camera = camera + self._name = self._camera.name + self._ffmpeg = hass.data[DATA_FFMPEG] + self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS) + + def camera_image(self): + """Return a still image reponse from the camera.""" + return self._camera.last_image + + @asyncio.coroutine + def handle_async_mjpeg_stream(self, request): + """Generate an HTTP MJPEG stream from the camera.""" + from haffmpeg import CameraMjpeg + video = self._camera.last_video + if not video: + return + + stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) + yield from stream.open_camera( + video.video_url, extra_cmd=self._ffmpeg_arguments) + + yield from async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + yield from stream.close() + + @property + def name(self): + """Return the name of this camera.""" + return self._name + + @property + def model(self): + """Camera model.""" + return self._camera.model_id + + @property + def brand(self): + """Camera brand.""" + return DEFAULT_BRAND diff --git a/homeassistant/components/sensor/arlo.py b/homeassistant/components/sensor/arlo.py new file mode 100644 index 0000000000000000000000000000000000000000..e4e0d0330f637258ef36181774a8c29fbfa31df9 --- /dev/null +++ b/homeassistant/components/sensor/arlo.py @@ -0,0 +1,126 @@ +""" +This component provides HA sensor for Netgear Arlo IP cameras. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.arlo/ +""" +import asyncio +import logging +from datetime import timedelta +import voluptuous as vol + +from homeassistant.helpers import config_validation as cv +from homeassistant.components.arlo import ( + CONF_ATTRIBUTION, DEFAULT_BRAND) + +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, STATE_UNKNOWN) +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = ['arlo'] + +_LOGGER = logging.getLogger(__name__) + +# sensor_type [ description, unit, icon ] +SENSOR_TYPES = { + 'last_capture': ['Last', None, 'run-fast'], + 'total_cameras': ['Arlo Cameras', None, 'video'], + 'captured_today': ['Captured Today', None, 'file-video'], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), +}) + +SCAN_INTERVAL = timedelta(seconds=90) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up an Arlo IP sensor.""" + arlo = hass.data.get('arlo') + if not arlo: + return False + + sensors = [] + for sensor_type in config.get(CONF_MONITORED_CONDITIONS): + if sensor_type == 'total_cameras': + sensors.append(ArloSensor(hass, + SENSOR_TYPES[sensor_type][0], + arlo, + sensor_type)) + else: + for camera in arlo.cameras: + name = '{0} {1}'.format(SENSOR_TYPES[sensor_type][0], + camera.name) + sensors.append(ArloSensor(hass, name, camera, sensor_type)) + + async_add_devices(sensors, True) + return True + + +class ArloSensor(Entity): + """An implementation of a Netgear Arlo IP sensor.""" + + def __init__(self, hass, name, device, sensor_type): + """Initialize an Arlo sensor.""" + super().__init__() + self._name = name + self._hass = hass + self._data = device + self._sensor_type = sensor_type + self._state = None + self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[2]) + + @property + def name(self): + """Return the name of this camera.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return SENSOR_TYPES.get(self._sensor_type)[1] + + def update(self): + """Get the latest data and updates the state.""" + self._data.update() + + if self._sensor_type == 'total_cameras': + self._state = len(self._data.cameras) + + elif self._sensor_type == 'captured_today': + self._state = len(self._data.captured_today) + + elif self._sensor_type == 'last_capture': + try: + video = self._data.videos()[0] + self._state = video.created_at_pretty("%m-%d-%Y %H:%M:%S") + except (AttributeError, IndexError): + self._state = STATE_UNKNOWN + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attrs = {} + + attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION + attrs['brand'] = DEFAULT_BRAND + + if self._sensor_type == 'last_capture' or \ + self._sensor_type == 'captured_today': + attrs['model'] = self._data.model_id + + return attrs diff --git a/requirements_all.txt b/requirements_all.txt index df8582b701e72ef494b23099722f77e4c1bd2147..68d7fd5aae6c5fa9fa6552522703c0c35026e896 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -489,6 +489,9 @@ pyRFXtrx==0.18.0 # homeassistant.components.alarm_control_panel.alarmdotcom pyalarmdotcom==0.3.0 +# homeassistant.components.arlo +pyarlo==0.0.4 + # homeassistant.components.notify.xmpp pyasn1-modules==0.0.8