From bfdc1e47bdd84903f1aca653605846f3c99bcfac Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 1 Mar 2019 22:00:37 -0500 Subject: [PATCH 01/22] Dev version bump --- blinkpy/helpers/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index afbe867..f880239 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -3,8 +3,8 @@ import os MAJOR_VERSION = 0 -MINOR_VERSION = 13 -PATCH_VERSION = 1 +MINOR_VERSION = 14 +PATCH_VERSION = '0.dev0' __version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION) From 4db7a33ef3cf8b51a3dee177eb249d66767e43d8 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 18 May 2019 10:48:58 -0400 Subject: [PATCH 02/22] Added changed video download endpoint --- blinkpy/api.py | 4 ++-- blinkpy/blinkpy.py | 35 +++++++++++++++++++++-------------- tests/test_blink_functions.py | 12 ++++++------ 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/blinkpy/api.py b/blinkpy/api.py index 6e41738..77cac8f 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -168,8 +168,8 @@ def request_videos(blink, time=None, page=0): :param page: Page number to get videos from. """ timestamp = get_time(time) - url = "{}/api/v2/videos/changed?since={}&page={}".format( - blink.urls.base_url, timestamp, page) + url = "{}/api/v1/accounts/{}/media/changed?since={}&page={}".format( + blink.urls.base_url, blink.account_id, timestamp, page) return http_get(blink, url) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 9a9b2ae..6518c95 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -247,7 +247,8 @@ class Blink(): combined = merge_dicts(combined, self.sync[sync].cameras) return combined - def download_videos(self, path, since=None, camera='all', stop=10): + def download_videos(self, path, since=None, + camera='all', stop=10, debug=False): """ Download all videos from server since specified time. @@ -258,6 +259,8 @@ class Blink(): :param camera: Camera name to retrieve. Defaults to "all". Use a list for multiple cameras. :param stop: Page to stop on (~25 items per page. Default page 10). + :param debug: Set to TRUE to prevent downloading of items. + Instead of downloading, entries will be printed to log. """ if since is None: since_epochs = self.last_refresh @@ -275,23 +278,23 @@ class Blink(): response = api.request_videos(self, time=since_epochs, page=page) _LOGGER.debug("Processing page %s", page) try: - result = response['videos'] + result = response['media'] if not result: raise IndexError except (KeyError, IndexError): _LOGGER.info("No videos found on page %s. Exiting.", page) break - self._parse_downloaded_items(result, camera, path) + self._parse_downloaded_items(result, camera, path, debug) - def _parse_downloaded_items(self, result, camera, path): + def _parse_downloaded_items(self, result, camera, path, debug): """Parse downloaded videos.""" for item in result: try: created_at = item['created_at'] - camera_name = item['camera_name'] + camera_name = item['device_name'] is_deleted = item['deleted'] - address = item['address'] + address = item['media'] except KeyError: _LOGGER.info("Missing clip information, skipping...") continue @@ -310,13 +313,17 @@ class Blink(): filename = "{}_{}.mp4".format(camera_name, created_at) filename = os.path.join(path, filename) - if os.path.isfile(filename): - _LOGGER.info("%s already exists, skipping...", filename) - continue + if not debug: + if os.path.isfile(filename): + _LOGGER.info("%s already exists, skipping...", filename) + continue - response = api.http_get(self, url=clip_address, - stream=True, json=False) - with open(filename, 'wb') as vidfile: - copyfileobj(response.raw, vidfile) + response = api.http_get(self, url=clip_address, + stream=True, json=False) + with open(filename, 'wb') as vidfile: + copyfileobj(response.raw, vidfile) - _LOGGER.info("Downloaded video to %s", filename) + _LOGGER.info("Downloaded video to %s", filename) + else: + _LOGGER.info("Camera: %s, Timestamp: %s, Address: %s", + camera_name, created_at, address) diff --git a/tests/test_blink_functions.py b/tests/test_blink_functions.py index 3607a73..3e79195 100644 --- a/tests/test_blink_functions.py +++ b/tests/test_blink_functions.py @@ -121,12 +121,12 @@ class TestBlinkFunctions(unittest.TestCase): blinkpy._LOGGER.setLevel(logging.DEBUG) generic_entry = { 'created_at': '1970', - 'camera_name': 'foo', + 'device_name': 'foo', 'deleted': True, - 'address': '/bar.mp4' + 'media': '/bar.mp4' } result = [generic_entry] - mock_req.return_value = {'videos': result} + mock_req.return_value = {'media': result} blink.last_refresh = 0 formatted_date = get_time(blink.last_refresh) expected_log = [ @@ -147,12 +147,12 @@ class TestBlinkFunctions(unittest.TestCase): blinkpy._LOGGER.setLevel(logging.DEBUG) generic_entry = { 'created_at': '1970', - 'camera_name': 'foo', + 'device_name': 'foo', 'deleted': True, - 'address': '/bar.mp4' + 'media': '/bar.mp4' } result = [generic_entry] - mock_req.return_value = {'videos': result} + mock_req.return_value = {'media': result} blink.last_refresh = 0 formatted_date = get_time(blink.last_refresh) expected_log = [ From 43c11626341531a635eacdb4cc61aa139e70f0e8 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 18 May 2019 10:54:24 -0400 Subject: [PATCH 03/22] Added vieo endpoint key changes to motion detect logic --- blinkpy/sync_module.py | 8 ++++---- tests/test_sync_module.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index 51d4607..97dfdf0 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -164,21 +164,21 @@ class BlinkSyncModule(): """Check if new videos since last refresh.""" resp = api.request_videos(self.blink, time=self.blink.last_refresh, - page=0) + page=1) for camera in self.cameras.keys(): self.motion[camera] = False try: - info = resp['videos'] + info = resp['media'] except (KeyError, TypeError): _LOGGER.warning("Could not check for motion. Response: %s", resp) return False for entry in info: try: - name = entry['camera_name'] - clip = entry['address'] + name = entry['device_name'] + clip = entry['media'] timestamp = entry['created_at'] self.motion[name] = True self.last_record[name] = {'clip': clip, 'time': timestamp} diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index ae7ad8c..7962a07 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -62,9 +62,9 @@ class TestBlinkSyncModule(unittest.TestCase): def test_check_new_videos(self, mock_resp): """Test recent video response.""" mock_resp.return_value = { - 'videos': [{ - 'camera_name': 'foo', - 'address': '/foo/bar.mp4', + 'media': [{ + 'device_name': 'foo', + 'media': '/foo/bar.mp4', 'created_at': '1970-01-01T00:00:00+0:00' }] } @@ -76,7 +76,7 @@ class TestBlinkSyncModule(unittest.TestCase): {'clip': '/foo/bar.mp4', 'time': '1970-01-01T00:00:00+0:00'}) self.assertEqual(sync_module.motion, {'foo': True}) - mock_resp.return_value = {'videos': []} + mock_resp.return_value = {'media': []} self.assertTrue(sync_module.check_new_videos()) self.assertEqual(sync_module.motion, {'foo': False}) self.assertEqual(sync_module.last_record['foo'], From 102190e61b58ec964c7562914f8949165e47cff5 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 18 May 2019 11:41:17 -0400 Subject: [PATCH 04/22] Changed log to print --- blinkpy/blinkpy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 6518c95..d574d1d 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -325,5 +325,5 @@ class Blink(): _LOGGER.info("Downloaded video to %s", filename) else: - _LOGGER.info("Camera: %s, Timestamp: %s, Address: %s", - camera_name, created_at, address) + print("Camera: {}, Timestamp: {}, Address: {}".format( + camera_name, created_at, address)) From dcf0ce639438d347fe2bb4025955a97c388c2241 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 18 May 2019 11:51:58 -0400 Subject: [PATCH 05/22] Fix lint issue --- blinkpy/blinkpy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index d574d1d..0e4569f 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -326,4 +326,4 @@ class Blink(): _LOGGER.info("Downloaded video to %s", filename) else: print("Camera: {}, Timestamp: {}, Address: {}".format( - camera_name, created_at, address)) + camera_name, created_at, address)) From 37b729b597cb3a46e6cb5308d5b5b585f828282d Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 18 May 2019 12:15:57 -0400 Subject: [PATCH 06/22] Remove throttling from critical api methods --- blinkpy/api.py | 3 --- blinkpy/sync_module.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/blinkpy/api.py b/blinkpy/api.py index 77cac8f..e4fc2fd 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -40,7 +40,6 @@ def request_networks(blink): return http_get(blink, url) -@Throttle(seconds=MIN_THROTTLE_TIME) def request_network_status(blink, network): """ Request network information. @@ -52,7 +51,6 @@ def request_network_status(blink, network): return http_get(blink, url) -@Throttle(seconds=MIN_THROTTLE_TIME) def request_syncmodule(blink, network): """ Request sync module info. @@ -173,7 +171,6 @@ def request_videos(blink, time=None, page=0): return http_get(blink, url) -@Throttle(seconds=MIN_THROTTLE_TIME) def request_cameras(blink, network): """ Request all camera information. diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index 97dfdf0..337b56c 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -80,8 +80,7 @@ class BlinkSyncModule(): def start(self): """Initialize the system.""" response = api.request_syncmodule(self.blink, - self.network_id, - force=True) + self.network_id) try: self.summary = response['syncmodule'] self.network_id = self.summary['network_id'] From 92d89b9fe5e90f34bfeeea6717d4917b22565602 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 18 May 2019 12:42:01 -0400 Subject: [PATCH 07/22] Change battery percentage to state --- blinkpy/camera.py | 5 +++-- tests/test_cameras.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/blinkpy/camera.py b/blinkpy/camera.py index 7b05b93..8b904f5 100644 --- a/blinkpy/camera.py +++ b/blinkpy/camera.py @@ -41,6 +41,7 @@ class BlinkCamera(): 'temperature_c': self.temperature_c, 'temperature_calibrated': self.temperature_calibrated, 'battery': self.battery, + 'battery_voltage': self.battery_voltage, 'thumbnail': self.thumbnail, 'video': self.clip, 'motion_enabled': self.motion_enabled, @@ -54,8 +55,8 @@ class BlinkCamera(): @property def battery(self): - """Return battery level as percentage.""" - return round(self.battery_voltage / 180 * 100) + """Return battery as string.""" + return self.battery_state @property def temperature_c(self): diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 866a2a0..17f6f64 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -87,7 +87,7 @@ class TestBlinkCameraSetup(unittest.TestCase): self.assertEqual(self.camera.network_id, '5678') self.assertEqual(self.camera.serial, '12345678') self.assertEqual(self.camera.motion_enabled, False) - self.assertEqual(self.camera.battery, 50) + self.assertEqual(self.camera.battery, 'ok') self.assertEqual(self.camera.temperature, 68) self.assertEqual(self.camera.temperature_c, 20) self.assertEqual(self.camera.temperature_calibrated, 71) From 130aa8382add043d96f013cb0e6a8950a7bd20c9 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 18 May 2019 18:23:46 -0400 Subject: [PATCH 08/22] Change subdomain from rest.region to rest-region --- blinkpy/helpers/util.py | 2 +- tests/test_cameras.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/blinkpy/helpers/util.py b/blinkpy/helpers/util.py index e2e44a4..2d0e1c8 100644 --- a/blinkpy/helpers/util.py +++ b/blinkpy/helpers/util.py @@ -113,7 +113,7 @@ class BlinkURLHandler(): def __init__(self, region_id): """Initialize the urls.""" - self.base_url = "https://rest.{}.{}".format(region_id, BLINK_URL) + self.base_url = "https://rest-{}.{}".format(region_id, BLINK_URL) self.home_url = "{}/homescreen".format(self.base_url) self.event_url = "{}/events/network".format(self.base_url) self.network_url = "{}/network".format(self.base_url) diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 866a2a0..4eb3fa8 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -93,9 +93,9 @@ class TestBlinkCameraSetup(unittest.TestCase): self.assertEqual(self.camera.temperature_calibrated, 71) self.assertEqual(self.camera.wifi_strength, 4) self.assertEqual(self.camera.thumbnail, - 'https://rest.test.immedia-semi.com/thumb.jpg') + 'https://rest-test.immedia-semi.com/thumb.jpg') self.assertEqual(self.camera.clip, - 'https://rest.test.immedia-semi.com/test.mp4') + 'https://rest-test.immedia-semi.com/test.mp4') self.assertEqual(self.camera.image_from_cache, 'test') self.assertEqual(self.camera.video_from_cache, 'foobar') @@ -136,7 +136,7 @@ class TestBlinkCameraSetup(unittest.TestCase): } self.camera.update(config) self.assertEqual(self.camera.thumbnail, - 'https://rest.test.immedia-semi.com/new/thumb.jpg') + 'https://rest-test.immedia-semi.com/new/thumb.jpg') def test_no_thumbnails(self, mock_sess): """Tests that thumbnail is 'None' if none found.""" From d90cd3309fdb7371cc3e70e42d7dab4757a04f67 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 19 May 2019 18:00:42 -0400 Subject: [PATCH 09/22] Update README.rst --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index cbfc1e7..c085f90 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,10 @@ blinkpy |Build Status| |Coverage Status| |Docs| |PyPi Version| |Python Version| ================================================================================ A Python library for the Blink Camera system +Like the library? Consider buying me a cup of coffee! + +|Donate| + Disclaimer: ~~~~~~~~~~~~~~~ Published under the MIT license - See LICENSE file for more details. @@ -106,3 +110,6 @@ Example usage, which downloads all videos recorded since July 4th, 2018 at 9:34a :target: http://blinkpy.readthedocs.io/en/latest/?badge=latest .. |Python Version| image:: https://img.shields.io/pypi/pyversions/blinkpy.svg :target: https://img.shields.io/pypi/pyversions/blinkpy.svg + +.. |Donate| image:: https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif + :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UR6Z2B8GXYUCC From f67f0dd6a2663c699143f82e30182a18f3e491de Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Mon, 20 May 2019 00:00:57 -0400 Subject: [PATCH 10/22] Dev version bump --- blinkpy/helpers/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index f880239..9acc021 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -4,7 +4,7 @@ import os MAJOR_VERSION = 0 MINOR_VERSION = 14 -PATCH_VERSION = '0.dev0' +PATCH_VERSION = '0.dev1' __version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION) From 00395b382504a2e98ddbb474024cd65e5c0da650 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Mon, 20 May 2019 11:04:58 -0400 Subject: [PATCH 11/22] Slugify filenames in video download to ensure OS interoperability --- blinkpy/blinkpy.py | 9 ++++++--- requirements.txt | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 0e4569f..279c513 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -21,6 +21,7 @@ from shutil import copyfileobj from requests.structures import CaseInsensitiveDict from dateutil.parser import parse +from slugify import slugify from blinkpy import api from blinkpy.sync_module import BlinkSyncModule @@ -310,7 +311,8 @@ class Blink(): continue clip_address = "{}{}".format(self.urls.base_url, address) - filename = "{}_{}.mp4".format(camera_name, created_at) + filename = "{}-{}".format(camera_name, created_at) + filename = "{}.mp4".format(slugify(filename)) filename = os.path.join(path, filename) if not debug: @@ -325,5 +327,6 @@ class Blink(): _LOGGER.info("Downloaded video to %s", filename) else: - print("Camera: {}, Timestamp: {}, Address: {}".format( - camera_name, created_at, address)) + print(("Camera: {}, Timestamp: {}, " + "Address: {}, Filename: {}").format( + camera_name, created_at, address, filename)) diff --git a/requirements.txt b/requirements.txt index 2b7eda5..6d7405d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ python-dateutil==2.7.5 requests>=2.20.0 +python-slugify==3.0.2 testtools==2.3.0 From cf65648293622445f16f2f15757efcb1630085d0 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Mon, 20 May 2019 21:15:08 -0400 Subject: [PATCH 12/22] Add legacy flag to be able to use old 'rest.region' subdomain --- blinkpy/blinkpy.py | 8 ++++++-- blinkpy/helpers/util.py | 7 +++++-- tests/test_util.py | 9 ++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 0e4569f..cf6dcb6 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -45,7 +45,7 @@ class Blink(): """Class to initialize communication.""" def __init__(self, username=None, password=None, - refresh_rate=REFRESH_RATE): + refresh_rate=REFRESH_RATE, legacy_subdomain=False): """ Initialize Blink system. @@ -53,6 +53,9 @@ class Blink(): :param password: Blink password :param refresh_rate: Refresh rate of blink information. Defaults to 15 (seconds) + :param legacy_subdomain: Set to TRUE to use old 'rest.region' + endpoints (only use if you are having + api issues). """ self._username = username self._password = password @@ -73,6 +76,7 @@ class Blink(): self.video_list = CaseInsensitiveDict({}) self._login_url = LOGIN_URL self.version = __version__ + self.legacy = legacy_subdomain @property def auth_header(self): @@ -136,7 +140,7 @@ class Blink(): self._auth_header = {'Host': self._host, 'TOKEN_AUTH': self._token} - self.urls = BlinkURLHandler(self.region_id) + self.urls = BlinkURLHandler(self.region_id, legacy=self.legacy) return self._auth_header diff --git a/blinkpy/helpers/util.py b/blinkpy/helpers/util.py index 2d0e1c8..165c3fa 100644 --- a/blinkpy/helpers/util.py +++ b/blinkpy/helpers/util.py @@ -111,9 +111,12 @@ class BlinkAuthenticationException(BlinkException): class BlinkURLHandler(): """Class that handles Blink URLS.""" - def __init__(self, region_id): + def __init__(self, region_id, legacy=False): """Initialize the urls.""" - self.base_url = "https://rest-{}.{}".format(region_id, BLINK_URL) + self.subdomain = 'rest-{}'.format(region_id) + if legacy: + self.subdomain = 'rest.{}'.format(region_id) + self.base_url = "https://{}.{}".format(self.subdomain, BLINK_URL) self.home_url = "{}/homescreen".format(self.base_url) self.event_url = "{}/events/network".format(self.base_url) self.network_url = "{}/network".format(self.base_url) diff --git a/tests/test_util.py b/tests/test_util.py index f75cf76..21524bf 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -3,7 +3,7 @@ import unittest from unittest import mock import time -from blinkpy.helpers.util import Throttle +from blinkpy.helpers.util import Throttle, BlinkURLHandler class TestUtil(unittest.TestCase): @@ -98,3 +98,10 @@ class TestUtil(unittest.TestCase): with mock.patch('time.time', return_value=now_plus_6): self.assertEqual(tester.test1(), None) self.assertEqual(tester.test2(), True) + + def test_legacy_subdomains(self): + """Test that subdomain can be set to legacy mode.""" + urls = BlinkURLHandler('test') + self.assertEqual(urls.subdomain, 'rest-test') + urls = BlinkURLHandler('test', legacy=True) + self.assertEqual(urls.subdomain, 'rest.test') From 58ce109518df273517f666cc8ad4147279015762 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 21 May 2019 22:09:36 -0400 Subject: [PATCH 13/22] Use UTC for time conversions --- blinkpy/helpers/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkpy/helpers/util.py b/blinkpy/helpers/util.py index e2e44a4..196c904 100644 --- a/blinkpy/helpers/util.py +++ b/blinkpy/helpers/util.py @@ -15,7 +15,7 @@ def get_time(time_to_convert=None): """Create blink-compatible timestamp.""" if time_to_convert is None: time_to_convert = time.time() - return time.strftime(TIMESTAMP_FORMAT, time.localtime(time_to_convert)) + return time.strftime(TIMESTAMP_FORMAT, time.gmtime(time_to_convert)) def merge_dicts(dict_a, dict_b): From 85c14ede8d2fc50c871b5ffbc72f98714aaff700 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 21 May 2019 22:23:31 -0400 Subject: [PATCH 14/22] Move constants into helpers/constants.py --- blinkpy/helpers/constants.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index 9acc021..cc907bd 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -60,3 +60,7 @@ ONLINE = {'online': True, 'offline': False} OTHER ''' TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S%Z' + +DEFAULT_MOTION_INTERVAL = 0 +DEFAULT_REFRESH = 30 +MIN_THROTTLE_TIME = 2 From 93472e38de875b64e2ed4f5df1b8f6eb97e80743 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 21 May 2019 22:36:46 -0400 Subject: [PATCH 15/22] Added in motion interval to allow for looking at historic motion events. - Good debug utility - Helps iron out rapid motion events, or missed events due to quick calls to refresh method --- blinkpy/blinkpy.py | 16 +++++++++------- blinkpy/sync_module.py | 10 +++++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 279c513..53f0ca8 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -30,14 +30,10 @@ from blinkpy.helpers.util import ( create_session, merge_dicts, get_time, BlinkURLHandler, BlinkAuthenticationException, Throttle) from blinkpy.helpers.constants import ( - BLINK_URL, LOGIN_URL, OLD_LOGIN_URL, LOGIN_BACKUP_URL) + BLINK_URL, LOGIN_URL, OLD_LOGIN_URL, LOGIN_BACKUP_URL, + DEFAULT_MOTION_INTERVAL, DEFAULT_REFRESH, MIN_THROTTLE_TIME) from blinkpy.helpers.constants import __version__ -REFRESH_RATE = 30 - -# Prevents rapid calls to blink.refresh() -# with the force_cache flag set to True -MIN_THROTTLE_TIME = 2 _LOGGER = logging.getLogger(__name__) @@ -46,7 +42,8 @@ class Blink(): """Class to initialize communication.""" def __init__(self, username=None, password=None, - refresh_rate=REFRESH_RATE): + refresh_rate=DEFAULT_REFRESH, + motion_interval=DEFAULT_MOTION_INTERVAL): """ Initialize Blink system. @@ -54,6 +51,10 @@ class Blink(): :param password: Blink password :param refresh_rate: Refresh rate of blink information. Defaults to 15 (seconds) + :param motion_interval: How far back to register motion in minutes. + Defaults to last refresh time. + Useful for preventing motion_detected property + from de-asserting too quickly. """ self._username = username self._password = password @@ -73,6 +74,7 @@ class Blink(): self.cameras = CaseInsensitiveDict({}) self.video_list = CaseInsensitiveDict({}) self._login_url = LOGIN_URL + self.motion_interval = DEFAULT_MOTION_INTERVAL self.version = __version__ @property diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index 337b56c..6c6a6ce 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -33,6 +33,7 @@ class BlinkSyncModule(): self.network_info = None self.events = [] self.cameras = CaseInsensitiveDict({}) + self.motion_interval = blink.motion_interval self.motion = {} self.last_record = {} self.camera_list = camera_list @@ -161,8 +162,15 @@ class BlinkSyncModule(): def check_new_videos(self): """Check if new videos since last refresh.""" + try: + interval = self.blink.last_refresh - self.motion_interval*60 + except TypeError: + # This is the first start, so refresh hasn't happened yet. + # No need to check for motion. + return False + resp = api.request_videos(self.blink, - time=self.blink.last_refresh, + time=interval, page=1) for camera in self.cameras.keys(): From b88a5feddf3524fa39372348c7daa4be6f260124 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 21 May 2019 22:38:49 -0400 Subject: [PATCH 16/22] Change default interval to one minute --- blinkpy/helpers/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index cc907bd..28eb8e0 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -61,6 +61,6 @@ OTHER ''' TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S%Z' -DEFAULT_MOTION_INTERVAL = 0 +DEFAULT_MOTION_INTERVAL = 1 DEFAULT_REFRESH = 30 MIN_THROTTLE_TIME = 2 From 50b1a351685d275e80690c8f7b7a4c89fc6ee31d Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 21 May 2019 22:46:11 -0400 Subject: [PATCH 17/22] Update tests --- tests/test_sync_module.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index 7962a07..191e83c 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -17,12 +17,14 @@ class TestBlinkSyncModule(unittest.TestCase): def setUp(self): """Set up Blink module.""" self.blink = blinkpy.Blink(username=USERNAME, - password=PASSWORD) + password=PASSWORD, + motion_interval=0) # pylint: disable=protected-access self.blink._auth_header = { 'Host': 'test.url.tld', 'TOKEN_AUTH': 'foobar123' } + self.blink.last_refresh = 0 self.blink.urls = blinkpy.BlinkURLHandler('test') self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', @@ -59,6 +61,12 @@ class TestBlinkSyncModule(unittest.TestCase): self.assertEqual(self.blink.sync['test'].get_camera_info('1234'), 'foobar') + def test_check_new_videos_startup(self, mock_resp): + """Test that check_new_videos does not block startup.""" + sync_module = self.blink.sync['test'] + self.blink.last_refresh = None + self.assertFalse(sync_module.check_new_videos()) + def test_check_new_videos(self, mock_resp): """Test recent video response.""" mock_resp.return_value = { From 726d8b8c65aecf1df052b7a85d3ed54c654513db Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 21 May 2019 23:14:26 -0400 Subject: [PATCH 18/22] dev version bump --- blinkpy/helpers/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index 28eb8e0..cd5d7d5 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -4,7 +4,7 @@ import os MAJOR_VERSION = 0 MINOR_VERSION = 14 -PATCH_VERSION = '0.dev1' +PATCH_VERSION = '0.dev2' __version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION) From bb887936b6dad420c45835cba437c45f095fe5d3 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 23 May 2019 14:35:27 -0400 Subject: [PATCH 19/22] Blink instantiation bugfix --- blinkpy/blinkpy.py | 2 +- blinkpy/helpers/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 850baa4..9edf985 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -78,7 +78,7 @@ class Blink(): self.cameras = CaseInsensitiveDict({}) self.video_list = CaseInsensitiveDict({}) self._login_url = LOGIN_URL - self.motion_interval = DEFAULT_MOTION_INTERVAL + self.motion_interval = motion_interval self.version = __version__ self.legacy = legacy_subdomain diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index cd5d7d5..7b1b1da 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -4,7 +4,7 @@ import os MAJOR_VERSION = 0 MINOR_VERSION = 14 -PATCH_VERSION = '0.dev2' +PATCH_VERSION = '0.dev3' __version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION) From 1e71af28ddac2a07fbcc3e1c2f6eaa9280702e9d Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 23 May 2019 16:06:56 -0400 Subject: [PATCH 20/22] Version bump --- blinkpy/helpers/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index 7b1b1da..81fe6f8 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -4,7 +4,7 @@ import os MAJOR_VERSION = 0 MINOR_VERSION = 14 -PATCH_VERSION = '0.dev3' +PATCH_VERSION = 0 __version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION) From d021f48c679fd436f8942a12cd3fd94ccdb0d28c Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 23 May 2019 16:15:07 -0400 Subject: [PATCH 21/22] Update CHANGES.rst --- CHANGES.rst | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 92fa0e2..b473a6a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,28 @@ Changelog A list of changes between each release +0.14.0 (2019-05-23) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +**Breaking Changes:** + +- ``BlinkCamera.battery`` no longer reports a percentage, instead it returns a string representing the state of the battery. +- Previous logic for calculating percentage was incorrect +- raw battery voltage can be accessed via ``BlinkCamera.battery_voltage`` + +**Bug Fixes:** + +- Updated video endpoint (fixes broken motion detection) +- Removed throttling from critical api methods which prevented proper operation of multi-sync unit setups +- Slugify downloaded video names to allow for OS interoperability +- Added one minute offset (``Blink.motion_interval``) when checking for recent motion to allow time for events to propagate to server prior to refresh call. + +**Everything else:** + +- Changed all urls to use ``rest-region`` rather than ``rest.region``. Ability to revert to old method is enabled by instantiating ``Blink()`` with the ``legacy_subdomain`` variable set to ``True``. +- Added debug mode to ``blinkpy.download_videos`` routine to simply print the videos prepped for download, rather than actually saving them. +- Use UTC for time conversions, rather than local timezone + + 0.13.1 (2019-03-01) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Remove throttle decorator from network status request @@ -10,10 +32,10 @@ A list of changes between each release 0.13.0 (2019-03-01) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **Breaking change:** -Wifi status reported in dBm again, instead of bars (which is great). Also, the old `get_camera_info` method has changed and requires a `camera_id` parameter. +Wifi status reported in dBm again, instead of bars (which is great). Also, the old ``get_camera_info`` method has changed and requires a ``camera_id`` parameter. - Adds throttle decorator -- Decorate following functions with 4s throttle (call method with `force=True` to override): +- Decorate following functions with 4s throttle (call method with ```force=True` to override): - request_network_status - request_syncmodule - request_system_arm @@ -28,7 +50,7 @@ Wifi status reported in dBm again, instead of bars (which is great). Also, the - request_motion_detection_enable - request_motion_detection_disable - Use the updated homescreen api endpoint to retrieve camera information. The old method to retrieve all cameras at once seems to not exist, and this was the only solution I could figure out and confirm to work. -- Adds throttle decorator to refresh function to prevent too many frequent calls with `force_cache` flag set to `True`. This additional throttle can be overridden with the `force=True` argument passed to the refresh function. +- Adds throttle decorator to refresh function to prevent too many frequent calls with ``force_cache`` flag set to ``True``. This additional throttle can be overridden with the ``force=True`` argument passed to the refresh function. - Add ability to cycle through login api endpoints to anticipate future endpoint deprecation @@ -70,7 +92,7 @@ Wifi status reported in dBm again, instead of bars (which is great). Also, the 0.10.2 (2018-10-30) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Set minimum required version of the requests library to 2.20.0 due to vulnerability in earlier releases. -- When multiple networks detected, changed log level to 'warning' from 'error' +- When multiple networks detected, changed log level to ``warning`` from ``error`` 0.10.1 (2018-10-18) @@ -98,7 +120,7 @@ Wifi status reported in dBm again, instead of bars (which is great). Also, the - Use session for http requests **Breaking change:** -- Cameras now accessed through sync module Blink.sync.cameras +- Cameras now accessed through sync module ``Blink.sync.cameras`` 0.8.1 (2018-09-24) From 79e699ba78aab185ec48ea67b9d397925438ea34 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 23 May 2019 16:42:12 -0400 Subject: [PATCH 22/22] Update CHANGES.rst --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index b473a6a..b66c62c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -35,7 +35,7 @@ A list of changes between each release Wifi status reported in dBm again, instead of bars (which is great). Also, the old ``get_camera_info`` method has changed and requires a ``camera_id`` parameter. - Adds throttle decorator -- Decorate following functions with 4s throttle (call method with ```force=True` to override): +- Decorate following functions with 4s throttle (call method with ``force=True`` to override): - request_network_status - request_syncmodule - request_system_arm