Package: blinkpy
blinkpyA Python library for the Blink Camera system (Python 3.9+)Like the library? Consider buying me a cup of coffee!Buy me a Coffee!BREAKING CHANGE WARNING:As of0.22.0the library uses asyncio which will break any user scripts used prior to this version. Please see the updated examples below and theblinkapp.pyorblinksync.pyexamples in theblinkapp/directory for examples on how to migrate.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.
I am in no way affiliated with Blink, nor Immedia Inc.Original protocol hacking by MattTW :https://github.com/MattTW/BlinkMonitorProtocolAPI calls faster than 60 seconds is not recommended as it can overwhelm Blink’s servers.  Please use this module responsibly.Installationpip install blinkpyInstalling Development VersionTo install the current development version, perform the following steps.  Note that the following will create a blinkpy directory in your home area:$cd~$gitclonehttps://github.com/fronzbot/blinkpy.git$cdblinkpy$pipinstall.If you’d like to contribute to this library, please read thecontributing instructions.PurposeThis library was built with the intention of allowing easy communication with Blink camera systems, specifically to support theBlink componentinhomeassistant.Quick StartThe simplest way to use this package from a terminal is to callawait Blink.start()which will prompt for your Blink username and password and then log you in.  In addition, http requests are throttled internally via use of theBlink.refresh_ratevariable, which can be set at initialization and defaults to 30 seconds.importasynciofromaiohttpimportClientSessionfromblinkpy.blinkpyimportBlinkasyncdefstart():blink=Blink(session=ClientSession())awaitblink.start()returnblinkblink=asyncio.run(start())This flow will prompt you for your username and password.  Once entered, if you likely will need to send a 2FA key to the blink servers (this pin is sent to your email address).  When you receive this pin, enter at the prompt and the Blink library will proceed with setup.Starting blink without a promptIn some cases, having an interactive command-line session is not desired.  In this case, you will need to set theBlink.auth.no_promptvalue toTrue.  In addition, since you will not be prompted with a username and password, you must supply the login data to the blink authentication handler.  This is best done by instantiating your own auth handler with a dictionary containing at least your username and password.importasynciofromaiohttpimportClientSessionfromblinkpy.blinkpyimportBlinkfromblinkpy.authimportAuthasyncdefstart():blink=Blink(session=ClientSession())# Can set no_prompt when initializing auth handlerauth=Auth({"username":<yourusername>,"password":<yourpassword>},no_prompt=True)blink.auth=authawaitblink.start()returnblinkblink=asyncio.run(start())Since you will not be prompted for any 2FA pin, you must call theblink.auth.send_auth_keyfunction.  There are two required parameters: theblinkobject as well as thekeyyou received from Blink for 2FA:awaitauth.send_auth_key(blink,<yourkey>)awaitblink.setup_post_verify()Supplying credentials from fileOther use cases may involved loading credentials from a file.  This file must bejsonformatted and contain a minimum ofusernameandpassword.  A built in function in theblinkpy.helpers.utilmodule can aid in loading this file.  Note, ifno_promptis desired, a similar flow can be followed as above.importasynciofromaiohttpimportClientSessionfromblinkpy.blinkpyimportBlinkfromblinkpy.authimportAuthfromblinkpy.helpers.utilimportjson_loadasyncdefstart():blink=Blink()auth=Auth(awaitjson_load("<File Location>"))blink.auth=authawaitblink.start()returnblinkblink=asyncio.run(start())Saving credentialsThis library also allows you to save your credentials to use in future sessions.  Saved information includes authentication tokens as well as unique ids which should allow for a more streamlined experience and limits the frequency of login requests.  This data can be saved as follows (it can then be loaded by following the instructions above for supplying credentials from a file):awaitblink.save("<File location>")Getting camerasCameras are instantiated as individualBlinkCameraclasses within aBlinkSyncModuleinstance.  All of your sync modules are stored within theBlink.syncdictionary 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:forname,camerainblink.cameras.items():print(name)# Name of the cameraprint(camera.attributes)# Print available attributes of cameraThe most recent images and videos can be accessed as a bytes-object via internal variables.  These can be updated with calls toBlink.refresh()but will only make a request if motion has been detected or other changes have been found.  This can be overridden with theforceflag, but this should be used for debugging only since it overrides the internal request throttling.camera=blink.cameras['SOME CAMERA NAME']awaitblink.refresh(force=True)# force a cache update USE WITH CAUTIONcamera.image_from_cache# bytes-like image object (jpg)camera.video_from_cache# bytes-like video object (mp4)Theblinkpyapi also allows for saving images and videos to a file and snapping a new picture from the camera remotely:camera=blink.cameras['SOME CAMERA NAME']awaitcamera.snap_picture()# Take a new picture with the cameraawaitblink.refresh()# Get new information from serverawaitcamera.image_to_file('/local/path/for/image.jpg')awaitcamera.video_to_file('/local/path/for/video.mp4')Arming BlinkMethods exist to arm/disarm the sync module, as well as enable/disable motion detection for individual cameras.  This is done as follows:# Arm a sync moduleawaitblink.sync["SYNC MODULE NAME"].async_arm(True)# Disarm a sync moduleawaitblink.sync["SYNC MODULE NAME"].async_arm(False)# Print arm status of a sync module - a system refresh should be performed firstawaitblink.refresh()sync=blink.sync["SYNC MODULE NAME"]print(f"{sync.name}status:{sync.arm}")Similar methods exist for individual cameras:camera=blink.cameras["SOME CAMERA NAME"]# Enable motion detection on a cameraawaitcamera.async_arm(True)# Disable motion detection on a cameraawaitcamera.async_arm(False)# Print arm status of a sync module - a system refresh should be performed firstawaitblink.refresh()print(f"{camera.name}status:{camera.arm}")Download videosYou can also use this library to download all videos from the server.  In order to do this, you must specify apath.  You may also specifiy a how far back in time to go to retrieve videos via thesince=variable (a simple string such as"2017/09/21"is sufficient), as well as how many pages to traverse via thestop=variable.  Note that by default, the library will search the first ten pages which is sufficient in most use cases.  Additionally, you can specify one or more cameras via thecamera=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. If you are downloading many items, setting thedelayparameter is advised in order to throttle sequential calls to the API. By default this is set to1but can be any integer representing the number of seconds to delay between calls.Example usage, which downloads all videos recorded since July 4th, 2018 at 9:34am to the/home/blinkdirectory with a 2s delay between calls:awaitblink.download_videos('/home/blink',since='2018/07/04 09:34',delay=2)Sync Module Local StorageDescription of how I think the local storage API is used by BlinkSince local storage is within a customer’s residence, there are no guarantees for latency
and availability.  As a result, the API seems to be built to deal with these conditions.In general, the approach appears to be this:  The Blink app has to query the sync
module for all information regarding the stored clips.  On a click to view a clip, the app asks
for the full list of stored clips, finds the clip in question, uploads the clip to the
cloud, and then downloads the clip back from a cloud URL. Each interaction requires polling for
the response since networking conditions are uncertain.  The app also caches recent clips and the manifest.API stepsRequest the local storage manifest be created by the sync module.POST{base_url}/api/v1/accounts/{account_id}/networks/{network_id}/sync_modules/{sync_id}/local_storage/manifest/requestReturns an ID that is used to get the manifest.Retrieve the local storage manifest.GET{base_url}/api/v1/accounts/{account_id}/networks/{network_id}/sync_modules/{sync_id}/local_storage/manifest/request/{manifest_request_id}Returns full manifest.Extract the manifest ID from the response.Find a clip ID in the clips list from the manifest to retrieve, and request an upload.POST{base_url}/api/v1/accounts/{account_id}/networks/{network_id}/sync_modules/{sync_id}/local_storage/manifest/{manifest_id}/clip/request/{clip_id}When the response is returned, the upload has finished.Download the clip using the same clip ID.GET{base_url}/api/v1/accounts/{account_id}/networks/{network_id}/sync_modules/{sync_id}/local_storage/manifest/{manifest_id}/clip/request/{clip_id}