class LogicalConnection: # This class takes: Device Obj, CallBack, Reconnect Frequency, Label Obj def __init__(self, ModuleObj, CallBackMethod, ReconnectTimer=5, Label=None): self.StoredWait = None self.Module = ModuleObj self.ConnectionStatusLbl = Label self.ReconnectTimer = ReconnectTimer self.CallBackMethod = CallBackMethod self.LogicalMessage = '' self.PhysicalMessage = '' self.Module.SubscribeStatus('ConnectionStatus', None, self.ModuleStatus) if self.Module.ConnectionType == 'Ethernet': self.DoConnect() def UpdateConnectionStatus(self): ''' Calls the CallbackMethod and Updates Label when required ''' value = '{0} {1}'.format(self.LogicalMessage, self.PhysicalMessage) self.CallBackMethod(value) # if Label exist, Update Label if self.ConnectionStatusLbl: self.ConnectionStatusLbl.SetText(value) def ModuleStatus(self, command, value, qualifier): if value == 'Connected': self.LogicalMessage = 'Logical:Connected' if self.StoredWait: self.StoredWait.Cancel() else: self.LogicalMessage = 'Logical:Disconnected' self.Module.Disconnect() self.Module.OnDisconnected() # Only for Ethernet Communication if self.Module.ConnectionType == 'Ethernet': if not self.StoredWait: self.StoredWait = Wait(self.ReconnectTimer, self.DoConnect) else: self.StoredWait.Restart() # When Logical Connections status changes from Extron Module self.UpdateConnectionStatus() def DoConnect(self): result = self.Module.Connect(self.ReconnectTimer) if result == 'Connected': self.PhysicalMessage = 'Physical:Connected' elif result == 'TimedOut': self.LogicalMessage = 'Logical:Disconnected' self.PhysicalMessage = 'Physical:Reconnecting' elif result == 'HostError': self.LogicalMessage = 'Logical:Disconnected' self.PhysicalMessage = 'Physical:Reconnecting' self.UpdateConnectionStatus() if self.StoredWait: self.StoredWait.Restart()
def __init__(self, username, password, debug=False, persist_token=False, push_token_notify_url="http://localhost/", reuse_session=True, cache_file=CACHE_FILENAME): """Initialize the Ring object.""" print('Initialize the Ring object.') self.is_connected = None self.token = None self.params = None self._persist_token = persist_token self._push_token_notify_url = push_token_notify_url self.debug = debug self.username = username self.password = password self.session = requests.Session() self.cache = CACHE_ATTRS self.cache['account'] = self.username self.cache_file = cache_file self._reuse_session = reuse_session self._knownEvents = defaultdict(dict) # int(ID): dict(eventDetails) # } self._scriptStartDT = datetime.utcnow( ) # ignore events that happen before this DT self._motionEventCallback = None self._dingEventCallback = None self._connectedCallback = None self._disconnectedCallback = None self._otherEventCallback = None self._wait_Update = Wait(60, self._Update) self._maxLen = 15 # tries to re-use old session if self._reuse_session: self.cache['token'] = self.token self._process_cached_session() else: try: self._authenticate() except: pass self._Update() # force update on boot up
def ModuleStatus(self, command, value, qualifier): if value == 'Connected': self.LogicalMessage = 'Logical:Connected' if self.StoredWait: self.StoredWait.Cancel() else: self.LogicalMessage = 'Logical:Disconnected' self.Module.Disconnect() self.Module.OnDisconnected() # Only for Ethernet Communication if self.Module.ConnectionType == 'Ethernet': if not self.StoredWait: self.StoredWait = Wait(self.ReconnectTimer, self.DoConnect) else: self.StoredWait.Restart() # When Logical Connections status changes from Extron Module self.UpdateConnectionStatus()
def autoShutdown(): date = datetime.datetime.now() hour = date.strftime('%H') minute = date.strftime('%M') if hour == '22' and minute == '28': print('autoshutdown at {0}:{1}'.format(hour, minute)) confirmShutdown() Wait(30, autoShutdown)
def poll_events(self): date = datetime.datetime.now() hour = date.strftime('%H') minute = date.strftime('%M') day = date.strftime('%A') for event in self._events: if event._hour == hour \ and event._minute == minute \ and day in event._days: event._function() Wait(31.0, self.poll_events)
def connectSMD202(): connectionStatus = '' try: connectionStatus = devices.SMD202EthernetClient.Connect(5) except: print('Error occured connecting to SMD202 [{0}]'.format( devices.SMD202EthernetClient.IPAddress)) print(connectionStatus) if connectionStatus == 'Connected': print('SMD202 connected') def pollSMDPlayState(): devices.SMD202EthernetClient.Send('WK1PLYR\r') Wait(0.5, pollSMDPlayState) pollSMDPlayState() else: print('SMD202 connection failed...retrying') Wait(30, connectSMD202)
def delay(self, time): # stop current timer and create a new one with new time self.stop() self._pollingWait = Wait(time, self._polling_sequence)
def start(self): # Create Wait object to call polling_sequence if not self._pollingWait: self._pollingWait = Wait(self.interval, self._polling_sequence) else: self._pollingWait.Restart()
class PollingEngine: def __init__(self, deviceObject, queryList=[], interval=1): # Device object. Instance of SerialClass or EthernetClass from modules self._deviceObject = deviceObject # List of queries self._queryList = queryList # time between each query self.interval = float(interval) # store global Wait object self._pollingWait = None # initial start index self.index = 0 def _polling_sequence(self): if self._pollingWait and len(self._queryList): # reset index if it is at max if self.index >= len(self._queryList): self.index = 0 query = self._queryList[self.index] self._deviceObject.Update(query['Update'], query['Qualifier']) self.index += 1 # if current timer's time is different from self.interval, # create new timer with self.interval time if self._pollingWait.Time != self.interval: self.stop() self.start() else: self._pollingWait.Restart() def add_query(self, query): # Check to make sure query argument has valid keys and is a dictionary if isinstance(query, dict): if query.__contains__('Update') and query.__contains__( 'Qualifier'): # Help avoid raise conditions (possibly) if self._pollingWait: self.stop() self._queryList.append(query) self.start() else: self._queryList.append(query) else: raise KeyError( 'query dictionary does not have Update and Qualifier keys') else: raise TypeError('query argument is not a dictionary') def remove_query(self, query): # Check to make sure query argument has valid keys and is a dictionary if isinstance(query, dict): if query.__contains__('Update') and query.__contains__( 'Qualifier'): # Help avoid race conditions (possibly) if self._pollingWait: self.stop() self._queryList.remove(query) self.start() else: self._queryList.remove(query) else: raise KeyError( 'query dictionary does not have Update and Qualifier keys') else: raise TypeError('query argument is not a dictionary') def start(self): # Create Wait object to call polling_sequence if not self._pollingWait: self._pollingWait = Wait(self.interval, self._polling_sequence) else: self._pollingWait.Restart() def stop(self): if self._pollingWait: self._pollingWait.Cancel() self._pollingWait = None def delay(self, time): # stop current timer and create a new one with new time self.stop() self._pollingWait = Wait(time, self._polling_sequence)
def pollSMDPlayState(): devices.SMD202EthernetClient.Send('WK1PLYR\r') Wait(0.5, pollSMDPlayState)
class Ring(object): """A Python Abstraction object to Ring Door Bell.""" def __init__(self, username, password, debug=False, persist_token=False, push_token_notify_url="http://localhost/", reuse_session=True, cache_file=CACHE_FILENAME): """Initialize the Ring object.""" print('Initialize the Ring object.') self.is_connected = None self.token = None self.params = None self._persist_token = persist_token self._push_token_notify_url = push_token_notify_url self.debug = debug self.username = username self.password = password self.session = requests.Session() self.cache = CACHE_ATTRS self.cache['account'] = self.username self.cache_file = cache_file self._reuse_session = reuse_session self._knownEvents = defaultdict(dict) # int(ID): dict(eventDetails) # } self._scriptStartDT = datetime.utcnow( ) # ignore events that happen before this DT self._motionEventCallback = None self._dingEventCallback = None self._connectedCallback = None self._disconnectedCallback = None self._otherEventCallback = None self._wait_Update = Wait(60, self._Update) self._maxLen = 15 # tries to re-use old session if self._reuse_session: self.cache['token'] = self.token self._process_cached_session() else: try: self._authenticate() except: pass self._Update() # force update on boot up @property def Motion(self): return self._motionEventCallback @Motion.setter def Motion(self, callback): # callback should accept two params, str(deviceName), dict(eventDetails) self._motionEventCallback = callback @property def Ding(self): return self._dingEventCallback @Ding.setter def Ding(self, callback): # callback should accept two params, str(deviceName), dict(eventDetails) self._dingEventCallback = callback @property def Other(self): return self._otherEventCallback @Other.setter def Other(self, callback): # callback should accept two params, str(deviceName), dict(eventDetails) self._otherEventCallback = callback @property def Connected(self): return self._connectedCallback @Connected.setter def Connected(self, callback): # callback should accept two params, self, str('Connected') self._connectedCallback = callback if self.is_connected: self._connectedCallback(self, 'Connected') @property def Disconnected(self): return self._disconnectedCallback @Disconnected.setter def Disconnected(self, callback): # callback should accept two params, self, str('Disconnected') self._disconnectedCallback = callback if not self.is_connected: self._disconnectedCallback(self, 'Disconnected') def _Update(self): print('_Update') if not self.is_connected: try: self._authenticate() except: self._wait_Update.Restart() return self._maxLen = 0 for devType, devList in self.devices.items(): for device in devList: self._maxLen += 15 for event in device.history(limit=15): ID = event['id'] dt = event['created_at'] if dt < self._scriptStartDT: # ignore events that happened before this script was started continue if ID not in self._knownEvents: # this is a new event, trigger a callback if event['kind'] == 'ding': if self._disconnectedCallback: self._dingEventCallback( device.name, dict(event)) elif event['kind'] == 'motion': if self._motionEventCallback: self._motionEventCallback( device.name, dict(event)) else: if self._otherEventCallback: self._otherEventCallback( device.name, dict(event)) # save the event into memory self._knownEvents[ID] = dict(event) if len(self._knownEvents) > self._maxLen: self._ClearOldEvents() self._wait_Update.Restart() def _ClearOldEvents(self): # prevent memory leak from storing too many events in memory # if every device returned 15 events, then this would be the max len # we dont need to hold any more events than this while len(self._knownEvents) > self._maxLen: oldestEvent = None for _, evt in self._knownEvents.items(): if oldestEvent is None: oldestEvent = evt else: if evt['created_at'] < oldestEvent['created_at']: oldestEvent = evt removed = self._knownEvents.pop(oldestEvent['id']) print('removed=', removed) def _process_cached_session(self): """Process cache_file to reuse token instead.""" if _exists_cache(self.cache_file): self.cache = _read_cache(self.cache_file) # if self.cache['token'] is None, the cache file was corrupted. # of if self.cache['account'] does not match with self.username # In both cases, a new auth token is required. if (self.cache['token'] is None) or \ (self.cache['account'] is None) or \ (self.cache['account'] != self.username): self._authenticate() else: # we need to set the self.token and self.params # to make use of the self.query() method self.token = self.cache['token'] self.params = { 'api_version': API_VERSION, 'auth_token': self.token } # test if token from cache_file is still valid and functional # if not, it should continue to get a new auth token url = API_URI + DEVICES_ENDPOINT req = self.query(url, raw=True) if req and req.status_code == 200: self._authenticate(session=req) else: self._authenticate() else: # first time executing, so we have to create a cache file self._authenticate() def _get_oauth_token(self): """Return Oauth Bearer token.""" oauth_data = OAUTH_DATA.copy() oauth_data['username'] = self.username oauth_data['password'] = self.password response = self.session.post(OAUTH_ENDPOINT, data=oauth_data, headers=HEADERS) oauth_token = None if response.status_code == 200: oauth_token = response.json().get('access_token') return oauth_token def _authenticate(self, attempts=RETRY_TOKEN, session=None): """Authenticate user against Ring API.""" url = API_URI + NEW_SESSION_ENDPOINT loop = 0 while loop <= attempts: HEADERS['Authorization'] = \ 'Bearer {}'.format(self._get_oauth_token()) loop += 1 try: if session is None: req = self.session.post(url, data=POST_DATA, headers=HEADERS) else: req = session except requests.exceptions.RequestException as err_msg: _LOGGER.error("Error!! %s", err_msg) raise if not req: continue # if token is expired, refresh credentials and try again if req.status_code == 200 or req.status_code == 201: # the only way to get a JSON with token is via POST, # so we need a special conditional for 201 code if req.status_code == 201: data = req.json().get('profile') self.token = data.get('authentication_token') self._NewConnectionStatus(True) self.params = { 'api_version': API_VERSION, 'auth_token': self.token } if self._persist_token and self._push_token_notify_url: url = API_URI + PERSIST_TOKEN_ENDPOINT PERSIST_TOKEN_DATA['auth_token'] = self.token PERSIST_TOKEN_DATA['device[push_notification_token]'] = \ self._push_token_notify_url req = self.session.put((url), headers=HEADERS, data=PERSIST_TOKEN_DATA) # update token if reuse_session is True if self._reuse_session: self.cache['account'] = self.username self.cache['token'] = self.token _save_cache(self.cache, self.cache_file) return True self._NewConnectionStatus(False) req.raise_for_status() return True def _NewConnectionStatus(self, newState): print('_NewConnectionStatus(', newState) print('is_connected=', self.is_connected) if newState != self.is_connected: if newState is True: if self._connectedCallback: self._connectedCallback(self, 'Connected') elif newState is False: if self._disconnectedCallback: self._disconnectedCallback(self, 'Disconnected') self.is_connected = newState def query(self, url, attempts=RETRY_TOKEN, method='GET', raw=False, extra_params=None, json=None): """Query data from Ring API.""" if self.debug: _LOGGER.debug("Querying %s", url) if self.debug and not self.is_connected: _LOGGER.debug("Not connected. Refreshing token...") self._authenticate() response = None loop = 0 while loop <= attempts: if self.debug: _LOGGER.debug("running query loop %s", loop) # allow to override params when necessary # and update self.params globally for the next connection if extra_params: params = self.params params.update(extra_params) else: params = self.params loop += 1 try: if method == 'GET': resp = self.session.get(url, params=urlencode(params)) elif method == 'PUT': resp = self.session.put(url, params=urlencode(params)) elif method == 'POST': resp = self.session.post(url, params=urlencode(params), json=json) if self.debug: _LOGGER.debug("_query %s ret %s", loop, resp.status_code) except requests.exceptions.RequestException as err_msg: _LOGGER.error("Error!! %s", err_msg) raise # if token is expired, refresh credentials and try again if resp.status_code == 401: self._NewConnectionStatus(False) self._authenticate() continue if resp.status_code == 200 or resp.status_code == 204: # if raw, return session object otherwise return JSON if raw: response = resp else: if method == 'GET': response = resp.json() break if self.debug and response is None: _LOGGER.debug("%s", MSG_GENERIC_FAIL) return response @property def devices(self): """Return all devices.""" devs = {} devs['chimes'] = self.chimes devs['stickup_cams'] = self.stickup_cams devs['doorbells'] = self.doorbells return devs def __devices(self, device_type): """Private method to query devices.""" lst = [] url = API_URI + DEVICES_ENDPOINT try: if device_type == 'stickup_cams': req = self.query(url).get('stickup_cams') for member in list((obj['description'] for obj in req)): lst.append(RingStickUpCam(self, member)) if device_type == 'chimes': req = self.query(url).get('chimes') for member in list((obj['description'] for obj in req)): lst.append(RingChime(self, member)) if device_type == 'doorbells': req = self.query(url).get('doorbots') for member in list((obj['description'] for obj in req)): lst.append(RingDoorBell(self, member)) # get shared doorbells, however device is read-only req = self.query(url).get('authorized_doorbots') for member in list((obj['description'] for obj in req)): lst.append(RingDoorBell(self, member, shared=True)) except AttributeError: pass return lst @property def chimes(self): """Return a list of RingDoorChime objects.""" return self.__devices('chimes') @property def stickup_cams(self): """Return a list of RingStickUpCam objects.""" return self.__devices('stickup_cams') @property def doorbells(self): """Return a list of RingDoorBell objects.""" return self.__devices('doorbells') def update(self): """Refreshes attributes for all linked devices.""" for device_lst in self.devices.values(): for device in device_lst: if hasattr(device, "update"): _LOGGER.debug("Updating attributes from %s", device.name) getattr(device, "update") return True