-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Refactored Arlo component and enhanced Arlo API queries and times #14823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
d6ac61a
ec1442f
3f3387a
e0a252f
0cc5079
e437a0c
1cbd2f8
63ae201
66a791d
cae9616
da74c93
377a9b7
b0c3d03
a83658c
6a0a9c8
db2d2ee
1f2f03b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,9 +10,11 @@ | |
| import voluptuous as vol | ||
|
|
||
| import homeassistant.helpers.config_validation as cv | ||
| 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) | ||
|
|
@@ -36,21 +38,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].hub | ||
|
|
||
| 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): | ||
|
|
@@ -68,24 +69,29 @@ def icon(self): | |
| """Return icon.""" | ||
| return ICON | ||
|
|
||
| @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): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decorate this with |
||
| """Call update method.""" | ||
| self.schedule_update_ha_state(True) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
|
|
||
| @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.info("Updating Arlo Alarm Control Panel %s", self.name) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Max debug. |
||
| 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): | ||
|
|
@@ -125,4 +131,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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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__) | ||
|
|
||
|
|
@@ -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) | ||
|
|
||
|
|
@@ -38,14 +48,24 @@ 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 | ||
| hass.data[DATA_ARLO] = arlo | ||
|
|
||
| # assign refresh period to base station thread | ||
| try: | ||
| if arlo.base_stations: | ||
| arlo_base_station = arlo.base_stations[0] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is better: 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() | ||
| except (AttributeError, IndexError): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would there be an attribute error? The index error is solved by using my suggestion above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that is not needed anymore. Good refactoring!! Thanks |
||
| return False | ||
|
|
||
| hass.data[DATA_ARLO] = ArloHub(arlo) | ||
| except (ConnectTimeout, HTTPError) as ex: | ||
| _LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex)) | ||
| hass.components.persistent_notification.create( | ||
|
|
@@ -55,4 +75,25 @@ 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].hub.update(update_cameras=True, | ||
| update_base_station=True) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
|
|
||
| class ArloHub(object): | ||
| """Representation of the base Arlo hub component.""" | ||
|
|
||
| def __init__(self, hub): | ||
| """Initialize the entity.""" | ||
| self.hub = hub | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why store the hub on another instance if it's just that attribute and no methods needed? |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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' | ||
|
|
||
|
|
@@ -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.get(DATA_ARLO).hub | ||
| if not arlo: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can't happen.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this can happen? |
||
| return False | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only |
||
|
|
||
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, my bad. |
||
| """Call update method.""" | ||
| self.schedule_update_ha_state(True) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
|
@@ -135,7 +144,7 @@ def brand(self): | |
| @property | ||
| def should_poll(self): | ||
| """Camera should poll periodically.""" | ||
| return True | ||
| return False | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the default for cameras. |
||
|
|
||
| @property | ||
| def motion_detection_enabled(self): | ||
|
|
@@ -145,7 +154,7 @@ def motion_detection_enabled(self): | |
| def set_base_station_mode(self, mode): | ||
| """Set the mode in the base station.""" | ||
| # Get the list of base stations identified by library | ||
| base_stations = self.hass.data[DATA_ARLO].base_stations | ||
| base_stations = self.hass.data.get(DATA_ARLO).hub.base_stations | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't change to |
||
|
|
||
| # Some Arlo cameras does not have base station | ||
| # So check if there is base station detected first | ||
|
|
@@ -164,7 +173,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() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're now using Python 3.5 async syntax, ie
async def.