Compare commits

..
38 Commits
Author SHA1 Message Date
Kevin FronczakandGitHub 37fc047174 Merge pull request #80 from fronzbot/v0.8.3
V0.8.3
2018-09-24 22:31:12 -04:00
Kevin FronczakandGitHub 6343965bbc Merge branch 'master' into v0.8.3 2018-09-24 22:30:56 -04:00
Kevin Fronczak 411da60bb2 version_bump 2018-09-24 22:30:17 -04:00
Kevin Fronczak 72c36d8381 revert tests 2018-09-24 22:27:42 -04:00
Kevin Fronczak b0974c1ab2 reverting to 0.8.1 style thumbnail retreival 2018-09-24 22:25:43 -04:00
Kevin Fronczak a76eac085a Fix thumbnail since we aren't using homescreen anymore 2018-09-24 22:12:07 -04:00
Kevin FronczakandGitHub 5319714a37 Merge pull request #79 from fronzbot/v0.8.2
V0.8.2
2018-09-24 21:36:44 -04:00
Kevin FronczakandGitHub 5d9105655f Merge branch 'master' into v0.8.2 2018-09-24 21:32:02 -04:00
Kevin Fronczak b98c03b96b version bump 2018-09-24 21:30:23 -04:00
Kevin FronczakandGitHub ec703ae4b0 Merge pull request #78 from fronzbot/sort-motion-clips
Changed motion check to look at sorted date list
2018-09-24 21:29:01 -04:00
Kevin Fronczak 9ce09b4761 Fix thumbnails grabbing wrong date 2018-09-24 21:24:21 -04:00
Kevin Fronczak 6d002fc8b0 record_dates needs to default to dict, not list 2018-09-24 20:59:16 -04:00
Kevin Fronczak 7228340677 Changed motion check to look at sorted date list
- Previous method using clips resulted in an unsorted array, so the
newest clip wasn't always added during check
2018-09-24 20:26:36 -04:00
Kevin FronczakandGitHub 9335a37feb Merge pull request #77 from fronzbot/v0.8.1
v0.8.1
2018-09-24 13:35:48 -04:00
Kevin Fronczak 371348533f Fix trailing whitespace 2018-09-24 13:30:56 -04:00
Kevin Fronczak 26f6defe60 Version bump 2018-09-24 13:27:10 -04:00
Kevin FronczakandGitHub 52bc4af1f0 Merge pull request #76 from fronzbot/better-motion
Fix motion detection
2018-09-24 13:25:14 -04:00
Kevin Fronczak 8c94305804 Fix motion detection
- Use an array of recent video clips to determine if motion has been
detected.
- Reset the value every system refresh
2018-09-24 13:17:15 -04:00
Kevin FronczakandGitHub 0eb57698bd Merge pull request #72 from fronzbot/lint-upgrade
Update linter versions
2018-08-09 09:43:51 -04:00
Kevin FronczakandGitHub 7b0db80edd Disable no-else-return 2018-08-09 09:37:57 -04:00
Kevin FronczakandGitHub 0e63e97a57 Remove whitespace 2018-08-09 09:27:36 -04:00
Kevin FronczakandGitHub e4a21964d2 Fix useless-import-alias 2018-08-09 09:26:22 -04:00
Kevin FronczakandGitHub 39ad37f23b Fix pylint errors
- change if comparison to fix (consider-using-in)
- Disabled no else-if-return check
2018-08-09 09:25:42 -04:00
Kevin FronczakandGitHub 34bc61e1c3 Fix pylint warnings
- Remove object from class declarations
- Remove useless returns from functions
2018-08-09 09:21:49 -04:00
Kevin FronczakandGitHub e6a7c367b3 Merge branch 'dev' into lint-upgrade 2018-08-09 07:35:26 -04:00
Kevin FronczakandGitHub 66afd33b79 Merge pull request #71 from fronzbot/upgrade-pytest
Pytest 3.7.1
2018-08-09 07:28:51 -04:00
Kevin FronczakandGitHub cf617d720e Update linter versions 2018-08-09 07:27:12 -04:00
Kevin FronczakandGitHub 4f2eca08e0 Update requirements_test.txt 2018-08-09 07:21:05 -04:00
Kevin FronczakandGitHub 2972780c18 Merge pull request #69 from fronzbot/dev-version-bump
Dev version bump
2018-05-21 11:47:52 -04:00
Kevin Fronczak 9c3f3f2f98 Version bump 2018-05-21 11:44:05 -04:00
Kevin FronczakandGitHub 0d83a7fcb1 Merge pull request #68 from fronzbot/v0.8.0
V0.8.0
2018-05-21 11:40:14 -04:00
Kevin FronczakandGitHub 80f4ca7c4f Merge branch 'master' into v0.8.0 2018-05-21 11:36:34 -04:00
Kevin Fronczak 96e7fce304 Version bump 2018-05-21 11:34:09 -04:00
Kevin FronczakandGitHub 13a358c07c Merge pull request #67 from fronzbot/celcius-support
Add celcius property for cameras
2018-05-21 11:26:43 -04:00
Kevin Fronczak 389745533c Add celcius property for cameras 2018-05-21 11:12:32 -04:00
Kevin FronczakandGitHub 65dd547a32 Merge pull request #65 from fronzbot/battery-percent
Added new request url for camera config
2018-05-21 11:03:25 -04:00
Kevin Fronczak 91a877da79 Fix tests, updated changes in changelog 2018-05-21 10:54:32 -04:00
Kevin Fronczak d357215e0e Added new request url for camera config
- Adds support for battery voltage level
- Added new camera properties (wifi_strength, motion_detected)
- Store full camera config from request into dict
2018-05-21 00:16:24 -04:00
10 changed files with 160 additions and 35 deletions
+1
View File
@@ -1,3 +1,4 @@
.pytest_cache/*
.cache/*
.tox/*
__pycache__/*
+10
View File
@@ -3,6 +3,16 @@ Changelog
A list of changes between each release
0.9.0.dev (Development Version)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
0.8.0 (2018-05-21)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Added support for battery voltage level (fixes `#64 <https://github.com/fronzbot/blinkpy/issues/64>`_)
- Added motion detection per camera
- Added fully accessible camera configuration dict
- Added celcius property to camera (fixes `#60 <https://github.com/fronzbot/blinkpy/issues/60>`_)
0.7.1 (2018-05-09)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed pip 10 import issue during setup (`@fronzbot <https://github.com/fronzbot/blinkpy/pull/61>`_)
+93 -13
View File
@@ -26,6 +26,8 @@ from blinkpy.helpers.constants import (
_LOGGER = logging.getLogger('blinkpy')
MAX_CLIPS = 5
def _attempt_reauthorization(blink):
"""Attempt to refresh auth token and links."""
@@ -79,7 +81,7 @@ class BlinkAuthenticationException(BlinkException):
pass
class BlinkURLHandler(object):
class BlinkURLHandler():
"""Class that handles Blink URLS."""
def __init__(self, region_id):
@@ -93,7 +95,7 @@ class BlinkURLHandler(object):
_LOGGER.debug("Setting base url to %s.", self.base_url)
class BlinkCamera(object):
class BlinkCamera():
"""Class to initialize individual camera."""
def __init__(self, config, blink):
@@ -107,13 +109,19 @@ class BlinkCamera(object):
config['thumbnail'])
self.clip = "{}{}".format(self.urls.base_url, config['video'])
self.temperature = config['temp']
self.battery = config['battery']
self._battery_string = config['battery']
self.notifications = config['notifications']
self.motion = {}
self.motion = dict()
self.header = None
self.image_link = None
self.arm_link = None
self.region_id = config['region_id']
self.battery_voltage = -180
self.motion_detected = None
self.wifi_strength = None
self.camera_config = dict()
self.motion_enabled = None
self.last_record = list()
@property
def attributes(self):
@@ -124,11 +132,16 @@ class BlinkCamera(object):
'status': self._status,
'armed': self.armed,
'temperature': self.temperature,
'temperature_c': self.temperature_c,
'battery': self.battery,
'thumbnail': self.thumbnail,
'video': self.clip,
'motion_enabled': self.motion_enabled,
'notifications': self.notifications,
'network_id': self.blink.network_id
'motion_detected': self.motion_detected,
'wifi_strength': self.wifi_strength,
'network_id': self.blink.network_id,
'last_record': self.last_record
}
return attributes
@@ -142,16 +155,26 @@ class BlinkCamera(object):
"""Return camera arm status."""
return True if self._status == 'armed' else False
@property
def battery(self):
"""Return battery level as percentage."""
return round(self.battery_voltage / 180 * 100)
@property
def battery_string(self):
"""Return string indicating battery status."""
status = "Unknown"
if self.battery > 1 and self.battery <= 3:
if self._battery_string > 1 and self._battery_string <= 3:
status = "OK"
elif self.battery >= 0:
elif self._battery_string >= 0:
status = "Low"
return status
@property
def temperature_c(self):
"""Return temperature in celcius."""
return round((self.temperature - 32) / 9.0 * 5.0, 1)
def snap_picture(self):
"""Take a picture with camera to create a new thumbnail."""
_request(self.blink, url=self.image_link,
@@ -171,14 +194,49 @@ class BlinkCamera(object):
"""Update camera information."""
self.name = values['name']
self._status = values['active']
self.thumbnail = "{}{}.jpg".format(
self.urls.base_url, values['thumbnail'])
self.clip = "{}{}".format(
self.urls.base_url, values['video'])
self.temperature = values['temp']
self.battery = values['battery']
self.thumbnail = "{}{}.jpg".format(
self.urls.base_url, values['thumbnail'])
self._battery_string = values['battery']
self.notifications = values['notifications']
try:
cfg = self.blink.camera_config_request(self.id)
self.camera_config = cfg
except requests.exceptions.RequestException as err:
_LOGGER.warning("Could not get config for %s with id %s",
self.name, self.id)
_LOGGER.warning("Exception raised: %s", err)
try:
self.battery_voltage = cfg['camera'][0]['battery_voltage']
self.motion_enabled = cfg['camera'][0]['motion_alert']
self.wifi_strength = cfg['camera'][0]['wifi_strength']
self.temperature = cfg['camera'][0]['temperature']
except KeyError:
_LOGGER.warning("Problem extracting config for camera %s",
self.name)
# Check if the most recent clip is included in the last_record list
# and that the last_record list is populated
try:
records = sorted(self.blink.record_dates[self.name])
new_clip = records.pop()
if new_clip not in self.last_record and self.last_record:
self.motion_detected = True
self.last_record.insert(0, new_clip)
if len(self.last_record) > MAX_CLIPS:
self.last_record.pop()
elif not self.last_record:
self.last_record.insert(0, new_clip)
self.motion_detected = False
else:
self.motion_detected = False
except KeyError:
_LOGGER.warning("Could not extract clip info from camera %s",
self.name)
def image_refresh(self):
"""Refresh current thumbnail."""
url = self.urls.home_url
@@ -200,11 +258,15 @@ class BlinkCamera(object):
"""Write image to file."""
_LOGGER.debug("Writing image from %s to %s", self.name, path)
thumb = self.image_refresh()
if not thumb:
thumb = self.thumbnail
response = _request(self.blink, url=thumb, headers=self.header,
reqtype='get', stream=True, json_resp=False)
if response.status_code == 200:
with open(path, 'wb') as imgfile:
copyfileobj(response.raw, imgfile)
else:
print(response)
def video_to_file(self, path):
"""Write video to file."""
@@ -215,7 +277,7 @@ class BlinkCamera(object):
copyfileobj(response.raw, vidfile)
class Blink(object):
class Blink():
"""Class to initialize communication and sync module."""
def __init__(self, username=None, password=None):
@@ -236,6 +298,7 @@ class Blink(object):
self._video_count = 0
self._all_videos = {}
self._summary = None
self.record_dates = dict()
@property
def camera_thumbs(self):
@@ -315,11 +378,11 @@ class Blink(object):
camera.update(element)
except KeyError:
pass
return None
def get_videos(self, start_page=0, end_page=1):
"""Retrieve last recorded videos per camera."""
videos = list()
all_dates = dict()
for page_num in range(start_page, end_page + 1):
this_page = self._video_request(page_num)
if not this_page:
@@ -327,10 +390,17 @@ class Blink(object):
videos.append(this_page)
for page in videos:
_LOGGER.debug("Retrieved video page %s", page)
for entry in page:
camera_name = entry['camera_name']
clip_addr = entry['address']
thumb_addr = entry['thumbnail']
clip_date = clip_addr.split('_')[-6:]
clip_date = '_'.join(clip_date)
clip_date = clip_date.split('.')[0]
if camera_name not in all_dates:
all_dates[camera_name] = list()
all_dates[camera_name].append(clip_date)
try:
self._all_videos[camera_name].append(
{
@@ -345,6 +415,7 @@ class Blink(object):
'thumb': thumb_addr,
}
]
self.record_dates = all_dates
def get_cameras(self):
"""Find and creates cameras."""
@@ -365,6 +436,7 @@ class Blink(object):
device = BlinkCamera(element, self)
self.cameras[device.name] = device
self._idlookup[device.id] = device.name
self.refresh()
def set_links(self):
"""Set access links and required headers for each camera in system."""
@@ -497,3 +569,11 @@ class Blink(object):
self.network_id)
headers = self._auth_header
return _request(self, url=url, headers=headers, reqtype='get')
def camera_config_request(self, camera_id):
"""Retrieve more info about Blink config."""
url = "{}/network/{}/camera/{}/config".format(self.urls.base_url,
self.network_id,
str(camera_id))
headers = self._auth_header
return _request(self, url=url, headers=headers, reqtype='get')
+2 -2
View File
@@ -3,8 +3,8 @@
import os
MAJOR_VERSION = 0
MINOR_VERSION = 7
PATCH_VERSION = 1
MINOR_VERSION = 8
PATCH_VERSION = 3
__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
+3 -1
View File
@@ -7,6 +7,7 @@ reports=no
# unused-argument - generic callbacks and setup methods create a lot of warnings
# too-many-* - are not enforced for the sake of readability
# too-few-* - same as too-many-*
# no-else-return - I don't see any reason to enforce this. both forms are readable
disable=
locally-disabled,
@@ -20,4 +21,5 @@ disable=
too-many-return-statements,
too-many-statements,
too-many-lines,
too-few-public-methods,
too-few-public-methods,
no-else-return,
+6 -6
View File
@@ -1,10 +1,10 @@
flake8==3.3
flake8-docstrings==1.1.0
pylint==1.8.1
pydocstyle==2.0.0
pytest==3.3.1
flake8==3.5.0
flake8-docstrings==1.3.0
pylint==2.1.1
pydocstyle==2.1.1
pytest==3.7.1
pytest-cov>=2.3.1
pytest-sugar>=0.9.0
pytest-timeout>=1.0.0
restructuredtext-lint>=1.0.1
pygments>=2.2.0
pygments>=2.2.0
+2 -1
View File
@@ -39,7 +39,7 @@ def mocked_requests_post(*args, **kwargs):
response_to_return = {'message': 'Error', 'code': 404}
code_to_return = 404
if url_arg == const.LOGIN_URL or url_arg == const.LOGIN_BACKUP_URL:
if url_arg in (const.LOGIN_URL, const.LOGIN_BACKUP_URL):
response_to_return = LOGIN_RESPONSE
code_to_return = 200
elif url_arg is not None:
@@ -77,6 +77,7 @@ def mocked_requests_get(*args, **kwargs):
url_arg = args[0]
# pylint: disable=R1711
if url_arg == 'use_bad_response':
return MockGetResponse({'foo': 'bar'}, 200)
elif url_arg == 'reauth':
+33 -10
View File
@@ -15,6 +15,17 @@ import tests.mock_responses as mresp
USERNAME = 'foobar'
PASSWORD = 'deadbeef'
CAMERA_CFG = {
'camera': [
{
'battery_voltage': 90,
'motion_alert': True,
'wifi_strength': -30,
'temperature': 68
}
]
}
class TestBlinkCameraSetup(unittest.TestCase):
"""Test the Blink class in blinkpy."""
@@ -35,6 +46,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
'notifications': 2,
'region_id': 'test'
}
self.blink.urls = blinkpy.BlinkURLHandler('test')
self.blink.network_id = '0000'
@@ -42,11 +54,13 @@ class TestBlinkCameraSetup(unittest.TestCase):
"""Clean up after test."""
self.blink = None
@mock.patch('blinkpy.blinkpy.Blink.camera_config_request',
return_value=CAMERA_CFG)
@mock.patch('blinkpy.blinkpy.requests.post',
side_effect=mresp.mocked_requests_post)
@mock.patch('blinkpy.blinkpy.requests.get',
side_effect=mresp.mocked_requests_get)
def test_camera_properties(self, mock_get, mock_post):
def test_camera_properties(self, mock_get, mock_post, mock_cfg):
"""Tests all property set/recall."""
self.blink.urls = blinkpy.BlinkURLHandler('test')
@@ -56,7 +70,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
for name in self.blink.cameras:
camera = self.blink.cameras[name]
camera.update(self.camera_config)
self.assertEqual(camera.id, '1111')
self.assertEqual(camera.name, 'foobar')
self.assertEqual(camera.armed, False)
@@ -68,11 +82,15 @@ class TestBlinkCameraSetup(unittest.TestCase):
camera.clip,
"https://rest.test.{}/test/clip/clip.mp4".format(BLINK_URL)
)
self.assertEqual(camera.temperature, 70)
self.assertEqual(camera.battery, 3)
self.assertEqual(camera.temperature, 68)
self.assertEqual(camera.temperature_c, 20.0)
self.assertEqual(camera.battery, 50)
self.assertEqual(camera.battery_string, "OK")
self.assertEqual(camera.notifications, 2)
self.assertEqual(camera.region_id, 'test')
self.assertEqual(camera.motion_enabled, True)
self.assertEqual(camera.wifi_strength, -30)
camera_config = self.camera_config
camera_config['active'] = 'armed'
camera_config['thumbnail'] = '/test2/image'
@@ -80,7 +98,6 @@ class TestBlinkCameraSetup(unittest.TestCase):
camera_config['temp'] = 60
camera_config['battery'] = 0
camera_config['notifications'] = 4
for name in self.blink.cameras:
camera = self.blink.cameras[name]
camera.update(camera_config)
@@ -93,8 +110,8 @@ class TestBlinkCameraSetup(unittest.TestCase):
camera.clip,
"https://rest.test.{}/test2/clip.mp4".format(BLINK_URL)
)
self.assertEqual(camera.temperature, 60)
self.assertEqual(camera.battery, 0)
self.assertEqual(camera.temperature, 68)
self.assertEqual(camera.battery, 50)
self.assertEqual(camera.battery_string, "Low")
self.assertEqual(camera.notifications, 4)
camera_config['battery'] = -10
@@ -107,7 +124,9 @@ class TestBlinkCameraSetup(unittest.TestCase):
self.blink.cameras['foobar'] = camera_object
self.assertEqual(camera_object, self.blink.cameras['fOoBaR'])
def test_camera_attributes(self):
@mock.patch('blinkpy.blinkpy.Blink.camera_config_request',
return_value=CAMERA_CFG)
def test_camera_attributes(self, mock_cfg):
"""Tests camera attributes."""
self.blink.urls = blinkpy.BlinkURLHandler('test')
@@ -117,6 +136,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
for name in self.blink.cameras:
camera = self.blink.cameras[name]
camera.update(self.camera_config)
camera_attr = camera.attributes
self.assertEqual(camera_attr['device_id'], '1111')
self.assertEqual(camera_attr['name'], 'foobar')
@@ -129,7 +149,10 @@ class TestBlinkCameraSetup(unittest.TestCase):
camera_attr['video'],
"https://rest.test.{}/test/clip/clip.mp4".format(BLINK_URL)
)
self.assertEqual(camera_attr['temperature'], 70)
self.assertEqual(camera_attr['battery'], 3)
self.assertEqual(camera_attr['temperature'], 68)
self.assertEqual(camera_attr['temperature_c'], 20.0)
self.assertEqual(camera_attr['battery'], 50)
self.assertEqual(camera_attr['notifications'], 2)
self.assertEqual(camera_attr['network_id'], '0000')
self.assertEqual(camera_attr['motion_enabled'], True)
self.assertEqual(camera_attr['wifi_strength'], -30)
+9 -1
View File
@@ -61,9 +61,17 @@ class TestBlinkFunctions(unittest.TestCase):
'/test/thumb')
@mock.patch('blinkpy.blinkpy._request')
def test_get_cameras(self, req):
@mock.patch('blinkpy.blinkpy.Blink._video_request')
def test_get_cameras(self, vid_req, req):
"""Test camera extraction."""
req.return_value = {'devices': [self.config]}
vid_req.return_value = [
{
'camera_name': 'foobar',
'address': '/new.mp4',
'thumbnail': '/new'
}
]
self.blink.get_cameras()
self.assertTrue('foobar' in self.blink.cameras)
+1 -1
View File
@@ -8,7 +8,7 @@ any communication related errors at startup.
import unittest
from unittest import mock
from blinkpy import blinkpy as blinkpy
from blinkpy import blinkpy
import tests.mock_responses as mresp
USERNAME = 'foobar'