Skip to content

Commit

Permalink
Merge pull request #1 from twrecked/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
twrecked authored Jan 30, 2019
2 parents c1ef098 + b70a89d commit 7754a7f
Show file tree
Hide file tree
Showing 17 changed files with 160 additions and 59 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@ venv.bak/

# mypy
.mypy_cache/

# backups
*~
.*.sw?
29 changes: 24 additions & 5 deletions aarlo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,46 @@
NOTIFICATION_ID = 'aarlo_notification'
NOTIFICATION_TITLE = 'aarlo Component Setup'

SCAN_INTERVAL = timedelta(seconds=60)
CONF_PACKET_DUMP = 'packet_dump'
CONF_CACHE_VIDEOS = 'cache_videos'
CONF_DB_MOTION_TIME = 'db_motion_time'
CONF_DB_DING_TIME = 'db_ding_time'

SCAN_INTERVAL = timedelta(seconds=60)
PACKET_DUMP = False
CACHE_VIDEOS = False
DB_MOTION_TIME = timedelta(seconds=30)
DB_DING_TIME = timedelta(seconds=10)

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period,
vol.Optional(CONF_PACKET_DUMP, default=PACKET_DUMP): cv.boolean,
vol.Optional(CONF_CACHE_VIDEOS, default=CACHE_VIDEOS): cv.boolean,
vol.Optional(CONF_DB_MOTION_TIME, default=DB_MOTION_TIME): cv.time_period,
vol.Optional(CONF_DB_DING_TIME, default=DB_DING_TIME): cv.time_period,
}),
}, extra=vol.ALLOW_EXTRA)


def setup(hass, config):
"""Set up an Arlo component."""
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
packet_dump = conf.get(CONF_PACKET_DUMP)
cache_videos = conf.get(CONF_CACHE_VIDEOS)
motion_time = conf.get(CONF_DB_MOTION_TIME).total_seconds()
ding_time = conf.get(CONF_DB_DING_TIME).total_seconds()

try:
from custom_components.aarlo.pyarlo import PyArlo
from custom_components.aarlo.pyaarlo import PyArlo

arlo = PyArlo( username,password )
arlo = PyArlo( username,password,
dump=packet_dump,
db_motion_time=motion_time,db_ding_time=ding_time )
if not arlo.is_connected:
return False

Expand Down
40 changes: 25 additions & 15 deletions aarlo/pyarlo/__init__.py → aarlo/pyaarlo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@

import os
import logging
import time
import datetime
import base64
import pprint
import threading

from custom_components.aarlo.pyarlo.background import ArloBackground
from custom_components.aarlo.pyarlo.storage import ArloStorage
from custom_components.aarlo.pyarlo.backend import ArloBackEnd
from custom_components.aarlo.pyarlo.media import ArloMediaLibrary
from custom_components.aarlo.pyarlo.base import ArloBase
from custom_components.aarlo.pyarlo.camera import ArloCamera
from custom_components.aarlo.pyarlo.doorbell import ArloDoorBell
from custom_components.aarlo.pyaarlo.background import ArloBackground
from custom_components.aarlo.pyaarlo.storage import ArloStorage
from custom_components.aarlo.pyaarlo.backend import ArloBackEnd
from custom_components.aarlo.pyaarlo.media import ArloMediaLibrary
from custom_components.aarlo.pyaarlo.base import ArloBase
from custom_components.aarlo.pyaarlo.camera import ArloCamera
from custom_components.aarlo.pyaarlo.doorbell import ArloDoorBell

from custom_components.aarlo.pyarlo.constant import ( BLANK_IMAGE,
from custom_components.aarlo.pyaarlo.constant import ( BLANK_IMAGE,
DEVICE_KEYS,
DEVICES_URL,
TOTAL_BELLS_KEY,
TOTAL_CAMERAS_KEY )

_LOGGER = logging.getLogger('cc.aarlo.pyarlo')
_LOGGER = logging.getLogger('pyaarlo')

class PyArlo(object):

def __init__( self,username,password,name='arlo',store='/config/state-arlo',preload=False ):
def __init__( self,username,password,name='aarlo',
storage_dir='/config/.aarlo',dump=False,max_days=365,
db_motion_time=30,db_ding_time=10 ):

try:
os.mkdir( storage_dir )
except:
pass

self._name = name
self._bg = ArloBackground( self )
self._st = ArloStorage( self,store )
self._be = ArloBackEnd( self,username,password )
self._ml = ArloMediaLibrary( self )
self._st = ArloStorage( self,name=name,storage_dir=storage_dir )
self._be = ArloBackEnd( self,username,password,dump=dump,storage_dir=storage_dir )
self._ml = ArloMediaLibrary( self,max_days=max_days )
self._lock = threading.Lock()
self._bases = []
self._cameras = []
Expand All @@ -53,10 +62,11 @@ def __init__( self,username,password,name='arlo',store='/config/state-arlo',prel
self.info('skipping ' + dname + ': state unknown')
elif dtype == 'basestation':
self._bases.append( ArloBase( dname,self,device ) )
elif dtype == 'camera':
elif dtype == 'camera' or dtype == 'arloq' or dtype == 'arloqs':
self._cameras.append( ArloCamera( dname,self,device ) )
elif dtype == 'doorbell':
self._doorbells.append( ArloDoorBell( dname,self,device ) )
self._doorbells.append( ArloDoorBell( dname,self,device,
motion_time=db_motion_time,ding_time=db_ding_time ) )

# save out unchanging stats!
self._st.set( ['ARLO',TOTAL_CAMERAS_KEY],len(self._cameras) )
Expand Down
14 changes: 9 additions & 5 deletions aarlo/pyarlo/backend.py → aarlo/pyaarlo/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@
import requests
import pprint

from custom_components.aarlo.pyarlo.sseclient import ( SSEClient )
from custom_components.aarlo.pyarlo.constant import ( LOGOUT_URL,NOTIFY_URL,
from custom_components.aarlo.pyaarlo.sseclient import ( SSEClient )
from custom_components.aarlo.pyaarlo.constant import ( LOGOUT_URL,NOTIFY_URL,
SUBSCRIBE_URL,
UNSUBSCRIBE_URL,
TRANSID_PREFIX )

# include token and session details
class ArloBackEnd(object):

def __init__( self,arlo,username,password ):
def __init__( self,arlo,username,password,dump,storage_dir ):

self._arlo = arlo
self.session = requests.Session()
self.request_lock_ = threading.Lock()
self.lock_ = threading.Condition()

self._dump = dump
self._dump_file = storage_dir + '/' + 'packets.dump'

self.requests_ = {}
self.subscriptions_ = {}
self.callbacks_ = {}
Expand Down Expand Up @@ -126,8 +129,9 @@ def _ev_loop( self,stream ):
break

response = json.loads( event.data )
with open('/config/packets.dump','a') as dump:
dump.write( pprint.pformat( response,indent=2 ) + '\n' )
if self._dump:
with open( self._dump_file,'a' ) as dump:
dump.write( pprint.pformat( response,indent=2 ) + '\n' )

# logged out? signal exited
if response.get('action') == 'logout':
Expand Down
17 changes: 16 additions & 1 deletion aarlo/pyarlo/background.py → aarlo/pyaarlo/background.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__( self ):

def _next_id( self ):
self._id += 1
return str(self._id) + ':' + str(time.monotonic)
return str(self._id) + ':' + str(time.monotonic())

def _run_next( self ):

Expand Down Expand Up @@ -72,6 +72,17 @@ def queue_job( self,run_at,prio,job ):
self._lock.notify()
return job_id

def stop_job( self,to_delete ):
with self._lock:
for prio in self._queue.keys():
for run_at,job_id in self._queue[prio].keys():
if job_id == to_delete:
#print( 'cancelling ' + str(job_id) )
del self._queue[prio][ (run_at,job_id) ]
return True
return False


class ArloBackground(threading.Thread):

def __init__( self,arlo ):
Expand Down Expand Up @@ -115,3 +126,7 @@ def run_every( self,cb,seconds,**kwargs):
def run_low_every( self,cb,seconds,**kwargs):
return self._run_every( cb,99,seconds,**kwargs)

def cancel( self,to_delete ):
if to_delete is not None:
self._worker.stop_job( to_delete )

4 changes: 2 additions & 2 deletions aarlo/pyarlo/base.py → aarlo/pyaarlo/base.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

import pprint
from custom_components.aarlo.pyarlo.device import ArloDevice
from custom_components.aarlo.pyaarlo.device import ArloDevice

from custom_components.aarlo.pyarlo.constant import ( DEFAULT_MODES,
from custom_components.aarlo.pyaarlo.constant import ( DEFAULT_MODES,
MODES_KEY,
MODE_ID_TO_NAME_KEY,
MODE_KEY,
Expand Down
8 changes: 4 additions & 4 deletions aarlo/pyarlo/camera.py → aarlo/pyaarlo/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import threading
import pprint

from custom_components.aarlo.pyarlo.device import ArloChildDevice
from custom_components.aarlo.pyarlo.util import ( http_get )
from custom_components.aarlo.pyarlo.constant import( BRIGHTNESS_KEY,
from custom_components.aarlo.pyaarlo.device import ArloChildDevice
from custom_components.aarlo.pyaarlo.util import ( http_get )
from custom_components.aarlo.pyaarlo.constant import( BRIGHTNESS_KEY,
CAPTURED_TODAY_KEY,
FLIP_KEY,
LAST_CAPTURE_KEY,
Expand Down Expand Up @@ -154,7 +154,7 @@ def update_last_image( self ):
def has_capability( self,cap ):
if cap in ( 'last_capture','captured_today','battery_level','signal_strength' ):
return True
if cap in ( 'audioDetected' ) and self.model_id.startswith('VMC4030'):
if cap in ( 'audio','audioDetected','sound' ) and self.model_id.startswith('VMC4030'):
return True
return super().has_capability( cap )

7 changes: 4 additions & 3 deletions aarlo/pyarlo/constant.py → aarlo/pyaarlo/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
SIGNAL_STR_KEY = 'signalStrength'

# we can get these from the resource; doorbell is subset
RESOURCE_KEYS = [ BATTERY_KEY, BRIGHTNESS_KEY, CONNECTION_KEY,
FLIP_KEY, MIRROR_KEY, MOTION_ENABLED_KEY, MOTION_SENS_KEY,
POWER_SAVE_KEY, SIGNAL_STR_KEY ]
RESOURCE_KEYS = [ ACTIVITY_STATE, AUDIO_DETECTED_KEY, BATTERY_KEY,
BRIGHTNESS_KEY, CONNECTION_KEY, FLIP_KEY,
MIRROR_KEY, MOTION_DETECTED_KEY, MOTION_ENABLED_KEY,
MOTION_SENS_KEY, POWER_SAVE_KEY, SIGNAL_STR_KEY ]

RESOURCE_UPDATE_KEYS = [ ACTIVITY_STATE, AUDIO_DETECTED_KEY, BATTERY_KEY,
MOTION_DETECTED_KEY, SIGNAL_STR_KEY ]
Expand Down
4 changes: 2 additions & 2 deletions aarlo/pyarlo/device.py → aarlo/pyaarlo/device.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

import pprint
import threading
import custom_components.aarlo.pyarlo.storage
import custom_components.aarlo.pyaarlo.storage

from custom_components.aarlo.pyarlo.constant import( BATTERY_KEY,
from custom_components.aarlo.pyaarlo.constant import( BATTERY_KEY,
PARENT_ID_KEY,
RESOURCE_KEYS,
RESOURCE_UPDATE_KEYS,
Expand Down
20 changes: 16 additions & 4 deletions aarlo/pyarlo/doorbell.py → aarlo/pyaarlo/doorbell.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@

import pprint
from custom_components.aarlo.pyarlo.device import ArloChildDevice
from custom_components.aarlo.pyaarlo.device import ArloChildDevice

class ArloDoorBell(ArloChildDevice):

def __init__( self,name,arlo,attrs ):
def __init__( self,name,arlo,attrs,motion_time,ding_time ):
super().__init__( name,arlo,attrs )
self._mt = motion_time
self._mt_job = None
self._dt = ding_time
self._dt_job = None

def _motion_stopped( self ):
self._save_and_do_callbacks( 'motionDetected',False )
with self._lock:
self._mt_job = None

def _button_unpressed( self ):
self._save_and_do_callbacks( 'buttonPressed',False )
with self._lock:
self._dt_job = None

def _event_handler( self,resource,event ):
self._arlo.info( self.name + ' DOORBELL got one ' + resource )
Expand All @@ -23,10 +31,14 @@ def _event_handler( self,resource,event ):
#acts = event.get('properties',{}).get('activityState',False)
if cons and cons == 'available':
self._save_and_do_callbacks( 'motionDetected',True )
self._arlo._bg.run_in( self._motion_stopped,15 )
with self._lock:
self._arlo._bg.cancel( self._mt_job )
self._mt_job = self._arlo._bg.run_in( self._motion_stopped,self._mt )
if butp:
self._save_and_do_callbacks( 'buttonPressed',True )
self._arlo._bg.run_in( self._button_unpressed,5 )
with self._lock:
self._arlo._bg.cancel( self._dt_job )
self._dt_job = self._arlo._bg.run_in( self._button_unpressed,self._dt )
# if acts and acts == 'idle':
# self._save_and_do_callbacks( 'motionDetected',False )
# self._save_and_do_callbacks( 'buttonPressed',False )
Expand Down
6 changes: 3 additions & 3 deletions aarlo/pyarlo/media.py → aarlo/pyaarlo/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
import threading
from datetime import datetime
from datetime import timedelta
from custom_components.aarlo.pyarlo.constant import ( LIBRARY_URL,
from custom_components.aarlo.pyaarlo.constant import ( LIBRARY_URL,
PRELOAD_DAYS )
from custom_components.aarlo.pyarlo.util import ( arlotime_strftime,
from custom_components.aarlo.pyaarlo.util import ( arlotime_strftime,
arlotime_to_datetime,
http_stream,
http_get )

class ArloMediaLibrary(object):
"""Arlo Library Media module implementation."""

def __init__( self,arlo ):
def __init__( self,arlo,max_days ):
self._arlo = arlo
self._lock = threading.Lock()
self._load_cbs_ = []
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions aarlo/pyarlo/storage.py → aarlo/pyaarlo/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

class ArloStorage(object):

def __init__( self,arlo,file_name ):
def __init__( self,arlo,name,storage_dir ):
self._arlo = arlo
self.file = file_name + '.pickle'
self.file = storage_dir + '/' + name + '.pickle'
self.db = {}
self.lock = threading.Lock()
self.load()
Expand Down
File renamed without changes.
26 changes: 13 additions & 13 deletions camera/aarlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ def __init__(self, hass, camera, device_info):
self._unique_id = self._name.lower().replace(' ','_')
self._camera = camera
self._state = None
self._on = True
self._motion_status = False
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
Expand All @@ -86,11 +85,16 @@ async def async_added_to_hass(self):
def update_state( device,attr,value ):
_LOGGER.info( 'callback:' + self._name + ':' + attr + ':' + str(value)[:80])

# set state
if attr == 'activityState':
self._state = value
if attr == 'connectionState':
self._on = value != 'thermalShutdownCold'
# set state
if attr == 'activityState' or attr == 'connectionState':
if value == 'thermalShutdownCold':
self._state = 'Offline, Too Cold'
elif value == 'userStreamActive':
self._state = STATE_STREAMING
elif value == 'alertStreamActive':
self._state = STATE_RECORDING
else:
self._state = STATE_IDLE

self.async_schedule_update_ha_state()

Expand Down Expand Up @@ -128,20 +132,16 @@ def unique_id(self):

@property
def is_recording(self):
return self._state == 'alertStreamActive'
return self._state == STATE_RECORDING

@property
def is_on(self):
return self._on
return True

@property
def state(self):
"""Return the camera state."""
if self.is_recording:
return STATE_RECORDING
if self._state == 'userStreamActive':
return STATE_STREAMING
return STATE_IDLE
return self._state

@property
def device_state_attributes(self):
Expand Down
Loading

0 comments on commit 7754a7f

Please sign in to comment.