class ThingSpeakPoller(Thread):
    def __init__(self, channel):
        Thread.__init__(self)

        self.daemon = True

        self.channel = channel
        self.last_entry = self.channel.get_last_entry_id()

        self.clients = dict()
        self.should_stop = Event()
        self.has_client = Event()

    def run(self):
        while not self.should_stop.is_set():
            # Don't do anything until there is at least one registered client
            self.has_client.wait()

            # Poll for new entries on the ThingSpeak channel
            current_entry_id = 0
            newest_entry_list = self.channel.read(count=1)
            if not newest_entry_list:
                entry = None
            else:
                entry = newest_entry_list[0]
            if entry:
                current_entry_id = entry['entry_id']
            # If there have been new entries since we last checked, get them all
            num_new_entries = current_entry_id - self.last_entry
            entries = []
            if num_new_entries == 1:
                # There is only one new entry, we already fetched it
                entries = [entry]
            else:
                # There is more than one new entry, we need to fetch them all
                entries = self.channel.read(count=num_new_entries)
            # Update the last entry number we have seen
            self.last_entry = current_entry_id

            # Iterate over entries from oldest to newest
            for e in reversed(entries):
                # Call all of the registered callbacks for each entry
                for cb in self.clients.keys():
                    cb(e)
            # Call backbacks again to indicate that we have just finished an
            # update
            for cb in self.clients.keys():
                cb(None)

            if self.has_client.is_set():
                # If we have at least one client, then wait for the polling period of the client
                # that has requested the most frequent polling or until we need to stop
                self.should_stop.wait(timeout=min(self.clients.values()))

    def stop(self):
        self.should_stop.set()
        self.join()

    def register(self, callback, poll_period=1):
        self.clients[callback] = poll_period
        self.has_client.set()

    def unregister(self, callback):
        self.clients.pop(callback)
        if not self.clients:
            self.has_client.reset()
Beispiel #2
0
class SMOSyncObject(SMOBaseObject):
    """This File contains a base class for all threadded SMO Objects """

    # global class data
    thr_call_sync = Event()
    """Synchronisation for calls of different threads"""
    sequencer = [None]
    """default sequencer for all  SMOSyncObjects, set and start after class definition"""
    obj_no = -3
    """compatibility definition for default sequencer"""

    def __init__(self, smo_init_mode='new', smo_restore=None, id_str=smo_util.smo_default_id_str,
                 smo_filename=None, smo_file=None, parent_thr_obj=None,
                 threaded=smo_util.smo_default_threaded, auto_serialize=smo_util.smo_default_serialize,
                 auto_compute=smo_util.smo_default_autocompute, period=smo_util.smo_default_period, glob=None, **kwd):
        """
        Initalisation of SMOSyncObject, pass parameters: smo_restore=None, smo_filename=None,
        smo_file=None to SMOBaseObject. Periodic execution is only allowed when threadded =True
        Otherwise method calls were synchronized by the parent_thr_obj. This ref may be indirect.
        """

        self.id_str = id_str
        """Id name for IP access pubilsh and IP Id """
        # init default values
        self.threaded = threaded
        """Flag that indicates if the the object spans an own thread"""
        # same as self.sequencer[0].obj == self
        self.auto_serialize = auto_serialize
        """Flag that indicates if the the object automatically starts serialisation"""
        self.auto_compute = auto_compute
        """flag indicates that computation should automatically start after initialisation (persistent) """
        self.period = period
        """period for calling compute_handler"""

        # init base object
        SMOBaseObject.__init__(self, smo_init_mode=smo_init_mode, smo_restore=smo_restore,
                               smo_filename=smo_filename, smo_file=smo_file, glob=glob, **kwd)

        self.run_compute = False
        """runtime flag that indicates periodic execution of compute handler"""

        # init timing data
        self.cycle_cnt = 0
        """executed cycles since start"""
        self.delay_time = 0
        """accumulated stop time"""
        self.start_time = 0
        """system time since first start or start after reset"""
        self.stop_time = 0
        """system time of the last stop call"""
        self.last_call = 0
        """system time of the last cycle at the beginning"""
        self.last_cnt = 0
        """cycle count of the previous executed cycle"""
        self.act_call = 0
        """system time of the actual cycle at the beginning"""
        self.act_cnt = 0
        """actual cycle count"""

        # synchronize sequencer for possible changes
        self.thr_call_sync = Event()
        self.thr_call_sync.set()
        # determin execution thread
        if not self.threaded:
            if parent_thr_obj is None or parent_thr_obj == SMOSyncObject:
                self.sequencer = SMOSyncObject.sequencer
            elif isinstance(parent_thr_obj, SMOSyncObject):
                self.sequencer = parent_thr_obj.sequencer
            else:
                self.sequencer = SMOSyncObject.sequencer
                if self.warning:
                    mstr = '*** WARNING *** SMOSyncObject init: Type %s, obj_no %s: Could not find parent thread %s, set it to SMOSyncObject'\
                        % (self.__class__.__name__, str(self.obj_no), str(obj_index))
                    self.debug_handler.out(mstr)
        else:
            # list used for propagation of in sequencer in the hierarchy
            self.sequencer = [SMOSequencer(self)]

        # default start serialization
        if self.threaded and self.auto_serialize:
            self.sequencer[0].start_serialize()
            # default start periodic execution
            if self.auto_compute and self.period >= 0:
                self.start_compute()
        return

    def __str__(self):
        """Produces a string containing the type of the SMOSyncObject"""
        # This string will be extended by subclasses
        retstr = SMOBaseObject.__str__(self)
        retstr += ' IP Id String: ' + self.id_str
        if self.threaded:
            retstr += ' threaded, period: %s' % str(self.period)
        else:
            retstr += ' serialized by object no %s' % str(self.sequencer[0].obj.obj_no)
        return retstr

    def get_restore_dict(self):
        """
        This method should return a dictionary which contain all persistent data
        for all members of restore eval(repr(restore)) has to work
        """
        ret_dict = SMOBaseObject.get_restore_dict(self)
        local_dict = {
            'id_str': self.id_str,
            'threaded': self.threaded,
            'auto_serialize': self.auto_compute,
            'auto_compute': self.auto_compute,
            'period': self.period}
        ret_dict.update(local_dict)
        if self.trace:
            mstr = '*** TRACE *** SMOSyncObject get_restore_dict: return %s' % str(ret_dict)
            self.debug_handler.out(mstr)
        return ret_dict

    def get_time_table(self):
        time_tab = {
            'start_time': self.start_time,
            'stop_time': self.stop_time,
            'act_cycle': self.act_cnt,
            'act_cycle_time': self.act_call,
            'last_cycle': self.last_cnt,
            'last_cycle_time': self.last_call}
        return time_tab

    def get_id_str(self):
        return self.id_str

    def set_id_str(self, id_str):
        if id_str != smo_util.smo_server_id_str:
            self.id_str = id_str
        return self.id_str

    # handling of serialize and compute by own methods
    def config(self, **kwd):
        if kwd == {}:
            kwd['auto_serialize'] = self.auto_serialize,
            kwd['auto_compute'] = self.auto_compute,
            kwd['threaded'] = self.threaded
            kwd['period'] = self.period
            kwd['id_str'] = self.id_str
            kwd['cycle_cnt'] = self.cycle_cnt               # read only
            kwd['delay_time'] = self.delay_time             # read only
            kwd['time_tab'] = self.get_time_tab()           # read only
            kwd['sync_obj'] = self.sequencer[0].obj
            return kwd
        else:
            ret_flag = True
            if 'auto_serialize' in kwd:
                self.auto_serialize = bool(kwd['auto_serialize'])
            if 'auto_compute' in kwd:
                self.auto_compute = bool(kwd['auto_compute'])
            if 'period' in kwd:
                self.period = int(kwd['period'])
            if 'id_str' in kwd and kwd['id_str'] != smo_util.smo_server_id_str:
                self.id_str = str(kwd['id_str'])

            # check threaded option
            if 'threaded' in kwd:
                threaded = bool(kwd['threaded'])
            else:
                threaded = None

            # replace sequencer by a new own one
            if threaded and self.threaded == False:

                if self.info:
                    mstr = '*** INFO *** SMOSyncObject config for object no %s: start own sequencer' % str(self.obj_no)
                    self.debug_handler.out(mstr)

                if self.sequencer[0] is None:
                    self._set_threaded()
                else:
                    self.thr_call_sync.reset()
                    self.sequencer[0].thr_call('Call', None, self._set_threaded, (), {})

            # stop own sequencer and replace it
            # looking for the requested new sequencer in option sync_object
            # otherwise use default SMOSequencer

            if not threaded:

                # check sync_obj option
                if 'sync_obj' in kwd and isinstance(kwd['sync_obj'], SMOSyncObject):
                    sync_obj = kwd['sync_obj']
                else:
                    sync_obj = SMOSyncObject

                # check if sequencer is different to the existing one
                if sync_obj.sequencer[0] != self.sequencer[0]:
                    if self.info:
                        mstr = '*** INFO *** SMOSyncObject config for object no %s: change sequencer' % str(
                            self.obj_no)
                        self.debug_handler.out(mstr)

                    # install new sequencer
                    if self.sequencer[0] is None:
                        self.sequencer = sync_obj.sequencer
                    else:
                        # change sequencer to an different sync_object OR (see _reset_threaded)
                        # stop own Thread (self.threaded == True)
                        self.thr_call_sync.reset()

                        self.sequencer[0].thr_call('Call', None, self._reset_threaded, (), {parent: sync_obj})
        return

    # auxiliary methods to start/stop sequencers
    def _reset_threaded(self, parent=None, thr_exit=True, **kwd):
        # no pending calls (thr_call_sync.reset)

        # stop own thread
        if self.sequencer[0] is not None and self.threaded and self.sequencer[0] != SMOSyncObject.sequencer[0]:
            self.sequencer[0]._thr_stop_serialize(thr_exit=thr_exit, thr_new_sequencer=parent.sequencer[0])

        # update own data
        self.sequencer = parent.sequencer
        self.threaded = False

        # enable remote calls
        self.thr_call_sync.set()
        return

    def _set_threaded(self, parent=None, **kwd):
        # no pending calls (thr_call_sync.reset)
        # initialize data for threadding

        # init timing data
        ### self.period = smo_default_period
        self.cycle_cnt = 0
        self.delay_time = 0
        self.start_time = 0
        self.stop_time = 0
        self.last_call = 0
        self.last_cnt = 0
        self.act_call = 0
        self.act_cnt = 0

        # create and start own sequencer
        self.sequencer = [SMOSequencer(self)]
        self.threaded = True
        # default start serialization
        if self.auto_serialize:
            self.sequencer[0].start_serialize()
            # default start periodic execution
            if self.auto_compute and self.period >= 0:
                self._start_compute()

        # enable remote calls
        self.thr_call_sync.set()
        return

# methods for serialization
    def is_serialized(self):
        self.thr_call_sync.wait(timeout=None)
        if self.threaded and self.sequencer[0] is not None:
            return self.sequencer[0].thr_serialize.isSet()
        else:
            if warning:
                mstr = '*** WARNING *** SMOSyncObject object no %s not threaded' % str(self.obj_no)
                self.debug_handler.out(mstr)
            return False

    def start_serialize(self):
        self.thr_call_sync.wait(timeout=None)
        if self.threaded and self.sequencer[0] is not None:
            return self.sequencer[0].start_serialize()
        else:
            if warning:
                mstr = '*** WARNING *** SMOSyncObject object no %s not threaded' % str(self.obj_no)
                self.debug_handler.out(mstr)
            return False

    def stop_serialize(self, thr_exit=False, thr_new_sequencer=None):
        self.thr_call_sync.wait(timeout=None)
        if not self.threaded or self.sequencer[0] is None:
            if warning:
                mstr = '*** WARNING *** SMOSyncObject object no %s not threaded' % str(self.obj_no)
                self.debug_handler.out(mstr)
            return False
        else:
            return self.sequencer[0].stop_serialize(thr_exit=thr_exit, thr_new_sequencer=thr_new_sequencer)

    def sync_call(self, method, *para_args, **para_kwd):

        # method call or send message (no return)
        call_type = 'Call'
        if 'smo_call_type' in para_kwd:
            call_type = para_kwd['smo_call_type']
            del para_kwd['smo_call_type']

        # no synchronisation
        if self.sequencer[0] is None:
            result = method(self, *para_args, **para_kwd)
            if call_type == 'Call':
                return result
            else:
                return None
        # syncronized call/send
# raises exeptions .... self ???

        SMOSyncObject.thr_call_sync.wait(timeout=None)
        ### thr_call( smo_sync_msg_type, smo_ack, smo_call, sync_obj, sync_method,  para_args,  para_kwd)
        return SMOSyncObject.sequencer[0].thr_call(call_type, None, method, para_args, para_kwd)

# periodic execution methods
    def is_computing(self):
        return self.threadded and self.run_compute

    def start_compute(self, **kwd):
        """Method for starting the computation of the node"""
        if self.threaded:
            res = self.sequencer[0].thr_call('Call', None, self._start_compute, (), kwd)
            return res
        else:
            if warning:
                mstr = '*** WARNING *** SMOSyncObject object no %s not threaded' % str(self.obj_no)
                self.debug_handler.out(mstr)
            return False

    def _start_compute(self, **kwd):
        """Methode which is called from THR class
        This method is is called during the serialization process"""
        """Method to determine the different times"""
        # Time may be passed by the simulation algorithm
        if self.trace:
            mstr = '*** TRACE *** SMOSyncObject _start_compute for objcet no %s' % str(self.obj_no)
            self.debug_handler.out(mstr)
        if self.run_compute:
            if self.warning:
                mstr = '*** WARNING *** SMOSyncObject _start_compute: object no  %s computation is still running' % str(
                    self.obj_no)
                self.debug_handler.out(mstr)
            return False
        else:
            start_time = smo_util.get_real_time()
            if self.stop_time <= 0:
                # first start after initialisation or reset
                self.start_time = start_time
                self.cycle_cnt = 0
                self.stop_time = 0
                self.delay_time = 0
                self.last_call = start_time
                self.last_cnt = 0
                self.act_call = start_time
                self.act_cnt = 0
            else:
                # restart after stop (break)
                delta = start_time - self.stop_time
                self.delay_time += delta
                self.last_call += delta
                self.act_call += delta
                self.stop_time = 0
            self.run_compute = True
            self.sequencer[0].thr_start = True
            return True

    def stop_compute(self, **kwd):
        if self.threaded:
            ### thr_call( smo_sync_msg_type, smo_ack, smo_call, sync_obj, sync_method,  para_args,  para_kwd)
            return self.sequencer[0].thr_call('Send', None, self._stop_compute, (), kwd)
        else:
            if warning:
                mstr = '*** WARNING *** SMOSyncObject object no %s not threaded' % str(self.obj_no)
                self.debug_handler.out(mstr)
            return False

    def _stop_compute(self, **kwd):
        if self.trace:
            mstr = '*** TRACE *** SMOSyncObject _stop_compute for obnject no %s' % str(self.obj_no)
            self.debug_handler.out(mstr)
        if not self.run_compute:
            if self.warning:
                mstr = '*** WARNING *** SMOSyncObject _stop_compute object no %s computation not started' % str(
                    self.obj_no)
                self.debug_handler.out(mstr)
            return False
        self.stop_time = smo_util.get_real_time()
        self.run_compute = False
        self.sequencer[0].thr_start = False
        return True

    def reset_compute(self, *arg, **kwd):
        if self.threaded:
            return self.sequencer[0].thr_call('Call', None, self._reset_compute, arg, kwd)
        else:
            if warning:
                mstr = '*** WARNING *** SMOSyncObject object no %s not threaded' % str(self.obj_no)
                self.debug_handler.out(mstr)
            return False

    def _reset_compute(self, *arg, **kwd):
        if self.trace:
            mstr = '*** TRACE *** SMOSyncObject _reset_compute for obnject no %s' % str(self.obj_no)
            self.debug_handler.out(mstr)
        self.thr[0].thr_start = False
        self.run_compute = False
        self.cycle_cnt = 0
        self.start_time = 0
        self.stop_time = 0
        self.last_call = 0
        self.last_cnt = 0
        self.act_call = 0
        self.act_cnt = 0
        return True

    def execute_compute(self, **kwd):
        # call compute handler
        """This method calculate the time data for the next period and call the computation handler
            time_tab ( <actual call number>, <actual call time>,  <last call number>, <last call time>)"""
        if not self.run_compute:
            if self.error:
                mstr = '*** ERROR *** SMOSyncObject execute compute: object no %s computation not started' % str(
                    self.obj_no)
                self.debug_handler.out(mstr)
        if self.trace:
            mstr = '*** TRACE *** SMOSyncObject execute_compute for object no %s' % str(self.obj_no)
            self.debug_handler.out(mstr)
        act_time = smo_util.get_real_time()
        if act_time < self.act_call + self.period:
            sleep_time = (self.act_call + self.period) - act_time
            if self.debug:
                mstr = '*** DEBUG *** SMOSyncObject execute_compute: object no %s sleep for %f seconds'\
                    % (str(self.obj_no), sleep_time)
                self.debug_handler.out(mstr)
            sleep(sleep_time)
            act_time = smo_util.get_real_time()
        self.last_cnt = self.act_cnt
        self.last_call = self.act_call
        self.act_cnt += 1
        self.act_call = act_time
        time_tab = {
            'start_time': self.start_time,
            'stop_time': self.stop_time,
            'act_cycle': self.act_cnt,
            'act_cycle_time': self.act_call,
            'last_cycle': self.last_cnt,
            'last_cycle_time': self.last_call}
        try:
            if self.debug:
                #mstr = '*** DEBUG *** SMOSyncObject execute_compute: object no %s\n   time table %s '\
                mstr = '*** DEBUG *** SMOSyncObject execute_compute for object no %s cycles %s, time %s'\
                    % (str(self.obj_no), str(time_tab['act_cycle'] - time_tab['last_cycle']), str(time_tab['act_cycle_time'] - time_tab['last_cycle_time']))
                self.debug_handler.out(mstr)
            self.compute_handler(time_tab=time_tab, **kwd)
        except:
            error_list = extract_tb(sys.exc_info()[2])
            error = error_list[len(error_list) - 1]
            if self.error:
                mstr = '*** ERROR *** SMOSyncObject execute compute: object no %s compute_handler raises an exception:\n  %s\n File: %s , Line number: %s ,\n   Method: %s , Statement: %s'\
                    % (str(self.obj_no), sys.exc_info()[1], error[0], error[1], error[2], error[3])
                self.debug_handler.out(mstr)
        return True

    def compute_handler(self, time_tab, **kwd):
        """
        This handler will be called for computation shouls be overwritten
        """
        if self.warning:
            mstr = '*** WARNING *** SMOSyncObject compute_handler for object no:  %s calling virtual method\n   time table time table: %s' % (
                str(self.obj_no), str(time_tab))
            self.debug_handler.out(mstr)
        return