Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 26 additions & 19 deletions homeassistant/components/alarm_control_panel/arlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.alarm_control_panel import (
AlarmControlPanel, PLATFORM_SCHEMA)
from homeassistant.components.arlo import (DATA_ARLO, CONF_ATTRIBUTION)
from homeassistant.components.arlo import (
DATA_ARLO, CONF_ATTRIBUTION, SIGNAL_UPDATE_ARLO)
from homeassistant.const import (
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
Expand All @@ -36,21 +39,20 @@
})


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Arlo Alarm Control Panels."""
data = hass.data[DATA_ARLO]
arlo = hass.data[DATA_ARLO]

if not data.base_stations:
if not arlo.base_stations:
return

home_mode_name = config.get(CONF_HOME_MODE_NAME)
away_mode_name = config.get(CONF_AWAY_MODE_NAME)
base_stations = []
for base_station in data.base_stations:
for base_station in arlo.base_stations:
base_stations.append(ArloBaseStation(base_station, home_mode_name,
away_mode_name))
async_add_devices(base_stations, True)
add_devices(base_stations, True)


class ArloBaseStation(AlarmControlPanel):
Expand All @@ -68,24 +70,29 @@ def icon(self):
"""Return icon."""
return ICON

async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)

@callback
def _update_callback(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decorate this with @callback imported from core.py.

"""Call update method."""
self.async_schedule_update_ha_state()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass True as argument to call the update method before updating state.

self.async_schedule_update_ha_state(True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add True as argument.


@property
def state(self):
"""Return the state of the device."""
return self._state

def update(self):
"""Update the state of the device."""
# PyArlo sometimes returns None for mode. So retry 3 times before
# returning None.
num_retries = 3
i = 0
while i < num_retries:
mode = self._base_station.mode
if mode:
self._state = self._get_state_from_mode(mode)
return
i += 1
self._state = None
_LOGGER.debug("Updating Arlo Alarm Control Panel %s", self.name)
mode = self._base_station.mode
if mode:
self._state = self._get_state_from_mode(mode)
else:
self._state = None

@asyncio.coroutine
def async_alarm_disarm(self, code=None):
Expand Down Expand Up @@ -125,4 +132,4 @@ def _get_state_from_mode(self, mode):
return STATE_ALARM_ARMED_HOME
elif mode == self._away_mode_name:
return STATE_ALARM_ARMED_AWAY
return None
return mode
38 changes: 36 additions & 2 deletions homeassistant/components/arlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
https://home-assistant.io/components/arlo/
"""
import logging
from datetime import timedelta

import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout

from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL)
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send

REQUIREMENTS = ['pyarlo==0.1.2']
REQUIREMENTS = ['pyarlo==0.1.4']

_LOGGER = logging.getLogger(__name__)

Expand All @@ -25,10 +29,16 @@
NOTIFICATION_ID = 'arlo_notification'
NOTIFICATION_TITLE = 'Arlo Component Setup'

SCAN_INTERVAL = timedelta(seconds=60)

SIGNAL_UPDATE_ARLO = "arlo_update"

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
}),
}, extra=vol.ALLOW_EXTRA)

Expand All @@ -38,14 +48,25 @@ def setup(hass, config):
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
scan_interval = conf.get(CONF_SCAN_INTERVAL)

try:
from pyarlo import PyArlo

arlo = PyArlo(username, password, preload=False)
if not arlo.is_connected:
return False

# assign refresh period to base station thread
arlo_base_station = next((
station for station in arlo.base_stations), None)

if arlo_base_station is None:
return False

arlo_base_station.refresh_rate = scan_interval.total_seconds()
hass.data[DATA_ARLO] = arlo

except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
hass.components.persistent_notification.create(
Expand All @@ -55,4 +76,17 @@ def setup(hass, config):
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False

def hub_refresh(event_time):
"""Call ArloHub to refresh information."""
_LOGGER.info("Updating Arlo Hub component")
hass.data[DATA_ARLO].update(update_cameras=True,
update_base_station=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line over-indented for visual indent

dispatcher_send(hass, SIGNAL_UPDATE_ARLO)

# register service
hass.services.register(DOMAIN, 'update', hub_refresh)

# register scan interval for ArloHub
track_time_interval(hass, hub_refresh, scan_interval)
return True
40 changes: 20 additions & 20 deletions homeassistant/components/camera/arlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@
"""
import asyncio
import logging
from datetime import timedelta

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO
from homeassistant.components.arlo import (
DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.helpers.dispatcher import async_dispatcher_connect

_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(seconds=90)

ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed'

Expand All @@ -44,14 +43,13 @@
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS):
cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an Arlo IP Camera."""
arlo = hass.data.get(DATA_ARLO)
arlo = hass.data[DATA_ARLO]
if not arlo:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't happen.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this can happen?

return False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only return.


Expand All @@ -74,21 +72,32 @@ def __init__(self, hass, camera, device_info):
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._last_refresh = None
if self._camera.base_station:
self._camera.base_station.refresh_rate = \
SCAN_INTERVAL.total_seconds()
self.attrs = {}

def camera_image(self):
"""Return a still image response from the camera."""
return self._camera.last_image
return self._camera.last_image_from_cache

@asyncio.coroutine

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update all coroutines to use new syntax.

def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)

def _update_callback(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update this like my previous comment. I haven't commented on all the places that needs the same changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, my bad.

"""Call update method."""
self.schedule_update_ha_state(True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.


@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:
error_msg = \
'Video not found for {0}. Is it older than {1} days?'.format(
self.name, self._camera.min_days_vdo_cache)
_LOGGER.error(error_msg)
return

stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
Expand Down Expand Up @@ -132,11 +141,6 @@ def brand(self):
"""Return the camera brand."""
return DEFAULT_BRAND

@property
def should_poll(self):
"""Camera should poll periodically."""
return True

@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
Expand Down Expand Up @@ -164,7 +168,3 @@ def disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self.set_base_station_mode(ARLO_MODE_DISARMED)

def update(self):
"""Add an attribute-update task to the executor pool."""
self._camera.update()
49 changes: 24 additions & 25 deletions homeassistant/components/sensor/arlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@
"""
import asyncio
import logging
from datetime import timedelta

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.arlo import (
CONF_ATTRIBUTION, DEFAULT_BRAND, DATA_ARLO)
CONF_ATTRIBUTION, DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO)
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.icon import icon_for_battery_level

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = ['arlo']

SCAN_INTERVAL = timedelta(seconds=90)

# sensor_type [ description, unit, icon ]
SENSOR_TYPES = {
'last_capture': ['Last', None, 'run-fast'],
Expand All @@ -39,8 +37,7 @@
})


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an Arlo IP sensor."""
arlo = hass.data.get(DATA_ARLO)
if not arlo:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Expand All @@ -50,24 +47,23 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
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))
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))
sensors.append(ArloSensor(name, camera, sensor_type))

async_add_devices(sensors, True)
add_devices(sensors, True)
return True

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this.



class ArloSensor(Entity):
"""An implementation of a Netgear Arlo IP sensor."""

def __init__(self, hass, name, device, sensor_type):
def __init__(self, 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
Expand All @@ -78,6 +74,16 @@ def name(self):
"""Return the name of this camera."""
return self._name

@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)

def _update_callback(self):
"""Call update method."""
self.schedule_update_ha_state(True)

@property
def state(self):
"""Return the state of the sensor."""
Expand All @@ -98,18 +104,7 @@ def unit_of_measurement(self):

def update(self):
"""Get the latest data and updates the state."""
try:
base_station = self._data.base_station
except (AttributeError, IndexError):
return

if not base_station:
return

base_station.refresh_rate = SCAN_INTERVAL.total_seconds()

self._data.update()

_LOGGER.debug("Updating Arlo sensor %s", self.name)
if self._sensor_type == 'total_cameras':
self._state = len(self._data.cameras)

Expand All @@ -118,9 +113,13 @@ def update(self):

elif self._sensor_type == 'last_capture':
try:
video = self._data.videos()[0]
video = self._data.last_video
self._state = video.created_at_pretty("%m-%d-%Y %H:%M:%S")
except (AttributeError, IndexError):
error_msg = \
'Video not found for {0}. Older than {1} days?'.format(
self.name, self._data.min_days_vdo_cache)
_LOGGER.debug(error_msg)
self._state = None

elif self._sensor_type == 'battery_level':
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ pyairvisual==1.0.0
pyalarmdotcom==0.3.2

# homeassistant.components.arlo
pyarlo==0.1.2
pyarlo==0.1.4

# homeassistant.components.notify.xmpp
pyasn1-modules==0.1.5
Expand Down