Skip to content

Commit

Permalink
Merge pull request #9 from rado0x54/feature/martin-0.0.4
Browse files Browse the repository at this point in the history
Feature Branch 0.0.4
  • Loading branch information
rado0x54 authored Jan 29, 2020
2 parents 0b716d7 + 0991fb3 commit 7b8c3c1
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 89 deletions.
21 changes: 20 additions & 1 deletion carson_living/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
CarsonBuilding,
CarsonUser)

from carson_living.const import (EEN_ASSET_REF_ASSET,
EEN_ASSET_REF_PREV,
EEN_ASSET_REF_NEXT,
EEN_ASSET_REF_AFTER,
EEN_ASSET_CLS_ALL,
EEN_ASSET_CLS_PRE,
EEN_ASSET_CLS_THUMB,
EEN_VIDEO_FORMAT_FLV,
EEN_VIDEO_FORMAT_MP4)


__all__ = ['CarsonAuth',
'Carson',
Expand All @@ -31,4 +41,13 @@
'EagleEyeCamera',
'CarsonDoor',
'CarsonBuilding',
'CarsonUser']
'CarsonUser',
'EEN_ASSET_REF_ASSET',
'EEN_ASSET_REF_PREV',
'EEN_ASSET_REF_NEXT',
'EEN_ASSET_REF_AFTER',
'EEN_ASSET_CLS_ALL',
'EEN_ASSET_CLS_PRE',
'EEN_ASSET_CLS_THUMB',
'EEN_VIDEO_FORMAT_FLV',
'EEN_VIDEO_FORMAT_MP4']
6 changes: 3 additions & 3 deletions carson_living/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@


from carson_living.const import (BASE_HEADERS,
API_URI,
AUTH_ENDPOINT)
C_API_URI,
C_AUTH_ENDPOINT)
from carson_living.util import default_carson_response_handler
from carson_living.error import (CarsonAPIError,
CarsonAuthenticationError,
Expand Down Expand Up @@ -125,7 +125,7 @@ def update_token(self):
_LOGGER.info('Getting new access token for %s', self._username)

response = requests.post(
(API_URI + AUTH_ENDPOINT),
(C_API_URI + C_AUTH_ENDPOINT),
json={
'username': self._username,
'password': self._password,
Expand Down
6 changes: 3 additions & 3 deletions carson_living/carson.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

from carson_living.carson_entities import (CarsonUser,
CarsonBuilding)
from carson_living.const import (API_URI,
ME_ENDPOINT)
from carson_living.const import (C_API_URI,
C_ME_ENDPOINT)
from carson_living.util import update_dictionary


Expand Down Expand Up @@ -54,7 +54,7 @@ def update(self):
"""
_LOGGER.debug('Updating Carson Living API and associated entities')
url = API_URI + ME_ENDPOINT
url = C_API_URI + C_ME_ENDPOINT
me_payload = self.authenticated_query(url)

self._update_user(me_payload)
Expand Down
10 changes: 5 additions & 5 deletions carson_living/carson_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

from carson_living.eagleeye import EagleEye

from carson_living.const import (API_URI,
EAGLEEYE_SESSION_ENDPOINT,
DOOR_OPEN_ENDPOINT)
from carson_living.const import (C_API_URI,
C_EEN_SESSION_ENDPOINT,
C_DOOR_OPEN_ENDPOINT)

from carson_living.util import update_dictionary

Expand Down Expand Up @@ -73,7 +73,7 @@ def _get_eagleeye_session(carson_api, building_id):
subdomain(str): Eagle Eye subdomain to use with account
"""
url = API_URI + EAGLEEYE_SESSION_ENDPOINT.format(building_id)
url = C_API_URI + C_EEN_SESSION_ENDPOINT.format(building_id)
session = carson_api.authenticated_query(url)
return session.get('sessionId'), session.get('activeBrandSubdomain')

Expand Down Expand Up @@ -524,5 +524,5 @@ def open(self):
"""Unlock the door
"""
url = API_URI + DOOR_OPEN_ENDPOINT.format(self.entity_id)
url = C_API_URI + C_DOOR_OPEN_ENDPOINT.format(self.entity_id)
self._api.authenticated_query(url, method='post')
36 changes: 25 additions & 11 deletions carson_living/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,33 @@
# Carson API endpoints
# Beware URLs end in '/', otherwise it returns a
# HTTP/1.1 301 Moved Permanently to the correct version.
API_VERSION = 'v1.4.1'
API_URI = 'https://api.carson.live/api/' + API_VERSION
C_API_VERSION = 'v1.4.1'
C_API_URI = 'https://api.carson.live/api/' + C_API_VERSION

AUTH_ENDPOINT = '/auth/login/'
ME_ENDPOINT = '/me/'
C_AUTH_ENDPOINT = '/auth/login/'
C_ME_ENDPOINT = '/me/'

DOOR_OPEN_ENDPOINT = '/doors/{}/open/'
EAGLEEYE_SESSION_ENDPOINT = '/properties/buildings/{}/eagleeye/session/'
C_DOOR_OPEN_ENDPOINT = '/doors/{}/open/'
C_EEN_SESSION_ENDPOINT = '/properties/buildings/{}/eagleeye/session/'

# Eagle Eye API endpoints
# Beware URLs DO NOT end in '/', otherwise it returns a 500
EAGLE_EYE_API_URI = 'https://{}.eagleeyenetworks.com'
EAGLE_EYE_DEVICE_ENDPOINT = '/g/device'
EAGLE_EYE_DEVICE_LIST_ENDPOINT = '/g/device/list'
EAGLE_EYE_GET_IMAGE_ENDPOINT = '/asset/{}/image.jpeg'
EAGLE_EYE_GET_VIDEO_ENDPOINT = '/asset/play/video.{}'
EEN_API_URI = 'https://{}.eagleeyenetworks.com'
EEN_DEVICE_ENDPOINT = '/g/device'
EEN_DEVICE_LIST_ENDPOINT = '/g/device/list'
EEN_GET_IMAGE_ENDPOINT = '/asset/{}/image.jpeg'
EEN_GET_VIDEO_ENDPOINT = '/asset/play/video.{}'
EEN_IS_AUTH_ENDPOINT = '/g/aaa/isauth'

# Eagle Eye Network Interface options
EEN_ASSET_REF_ASSET = 'asset'
EEN_ASSET_REF_PREV = 'prev'
EEN_ASSET_REF_NEXT = 'next'
EEN_ASSET_REF_AFTER = 'after'

EEN_ASSET_CLS_ALL = 'all'
EEN_ASSET_CLS_PRE = 'pre'
EEN_ASSET_CLS_THUMB = 'thumb'

EEN_VIDEO_FORMAT_FLV = 'flv'
EEN_VIDEO_FORMAT_MP4 = 'mp4'
32 changes: 29 additions & 3 deletions carson_living/eagleeye.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

from carson_living.util import update_dictionary
from carson_living.const import (BASE_HEADERS,
EAGLE_EYE_API_URI,
EAGLE_EYE_DEVICE_LIST_ENDPOINT)
EEN_API_URI,
EEN_DEVICE_LIST_ENDPOINT,
EEN_IS_AUTH_ENDPOINT)

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -78,6 +79,31 @@ def update_session_auth_key(self):
self._session_auth_key = auth_key
self._session_brand_subdomain = brand_subdomain

def check_auth(self, refresh=True):
"""Check if the current auth_key is still valid
Args:
refresh:
automatically update auth_key if not valid
Returns: True if a valid auth_key exists.
"""
if not refresh and not self._session_auth_key:
return False

retry_auth = 1 if refresh else 0

try:
self.authenticated_query(
EEN_API_URI + EEN_IS_AUTH_ENDPOINT,
retry_auth=retry_auth
)
except CarsonAPIError:
return False

return True

def authenticated_query(self, url, method='get', params=None,
json=None, retry_auth=1, stream=None,
response_handler=lambda r: r.json()):
Expand Down Expand Up @@ -146,7 +172,7 @@ def update(self):
def _update_cameras(self):
# Query List
device_list = self.authenticated_query(
EAGLE_EYE_API_URI + EAGLE_EYE_DEVICE_LIST_ENDPOINT
EEN_API_URI + EEN_DEVICE_LIST_ENDPOINT
)

update_cameras = {
Expand Down
64 changes: 38 additions & 26 deletions carson_living/eagleeye_entities.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
"""Eagle Eye API Entities"""
import shutil

from datetime import timedelta
from requests import Request

from carson_living.entities import _AbstractAPIEntity

from carson_living.const import (EAGLE_EYE_API_URI,
EAGLE_EYE_DEVICE_ENDPOINT,
EAGLE_EYE_GET_IMAGE_ENDPOINT,
EAGLE_EYE_GET_VIDEO_ENDPOINT)
from carson_living.const import (EEN_API_URI,
EEN_DEVICE_ENDPOINT,
EEN_GET_IMAGE_ENDPOINT,
EEN_GET_VIDEO_ENDPOINT,
EEN_ASSET_CLS_PRE,
EEN_ASSET_REF_PREV,
EEN_VIDEO_FORMAT_FLV)

from carson_living.error import CarsonAPIError

Expand Down Expand Up @@ -103,7 +105,7 @@ def get_payload(api, camera_id):
Eagle eye entity payload
"""
url = EAGLE_EYE_API_URI + EAGLE_EYE_DEVICE_ENDPOINT
url = EEN_API_URI + EEN_DEVICE_ENDPOINT
return api.authenticated_query(
url, params={'id': camera_id})

Expand All @@ -124,17 +126,13 @@ def __str__(self):
name: {name}
account id: {account_id}
guid: {guid}
tags: {tags}
live_image: {live_image_url}
live_video: {live_video_url}"""
tags: {tags}"""
return pattern.format(
entity_id=self.entity_id,
name=self.name,
account_id=self.account_id,
guid=self.guid,
tags=', '.join(self.tags),
live_image_url=self.get_image_url(),
live_video_url=self.get_video_url(timedelta(seconds=30))
tags=', '.join(self.tags)
)

@property
Expand Down Expand Up @@ -242,7 +240,7 @@ def _get_video_timestamps(length, utc_dt, video_format):
end_ts = '+{}'.format(length_millies)

if utc_dt is None:
if video_format != 'flv':
if video_format != EEN_VIDEO_FORMAT_FLV:
raise CarsonAPIError(
'Live video streaming is only possible with .flv')
else:
Expand All @@ -253,7 +251,9 @@ def _get_video_timestamps(length, utc_dt, video_format):
return start_ts, end_ts

def get_image(self, file,
utc_dt=None, asset_ref='prev', asset_class='pre'):
utc_dt=None,
asset_ref=EEN_ASSET_REF_PREV,
asset_class=EEN_ASSET_CLS_PRE):
"""Get binary JPEG image from the camera
Args:
Expand All @@ -279,7 +279,7 @@ def _response_file_handler(response):
if utc_dt is not None:
timestamp = self.utc_to_een_timestamp(utc_dt)

url = EAGLE_EYE_API_URI + EAGLE_EYE_GET_IMAGE_ENDPOINT.format(
url = EEN_API_URI + EEN_GET_IMAGE_ENDPOINT.format(
asset_ref)
return self._api.authenticated_query(
url, params={'id': self.entity_id,
Expand All @@ -291,8 +291,10 @@ def _response_file_handler(response):
# Not this is quite duplicate at the moment, but a major refactor
# would be needed to return a prepared url via authenticated_query
def get_image_url(self, utc_dt=None,
asset_ref='prev', asset_class='pre'):
"""Get binary JPEG image from the camera
asset_ref=EEN_ASSET_REF_PREV,
asset_class=EEN_ASSET_CLS_PRE,
check_auth=True):
"""Get JPEG image URL from the camera
Args:
utc_dt:
Expand All @@ -303,18 +305,22 @@ def get_image_url(self, utc_dt=None,
asset: image at timestamp
asset_class:
all, pre, thumb
check_auth:
Check auth token and refresh
Returns:
JPEG Image
JPEG Image URL or None if not valid Auth exists
"""
if check_auth and not self._api.check_auth():
return None

timestamp = 'now'
if utc_dt is not None:
timestamp = self.utc_to_een_timestamp(utc_dt)

url = EAGLE_EYE_API_URI.format(
url = EEN_API_URI.format(
self._api.session_brand_subdomain
) + EAGLE_EYE_GET_IMAGE_ENDPOINT.format(
) + EEN_GET_IMAGE_ENDPOINT.format(
asset_ref
)

Expand All @@ -326,7 +332,8 @@ def get_image_url(self, utc_dt=None,
return prepared.url

# stream Live video to file
def get_video(self, file, length, utc_dt=None, video_format='flv'):
def get_video(self, file, length, utc_dt=None,
video_format=EEN_VIDEO_FORMAT_FLV):
"""Get a (live) video stream from the camera
Args:
Expand All @@ -345,7 +352,7 @@ def _response_file_handler(response):
start_ts, end_ts = self._get_video_timestamps(
length, utc_dt, video_format)

url = EAGLE_EYE_API_URI + EAGLE_EYE_GET_VIDEO_ENDPOINT.format(
url = EEN_API_URI + EEN_GET_VIDEO_ENDPOINT.format(
video_format)
return self._api.authenticated_query(
url, params={'id': self.entity_id,
Expand All @@ -356,24 +363,29 @@ def _response_file_handler(response):

# Not this is quite duplicate at the moment, but a major refactor
# would be needed to return a prepared url via authenticated_query
def get_video_url(self, length, utc_dt=None, video_format='flv'):
def get_video_url(self, length, utc_dt=None,
video_format=EEN_VIDEO_FORMAT_FLV, check_auth=True):
"""Get a (live) video stream from the camera
Args:
file: file handler for the response
length: of the stream in timedelta
video_format: flv or mp4
utc_dt: utc timestamp for video, live for None
check_auth: Check auth token and refresh
Returns:
Video url
Video url or None if not valid Auth exists
"""
if check_auth and not self._api.check_auth():
return None

start_ts, end_ts = self._get_video_timestamps(
length, utc_dt, video_format)

url = EAGLE_EYE_API_URI.format(
url = EEN_API_URI.format(
self._api.session_brand_subdomain
) + EAGLE_EYE_GET_VIDEO_ENDPOINT.format(
) + EEN_GET_VIDEO_ENDPOINT.format(
video_format
)

Expand Down
7 changes: 4 additions & 3 deletions scripts/carsoncli.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def main():
_bar()

three_days_ago = datetime.utcnow() - timedelta(days=3)
# download all images from 3 hours ago
# download all images from 3 days ago

# Update Session Auth Key of Eagle Eye once in a while if using
# generated authenticated URLs.
Expand All @@ -125,15 +125,16 @@ def main():
img_url = cam.get_image_url(three_days_ago)
print(img_url)
response = requests.get(img_url)
with open('image_{}_with_url.jpeg'.format(
with open('image_{}_3d_with_url.jpeg'.format(
cam.entity_id), 'wb') as file:
file.write(response.content)
# do only 1 cam.
break

try:
for cam in building.cameras:
with open('video_{}.flv'.format(cam.entity_id), 'wb') as file:
with open('video_{}_3d.flv'.format(
cam.entity_id), 'wb') as file:
cam.get_video(file, timedelta(seconds=5), three_days_ago)
except CarsonAPIError as error:
# Somehow historic videos currently return
Expand Down
Loading

0 comments on commit 7b8c3c1

Please sign in to comment.