Compare commits

...
350 Commits
Author SHA1 Message Date
Kevin FronczakandGitHub 77e200f6a5 Merge pull request #152 from fronzbot/dev
0.12.0
2019-01-31 17:05:03 -08:00
Kevin Fronczak d880998c43 Version bump 2019-01-31 19:58:15 -05:00
Kevin FronczakandGitHub fbd82030bb Merge pull request #151 from fronzbot/fix-log-format
Added log formatter
2019-01-31 16:42:47 -08:00
Kevin Fronczak 162b409c52 Added log formatter 2019-01-31 19:33:29 -05:00
Kevin FronczakandGitHub 8e2d2e81e8 Merge pull request #150 from fronzbot/fix-logging
Add improved logging functionality.
2019-01-31 11:10:15 -08:00
Kevin FronczakandGitHub 5504dfa94e Merge branch 'dev' into fix-logging 2019-01-31 10:58:56 -08:00
Kevin FronczakandGitHub d5f866bd34 Merge pull request #149 from fronzbot/add-download-videos
Add download videos routine
2019-01-31 10:56:08 -08:00
Kevin FronczakandGitHub 02937c0ef5 Added more info to README 2019-01-31 13:41:26 -05:00
Kevin Fronczak ae5d73e197 Fix for lint errors 2019-01-31 13:24:14 -05:00
Kevin Fronczak 15da35a6bc Add improved logging functionality. 2019-01-31 13:18:53 -05:00
Kevin Fronczak 9738f84457 Add download videos routine 2019-01-31 12:56:08 -05:00
Kevin FronczakandGitHub 663a128469 Merge pull request #148 from fronzbot/backup-temp
Set calibrated temperature to default to temperature if endpoint fails
2019-01-30 13:24:21 -08:00
Kevin FronczakandGitHub bc14ab053c Merge branch 'dev' into backup-temp 2019-01-30 13:01:53 -08:00
Kevin FronczakandGitHub 3b897f6dbc Merge pull request #147 from fronzbot/better-logging
Add repeat log handling filter.
2019-01-29 20:34:08 -08:00
Kevin Fronczak 92f908928d Set calibrated temperature to default to temperature if endpoint fails 2019-01-29 23:13:31 -05:00
Kevin Fronczak da537773a7 Add repeat log handling filter.
- Added exc_info=True to some errors for traceback logging
2019-01-29 23:09:28 -05:00
Kevin FronczakandGitHub 778592c6cf Merge pull request #146 from fronzbot/typo
Fix typo in video url
2019-01-28 11:15:52 -08:00
Kevin Fronczak 0e33457ed0 Fix typo in video url 2019-01-28 14:10:13 -05:00
Kevin FronczakandGitHub abcf2ae1a1 Merge pull request #145 from fronzbot/dont-poop-on-failures
Dont poop on failures
2019-01-26 18:06:50 -08:00
Kevin Fronczak 28fb53029c Fixed recurssion error by adding is_retry property to get_auth_token 2019-01-26 20:56:10 -05:00
Kevin Fronczak d809cbcb3d Stashing commit. Getting RecursonError on failed login. 2019-01-26 20:14:50 -05:00
Kevin Fronczak 8dea1a4312 Added try/excepts around methods that would bomb setup if api fails 2019-01-26 19:41:01 -05:00
Kevin FronczakandGitHub 45f7dd6cbb Merge pull request #144 from fronzbot/api-doc-strings
Api doc strings
2019-01-26 13:41:21 -08:00
Kevin Fronczak ac28dddd8b Finished adding docstrings, added version attr to Blink class 2019-01-26 16:20:48 -05:00
Kevin Fronczak aa58383318 Started adding doctrings. 2019-01-26 16:07:59 -05:00
Kevin FronczakandGitHub b0698f246a Merge pull request #143 from fronzbot/fix-video-api
Fix video api to re-enable motion detection
2019-01-26 12:55:32 -08:00
Kevin Fronczak 46c5d5eb3c Added check videos tests
- Moved motion detection setting to happen prior to return to prevent
motion detect sticking upon api failure
2019-01-26 15:40:02 -05:00
Kevin Fronczak 1922b2535c Fix linting errors 2019-01-26 14:45:11 -05:00
Kevin Fronczak 4297290fba Fix broken tests 2019-01-26 14:35:28 -05:00
Kevin Fronczak 1daf5d22af Fix lint, remove some camera tests 2019-01-25 20:51:14 -05:00
Kevin Fronczak 8fd989e991 Motion detection now working, removed get_videos method 2019-01-25 20:44:31 -05:00
Kevin Fronczak 559cc4a5ac First cut at fixing video api 2019-01-25 13:51:07 -05:00
Kevin FronczakandGitHub 66a66c9bd3 Dev version bump 2019-01-23 11:05:34 -05:00
Kevin FronczakandGitHub f3a8036649 Merge pull request #141 from fronzbot/dev
0.11.2
2019-01-23 08:05:05 -08:00
Kevin FronczakandGitHub 5c6e4364e8 Update CHANGES.rst 2019-01-23 10:57:18 -05:00
Kevin FronczakandGitHub fd64f41b22 Update constants.py 2019-01-23 10:55:39 -05:00
Kevin FronczakandGitHub 0d7aac3458 Merge pull request #140 from fronzbot/hotfix
Added some error handling for video method due to api changes
2019-01-23 07:54:19 -08:00
Kevin Fronczak 0bebcd8303 Added some error handling for video method due to api changes 2019-01-23 10:48:51 -05:00
Kevin Fronczak ff35c56c9a Dev version bump 2019-01-02 12:24:30 -05:00
Kevin FronczakandGitHub 853ccbce79 Merge pull request #136 from fronzbot/dev
0.11.1
2019-01-02 09:19:08 -08:00
Kevin Fronczak 8153fab887 Version bump 2019-01-02 11:59:27 -05:00
Kevin FronczakandGitHub 216541288e Merge pull request #135 from fronzbot/link-fix
Fixed link formatting
2019-01-02 08:53:08 -08:00
Kevin FronczakandGitHub a855cceb05 Merge pull request #134 from fronzbot/calibrated-temperature
Add calibrated temperature property (fixes #133)
2019-01-02 08:52:56 -08:00
Kevin Fronczak d9991d5acd Fixed link formatting (fixes #132) 2019-01-02 11:45:48 -05:00
Kevin Fronczak 2ee2ae7d4e Add calibrated temperature property (fixes #133) 2019-01-02 11:35:40 -05:00
Kevin FronczakandGitHub 04c2be9087 Add changes from source repo 2018-11-29 15:23:28 -05:00
Kevin Fronczak 34fc9bc527 dev version bump 2018-11-23 20:32:12 -05:00
Kevin FronczakandGitHub 1a24e1896f Merge pull request #129 from fronzbot/dev
0.11.0
2018-11-23 20:27:40 -05:00
Kevin Fronczak 1d67764dc8 version bump 2018-11-23 20:09:46 -05:00
Kevin Fronczak 8f250d204f Fixed docs for real 2018-11-23 20:08:21 -05:00
Kevin Fronczak 643ae8c387 trying again 2018-11-23 19:44:25 -05:00
Kevin Fronczak f9615a2d90 fix docs again 2018-11-23 19:39:23 -05:00
Kevin Fronczak f7a3b1d0ef Update docs 2018-11-23 19:37:38 -05:00
Kevin FronczakandGitHub 94aed43d2e Merge pull request #128 from fronzbot/fix-sync-refresh
Fix broken sync refresh
2018-11-23 19:25:39 -05:00
Kevin Fronczak 90f30deea8 Fix broken sync refresh 2018-11-23 19:20:18 -05:00
Kevin FronczakandGitHub 1bbc71ef17 Merge pull request #125 from fronzbot/multi-sync-support
Support for Multiple Sync Modules
2018-11-23 18:23:03 -05:00
Kevin Fronczak c84a0deb5f Add sync parent to attributes 2018-11-23 18:08:58 -05:00
Kevin Fronczak ece65a9158 Fix linting errors 2018-11-23 17:52:12 -05:00
Kevin Fronczak 5a8af75a5b Added cameras dict to blink to keep track of ALL cameras 2018-11-23 17:47:44 -05:00
Kevin Fronczak 84f987d29c Updated to reflect multi-sync unit support 2018-11-23 16:46:20 -05:00
Kevin Fronczak ed25745deb Added case insesnitive dict, changed sync module name to network name
(to mimic app)
2018-11-23 16:36:17 -05:00
Kevin Fronczak a1fd05735e Initial support for multiple sync modules 2018-11-23 14:22:54 -05:00
Kevin FronczakandGitHub 5a9f8f6dfc Merge pull request #124 from fronzbot/improve-contribution-doc
Improve contribution instructions
2018-11-21 10:08:31 -05:00
Kevin FronczakandGitHub 2639055372 Merge pull request #123 from fronzbot/update-readme
Update README.rst
2018-11-21 10:03:18 -05:00
Kevin FronczakandGitHub c53b2bd777 Improve contribution instructions 2018-11-21 10:02:12 -05:00
Kevin FronczakandGitHub db29bae140 Broke out blink home assistant links 2018-11-21 09:38:23 -05:00
Kevin FronczakandGitHub 52f1fde47a Bad link format 2018-11-21 09:37:27 -05:00
Kevin FronczakandGitHub e705fbd403 Update README.rst 2018-11-21 09:36:36 -05:00
Kevin FronczakandGitHub 2d437b4462 Merge pull request #122 from fronzbot/dev-bump
Dev version bump
2018-11-20 21:53:09 -05:00
Kevin FronczakandGitHub ae946dbb53 Merge pull request #121 from fronzbot/dev
0.10.3
2018-11-20 21:47:42 -05:00
Kevin Fronczak ea2b22dbf0 Dev version bump 2018-11-20 21:46:38 -05:00
Kevin Fronczak 7ee57b2c6b Version bump 2018-11-20 21:40:18 -05:00
Kevin FronczakandGitHub ccd393a533 Merge pull request #120 from fronzbot/fix-bad-logging
Logging fixes
2018-11-20 21:26:32 -05:00
Kevin Fronczak 1b571c7d4b Fix logging string format 2018-11-20 21:21:42 -05:00
Kevin Fronczak 4b91fcb615 Improved video page debug logging 2018-11-20 21:20:35 -05:00
Kevin Fronczak 1e0d069dcd Fix linting errors 2018-11-20 21:16:41 -05:00
Kevin Fronczak 92088779e0 Added debug logging 2018-11-20 21:15:01 -05:00
Kevin Fronczak a6e02433b6 Fix #113 2018-11-20 21:12:12 -05:00
Kevin FronczakandGitHub 1aa5dc1c71 Merge pull request #119 from md-reddevil/dev
Use network_status instead of homescreen
2018-11-19 12:49:47 -05:00
Kevin Fronczak 3c78923853 Fix linting error (line was too long) 2018-11-19 12:46:01 -05:00
Kevin Fronczak d17183a152 Fixed broken sync module test 2018-11-19 12:42:54 -05:00
2e5e343913 Update blinkpy/sync_module.py fix linting
Co-Authored-By: md-reddevil <16707502+md-reddevil@users.noreply.github.com>
2018-11-18 14:06:21 +02:00
md-reddevilandGitHub cec6035d23 fix tests after API changes 2018-11-18 14:01:22 +02:00
md-reddevil bdc760608e fix indentation 2018-11-17 18:20:28 +02:00
md-reddevil 4f41899338 fix indentation 2018-11-17 18:18:57 +02:00
md-reddevil 557f21b637 Use network_status instead of homescreen
Use network_status to check if System is armed or disarmed
2018-11-17 18:12:26 +02:00
Kevin FronczakandGitHub 4241b50002 Merge pull request #118 from md-reddevil/dev
API: Fix Command Status method add a new method to manage correctly the System status
2018-11-14 09:01:10 -05:00
md-reddevilandGitHub 8b0507f1a7 fix indentation for method request_command_status 2018-11-14 15:57:46 +02:00
md-reddevilandGitHub d62429189e Fix formatting issue in api.py 2018-11-14 11:04:40 +02:00
md-reddevilandGitHub c83b173a17 update description of the networks method 2018-11-14 09:55:17 +02:00
md-reddevilandGitHub 9c1974c227 Add method to request the Network/System status
Home screen method returns just the information about last added System(network). The method "request_network_status", should replace the homescreen method in order to obtain all the information about a system.
2018-11-14 09:39:44 +02:00
md-reddevilandGitHub c621ba3150 fix check command status 2018-11-14 09:34:25 +02:00
Kevin FronczakandGitHub 7710e3f972 Errant whitespace 2018-10-30 21:08:43 -04:00
Kevin Fronczak a4f65c1102 dev version bump 2018-10-30 20:24:39 -04:00
Kevin FronczakandGitHub 80c6444c2e Merge pull request #112 from fronzbot/dev
0.10.2
2018-10-30 20:22:08 -04:00
Kevin Fronczak 2022b80f9d Version bump 2018-10-30 20:16:37 -04:00
Kevin FronczakandGitHub 7e9900c5f5 Merge pull request #111 from fronzbot/fix-typo
Reformatted log message when multiple networks detected.
2018-10-30 20:07:03 -04:00
Kevin Fronczak 132df9e8c4 Fixed wrong string format 2018-10-30 19:58:13 -04:00
Kevin Fronczak 07e3549306 Reformatted log message when multiple networks detected. 2018-10-30 19:48:08 -04:00
Kevin FronczakandGitHub e0fa0a0cde Merge pull request #110 from fronzbot/upgrade-requests
Upgraded requests due to security issue prior to 2.20.0
2018-10-30 19:47:38 -04:00
Kevin Fronczak 31771ca87d Upgraded requests due to security issue prior to 2.20.0 2018-10-30 19:42:35 -04:00
Kevin FronczakandGitHub 1b898398a7 Fix rst lint error 2018-10-26 11:09:43 -04:00
Kevin FronczakandGitHub d8fcb679cd Add python versioning badge 2018-10-26 11:05:00 -04:00
Kevin Fronczak 72a5ccb769 dev version bump 2018-10-18 13:20:34 -04:00
Kevin FronczakandGitHub e872fc9ce5 Merge pull request #105 from fronzbot/dev
0.10.1
2018-10-18 13:19:49 -04:00
Kevin Fronczak 51961474b6 Version bump 2018-10-18 13:10:42 -04:00
Kevin FronczakandGitHub 8be35dab30 Merge pull request #104 from fronzbot/fix-warning
Changed 'warning' to 'info' in camera
2018-10-18 13:05:17 -04:00
Kevin FronczakandGitHub 0ab17aede8 Merge pull request #103 from fronzbot/reauth-bug-fix
Reauth bug fix
2018-10-18 13:01:52 -04:00
Kevin Fronczak 6db8a151f8 Changed 'warning' to 'info' and added an error if trying to save video
that doesn't exist
2018-10-18 12:58:43 -04:00
Kevin Fronczak 1a12eb367f Added additional re-auth attempt in try/except 2018-10-18 12:49:18 -04:00
Kevin Fronczak f61f68a5c2 Fix reauthorization bug 2018-10-18 12:23:23 -04:00
Kevin FronczakandGitHub 0781d79ca7 Update bug_report.md 2018-10-17 11:18:29 -04:00
Kevin FronczakandGitHub 4ffdf499c9 Update PULL_REQUEST_TEMPLATE.md 2018-10-17 10:28:40 -04:00
Kevin FronczakandGitHub f8f318c203 Update bug_report.md 2018-10-17 10:26:28 -04:00
Kevin Fronczak 59540818f9 Add feature request template 2018-10-17 10:24:52 -04:00
Kevin Fronczak b2b531ad3c Add issue template 2018-10-17 10:22:59 -04:00
Kevin FronczakandGitHub 4e6957a772 Merge pull request #100 from fronzbot/dev
Release 0.10.0
2018-10-16 17:43:21 -04:00
Kevin FronczakandGitHub e747fb1010 Merge branch 'master' into dev 2018-10-16 17:28:34 -04:00
Kevin FronczakandGitHub 9d110cdd72 Merge pull request #99 from fronzbot/0.10.0
Version bump
2018-10-16 16:53:51 -04:00
Kevin Fronczak 6fa0eacff3 Fix trailing whitespace 2018-10-16 16:26:47 -04:00
Kevin Fronczak 7ce924f52e Version bump 2018-10-16 16:24:39 -04:00
Kevin FronczakandGitHub 91b3c2a64b Merge pull request #98 from fronzbot/update-travis
Added py38 to tox and travis
2018-10-16 16:19:11 -04:00
Kevin FronczakandGitHub 97579400b1 Merge pull request #97 from fronzbot/multi-network-support
Adds multi-network handling (Thanks @dgomes and @dib0 )
2018-10-16 16:17:48 -04:00
Kevin Fronczak 63b01dde14 Removed condition on py38-dev 2018-10-16 15:43:02 -04:00
Kevin Fronczak d9b996ab02 Added py38 2018-10-16 15:41:12 -04:00
Kevin Fronczak 3af105edf5 Check for 'None' clip before saving to cache 2018-10-16 15:26:15 -04:00
Kevin Fronczak 5057592b0d Fix blink_functions test 2018-10-16 13:52:09 -04:00
Kevin Fronczak 13bb819e07 Added network checking 2018-10-16 13:28:44 -04:00
Kevin Fronczak eefa9252e5 [WIP] Adding in network checking 2018-10-16 12:27:57 -04:00
Kevin FronczakandGitHub c5b84ac357 Merge pull request #96 from fronzbot/update-changes-rst
Add changes from PR #94
2018-10-16 11:42:12 -04:00
Kevin Fronczak 787256ce66 Fixed bad linkwq 2018-10-16 11:28:18 -04:00
Kevin Fronczak 2f1877267f Add changes from PR #94 2018-10-16 11:23:31 -04:00
Kevin FronczakandGitHub 9c34fc9281 Merge pull request #94 from fronzbot/breakout-api-calls
Improve API calls
2018-10-16 11:18:52 -04:00
Kevin Fronczak 1b9bba7c3d Modified tests, added homescreen thumbnail backup Fixes #93 2018-10-15 20:52:05 -04:00
Kevin FronczakandGitHub 123867ecd8 Merge pull request #95 from fronzbot/update-changes
Updated CHANGES to reflect recent releases
2018-10-14 08:54:01 -04:00
Kevin Fronczak 26d900897b Updated CHANGES to reflect recent releases 2018-10-14 00:27:59 -04:00
Kevin Fronczak f7a5b5cc03 Tests now working
- Simplified tests a bit
2018-10-14 00:12:44 -04:00
Kevin Fronczak 7fd822caf2 Removed old api calls, using new api module
- Tests currently broken
- wifi_strength is now a number of bars rather than dBm level
- camera no longer has armed or status attributes
- added serial numbers for sync and cameras
2018-10-13 20:38:01 -04:00
Kevin Fronczak 01c6f0b12b Add all requests to single api file 2018-10-13 16:49:31 -04:00
Kevin FronczakandGitHub 62c89a925e Dev version bump 2018-09-28 15:00:51 -04:00
Kevin FronczakandGitHub 07e1759819 Merge pull request #85 from fronzbot/v0.9.0
V0.9.0
2018-09-27 21:46:04 -04:00
Kevin FronczakandGitHub 8fac6c5bc0 Merge branch 'master' into v0.9.0 2018-09-27 21:42:05 -04:00
Kevin Fronczak bd4307af58 Version bump 2018-09-27 21:33:53 -04:00
Kevin FronczakandGitHub bd6e3bb4e3 Merge pull request #84 from fronzbot/better-docstrings
Added docstrings, removed docs/_build
2018-09-27 16:17:07 -04:00
Kevin Fronczak c2630a43e1 Added docstrings, removed docs/_build 2018-09-27 16:13:19 -04:00
Kevin FronczakandGitHub 2e6fb97cc5 Merge pull request #83 from fronzbot/use-sessions
Use sessions for requests
2018-09-27 15:19:11 -04:00
Kevin Fronczak 674dae4440 Fix lint error 2018-09-27 15:15:25 -04:00
Kevin Fronczak a1082812bb Modified tests to pass with session
- Added session mock
- Removed post and get mocks
- Modified the throttling for events and summary
    - Now they're throttled, but it's overridden during a system refresh
- Store the login url in a protected variable
2018-09-27 15:10:48 -04:00
Kevin Fronczak 9d838fe4eb Added session for requests
- fixed refresh issue throttling summary and events
2018-09-27 12:35:15 -04:00
Kevin FronczakandGitHub fe0aef69e4 Merge pull request #82 from fronzbot/refactor
Refactor blinkpy code
2018-09-27 11:15:17 -04:00
Kevin Fronczak 1b1235210e Split up tests, added cache test 2018-09-27 11:07:31 -04:00
Kevin Fronczak 451d725946 Update functionality in README 2018-09-26 13:02:48 -04:00
Kevin Fronczak 9d5ae9552a Check if first init, if so force cache update 2018-09-26 12:45:29 -04:00
Kevin Fronczak c3c558e934 Fix broken refresh throttle 2018-09-26 12:24:45 -04:00
Kevin Fronczak a570413e66 Fixed broken tests...probably should re-write them 2018-09-26 12:11:41 -04:00
Kevin Fronczak 3e23374b32 Modified requests so cameras only talk to sync 2018-09-26 11:09:51 -04:00
Kevin Fronczak 1c6a5eb6c6 Fixed tests. Need to improve mocking in functions test. 2018-09-25 16:02:59 -04:00
Kevin Fronczak 40e9e4bd6c Fixed camera tests 2018-09-25 14:31:23 -04:00
Kevin Fronczak e1ff851068 First cut of refactoring 2018-09-25 14:16:14 -04:00
Kevin FronczakandGitHub 8a4e507f22 Merge pull request #81 from fronzbot/add-refresh-rate
Add refresh rate and image caching
2018-09-25 12:23:41 -04:00
Kevin Fronczak cd7b5bfabc Fixing travis errors with new python versions 2018-09-25 12:15:19 -04:00
Kevin Fronczak 346882f1ff Remove Python 3.4 from travis, add py38-dev 2018-09-25 12:07:40 -04:00
Kevin Fronczak 5bc2466da2 Add py37 to tox.ini 2018-09-25 12:04:22 -04:00
Kevin Fronczak 672f59ab4e Added image and video caching 2018-09-25 12:03:32 -04:00
Kevin Fronczak ae26a9e371 Added refresh rate throttling 2018-09-25 11:07:40 -04:00
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
Kevin FronczakandGitHub ff47dc4731 Merge pull request #63 from fronzbot/release-0.7.1
v0.7.1
2018-05-09 11:15:56 -04:00
Kevin Fronczak fedea74256 Version bump 2018-05-09 11:08:35 -04:00
Kevin FronczakandGitHub 66a6701a66 Merge pull request #62 from fronzbot/gitlab
Add gitlab support, py3.7 checks.  Also removes py34 tests
2018-05-09 10:46:13 -04:00
Kevin Fronczak 5bdfd6da52 Add gitlab support, py3.7 checks. Also removes py34 tests 2018-05-09 10:29:33 -04:00
Kevin FronczakandGitHub a8f90400b2 Merge pull request #61 from fronzbot/pip-fix
Fixed import error for pip version 10
2018-05-09 10:21:43 -04:00
Kevin Fronczak 3348386a4c Style fixes 2018-05-09 10:05:03 -04:00
Kevin Fronczak 1b53c4a0dd Hound should ignore setup.py 2018-05-09 10:02:58 -04:00
Kevin Fronczak 24158638ef Fix hound 2018-05-09 10:00:13 -04:00
Kevin Fronczak 901cbfc9d1 Fixed import error for pip version 10 2018-05-09 09:49:41 -04:00
Kevin FronczakandGitHub 9f85068a0d Merge pull request #59 from fronzbot/dev-version-bump
dev version bump
2018-02-08 17:02:38 -05:00
Kevin FronczakandGitHub 90b83544f4 Update constants.py 2018-02-08 16:53:03 -05:00
Kevin FronczakandGitHub 1d8e8e53d9 Update CHANGES.rst 2018-02-08 16:52:17 -05:00
Kevin FronczakandGitHub 46a0a433ec Merge pull request #58 from fronzbot/master
Rebase with master
2018-02-08 16:51:05 -05:00
Kevin FronczakandGitHub d38d30471a Update CHANGES.rst 2018-02-08 16:46:21 -05:00
Kevin FronczakandGitHub 5aad8ea641 Merge pull request #57 from fronzbot/dev
v0.7.0
2018-02-08 16:44:37 -05:00
Kevin FronczakandGitHub 74f4bd7763 Version bump 2018-02-08 16:35:37 -05:00
Kevin FronczakandGitHub 51dd69a51f Merge pull request #56 from fronzbot/camera-attributes
Camera attributes
2018-02-08 16:22:14 -05:00
Kevin FronczakandGitHub 950e7d4971 Merge pull request #55 from fronzbot/auth-at-login
Added authentication to login function and deprecated setup_system
2018-02-08 16:18:19 -05:00
Kevin Fronczak 623b694c78 Added attributes property to camera object 2018-02-08 16:16:23 -05:00
Kevin Fronczak 7162ea24cc Added authentication to login function and deprecated setup_system 2018-02-08 15:31:26 -05:00
Kevin FronczakandGitHub 968b77b8bd Merge pull request #53 from fronzbot/logging-support
Added logging
2018-02-08 14:59:46 -05:00
Kevin Fronczak 6e00b126d1 Added logging 2018-02-08 14:49:10 -05:00
Kevin FronczakandGitHub 003ceb0944 Merge pull request #50 from b10m/active_vs_armed
Handle the armed status better
2018-02-03 09:53:43 -05:00
Kevin Fronczak ce6dec50cd Fixed requirement issue causing tox failure
- Updated camera test to check for new status change
- Added updated to CHANGES.rst
2018-02-03 09:50:08 -05:00
B10m 87643ddcab Reflect changes in tests 2018-02-03 01:11:27 +01:00
B10m 017d8aa6c0 Handle the armed status better
Blink allows users to arm/disarm individual cameras. This is pretty useful for
inside and outside cameras, for instance.

The 'armed' information in the JSON however, merely indicates whether the
*system* is armed, not whether the camera itself is armed. This value, which is
the same as blink.arm, does not indicate whether the camera has motion
detection enabled.

In the JSON, the true status information lies in the 'active' key. The value
can be 'armed', 'disarmed' or 'disabled'. This commit reflects that value
in the armed property.
2018-02-03 00:32:20 +01:00
Kevin FronczakandGitHub 87b83465b3 Merge pull request #48 from fronzbot/update-readme
Add doc badge
2018-01-20 23:26:57 -05:00
Kevin Fronczak a1900f0130 Add doc badge 2018-01-20 23:26:03 -05:00
Kevin FronczakandGitHub cf9ecff529 Merge pull request #47 from fronzbot/docs
Updated theme
2018-01-20 23:18:14 -05:00
Kevin Fronczak db3dee81e9 Updated theme 2018-01-20 23:17:06 -05:00
Kevin FronczakandGitHub 798032be08 Merge pull request #46 from fronzbot/docs
Add documentation
2018-01-20 23:01:44 -05:00
Kevin Fronczak d6834dca06 Ignore docs for Hound 2018-01-20 22:57:38 -05:00
Kevin Fronczak 933cd7229a Add documentation 2018-01-20 22:53:02 -05:00
Kevin FronczakandGitHub 8e51a86971 Merge pull request #45 from fronzbot/video-clip-support
Added ability to download recent video clip
2018-01-20 20:22:36 -05:00
Kevin Fronczak 8ab44041eb Added ability to download recent video clip 2018-01-20 20:13:33 -05:00
Kevin Fronczak 79191d757a Changed coveralls badge to dev branch 2018-01-20 20:03:28 -05:00
Kevin FronczakandGitHub 7d01f54d73 Merge pull request #44 from fronzbot/update-api
Update Blink API Calls
2018-01-20 20:00:55 -05:00
Kevin Fronczak 63c0e11cc9 Fix linting error 2018-01-20 19:57:06 -05:00
Kevin Fronczak 7347d03f93 Added case-sensitive test, logged all changes 2018-01-20 19:53:27 -05:00
Kevin Fronczak fffb10d7e6 Refactored unit tests for special functions 2018-01-20 19:41:15 -05:00
Kevin Fronczak 91b3de1444 Refactored requests, expanded video retreival
- Removed blinkpy.last_motion()
- blinkpy.get_summary() changed to blinkpy.summary property
2018-01-20 16:53:38 -05:00
Kevin Fronczak 8340f2f78a WIP Simplified unit tests 2018-01-20 15:38:37 -05:00
Kevin Fronczak e65840d269 Fix lint 2018-01-20 11:43:46 -05:00
Kevin Fronczak c245bc24a6 Added video retrieval 2018-01-20 11:35:01 -05:00
Kevin Fronczak 71ebc3dc8a Updated links to retrieve images 2018-01-20 10:42:14 -05:00
Kevin FronczakandGitHub 7ade321dd3 Merge pull request #43 from fronzbot/update-travis
Update travis to use 3.6 for linting and build
2018-01-20 09:19:45 -05:00
Kevin Fronczak 19c0114eea Update travis to use 3.6 for linting and build 2018-01-20 09:15:09 -05:00
Kevin Fronczak 968c0cf8fc Fixed typos 2018-01-19 14:52:40 -05:00
Kevin Fronczak f8d4874f2f Updated README badges to dev branch, improved setup script 2018-01-19 14:47:12 -05:00
Kevin Fronczak 261d6293dd Fix lint, upgrade pylint to 1.8.1 2018-01-19 14:38:16 -05:00
Kevin Fronczak 988265fd9f Removed API warning 2018-01-19 11:45:08 -05:00
Kevin Fronczak ba03894e94 Refactored code in prep for API overhaul 2018-01-19 11:41:26 -05:00
Kevin FronczakandGitHub 556c28d4b9 Update README.rst 2017-12-29 20:09:34 -05:00
Kevin FronczakandGitHub 9d2a8c6b69 Update README.rst 2017-07-05 13:33:05 -04:00
Kevin FronczakandGitHub 89b517f47e Merge pull request #39 from fronzbot/pylint-upgrade
Upgrade to pylint==1.7.2
2017-07-05 13:31:45 -04:00
Kevin Fronczak 150016f543 Fixed underline too short 2017-07-05 13:24:22 -04:00
Kevin Fronczak 5df599be15 Upgrade to pylint==1.7.2
Also added PyPi badge to readme
2017-07-05 13:16:12 -04:00
Kevin FronczakandGitHub 0fd4b4b274 Merge pull request #37 from fronzbot/case_insensitive_cameras
Added case handling on camera dict
2017-06-18 18:47:41 -04:00
Kevin FronczakandGitHub dd744053f4 Fixed issue link 2017-06-18 18:41:21 -04:00
Kevin Fronczak be68c0d714 Added case handling on camera dict 2017-06-18 18:37:19 -04:00
Kevin FronczakandGitHub 2a4b0d3f4b Merge pull request #33 from fronzbot/fronzbot-patch-1
Update README.rst
2017-05-15 18:24:17 -04:00
Kevin FronczakandGitHub 46dd002e5d Update README.rst 2017-05-15 18:18:59 -04:00
Kevin FronczakandGitHub 04bc0ace3f Update README.rst
Fixed incorrect example code
2017-05-15 18:17:46 -04:00
Kevin FronczakandGitHub fa6cc49343 Merge pull request #32 from fronzbot/rst-linter
Added rst checking
2017-05-13 20:26:12 -04:00
Kevin Fronczak 231d191556 Added rst checking
Added reStructuredText checking so that the README is actually readable when uploaded to PyPi
2017-05-13 20:14:32 -04:00
Kevin FronczakandGitHub b6c8141042 Merge pull request #31 from fronzbot/upgrade-tests
Upgrade tests
2017-05-13 18:34:10 -04:00
Kevin Fronczak ae209a37f7 Pass lint version bumps 2017-05-13 18:27:01 -04:00
Kevin Fronczak 0d07ae1b4c Upgrade tests
-  pylint to 1.7.1
-  pydocstyle to 2.0.0
-  Added bdist_wheel test to ensure packaging working properly
2017-05-13 12:44:36 -04:00
Kevin FronczakandGitHub cfeed4f432 Merge pull request #30 from fronzbot/v0.6.0
v0.6.0
2017-05-12 16:09:45 -04:00
Kevin Fronczak 6ca243c05c Version bump 2017-05-12 16:03:50 -04:00
Kevin FronczakandGitHub cfa2f0f1bc Merge pull request #29 from fronzbot/better-battery-report
Added battery_string property to camera
2017-05-12 15:53:00 -04:00
Kevin Fronczak d4456555b3 Updated README 2017-05-12 15:46:53 -04:00
Kevin Fronczak 01babca0f4 Added battery_string property to camera
Ability to detect if battery is "OK" or "Low"
2017-05-12 15:42:18 -04:00
Kevin FronczakandGitHub 123444ddf6 Merge pull request #27 from fronzbot/reauth-tests
Reauth tests
2017-05-12 15:08:44 -04:00
Kevin Fronczak aeeb894ad9 Added reauth test 2017-05-12 14:46:13 -04:00
Kevin Fronczak d290fe4096 Added test for reauth
Test to check that we throw an exception if we cannot reauth (to ensure we don't get stuck in a loop)
2017-05-12 14:03:35 -04:00
Kevin FronczakandGitHub 565e3e1b9d Merge pull request #24 from tySwift93/development
Add Token Reauthorization
2017-05-12 10:39:27 -04:00
tySwift93andGitHub b23fe8319b Removed a single space to make lint happy once more
PLEASE BE HAPPY
2017-05-10 19:47:22 -04:00
tySwift93andGitHub eb41b4285e Lint is the bane of my existence 2017-05-10 19:42:20 -04:00
tySwift93andGitHub ab684f64ff Lint is killing me 2017-05-10 19:27:12 -04:00
tySwift93andGitHub abea28ef00 Undo my attempt at fixing code coverage 2017-05-10 19:23:26 -04:00
tySwift93andGitHub 8934645c4d One final attempt to get coverage to work...
I'm just hoping something sticks!
2017-05-10 19:20:26 -04:00
tySwift93andGitHub 68d64665a6 Trying to fix test coverage really quickly... 2017-05-10 19:03:39 -04:00
tySwift93andGitHub 5099d0e67e Update the changes made to _request method
Forgot to include 'return'
2017-05-10 18:51:34 -04:00
tySwift93andGitHub 7a3ba3ae03 Minor change that will hopefully trigger Hound 2017-05-10 10:18:27 -04:00
Kevin FronczakandGitHub 80272b1e27 Merge pull request #25 from fronzbot/hound
Create .hound.yml
2017-05-10 09:11:42 -04:00
Kevin FronczakandGitHub 8d807289ae Create .hound.yml 2017-05-10 09:04:56 -04:00
ttrushin cb946209a2 Has it become obvious that I have never used pylint before?...Please work now... 2017-05-09 16:09:44 -04:00
ttrushin 890b58d0ce Another attempt to fix pylint-related issues 2017-05-09 16:01:31 -04:00
ttrushin 6623f2559d Hopefully fixed pylint-related issues 2017-05-09 15:46:18 -04:00
ttrushin b395eba4f3 Fixed misspelling yet again... 2017-05-09 15:28:03 -04:00
ttrushin c13f3d5b77 Fixed tests, fixed misspelling, and fixed a small error in _request method 2017-05-09 15:15:31 -04:00
ttrushin 13c7373917 Added auto-reauthorization (token refresh) when a request fails due to an expired token 2017-05-09 14:47:31 -04:00
Kevin FronczakandGitHub b0cb3dda37 Merge pull request #22 from fronzbot/dev
Merge 0.6.0.dev0 branch
2017-04-15 17:38:36 -04:00
Kevin FronczakandGitHub a0ab6700d8 Update CHANGES.rst 2017-03-21 10:01:24 -04:00
Kevin FronczakandGitHub ebf4cb53b5 Merge pull request #21 from fronzbot/cleanup
Cleanup and Coverage Increase
2017-03-20 15:57:43 -04:00
Kevin Fronczak 9fa2e967dc Removed redundant functions, increased test coverage 2017-03-20 15:53:53 -04:00
Kevin Fronczak f853b410e7 Merge branch 'dev' of github.com:fronzbot/blinkpy into cleanup
Conflicts:
	helpers/constants.py
2017-03-20 14:06:35 -04:00
Kevin Fronczak 874f717bd1 Fixed formatting 2017-03-20 14:05:40 -04:00
Kevin Fronczak 479a41c900 Had to update version due to messing up the module distribution 2017-03-20 14:05:39 -04:00
Kevin Fronczak a0a1e2837c Fixed coveralls badge 2017-03-20 14:05:39 -04:00
Kevin FronczakandGitHub bd783385de Merge pull request #20 from fronzbot/fronzbot-api-formatting
Fixed formatting
2017-03-20 13:37:23 -04:00
Kevin FronczakandGitHub 5f19b3e47b Fixed formatting 2017-03-20 13:22:12 -04:00
Kevin FronczakandGitHub c7fe0a5b5c Merge pull request #19 from fronzbot/constant-fix
Fixed copy-paste error
2017-03-14 15:11:38 -04:00
Kevin FronczakandGitHub 5eb1b663f4 Fixed copy-paste error 2017-03-14 15:11:22 -04:00
Kevin FronczakandGitHub 1e194385b8 Merge pull request #18 from fronzbot/dev-version-bump
Bump version to dev
2017-03-14 15:10:12 -04:00
Kevin FronczakandGitHub 731fb87012 Bump version to dev 2017-03-14 15:09:35 -04:00
Kevin FronczakandGitHub 5e1634dbfa Merge pull request #17 from fronzbot/master
Pulling in updates from master to dev branch
2017-03-14 15:07:56 -04:00
Kevin Fronczak 7d366386da Had to update version due to messing up the module distribution 2017-03-12 22:06:39 -04:00
Kevin Fronczak b4a03778d3 Fixed coveralls badge 2017-03-12 20:04:42 -04:00
Kevin FronczakandGitHub 657924532e Merge pull request #16 from fronzbot/dev
0.5.0
2017-03-12 19:56:02 -04:00
Kevin Fronczak 9099909983 Bumping release to 0.5.0 2017-03-12 19:51:28 -04:00
Kevin FronczakandGitHub 9cf94ceb86 Merge pull request #15 from fronzbot/robust_regions
Added support for rest.piri subdomain
2017-03-12 19:49:06 -04:00
Kevin Fronczak c529f474fe Added support for rest.piri subdomain 2017-03-12 19:45:33 -04:00
Kevin Fronczak e31a5979bb Fixed 2017-03-12 18:45:52 -04:00
Kevin FronczakandGitHub 90ee45349c Merge pull request #14 from fronzbot/better_tests
Better tests, proper region handling
2017-03-12 18:38:33 -04:00
Kevin Fronczak ab7305b7b2 Fixed typo 2017-03-12 18:26:23 -04:00
Kevin Fronczak af0cdb006a Completed test suite. Fixed some minor issues with file writing and regions 2017-03-12 18:21:28 -04:00
Kevin Fronczak 326906f073 Getting there... 2017-03-11 23:21:26 -05:00
Kevin Fronczak d54a6cbb71 Started adding more camera tests, reorganized package 2017-03-11 21:53:58 -05:00
Kevin Fronczak 94dab01e6b Blink tests complete, camera module next 2017-03-11 12:33:11 -05:00
Kevin Fronczak afa7b2259f Fixed travis to run lint instead of pep8 and moved test reqs to own file 2017-03-10 14:33:51 -05:00
Kevin Fronczak dad1aec874 Initial checkin for test_blink_system.py 2017-03-09 22:50:54 -05:00
Kevin Fronczak 14c7322cde Removed leftover merge junk 2017-03-08 23:19:06 -05:00
Kevin Fronczak 7b07146afa Constants and better style testing 2017-03-08 23:11:56 -05:00
Kevin Fronczak 296c21fd3f Revert "Added constants and error file to clean stuff up"
This reverts commit 69b7be744a.
2017-03-08 23:09:57 -05:00
Kevin Fronczak 5215d0f298 Revert "More robust style testing... next up is more robust testing"
This reverts commit daa27e4989.
2017-03-08 23:09:41 -05:00
Kevin Fronczak daa27e4989 More robust style testing... next up is more robust testing 2017-03-08 23:03:09 -05:00
Kevin Fronczak 69b7be744a Added constants and error file to clean stuff up 2017-03-08 22:00:28 -05:00
Kevin Fronczak 7fa1353173 Working on updating constants 2017-03-08 21:26:28 -05:00
Kevin Fronczak 3653e9d63d Merge branch 'master' of github.com:fronzbot/blinkpy into patch
Conflicts:
	blinkpy.py
	tests/test_const.py
2017-03-08 21:00:21 -05:00
Kevin Fronczak 0a2e087eaa Working on updating with constants. This commit is broken 2017-03-08 20:57:38 -05:00
Kevin Fronczak 11b683a156 Fixed issue with host name in header, added test to catch this 2017-03-08 20:57:38 -05:00
Kevin Fronczak d387a40154 Changed to wheel and incremented minor version 2017-03-08 20:57:38 -05:00
Kevin Fronczak 0023be40c1 Updated test_const with realistic response and fixed key error in blinkpy 2017-03-08 20:57:38 -05:00
Kevin Fronczak 529db79ea4 Added a thumb dict function 2017-03-08 20:57:38 -05:00
Kevin Fronczak 32a27f2d98 Added image refresh function, increased test coverage 2017-03-08 20:57:38 -05:00
Kevin Fronczak 09f2e29f27 String cast for refresh 2017-03-08 20:57:38 -05:00
Kevin Fronczak fa4a1a4e8c Fixed refresh function 2017-03-08 20:57:38 -05:00
Kevin FronczakandGitHub 8206a49dc7 Update README.rst 2017-03-06 23:39:38 -05:00
Kevin FronczakandGitHub 6ad66f0d14 Update README.rst 2017-03-06 23:36:49 -05:00
Kevin FronczakandGitHub 6399f43677 Update README.rst 2017-03-06 23:36:29 -05:00
Kevin FronczakandGitHub 3a5c85e6dd Update .travis.yml 2017-03-06 23:32:08 -05:00
Kevin FronczakandGitHub ac440d814e Added coverage badge 2017-03-06 23:29:09 -05:00
Kevin FronczakandGitHub 270f2f8de4 Create .travis.yml 2017-03-06 23:28:00 -05:00
Kevin FronczakandGitHub 8ad03fd30b Merge pull request #9 from fronzbot/patch
Fixed issue with host name in header, added test to catch this
2017-03-06 23:21:38 -05:00
Kevin Fronczak 10837e3841 Fixed issue with host name in header, added test to catch this 2017-03-06 23:20:46 -05:00
51 changed files with 3097 additions and 924 deletions
+5
View File
@@ -0,0 +1,5 @@
[run]
omit =
helpers/*
tests/*
setup.py
+29
View File
@@ -0,0 +1,29 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Home Assistant version (if applicable):** <HA Version Number>
**`blinkpy` version (not needed if filling out Home Assistant version):**
**Log Output/Additional Information**
If using home-assistant, please paste the output of the log showing your error below. If not, please include any additional useful information.
```
PASTE LOG OUTPUT HERE
```
+14
View File
@@ -0,0 +1,14 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context about the feature request (such as API endpoint responses, etc).
+9
View File
@@ -0,0 +1,9 @@
## Description:
**Related issue (if applicable):** fixes #<blinkpy issue number goes here>
## Checklist:
- [ ] Local tests with `tox` run successfully **PR cannot be meged unless tests pass**
- [ ] Changes tested locally to ensure platform still works as intended
- [ ] Tests added to verify new code works
+4 -2
View File
@@ -1,3 +1,4 @@
.pytest_cache/*
.cache/*
.tox/*
__pycache__/*
@@ -6,6 +7,7 @@ htmlcov/*
*.pyc
*.egg*/*
dist/*
MANIFEST
README
.sh
build/*
docs/_build
*.log
+25
View File
@@ -0,0 +1,25 @@
image: python
stages:
- test
before_script:
- curl -O https://bootstrap.pypa.io/get-pip.py
- python get-pip.py
- pip install tox
python35:
image: python:3.5
stage: test
script: tox -e py35
python36:
image: python:3.6
stage: test
script: tox -e py36
lint:
image: python:3.6
stage: test
script: tox -e lint
+7
View File
@@ -0,0 +1,7 @@
python:
enabled: true
ignore_file: setup.py
ignored_directories:
- docs
fail_on_violations: true
+23
View File
@@ -0,0 +1,23 @@
sudo: required
matrix:
fast_finish: true
include:
- python: "3.6"
env: TOXENV=lint
- python: "3.5.3"
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
- python: "3.6"
env: TOXENV=build
- python: "3.7"
env: TOXENV=py37
dist: xenial
- python: "3.8-dev"
env: TOXENV=py38
dist: xenial
install: pip install -U tox coveralls
language: python
script: tox
after_success: coveralls
+289
View File
@@ -0,0 +1,289 @@
# BlinkMonitorProtocol
Unofficial documentation for the Client API of the Blink Wire-Free HD Home Monitoring &amp; Alert System.
Copied from https://github.com/MattTW/BlinkMonitorProtocol
I am not affiliated with the company in any way - this documentation is strictly **"AS-IS"**. My goal was to uncover enough to arm and disarm the system programatically so that I can issue those commands in sync with my home alarm system arm/disarm. Just some raw notes at this point but should be enough for creating programmatic APIs. Lots more to be discovered and documented - feel free to contribute!
The Client API is a straightforward REST API using JSON and HTTPS.
## Login
Client login to the Blink Servers.
**Request:**
>curl -H "Host: prod.immedia-semi.com" -H "Content-Type: application/json" --data-binary '{
> "password" : "*your blink password*",
> "client_specifier" : "iPhone 9.2 | 2.2 | 222",
> "email" : "*your blink login/email*"
>}' --compressed https://rest.prod.immedia-semi.com/login
**Response:**
>{"authtoken":{"authtoken":"*an auth token*","message":"auth"},"networks":{"*network id*":{"name":"*name*","onboarded":true}},"region":{"*regioncode for endpoint*":"*region name"}}
**Notes:**
The authtoken value is passed in a header in future calls.
The region code for endpoint is required to form the URL of the REST endpoint for future calls.
Depending on the region you are registered you will need to change the REST endpoints below:
- from `https://rest.prod.immedia-semi.com`
- to `https://rest.prde.immedia-semi.com` if e.g. your device is registered in Germany
Please note that at this moment it seems that all regions are not implemented equally: not all endpoints are available in all regions
## Networks
Obtain information about the Blink networks defined for the logged in user.
**Request:**
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/networks
**Response:**
JSON response containing information including Network ID and Account ID.
**Notes:**
Network ID is needed to issue arm/disarm calls
## Sync Modules
Obtain information about the Blink Sync Modules on the given network.
**Request:**
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/network/*network_id_from_networks_call*/syncmodules
**Response:**
JSON response containing information about the known state of the Sync module, most notably if it is online
**Notes:**
Probably not strictly needed but checking result can verify that the sync module is online and will respond to requests to arm/disarm, etc.
## Arm
Arm the given network (start recording/reporting motion events)
**Request:**
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --data-binary --compressed https://rest.prod.immedia-semi.com/network/*network_id_from_networks_call*/arm
**Response:**
JSON response containing information about the arm command request, including the command/request ID
**Notes:**
When this call returns, it does not mean the arm request is complete, the client must gather the request ID from the response and poll for the status of the command.
## Disarm
Disarm the given network (stop recording/reporting motion events)
**Request:**
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --data-binary --compressed https://rest.prod.immedia-semi.com/network/*network_id_from_networks_call*/disarm
**Response:**
JSON response containing information about the disarm command request, including the command/request ID
**Notes:**
When this call returns, it does not mean the disarm request is complete, the client must gather the request ID from the response and poll for the status of the command.
## Command Status
Get status info on the given command
**Request:**
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/network/*network_id*/command/*command_id*
**Response:**
JSON response containing state information of the given command, most notably whether it has completed and was successful.
**Notes:**
After an arm/disarm command, the client appears to poll this URL every second or so until the response indicates the command is complete.
**Known Commands:**
lv_relay, arm, disarm, thumbnail, clip
## Home Screen
Return information displayed on the home screen of the mobile client
**Request:**
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/homescreen
**Response:**
JSON response containing information that the mobile client displays on the home page, including: status, armed state, links to thumbnails for each camera, etc.
**Notes:**
Not necessary to as part of issuing arm/disarm commands, but contains good summary info.
## Events, thumbnails & video captures
**Request**
Get events for a given network (sync module) -- Need network ID from home
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/events/network/*network__id*
**Response**
A json list of evets incluing URL's. Replace the "mp4" with "jpg" extension to get the thumbnail of each clip
**Request**
Get a video clip from the events list
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed **video url from events list.mp4** > video.mp4
**Response**
The mp4 video
**Request**
Get a thumbnail from the events list
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed **video url from events list.jpg** > video_thumb.jpg
**Response**
The jpg bytes.
**Notes**
Note that you replace the 'mp4' with a 'jpg' to get the thumbnail
**Request**
Captures a new thumbnail for a camera
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --data-binary --compressed https://rest.prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/thumbnail
**Response**
Command information.
**Request**
Captures a new video for a camera
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --data-binary --compressed https://rest.prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/clip
**Response**
Command information.
## Video Information
**Request**
Get the total number of videos in the system
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/api/v2/videos/count
**Response**
JSON response containing the total video count.
**Request**
Gets a paginated set of video information
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/api/v2/videos/page/0
**Response**
JSON response containing a set of video information, including: camera name, creation time, thumbnail URI, size, length
**Request**
Gets information for a specific video by ID
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/api/v2/video/*video_id*
**Response**
JSON response containing video information
**Request**
Gets a list of unwatched videos
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/api/v2/videos/unwatched
**Response**
JSON response containing unwatched video information
**Request**
Deletes a video
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --data-binary --compressed https://rest.prod.immedia-semi.com/api/v2/video/*video_id*/delete
**Response**
Unknown - not tested
**Request**
Deletes all videos
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --data-binary --compressed https://rest.prod.immedia-semi.com/api/v2/videos/deleteall
**Response**
Unknown - not tested
## Cameras
**Request**
Gets a list of cameras
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/network/*network_id*/cameras
**Response**
JSON response containing camera information
**Request**
Gets information for one camera
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/network/*network_id*/camera/*camera_id*
**Response**
JSON response containing camera information
**Request**
Gets camera sensor information
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/signals
**Response**
JSON response containing camera sensor information, such as wifi strength, temperature, and battery level
**Request**
Enables motion detection for one camera
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: $auth_token" --data-binary --compressed https://rest.prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/enable
**Response**
JSON response containing camera information
**Request**
Disables motion detection for one camera
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: $auth_token" --data-binary --compressed https://rest.prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/disable
**Response**
JSON response containing camera information
*Note*: enabling or disabling motion detection is independent of arming or disarming the system. No motion detection or video recording will take place unless the system is armed.
## Miscellaneous
**Request**
Gets information about devices that have connected to the blink service
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/account/clients
**Response**
JSON response containing client information, including: type, name, connection time, user ID
**Request**
Gets information about supported regions
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/regions
**Response**
JSON response containing region information
**Request**
Gets information about system health
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/health
**Response**
"all ports tested are open"
**Request**
Gets information about programs
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://rest.prod.immedia-semi.com/api/v1/networks/*network_id*/programs
**Response**
Unknown.
+153
View File
@@ -0,0 +1,153 @@
Changelog
-----------
A list of changes between each release
0.11.2 (2019-01-23)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Hotfix to prevent platform from stalling due to API change
- Motion detection and video recovery broken until new API endpoint discovered
0.11.1 (2019-01-02)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed incorrect backup login url
- Added calibrated temperature property for cameras
0.11.0 (2018-11-23)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Added support for multiple sync modules
0.10.3 (2018-11-18)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Use networks endpoint rather than homecreen to retrieve arm/disarm status (`@md-reddevil <https://github.com/fronzbot/blinkpy/pull/119>`__)
- Fix incorrect command status endpoint (`@md-reddevil <https://github.com/fronzbot/blinkpy/pull/118>`__)
- Add extra debug logging
- Remove error prior to re-authorization (only log error when re-auth failed)
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'
0.10.1 (2018-10-18)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fix re-authorization bug (fixes `#101 <https://github.com/fronzbot/blinkpy/issues/#101>`__)
- Log an error if saving video that doesn't exist
0.10.0 (2018-10-16)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Moved all API calls to own module for easier maintainability
- Added network ids to sync module and cameras to allow for multi-network use
- Removed dependency on video existance prior to camera setup (fixes `#93 <https://github.com/fronzbot/blinkpy/issues/#93>`__)
- Camera wifi_strength now reported in wifi "bars" rather than dBm due to API endpoint change
- Use homescreen thumbnail as fallback in case it's not in the camera endpoint
- Removed "armed" and "status" attributes from camera (status of camera only reported by "motion_enabled" now)
- Added serial number attributes to sync module and cameras
- Check network_id from login response and verify that network is onboarded (fixes `#90 <https://github.com/fronzbot/#90>`__)
- Check if retrieved clip is "None" prior to storing in cache
0.9.0 (2018-09-27)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Complete code refactoring to enable future multi-sync module support
- Add image and video caching to the cameras
- Add internal throttling of system refresh
- Use session for http requests
**Breaking change:**
- Cameras now accessed through sync module Blink.sync.cameras
0.8.1 (2018-09-24)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Update requirements_test.txt
- Update linter versions
- Fix pylint warnings
- Remove object from class declarations
- Remove useless returns from functions
- Fix pylint errors
- change if comparison to fix (consider-using-in)
- Disabled no else-if-return check
- Fix useless-import-alias
- Disable no-else-return
- Fix motion detection
- Use an array of recent video clips to determine if motion has been detected.
- Reset the value every system refresh
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>`__)
0.7.0 (2018-02-08)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed style errors for bumped pydocstring and pylint versions
- Changed Blink.cameras dictionary to be case-insensitive (fixes `#35 <https://github.com/fronzbot/blinkpy/issues/35>`__)
- Changed api endpoint for video extraction (fixes `#35 <https://github.com/fronzbot/blinkpy/issues/35>`__ and `#41 <https://github.com/fronzbot/blinkpy/issues/41>`__)
- Removed last_motion() function from Blink class
- Refactored code for better organization
- Moved some request calls out of @property methods (enables future CLI support)
- Renamed get_summary() method to summary and changed to @property
- Added ability to download most recent video clip
- Improved camera arm/disarm handling (`@b10m <https://github.com/fronzbot/blinkpy/pull/50>`__)
- Added authentication to ``login()`` function and deprecated ``setup_system()`` in favor of ``start()``
- Added ``attributes`` dictionary to camera object
0.6.0 (2017-05-12)
^^^^^^^^^^^^^^^^^^
- Removed redundent properties that only called hidden variables
- Revised request wrapper function to be more intelligent
- Added tests to ensure exceptions are caught and handled (100% coverage!)
- Added auto-reauthorization (token refresh) when a request fails due to an expired token (`@tySwift93 <https://github.com/fronzbot/blinkpy/pull/24>`__)
- Added battery level string to reduce confusion with the way Blink reports battery level as integer from 0 to 3
0.5.2 (2017-03-12)
^^^^^^^^^^^^^^^^^^
- Fixed packaging mishap, same as 0.5.0 otherwise
0.5.0 (2017-03-12)
^^^^^^^^^^^^^^^^^^
- Fixed region handling problem
- Added rest.piri subdomain as a backup if region can't be found
- Improved the file writing function
- Large test coverage increase
0.4.4 (2017-03-06)
^^^^^^^^^^^^^^^^^^
- Fixed bug where region id was not being set in the header
0.4.3 (2017-03-05)
^^^^^^^^^^^^^^^^^^
- Changed to bdist_wheel release
0.4.2 (2017-01-28)
^^^^^^^^^^^^^^^^^^
- Fixed inability to retrieve motion data due to Key Error
0.4.1 (2017-01-27)
^^^^^^^^^^^^^^^^^^
- Fixed refresh bug (0.3.1 did not actually fix the problem)
- Image refresh routine added (per camera)
- Dictionary of thumbnails per camera added
- Improved test coverage
0.3.1 (2017-01-25)
^^^^^^^^^^^^^^^^^^
- Fixed refresh bug (Key Error)
0.3.0 (2017-01-25)
^^^^^^^^^^^^^^^^^^
- Added device id to camera lookup table
- Added image to file method
0.2.0 (2017-01-21)
^^^^^^^^^^^^^^^^^^
- Initial release of blinkpy
+89
View File
@@ -0,0 +1,89 @@
# Contributing to blinkpy
Everyone is welcome to contribute to blinkpy! The process to get started is described below.
## Fork the Repository
You can do this right in github: just click the 'fork' button at the top right.
## Start Developing
1. Setup Local Repository
```shell
$ git clone https://github.com/<YOUR_GIT_USERNAME>/blinkpy.git
$ cd blinkpy
$ git remote add upstream https://github.com/fronzbot/blinkpy.git
```
2. Create a Local Branch
First, you will want to create a new branch to hold your changes:
``git checkout -b <your-branch-name>``
3. Make changes
Now you can make changes to your code. It is worthwhile to test your code as you progress (see the **Testing** section)
4. Commit Your Changes
To commit changes to your branch, simply add the files you want and the commit them to the branch. After that, you can push to your fork on GitHub:
```shell
$ git add .
$ git commit -m "Put your commit text here. Please be concise, but descriptive."
$ git push origin HEAD
```
5. Submit your pull request on GitHub
- On GitHub, navigate to the [blinkpy](https://github.com/fronzbot/blinkpy) repository.
- In the "Branch" menu, choose the branch that contains your commits (from your fork).
- To the right of the Branch menu, click New pull request.
- The base branch dropdown menu should read `dev`. Use the compare branch drop-down menu to choose the branch you made your changes in.
- Type a title and complete the provided description for your pull request.
- Click Create pull request.
- More detailed instructions can be found here: [Creating a Pull Request](https://help.github.com/articles/creating-a-pull-request/)
6. Prior to merge approval
Finally, the `blinkpy` repository uses continuous integration tools to run tests prior to merging. If there are any problems, you will see a red 'X' next to your pull request. To see what's wrong, you can find your pull request [here](https://travis- ci.org/fronzbot/blinkpy/pull_requests) and click on the failing test to see the logs. Those logs will indicate, as best as they can, what is causing that test to fail.
## Testing
It is important to test the code to make sure your changes don't break anything major and that they pass PEP8 style conventions.
First, you need to locally install ``tox``
```shell
$ pip3 install tox
```
You can then run all of the tests with the following command:
```shell
$ tox
```
### Tips
If you only want to see if you can pass the local tests, you can run `tox -e py35` (or whatever python version you have installed. Only `py35`, `py36`, and `py37` will be accepted). If you just want to check for style violations, you can run `tox -e lint`. Regardless, when you submit a pull request, your code MUST pass both the unit tests, and the linters.
If you need to change anything in `requirements.txt` for any reason, you'll want to regenerate the virtual envrionments used by `tox` by running with the `-r` flag: `tox -r`
If you want to run a single test (perhaps you only changed a small thing in one file) you can run `tox -e py35 -- tests/<testname>.py -x`. This will run the test `<testname>.py` and stop testing upon the first failure, making it easier to figure out why a particular test might be failing. The test structure mimics the library structure, so if you changed something in `sync_module.py`, the associated test file would be in `test_sync_module.py` (ie. the filename is prepended with `test_`.
# Catching Up With Reality
If your code is taking a while to develop, you may be behind the ``dev`` branch, in which case you need to catch up before creating your pull-request. To do this you can run ``git rebase`` as follows (running this on your local branch):
```shell
$ git fetch upstream dev
$ git rebase upstream/dev
```
If rebase detects conflicts, repeat the following process until all changes have been resolved:
1. ``git status`` shows you the filw with a conflict. You will need to edit that file and resolve the lines between ``<<<< | >>>>`.
2. Add the modified file: ``git add <file>`` or ``git add .``.
3. Continue rebase: ``git rebase --continue``.
4. Repeat until all conflicts resolved.
View File
+4
View File
@@ -0,0 +1,4 @@
include README.rst
include LICENSE.md
include API.md
include tests/*.py
+109 -112
View File
@@ -1,9 +1,9 @@
**blinkpy**
============
blinkpy |Build Status| |Coverage Status| |Docs| |PyPi Version| |Python Version|
================================================================================
A Python library for the Blink Camera system
**Disclaimers**
===============
Disclaimer:
~~~~~~~~~~~~~~~
Published under the MIT license - See LICENSE file for more details.
"Blink Wire-Free HS Home Monitoring & Alert Systems" is a trademark owned by Immedia Inc., see www.blinkforhome.com for more information.
@@ -11,118 +11,115 @@ I am in no way affiliated with Blink, nor Immedia Inc.
Original protocol hacking by MattTW : https://github.com/MattTW/BlinkMonitorProtocol
**Installation**
API calls faster than 60 seconds is not recommended as it can overwhelm Blink's servers. Please use this module responsibly.
Installation
================
``pip3 install blinkpy``
**Purpose**
Installing Development Version
==================================
To install the current development version, perform the following steps. Note that the following will create a blinkpy directory in your home area:
.. code:: bash
$ cd ~
$ git clone https://github.com/fronzbot/blinkpy.git
$ cd blinkpy
$ rm -rf build dist
$ python3 setup.py bdist_wheel
$ pip3 install --upgrade dist/*.whl
If you'd like to contribute to this library, please read the `contributing instructions <https://github.com/fronzbot/blinkpy/blob/dev/CONTRIBUTING.md>`__.
For more information on how to use this library, please `read the docs <https://blinkpy.readthedocs.io/en/latest/>`__.
Purpose
===========
This library was built with the intention of allowing easy communication with Blink camera systems, specifically so I can add a module into homeassistant https://home-assistant.io
**Usage**
=========
In terms of usage, you just need to instantiate the module with a username and password
::
import blinkpy
blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD')
If you leave out either of those parameters, you need to call the login function which will prompt for your username and password
::
blink.login()
Once the login information is entered, you can run the `setup_system()` function which will attempt to authenticate with Blink servers using your username and password, obtain network ids, and create a list of cameras.
The cameras are of a BlinkCamera class, of which the following parameters can be used (the code below creates a Blink object and iterates through each camera found)
::
blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD')
blink.setup_system()
for camera in blink.cameras:
print(camera.name) # Name of the camera
print(camera.id) # Integer id of the camera (assigned by Blink)
print(camera.armed) # Whether the device is armed/disarmed (ie. detecting motion)
print(camera.clip) # Link to last motion clip captured
print(camera.thumbnail) # Link to current camera thumbnail
print(camera.temperature) # Current camera temperature (not super accurate, but might be useful for someone)
print(camera.battery) # Current battery level... I think the value ranges from 0-3, but not quite sure yet.
print(camera.notifications) # Number of unread notifications (ie. motion alerts that haven't been viewed)
print(camera.motion) # Dictionary containing values for keys ['video', 'image', 'time']
# which correspond to last motion recorded, thumbnail of last motion, and timestamp of last motion
**class Blink**
---------------
The following properties/methods are availiable to the Blink class
**Blink.cameras**
Returns a list of BlinkCamera objects found by the system
**Blink.network_id**
Returns the current network id
**Blink.account_id**
Returns the account id
**Blink.events**
Returns a list of events recorded by blink. This information will contain links to any motion caught by an armed camera.
**Blink.online**
Returns online status of sync module (True = online, False = offline)
**Blink.last_motion()**
Finds last motion information for each camera and stores it in the BlinkCamera.motion field
**Blink.arm**
Set to True to arm, False to disarm. Can be used to see the status of the system as well
**Blink.refresh()**
Forces a refresh of all camera information
**Blink.get_summary()**
Returns json formatted summary of the system
**Blink.get_cameras()**
Finds all cameras in the system and creates them
**Blink.set_links()**
Gives each BlinkCamera object the links needed to find recent images and videos
**Blink.login()**
Prompts user for login information
**Blink.get_auth_token()**
Uses login information to retrieve authorization token from Blink for further communication
**Blink.get_ids()**
Retrieves the network_id and account_id from Blink in order to access video and image pages on their server
**Blink.setup_system()**
A wrapper script that calls:
::
self.get_auth_token()
self.get_ids()
self.get_camers()
self.set_links()
**class BlinkCamera**
---------------------
This class is just a wrapper for each individual camera in order to make communication with individual cameras less clunky. The following properties/methods are availiable (in addition to the ones mentioned earlier)
**BlinkCamera.snap_picture()**
Takes an image with the camera and saves it as the new thumbnail. The Blink.refresh() method should be called after this if you want to store the new thumbnail link
**BlinkCamera.set_motion_detect(enable=True/False)**
Sending True to this function will enable motion detection for the camera. Setting to False will disable motion detection
**BlinkCamera.image_to_file(path)**
This will write the current thumbnail to the location indicated in 'path'
This library was built with the intention of allowing easy communication with Blink camera systems, specifically to support the `Blink component <https://home-assistant.io/components/blink>`__ in `homeassistant <https://home-assistant.io/>`__.
Quick Start
=============
The simplest way to use this package from a terminal is to call ``Blink.start()`` which will prompt for your Blink username and password and then log you in. Alternatively, you can instantiate the Blink class with a username and password, and call ``Blink.start()`` to login and setup without prompt, as shown below. In addition, http requests are throttled internally via use of the ``Blink.refresh_rate`` variable, which can be set at initialization and defaults to 30 seconds.
.. code:: python
from blinkpy import blinkpy
blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD', refresh_rate=30)
blink.start()
If you would like to log in without setting up the cameras or system, you can simply call the ``Blink.login()`` function which will prompt for a username and password and then authenticate with the server. This is useful if you want to avoid use of the ``start()`` function which simply acts as a wrapper for more targeted API methods.
At initialization, you may also set the logging level of the ``blinkpy`` library like so (default is ``INFO``:
.. code:: python
import logging
from blinkpy import blinkpy
blink = blinkpy.Blink(..., loglevel=logging.<LEVEL>)
blink.start()
You can also disable logging of duplicate entries via the ``allow_duplicate_logs`` flag (default is ``True``):
.. code:: python
from blinkpy import blinkpy
blink = blinkpy.Blink(..., allow_duplicate_logs=False)
blink.start()
Cameras are instantiated as individual ``BlinkCamera`` classes within a ``BlinkSyncModule`` instance. All of your sync modules are stored within the ``Blink.sync`` dictionary and can be accessed using the name of the sync module as the key (this is the name of your sync module in the Blink App).
The below code will display cameras and their available attributes:
.. code:: python
from blinkpy import blinkpy
blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD')
blink.start()
for name, camera in blink.cameras.items():
print(name) # Name of the camera
print(camera.attributes) # Print available attributes of camera
The most recent images and videos can be accessed as a bytes-object via internal variables. These can be updated with calls to ``Blink.refresh()`` but will only make a request if motion has been detected or other changes have been found. This can be overridden with the ``force_cache`` flag, but this should be used for debugging only since it overrides the internal request throttling.
.. code:: python
camera = blink.cameras['SOME CAMERA NAME']
blink.refresh(force_cache=True) # force a cache update USE WITH CAUTION
camera.image_from_cache.raw # bytes-like image object (jpg)
camera.video_from_cache.raw # bytes-like video object (mp4)
The ``blinkpy`` api also allows for saving images and videos to a file and snapping a new picture from the camera remotely:
.. code:: python
camera = blink.cameras['SOME CAMERA NAME']
camera.snap_picture() # Take a new picture with the camera
blink.refresh() # Get new information from server
camera.image_to_file('/local/path/for/image.jpg')
camera.video_to_file('/local/path/for/video.mp4')
You can also use this library to download all videos from the server. In order to do this, you must specify a ``path``. You may also specifiy a how far back in time to go to retrieve videos via the ``since=`` variable (a simple string such as ``"2017/09/21"`` is sufficient), as well as how many pages to traverse via the ``page=`` variable. Note that by default, the library will search the first ten pages which is sufficient in most use cases. Additionally, you can specidy one or more cameras via the ``camera=`` property. This can be a single string indicating the name of the camera, or a list of camera names. By default, it is set to the string ``'all'`` to grab videos from all cameras.
Example usage, which downloads all videos recorded since July 4th, 2018 at 9:34am to the ``/home/blink`` directory:
.. code:: python
blink = blinkpy.Blink(username="YOUR USER NAME", password="YOUR PASSWORD")
blink.start()
blink.download_videos('/home/blink', since='2018/07/04 09:34')
.. |Build Status| image:: https://travis-ci.org/fronzbot/blinkpy.svg?branch=dev
:target: https://travis-ci.org/fronzbot/blinkpy
.. |Coverage Status| image:: https://coveralls.io/repos/github/fronzbot/blinkpy/badge.svg?branch=dev
:target: https://coveralls.io/github/fronzbot/blinkpy?branch=dev
.. |PyPi Version| image:: https://img.shields.io/pypi/v/blinkpy.svg
:target: https://pypi.python.org/pypi/blinkpy
.. |Docs| image:: https://readthedocs.org/projects/blinkpy/badge/?version=latest
: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
-1
View File
@@ -1 +0,0 @@
from blinkpy import Blink
-394
View File
@@ -1,394 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
blinkpy by Kevin Fronczak - A Blink camera Python library
https://github.com/fronzbot/blinkpy
Original protocol hacking by MattTW : https://github.com/MattTW/BlinkMonitorProtocol
Published under the MIT license - See LICENSE file for more details.
"Blink Wire-Free HS Home Monitoring & Alert Systems" is a trademark owned by Immedia Inc., see www.blinkforhome.com for more information.
I am in no way affiliated with Blink, nor Immedia Inc.
'''
import logging
import requests
import getpass
import json
BLINK_URL = 'immedia-semi.com'
LOGIN_URL = 'https://prod.' + BLINK_URL + '/login'
BASE_URL = 'https://prod.' + BLINK_URL
DEFAULT_URL = 'prod.' + BLINK_URL
logger = logging.getLogger('blinkpy')
def _request(url, data=None, headers=None, type='get', stream=False, json=True):
"""Wrapper function for request"""
if type is 'post':
response = requests.post(url, headers=headers, data=data).json()
elif type is 'get' and json:
response = requests.get(url, headers=headers, stream=stream).json()
elif type is 'get' and not json:
response = requests.get(url, headers=headers, stream=stream)
else:
raise ValueError("Cannot perform requests of type " + type)
if json and 'message' in response.keys():
raise BlinkAuthenticationException(response['code'], response['message'])
return response
class BlinkException(Exception):
def __init__(self, id, message):
self.id = id
self.message = message
class BlinkAuthenticationException(BlinkException):
pass
class BlinkCamera(object):
"""Class to initialize individual camera"""
def __init__(self, config):
self._ID = str(config['device_id'])
self._NAME = config['name']
self._STATUS = config['armed']
self._THUMB = BASE_URL + config['thumbnail'] + '.jpg'
self._CLIP = BASE_URL + config['thumbnail'] + '.mp4'
self._TEMPERATURE = config['temp']
self._BATTERY = config['battery']
self._NOTIFICATIONS = config['notifications']
self._MOTION = {}
self._HEADER = None
self._IMAGE_LINK = None
self._ARM_LINK = None
self._REGION_ID = config['region_id']
@property
def id(self):
return self._ID
@property
def name(self):
return self._NAME
@name.setter
def name(self, value):
self._NAME = value
@property
def region_id(self):
return self._REGION_ID
@property
def armed(self):
return self._STATUS
@property
def clip(self):
return self._CLIP
@clip.setter
def clip(self, value):
self._CLIP = value
@property
def thumbnail(self):
# RUN THUMB ACQ HERE
return self._THUMB
@thumbnail.setter
def thumbnail(self, value):
self._THUMB = value
@property
def temperature(self):
return self._TEMPERATURE
@temperature.setter
def temperature(self, value):
self._TEMPERATURE = value
@property
def battery(self):
return self._BATTERY
@battery.setter
def battery(self, value):
self._BATTERY = value
@property
def notifications(self):
return self._NOTIFICATIONS
@notifications.setter
def notifications(self, value):
self._NOTIFICATIONS = value
@property
def image_link(self):
return self._IMAGE_LINK
@image_link.setter
def image_link(self, value):
self._IMAGE_LINK = value
@property
def arm_link(self):
return self._ARM_LINK
@arm_link.setter
def arm_link(self, value):
self._ARM_LINK = value
@property
def header(self):
return self._HEADER
@header.setter
def header(self, value):
self._HEADER = value
@property
def motion(self):
return self._MOTION
@motion.setter
def motion(self, value):
"""Sets link to last motion and timestamp"""
self._MOTION = value
def snap_picture(self):
"""Takes a picture with camera to create a new thumbnail"""
_request(self._IMAGE_LINK, headers=self._HEADER, type='post')
def set_motion_detect(self, enable):
"""Sets motion detection"""
url = self._ARM_LINK
if enable:
_request(url + 'enable', headers=self._HEADER, type='post')
else:
_request(url + 'disable', headers=self._HEADER, type='post')
def update(self, values):
self._NAME = values['name']
self._STATUS = values['armed']
self._THUMB = BASE_URL + values['thumbnail'] + '.jpg'
self._CLIP = BASE_URL + values['thumbnail'] + '.mp4'
self._TEMPERATURE = values['temp']
self._BATTERY = values['battery']
self._NOTIFICATIONS = values['notifications']
def image_refresh(self):
url = BASE_URL + '/homescreen'
response = _request(url, headers=self._HEADER, type='get')['devices']
for element in response:
try:
if str(element['device_id']) == self._ID:
self._THUMB = BASE_URL + element['thumbnail'] + '.jpg'
return self._THUMB
except KeyError:
pass
return None
def image_to_file(self, path):
thumb = self.image_refresh()
response = _request(thumb, headers=self._HEADER, stream=True, json=False)
if response.status_code == 200:
with open(path, 'wb') as f:
for chunk in response:
f.write(chunk)
class Blink(object):
"""Class to initialize communication and sync module"""
def __init__(self, username=None, password=None):
"""Constructor for class"""
self._username = username
self._password = password
self._TOKEN = None
self._AUTH_HEADER = None
self._NETWORKID = None
self._ACCOUNTID = None
self._REGION = None
self._REGION_ID = None
self._HOST = None
self._EVENTS = []
self._CAMERAS = {}
self._IDLOOKUP = {}
@property
def cameras(self):
return self._CAMERAS
@property
def camera_thumbs(self):
self.refresh()
data = {}
for name, camera in self._CAMERAS.items():
data[name] = camera.thumbnail
return data
@property
def id_table(self):
return self._IDLOOKUP
@property
def network_id(self):
return self._NETWORKID
@property
def account_id(self):
return self._ACCOUNTID
@property
def region(self):
return self._REGION
@property
def region_id(self):
return self._REGION_ID
@property
def events(self):
"""Gets all events on server"""
url = BASE_URL + '/events/network/' + self._NETWORKID
headers = self._AUTH_HEADER
self._EVENTS = _request(url, headers=headers, type='get')['event']
return self._EVENTS
@property
def online(self):
"""Returns True or False depending on if sync module is online/offline"""
url = BASE_URL + 'network/' + self._NETWORKID + '/syncmodules'
headers = self._AUTH_HEADER
online_dict = {'online': True, 'offline': False}
return online_dict[_request(url, headers=headers, type='get')['syncmodule']['status']]
def last_motion(self):
"""Finds last motion of each camera"""
recent = self.events
for element in recent:
try:
camera_id = str(element['camera_id'])
camera_name = self._IDLOOKUP[camera_id]
camera = self._CAMERAS[camera_name]
if element['type'] == 'motion':
url = BASE_URL + element['video_url']
camera.motion = {'video': url, 'image': url[:-3] + 'jpg', 'time': element['created_at']}
except KeyError:
pass
@property
def arm(self):
"""Returns status of sync module: armed/disarmed"""
return self.get_summary()['network']['armed']
@arm.setter
def arm(self, value):
"""Arms or disarms system. Arms/disarms all if camera not named"""
if value:
value_to_append = 'arm'
else:
value_to_append = 'disarm'
url = BASE_URL + '/network/' + self._NETWORKID + '/' + value_to_append
_request(url, headers=self._AUTH_HEADER, type='post')
def refresh(self):
"""Gets all blink cameras and pulls their most recent status"""
response = self.get_summary()['devices']
for name, camera in self._CAMERAS.items():
for element in response:
try:
if str(element['device_id']) == camera.id:
camera.update(element)
except KeyError:
pass
def get_summary(self):
"""Gets a full summary of device information"""
url = BASE_URL + '/homescreen'
headers = self._AUTH_HEADER
if self._AUTH_HEADER is None:
raise BlinkException(0, "Authentication header incorrect. Are you sure you logged in and received your token?")
return _request(url, headers=headers, type='get')
def get_cameras(self):
"""Finds and creates cameras"""
response = self.get_summary()['devices']
for element in response:
if 'device_type' in element.keys():
if element['device_type'] == 'camera':
# Add region to config
element['region_id'] = self._REGION_ID
device = BlinkCamera(element)
self._CAMERAS[device.name] = device
self._IDLOOKUP[device.id] = device.name
def set_links(self):
"""Sets access links and required headers for each camera in system"""
for name, camera in self._CAMERAS.items():
image_url = BASE_URL + '/network/' + self._NETWORKID + '/camera/' + camera.id + '/thumbnail'
arm_url = BASE_URL + '/network/' + self._NETWORKID + '/camera/' + camera.id + '/'
camera.image_link = image_url
camera.arm_link = arm_url
camera.header = self._AUTH_HEADER
def setup_system(self):
"""Method logs in and sets auth token and network ids for future requests"""
if self._username is None or self._password is None:
raise BlinkAuthenticationException(3, "Cannot authenticate since either password or username has not been set")
self.get_auth_token()
self.get_ids()
self.get_cameras()
self.set_links()
def login(self):
"""Prompts user for username and password"""
self._username = input("Username:")
self._password = getpass.getpass("Password:")
def get_auth_token(self):
"""Retrieves the authentication token from Blink"""
if not isinstance(self._username, str):
raise BlinkAuthenticationException(0, "Username must be a string")
if not isinstance(self._password, str):
raise BlinkAuthenticationException(0, "Password must be a string")
headers = {'Host': DEFAULT_URL,
'Content-Type': 'application/json'
}
data = json.dumps({
"email": self._username,
"password": self._password,
"client_specifier": "iPhone 9.2 | 2.2 | 222"
})
response = _request(LOGIN_URL, headers=headers, data=data, type='post')
self._TOKEN = response['authtoken']['authtoken']
(self._REGION_ID, self._REGION), = response['region'].items()
self._HOST = self._REGION + '.' + BLINK_URL
self._AUTH_HEADER = {'Host': self._HOST,
'TOKEN_AUTH': self._TOKEN
}
def get_ids(self):
"""Sets the network ID and Account ID"""
url = BASE_URL + '/networks'
headers = self._AUTH_HEADER
if self._AUTH_HEADER is None:
raise BlinkException(0, "Authentication header incorrect. Are you sure you logged in and received your token?")
response = _request(url, headers=headers, type='get')
self._NETWORKID = str(response['networks'][0]['id'])
self._ACCOUNTID = str(response['networks'][0]['account_id'])
+1
View File
@@ -0,0 +1 @@
"""Init file for blinkpy."""
+258
View File
@@ -0,0 +1,258 @@
"""Implements known blink API calls."""
import logging
from json import dumps
import blinkpy.helpers.errors as ERROR
from blinkpy.helpers.util import http_req, get_time, BlinkException
from blinkpy.helpers.constants import DEFAULT_URL
_LOGGER = logging.getLogger(__name__)
def request_login(blink, url, username, password, is_retry=False):
"""
Login request.
:param blink: Blink instance.
:param url: Login url.
:param username: Blink username.
:param password: Blink password.
:param is_retry: Is this part of a re-authorization attempt?
"""
headers = {
'Host': DEFAULT_URL,
'Content-Type': 'application/json'
}
data = dumps({
'email': username,
'password': password,
'client_specifier': 'iPhone 9.2 | 2.2 | 222'
})
return http_req(blink, url=url, headers=headers, data=data,
json_resp=False, reqtype='post', is_retry=is_retry)
def request_networks(blink):
"""Request all networks information."""
url = "{}/networks".format(blink.urls.base_url)
return http_get(blink, url)
def request_network_status(blink, network):
"""
Request network information.
:param blink: Blink instance.
:param network: Sync module network id.
"""
url = "{}/network/{}".format(blink.urls.base_url, network)
return http_get(blink, url)
def request_syncmodule(blink, network):
"""
Request sync module info.
:param blink: Blink instance.
:param network: Sync module network id.
"""
url = "{}/network/{}/syncmodules".format(blink.urls.base_url, network)
return http_get(blink, url)
def request_system_arm(blink, network):
"""
Arm system.
:param blink: Blink instance.
:param network: Sync module network id.
"""
url = "{}/network/{}/arm".format(blink.urls.base_url, network)
return http_post(blink, url)
def request_system_disarm(blink, network):
"""
Disarm system.
:param blink: Blink instance.
:param network: Sync module network id.
"""
url = "{}/network/{}/disarm".format(blink.urls.base_url, network)
return http_post(blink, url)
def request_command_status(blink, network, command_id):
"""
Request command status.
:param blink: Blink instance.
:param network: Sync module network id.
:param command_id: Command id to check.
"""
url = "{}/network/{}/command/{}".format(blink.urls.base_url,
network,
command_id)
return http_get(blink, url)
def request_homescreen(blink):
"""Request homescreen info."""
url = "{}/homescreen".format(blink.urls.base_url)
return http_get(blink, url)
def request_sync_events(blink, network):
"""
Request events from sync module.
:param blink: Blink instance.
:param network: Sync module network id.
"""
url = "{}/events/network/{}".format(blink.urls.base_url, network)
return http_get(blink, url)
def request_new_image(blink, network, camera_id):
"""
Request to capture new thumbnail for camera.
:param blink: Blink instance.
:param network: Sync module network id.
:param camera_id: Camera ID of camera to request new image from.
"""
url = "{}/network/{}/camera/{}/thumbnail".format(blink.urls.base_url,
network,
camera_id)
return http_post(blink, url)
def request_new_video(blink, network, camera_id):
"""
Request to capture new video clip.
:param blink: Blink instance.
:param network: Sync module network id.
:param camera_id: Camera ID of camera to request new video from.
"""
url = "{}/network/{}/camera/{}/clip".format(blink.urls.base_url,
network,
camera_id)
return http_post(blink, url)
def request_video_count(blink):
"""Request total video count."""
url = "{}/api/v2/videos/count".format(blink.urls.base_url)
return http_get(blink, url)
def request_videos(blink, time=None, page=0):
"""
Perform a request for videos.
:param blink: Blink instance.
:param time: Get videos since this time. In epoch seconds.
: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)
return http_get(blink, url)
def request_cameras(blink, network):
"""
Request all camera information.
:param Blink: Blink instance.
:param network: Sync module network id.
"""
url = "{}/network/{}/cameras".format(blink.urls.base_url, network)
return http_get(blink, url)
def request_camera_info(blink, network, camera_id):
"""
Request camera info for one camera.
:param blink: Blink instance.
:param network: Sync module network id.
:param camera_id: Camera ID of camera to request info from.
"""
url = "{}/network/{}/camera/{}".format(blink.urls.base_url,
network,
camera_id)
return http_get(blink, url)
def request_camera_sensors(blink, network, camera_id):
"""
Request camera sensor info for one camera.
:param blink: Blink instance.
:param network: Sync module network id.
:param camera_id: Camera ID of camera to request sesnor info from.
"""
url = "{}/network/{}/camera/{}/signals".format(blink.urls.base_url,
network,
camera_id)
return http_get(blink, url)
def request_motion_detection_enable(blink, network, camera_id):
"""
Enable motion detection for a camera.
:param blink: Blink instance.
:param network: Sync module network id.
:param camera_id: Camera ID of camera to enable.
"""
url = "{}/network/{}/camera/{}/enable".format(blink.urls.base_url,
network,
camera_id)
return http_post(blink, url)
def request_motion_detection_disable(blink, network, camera_id):
"""Disable motion detection for a camera.
:param blink: Blink instance.
:param network: Sync module network id.
:param camera_id: Camera ID of camera to disable.
"""
url = "{}/network/{}/camera/{}/disable".format(blink.urls.base_url,
network,
camera_id)
return http_post(blink, url)
def http_get(blink, url, stream=False, json=True, is_retry=False):
"""
Perform an http get request.
:param url: URL to perform get request.
:param stream: Stream response? True/FALSE
:param json: Return json response? TRUE/False
:param is_retry: Is this part of a re-auth attempt?
"""
if blink.auth_header is None:
raise BlinkException(ERROR.AUTH_TOKEN)
_LOGGER.debug("Making GET request to %s", url)
return http_req(blink, url=url, headers=blink.auth_header,
reqtype='get', stream=stream, json_resp=json,
is_retry=is_retry)
def http_post(blink, url, is_retry=False):
"""
Perform an http post request.
:param url: URL to perfom post request.
:param is_retry: Is this part of a re-auth attempt?
"""
if blink.auth_header is None:
raise BlinkException(ERROR.AUTH_TOKEN)
_LOGGER.debug("Making POST request to %s", url)
return http_req(blink, url=url, headers=blink.auth_header,
reqtype='post', is_retry=is_retry)
+303
View File
@@ -0,0 +1,303 @@
# -*- coding: utf-8 -*-
"""
blinkpy is an unofficial api for the Blink security camera system.
repo url: https://github.com/fronzbot/blinkpy
Original protocol hacking by MattTW :
https://github.com/MattTW/BlinkMonitorProtocol
Published under the MIT license - See LICENSE file for more details.
"Blink Wire-Free HS Home Monitoring & Alert Systems" is a trademark
owned by Immedia Inc., see www.blinkforhome.com for more information.
blinkpy is in no way affiliated with Blink, nor Immedia Inc.
"""
import os.path
import time
import getpass
import logging
from shutil import copyfileobj
from requests.structures import CaseInsensitiveDict
from dateutil.parser import parse
from blinkpy import api
from blinkpy.sync_module import BlinkSyncModule
from blinkpy.helpers import errors as ERROR
from blinkpy.helpers import log
from blinkpy.helpers.util import (
create_session, merge_dicts, get_time, BlinkURLHandler,
BlinkAuthenticationException)
from blinkpy.helpers.constants import (
BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL)
from blinkpy.helpers.constants import __version__
REFRESH_RATE = 30
_LOGGER = log.create_logger('blinkpy')
class Blink():
"""Class to initialize communication."""
def __init__(self, username=None, password=None,
refresh_rate=REFRESH_RATE, loglevel=logging.INFO,
allow_duplicate_logs=True):
"""
Initialize Blink system.
:param username: Blink username (usually email address)
:param password: Blink password
:param refresh_rate: Refresh rate of blink information.
Defaults to 15 (seconds)
:param loglevel: Sets the log level for the logger.
:param allow_duplicate_logs: Set to 'False' to only allow a log
message to be logged once.
"""
self._username = username
self._password = password
self._token = None
self._auth_header = None
self._host = None
self.account_id = None
self.network_ids = []
self.urls = None
self.sync = CaseInsensitiveDict({})
self.region = None
self.region_id = None
self.last_refresh = None
self.refresh_rate = refresh_rate
self.session = create_session()
self.networks = []
self.cameras = CaseInsensitiveDict({})
self.video_list = CaseInsensitiveDict({})
self._login_url = LOGIN_URL
self.version = __version__
self.allow_duplicate_logs = allow_duplicate_logs
self.loglevel = loglevel
self._reset_logger()
@property
def auth_header(self):
"""Return the authentication header."""
return self._auth_header
def start(self):
"""
Perform full system setup.
Method logs in and sets auth token, urls, and ids for future requests.
Essentially this is just a wrapper function for ease of use.
"""
self._reset_logger()
if self._username is None or self._password is None:
if not self.login():
return
elif not self.get_auth_token():
return
networks = self.get_ids()
for network_name, network_id in networks.items():
sync_module = BlinkSyncModule(self, network_name, network_id)
sync_module.start()
self.sync[network_name] = sync_module
self.cameras = self.merge_cameras()
def login(self):
"""Prompt user for username and password."""
self._username = input("Username:")
self._password = getpass.getpass("Password:")
if self.get_auth_token():
_LOGGER.info("Login successful!")
return True
_LOGGER.warning("Unable to login with %s.", self._username)
return False
def get_auth_token(self, is_retry=False):
"""Retrieve the authentication token from Blink."""
if not isinstance(self._username, str):
raise BlinkAuthenticationException(ERROR.USERNAME)
if not isinstance(self._password, str):
raise BlinkAuthenticationException(ERROR.PASSWORD)
login_url = LOGIN_URL
response = api.request_login(self,
login_url,
self._username,
self._password,
is_retry=is_retry)
try:
if response.status_code != 200:
_LOGGER.debug("Received response code %s during login.",
response.status_code)
login_url = LOGIN_BACKUP_URL
response = api.request_login(self,
login_url,
self._username,
self._password,
is_retry=is_retry)
response = response.json()
(self.region_id, self.region), = response['region'].items()
except AttributeError:
_LOGGER.error("Login API endpoint failed with response %s",
response,
exc_info=True)
return False
except KeyError:
_LOGGER.warning("Could not extract region info.")
self.region_id = 'piri'
self.region = 'UNKNOWN'
self._host = "{}.{}".format(self.region_id, BLINK_URL)
self._token = response['authtoken']['authtoken']
self.networks = response['networks']
self._auth_header = {'Host': self._host,
'TOKEN_AUTH': self._token}
self.urls = BlinkURLHandler(self.region_id)
self._login_url = login_url
return self._auth_header
def get_ids(self):
"""Set the network ID and Account ID."""
response = api.request_networks(self)
all_networks = []
network_dict = {}
for network, status in self.networks.items():
if status['onboarded']:
all_networks.append('{}'.format(network))
network_dict[status['name']] = network
# For the first onboarded network we find, grab the account id
for resp in response['networks']:
if str(resp['id']) in all_networks:
self.account_id = resp['account_id']
break
self.network_ids = all_networks
return network_dict
def refresh(self, force_cache=False):
"""
Perform a system refresh.
:param force_cache: Force an update of the camera cache
"""
if self.check_if_ok_to_update() or force_cache:
for sync_name, sync_module in self.sync.items():
_LOGGER.debug("Attempting refresh of sync %s", sync_name)
sync_module.refresh(force_cache=force_cache)
if not force_cache:
# Prevents rapid clearing of motion detect property
self.last_refresh = int(time.time())
def check_if_ok_to_update(self):
"""Check if it is ok to perform an http request."""
current_time = int(time.time())
last_refresh = self.last_refresh
if last_refresh is None:
last_refresh = 0
if current_time >= (last_refresh + self.refresh_rate):
return True
return False
def merge_cameras(self):
"""Merge all sync camera dicts into one."""
combined = CaseInsensitiveDict({})
for sync in self.sync:
combined = merge_dicts(combined, self.sync[sync].cameras)
return combined
def download_videos(self, path, since=None, camera='all', stop=10):
"""
Download all videos from server since specified time.
:param path: Path to write files. /path/<cameraname>_<recorddate>.mp4
:param since: Date and time to get videos from.
Ex: "2018/07/28 12:33:00" to retrieve videos since
July 28th 2018 at 12:33:00
: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).
"""
if since is None:
since_epochs = self.last_refresh
else:
parsed_datetime = parse(since, fuzzy=True)
since_epochs = parsed_datetime.timestamp()
formatted_date = get_time(time_to_convert=since_epochs)
_LOGGER.info("Retrieving videos since %s", formatted_date)
if not isinstance(camera, list):
camera = [camera]
for page in range(1, stop):
response = api.request_videos(self, time=since_epochs, page=page)
_LOGGER.debug("Processing page %s", page)
try:
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)
def _parse_downloaded_items(self, result, camera, path):
"""Parse downloaded videos."""
for item in result:
try:
created_at = item['created_at']
camera_name = item['camera_name']
is_deleted = item['deleted']
address = item['address']
except KeyError:
_LOGGER.info("Missing clip information, skipping...")
continue
if camera_name not in camera and 'all' not in camera:
_LOGGER.debug("Skipping videos for %s.", camera_name)
continue
if is_deleted:
_LOGGER.debug("%s: %s is marked as deleted.",
camera_name,
address)
continue
clip_address = "{}{}".format(self.urls.base_url, address)
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
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)
# pylint: disable=no-self-use
def _reset_logger(self):
"""Reset the log handler."""
for handler in _LOGGER.handlers:
_LOGGER.removeHandler(handler)
handler.close()
_LOGGER.setLevel(self.loglevel)
if self.allow_duplicate_logs:
handler = logging.StreamHandler()
handler.setFormatter(log.log_formatter())
else:
handler = log.RepeatLogHandler()
handler.setFormatter(log.log_formatter())
_LOGGER.addHandler(handler)
+208
View File
@@ -0,0 +1,208 @@
"""Defines Blink cameras."""
from shutil import copyfileobj
import logging
from blinkpy import api
_LOGGER = logging.getLogger(__name__)
class BlinkCamera():
"""Class to initialize individual camera."""
def __init__(self, sync):
"""Initiailize BlinkCamera."""
self.sync = sync
self.name = None
self.camera_id = None
self.network_id = None
self.thumbnail = None
self.serial = None
self.motion_enabled = None
self.battery_voltage = None
self.clip = None
self.temperature = None
self.temperature_calibrated = None
self.battery_state = None
self.motion_detected = None
self.wifi_strength = None
self.last_record = None
self._cached_image = None
self._cached_video = None
@property
def attributes(self):
"""Return dictionary of all camera attributes."""
attributes = {
'name': self.name,
'camera_id': self.camera_id,
'serial': self.serial,
'temperature': self.temperature,
'temperature_c': self.temperature_c,
'temperature_calibrated': self.temperature_calibrated,
'battery': self.battery,
'thumbnail': self.thumbnail,
'video': self.clip,
'motion_enabled': self.motion_enabled,
'motion_detected': self.motion_detected,
'wifi_strength': self.wifi_strength,
'network_id': self.sync.network_id,
'sync_module': self.sync.name,
'last_record': self.last_record
}
return attributes
@property
def battery(self):
"""Return battery level as percentage."""
return round(self.battery_voltage / 180 * 100)
@property
def temperature_c(self):
"""Return temperature in celcius."""
return round((self.temperature - 32) / 9.0 * 5.0, 1)
@property
def image_from_cache(self):
"""Return the most recently cached image."""
if self._cached_image:
return self._cached_image
return None
@property
def video_from_cache(self):
"""Return the most recently cached video."""
if self._cached_video:
return self._cached_video
return None
def snap_picture(self):
"""Take a picture with camera to create a new thumbnail."""
return api.request_new_image(self.sync.blink,
self.network_id,
self.camera_id)
def set_motion_detect(self, enable):
"""Set motion detection."""
if enable:
return api.request_motion_detection_enable(self.sync.blink,
self.network_id,
self.camera_id)
return api.request_motion_detection_disable(self.sync.blink,
self.network_id,
self.camera_id)
def update(self, config, force_cache=False):
"""Update camera info."""
self.name = config['name']
self.camera_id = str(config['camera_id'])
self.network_id = str(config['network_id'])
self.serial = config['serial']
self.motion_enabled = config['enabled']
self.battery_voltage = config['battery_voltage']
self.battery_state = config['battery_state']
self.temperature = config['temperature']
self.wifi_strength = config['wifi_strength']
# Retrieve calibrated temperature from special endpoint
resp = api.request_camera_sensors(self.sync.blink,
self.network_id,
self.camera_id)
try:
self.temperature_calibrated = resp['temp']
except KeyError:
self.temperature_calibrated = self.temperature
_LOGGER.warning("Could not retrieve calibrated temperature.")
# Check if thumbnail exists in config, if not try to
# get it from the homescreen info in teh sync module
# otherwise set it to None and log an error
new_thumbnail = None
if config['thumbnail']:
thumb_addr = config['thumbnail']
else:
thumb_addr = self.get_thumb_from_homescreen()
if thumb_addr is not None:
new_thumbnail = "{}{}.jpg".format(self.sync.urls.base_url,
thumb_addr)
try:
self.motion_detected = self.sync.motion[self.name]
except KeyError:
self.motion_detected = False
clip_addr = None
if self.name in self.sync.last_record:
clip_addr = self.sync.last_record[self.name]['clip']
self.last_record = self.sync.last_record[self.name]['time']
self.clip = "{}{}".format(self.sync.urls.base_url,
clip_addr)
# If the thumbnail or clip have changed, update the cache
update_cached_image = False
if new_thumbnail != self.thumbnail or self._cached_image is None:
update_cached_image = True
self.thumbnail = new_thumbnail
update_cached_video = False
if self._cached_video is None or self.motion_detected:
update_cached_video = True
if new_thumbnail is not None and (update_cached_image or force_cache):
self._cached_image = api.http_get(self.sync.blink,
url=self.thumbnail,
stream=True,
json=False)
if clip_addr is not None and (update_cached_video or force_cache):
self._cached_video = api.http_get(self.sync.blink,
url=self.clip,
stream=True,
json=False)
def image_to_file(self, path):
"""
Write image to file.
:param path: Path to write file
"""
_LOGGER.debug("Writing image from %s to %s", self.name, path)
response = self._cached_image
if response.status_code == 200:
with open(path, 'wb') as imgfile:
copyfileobj(response.raw, imgfile)
else:
_LOGGER.error("Cannot write image to file, response %s",
response.status_code,
exc_info=True)
def video_to_file(self, path):
"""Write video to file.
:param path: Path to write file
"""
_LOGGER.debug("Writing video from %s to %s", self.name, path)
response = self._cached_video
if response is None:
_LOGGER.error("No saved video exist for %s.",
self.name,
exc_info=True)
return
with open(path, 'wb') as vidfile:
copyfileobj(response.raw, vidfile)
def get_thumb_from_homescreen(self):
"""Retrieve thumbnail from homescreen."""
for device in self.sync.homescreen['devices']:
try:
device_type = device['device_type']
device_name = device['name']
device_thumb = device['thumbnail']
if device_type == 'camera' and device_name == self.name:
return device_thumb
except KeyError:
pass
_LOGGER.error("Could not find thumbnail for camera %s",
self.name,
exc_info=True)
return None
+1
View File
@@ -0,0 +1 @@
"""Init file for blinkpy helper functions."""
+61
View File
@@ -0,0 +1,61 @@
"""Generates constants for use in blinkpy."""
import os
MAJOR_VERSION = 0
MINOR_VERSION = 12
PATCH_VERSION = 0
__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3)
PROJECT_NAME = 'blinkpy'
PROJECT_PACKAGE_NAME = 'blinkpy'
PROJECT_LICENSE = 'MIT'
PROJECT_AUTHOR = 'Kevin Fronczak'
PROJECT_COPYRIGHT = ' 2017, {}'.format(PROJECT_AUTHOR)
PROJECT_URL = 'https://github.com/fronzbot/blinkpy'
PROJECT_EMAIL = 'kfronczak@gmail.com'
PROJECT_DESCRIPTION = ('A Blink camera Python library '
'running on Python 3.')
PROJECT_LONG_DESCRIPTION = ('blinkpy is an open-source '
'unofficial API for the Blink Camera '
'system with the intention for easy '
'integration into various home '
'automation platforms.')
if os.path.exists('README.rst'):
PROJECT_LONG_DESCRIPTION = open('README.rst').read()
PROJECT_CLASSIFIERS = [
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Home Automation'
]
PROJECT_GITHUB_USERNAME = 'fronzbot'
PROJECT_GITHUB_REPOSITORY = 'blinkpy'
PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME)
'''
URLS
'''
BLINK_URL = 'immedia-semi.com'
DEFAULT_URL = "{}.{}".format('prod', BLINK_URL)
BASE_URL = "https://{}".format(DEFAULT_URL)
LOGIN_URL = "{}/login".format(BASE_URL)
LOGIN_BACKUP_URL = "https://{}.{}/login".format('rest.piri', BLINK_URL)
'''
Dictionaries
'''
ONLINE = {'online': True, 'offline': False}
'''
OTHER
'''
TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S%Z'
+13
View File
@@ -0,0 +1,13 @@
"""Module to define error types."""
USERNAME = (0, "Username must be a string")
PASSWORD = (1, "Password must be a string")
AUTHENTICATE = (
2,
"Cannot authenticate since either password or username has not been set"
)
AUTH_TOKEN = (
3,
"Authentication header incorrect. Are you sure you received your token?"
)
REQUEST = (4, "Cannot perform request (get/post type incorrect)")
+33
View File
@@ -0,0 +1,33 @@
"""Module for blinkpy logging."""
import logging
def create_logger(name):
"""Create a logger instance."""
handler = RepeatLogHandler()
handler.setFormatter(log_formatter())
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)
return logger
def log_formatter():
"""Create log formatter."""
fmt = "%(asctime)s %(levelname)s [%(name)s] %(message)s"
return logging.Formatter(fmt)
class RepeatLogHandler(logging.StreamHandler):
"""Log handler for repeat entries."""
def __init__(self):
"""Initialize repeat log handler."""
super().__init__()
self.log_record = set()
def emit(self, record):
"""Ensure we only log a message once."""
if record.msg not in self.log_record:
self.log_record.add(record.msg)
super().emit(record)
+123
View File
@@ -0,0 +1,123 @@
"""Useful functions for blinkpy."""
import logging
import time
from requests import Request, Session, exceptions
from blinkpy.helpers.constants import BLINK_URL, TIMESTAMP_FORMAT
import blinkpy.helpers.errors as ERROR
_LOGGER = logging.getLogger(__name__)
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))
def merge_dicts(dict_a, dict_b):
"""Merge two dictionaries into one."""
duplicates = [val for val in dict_a if val in dict_b]
if duplicates:
_LOGGER.warning(("Duplicates found during merge: %s. "
"Renaming is recommended."), duplicates)
return {**dict_a, **dict_b}
def create_session():
"""Create a session for blink communication."""
sess = Session()
return sess
def attempt_reauthorization(blink):
"""Attempt to refresh auth token and links."""
_LOGGER.info("Auth token expired, attempting reauthorization.")
headers = blink.get_auth_token(is_retry=True)
return headers
def http_req(blink, url='http://example.com', data=None, headers=None,
reqtype='get', stream=False, json_resp=True, is_retry=False):
"""
Perform server requests and check if reauthorization neccessary.
:param blink: Blink instance
:param url: URL to perform request
:param data: Data to send (default: None)
:param headers: Headers to send (default: None)
:param reqtype: Can be 'get' or 'post' (default: 'get')
:param stream: Stream response? True/FALSE
:param json_resp: Return JSON response? TRUE/False
:param is_retry: Is this a retry attempt? True/FALSE
"""
if reqtype == 'post':
req = Request('POST', url, headers=headers, data=data)
elif reqtype == 'get':
req = Request('GET', url, headers=headers)
else:
_LOGGER.error("Invalid request type: %s", reqtype)
raise BlinkException(ERROR.REQUEST)
prepped = req.prepare()
try:
response = blink.session.send(prepped, stream=stream, timeout=10)
if json_resp and 'code' in response.json():
if is_retry:
_LOGGER.error("Cannot obtain new token for server auth.")
return None
else:
headers = attempt_reauthorization(blink)
if not headers:
raise exceptions.ConnectionError
return http_req(blink, url=url, data=data, headers=headers,
reqtype=reqtype, stream=stream,
json_resp=json_resp, is_retry=True)
except (exceptions.ConnectionError, exceptions.Timeout):
_LOGGER.info("Cannot connect to server with url %s.", url)
if not is_retry:
headers = attempt_reauthorization(blink)
return http_req(blink, url=url, data=data, headers=headers,
reqtype=reqtype, stream=stream,
json_resp=json_resp, is_retry=True)
_LOGGER.error("Endpoint %s failed. Possible issue with Blink servers.",
url)
return None
if json_resp:
return response.json()
return response
class BlinkException(Exception):
"""Class to throw general blink exception."""
def __init__(self, errcode):
"""Initialize BlinkException."""
super().__init__()
self.errid = errcode[0]
self.message = errcode[1]
class BlinkAuthenticationException(BlinkException):
"""Class to throw authentication exception."""
pass
class BlinkURLHandler():
"""Class that handles Blink URLS."""
def __init__(self, region_id):
"""Initialize the urls."""
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)
self.networks_url = "{}/networks".format(self.base_url)
self.video_url = "{}/api/v2/videos".format(self.base_url)
_LOGGER.debug("Setting base url to %s.", self.base_url)
+171
View File
@@ -0,0 +1,171 @@
"""Defines a sync module for Blink."""
import logging
from requests.structures import CaseInsensitiveDict
from blinkpy import api
from blinkpy.camera import BlinkCamera
from blinkpy.helpers.constants import ONLINE
_LOGGER = logging.getLogger(__name__)
class BlinkSyncModule():
"""Class to initialize sync module."""
def __init__(self, blink, network_name, network_id):
"""
Initialize Blink sync module.
:param blink: Blink class instantiation
"""
self.blink = blink
self._auth_header = blink.auth_header
self.network_id = network_id
self.region = blink.region
self.region_id = blink.region_id
self.name = network_name
self.serial = None
self.status = None
self.sync_id = None
self.host = None
self.summary = None
self.homescreen = None
self.network_info = None
self.events = []
self.cameras = CaseInsensitiveDict({})
self.motion = {}
self.last_record = {}
@property
def attributes(self):
"""Return sync attributes."""
attr = {
'name': self.name,
'id': self.sync_id,
'network_id': self.network_id,
'serial': self.serial,
'status': self.status,
'region': self.region,
'region_id': self.region_id,
}
return attr
@property
def urls(self):
"""Return device urls."""
return self.blink.urls
@property
def online(self):
"""Return boolean system online status."""
return ONLINE[self.status]
@property
def arm(self):
"""Return status of sync module: armed/disarmed."""
return self.network_info['network']['armed']
@arm.setter
def arm(self, value):
"""Arm or disarm system."""
if value:
return api.request_system_arm(self.blink, self.network_id)
return api.request_system_disarm(self.blink, self.network_id)
def start(self):
"""Initialize the system."""
response = api.request_syncmodule(self.blink, self.network_id)
try:
self.summary = response['syncmodule']
self.network_id = self.summary['network_id']
except (TypeError, KeyError):
_LOGGER.error(("Could not retrieve sync module information "
"with response: %s"), response, exc_info=True)
return False
try:
self.sync_id = self.summary['id']
self.serial = self.summary['serial']
self.status = self.summary['status']
except KeyError:
_LOGGER.error("Could not extract some sync module info: %s",
response,
exc_info=True)
self.events = self.get_events()
self.homescreen = api.request_homescreen(self.blink)
self.network_info = api.request_network_status(self.blink,
self.network_id)
self.check_new_videos()
camera_info = self.get_camera_info()
for camera_config in camera_info:
name = camera_config['name']
self.cameras[name] = BlinkCamera(self)
self.motion[name] = False
self.cameras[name].update(camera_config, force_cache=True)
return True
def get_events(self):
"""Retrieve events from server."""
response = api.request_sync_events(self.blink, self.network_id)
try:
return response['event']
except (TypeError, KeyError):
_LOGGER.error("Could not extract events: %s",
response,
exc_info=True)
return False
def get_camera_info(self):
"""Retrieve camera information."""
response = api.request_cameras(self.blink, self.network_id)
try:
return response['devicestatus']
except (TypeError, KeyError):
_LOGGER.error("Could not extract camera info: %s",
response,
exc_info=True)
return []
def refresh(self, force_cache=False):
"""Get all blink cameras and pulls their most recent status."""
self.events = self.get_events()
self.homescreen = api.request_homescreen(self.blink)
self.network_info = api.request_network_status(self.blink,
self.network_id)
camera_info = self.get_camera_info()
self.check_new_videos()
for camera_config in camera_info:
name = camera_config['name']
self.cameras[name].update(camera_config, force_cache=force_cache)
def check_new_videos(self):
"""Check if new videos since last refresh."""
resp = api.request_videos(self.blink,
time=self.blink.last_refresh,
page=0)
for camera in self.cameras.keys():
self.motion[camera] = False
try:
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['camera_name']
clip = entry['address']
timestamp = entry['created_at']
self.motion[name] = True
self.last_record[name] = {'clip': clip, 'time': timestamp}
except KeyError:
_LOGGER.debug("No new videos since last refresh.")
return True
+20
View File
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = blinkpy
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+1
View File
@@ -0,0 +1 @@
../README.rst
+17
View File
@@ -0,0 +1,17 @@
.. _core_module:
Blinkpy
----------------------
.. automodule:: blinkpy.blinkpy
:members:
.. automodule:: blinkpy.sync_module
:members:
.. automodule:: blinkpy.camera
:members:
.. automodule:: blinkpy.helpers.util
:members:
+6
View File
@@ -0,0 +1,6 @@
API Reference
----------------------
.. automodule:: blinkpy.api
:members:
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# blinkpy documentation build configuration file, created by
# sphinx-quickstart on Sat Jan 20 22:20:16 2018.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
import sphinx_rtd_theme
from blinkpy.helpers.constants import __version__
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.mathjax',
'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'blinkpy'
copyright = '2018, Kevin Fronczak'
author = 'Kevin Fronczak'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __version__
# The full version, including alpha/beta/rc tags.
release = __version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
#html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
'**': [
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html',
]
}
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'blinkpydoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'blinkpy.tex', 'blinkpy Documentation',
'Kevin Fronczak', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'blinkpy', 'blinkpy Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'blinkpy', 'blinkpy Documentation',
author, 'blinkpy', 'One line description of project.',
'Miscellaneous'),
]
+22
View File
@@ -0,0 +1,22 @@
.. blinkpy documentation master file, created by
sphinx-quickstart on Sat Jan 20 22:20:16 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to blinkpy's documentation!
===================================
.. toctree::
:maxdepth: 2
:caption: Contents:
:glob:
README
api/*
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
+36
View File
@@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=blinkpy
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/python
import blinkpy
import time
blink = blinkpy.Blink()
blink.login()
blink.setup_system()
for camera in blink.cameras:
print('Arming ' + camera.name)
camera.set_motion_detect(True)
time.sleep(5)
blink.refresh()
print('Arming Blink')
blink.arm = True
time.sleep(5)
print('Blink armed? ' + str(blink.arm))
print('Disarming Blink')
time.sleep(5)
blink.arm = False
print('Blink armed? ' + str(blink.arm))
-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/python
import blinkpy
blink = blinkpy.Blink()
blink.login()
blink.setup_system()
for camera in blink.cameras:
print(camera.name) # Name of the camera
print(camera.id) # Integer id of the camera (assigned by Blink)
print(camera.armed) # Whether the device is armed/disarmed (ie. detecting motion)
print(camera.clip) # Link to last motion clip captured
print(camera.thumbnail) # Link to current camera thumbnail
print(camera.temperature) # Current camera temperature (not super accurate, but might be useful for someone)
print(camera.battery) # Current battery level... I think the value ranges from 0-3, but not quite sure yet.
print(camera.notifications) # Number of unread notifications (ie. motion alerts that haven't been viewed)
print(camera.motion) # Dictionary containing values for keys ['video', 'image', 'time']
# which correspond to last motion recorded, thumbnail of last motion, and timestamp of last motion
+25
View File
@@ -0,0 +1,25 @@
[MASTER]
reports=no
# Reasons disabled:
# locally-disabled - it spams too much
# duplicate-code - it's annoying
# 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,
unused-argument,
duplicate-code,
too-many-arguments,
too-many-branches,
too-many-instance-attributes,
too-many-locals,
too-many-public-methods,
too-many-return-statements,
too-many-statements,
too-many-lines,
too-few-public-methods,
no-else-return,
+3 -5
View File
@@ -1,5 +1,3 @@
pytest>=2.9.2
pytest-cov>=2.3.1
pytest-timeout>=1.0.0
requests>=2.12.4
testtools
python-dateutil==2.7.5
requests>=2.20.0
testtools==2.3.0
+10
View File
@@ -0,0 +1,10 @@
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
+44 -22
View File
@@ -1,26 +1,48 @@
# -*- coding: utf-8 -*-
from os.path import abspath, dirname, join
from setuptools import setup, find_packages
# Fixes import error due to breaking change in pip 10
# https://stackoverflow.com/a/49867265
# pip > 10
try:
from pip._internal.req import parse_requirements
# pip < 10
except ImportError:
from pip.req import parse_requirements
from blinkpy.helpers.constants import (
__version__, PROJECT_PACKAGE_NAME, PROJECT_LICENSE, PROJECT_URL,
PROJECT_EMAIL, PROJECT_DESCRIPTION, PROJECT_CLASSIFIERS, PROJECT_AUTHOR,
)
from setuptools import setup
this_dir = abspath(dirname(__file__))
install_reqs = parse_requirements('{}/requirements.txt'.format(this_dir), session=False)
test_reqs = parse_requirements('{}/requirements_test.txt'.format(this_dir), session=False)
REQUIRES = [str(ir.req) for ir in install_reqs]
TEST_REQUIRES = [str(ir.req) for ir in test_reqs]
PACKAGES = find_packages(exclude=['tests*', 'docs'])
with open('{}/README.rst'.format(this_dir), encoding='utf-8') as readme_file:
long_description = readme_file.read()
name = PROJECT_PACKAGE_NAME
version = __version__
setup(
name = 'blinkpy',
version = '0.4.',
description = 'A Blink camera Python library',
long_description='A library that communicates with Blink cameras',
author = 'Kevin Fronczak',
author_email = "kfronczak@gmail.com",
license='MIT',
url = 'https://github.com/fronzbot/blinkpy',
py_modules=['blinkpy'],
install_requires=['requests>=2,<3'],
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Environment :: Plugins',
'Environment :: Web Environment'
]
)
name = PROJECT_PACKAGE_NAME,
version = __version__,
description = PROJECT_DESCRIPTION,
long_description = long_description,
author = PROJECT_AUTHOR,
author_email = PROJECT_EMAIL,
license = PROJECT_LICENSE,
url = PROJECT_URL,
platforms = 'any',
py_modules = ['blinkpy'],
packages=PACKAGES,
include_package_data = True,
install_requires = REQUIRES,
test_suite = 'tests',
classifiers = PROJECT_CLASSIFIERS
)
+1
View File
@@ -0,0 +1 @@
"""Init file for tests directory."""
+71
View File
@@ -0,0 +1,71 @@
"""Simple mock responses definitions."""
from blinkpy.helpers.util import BlinkURLHandler
import blinkpy.helpers.constants as const
LOGIN_RESPONSE = {
'region': {'mock': 'Test'},
'networks': {
'1234': {'name': 'test', 'onboarded': True}
},
'authtoken': {'authtoken': 'foobar123', 'message': 'auth'}
}
class MockResponse:
"""Class for mock request response."""
def __init__(self, json_data, status_code, raw_data=None):
"""Initialize mock get response."""
self.json_data = json_data
self.status_code = status_code
self.raw_data = raw_data
def json(self):
"""Return json data from get_request."""
return self.json_data
@property
def raw(self):
"""Return raw data from get request."""
return self.raw_data
def mocked_session_send(*args, **kwargs):
"""Mock session."""
prepped = args[0]
url = prepped.url
header = prepped.headers
method = prepped.method
if method == 'GET':
expected_token = LOGIN_RESPONSE['authtoken']['authtoken']
if header['TOKEN_AUTH'] != expected_token:
response = {'message': 'Not Authorized', 'code': 400}
status = 400
elif url == 'use_bad_response':
response = {'foo': 'bar'}
status = 200
elif url == 'reauth':
response = {'message': 'REAUTH', 'code': 777}
status = 777
else:
response = {'test': 'foo'}
status = 200
elif method == 'POST':
if url in (const.LOGIN_URL, const.LOGIN_BACKUP_URL):
response = LOGIN_RESPONSE
status = 200
elif url == 'http://wrong.url/' or url is None:
response = {'message': 'Error', 'code': 404}
status = 404
else:
response = {'message': 'foo', 'code': 200}
status = 200
return MockResponse(response, status)
class MockURLHandler(BlinkURLHandler):
"""Mocks URL Handler in blinkpy module."""
pass
+42
View File
@@ -0,0 +1,42 @@
"""Test various api functions."""
import unittest
from unittest import mock
from blinkpy import api
from blinkpy.blinkpy import Blink
from blinkpy.helpers.util import create_session
class TestBlinkAPI(unittest.TestCase):
"""Test the blink api module."""
def setUp(self):
"""Initialize the blink module."""
self.blink = Blink()
self.blink.session = create_session()
# pylint: disable=protected-access
self.blink._auth_header = {}
def tearDown(self):
"""Tear down blink module."""
self.blink = None
@mock.patch('blinkpy.blinkpy.Blink.get_auth_token')
def test_http_req_connect_error(self, mock_auth):
"""Test http_get error condition."""
mock_auth.return_value = {'foo': 'bar'}
firstlog = ("INFO:blinkpy.helpers.util:"
"Cannot connect to server with url {}.").format(
'http://notreal.fake')
nextlog = ("INFO:blinkpy.helpers.util:"
"Auth token expired, attempting reauthorization.")
lastlog = ("ERROR:blinkpy.helpers.util:"
"Endpoint {} failed. Possible issue with "
"Blink servers.").format('http://notreal.fake')
expected = [firstlog, nextlog, firstlog, lastlog]
with self.assertLogs() as getlog:
api.http_get(self.blink, 'http://notreal.fake')
with self.assertLogs() as postlog:
api.http_post(self.blink, 'http://notreal.fake')
self.assertEqual(getlog.output, expected)
self.assertEqual(postlog.output, expected)
+150
View File
@@ -0,0 +1,150 @@
"""Tests camera and system functions."""
import unittest
from unittest import mock
import logging
from requests import Request
from blinkpy import blinkpy
from blinkpy.sync_module import BlinkSyncModule
from blinkpy.helpers.util import create_session, get_time
import tests.mock_responses as mresp
USERNAME = 'foobar'
PASSWORD = 'deadbeef'
class MockSyncModule(BlinkSyncModule):
"""Mock http requests from sync module."""
def __init__(self, blink, header):
"""Create mock sync module instance."""
super().__init__(blink, header)
self.blink = blink
self.header = header
self.return_value = None
self.return_value2 = None
def http_get(self, url, stream=False, json=True):
"""Mock get request."""
if stream and self.return_value2 is not None:
return self.return_value2
return self.return_value
def http_post(self, url):
"""Mock post request."""
return self.return_value
@mock.patch('blinkpy.helpers.util.Session.send',
side_effect=mresp.mocked_session_send)
class TestBlinkFunctions(unittest.TestCase):
"""Test Blink and BlinkCamera functions in blinkpy."""
def setUp(self):
"""Set up Blink module."""
self.blink = blinkpy.Blink(username=USERNAME,
password=PASSWORD)
# pylint: disable=protected-access
self.blink._auth_header = {
'Host': 'test.url.tld',
'TOKEN_AUTH': 'foobar123'
}
self.blink.urls = blinkpy.BlinkURLHandler('test')
self.blink.session = create_session()
def tearDown(self):
"""Clean up after test."""
self.blink = None
@mock.patch('blinkpy.blinkpy.api.http_req')
def test_backup_url(self, req, mock_sess):
"""Test backup login method."""
fake_req = Request('POST', 'http://wrong.url').prepare()
json_resp = {
'authtoken': {'authtoken': 'foobar123'},
'networks': {'1234': {'name': 'foobar', 'onboarded': True}}
}
new_req = mresp.MockResponse(json_resp, 200)
req.side_effect = [
mresp.mocked_session_send(fake_req),
new_req
]
self.blink.get_auth_token()
self.assertEqual(self.blink.region_id, 'piri')
self.assertEqual(self.blink.region, 'UNKNOWN')
# pylint: disable=protected-access
self.assertEqual(self.blink._token, 'foobar123')
def test_merge_cameras(self, mock_sess):
"""Test merge camera functionality."""
first_dict = {'foo': 'bar', 'test': 123}
next_dict = {'foobar': 456, 'bar': 'foo'}
self.blink.sync['foo'] = BlinkSyncModule(self.blink, 'foo', 1)
self.blink.sync['bar'] = BlinkSyncModule(self.blink, 'bar', 2)
self.blink.sync['foo'].cameras = first_dict
self.blink.sync['bar'].cameras = next_dict
result = self.blink.merge_cameras()
expected = {'foo': 'bar', 'test': 123, 'foobar': 456, 'bar': 'foo'}
self.assertEqual(expected, result)
@mock.patch('blinkpy.blinkpy.api.request_videos')
def test_download_video_exit(self, mock_req, mock_sess):
"""Test we exit method when provided bad response."""
blink = blinkpy.Blink(loglevel=logging.DEBUG)
blink.last_refresh = 0
mock_req.return_value = {}
formatted_date = get_time(blink.last_refresh)
expected_log = [
"INFO:blinkpy:Retrieving videos since {}".format(formatted_date),
"DEBUG:blinkpy:Processing page 1",
"INFO:blinkpy:No videos found on page 1. Exiting."
]
with self.assertLogs() as dl_log:
blink.download_videos('/tmp')
self.assertEqual(dl_log.output, expected_log)
@mock.patch('blinkpy.blinkpy.api.request_videos')
def test_parse_downloaded_items(self, mock_req, mock_sess):
"""Test ability to parse downloaded items list."""
blink = blinkpy.Blink(loglevel=logging.DEBUG)
generic_entry = {
'created_at': '1970',
'camera_name': 'foo',
'deleted': True,
'address': '/bar.mp4'
}
result = [generic_entry]
mock_req.return_value = {'videos': result}
blink.last_refresh = 0
formatted_date = get_time(blink.last_refresh)
expected_log = [
"INFO:blinkpy:Retrieving videos since {}".format(formatted_date),
"DEBUG:blinkpy:Processing page 1",
"DEBUG:blinkpy:foo: /bar.mp4 is marked as deleted."
]
with self.assertLogs() as dl_log:
blink.download_videos('/tmp', stop=2)
self.assertEqual(dl_log.output, expected_log)
@mock.patch('blinkpy.blinkpy.api.request_videos')
def test_parse_camera_not_in_list(self, mock_req, mock_sess):
"""Test ability to parse downloaded items list."""
blink = blinkpy.Blink(loglevel=logging.DEBUG)
generic_entry = {
'created_at': '1970',
'camera_name': 'foo',
'deleted': True,
'address': '/bar.mp4'
}
result = [generic_entry]
mock_req.return_value = {'videos': result}
blink.last_refresh = 0
formatted_date = get_time(blink.last_refresh)
expected_log = [
"INFO:blinkpy:Retrieving videos since {}".format(formatted_date),
"DEBUG:blinkpy:Processing page 1",
"DEBUG:blinkpy:Skipping videos for foo."
]
with self.assertLogs() as dl_log:
blink.download_videos('/tmp', camera='bar', stop=2)
self.assertEqual(dl_log.output, expected_log)
-100
View File
@@ -1,100 +0,0 @@
import blinkpy
import requests
import unittest
from unittest import mock
from blinkpy import LOGIN_URL
from blinkpy import BASE_URL
import test_const as const
def mocked_requests_post(*args, **kwargs):
class MockPostResponse:
def __init__(self,json_data,status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
if args[0] == LOGIN_URL:
return MockPostResponse({"region":{const.REGION_ID: const.REGION}, "authtoken":{"authtoken":const.TOKEN}}, 200)
elif args[0].split("/")[-1] == 'arm':
return MockPostResponse({"armed":True}, 200)
elif args[0].split("/")[-1] == 'disarm':
return MockPostResponse({"armed":False}, 200)
else:
return MockPostResponse({}, 200)
return MockPostResponse({'message':'ERROR','code':404}, 404)
def mocked_requests_get(*args, **kwargs):
class MockGetResponse:
def __init__(self,json_data,status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
if args[0] == BASE_URL + '/networks':
return MockGetResponse({'networks':[{"id":const.NETWORK_ID,"account_id":const.ACCOUNT_ID},{"nothing":"nothing"}]}, 200)
else:
return MockGetResponse(const.response, 200)
return MockGetResponse({'message':'ERROR','code':404}, 404)
class TestBlinkRequests(unittest.TestCase):
@mock.patch('blinkpy.requests.post', side_effect=mocked_requests_post)
@mock.patch('blinkpy.requests.get', side_effect=mocked_requests_get)
def test_blink_setup(self, mock_get, mock_post):
blink = blinkpy.Blink(username='user',password='password')
blink.setup_system()
self.assertEqual(blink.network_id, str(const.NETWORK_ID))
self.assertEqual(blink.account_id, str(const.ACCOUNT_ID))
self.assertEqual(blink.region, const.REGION)
self.assertEqual(blink.region_id, const.REGION_ID)
self.assertEqual(blink.online, const.ISONLINE)
self.assertEqual(blink.arm, const.ARMED)
@mock.patch('blinkpy.requests.post', side_effect=mocked_requests_post)
@mock.patch('blinkpy.requests.get', side_effect=mocked_requests_get)
def test_blink_camera_setup_and_motion(self, mock_get, mock_post):
blink = blinkpy.Blink(username='user',password='password')
blink.setup_system()
blink.last_motion()
for name, camera in blink.cameras.items():
if camera.id == str(const.DEVICE_ID):
self.assertEqual(name, const.CAMERA_NAME)
self.assertEqual(camera.armed, const.ARMED)
self.assertEqual(camera.motion['video'], BASE_URL + const.THUMB + '.mp4')
elif camera.id == str(const.DEVICE_ID2):
self.assertEqual(name, const.CAMERA_NAME2)
self.assertEqual(camera.armed, const.ARMED2)
self.assertEqual(len(camera.motion.keys()), 0)
else:
assert False is True
@mock.patch('blinkpy.requests.post', side_effect=mocked_requests_post)
@mock.patch('blinkpy.requests.get', side_effect=mocked_requests_get)
def test_blink_refresh(self, mock_get, mock_post):
blink = blinkpy.Blink(username='user',password='password')
blink.setup_system()
const.response['devices'][0]['thumbnail'] = const.THUMB + const.THUMB2
blink.refresh()
for name, camera in blink.cameras.items():
if camera.id == str(const.DEVICE_ID):
self.assertEqual(camera.thumbnail, BASE_URL + const.THUMB + const.THUMB2 + '.jpg')
elif camera.id == str(const.DEVICE_ID2):
pass
else:
assert False is True
const.response['devices'][0]['thumbnail'] = 'new'
blink.cameras[const.CAMERA_NAME].image_refresh()
for name, camera in blink.cameras.items():
if camera.id == str(const.DEVICE_ID):
self.assertEqual(camera.thumbnail, BASE_URL + 'new' + '.jpg')
elif camera.id == str(const.DEVICE_ID2):
pass
else:
assert False is True
+162
View File
@@ -0,0 +1,162 @@
"""
Test full system.
Tests the system initialization and attributes of
the main Blink system. Tests if we properly catch
any communication related errors at startup.
"""
import unittest
from unittest import mock
from blinkpy import api
from blinkpy.blinkpy import Blink
from blinkpy.sync_module import BlinkSyncModule
from blinkpy.helpers.util import (
http_req, create_session, BlinkAuthenticationException,
BlinkException, BlinkURLHandler)
from blinkpy.helpers.constants import __version__
import tests.mock_responses as mresp
USERNAME = 'foobar'
PASSWORD = 'deadbeef'
@mock.patch('blinkpy.helpers.util.Session.send',
side_effect=mresp.mocked_session_send)
class TestBlinkSetup(unittest.TestCase):
"""Test the Blink class in blinkpy."""
def setUp(self):
"""Set up Blink module."""
self.blink_no_cred = Blink()
self.blink = Blink(username=USERNAME,
password=PASSWORD)
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', '1234')
self.blink.urls = BlinkURLHandler('test')
self.blink.session = create_session()
def tearDown(self):
"""Clean up after test."""
self.blink = None
self.blink_no_cred = None
def test_initialization(self, mock_sess):
"""Verify we can initialize blink."""
self.assertEqual(self.blink.version, __version__)
# pylint: disable=protected-access
self.assertEqual(self.blink._username, USERNAME)
# pylint: disable=protected-access
self.assertEqual(self.blink._password, PASSWORD)
def test_no_credentials(self, mock_sess):
"""Check that we throw an exception when no username/password."""
with self.assertRaises(BlinkAuthenticationException):
self.blink_no_cred.get_auth_token()
# pylint: disable=protected-access
self.blink_no_cred._username = USERNAME
with self.assertRaises(BlinkAuthenticationException):
self.blink_no_cred.get_auth_token()
def test_no_auth_header(self, mock_sess):
"""Check that we throw an exception when no auth header given."""
# pylint: disable=unused-variable
(region_id, region), = mresp.LOGIN_RESPONSE['region'].items()
self.blink.urls = BlinkURLHandler(region_id)
with self.assertRaises(BlinkException):
self.blink.get_ids()
@mock.patch('blinkpy.blinkpy.getpass.getpass')
def test_manual_login(self, getpwd, mock_sess):
"""Check that we can manually use the login() function."""
getpwd.return_value = PASSWORD
with mock.patch('builtins.input', return_value=USERNAME):
self.assertTrue(self.blink_no_cred.login())
# pylint: disable=protected-access
self.assertEqual(self.blink_no_cred._username, USERNAME)
# pylint: disable=protected-access
self.assertEqual(self.blink_no_cred._password, PASSWORD)
def test_bad_request(self, mock_sess):
"""Check that we raise an Exception with a bad request."""
self.blink.session = create_session()
explog = ("ERROR:blinkpy.helpers.util:"
"Cannot obtain new token for server auth.")
with self.assertRaises(BlinkException):
http_req(self.blink, reqtype='bad')
with self.assertLogs() as logrecord:
http_req(self.blink, reqtype='post', is_retry=True)
self.assertEqual(logrecord.output, [explog])
def test_authentication(self, mock_sess):
"""Check that we can authenticate Blink up properly."""
authtoken = self.blink.get_auth_token()['TOKEN_AUTH']
expected = mresp.LOGIN_RESPONSE['authtoken']['authtoken']
self.assertEqual(authtoken, expected)
def test_reauthorization_attempt(self, mock_sess):
"""Check that we can reauthorize after first unsuccessful attempt."""
original_header = self.blink.get_auth_token()
# pylint: disable=protected-access
bad_header = {'Host': self.blink._host, 'TOKEN_AUTH': 'BADTOKEN'}
# pylint: disable=protected-access
self.blink._auth_header = bad_header
self.assertEqual(self.blink.auth_header, bad_header)
api.request_homescreen(self.blink)
self.assertEqual(self.blink.auth_header, original_header)
@mock.patch('blinkpy.api.request_networks')
def test_multiple_networks(self, mock_net, mock_sess):
"""Check that we handle multiple networks appropriately."""
mock_net.return_value = {
'networks': [{'id': 1234, 'account_id': 1111},
{'id': 5678, 'account_id': 2222}]
}
self.blink.networks = {'0000': {'onboarded': False, 'name': 'foo'},
'5678': {'onboarded': True, 'name': 'bar'},
'1234': {'onboarded': False, 'name': 'test'}}
self.blink.get_ids()
self.assertTrue('5678' in self.blink.network_ids)
self.assertEqual(self.blink.account_id, 2222)
@mock.patch('blinkpy.api.request_networks')
def test_multiple_onboarded_networks(self, mock_net, mock_sess):
"""Check that we handle multiple networks appropriately."""
mock_net.return_value = {
'networks': [{'id': 0000, 'account_id': 2222},
{'id': 5678, 'account_id': 1111}]
}
self.blink.networks = {'0000': {'onboarded': False, 'name': 'foo'},
'5678': {'onboarded': True, 'name': 'bar'},
'1234': {'onboarded': True, 'name': 'test'}}
self.blink.get_ids()
self.assertTrue('5678' in self.blink.network_ids)
self.assertTrue('1234' in self.blink.network_ids)
self.assertEqual(self.blink.account_id, 1111)
@mock.patch('blinkpy.blinkpy.time.time')
def test_throttle(self, mock_time, mock_sess):
"""Check throttling functionality."""
now = self.blink.refresh_rate + 1
mock_time.return_value = now
self.assertEqual(self.blink.last_refresh, None)
self.assertEqual(self.blink.check_if_ok_to_update(), True)
self.assertEqual(self.blink.last_refresh, None)
with mock.patch('blinkpy.sync_module.BlinkSyncModule.refresh',
return_value=True):
self.blink.refresh()
self.assertEqual(self.blink.last_refresh, now)
self.assertEqual(self.blink.check_if_ok_to_update(), False)
self.assertEqual(self.blink.last_refresh, now)
def test_sync_case_insensitive_dict(self, mock_sess):
"""Check that we can access sync modules ignoring case."""
self.assertEqual(self.blink.sync['test'].name, 'test')
self.assertEqual(self.blink.sync['TEST'].name, 'test')
@mock.patch('blinkpy.api.request_login')
def test_unexpected_login(self, mock_login, mock_sess):
"""Check that we appropriately handle unexpected login info."""
mock_login.return_value = None
self.assertFalse(self.blink.get_auth_token())
+194
View File
@@ -0,0 +1,194 @@
"""
Test all camera attributes.
Tests the camera initialization and attributes of
individual BlinkCamera instantiations once the
Blink system is set up.
"""
import unittest
from unittest import mock
from blinkpy import blinkpy
from blinkpy.helpers.util import create_session, BlinkURLHandler
from blinkpy.sync_module import BlinkSyncModule
from blinkpy.camera import BlinkCamera
import tests.mock_responses as mresp
USERNAME = 'foobar'
PASSWORD = 'deadbeef'
CAMERA_CFG = {
'camera': [
{
'battery_voltage': 90,
'motion_alert': True,
'wifi_strength': -30,
'temperature': 68
}
]
}
@mock.patch('blinkpy.helpers.util.Session.send',
side_effect=mresp.mocked_session_send)
class TestBlinkCameraSetup(unittest.TestCase):
"""Test the Blink class in blinkpy."""
def setUp(self):
"""Set up Blink module."""
self.blink = blinkpy.Blink(username=USERNAME,
password=PASSWORD)
header = {
'Host': 'abc.zxc',
'TOKEN_AUTH': mresp.LOGIN_RESPONSE['authtoken']['authtoken']
}
# pylint: disable=protected-access
self.blink._auth_header = header
self.blink.session = create_session()
self.blink.urls = BlinkURLHandler('test')
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', 1234)
self.camera = BlinkCamera(self.blink.sync['test'])
self.camera.name = 'foobar'
self.blink.sync['test'].cameras['foobar'] = self.camera
def tearDown(self):
"""Clean up after test."""
self.blink = None
def test_camera_update(self, mock_sess):
"""Test that we can properly update camera properties."""
config = {
'name': 'new',
'camera_id': 1234,
'network_id': 5678,
'serial': '12345678',
'enabled': False,
'battery_voltage': 90,
'battery_state': 'ok',
'temperature': 68,
'wifi_strength': 4,
'thumbnail': '/thumb',
}
self.camera.last_record = ['1']
self.camera.sync.last_record = {
'new': {
'clip': '/test.mp4',
'time': '1970-01-01T00:00:00'
}
}
mock_sess.side_effect = [
mresp.MockResponse({'temp': 71}, 200),
'test',
'foobar'
]
self.camera.update(config)
self.assertEqual(self.camera.name, 'new')
self.assertEqual(self.camera.camera_id, '1234')
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.temperature, 68)
self.assertEqual(self.camera.temperature_c, 20)
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')
self.assertEqual(self.camera.clip,
'https://rest.test.immedia-semi.com/test.mp4')
self.assertEqual(self.camera.image_from_cache, 'test')
self.assertEqual(self.camera.video_from_cache, 'foobar')
def test_thumbnail_not_in_info(self, mock_sess):
"""Test that we grab thumbanil if not in camera_info."""
mock_sess.side_effect = [
mresp.MockResponse({'temp': 71}, 200),
'foobar',
'barfoo'
]
self.camera.last_record = ['1']
self.camera.sync.last_record = {
'new': {
'clip': '/test.mp4',
'time': '1970-01-01T00:00:00'
}
}
config = {
'name': 'new',
'camera_id': 1234,
'network_id': 5678,
'serial': '12345678',
'enabled': False,
'battery_voltage': 90,
'battery_state': 'ok',
'temperature': 68,
'wifi_strength': 4,
'thumbnail': '',
}
self.camera.sync.homescreen = {
'devices': [
{'foo': 'bar'},
{'device_type': 'foobar'},
{'device_type': 'camera',
'name': 'new',
'thumbnail': '/new/thumb'}
]
}
self.camera.update(config)
self.assertEqual(self.camera.thumbnail,
'https://rest.test.immedia-semi.com/new/thumb.jpg')
def test_no_thumbnails(self, mock_sess):
"""Tests that thumbnail is 'None' if none found."""
mock_sess.return_value = 'foobar'
self.camera.last_record = ['1']
config = {
'name': 'new',
'camera_id': 1234,
'network_id': 5678,
'serial': '12345678',
'enabled': False,
'battery_voltage': 90,
'battery_state': 'ok',
'temperature': 68,
'wifi_strength': 4,
'thumbnail': '',
}
self.camera.sync.homescreen = {
'devices': []
}
self.assertEqual(self.camera.temperature_calibrated, None)
with self.assertLogs() as logrecord:
self.camera.update(config)
self.assertEqual(self.camera.thumbnail, None)
self.assertEqual(self.camera.last_record, ['1'])
self.assertEqual(self.camera.temperature_calibrated, 68)
self.assertEqual(
logrecord.output,
[("WARNING:blinkpy.camera:Could not retrieve calibrated "
"temperature."),
("ERROR:blinkpy.camera:Could not find thumbnail for camera new"
"\nNoneType: None")]
)
def test_no_video_clips(self, mock_sess):
"""Tests that we still proceed with camera setup with no videos."""
mock_sess.return_value = 'foobar'
config = {
'name': 'new',
'camera_id': 1234,
'network_id': 5678,
'serial': '12345678',
'enabled': False,
'battery_voltage': 90,
'battery_state': 'ok',
'temperature': 68,
'wifi_strength': 4,
'thumbnail': '/foobar',
}
self.camera.sync.homescreen = {
'devices': []
}
self.camera.update(config, force_cache=True)
self.assertEqual(self.camera.clip, None)
self.assertEqual(self.camera.video_from_cache, None)
-75
View File
@@ -1,75 +0,0 @@
import testtools
import unittest
import blinkpy
from blinkpy import BASE_URL
class TestBlink(unittest.TestCase):
def test_blink_instance(self):
blink = blinkpy.Blink()
def test_blink_camera_attributes(self):
config = {'name':'name', 'device_id': 1248, 'armed':True, 'thumbnail':'/test/url/image', 'temp':70, 'battery':3, 'notifications':0, 'region_id':'rid'}
camera = blinkpy.BlinkCamera(config)
# Check that values are properly stored and can be recalled
self.assertEqual(camera.name, config['name'])
self.assertEqual(camera.id, str(config['device_id']))
self.assertEqual(camera.armed, config['armed'])
self.assertEqual(camera.clip, BASE_URL + config['thumbnail'] + '.mp4')
self.assertEqual(camera.thumbnail, BASE_URL + config['thumbnail'] + '.jpg')
self.assertEqual(camera.temperature, config['temp'])
self.assertEqual(camera.battery, config['battery'])
self.assertEqual(camera.notifications, config['notifications'])
self.assertEqual(camera.region_id, config['region_id'])
# Check that values can be changed with individual methods
test_header = {'Header':'test','Test':'1234'}
test_img = 'http://image-link.com'
test_arm = 'http://arm-link.com'
test_motion = {'video':'url','image':'url','time':'timestamp'}
camera.name = config['name']+'_new'
camera.clip = BASE_URL + config['thumbnail'] + '.mp4_new'
camera.thumbnail = BASE_URL + config['thumbnail'] + '.jpg_new'
camera.temperature = config['temp'] + 10
camera.battery = config['battery'] + 1
camera.notifications = config['notifications'] + 1
camera.image_link = test_img
camera.arm_link = test_arm
camera.header = test_header
camera.motion = test_motion
# Check that values are properly stored and can be recalled
self.assertEqual(camera.name , config['name']+'_new')
self.assertEqual(camera.clip , BASE_URL + config['thumbnail'] + '.mp4_new')
self.assertEqual(camera.thumbnail, BASE_URL + config['thumbnail'] + '.jpg_new')
self.assertEqual(camera.temperature, config['temp'] + 10)
self.assertEqual(camera.battery, config['battery'] + 1)
self.assertEqual(camera.notifications, config['notifications'] + 1)
self.assertEqual(camera.image_link, test_img)
self.assertEqual(camera.arm_link, test_arm)
self.assertEqual(camera.header, test_header)
self.assertEqual(camera.motion, test_motion)
# Verify bulk update function
test_name = camera.name +' last'
test_status = True
test_url = '-this-is-a-test/for_realz'
test_temp = camera.temperature + 7
test_battery = camera.battery - 1
test_notif = camera.notifications - 1
values = {'name':test_name, 'armed':test_status, 'thumbnail':test_url, 'temp':test_temp, 'battery':test_battery, 'notifications':test_notif}
camera.update(values)
# Check that values are properly stored and can be recalled
jpg_url = BASE_URL + test_url +'.jpg'
mp4_url = BASE_URL + test_url +'.mp4'
self.assertEqual(camera.name, test_name)
self.assertEqual(camera.armed, test_status)
self.assertEqual(camera.clip, mp4_url)
self.assertEqual(camera.thumbnail, jpg_url)
self.assertEqual(camera.temperature, test_temp)
self.assertEqual(camera.battery, test_battery)
self.assertEqual(camera.notifications, test_notif)
-157
View File
@@ -1,157 +0,0 @@
ISONLINE = True
ARMED = True
ARMED2 = False
REGION_ID = 'test'
REGION = 'Oceaniaeurasia'
TOKEN = 'abcd1234$$@@'
NETWORK_ID = 1337
DEVICE_ID = 9876
DEVICE_ID2 = 6789
ACCOUNT_ID = 1234
CAMERA_NAME = 'My Camera'
CAMERA_NAME2 = 'Number 2'
BATTERY = 3
BATTERY2 = 1
TEMP = 70
TEMP2 = 66
THUMB = '/url/camera/7777/clip'
THUMB2 = '/url/camera/8888/clip'
NOTIFS = 1
NOTIFS2 = 1
SYNC_ID = 1000
if ISONLINE:
ONLINE = 'online'
else:
ONLINE = 'offline'
response = {'account': {'notifications': 1},
'devices': [{'device_type': 'camera',
'notifications': NOTIFS,
'battery': BATTERY,
'active': 'disabled',
'errors': 0,
'error_msg': '',
'enabled': False,
'temp': TEMP,
'updated_at': '2017-01-27T03:14:24+00:00',
'lfr_strength': 3,
'armed': ARMED,
'device_id': DEVICE_ID,
'wifi_strength': 5,
'warning': 0,
'thumbnail': THUMB,
'name': CAMERA_NAME,
'status': 'done'},
{'device_type': 'camera',
'notifications': NOTIFS2,
'battery': BATTERY2,
'active': 'disabled',
'errors': 0,
'error_msg': '',
'enabled': False,
'temp': TEMP2,
'updated_at': '2017-01-27T03:14:24+00:00',
'lfr_strength': 3,
'armed': ARMED2,
'device_id': DEVICE_ID2,
'wifi_strength': 5,
'warning': 0,
'thumbnail': THUMB2,
'name': CAMERA_NAME2,
'status': 'done'},
{'updated_at': '2017-01-26T19:32:10+00:00',
'device_type': 'sync_module',
'notifications': 0,
'device_id': SYNC_ID,
'status':
'online',
'last_hb':
'2017-01-27T03:14:49+00:00',
'errors': 0,
'error_msg': '',
'warning': 0}],
'network': {'armed': ARMED,
'wifi_strength': 5,
'warning': 0,
'error_msg': '',
'name': 'Blink',
'notifications': NOTIFS,
'status': 'ok'},
'event': [{'camera_name': CAMERA_NAME,
'updated_at': '2017-01-28T19:51:52+00:00',
'sync_module_id': None,
'camera': DEVICE_ID,
'type': 'motion',
'duration': None,
'status': None,
'created_at': '2017-01-28T19:51:52+00:00',
'camera_id': DEVICE_ID,
'id': 123456789,
'siren_id': None,
'account_id': ACCOUNT_ID,
'notified': True,
'siren': None,
'syncmodule': None,
'video_url': THUMB+'.mp4',
'command_id': None,
'network_id': None,
'account': ACCOUNT_ID,
'video_id': 100000001},
{'updated_at': '2017-01-28T19:51:29+00:00',
'sync_module_id': SYNC_ID,
'camera': None,
'type': 'armed',
'duration': None,
'status': None,
'created_at':
'2017-01-28T19:51:29+00:00',
'camera_id': None,
'id': 123456789,
'siren_id': None,
'account_id': ACCOUNT_ID,
'notified': False,
'siren': None,
'syncmodule': SYNC_ID,
'command_id': None,
'network_id': None,
'account': ACCOUNT_ID},
{'updated_at': '2017-01-28T19:00:12+00:00',
'sync_module_id': SYNC_ID,
'camera': None,
'type': 'disarmed',
'duration': None,
'status': None,
'created_at': '2017-01-28T19:00:12+00:00',
'camera_id': None,
'id': 123456789,
'siren_id': None,
'account_id': 2463,
'notified': False,
'siren': None,
'syncmodule': SYNC_ID,
'command_id': None,
'network_id': None,
'account': ACCOUNT_ID},
{'camera_name': CAMERA_NAME,
'updated_at': '2017-01-28T18:59:55+00:00',
'sync_module_id': None,
'camera': DEVICE_ID,
'type': 'motion',
'duration': None,
'status': None,
'created_at': '2017-01-28T18:59:55+00:00',
'camera_id': DEVICE_ID,
'id': 123456789,
'siren_id': None,
'account_id': ACCOUNT_ID,
'notified': True,
'siren': None,
'syncmodule': None,
'command_id': None,
'network_id': None,
'account': ACCOUNT_ID}],
'syncmodule':{'name':'SyncName',
'status':ONLINE}}
-6
View File
@@ -1,6 +0,0 @@
import testtools
import blinkpy
class TestImport(testtools.TestCase):
def test_import(self):
pass
+156
View File
@@ -0,0 +1,156 @@
"""Tests camera and system functions."""
import unittest
from unittest import mock
from blinkpy import blinkpy
from blinkpy.sync_module import BlinkSyncModule
from blinkpy.camera import BlinkCamera
USERNAME = 'foobar'
PASSWORD = 'deadbeef'
@mock.patch('blinkpy.api.http_req')
class TestBlinkSyncModule(unittest.TestCase):
"""Test BlinkSyncModule functions in blinkpy."""
def setUp(self):
"""Set up Blink module."""
self.blink = blinkpy.Blink(username=USERNAME,
password=PASSWORD)
# pylint: disable=protected-access
self.blink._auth_header = {
'Host': 'test.url.tld',
'TOKEN_AUTH': 'foobar123'
}
self.blink.urls = blinkpy.BlinkURLHandler('test')
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', '1234')
self.camera = BlinkCamera(self.blink.sync)
self.mock_start = [
{'syncmodule': {
'id': 1234,
'network_id': 5678,
'serial': '12345678',
'status': 'foobar'}},
{'event': True},
{},
{},
None,
{'devicestatus': {}},
]
def tearDown(self):
"""Clean up after test."""
self.blink = None
self.camera = None
self.mock_start = None
def test_get_events(self, mock_resp):
"""Test get events function."""
mock_resp.return_value = {'event': True}
self.assertEqual(self.blink.sync['test'].get_events(), True)
def test_get_camera_info(self, mock_resp):
"""Test get camera info function."""
mock_resp.return_value = {'devicestatus': True}
self.assertEqual(self.blink.sync['test'].get_camera_info(), True)
def test_check_new_videos(self, mock_resp):
"""Test recent video response."""
mock_resp.return_value = {
'videos': [{
'camera_name': 'foo',
'address': '/foo/bar.mp4',
'created_at': '1970-01-01T00:00:00+0:00'
}]
}
sync_module = self.blink.sync['test']
sync_module.cameras = {'foo': None}
self.assertEqual(sync_module.motion, {})
self.assertTrue(sync_module.check_new_videos())
self.assertEqual(sync_module.last_record['foo'],
{'clip': '/foo/bar.mp4',
'time': '1970-01-01T00:00:00+0:00'})
self.assertEqual(sync_module.motion, {'foo': True})
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'],
{'clip': '/foo/bar.mp4',
'time': '1970-01-01T00:00:00+0:00'})
def test_check_new_videos_failed(self, mock_resp):
"""Test method when response is unexpected."""
mock_resp.side_effect = [None, 'just a string', {}]
sync_module = self.blink.sync['test']
sync_module.cameras = {'foo': None}
sync_module.motion['foo'] = True
self.assertFalse(sync_module.check_new_videos())
self.assertFalse(sync_module.motion['foo'])
sync_module.motion['foo'] = True
self.assertFalse(sync_module.check_new_videos())
self.assertFalse(sync_module.motion['foo'])
sync_module.motion['foo'] = True
self.assertFalse(sync_module.check_new_videos())
self.assertFalse(sync_module.motion['foo'])
def test_sync_start(self, mock_resp):
"""Test sync start function."""
mock_resp.side_effect = self.mock_start
self.blink.sync['test'].start()
self.assertEqual(self.blink.sync['test'].name, 'test')
self.assertEqual(self.blink.sync['test'].sync_id, 1234)
self.assertEqual(self.blink.sync['test'].network_id, 5678)
self.assertEqual(self.blink.sync['test'].serial, '12345678')
self.assertEqual(self.blink.sync['test'].status, 'foobar')
def test_unexpected_summary(self, mock_resp):
"""Test unexpected summary response."""
self.mock_start[0] = None
mock_resp.side_effect = self.mock_start
self.assertFalse(self.blink.sync['test'].start())
def test_summary_with_no_network_id(self, mock_resp):
"""Test handling of bad summary."""
self.mock_start[0]['syncmodule'] = None
mock_resp.side_effect = self.mock_start
self.assertFalse(self.blink.sync['test'].start())
def test_summary_with_only_network_id(self, mock_resp):
"""Test handling of sparse summary."""
self.mock_start[0]['syncmodule'] = {'network_id': 8675309}
mock_resp.side_effect = self.mock_start
self.blink.sync['test'].start()
self.assertEqual(self.blink.sync['test'].network_id, 8675309)
def test_unexpected_events(self, mock_resp):
"""Test unexpected events response."""
self.mock_start[1] = None
mock_resp.side_effect = self.mock_start
self.blink.sync['test'].start()
self.assertEqual(self.blink.sync['test'].events, False)
def test_missing_events(self, mock_resp):
"""Test missing events key from response."""
self.mock_start[1] = {}
mock_resp.side_effect = self.mock_start
self.blink.sync['test'].start()
self.assertEqual(self.blink.sync['test'].events, False)
def test_unexpected_camera_info(self, mock_resp):
"""Test unexpected camera info response."""
self.blink.sync['test'].cameras['foo'] = None
self.mock_start[5] = None
mock_resp.side_effect = self.mock_start
self.blink.sync['test'].start()
self.assertEqual(self.blink.sync['test'].cameras, {'foo': None})
def test_missing_camera_info(self, mock_resp):
"""Test missing key from camera info response."""
self.blink.sync['test'].cameras['foo'] = None
self.mock_start[5] = {}
self.blink.sync['test'].start()
self.assertEqual(self.blink.sync['test'].cameras, {'foo': None})
+28 -10
View File
@@ -1,23 +1,41 @@
[tox]
envlist = py34, pep8
envlist = build, py35, py36, py37, py38, lint
skip_missing_interpreters = True
skipsdist = True
[testenv]
setenv =
LAND=en_US.UTF-8
LANG=en_US.UTF-8
PYTHONPATH = {toxinidir}
commands =
py.test -v --timeout=30 --duration=10 --cov=blinkpy --cov-report html {posargs}
py.test --timeout=30 --duration=10 --cov=blinkpy --cov-report term-missing {posargs}
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/requirements_test.txt
[testenv:pep8]
[testenv:lint]
deps =
-r{toxinidir}/requirements.txt
flake8
-r{toxinidir}/requirements_test.txt
basepython = python3
ignore_errors = True
commands =
flake8 blinkpy.py
[flake8]
ignore = E501
exclude = .venv,.git,.tox,dist,doc,*lib/python,*egg,build
pylint --rcfile={toxinidir}/pylintrc blinkpy tests
flake8 blinkpy tests
pydocstyle blinkpy tests
rst-lint README.rst
rst-lint CHANGES.rst
[testenv:build]
recreate = True
skip_install = True
whitelist_externals =
/bin/sh
/bin/rm
deps =
-r{toxinidir}/requirements_test.txt
commands =
/bin/rm -rf build dist
python setup.py bdist_wheel
/bin/sh -c "pip install --upgrade dist/*.whl"
py.test tests