From d357215e0e44c86476e1e30a6f334185462bf38f Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Mon, 21 May 2018 00:16:24 -0400 Subject: [PATCH 1/2] 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 --- blinkpy/blinkpy.py | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index f8b73965..6ed5fc93 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -107,13 +107,17 @@ def __init__(self, config, blink): 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() @property def attributes(self): @@ -128,6 +132,8 @@ def attributes(self): 'thumbnail': self.thumbnail, 'video': self.clip, 'notifications': self.notifications, + 'motion_detected': self.motion_detected, + 'wifi_strength': self.wifi_strength, 'network_id': self.blink.network_id } return attributes @@ -142,13 +148,18 @@ def armed(self): """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 @@ -175,10 +186,24 @@ def update(self, values): self.urls.base_url, values['thumbnail']) self.clip = "{}{}".format( self.urls.base_url, values['video']) - self.temperature = values['temp'] - self.battery = values['battery'] + self._battery_string = values['battery'] self.notifications = values['notifications'] + try: + cfg = self.blink._camera_config_request(self.id) + self.camera_config = cfg + except: + _LOGGER.warning("Could not get config for {} with id {}".format( + self.name, self.id)) + try: + self.battery_voltage = cfg['camera'][0]['battery_voltage'] + self.motion_detected = 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 {}".format( + self.name)) + def image_refresh(self): """Refresh current thumbnail.""" url = self.urls.home_url @@ -365,6 +390,7 @@ def get_cameras(self): 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 +523,11 @@ def _status_request(self): 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') From 91a877da79ad71a30ceefa55570b9b3aa1f695cf Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Mon, 21 May 2018 10:54:32 -0400 Subject: [PATCH 2/2] Fix tests, updated changes in changelog --- CHANGES.rst | 3 +++ blinkpy/blinkpy.py | 19 +++++++++------- tests/test_blink_cameras.py | 41 ++++++++++++++++++++++++++--------- tests/test_blink_functions.py | 10 ++++++++- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5620cd12..d609625f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ A list of changes between each release 0.8.0.dev (Development version) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Added support for battery voltage level +- Added motion detection per camera +- Added fully accessible camera configuration dict 0.7.0 (2018-02-08) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 6ed5fc93..43fd8e93 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -114,7 +114,7 @@ def __init__(self, config, blink): self.image_link = None self.arm_link = None self.region_id = config['region_id'] - self.battery_voltage = -180 + self.battery_voltage = -180 self.motion_detected = None self.wifi_strength = None self.camera_config = dict() @@ -190,19 +190,21 @@ def update(self, values): self.notifications = values['notifications'] try: - cfg = self.blink._camera_config_request(self.id) + cfg = self.blink.camera_config_request(self.id) self.camera_config = cfg - except: - _LOGGER.warning("Could not get config for {} with id {}".format( - self.name, self.id)) + 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_detected = 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 {}".format( - self.name)) + _LOGGER.warning("Problem extracting config for camera %s", + self.name) def image_refresh(self): """Refresh current thumbnail.""" @@ -352,6 +354,7 @@ def get_videos(self, start_page=0, end_page=1): 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'] @@ -524,7 +527,7 @@ def _status_request(self): headers = self._auth_header return _request(self, url=url, headers=headers, reqtype='get') - def _camera_config_request(self, camera_id): + 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, diff --git a/tests/test_blink_cameras.py b/tests/test_blink_cameras.py index 859a2cf8..98b40b9a 100644 --- a/tests/test_blink_cameras.py +++ b/tests/test_blink_cameras.py @@ -15,6 +15,17 @@ 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 @@ def setUp(self): 'notifications': 2, 'region_id': 'test' } + self.blink.urls = blinkpy.BlinkURLHandler('test') self.blink.network_id = '0000' @@ -42,11 +54,13 @@ def tearDown(self): """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 @@ def test_camera_properties(self, mock_get, mock_post): 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,14 @@ def test_camera_properties(self, mock_get, mock_post): 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.battery, 50) self.assertEqual(camera.battery_string, "OK") self.assertEqual(camera.notifications, 2) self.assertEqual(camera.region_id, 'test') + self.assertEqual(camera.motion_detected, True) + self.assertEqual(camera.wifi_strength, -30) + camera_config = self.camera_config camera_config['active'] = 'armed' camera_config['thumbnail'] = '/test2/image' @@ -80,7 +97,6 @@ def test_camera_properties(self, mock_get, mock_post): 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 +109,8 @@ def test_camera_properties(self, mock_get, mock_post): 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 +123,9 @@ def test_camera_case(self): 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 +135,7 @@ def test_camera_attributes(self): 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 +148,9 @@ def test_camera_attributes(self): 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['battery'], 50) self.assertEqual(camera_attr['notifications'], 2) self.assertEqual(camera_attr['network_id'], '0000') + self.assertEqual(camera_attr['motion_detected'], True) + self.assertEqual(camera_attr['wifi_strength'], -30) diff --git a/tests/test_blink_functions.py b/tests/test_blink_functions.py index aa8f24af..97911c7f 100644 --- a/tests/test_blink_functions.py +++ b/tests/test_blink_functions.py @@ -61,9 +61,17 @@ def test_get_videos(self, req): '/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)