Compare commits

..
10 Commits
Author SHA1 Message Date
Kevin FronczakandGitHub 0262f14a53 Merge pull request #163 from fronzbot/dev
0.13.0
2019-03-01 21:04:36 -05:00
Kevin FronczakandGitHub ff8f091a74 Merge pull request #154 from fronzbot/dev
0.12.1
2019-01-31 18:22:09 -08:00
Kevin FronczakandGitHub 77e200f6a5 Merge pull request #152 from fronzbot/dev
0.12.0
2019-01-31 17:05:03 -08:00
Kevin FronczakandGitHub f3a8036649 Merge pull request #141 from fronzbot/dev
0.11.2
2019-01-23 08:05:05 -08:00
Kevin FronczakandGitHub 853ccbce79 Merge pull request #136 from fronzbot/dev
0.11.1
2019-01-02 09:19:08 -08:00
Kevin FronczakandGitHub 1a24e1896f Merge pull request #129 from fronzbot/dev
0.11.0
2018-11-23 20:27:40 -05:00
Kevin FronczakandGitHub ae946dbb53 Merge pull request #121 from fronzbot/dev
0.10.3
2018-11-20 21:47:42 -05:00
Kevin FronczakandGitHub 80c6444c2e Merge pull request #112 from fronzbot/dev
0.10.2
2018-10-30 20:22:08 -04:00
Kevin FronczakandGitHub e872fc9ce5 Merge pull request #105 from fronzbot/dev
0.10.1
2018-10-18 13:19:49 -04:00
Kevin FronczakandGitHub 4e6957a772 Merge pull request #100 from fronzbot/dev
Release 0.10.0
2018-10-16 17:43:21 -04:00
12 changed files with 52 additions and 96 deletions
-4
View File
@@ -3,10 +3,6 @@ Changelog
A list of changes between each release
0.13.1 (2019-03-01)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Remove throttle decorator from network status request
0.13.0 (2019-03-01)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**Breaking change:**
-7
View File
@@ -2,10 +2,6 @@ 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.
@@ -110,6 +106,3 @@ 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
+5 -2
View File
@@ -40,6 +40,7 @@ def request_networks(blink):
return http_get(blink, url)
@Throttle(seconds=MIN_THROTTLE_TIME)
def request_network_status(blink, network):
"""
Request network information.
@@ -51,6 +52,7 @@ 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.
@@ -166,11 +168,12 @@ def request_videos(blink, time=None, page=0):
:param page: Page number to get videos from.
"""
timestamp = get_time(time)
url = "{}/api/v1/accounts/{}/media/changed?since={}&page={}".format(
blink.urls.base_url, blink.account_id, timestamp, page)
url = "{}/api/v2/videos/changed?since={}&page={}".format(
blink.urls.base_url, timestamp, page)
return http_get(blink, url)
@Throttle(seconds=MIN_THROTTLE_TIME)
def request_cameras(blink, network):
"""
Request all camera information.
+22 -34
View File
@@ -21,7 +21,6 @@ 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
@@ -30,10 +29,14 @@ 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,
DEFAULT_MOTION_INTERVAL, DEFAULT_REFRESH, MIN_THROTTLE_TIME)
BLINK_URL, LOGIN_URL, OLD_LOGIN_URL, LOGIN_BACKUP_URL)
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__)
@@ -42,8 +45,7 @@ class Blink():
"""Class to initialize communication."""
def __init__(self, username=None, password=None,
refresh_rate=DEFAULT_REFRESH,
motion_interval=DEFAULT_MOTION_INTERVAL):
refresh_rate=REFRESH_RATE):
"""
Initialize Blink system.
@@ -51,10 +53,6 @@ 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
@@ -74,7 +72,6 @@ class Blink():
self.cameras = CaseInsensitiveDict({})
self.video_list = CaseInsensitiveDict({})
self._login_url = LOGIN_URL
self.motion_interval = DEFAULT_MOTION_INTERVAL
self.version = __version__
@property
@@ -250,8 +247,7 @@ class Blink():
combined = merge_dicts(combined, self.sync[sync].cameras)
return combined
def download_videos(self, path, since=None,
camera='all', stop=10, debug=False):
def download_videos(self, path, since=None, camera='all', stop=10):
"""
Download all videos from server since specified time.
@@ -262,8 +258,6 @@ 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
@@ -281,23 +275,23 @@ class Blink():
response = api.request_videos(self, time=since_epochs, page=page)
_LOGGER.debug("Processing page %s", page)
try:
result = response['media']
result = response['videos']
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, debug)
self._parse_downloaded_items(result, camera, path)
def _parse_downloaded_items(self, result, camera, path, debug):
def _parse_downloaded_items(self, result, camera, path):
"""Parse downloaded videos."""
for item in result:
try:
created_at = item['created_at']
camera_name = item['device_name']
camera_name = item['camera_name']
is_deleted = item['deleted']
address = item['media']
address = item['address']
except KeyError:
_LOGGER.info("Missing clip information, skipping...")
continue
@@ -313,22 +307,16 @@ class Blink():
continue
clip_address = "{}{}".format(self.urls.base_url, address)
filename = "{}-{}".format(camera_name, created_at)
filename = "{}.mp4".format(slugify(filename))
filename = "{}_{}.mp4".format(camera_name, created_at)
filename = os.path.join(path, filename)
if not debug:
if os.path.isfile(filename):
_LOGGER.info("%s already exists, skipping...", filename)
continue
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)
else:
print(("Camera: {}, Timestamp: {}, "
"Address: {}, Filename: {}").format(
camera_name, created_at, address, filename))
_LOGGER.info("Downloaded video to %s", filename)
+2 -3
View File
@@ -41,7 +41,6 @@ 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,
@@ -55,8 +54,8 @@ class BlinkCamera():
@property
def battery(self):
"""Return battery as string."""
return self.battery_state
"""Return battery level as percentage."""
return round(self.battery_voltage / 180 * 100)
@property
def temperature_c(self):
+2 -6
View File
@@ -3,8 +3,8 @@
import os
MAJOR_VERSION = 0
MINOR_VERSION = 14
PATCH_VERSION = '0.dev1'
MINOR_VERSION = 13
PATCH_VERSION = 0
__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
@@ -60,7 +60,3 @@ ONLINE = {'online': True, 'offline': False}
OTHER
'''
TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S%Z'
DEFAULT_MOTION_INTERVAL = 1
DEFAULT_REFRESH = 30
MIN_THROTTLE_TIME = 2
+1 -1
View File
@@ -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.gmtime(time_to_convert))
return time.strftime(TIMESTAMP_FORMAT, time.localtime(time_to_convert))
def merge_dicts(dict_a, dict_b):
+8 -18
View File
@@ -33,7 +33,6 @@ 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
@@ -65,10 +64,7 @@ class BlinkSyncModule():
@property
def arm(self):
"""Return status of sync module: armed/disarmed."""
try:
return self.network_info['network']['armed']
except (KeyError, TypeError):
return None
return self.network_info['network']['armed']
@arm.setter
def arm(self, value):
@@ -81,7 +77,8 @@ class BlinkSyncModule():
def start(self):
"""Initialize the system."""
response = api.request_syncmodule(self.blink,
self.network_id)
self.network_id,
force=True)
try:
self.summary = response['syncmodule']
self.network_id = self.summary['network_id']
@@ -162,30 +159,23 @@ 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=interval,
page=1)
time=self.blink.last_refresh,
page=0)
for camera in self.cameras.keys():
self.motion[camera] = False
try:
info = resp['media']
info = resp['videos']
except (KeyError, TypeError):
_LOGGER.warning("Could not check for motion. Response: %s", resp)
return False
for entry in info:
try:
name = entry['device_name']
clip = entry['media']
name = entry['camera_name']
clip = entry['address']
timestamp = entry['created_at']
self.motion[name] = True
self.last_record[name] = {'clip': clip, 'time': timestamp}
-1
View File
@@ -1,4 +1,3 @@
python-dateutil==2.7.5
requests>=2.20.0
python-slugify==3.0.2
testtools==2.3.0
+6 -6
View File
@@ -121,12 +121,12 @@ class TestBlinkFunctions(unittest.TestCase):
blinkpy._LOGGER.setLevel(logging.DEBUG)
generic_entry = {
'created_at': '1970',
'device_name': 'foo',
'camera_name': 'foo',
'deleted': True,
'media': '/bar.mp4'
'address': '/bar.mp4'
}
result = [generic_entry]
mock_req.return_value = {'media': result}
mock_req.return_value = {'videos': 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',
'device_name': 'foo',
'camera_name': 'foo',
'deleted': True,
'media': '/bar.mp4'
'address': '/bar.mp4'
}
result = [generic_entry]
mock_req.return_value = {'media': result}
mock_req.return_value = {'videos': result}
blink.last_refresh = 0
formatted_date = get_time(blink.last_refresh)
expected_log = [
+1 -1
View File
@@ -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, 'ok')
self.assertEqual(self.camera.battery, 50)
self.assertEqual(self.camera.temperature, 68)
self.assertEqual(self.camera.temperature_c, 20)
self.assertEqual(self.camera.temperature_calibrated, 71)
+5 -13
View File
@@ -17,14 +17,12 @@ class TestBlinkSyncModule(unittest.TestCase):
def setUp(self):
"""Set up Blink module."""
self.blink = blinkpy.Blink(username=USERNAME,
password=PASSWORD,
motion_interval=0)
password=PASSWORD)
# 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',
@@ -61,18 +59,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 = {
'media': [{
'device_name': 'foo',
'media': '/foo/bar.mp4',
'videos': [{
'camera_name': 'foo',
'address': '/foo/bar.mp4',
'created_at': '1970-01-01T00:00:00+0:00'
}]
}
@@ -84,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 = {'media': []}
mock_resp.return_value = {'videos': []}
self.assertTrue(sync_module.check_new_videos())
self.assertEqual(sync_module.motion, {'foo': False})
self.assertEqual(sync_module.last_record['foo'],