Skip to content
This repository has been archived by the owner on Jul 30, 2021. It is now read-only.

Commit

Permalink
Merge pull request #79 from tchellomello/version_0.1.3
Browse files Browse the repository at this point in the history
Version 0.1.3
  • Loading branch information
tchellomello authored Jun 5, 2018
2 parents 8fbd2be + 0c86667 commit aad14b8
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 38 deletions.
33 changes: 30 additions & 3 deletions pyarlo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,12 @@ def lookup_camera_by_id(self, device_id):
def refresh_attributes(self, name):
"""Refresh attributes from a given Arlo object."""
url = DEVICES_ENDPOINT
data = self.query(url).get('data')
for device in data:
response = self.query(url)

if not response or not isinstance(response, dict):
return None

for device in response.get('data'):
if device.get('deviceName') == name:
return device
return None
Expand Down Expand Up @@ -237,8 +241,31 @@ def is_connected(self):
"""Connection status of client with Arlo system."""
return bool(self.authenticated)

def update(self):
def update(self, update_cameras=False, update_base_station=False):
"""Refresh object."""
self._authenticate()

# update attributes in all cameras to avoid duped queries
if update_cameras:
url = DEVICES_ENDPOINT
response = self.query(url)
if not response or not isinstance(response, dict):
return

for camera in self.cameras:
for dev_info in response.get('data'):
if dev_info.get('deviceName') == camera.name:
_LOGGER.debug("Refreshing %s attributes", camera.name)
camera.attrs = dev_info

# preload cached videos
# the user is still able to force a new query by
# calling the Arlo.video()
camera.make_video_cache()

# force update base_station
if update_base_station:
for base in self.base_stations:
base.update()

# vim:sw=4:ts=4:et:
58 changes: 36 additions & 22 deletions pyarlo/base_station.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,35 @@ def thread_function(self):
url = SUBSCRIBE_ENDPOINT + "?token=" + self._session_token

data = self._session.query(url, method='GET', raw=True, stream=True)
if not data or not data.ok:
_LOGGER.debug("Did not receive a valid response. Aborting..")
return None

self.__sseclient = sseclient.SSEClient(data)

for event in (self.__sseclient).events():
if not self.__subscribed:
break
data = json.loads(event.data)
if data.get('status') == "connected":
_LOGGER.debug("Successfully subscribed this base station")
elif data.get('action'):
action = data.get('action')
resource = data.get('resource')
if action == "logout":
_LOGGER.debug("Logged out by some other entity")
self.__subscribed = False
try:
for event in (self.__sseclient).events():
if not self.__subscribed:
break
elif action == "is" and "subscriptions/" not in resource:
self.__events.append(data)
self.__event_handle.set()
data = json.loads(event.data)
if data.get('status') == "connected":
_LOGGER.debug("Successfully subscribed this base station")
elif data.get('action'):
action = data.get('action')
resource = data.get('resource')
if action == "logout":
_LOGGER.debug("Logged out by some other entity")
self.__subscribed = False
break
elif action == "is" and "subscriptions/" not in resource:
self.__events.append(data)
self.__event_handle.set()

except TypeError as error:
_LOGGER.debug("Got unexpected error: %s", error)
return None

return True

def _get_event_stream(self):
"""Spawn a thread and monitor the Arlo Event Stream."""
Expand Down Expand Up @@ -277,13 +288,16 @@ def available_modes_with_ids(self):
all_modes = FIXED_MODES.copy()
self._available_mode_ids = all_modes
modes = self.get_available_modes()
if modes:
simple_modes = dict(
[(m.get("type", m.get("name")), m.get("id"))
for m in modes]
)
all_modes.update(simple_modes)
self._available_mode_ids = all_modes
try:
if modes:
simple_modes = dict(
[(m.get("type", m.get("name")), m.get("id"))
for m in modes]
)
all_modes.update(simple_modes)
self._available_mode_ids = all_modes
except TypeError:
_LOGGER.debug("Did not receive a valid response. Passing..")
return self._available_mode_ids

@property
Expand Down
73 changes: 63 additions & 10 deletions pyarlo/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
from pyarlo.const import (
RESET_CAM_ENDPOINT, STREAM_ENDPOINT, STREAMING_BODY,
SNAPSHOTS_ENDPOINT, SNAPSHOTS_BODY)
SNAPSHOTS_ENDPOINT, SNAPSHOTS_BODY, PRELOAD_DAYS)
from pyarlo.media import ArloMediaLibrary
from pyarlo.utils import http_get

Expand All @@ -13,21 +13,45 @@
class ArloCamera(object):
"""Arlo Camera module implementation."""

def __init__(self, name, attrs, arlo_session):
def __init__(self, name, attrs, arlo_session,
min_days_vdo_cache=PRELOAD_DAYS):
"""Initialize Arlo camera object.
:param name: Camera name
:param attrs: Camera attributes
:param arlo_session: PyArlo shared session
:param min_days_vdo_cache: min. days to preload in video cache
"""
self.name = name
self._attrs = attrs
self._session = arlo_session
self._cached_videos = None
self._min_days_vdo_cache = min_days_vdo_cache

def __repr__(self):
"""Representation string of object."""
return "<{0}: {1}>".format(self.__class__.__name__, self.name)

@property
def attrs(self):
"""Return device attributes."""
return self._attrs

@attrs.setter
def attrs(self, value):
"""Override device attributes."""
self._attrs = value

@property
def min_days_vdo_cache(self):
"""Return minimal days to lookup when building the video cache."""
return self._min_days_vdo_cache

@min_days_vdo_cache.setter
def min_days_vdo_cache(self, value):
"""Set minimal days to lookup when building the video cache."""
self._min_days_vdo_cache = value

# pylint: disable=invalid-name
@property
def device_id(self):
Expand Down Expand Up @@ -92,34 +116,63 @@ def user_role(self):

@property
def last_image(self):
"""Return last image capture by camera."""
"""Return last image captured by camera."""
return http_get(self._attrs.get('presignedLastImageUrl'))

@property
def last_image_from_cache(self):
"""
Return last thumbnail present in self._cached_images.
This is useful in Home Assistant when the ArloHub has not
updated all information, but the camera.arlo already pulled
the last image. Using this method, everything is kept synced.
"""
if self.last_video:
return http_get(self.last_video.thumbnail_url)
return None

@property
def last_video(self):
"""Return the last <ArloVideo> object from camera."""
library = ArloMediaLibrary(self._session, preload=False)
try:
return library.load(only_cameras=[self], limit=1)[0]
except IndexError:
return None
if self._cached_videos is None:
self.make_video_cache()

if self._cached_videos:
return self._cached_videos[0]
return None

def videos(self, days=180):
def make_video_cache(self, days=None):
"""Save videos on _cache_videos to avoid dups."""
if days is None:
days = self._min_days_vdo_cache
self._cached_videos = self.videos(days)

def videos(self, days=None):
"""
Return all <ArloVideo> objects from camera given days range
:param days: number of days to retrieve
"""
if days is None:
days = self._min_days_vdo_cache
library = ArloMediaLibrary(self._session, preload=False)
try:
return library.load(only_cameras=[self], days=days)
except (AttributeError, IndexError):
# make sure we are returning an empty list istead of None
# returning an empty list, cache will be forced only when calling
# the update method. Changing this can impact badly
# in the Home Assistant performance
return []

@property
def captured_today(self):
"""Return list of <ArloVideo> object captured today."""
return self.videos(days=0)
if self._cached_videos is None:
self.make_video_cache()

return [vdo for vdo in self._cached_videos if vdo.created_today]

def play_last_video(self):
"""Play last <ArloVideo> recorded from camera."""
Expand Down
14 changes: 13 additions & 1 deletion pyarlo/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from datetime import datetime
from datetime import timedelta
from pyarlo.const import LIBRARY_ENDPOINT, PRELOAD_DAYS
from pyarlo.utils import http_get, http_stream, pretty_timestamp
from pyarlo.utils import http_get, http_stream, to_datetime, pretty_timestamp

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -128,6 +128,18 @@ def created_at_pretty(self, date_format=None):
return pretty_timestamp(self.created_at, date_format=date_format)
return pretty_timestamp(self.created_at)

@property
def created_today(self):
"""Return True if created today."""
if self.datetime.date() == datetime.today().date():
return True
return False

@property
def datetime(self):
"""Return datetime when video was created."""
return to_datetime(self.created_at)

@property
def content_type(self):
"""Return content_type."""
Expand Down
7 changes: 7 additions & 0 deletions pyarlo/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# coding: utf-8
"""Implementation of Arlo utils."""
import time
from datetime import datetime as dt
import requests


def to_datetime(timestamp):
"""Return datetime object from timestamp."""
return dt.fromtimestamp(time.mktime(
time.localtime(int(str(timestamp)[:10]))))


def pretty_timestamp(timestamp, date_format='%a-%m_%d_%y:%H:%M:%S'):
"""Huminize timestamp."""
return time.strftime(date_format,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
setup(
name='pyarlo',
packages=['pyarlo'],
version='0.1.2',
version='0.1.3',
description='Python Arlo is a library written in Python 2.7/3x ' +
'that exposes the Netgear Arlo cameras as Python objects.',
author='Marcelo Moreira de Mello',
Expand Down
2 changes: 1 addition & 1 deletion tests/test_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_camera_properties(self, mock):
self.assertEqual(camera.hw_version, "H7")
self.assertEqual(camera.timezone, "America/New_York")
self.assertEqual(camera.user_role, "ADMIN")
self.assertTrue(len(camera.captured_today), 1)
self.assertEqual(len(camera.captured_today), 0)
self.assertIsNotNone(camera.properties)
self.assertEqual(camera.base_station, basestation)

Expand Down

0 comments on commit aad14b8

Please sign in to comment.