Compare commits

...
31 Commits
Author SHA1 Message Date
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 Fronczak ea2b22dbf0 Dev version bump 2018-11-20 21:46:38 -05:00
15 changed files with 237 additions and 150 deletions
+36 -34
View File
@@ -1,5 +1,5 @@
# BlinkMonitorProtocol
Unofficial documentation for the Client API of the Blink Wire-Free HD Home Monitoring and Alert System.
Unofficial documentation for the Client API of the Blink Wire-Free HD Home Monitoring & Alert System.
Copied from https://github.com/MattTW/BlinkMonitorProtocol
@@ -7,9 +7,6 @@ I am not affiliated with the company in any way - this documentation is strictly
The Client API is a straightforward REST API using JSON and HTTPS.
## This Document
The purpose here is to describe what is going on behind the scenes, and what commands we know are available to communicate with Blink servers. This is NOT a description of the blinkpy module, but a description of the commands blinkpy relies on to effectively communicate.
## Login
Client login to the Blink Servers.
@@ -19,20 +16,25 @@ Client login to the Blink Servers.
> "password" : "*your blink password*",
> "client_specifier" : "iPhone 9.2 | 2.2 | 222",
> "email" : "*your blink login/email*"
>}' --compressed https://prod.immedia-semi.com/login
>}' --compressed https://rest.prod.immedia-semi.com/login
**Response:**
>{"authtoken":{"authtoken":"*an auth token*","message":"auth"}}
>{"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
## 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://prod.immedia-semi.com/networks
>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.
@@ -46,7 +48,7 @@ Network ID is needed to issue arm/disarm calls
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://prod.immedia-semi.com/network/*network_id_from_networks_call*/syncmodules
>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
@@ -56,11 +58,11 @@ Probably not strictly needed but checking result can verify that the sync module
## 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://prod.immedia-semi.com/network/*network_id_from_networks_call*/arm
>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
@@ -73,7 +75,7 @@ When this call returns, it does not mean the arm request is complete, the clien
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://prod.immedia-semi.com/network/*network_id_from_networks_call*/disarm
>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
@@ -87,7 +89,7 @@ When this call returns, it does not mean the disarm request is complete, the cl
Get status info on the given command
**Request:**
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://prod.immedia-semi.com/network/*network_id*/command/*command_id*
>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.
@@ -103,7 +105,7 @@ lv_relay, arm, disarm, thumbnail, clip
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://prod.immedia-semi.com/homescreen
>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.
@@ -111,12 +113,12 @@ JSON response containing information that the mobile client displays on the home
**Notes:**
Not necessary to as part of issuing arm/disarm commands, but contains good summary info.
##Events, thumbnails & video captures
## 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://prod.immedia-semi.com/events/network/*network__id*
>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
@@ -144,7 +146,7 @@ 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://prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/thumbnail
>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.
@@ -152,17 +154,17 @@ 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://prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/clip
>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
## 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://prod.immedia-semi.com/api/v2/videos/count
>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.
@@ -170,7 +172,7 @@ 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://prod.immedia-semi.com/api/v2/videos/page/0
>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
@@ -178,7 +180,7 @@ JSON response containing a set of video information, including: camera name, cre
**Request**
Gets information for a specific video by ID
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://prod.immedia-semi.com/api/v2/video/*video_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
@@ -186,7 +188,7 @@ 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://prod.immedia-semi.com/api/v2/videos/unwatched
>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
@@ -194,7 +196,7 @@ 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://prod.immedia-semi.com/api/v2/video/*video_id*/delete
>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
@@ -202,7 +204,7 @@ Unknown - not tested
**Request**
Deletes all videos
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --data-binary --compressed https://prod.immedia-semi.com/api/v2/videos/deleteall
>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
@@ -212,7 +214,7 @@ Unknown - not tested
**Request**
Gets a list of cameras
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://prod.immedia-semi.com/network/*network_id*/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
@@ -220,7 +222,7 @@ 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://prod.immedia-semi.com/network/*network_id*/camera/*camera_id*
>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
@@ -228,7 +230,7 @@ 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://prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/signals
>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
@@ -236,7 +238,7 @@ JSON response containing camera sensor information, such as wifi strength, tempe
**Request**
Enables motion detection for one camera
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: $auth_token" --data-binary --compressed https://prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/enable
>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
@@ -244,7 +246,7 @@ 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://prod.immedia-semi.com/network/*network_id*/camera/*camera_id*/disable
>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
@@ -257,7 +259,7 @@ JSON response containing camera information
**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://prod.immedia-semi.com/account/clients
>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
@@ -265,7 +267,7 @@ JSON response containing client information, including: type, name, connection t
**Request**
Gets information about supported regions
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://prod.immedia-semi.com/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
@@ -273,7 +275,7 @@ 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://prod.immedia-semi.com/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"
@@ -281,7 +283,7 @@ Gets information about system health
**Request**
Gets information about programs
>curl -H "Host: prod.immedia-semi.com" -H "TOKEN_AUTH: *authtoken from login*" --compressed https://prod.immedia-semi.com/api/v1/networks/*network_id*/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.
+10
View File
@@ -3,6 +3,16 @@ Changelog
A list of changes between each release
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>`__)
+41 -35
View File
@@ -4,41 +4,55 @@ Everyone is welcome to contribute to blinkpy! The process to get started is desc
## Fork the Repository
You can do this right in gituhb: just click the 'fork' button at the top right.
You can do this right in github: just click the 'fork' button at the top right.
## Setup Local Repository
## Start Developing
```shell
$ git clone https://github.com/<YOUR_GIT_USERNAME>/blinkpy.git
$ cd blinkpy
$ git remote add upstream https://github.com/fronzbot/blinkpy.git
```
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
```
## Create a Local Branch
2. Create a Local Branch
First, you will want to create a new branch to hold your changes:
``git checkout -b <your-branch-name>``
First, you will want to create a new branch to hold your changes:
``git checkout -b <your-branch-name>``
Next, you need to make sure you pull from the 'dev' branch:
``git pull origin dev``
## Make changes
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)
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.
## 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
```
## 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``
First, you need to locally install ``tox``
```shell
$ pip3 install tox
@@ -52,11 +66,11 @@ $ tox
### Tips
If you only want to see if you can pass the local tests, you can run ``tox -e py34``. 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 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 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`
Please do not locally disable any linter warnings within the ``blinkpy.py`` module itself (it's ok to do this in any of the ``test_*.py`` files)
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
@@ -73,11 +87,3 @@ If rebase detects conflicts, repeat the following process until all changes have
2. Add the modified file: ``git add <file>`` or ``git add .``.
3. Continue rebase: ``git rebase --continue``.
4. Repeat until all conflicts resolved.
# Creating a Pull Request
Please follow these steps to create a pull request against the ``dev`` branch: [Creating a Pull Request](https://help.github.com/articles/creating-a-pull-request/)
# Monitor Build Status
Once you create your PR, you can monitor the status of your build [here](https://travis-ci.org/fronzbot/blinkpy), Your code will be tested to ensure it passes and won't cause any problems after merging.
+13 -10
View File
@@ -1,7 +1,6 @@
blinkpy |Build Status| |Coverage Status| |Docs| |PyPi Version| |Python Version|
================================================================================
A Python library for the Blink Camera system
Only compatible with Python 3+
Disclaimer:
~~~~~~~~~~~~~~~
@@ -32,12 +31,16 @@ To install the current development version, perform the following steps. Note t
$ 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
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/>`__.
Usage
=========
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
@@ -48,7 +51,7 @@ The simplest way to use this package from a terminal is to call ``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.
Cameras are instantiated as individual ``BlinkCamera`` classes within a ``BlinkSyncModule`` instance. Note: currently the API only supports one sync module, but multiple sync modules are planned to be supported in the future.
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:
@@ -59,15 +62,15 @@ The below code will display cameras and their available attributes:
blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD')
blink.start()
for name, camera in blink.sync.cameras.items():
print(name) # Name of the camera
print(camera.attributes) # Print available attributes of camera
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.sync.camera['SOME CAMERA NAME']
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)
@@ -76,7 +79,7 @@ The ``blinkpy`` api also allows for saving images and videos to a file and snapp
.. code:: python
camera = blink.sync.camera['SOME CAMERA NAME']
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')
+32 -17
View File
@@ -14,14 +14,15 @@ I am in no way affiliated with Blink, nor Immedia Inc.
import time
import getpass
import logging
from requests.structures import CaseInsensitiveDict
import blinkpy.helpers.errors as ERROR
from blinkpy import api
from blinkpy.sync_module import BlinkSyncModule
from blinkpy.helpers.util import (
create_session, BlinkURLHandler,
create_session, merge_dicts, BlinkURLHandler,
BlinkAuthenticationException)
from blinkpy.helpers.constants import (
BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL, PROJECT_URL)
BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL)
REFRESH_RATE = 30
@@ -46,16 +47,17 @@ class Blink():
self._token = None
self._auth_header = None
self._host = None
self.network_id = None
self.account_id = None
self.network_ids = []
self.urls = None
self.sync = None
self.sync = CaseInsensitiveDict({})
self.region = None
self.region_id = None
self.last_refresh = None
self.refresh_rate = refresh_rate
self.session = None
self.networks = []
self.cameras = CaseInsensitiveDict({})
self._login_url = LOGIN_URL
@property
@@ -75,9 +77,12 @@ class Blink():
else:
self.get_auth_token()
self.get_ids()
self.sync = BlinkSyncModule(self)
self.sync.start()
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."""
@@ -136,18 +141,20 @@ class Blink():
# Look for only onboarded network, flag warning if multiple
# since it's unexpected
all_networks = []
network_dict = {}
for network, status in self.networks.items():
if status['onboarded']:
all_networks.append(network)
self.network_id = all_networks.pop(0)
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']) == self.network_id:
if str(resp['id']) in all_networks:
self.account_id = resp['account_id']
if all_networks:
_LOGGER.warning(("More than one onboarded network. "
"Platform may not work as intended. "
"If you experience problems, please "
"open an issue on %s"), PROJECT_URL)
break
self.network_ids = all_networks
return network_dict
def refresh(self, force_cache=False):
"""
@@ -156,8 +163,9 @@ class Blink():
:param force_cache: Force an update of the camera cache
"""
if self.check_if_ok_to_update() or force_cache:
_LOGGER.debug("Attempting refresh of cameras.")
self.sync.refresh(force_cache=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)
def check_if_ok_to_update(self):
"""Check if it is ok to perform an http request."""
@@ -169,3 +177,10 @@ class Blink():
self.last_refresh = current_time
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
+12
View File
@@ -24,6 +24,7 @@ class BlinkCamera():
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
@@ -40,6 +41,7 @@ class BlinkCamera():
'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,
@@ -47,6 +49,7 @@ class BlinkCamera():
'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
@@ -103,6 +106,15 @@ class BlinkCamera():
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:
_LOGGER.error("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
+6 -6
View File
@@ -3,8 +3,8 @@
import os
MAJOR_VERSION = 0
MINOR_VERSION = 10
PATCH_VERSION = 3
MINOR_VERSION = 11
PATCH_VERSION = 1
__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
@@ -45,10 +45,10 @@ PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME)
URLS
'''
BLINK_URL = 'immedia-semi.com'
LOGIN_URL = 'https://prod.' + BLINK_URL + '/login'
LOGIN_BACKUP_URL = 'https://rest.piri/' + BLINK_URL + '/login'
BASE_URL = 'https://prod.' + BLINK_URL
DEFAULT_URL = 'prod.' + BLINK_URL
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
+9
View File
@@ -9,6 +9,15 @@ import blinkpy.helpers.errors as ERROR
_LOGGER = logging.getLogger(__name__)
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()
+3 -4
View File
@@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
class BlinkSyncModule():
"""Class to initialize sync module."""
def __init__(self, blink):
def __init__(self, blink, network_name, network_id):
"""
Initialize Blink sync module.
@@ -21,10 +21,10 @@ class BlinkSyncModule():
"""
self.blink = blink
self._auth_header = blink.auth_header
self.network_id = blink.network_id
self.network_id = network_id
self.region = blink.region
self.region_id = blink.region_id
self.name = 'sync'
self.name = network_name
self.serial = None
self.status = None
self.sync_id = None
@@ -79,7 +79,6 @@ class BlinkSyncModule():
"""Initialize the system."""
response = api.request_syncmodule(self.blink, self.network_id)
self.summary = response['syncmodule']
self.name = self.summary['name']
self.sync_id = self.summary['id']
self.network_id = self.summary['network_id']
self.serial = self.summary['serial']
+4 -17
View File
@@ -1,30 +1,17 @@
.. _core_module:
:mod:`blinkpy`
Blinkpy
----------------------
.. automodule:: blinkpy.blinkpy
:members:
.. automodule:: blinkpy.sync_module
:members:
.. automodule:: blinkpy.camera
:members:
.. automodule:: blinkpy.helpers.util
.. autoclass:: blinkpy.blinkpy.Blink
:members:
.. autoclass:: blinkpy.sync_module.BlinkSyncModule
:members:
.. autoclass:: blinkpy.camera.BlinkCamera
:members:
.. autoclass:: blinkpy.helpers.util.BlinkURLHandler
:members:
.. autofunction:: blinkpy.helpers.util.create_session
.. autofunction:: blinkpy.helpers.util.http_req
.. autofunction:: blinkpy.helpers.util.attempt_reauthorization
+6
View File
@@ -0,0 +1,6 @@
API Reference
----------------------
.. automodule:: blinkpy.api
:members:
+12
View File
@@ -70,3 +70,15 @@ class TestBlinkFunctions(unittest.TestCase):
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)
+25 -5
View File
@@ -30,7 +30,7 @@ class TestBlinkSetup(unittest.TestCase):
self.blink_no_cred = Blink()
self.blink = Blink(username=USERNAME,
password=PASSWORD)
self.blink.sync = BlinkSyncModule(self.blink)
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', '1234')
self.blink.urls = BlinkURLHandler('test')
self.blink.session = create_session()
@@ -110,13 +110,28 @@ class TestBlinkSetup(unittest.TestCase):
'networks': [{'id': 1234, 'account_id': 1111},
{'id': 5678, 'account_id': 2222}]
}
self.blink.networks = {'0000': {'onboarded': False},
'5678': {'onboarded': True},
'1234': {'onboarded': False}}
self.blink.networks = {'0000': {'onboarded': False, 'name': 'foo'},
'5678': {'onboarded': True, 'name': 'bar'},
'1234': {'onboarded': False, 'name': 'test'}}
self.blink.get_ids()
self.assertEqual(self.blink.network_id, '5678')
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."""
@@ -128,3 +143,8 @@ class TestBlinkSetup(unittest.TestCase):
self.assertEqual(result, True)
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')
+12 -5
View File
@@ -46,10 +46,10 @@ class TestBlinkCameraSetup(unittest.TestCase):
self.blink._auth_header = header
self.blink.session = create_session()
self.blink.urls = BlinkURLHandler('test')
self.blink.sync = BlinkSyncModule(self.blink)
self.camera = BlinkCamera(self.blink.sync)
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', 1234)
self.camera = BlinkCamera(self.blink.sync['test'])
self.camera.name = 'foobar'
self.blink.sync.cameras['foobar'] = self.camera
self.blink.sync['test'].cameras['foobar'] = self.camera
def tearDown(self):
"""Clean up after test."""
@@ -98,6 +98,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
self.camera.last_record = ['1']
self.camera.sync.all_clips = {'new': {'1': '/test.mp4'}}
mock_sess.side_effect = [
mresp.MockResponse({'temp': 71}, 200),
'test',
'foobar'
]
@@ -110,6 +111,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
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')
@@ -120,7 +122,11 @@ class TestBlinkCameraSetup(unittest.TestCase):
def test_thumbnail_not_in_info(self, mock_sess):
"""Test that we grab thumbanil if not in camera_info."""
mock_sess.side_effect = ['foobar', 'barfoo']
mock_sess.side_effect = [
mresp.MockResponse({'temp': 71}, 200),
'foobar',
'barfoo'
]
self.camera.last_record = ['1']
self.camera.sync.record_dates['new'] = ['1']
self.camera.sync.all_clips = {'new': {'1': '/test.mp4'}}
@@ -175,7 +181,8 @@ class TestBlinkCameraSetup(unittest.TestCase):
self.assertEqual(self.camera.thumbnail, None)
self.assertEqual(
logrecord.output,
["ERROR:blinkpy.camera:Could not find thumbnail for camera new"]
["ERROR:blinkpy.camera:Could not retrieve calibrated temperature.",
"ERROR:blinkpy.camera:Could not find thumbnail for camera new"]
)
def test_no_video_clips(self, mock_sess):
+16 -17
View File
@@ -24,7 +24,7 @@ class TestBlinkSyncModule(unittest.TestCase):
'TOKEN_AUTH': 'foobar123'
}
self.blink.urls = blinkpy.BlinkURLHandler('test')
self.blink.sync = BlinkSyncModule(self.blink)
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', '1234')
self.camera = BlinkCamera(self.blink.sync)
def tearDown(self):
@@ -35,12 +35,12 @@ class TestBlinkSyncModule(unittest.TestCase):
def test_get_events(self, mock_resp):
"""Test get events function."""
mock_resp.return_value = {'event': True}
self.assertEqual(self.blink.sync.get_events(), 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.get_camera_info(), True)
self.assertEqual(self.blink.sync['test'].get_camera_info(), True)
def test_get_videos_one_page(self, mock_resp):
"""Test video access."""
@@ -54,13 +54,13 @@ class TestBlinkSyncModule(unittest.TestCase):
expected_videos = {'foobar': [
{'clip': '/test/clip_1900_01_01_12_00_00AM.mp4',
'thumb': '/test/thumb'}]}
expected_records = {'foobar': ['1900_01_01_12_00_00AM']}
expected_recs = {'foobar': ['1900_01_01_12_00_00AM']}
expected_clips = {'foobar': {
'1900_01_01_12_00_00AM': '/test/clip_1900_01_01_12_00_00AM.mp4'}}
self.blink.sync.get_videos(start_page=0, end_page=0)
self.assertEqual(self.blink.sync.videos, expected_videos)
self.assertEqual(self.blink.sync.record_dates, expected_records)
self.assertEqual(self.blink.sync.all_clips, expected_clips)
self.blink.sync['test'].get_videos(start_page=0, end_page=0)
self.assertEqual(self.blink.sync['test'].videos, expected_videos)
self.assertEqual(self.blink.sync['test'].record_dates, expected_recs)
self.assertEqual(self.blink.sync['test'].all_clips, expected_clips)
def test_get_videos_multi_page(self, mock_resp):
"""Test video access with multiple pages."""
@@ -71,17 +71,16 @@ class TestBlinkSyncModule(unittest.TestCase):
'thumbnail': '/foobar'
}
]
self.blink.sync.get_videos()
self.blink.sync['test'].get_videos()
self.assertEqual(mock_resp.call_count, 2)
mock_resp.reset_mock()
self.blink.sync.get_videos(start_page=0, end_page=9)
self.blink.sync['test'].get_videos(start_page=0, end_page=9)
self.assertEqual(mock_resp.call_count, 10)
def test_sync_start(self, mock_resp):
"""Test sync start function."""
mock_resp.side_effect = [
{'syncmodule': {
'name': 'test',
'id': 1234,
'network_id': 5678,
'serial': '12345678',
@@ -93,9 +92,9 @@ class TestBlinkSyncModule(unittest.TestCase):
None,
None
]
self.blink.sync.start()
self.assertEqual(self.blink.sync.name, 'test')
self.assertEqual(self.blink.sync.sync_id, 1234)
self.assertEqual(self.blink.sync.network_id, 5678)
self.assertEqual(self.blink.sync.serial, '12345678')
self.assertEqual(self.blink.sync.status, 'foobar')
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')