예제 #1
0
파일: module.py 프로젝트: qpit/qudi-attolab
class ModuleStateMachine(QtCore.QObject, Fysom):
    """
    FIXME
    """
    # do not copy declaration of trigger(self, event, *args, **kwargs), just apply Slot decorator
    trigger = QtCore.Slot(str, result=bool)(Fysom.trigger)

    # signals
    sigStateChanged = QtCore.Signal(object)  # (module name, state change)

    def __init__(self, parent, callbacks=None, **kwargs):
        self._parent = parent
        if callbacks is None:
            callbacks = {}

        # State machine definition
        # the abbreviations for the event list are the following:
        #   name:   event name,
        #   src:    source state,
        #   dst:    destination state
        _baseStateList = {
            'initial':
            'deactivated',
            'events': [
                {
                    'name': 'activate',
                    'src': 'deactivated',
                    'dst': 'idle'
                },
                {
                    'name': 'deactivate',
                    'src': 'idle',
                    'dst': 'deactivated'
                },
                {
                    'name': 'deactivate',
                    'src': 'running',
                    'dst': 'deactivated'
                },
                {
                    'name': 'deactivate',
                    'src': 'locked',
                    'dst': 'deactivated'
                },
                {
                    'name': 'run',
                    'src': 'idle',
                    'dst': 'running'
                },
                {
                    'name': 'stop',
                    'src': 'running',
                    'dst': 'idle'
                },
                {
                    'name': 'lock',
                    'src': 'idle',
                    'dst': 'locked'
                },
                {
                    'name': 'lock',
                    'src': 'running',
                    'dst': 'locked'
                },
                {
                    'name': 'unlock',
                    'src': 'locked',
                    'dst': 'idle'
                },
                {
                    'name': 'runlock',
                    'src': 'locked',
                    'dst': 'running'
                },
            ],
            'callbacks':
            callbacks
        }

        # Initialise state machine:
        super().__init__(parent=parent, cfg=_baseStateList, **kwargs)

    def __call__(self):
        """
        Returns the current state.
        """
        return self.current

    def _build_event(self, event):
        """
        Overrides fysom _build_event to wrap on_activate and on_deactivate to
        catch and log exceptios.
        """
        base_event = super()._build_event(event)
        if event in ['activate', 'deactivate']:
            if event == 'activate':
                noun = 'activation'
            else:
                noun = 'deactivation'

            def wrap_event(*args, **kwargs):
                self._parent.log.debug('{0} in thread {1}'.format(
                    noun.capitalize(), QtCore.QThread.currentThreadId()))
                try:
                    base_event(*args, **kwargs)
                except:
                    self._parent.log.exception('Error during {0}'.format(noun))
                    return False
                return True

            return wrap_event
        else:
            return base_event

    def onchangestate(self, e):
        """ Fysom callback for state transition.

        @param object e: Fysom state transition description
        """
        self.sigStateChanged.emit(e)
예제 #2
0
class BaseMixin(Fysom, metaclass=ModuleMeta):
    """
    Base class for all loadable modules

    * Ensure that the program will not die during the load of modules in any case,
      and therefore do nothing!!!
    * Initialize modules
    * Provides a self identification of the used module
    * Output redirection (instead of print)
    * Provides a self de-initialization of the used module
    * Reload the module with code changes
    * Get your own configuration (for save)
    * Get name of status variables
    * Get status variables
    * Reload module data (from saved variables)
    """

    _modclass = 'base'
    _modtype = 'base'
    _connectors = dict()

    # do not copy declaration of trigger(self, event, *args, **kwargs), just apply Slot decorator
    trigger = QtCore.Slot(str, result=bool)(Fysom.trigger)

    # signals
    sigStateChanged = QtCore.Signal(object)  # (module name, state change)

    def __init__(self, manager, name, config=None, callbacks=None, **kwargs):
        """ Initialise Base class object and set up its state machine.

          @param object self: tthe object being initialised
          @param object manager: the manager object that
          @param str name: unique name for this object
          @param dict configuration: parameters from the configuration file
          @param dict callbacks: dictionary specifying functions to be run
                                 on state machine transitions

        """
        if config is None:
            config = {}
        if callbacks is None:
            callbacks = {}

        default_callbacks = {
            'onactivate': self.__load_status_vars_activate,
            'ondeactivate': self.__save_status_vars_deactivate
        }
        default_callbacks.update(callbacks)

        # State machine definition
        # the abbrivations for the event list are the following:
        #   name:   event name,
        #   src:    source state,
        #   dst:    destination state
        _baseStateList = {
            'initial':
            'deactivated',
            'events': [
                {
                    'name': 'activate',
                    'src': 'deactivated',
                    'dst': 'idle'
                },
                {
                    'name': 'deactivate',
                    'src': 'idle',
                    'dst': 'deactivated'
                },
                {
                    'name': 'deactivate',
                    'src': 'running',
                    'dst': 'deactivated'
                },
                {
                    'name': 'deactivate',
                    'src': 'locked',
                    'dst': 'deactivated'
                },
                {
                    'name': 'run',
                    'src': 'idle',
                    'dst': 'running'
                },
                {
                    'name': 'stop',
                    'src': 'running',
                    'dst': 'idle'
                },
                {
                    'name': 'lock',
                    'src': 'idle',
                    'dst': 'locked'
                },
                {
                    'name': 'lock',
                    'src': 'running',
                    'dst': 'locked'
                },
                {
                    'name': 'unlock',
                    'src': 'locked',
                    'dst': 'idle'
                },
                {
                    'name': 'runlock',
                    'src': 'locked',
                    'dst': 'running'
                },
            ],
            'callbacks':
            default_callbacks
        }

        # Initialise state machine:
        super().__init__(cfg=_baseStateList, **kwargs)

        # add connectors
        self.connectors = OrderedDict()
        for cname, con in self._conn.items():
            self.connectors[con.name] = con

        # add connection base (legacy)
        for con in self._connectors:
            self.connectors[con] = OrderedDict()
            self.connectors[con]['class'] = self._connectors[con]
            self.connectors[con]['object'] = None

        # add config options
        for oname, opt in self._config_options.items():
            if opt.name in config:
                cfg_val = config[opt.name]
            else:
                if opt.missing == MissingOption.error:
                    raise Exception(
                        'Required variable >> {0} << not given in configuration.\n'
                        'Configuration is: {1}'.format(opt.name, config))
                elif opt.missing == MissingOption.warn:
                    self.log.warning(
                        'No variable >> {0} << configured, using default value {1} instead.'
                        ''.format(opt.name, opt.default))
                elif opt.missing == MissingOption.info:
                    self.log.info(
                        'No variable >> {0} << configured, using default value {1} instead.'
                        ''.format(opt.name, opt.default))
                cfg_val = opt.default
            if opt.check(cfg_val):
                converted_val = opt.convert(cfg_val)
                if opt.constructor_function is None:
                    setattr(self, opt.var_name, converted_val)
                else:
                    setattr(self, opt.var_name,
                            opt.constructor_function(self, converted_val))

        self._manager = manager
        self._name = name
        self._configuration = config
        self._statusVariables = OrderedDict()
        # self.sigStateChanged.connect(lambda x: print(x.event, x.fsm._name))

    def __load_status_vars_activate(self, event):
        """ Restore status variables before activation.

            @param e: Fysom event
        """
        # add status vars
        for vname, var in self._stat_vars.items():
            sv = self._statusVariables
            svar = sv[var.name] if var.name in sv else var.default

            if var.constructor_function is None:
                setattr(self, var.var_name, svar)
            else:
                setattr(self, var.var_name,
                        var.constructor_function(self, svar))

        # activate
        self.on_activate()

    def __save_status_vars_deactivate(self, event):
        """ Save status variables after deactivation.

            @param e: Fysom event
        """
        try:
            self.on_deactivate()
        except Exception as e:
            raise e
        finally:
            # save status vars even if deactivation failed
            for vname, var in self._stat_vars.items():
                if hasattr(self, var.var_name):
                    if var.representer_function is None:
                        self._statusVariables[var.name] = getattr(
                            self, var.var_name)
                    else:
                        self._statusVariables[
                            var.name] = var.representer_function(
                                self, getattr(self, var.var_name))

    def _build_event(self, event):
        """
        Overrides fysom _build_event to wrap on_activate and on_deactivate to
        catch and log exceptios.
        """
        base_event = super()._build_event(event)
        if (event in ['activate', 'deactivate']):
            if (event == 'activate'):
                noun = 'activation'
            else:
                noun = 'deactivation'

            def wrap_event(*args, **kwargs):
                self.log.debug('{0} in thread {1}'.format(
                    noun.capitalize(), QtCore.QThread.currentThreadId()))
                try:
                    base_event(*args, **kwargs)
                except:
                    self.log.exception('Error during {0}'.format(noun))
                    return False
                return True

            return wrap_event
        else:
            return base_event

    @property
    def log(self):
        """
        Returns a logger object
        """
        return logging.getLogger("{0}.{1}".format(self.__module__,
                                                  self.__class__.__name__))

    def on_activate(self):
        """ Method called when module is activated. If not overridden
            this method returns an error.

        """
        self.log.error('Please implement and specify the activation method '
                       'for {0}.'.format(self.__class__.__name__))

    def on_deactivate(self):
        """ Method called when module is deactivated. If not overridden
            this method returns an error.
        """
        self.log.error('Please implement and specify the deactivation '
                       'method {0}.'.format(self.__class__.__name__))

    # Do not replace these in subclasses
    def onchangestate(self, e):
        """ Fysom callback for state transition.

        @param object e: Fysom state transition description
        """
        self.sigStateChanged.emit(e)

    def getStatusVariables(self):
        """ Return a dict of variable names and their content representing
            the module state for saving.

        @return dict: variable names and contents.

        """
        return self._statusVariables

    def setStatusVariables(self, variableDict):
        """ Give a module a dict of variable names and their content
            representing the module state.

          @param OrderedDict dict: variable names and contents.

        """
        if not isinstance(variableDict, (dict, OrderedDict)):
            self.log.error('Did not pass a dict or OrderedDict to '
                           'setStatusVariables in {0}.'.format(
                               self.__class__.__name__))
            return
        self._statusVariables = variableDict

    def getState(self):
        """Return the state of the state machine implemented in this class.

          @return str: state of state machine

          Valid return values are: 'deactivated', 'idle', 'running', 'locked',
                                   'blocked'
        """
        return self.current

    def getConfiguration(self):
        """ Return the configration dictionary for this module.

          @return dict: confiuration dictionary

        """
        return self._configuration

    def getConfigDirectory(self):
        """ Return the configuration directory for the manager this module
            belongs to.

          @return str: path of configuration directory

        """
        return self._manager.configDir

    @staticmethod
    def identify():
        """ Return module id.

          @return dict: id dictionary with modclass and modtype keys.
        """
        return {moduleclass: _class, moduletype: _modtype}

    def get_main_dir(self):
        """ Returns the absolut path to the directory of the main software.

             @return string: path to the main tree of the software

        """
        mainpath = os.path.abspath(
            os.path.join(os.path.dirname(__file__), ".."))
        return mainpath

    def get_home_dir(self):
        """ Returns the path to the home directory, which should definitely
            exist.
            @return string: absolute path to the home directory
        """
        return os.path.abspath(os.path.expanduser('~'))

    def get_connector(self, connector_name):
        """ Return module connected to the given named connector.
          @param str connector_name: name of the connector

          @return obj: module that is connected to the named connector
        """
        if connector_name in self.connectors:
            connector = self.connectors[connector_name]
            # new style connector
            if isinstance(connector, Connector):
                return connector()
            # legacy connector
            elif isinstance(connector, dict):
                obj = connector['object']
                if (obj is None):
                    raise TypeError('No module connected')
                return obj
            else:
                raise Exception(
                    'Entry {0} in connector dict is of wrong type {1}.'
                    ''.format(connector_name, type(connector)))
        else:
            raise Exception('Connector {0} does not exist.'
                            ''.format(connector_name))