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()
Пример #4
0
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)
Пример #5
0
    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)
Пример #6
0
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)
Пример #10
0
 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