diff --git a/README.rst b/README.rst
index 9c56e16..72d71aa 100644
--- a/README.rst
+++ b/README.rst
@@ -16,6 +16,9 @@ Python Arlo
Python Arlo is a library written in Python 2.7/3x that exposes the Netgear Arlo cameras as Python objects.
+Developer Documentation: `http://python-arlo.readthedocs.io/ `_
+
+
Installation
------------
@@ -41,24 +44,80 @@ Usage
# listing base stations
arlo.base_stations
+ # get base station handle
+ # assuming only 1 base station is available
+ base = arlo.base_stations[0]
+
# listing Arlo modes
base.available_modes
['armed', 'disarmed', 'schedule', 'custom']
- # setting a mode
- garage_cam.mode = 'armed'
-
# listing all cameras
arlo.cameras
# showing camera preferences
cam = arlo.cameras[0]
+ # check if camera is connected to base station
+ cam.is_camera_connected
+ True
+
+ # setting a mode
+ cam.mode = 'armed'
+
+ # getting the current active mode
+ cam.mode
+ 'armed'
+
# printing camera attributes
cam.serial_number
cam.model_id
cam.unseen_videos
+ # get brightness value of camera
+ cam.get_brightness
+
+ # get signal strength of camera with base station
+ cam.get_signal_strength
+
+ # get flip property from camera
+ cam.get_flip_state
+
+ # get mirror property from camera
+ cam.get_mirror_state
+
+ # get power save mode value from camera
+ cam.get_powersave_mode
+
+ # get current battery level of camera
+ cam.get_battery_level
+ 92
+
+ # get boolean result if motion detection
+ # is enabled or not
+ cam.is_motion_detection_enabled
+ True
+
+ # get battery levels of all cameras
+ # prints serial number and battery level of each camera
+ base.get_camera_battery_level
+ {'4N71235T12345': 92, '4N71235T12345': 90}
+
+ # get base station properties
+ base.get_basestation_properties
+
+ # get camera properties
+ base.get_camera_properties
+
+ # get camera rules
+ base.get_camera_rules
+
+ # get camera schedule
+ base.get_camera_schedule
+
+ # get camera motion detection sensitivity
+ cam.get_motion_detection_sensitivity
+
# refreshing camera properties
cam.update()
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..f2f0ccf
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = python -msphinx
+SPHINXPROJ = PyArlo
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..c8c5cd4
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# PyArlo documentation build configuration file, created by
+# sphinx-quickstart on Tue Jun 27 00:04:52 2017.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = ['sphinx.ext.autodoc',
+ 'sphinx.ext.doctest',
+ 'sphinx.ext.coverage',
+ 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'PyArlo'
+copyright = '2017, Marcelo Moreira de Mello '
+author = 'Marcelo Moreira de Mello '
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = ''
+# The full version, including alpha/beta/rc tags.
+release = ''
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'PyArlodoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'PyArlo.tex', 'PyArlo Documentation',
+ 'Marcelo Moreira de Mello \\textless{}tchello.mello@gmail.com\\textgreater{}', 'manual'),
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'pyarlo', 'PyArlo Documentation',
+ [author], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'PyArlo', 'PyArlo Documentation',
+ author, 'PyArlo', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..1a9a9fe
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,63 @@
+.. PyArlo documentation master file, created by
+ sphinx-quickstart on Tue Jun 27 00:04:52 2017.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to PyArlo's documentation!
+==================================
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+Python Arlo is a library written in Python 2.7/3x that exposes the Netgear Arlo cameras as Python objects.
+
+Documentation for developers.
+
+
+PyArlo
+------
+.. autoclass:: pyarlo.PyArlo
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+ArloBaseStation
+---------------
+.. autoclass:: pyarlo.base_station.ArloBaseStation
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+ArloCamera
+----------
+.. autoclass:: pyarlo.camera.ArloCamera
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+ArloMediaLibrary
+----------------
+.. autoclass:: pyarlo.media.ArloMediaLibrary
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+ArloVideo
+---------
+.. autoclass:: pyarlo.media.ArloVideo
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..36f959c
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,36 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=python -msphinx
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+set SPHINXPROJ=PyArlo
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The Sphinx module was not found. Make sure you have Sphinx installed,
+ echo.then set the SPHINXBUILD environment variable to point to the full
+ echo.path of the 'sphinx-build' executable. Alternatively you may add the
+ echo.Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd
diff --git a/pyarlo/__init__.py b/pyarlo/__init__.py
index fd80d46..0cee828 100644
--- a/pyarlo/__init__.py
+++ b/pyarlo/__init__.py
@@ -7,9 +7,9 @@
from pyarlo.camera import ArloCamera
from pyarlo.media import ArloMediaLibrary
from pyarlo.const import (
- BILLING_ENDPOINT, DEVICES_ENDPOINT, FRIENDS_ENDPOINT,
- LOGIN_ENDPOINT, PROFILE_ENDPOINT, PRELOAD_DAYS,
- RESET_ENDPOINT)
+ BILLING_ENDPOINT, DEVICES_ENDPOINT,
+ FRIENDS_ENDPOINT, LOGIN_ENDPOINT, PROFILE_ENDPOINT,
+ PRELOAD_DAYS, RESET_ENDPOINT)
_LOGGER = logging.getLogger(__name__)
@@ -40,6 +40,7 @@ def __init__(self, username=None, password=None,
self.__password = password
self.__username = username
self.session = requests.Session()
+ self.__base_stations = []
# login user
self.login()
@@ -90,7 +91,8 @@ def query(self,
extra_params=None,
extra_headers=None,
retry=3,
- raw=False):
+ raw=False,
+ stream=False):
"""
Return a JSON object or raw session.
@@ -100,6 +102,7 @@ def query(self,
:param extra_headers: Dictionary to be apppended on request.headers
:param retry: Attempts to retry a query. Default is 3.
:param raw: Boolean if query() will return request object instead JSON.
+ :param stream: Boolean if query() will return a stream object.
"""
response = None
loop = 0
@@ -129,7 +132,7 @@ def query(self,
# define connection method
if method == 'GET':
- req = self.session.get(url, headers=headers)
+ req = self.session.get(url, headers=headers, stream=stream)
elif method == 'PUT':
req = self.session.put(url, json=params, headers=headers)
elif method == 'POST':
@@ -145,6 +148,7 @@ def query(self,
# leave if everything worked fine
break
+
return response
@property
@@ -177,9 +181,10 @@ def devices(self):
if device.get('deviceType') == 'basestation' and \
device.get('state') == 'provisioned':
- devices['base_station'].append(ArloBaseStation(name,
- device,
- self))
+ base = ArloBaseStation(name, device, self.__token, self)
+ devices['base_station'].append(base)
+ self.__base_stations.append(base)
+
return devices
def lookup_camera_by_id(self, device_id):
@@ -222,7 +227,7 @@ def profile(self):
@property
def is_connected(self):
- """Return connection status."""
+ """Connection status of client with Arlo system."""
return bool(self.authenticated)
def update(self):
diff --git a/pyarlo/base_station.py b/pyarlo/base_station.py
index 94f91ae..f8ea1ea 100644
--- a/pyarlo/base_station.py
+++ b/pyarlo/base_station.py
@@ -1,29 +1,173 @@
# coding: utf-8
"""Generic Python Class file for Netgear Arlo Base Station module."""
+import json
+import threading
+import time
import logging
-from pyarlo.const import ACTION_MODES, NOTIFY_ENDPOINT, RUN_ACTION_BODY
-
+import sseclient
+from pyarlo.const import (
+ ACTION_BODY, SUBSCRIBE_ENDPOINT, UNSUBSCRIBE_ENDPOINT,
+ ACTION_MODES, NOTIFY_ENDPOINT, RESOURCES)
_LOGGER = logging.getLogger(__name__)
class ArloBaseStation(object):
"""Arlo Base Station module implementation."""
- def __init__(self, name, attrs, arlo_session):
+ def __init__(self, name, attrs, session_token, arlo_session):
"""Initialize Arlo Base Station object.
:param name: Base Station name
:param attrs: Attributes
+ :param session_token: Session token passed by camera class
:param arlo_session: PyArlo shared session
"""
self.name = name
self._attrs = attrs
self._session = arlo_session
+ self._session_token = session_token
+ self.__sseclient = None
+ self.__subscribed = False
+ self.__events = []
def __repr__(self):
"""Representation string of object."""
return "<{0}: {1}>".format(self.__class__.__name__, self.name)
+ def thread_function(self):
+ """Thread function."""
+ url = SUBSCRIBE_ENDPOINT + "?token=" + self._session_token
+ data = self._session.query(url, method='GET', raw=True, stream=True)
+ self.__sseclient = sseclient.SSEClient(data)
+ if self.__sseclient:
+ self.__subscribed = True
+ 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
+ break
+ elif action == "is" and "subscriptions/" not in resource:
+ self.__events.append(data)
+
+ def _get_event_stream(self):
+ """Spawn a thread and monitor the Arlo Event Stream."""
+ event_thread = threading.Thread(target=self.thread_function)
+ event_thread.start()
+
+ def _subscribe_myself(self):
+ """Subscribe this base station for all events."""
+ return self.__run_action(
+ method='SET',
+ resource='subscribe',
+ mode=None,
+ publish_response=False)
+
+ def _unsubscribe_myself(self):
+ """Unsubscribe this base station for all events."""
+ url = UNSUBSCRIBE_ENDPOINT
+ return self._session.query(url, method='GET', raw=True, stream=False)
+
+ def _close_event_stream(self):
+ """Stop the Event stream thread."""
+ self.__subscribed = False
+
+ def publish_and_get_event(self, resource):
+ """Publish and get the event from base station."""
+ self._get_event_stream()
+ self._subscribe_myself()
+
+ this_event = ''
+ status = self.__run_action(
+ method='GET',
+ resource=resource,
+ mode=None,
+ publish_response=False)
+ if status == 'success':
+ for i in range(0, 5):
+ _LOGGER.debug("Trying instance " + str(i))
+ time.sleep(1)
+ for event in self.__events:
+ if event['resource'] == resource:
+ this_event = event
+ self.__events.remove(event)
+ break
+ if this_event:
+ break
+
+ self._unsubscribe_myself()
+ self._close_event_stream()
+
+ if this_event:
+ return this_event
+
+ return None
+
+ def __run_action(
+ self,
+ method='GET',
+ resource=None,
+ mode=None,
+ publish_response=None):
+ """Run action.
+
+ :param method: Specify the method GET, POST or PUT. Default is GET.
+ :param resource: Specify one of the resources to fetch from arlo.
+ :param mode: Specify the mode to set, else None for GET operations
+ :param publish_response: Set to True for SETs. Default False
+ """
+ url = NOTIFY_ENDPOINT.format(self.device_id)
+
+ body = ACTION_BODY
+
+ if resource:
+ body['resource'] = resource
+
+ if method == 'GET':
+ body['action'] = "get"
+ body['properties'] = None
+ elif method == 'SET':
+ body['action'] = "set"
+ if resource == 'schedule':
+ body['properties'] = {'active': 'true'}
+ elif resource == 'subscribe':
+ body['resource'] = "subscriptions/" + \
+ "{0}_web".format(self.user_id)
+ dev = []
+ dev.append(self.device_id)
+ body['properties'] = {'devices': dev}
+ elif resource == 'modes':
+ body['properties'] = {'active': ACTION_MODES.get(mode)}
+ else:
+ _LOGGER.info("Invalid method requested")
+ return None
+
+ if not publish_response:
+ body['publishResponse'] = 'false'
+ else:
+ body['publishResponse'] = 'true'
+
+ body['from'] = "{0}_web".format(self.user_id)
+ body['to'] = self.device_id
+ body['transId'] = "web!e6d1b969.8aa4b!1498165992111"
+
+ _LOGGER.info("Action body: %s", body)
+
+ ret = \
+ self._session.query(url, method='POST', extra_params=body,
+ extra_headers={"xCloudId": self.xcloud_id})
+ if ret.get('success'):
+ return 'success'
+
+ return None
+
# pylint: disable=invalid-name
@property
def device_id(self):
@@ -75,36 +219,101 @@ def xcloud_id(self):
"""Return X-Cloud-ID attribute."""
return self._attrs.get('xCloudId')
- def __run_action(self, action):
- """Run action."""
- url = NOTIFY_ENDPOINT.format(self.device_id)
- body = RUN_ACTION_BODY
- body['from'] = "{0}_web".format(self.user_id)
- body['to'] = self.device_id
- body['properties'] = {'active': ACTION_MODES.get(action)}
-
- # if action is schedule, modify resource and properties
- if action == 'schedule':
- body['resource'] = 'schedule'
- body['properties'] = {'active': 'true'}
-
- _LOGGER.debug("Action body: %s", body)
-
- ret = \
- self._session.query(url, method='POST', extra_params=body,
- extra_headers={"xCloudId": self.xcloud_id})
- return ret.get('success')
-
@property
def available_modes(self):
"""Return list of available modes."""
return list(ACTION_MODES.keys())
+ @property
+ def available_resources(self):
+ """Return list of available resources."""
+ return list(RESOURCES.keys())
+
@property
def mode(self):
"""Return current mode."""
+ resource = "modes"
+ mode_event = self.publish_and_get_event(resource)
+ if mode_event:
+ active_mode = mode_event['properties']['active']
+ modes = mode_event['properties']['modes']
+ for mode in modes:
+ if mode['id'] == active_mode:
+ return mode['type']
+
+ return None
+
+ @property
+ def get_camera_properties(self):
+ """Return camera properties."""
+ resource = "cameras"
+ resource_event = self.publish_and_get_event(resource)
+ if resource_event:
+ return resource_event['properties']
+
+ return None
+
+ @property
+ def get_camera_battery_level(self):
+ """Return a list of battery levels of all cameras."""
+ battery_levels = {}
+ resource = "cameras"
+ resource_event = self.publish_and_get_event(resource)
+ if resource_event:
+ cameras = resource_event['properties']
+ for camera in cameras:
+ battery_levels[camera['serialNumber']] = camera['batteryLevel']
+ return battery_levels
+
+ return None
+
+ @property
+ def get_basestation_properties(self):
+ """Return the base station info."""
+ resource = "basestation"
+ basestn_event = self.publish_and_get_event(resource)
+ if basestn_event:
+ return basestn_event['properties']
+
return None
+ @property
+ def get_camera_rules(self):
+ """Return the camera rules."""
+ resource = "rules"
+ rules_event = self.publish_and_get_event(resource)
+ if rules_event:
+ return rules_event['properties']
+
+ return None
+
+ @property
+ def get_camera_schedule(self):
+ """Return the schedule set for cameras."""
+ resource = "schedule"
+ schedule_event = self.publish_and_get_event(resource)
+ if schedule_event:
+ return schedule_event['properties']
+
+ return None
+
+ @property
+ def is_motion_detection_enabled(self):
+ """Return Boolean if motion is enabled."""
+ return bool(self.mode == "armed" or self.get_camera_schedule['active'])
+
+ @property
+ def subscribe(self):
+ """Subscribe this session with Arlo system."""
+ self._get_event_stream()
+ self._subscribe_myself()
+
+ @property
+ def unsubscribe(self):
+ """Unsubscribe this session."""
+ self._unsubscribe_myself()
+ self._close_event_stream()
+
@mode.setter
def mode(self, mode):
"""Set Arlo camera mode.
@@ -113,7 +322,12 @@ def mode(self, mode):
"""
if mode not in ACTION_MODES.keys():
return "Invalid mode"
- return self.__run_action(mode)
+ self.__run_action(
+ method='SET',
+ resource='modes',
+ mode=mode,
+ publish_response=True)
+ self.update()
def update(self):
"""Update object properties."""
diff --git a/pyarlo/camera.py b/pyarlo/camera.py
index 1bbe03f..cc5b93e 100644
--- a/pyarlo/camera.py
+++ b/pyarlo/camera.py
@@ -125,6 +125,103 @@ def xcloud_id(self):
"""Return X-Cloud-ID attribute."""
return self._attrs.get('xCloudId')
+ @property
+ def get_battery_level(self):
+ """Get the camera battery level."""
+ base = self._session.base_stations[0]
+ return base.get_camera_battery_level[self.device_id]
+
+ @property
+ def get_signal_strength(self):
+ """Get the camera Signal strength."""
+ base = self._session.base_stations[0]
+ props = base.get_camera_properties
+ if not props:
+ return None
+ for cam in props:
+ if cam['serialNumber'] == self.device_id:
+ return cam['signalStrength']
+
+ return None
+
+ @property
+ def get_brightness(self):
+ """Get the brightness property of camera."""
+ base = self._session.base_stations[0]
+ props = base.get_camera_properties
+ if not props:
+ return None
+ for cam in props:
+ if cam['serialNumber'] == self.device_id:
+ return cam['brightness']
+
+ return None
+
+ @property
+ def get_mirror_state(self):
+ """Get the brightness property of camera."""
+ base = self._session.base_stations[0]
+ props = base.get_camera_properties
+ if not props:
+ return None
+ for cam in props:
+ if cam['serialNumber'] == self.device_id:
+ return cam['flip']
+
+ return None
+
+ @property
+ def get_flip_state(self):
+ """Get the brightness property of camera."""
+ base = self._session.base_stations[0]
+ props = base.get_camera_properties
+ if not props:
+ return None
+ for cam in props:
+ if cam['serialNumber'] == self.device_id:
+ return cam['mirror']
+
+ return None
+
+ @property
+ def get_powersave_mode(self):
+ """Get the brightness property of camera."""
+ base = self._session.base_stations[0]
+ props = base.get_camera_properties
+ if not props:
+ return None
+ for cam in props:
+ if cam['serialNumber'] == self.device_id:
+ return cam['powerSaveMode']
+
+ return None
+
+ @property
+ def is_camera_connected(self):
+ """Connectivity status of Cam with Base Station."""
+ base = self._session.base_stations[0]
+ props = base.get_camera_properties
+ if not props:
+ return None
+ for cam in props:
+ if cam['serialNumber'] == self.device_id:
+ return bool(cam['connectionState'] == 'available')
+
+ return None
+
+ @property
+ def get_motion_detection_sensitivity(self):
+ """Sensitivity level of Camera motion detection."""
+ base = self._session.base_stations[0]
+ props = base.get_camera_properties
+ if not props:
+ return None
+ for cam in props:
+ if cam['serialNumber'] == self.device_id:
+ this_cam = cam
+ triggers = this_cam['capabilities'][9]['Triggers']
+ return triggers[0]['sensitivity']['default']
+
def live_streaming(self):
"""Return live streaming generator."""
url = STREAM_ENDPOINT
diff --git a/pyarlo/const.py b/pyarlo/const.py
index 6669873..0358665 100644
--- a/pyarlo/const.py
+++ b/pyarlo/const.py
@@ -3,11 +3,14 @@
# API Endpoints
API_URL = "https://arlo.netgear.com/hmsweb"
-BILLING_ENDPOINT = API_URL + "/users/serviceLevel"
+DEVICE_SUPPORT_ENDPOINT = API_URL + "/devicesupport/v2"
+SUBSCRIBE_ENDPOINT = API_URL + "/client/subscribe"
+UNSUBSCRIBE_ENDPOINT = API_URL + "/client/unsubscribe"
+BILLING_ENDPOINT = API_URL + "/users/serviceLevel/v2"
DEVICES_ENDPOINT = API_URL + "/users/devices"
FRIENDS_ENDPOINT = API_URL + "/users/friends"
LIBRARY_ENDPOINT = API_URL + "/users/library"
-LOGIN_ENDPOINT = API_URL + "/login"
+LOGIN_ENDPOINT = API_URL + "/login/v2"
LOGOUT_ENDPOINT = API_URL + "/logout"
NOTIFY_ENDPOINT = API_URL + "/users/devices/notify/{0}"
PROFILE_ENDPOINT = API_URL + "/users/profile"
@@ -26,13 +29,22 @@
'schedule': 'true',
}
+# define resources
+RESOURCES = {
+ 'base_station': 'base_station',
+ 'modes': 'modes',
+ 'schedule': 'schedule',
+ 'rules': 'rules',
+ 'cameras': 'cameras',
+}
+
# define body used when executing an action
-RUN_ACTION_BODY = {
- 'action': 'set',
+ACTION_BODY = {
+ 'action': None,
'from': None,
'properties': None,
- 'publishResponse': 'true',
- 'resource': 'modes',
+ 'publishResponse': None,
+ 'resource': None,
'to': None
}
diff --git a/requirements.txt b/requirements.txt
index f229360..a944a1c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
requests
+sseclient-py
diff --git a/requirements_docs.txt b/requirements_docs.txt
new file mode 100644
index 0000000..6966869
--- /dev/null
+++ b/requirements_docs.txt
@@ -0,0 +1 @@
+sphinx
diff --git a/setup.cfg b/setup.cfg
index 4382942..d03345c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,4 +3,4 @@ description-file = README.rst
[tool:pytest]
testpaths = tests
-norecursedirs = .git
+norecursedirs = .git, docs
diff --git a/setup.py b/setup.py
index 64d95dd..7201e39 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
setup(
name='pyarlo',
packages=['pyarlo'],
- version='0.0.4',
+ version='0.0.5',
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',
@@ -13,7 +13,7 @@
url='https://github.com/tchellomello/python-arlo',
license='LGPLv3+',
include_package_data=True,
- install_requires=['requests'],
+ install_requires=['requests', 'sseclient-py'],
test_suite='tests',
keywords=[
'arlo',
@@ -30,6 +30,7 @@
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Software Development :: Libraries :: Python Modules'
diff --git a/tox.ini b/tox.ini
index 0847ccb..bcc1913 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py27, py35, py36, lint
+envlist = py27, py34, py35, py36, lint
skip_missing_interpreters = True
[testenv]
@@ -19,3 +19,6 @@ ignore_errors = True
commands =
flake8
pylint pyarlo
+
+[flake8]
+exclude = docs,.tox,*.egg,*.pyc,.git,__pycache