Skip to content
Merged

0.2.3 #122

Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ matrix:
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
- python: "3.7"
env: TOXENV=py37
dist: xenial
sudo: true
- python: "3.4.2"
env: TOXENV=lint
install: pip install -U tox coveralls
Expand Down
9 changes: 6 additions & 3 deletions ring_doorbell/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
_LOGGER = logging.getLogger(__name__)


# pylint: disable=useless-object-inheritance
class Ring(object):
"""A Python Abstraction object to Ring Door Bell."""

Expand Down Expand Up @@ -159,7 +160,8 @@ def query(self,
attempts=RETRY_TOKEN,
method='GET',
raw=False,
extra_params=None):
extra_params=None,
json=None):
"""Query data from Ring API."""
if self.debug:
_LOGGER.debug("Querying %s", url)
Expand Down Expand Up @@ -189,7 +191,8 @@ def query(self,
elif method == 'PUT':
req = self.session.put((url), params=urlencode(params))
elif method == 'POST':
req = self.session.post((url), params=urlencode(params))
req = self.session.post(
(url), params=urlencode(params), json=json)

if self.debug:
_LOGGER.debug("_query %s ret %s", loop, req.status_code)
Expand All @@ -213,7 +216,7 @@ def query(self,
response = req.json()
break

if self.debug:
if self.debug and response is None:
_LOGGER.debug("%s", MSG_GENERIC_FAIL)
return response

Expand Down
20 changes: 15 additions & 5 deletions ring_doorbell/chime.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ring_doorbell.const import (
API_URI, CHIMES_ENDPOINT, CHIME_VOL_MIN, CHIME_VOL_MAX,
LINKED_CHIMES_ENDPOINT, MSG_VOL_OUTBOUND, TESTSOUND_CHIME_ENDPOINT,
CHIME_TEST_SOUND_KINDS, KIND_DING)
CHIME_TEST_SOUND_KINDS, KIND_DING, CHIME_KINDS, CHIME_PRO_KINDS)

_LOGGER = logging.getLogger(__name__)

Expand All @@ -21,9 +21,19 @@ def family(self):
return 'chimes'

@property
def battery_life(self):
"""Return battery life."""
return int(self._health_attrs.get('battery_percentage'))
def model(self):
"""Return Ring device model name."""
if self.kind in CHIME_KINDS:
return 'Chime'
elif self.kind in CHIME_PRO_KINDS:
return 'Chime Pro'
return None

def has_capability(self, capability):
"""Return if device has specific capability."""
if capability == 'volume':
return True
return False

@property
def volume(self):
Expand All @@ -33,7 +43,7 @@ def volume(self):
@volume.setter
def volume(self, value):
if not ((isinstance(value, int)) and
(value >= CHIME_VOL_MIN and value <= CHIME_VOL_MAX)):
(CHIME_VOL_MIN <= value <= CHIME_VOL_MAX)):
_LOGGER.error("%s", MSG_VOL_OUTBOUND.format(CHIME_VOL_MIN,
CHIME_VOL_MAX))
return False
Expand Down
18 changes: 18 additions & 0 deletions ring_doorbell/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
NEW_SESSION_ENDPOINT = '/clients_api/session'
RINGTONES_ENDPOINT = '/ringtones'
SIREN_ENDPOINT = DOORBELLS_ENDPOINT + '/siren_{1}'
SNAPSHOT_ENDPOINT = "/clients_api/snapshots/image/{0}"
SNAPSHOT_TIMESTAMP_ENDPOINT = "/clients_api/snapshots/timestamps"
TESTSOUND_CHIME_ENDPOINT = CHIMES_ENDPOINT + '/play_sound'
URL_DOORBELL_HISTORY = DOORBELLS_ENDPOINT + '/history'
URL_RECORDING = '/clients_api/dings/{0}/recording'
Expand All @@ -67,6 +69,22 @@
SIREN_DURATION_MIN = 0
SIREN_DURATION_MAX = 120

# device model kinds
CHIME_KINDS = ['chime']
CHIME_PRO_KINDS = ['chime_pro']

DOORBELL_KINDS = ['doorbot', 'doorbell', 'doorbell_v3']
DOORBELL_2_KINDS = ['doorbell_v4', 'doorbell_v5']
DOORBELL_PRO_KINDS = ['lpd_v1', 'lpd_v2']
DOORBELL_ELITE_KINDS = ['jbox_v1']

FLOODLIGHT_CAM_KINDS = ['hp_cam_v1']
SPOTLIGHT_CAM_BATTERY_KINDS = ['stickup_cam_v4']
SPOTLIGHT_CAM_WIRED_KINDS = ['hp_cam_v2']
STICKUP_CAM_KINDS = ['stickup_cam', 'stickup_cam_v3']
STICKUP_CAM_BATTERY_KINDS = ['stickup_cam_lunar']
STICKUP_CAM_WIRED_KINDS = ['stickup_cam_elite']

# error strings
MSG_BOOLEAN_REQUIRED = "Boolean value is required."
MSG_EXISTING_TYPE = "Integer value where {0}.".format(DOORBELL_EXISTING_TYPE)
Expand Down
69 changes: 56 additions & 13 deletions ring_doorbell/doorbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging
from datetime import datetime
import os
import time
import pytz


Expand All @@ -12,9 +13,12 @@
from ring_doorbell.utils import _save_cache
from ring_doorbell.const import (
API_URI, DOORBELLS_ENDPOINT, DOORBELL_VOL_MIN, DOORBELL_VOL_MAX,
DOORBELL_EXISTING_TYPE, DINGS_ENDPOINT, FILE_EXISTS,
LIVE_STREAMING_ENDPOINT, MSG_BOOLEAN_REQUIRED, MSG_EXISTING_TYPE,
MSG_VOL_OUTBOUND, URL_DOORBELL_HISTORY, URL_RECORDING)
DOORBELL_EXISTING_TYPE, DINGS_ENDPOINT, DOORBELL_KINDS,
DOORBELL_2_KINDS, DOORBELL_PRO_KINDS, DOORBELL_ELITE_KINDS,
FILE_EXISTS, LIVE_STREAMING_ENDPOINT, MSG_BOOLEAN_REQUIRED,
MSG_EXISTING_TYPE, MSG_VOL_OUTBOUND, SNAPSHOT_ENDPOINT,
SNAPSHOT_TIMESTAMP_ENDPOINT, URL_DOORBELL_HISTORY,
URL_RECORDING)

_LOGGER = logging.getLogger(__name__)

Expand All @@ -27,12 +31,34 @@ def family(self):
"""Return Ring device family type."""
return 'doorbots'

@property
def model(self):
"""Return Ring device model name."""
if self.kind in DOORBELL_KINDS:
return 'Doorbell'
elif self.kind in DOORBELL_2_KINDS:
return 'Doorbell 2'
elif self.kind in DOORBELL_PRO_KINDS:
return 'Doorbell Pro'
elif self.kind in DOORBELL_ELITE_KINDS:
return 'Doorbell Elite'
return None

def has_capability(self, capability):
"""Return if device has specific capability."""
if capability == 'battery':
return self.kind in (DOORBELL_KINDS +
DOORBELL_2_KINDS)
elif capability == 'volume':
return True
return False

@property
def battery_life(self):
"""Return battery life."""
value = 0
if 'battery_life_2' in self._attrs:
# Camera has two battery bays
value = 0
if self._attrs.get('battery_life') is not None:
# Bay 1
value += int(self._attrs.get('battery_life'))
Expand All @@ -41,9 +67,11 @@ def battery_life(self):
value += int(self._attrs.get('battery_life_2'))
return value
# Camera has a single battery bay
value = int(self._attrs.get('battery_life'))
if value and value > 100:
value = 100
# Latest stickup cam can be externally powered
if self._attrs.get('battery_life') is not None:
value = int(self._attrs.get('battery_life'))
if value and value > 100:
value = 100
return value

def check_alerts(self):
Expand Down Expand Up @@ -150,7 +178,7 @@ def existing_doorbell_type_duration(self, value):
if self.existing_doorbell_type:

if not ((isinstance(value, int)) and
(value >= DOORBELL_VOL_MIN and value <= DOORBELL_VOL_MAX)):
(DOORBELL_VOL_MIN <= value <= DOORBELL_VOL_MAX)):
_LOGGER.error("%s", MSG_VOL_OUTBOUND.format(DOORBELL_VOL_MIN,
DOORBELL_VOL_MAX))
return False
Expand Down Expand Up @@ -259,8 +287,8 @@ def live_streaming_json(self):
def recording_download(self, recording_id, filename=None, override=False):
"""Save a recording in MP4 format to a file or return raw."""
if not self.has_subscription:
_LOGGER.warning("Your Ring account does not have" +
" an active subscription.")
msg = "Your Ring account does not have an active subscription."
_LOGGER.warning(msg)
return False

url = API_URI + URL_RECORDING.format(recording_id)
Expand All @@ -286,8 +314,8 @@ def recording_download(self, recording_id, filename=None, override=False):
def recording_url(self, recording_id):
"""Return HTTPS recording URL."""
if not self.has_subscription:
_LOGGER.warning("Your Ring account does not have" +
" an active subscription.")
msg = "Your Ring account does not have an active subscription."
_LOGGER.warning(msg)
return False

url = API_URI + URL_RECORDING.format(recording_id)
Expand Down Expand Up @@ -325,7 +353,7 @@ def volume(self):
@volume.setter
def volume(self, value):
if not ((isinstance(value, int)) and
(value >= DOORBELL_VOL_MIN and value <= DOORBELL_VOL_MAX)):
(DOORBELL_VOL_MIN <= value <= DOORBELL_VOL_MAX)):
_LOGGER.error("%s", MSG_VOL_OUTBOUND.format(DOORBELL_VOL_MIN,
DOORBELL_VOL_MAX))
return False
Expand All @@ -342,3 +370,18 @@ def volume(self, value):
def connection_status(self):
"""Return connection status."""
return self._attrs.get('alerts').get('connection')

def get_snapshot(self, retries=3, delay=1):
"""Take a snapshot and download it"""
url = API_URI + SNAPSHOT_TIMESTAMP_ENDPOINT
payload = {"doorbot_ids": [self._attrs.get('id')]}
self._ring.query(url, json=payload)
request_time = time.time()
for _ in range(retries):
time.sleep(delay)
response = self._ring.query(
url, method="POST", json=payload, raw=1).json()
if response["timestamps"][0]["timestamp"] / 1000 > request_time:
return self._ring.query(API_URI + SNAPSHOT_ENDPOINT.format(
self._attrs.get('id')), raw=True).content
return False
10 changes: 10 additions & 0 deletions ring_doorbell/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
_LOGGER = logging.getLogger(__name__)


# pylint: disable=useless-object-inheritance
class RingGeneric(object):
"""Generic Implementation for Ring Chime/Doorbell."""

Expand Down Expand Up @@ -39,6 +40,15 @@ def family(self):
"""Return Ring device family type."""
return None

@property
def model(self):
"""Return Ring device model name."""
return None

def has_capability(self, capability):
"""Return if device has specific capability."""
return False

def update(self):
"""Refresh attributes."""
self._get_attrs()
Expand Down
43 changes: 41 additions & 2 deletions ring_doorbell/stickup_cam.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from ring_doorbell import RingDoorBell
from ring_doorbell.const import (
API_URI, LIGHTS_ENDPOINT, MSG_ALLOWED_VALUES, MSG_VOL_OUTBOUND,
FLOODLIGHT_CAM_KINDS, SPOTLIGHT_CAM_BATTERY_KINDS,
SPOTLIGHT_CAM_WIRED_KINDS, STICKUP_CAM_KINDS,
STICKUP_CAM_BATTERY_KINDS, STICKUP_CAM_WIRED_KINDS,
SIREN_DURATION_MIN, SIREN_DURATION_MAX, SIREN_ENDPOINT)

_LOGGER = logging.getLogger(__name__)
Expand All @@ -19,6 +22,43 @@ def family(self):
"""Return Ring device family type."""
return 'stickup_cams'

@property
def model(self):
"""Return Ring device model name."""
if self.kind in FLOODLIGHT_CAM_KINDS:
return 'Floodlight Cam'
elif self.kind in SPOTLIGHT_CAM_BATTERY_KINDS:
return 'Spotlight Cam {}'.format(
self._attrs.get('ring_cam_setup_flow', 'battery').title())
elif self.kind in SPOTLIGHT_CAM_WIRED_KINDS:
return 'Spotlight Cam {}'.format(
self._attrs.get('ring_cam_setup_flow', 'wired').title())
elif self.kind in STICKUP_CAM_KINDS:
return 'Stick Up Cam'
elif self.kind in STICKUP_CAM_BATTERY_KINDS:
return 'Stick Up Cam Battery'
elif self.kind in STICKUP_CAM_WIRED_KINDS:
return 'Stick Up Cam Wired'
return None

def has_capability(self, capability):
"""Return if device has specific capability."""
if capability == 'battery':
return self.kind in (SPOTLIGHT_CAM_BATTERY_KINDS +
STICKUP_CAM_KINDS +
STICKUP_CAM_BATTERY_KINDS)
elif capability == 'light':
return self.kind in (FLOODLIGHT_CAM_KINDS +
SPOTLIGHT_CAM_BATTERY_KINDS +
SPOTLIGHT_CAM_WIRED_KINDS)
elif capability == 'siren':
return self.kind in (FLOODLIGHT_CAM_KINDS +
SPOTLIGHT_CAM_BATTERY_KINDS +
SPOTLIGHT_CAM_WIRED_KINDS +
STICKUP_CAM_BATTERY_KINDS +
STICKUP_CAM_WIRED_KINDS)
return False

@property
def lights(self):
"""Return lights status."""
Expand Down Expand Up @@ -48,8 +88,7 @@ def siren(self):
def siren(self, duration):
"""Control the siren."""
if not ((isinstance(duration, int)) and
(duration >= SIREN_DURATION_MIN and
duration <= SIREN_DURATION_MAX)):
(SIREN_DURATION_MIN <= duration <= SIREN_DURATION_MAX)):
_LOGGER.error("%s", MSG_VOL_OUTBOUND.format(SIREN_DURATION_MIN,
SIREN_DURATION_MAX))
return False
Expand Down
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"""Python Ring Door Bell setup script."""
from setuptools import setup

_VERSION = '0.2.3'


def readme():
with open('README.rst') as desc:
Expand All @@ -11,7 +13,7 @@ def readme():
setup(
name='ring_doorbell',
packages=['ring_doorbell'],
version='0.2.2',
version=_VERSION,
description='A Python library to communicate with Ring' +
' Door Bell (https://ring.com/)',
long_description=readme(),
Expand All @@ -37,6 +39,7 @@ def readme():
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Home Automation',
'Topic :: Software Development :: Libraries :: Python Modules'
],
Expand Down
Loading