Ejemplo n.º 1
0
def test_default_enum():
    good_values = (u'a', u'b', u'c')
    for good_val in good_values:
        for schema in (Enum.using(valid_values=good_values),
                       Enum.valued(*good_values)):
            el = schema()
            assert el.set(good_val)
            assert el.value == good_val
            assert el.u == good_val
            assert el.validate()
            assert not el.errors

    schema = Enum.valued(*good_values)
    el = schema()
    assert not el.set(u'd')
    assert el.value is None
    assert el.u == u'd'
    # present but not converted
    assert el.validate()

    el = schema()
    assert not el.set(None)
    assert el.value is None
    assert el.u == u''
    # not present
    assert not el.validate()
Ejemplo n.º 2
0
def test_default_enum():
    good_values = (u'a', u'b', u'c')
    for good_val in good_values:
        for schema in (Enum.using(valid_values=good_values),
                       Enum.valued(*good_values)):
            el = schema()
            assert el.set(good_val)
            assert el.value == good_val
            assert el.u == good_val
            assert el.validate()
            assert not el.errors

    schema = Enum.valued(*good_values)
    el = schema()
    assert not el.set(u'd')
    assert el.value is None
    assert el.u == u'd'
    # present but not converted
    assert el.validate()

    el = schema()
    assert not el.set(None)
    assert el.value is None
    assert el.u == u''
    # not present
    assert not el.validate()
Ejemplo n.º 3
0
class ZmqHubPlugin(SingletonPlugin, AppDataController):
    """
    This class is automatically registered with the PluginManager.
    """
    implements(IPlugin)
    plugin_name = 'wheelerlab.zmq_hub_plugin'
    '''
    AppFields
    ---------

    A flatland Form specifying application options for the current plugin.
    Note that nested Form objects are not supported.

    Since we subclassed AppDataController, an API is available to access and
    modify these attributes.  This API also provides some nice features
    automatically:
        -all fields listed here will be included in the app options dialog
            (unless properties=dict(show_in_gui=False) is used)
        -the values of these fields will be stored persistently in the microdrop
            config file, in a section named after this plugin's name attribute
    '''
    AppFields = Form.of(
        String.named('hub_uri').using(optional=True, default='tcp://*:31000'),
        Enum.named('log_level').using(default='info', optional=True).valued(
            'debug', 'info', 'warning', 'error', 'critical'),
    )

    def __init__(self):
        self.name = self.plugin_name
        self.hub_process = None

    def on_plugin_enable(self):
        """
        Handler called once the plugin instance is enabled.

        Note: if you inherit your plugin from AppDataController and don't
        implement this handler, by default, it will automatically load all
        app options from the config file. If you decide to overide the
        default handler, you should call:

            AppDataController.on_plugin_enable(self)

        to retain this functionality.
        """
        super(ZmqHubPlugin, self).on_plugin_enable()
        app_values = self.get_app_values()
        self.hub_process = Process(
            target=run_hub,
            args=(MicroDropHub(app_values['hub_uri'], self.name),
                  getattr(logging, app_values['log_level'].upper())))
        self.hub_process.start()

    def on_plugin_disable(self):
        """
        Handler called once the plugin instance is disabled.
        """
        if self.hub_process is not None:
            self.hub_process.terminate()
            self.hub_process = None
Ejemplo n.º 4
0
    def AppFields(self):
        serial_ports = list(get_serial_ports())
        if len(serial_ports):
            default_port = serial_ports[0]
        else:
            default_port = None

        return Form.of(
            Enum.named('serial_port').using(
                default=default_port, optional=True).valued(*serial_ports),
            Float.named('default_duration').using(default=1000, optional=True),
            Float.named('default_voltage').using(default=80, optional=True),
            Float.named('default_frequency').using(default=10e3,
                                                   optional=True),
            Boolean.named('Auto-run diagnostic tests').using(default=True,
                                                             optional=True))
Ejemplo n.º 5
0
def test_typed_enum():
    good_values = range(1, 4)
    schema = Enum.using(valid_values=good_values, child_type=Integer)

    for good_val in good_values:
        el = schema()
        assert el.set(unicode(str(good_val), 'ascii'))
        assert el.value == good_val
        assert el.u == unicode(str(good_val), 'ascii')
        assert not el.errors

    el = schema()
    assert not el.set(u'x')
    assert el.value is None
    assert el.u == u'x'

    el = schema()
    assert not el.set(u'5')
    assert el.value is None
    assert el.u == u'5'
Ejemplo n.º 6
0
def test_typed_enum():
    good_values = range(1, 4)
    schema = Enum.using(valid_values=good_values, child_type=Integer)

    for good_val in good_values:
        el = schema()
        assert el.set(text_transform(good_val))
        assert el.value == good_val
        assert el.u == text_transform(good_val)
        assert not el.errors

    el = schema()
    assert not el.set(u'x')
    assert el.value is None
    assert el.u == u'x'

    el = schema()
    assert not el.set(u'5')
    assert el.value is None
    assert el.u == u'5'
Ejemplo n.º 7
0
def test_typed_enum():
    good_values = range(1, 4)
    schema = Enum.using(valid_values=good_values, child_type=Integer)

    for good_val in good_values:
        el = schema()
        assert el.set(unicode(str(good_val), 'ascii'))
        assert el.value == good_val
        assert el.u == unicode(str(good_val), 'ascii')
        assert not el.errors

    el = schema()
    assert not el.set(u'x')
    assert el.value is None
    assert el.u == u'x'

    el = schema()
    assert not el.set(u'5')
    assert el.value is None
    assert el.u == u'5'
Ejemplo n.º 8
0
class App(SingletonPlugin, AppDataController):
    implements(IPlugin)
    '''
INFO:  <Plugin App 'microdrop.app'>
INFO:  <Plugin ConfigController 'microdrop.gui.config_controller'>
INFO:  <Plugin DmfDeviceController 'microdrop.gui.dmf_device_controller'>
INFO:  <Plugin ExperimentLogController
         'microdrop.gui.experiment_log_controller'>
INFO:  <Plugin MainWindowController 'microdrop.gui.main_window_controller'>
INFO:  <Plugin ProtocolController 'microdrop.gui.protocol_controller'>
INFO:  <Plugin ProtocolGridController 'microdrop.gui.protocol_grid_controller'>
    '''
    core_plugins = [
        'microdrop.app', 'microdrop.gui.config_controller',
        'microdrop.gui.dmf_device_controller',
        'microdrop.gui.experiment_log_controller',
        'microdrop.gui.main_window_controller',
        'microdrop.gui.protocol_controller',
        'microdrop.gui.protocol_grid_controller', 'microdrop.zmq_hub_plugin',
        'microdrop.electrode_controller_plugin', 'microdrop.device_info_plugin'
    ]

    AppFields = Form.of(
        Integer.named('x').using(default=None,
                                 optional=True,
                                 properties={'show_in_gui': False}),
        Integer.named('y').using(default=None,
                                 optional=True,
                                 properties={'show_in_gui': False}),
        Integer.named('width').using(default=400,
                                     optional=True,
                                     properties={'show_in_gui': False}),
        Integer.named('height').using(default=500,
                                      optional=True,
                                      properties={'show_in_gui': False}),
        String.named('server_url').using(
            default='http://microfluidics.utoronto.ca/update',
            optional=True,
            properties=dict(show_in_gui=False)),
        Boolean.named('realtime_mode').using(
            default=False, optional=True, properties=dict(show_in_gui=False)),
        Filepath.named('log_file').using(
            default='',
            optional=True,
            properties={'action': gtk.FILE_CHOOSER_ACTION_SAVE}),
        Boolean.named('log_enabled').using(default=False, optional=True),
        Enum.named('log_level').using(default='info', optional=True).valued(
            'debug', 'info', 'warning', 'error', 'critical'))

    def __init__(self):
        '''
        .. versionchanged:: 2.11.2
            Add :attr:`gtk_thread` attribute, holding a reference to the thread
            that the GTK main loop is executing in.

        .. versionchanged:: 2.17
            Remove :attr:`version` attribute.  Use
            :attr:`microdrop.__version__` instead.
        '''
        args = parse_args()

        print 'Arguments: %s' % args

        self.name = "microdrop.app"
        #: .. versionadded:: 2.11.2
        self.gtk_thread = None

        self.realtime_mode = False
        self.running = False
        self.builder = gtk.Builder()
        self.signals = {}
        self.plugin_data = {}

        # these members are initialized by plugins
        self.experiment_log_controller = None
        self.config_controller = None
        self.dmf_device_controller = None
        self.protocol_controller = None
        self.main_window_controller = None

        # Enable custom logging handler
        logging.getLogger().addHandler(CustomHandler())
        self.log_file_handler = None

        # config model
        try:
            self.config = Config(args.config)
        except IOError:
            logging.error(
                'Could not read configuration file, `%s`.  Make sure'
                ' it exists and is readable.', args.config)
            raise SystemExit(-1)

        # set the log level
        if self.name in self.config.data and ('log_level'
                                              in self.config.data[self.name]):
            self._set_log_level(self.config.data[self.name]['log_level'])
        _L().info('MicroDrop version: %s', __version__)
        _L().info('Running in working directory: %s', os.getcwd())

        # dmf device
        self.dmf_device = None

        # protocol
        self.protocol = None

    def get_data(self, plugin_name):
        data = self.plugin_data.get(plugin_name)
        if data:
            return data
        else:
            return {}

    def set_data(self, plugin_name, data):
        '''
        .. versionchanged:: 2.20
            Log data and plugin name to debug level.
        '''
        logger = _L()  # use logger with method context
        if logger.getEffectiveLevel() >= logging.DEBUG:
            caller = caller_name(skip=2)
            logger.debug('%s -> plugin_data:', caller)
            map(logger.debug, pprint.pformat(data).splitlines())
        self.plugin_data[plugin_name] = data

    def on_app_options_changed(self, plugin_name):
        if plugin_name == self.name:
            data = self.get_data(self.name)
            if 'realtime_mode' in data:
                if self.realtime_mode != data['realtime_mode']:
                    self.realtime_mode = data['realtime_mode']
                    if self.protocol_controller:
                        self.protocol_controller.run_step()
            if 'log_file' in data and 'log_enabled' in data:
                self.apply_log_file_config(data['log_file'],
                                           data['log_enabled'])
            if 'log_level' in data:
                self._set_log_level(data['log_level'])
            if 'width' in data and 'height' in data:
                self.main_window_controller.view.resize(
                    data['width'], data['height'])
                # allow window to resize before other signals are processed
                while gtk.events_pending():
                    gtk.main_iteration()
            if data.get('x') is not None and data.get('y') is not None:
                self.main_window_controller.view.move(data['x'], data['y'])
                # allow window to resize before other signals are processed
                while gtk.events_pending():
                    gtk.main_iteration()

    def apply_log_file_config(self, log_file, enabled):
        if enabled and not log_file:
            _L().error('Log file can only be enabled if a path is selected.')
            return False
        self.update_log_file()
        return True

    @property
    def plugins(self):
        return set(self.plugin_data.keys())

    def plugin_name_lookup(self, name, re_pattern=False):
        if not re_pattern:
            return name

        for plugin_name in self.plugins:
            if re.search(name, plugin_name):
                return plugin_name
        return None

    def update_plugins(self):
        '''
        .. versionchanged:: 2.16.2
            Method was deprecated.
        '''
        raise DeprecationWarning('The `update_plugins` method was deprecated '
                                 'in version 2.16.2.')

    def gtk_thread_active(self):
        '''
        Returns
        -------
        bool
            ``True`` if the currently active thread is the GTK thread.

        .. versionadded:: 2.11.2
        '''
        if self.gtk_thread is not None and (threading.current_thread().ident
                                            == self.gtk_thread.ident):
            return True
        else:
            return False

    def run(self):
        '''
        .. versionchanged:: 2.11.2
            Set :attr:`gtk_thread` attribute, holding a reference to the thread
            that the GTK main loop is executing in.

        .. versionchanged:: 2.16.2
            Do not attempt to update plugins.
        '''
        logger = _L()  # use logger with method context
        self.gtk_thread = threading.current_thread()

        # set realtime mode to false on startup
        if self.name in self.config.data and \
                'realtime_mode' in self.config.data[self.name]:
            self.config.data[self.name]['realtime_mode'] = False

        plugin_manager.emit_signal('on_plugin_enable')
        log_file = self.get_app_values()['log_file']
        if not log_file:
            self.set_app_values({
                'log_file':
                ph.path(self.config['data_dir']).joinpath('microdrop.log')
            })

        pwd = ph.path(os.getcwd()).realpath()
        if '' in sys.path and pwd.joinpath('plugins').isdir():
            logger.info(
                '[warning] Removing working directory `%s` from Python'
                ' import path.', pwd)
            sys.path.remove('')

        # Import enabled plugins from Conda environment.
        conda_plugins_dir = mpm.api.MICRODROP_CONDA_ETC.joinpath(
            'plugins', 'enabled')
        if conda_plugins_dir.isdir():
            plugin_manager.load_plugins(conda_plugins_dir,
                                        import_from_parent=False)
        self.update_log_file()

        logger.info('User data directory: %s', self.config['data_dir'])
        logger.info('Plugins directory: %s', conda_plugins_dir)
        logger.info('Devices directory: %s', self.get_device_directory())

        FormViewDialog.default_parent = self.main_window_controller.view
        self.builder.connect_signals(self.signals)

        observers = {}
        plugins_to_disable_by_default = []
        # Enable plugins according to schedule requests
        for package_name in self.config['plugins']['enabled']:
            try:
                service = plugin_manager. \
                    get_service_instance_by_package_name(package_name)
                observers[service.name] = service
            except KeyError:
                logger.warning('No plugin found registered with name `%s`',
                               package_name)
                # Mark plugin to be removed from "enabled" list to prevent
                # trying to enable it on future launches.
                plugins_to_disable_by_default.append(package_name)
            except Exception, exception:
                logger.error(exception, exc_info=True)
        # Remove marked plugins from "enabled" list to prevent trying to enable
        # it on future launches.
        for package_name_i in plugins_to_disable_by_default:
            self.config['plugins']['enabled'].remove(package_name_i)

        schedule = plugin_manager.get_schedule(observers, "on_plugin_enable")

        # Load optional plugins marked as enabled in config
        for p in schedule:
            try:
                plugin_manager.enable(p)
            except KeyError:
                logger.warning('Requested plugin (%s) is not available.\n\n'
                               'Please check that it exists in the plugins '
                               'directory:\n\n    %s' %
                               (p, self.config['plugins']['directory']),
                               exc_info=True)
        plugin_manager.log_summary()

        self.experiment_log = None

        # save the protocol name from the config file because it is
        # automatically overwritten when we load a new device
        protocol_name = self.config['protocol']['name']

        # if there is no device specified in the config file, try choosing one
        # from the device directory by default
        device_directory = ph.path(self.get_device_directory())
        if not self.config['dmf_device']['name']:
            try:
                self.config['dmf_device']['name'] = \
                    device_directory.dirs()[0].name
            except Exception:
                pass

        # load the device from the config file
        if self.config['dmf_device']['name']:
            if device_directory:
                device_path = os.path.join(device_directory,
                                           self.config['dmf_device']['name'],
                                           DEVICE_FILENAME)
                self.dmf_device_controller.load_device(device_path)

        # if we successfully loaded a device
        if self.dmf_device:
            # reapply the protocol name to the config file
            self.config['protocol']['name'] = protocol_name

            # load the protocol
            if self.config['protocol']['name']:
                directory = self.get_device_directory()
                if directory:
                    filename = os.path.join(directory,
                                            self.config['dmf_device']['name'],
                                            "protocols",
                                            self.config['protocol']['name'])
                    self.protocol_controller.load_protocol(filename)

        data = self.get_data("microdrop.app")
        x = data.get('x', None)
        y = data.get('y', None)
        width = data.get('width', 400)
        height = data.get('height', 600)
        self.main_window_controller.view.resize(width, height)
        if x is not None and y is not None:
            self.main_window_controller.view.move(x, y)
        plugin_manager.emit_signal('on_gui_ready')
        self.main_window_controller.main()
Ejemplo n.º 9
0
class DropBotDxPlugin(Plugin, StepOptionsController, AppDataController):
    """
    This class is automatically registered with the PluginManager.
    """
    implements(IPlugin)
    implements(IWaveformGenerator)

    serial_ports_ = [port for port in get_serial_ports()]
    if len(serial_ports_):
        default_port_ = serial_ports_[0]
    else:
        default_port_ = None

    AppFields = Form.of(
        Enum.named('serial_port').using(default=default_port_,
                                        optional=True).valued(*serial_ports_),
        Float.named('default_duration').using(default=1000, optional=True),
        Float.named('default_voltage').using(default=80, optional=True),
        Float.named('default_frequency').using(default=10e3, optional=True),
    )

    version = get_plugin_info(path(__file__).parent).version

    @property
    def StepFields(self):
        """
        Expose StepFields as a property to avoid breaking code that accesses
        the StepFields member (vs through the get_step_form_class method).
        """
        return self.get_step_form_class()

    def __init__(self):
        self.control_board = None
        self.name = get_plugin_info(path(__file__).parent).plugin_name
        self.connection_status = "Not connected"
        self.current_frequency = None
        self.timeout_id = None
        self.channel_states = pd.Series()
        self.plugin = None
        self.plugin_timeout_id = None

    def get_step_form_class(self):
        """
        Override to set default values based on their corresponding app options.
        """
        app = get_app()
        app_values = self.get_app_values()
        return Form.of(
            Integer.named('duration').using(
                default=app_values['default_duration'],
                optional=True,
                validators=[
                    ValueAtLeast(minimum=0),
                ]),
            Float.named('voltage').using(
                default=app_values['default_voltage'],
                optional=True,
                validators=[ValueAtLeast(minimum=0), max_voltage]),
            Float.named('frequency').using(
                default=app_values['default_frequency'],
                optional=True,
                validators=[ValueAtLeast(minimum=0), check_frequency]),
        )

    def update_channel_states(self, channel_states):
        # Update locally cached channel states with new modified states.
        try:
            self.channel_states = channel_states.combine_first(
                self.channel_states)
        except ValueError:
            logging.info('channel_states: %s', channel_states)
            logging.info('self.channel_states: %s', self.channel_states)
            logging.info('', exc_info=True)
        else:
            app = get_app()
            connected = self.control_board != None
            if connected and (app.realtime_mode or app.running):
                self.on_step_run()

    def cleanup_plugin(self):
        if self.plugin_timeout_id is not None:
            gobject.source_remove(self.plugin_timeout_id)
        if self.plugin is not None:
            self.plugin = None

    def on_plugin_enable(self):
        super(DropBotDxPlugin, self).on_plugin_enable()

        self.cleanup_plugin()
        # Initialize 0MQ hub plugin and subscribe to hub messages.
        self.plugin = DmfZmqPlugin(self,
                                   self.name,
                                   get_hub_uri(),
                                   subscribe_options={zmq.SUBSCRIBE: ''})
        # Initialize sockets.
        self.plugin.reset()

        # Periodically process outstanding message received on plugin sockets.
        self.plugin_timeout_id = gtk.timeout_add(10, self.plugin.check_sockets)

        self.check_device_name_and_version()
        if get_app().protocol:
            self.on_step_run()
            self._update_protocol_grid()

    def on_plugin_disable(self):
        self.cleanup_plugin()
        if get_app().protocol:
            self.on_step_run()
            self._update_protocol_grid()

    def on_app_exit(self):
        """
        Handler called just before the Microdrop application exits.
        """
        self.cleanup_plugin()
        try:
            self.control_board.hv_output_enabled = False
        except:  # ignore any exceptions (e.g., if the board is not connected)
            pass

    def on_protocol_swapped(self, old_protocol, protocol):
        self._update_protocol_grid()

    def _update_protocol_grid(self):
        pgc = get_service_instance(ProtocolGridController, env='microdrop')
        if pgc.enabled_fields:
            pgc.update_grid()

    def on_app_options_changed(self, plugin_name):
        app = get_app()
        if plugin_name == self.name:
            app_values = self.get_app_values()
            reconnect = False

            if self.control_board:
                for k, v in app_values.items():
                    if k == 'serial_port' and self.control_board.port != v:
                        reconnect = True

            if reconnect:
                self.connect()

            self._update_protocol_grid()
        elif plugin_name == app.name:
            # Turn off all electrodes if we're not in realtime mode and not
            # running a protocol.
            if (self.control_board and not app.realtime_mode
                    and not app.running):
                logger.info('Turning off all electrodes.')
                self.control_board.hv_output_enabled = False

    def connect(self):
        """
        Try to connect to the control board at the default serial port selected
        in the Microdrop application options.

        If unsuccessful, try to connect to the control board on any available
        serial port, one-by-one.
        """
        self.current_frequency = None
        if len(DropBotDxPlugin.serial_ports_):
            app_values = self.get_app_values()
            # try to connect to the last successful port
            try:
                self.control_board = SerialProxy(
                    port=str(app_values['serial_port']))
            except:
                logger.warning(
                    'Could not connect to control board on port %s.'
                    ' Checking other ports...',
                    app_values['serial_port'],
                    exc_info=True)
                self.control_board = SerialProxy()
            self.control_board.initialize_switching_boards()
            app_values['serial_port'] = self.control_board.port
            self.set_app_values(app_values)
        else:
            raise Exception("No serial ports available.")

    def check_device_name_and_version(self):
        """
        Check to see if:

         a) The connected device is a OpenDrop
         b) The device firmware matches the host driver API version

        In the case where the device firmware version does not match, display a
        dialog offering to flash the device with the firmware version that
        matches the host driver API version.
        """
        try:
            self.connect()
            name = self.control_board.properties['package_name']
            if name != self.control_board.host_package_name:
                raise Exception("Device is not a DropBot DX")

            host_software_version = self.control_board.host_software_version
            remote_software_version = self.control_board.remote_software_version

            # Reflash the firmware if it is not the right version.
            if host_software_version != remote_software_version:
                response = yesno(
                    "The DropBot DX firmware version (%s) "
                    "does not match the driver version (%s). "
                    "Update firmware?" %
                    (remote_software_version, host_software_version))
                if response == gtk.RESPONSE_YES:
                    self.on_flash_firmware()
        except Exception, why:
            logger.warning("%s" % why)

        self.update_connection_status()
Ejemplo n.º 10
0
class App(SingletonPlugin, AppDataController):
    implements(IPlugin)
    '''
INFO:  <Plugin App 'microdrop.app'>
INFO:  <Plugin ConfigController 'microdrop.gui.config_controller'>
INFO:  <Plugin DmfDeviceController 'microdrop.gui.dmf_device_controller'>
INFO:  <Plugin ExperimentLogController
         'microdrop.gui.experiment_log_controller'>
INFO:  <Plugin MainWindowController 'microdrop.gui.main_window_controller'>
INFO:  <Plugin ProtocolController 'microdrop.gui.protocol_controller'>
INFO:  <Plugin ProtocolGridController 'microdrop.gui.protocol_grid_controller'>
    '''
    core_plugins = [
        'microdrop.app', 'microdrop.gui.config_controller',
        'microdrop.gui.dmf_device_controller',
        'microdrop.gui.experiment_log_controller',
        'microdrop.gui.main_window_controller',
        'microdrop.gui.protocol_controller',
        'microdrop.gui.protocol_grid_controller', 'wheelerlab.zmq_hub_plugin',
        'wheelerlab.electrode_controller_plugin',
        'wheelerlab.device_info_plugin'
    ]

    AppFields = Form.of(
        Integer.named('x').using(default=None,
                                 optional=True,
                                 properties={'show_in_gui': False}),
        Integer.named('y').using(default=None,
                                 optional=True,
                                 properties={'show_in_gui': False}),
        Integer.named('width').using(default=400,
                                     optional=True,
                                     properties={'show_in_gui': False}),
        Integer.named('height').using(default=500,
                                      optional=True,
                                      properties={'show_in_gui': False}),
        Enum.named('update_automatically'  #pylint: disable-msg=E1101,E1120
                   ).using(default=1, optional=True).valued(
                       'auto-update',
                       'check for updates, but ask before installing',
                       '''don't check for updates'''),
        String.named('server_url').using(  #pylint: disable-msg=E1120
            default='http://microfluidics.utoronto.ca/update',
            optional=True,
            properties=dict(show_in_gui=False)),
        Boolean.named('realtime_mode').using(  #pylint: disable-msg=E1120
            default=False,
            optional=True,
            properties=dict(show_in_gui=False)),
        Filepath.named('log_file').using(  #pylint: disable-msg=E1120
            default='',
            optional=True,
            properties={'action': gtk.FILE_CHOOSER_ACTION_SAVE}),
        Boolean.named('log_enabled').using(  #pylint: disable-msg=E1120
            default=False, optional=True),
        Enum.named('log_level').using(  #pylint: disable-msg=E1101, E1120
            default='info', optional=True).valued('debug', 'info', 'warning',
                                                  'error', 'critical'),
    )

    def __init__(self):
        args = parse_args()

        print 'Arguments: %s' % args

        self.name = "microdrop.app"
        # get the version number
        self.version = ""
        try:
            raise Exception
            version = subprocess.Popen(
                ['git', 'describe'],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                stdin=subprocess.PIPE).communicate()[0].rstrip()
            m = re.match('v(\d+)\.(\d+)-(\d+)', version)
            self.version = "%s.%s.%s" % (m.group(1), m.group(2), m.group(3))
            branch = subprocess.Popen(
                ['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                stdin=subprocess.PIPE).communicate()[0].rstrip()
            if branch.strip() != 'master':
                self.version += "-%s" % branch
        except:
            import pkg_resources

            version = pkg_resources.get_distribution('microdrop').version

            dev = ('dev' in version)

            self.version = re.sub('\.dev.*', '', re.sub('post', '', version))
            if dev:
                self.version += "-dev"

        self.realtime_mode = False
        self.running = False
        self.builder = gtk.Builder()
        self.signals = {}
        self.plugin_data = {}

        # these members are initialized by plugins
        self.experiment_log_controller = None
        self.config_controller = None
        self.dmf_device_controller = None
        self.protocol_controller = None
        self.main_window_controller = None

        # Enable custom logging handler
        logging.getLogger().addHandler(CustomHandler())
        self.log_file_handler = None

        # config model
        try:
            self.config = Config(args.config)
        except IOError:
            logging.error(
                'Could not read configuration file, `%s`.  Make sure'
                ' it exists and is readable.', args.config)
            raise SystemExit(-1)

        # set the log level
        if self.name in self.config.data and ('log_level'
                                              in self.config.data[self.name]):
            self._set_log_level(self.config.data[self.name]['log_level'])
        logger.info('MicroDrop version: %s', self.version)
        logger.info('Running in working directory: %s', os.getcwd())

        # Run post install hooks for freshly installed plugins.
        # It is necessary to delay the execution of these hooks here due to
        # Windows file locking preventing the deletion of files that are in use.
        post_install_queue_path = \
            path(self.config.data['plugins']['directory']) \
            .joinpath('post_install_queue.yml')
        if post_install_queue_path.isfile():
            post_install_queue = yaml.load(post_install_queue_path.bytes())
            post_install_queue = map(path, post_install_queue)

            logger.info('[App] processing post install hooks.')
            for p in post_install_queue[:]:
                try:
                    info = get_plugin_info(p)
                    logger.info("  running post install hook for %s" %
                                info.plugin_name)
                    plugin_manager.post_install(p)
                except Exception:
                    logging.info(''.join(traceback.format_exc()))
                    logging.error('Error running post-install hook for %s.',
                                  p.name,
                                  exc_info=True)
                finally:
                    post_install_queue.remove(p)
            post_install_queue_path.write_bytes(yaml.dump(post_install_queue))

        # Delete paths that were marked during the uninstallation of a plugin.
        # It is necessary to delay the deletion until here due to Windows file
        # locking preventing the deletion of files that are in use.
        deletions_path = path(self.config.data['plugins']['directory'])\
                .joinpath('requested_deletions.yml')
        if deletions_path.isfile():
            requested_deletions = yaml.load(deletions_path.bytes())
            requested_deletions = map(path, requested_deletions)

            logger.info('[App] processing requested deletions.')
            for p in requested_deletions[:]:
                try:
                    if p != p.abspath():
                        logger.info(
                            '    (warning) ignoring path %s since it '
                            'is not absolute', p)
                        continue
                    if p.isdir():
                        info = get_plugin_info(p)
                        if info:
                            logger.info('  deleting %s' % p)
                            cwd = os.getcwd()
                            os.chdir(p.parent)
                            try:
                                path(p.name).rmtree()  #ignore_errors=True)
                            except Exception, why:
                                logger.warning('Error deleting path %s (%s)',
                                               p, why)
                                raise
                            os.chdir(cwd)
                            requested_deletions.remove(p)
                    else:  # if the directory doesn't exist, remove it from the
                        # list
                        requested_deletions.remove(p)
                except (AssertionError, ):
                    logger.info('  NOT deleting %s' % (p))
                    continue
                except (Exception, ):
                    logger.info('  NOT deleting %s' % (p))
                    continue
Ejemplo n.º 11
0
class DmfControlBoardPlugin(Plugin, StepOptionsController, AppDataController):
    """
    This class is automatically registered with the PluginManager.
    """
    implements(IPlugin)
    implements(IWaveformGenerator)

    serial_ports_ = [
        port for port in serial_device.SerialDevice().get_serial_ports()
    ]
    if len(serial_ports_):
        default_port_ = serial_ports_[0]
    else:
        default_port_ = None

    AppFields = Form.of(
        Integer.named('sampling_time_ms').using(default=10, optional=True,
            validators=[ValueAtLeast(minimum=0), ],),
        Integer.named('delay_between_samples_ms').using(default=0,
            optional=True, validators=[ValueAtLeast(minimum=0), ],),
        Enum.named('serial_port').using(default=default_port_, optional=True)\
            .valued(*serial_ports_),
    )

    StepFields = Form.of(
        Integer.named('duration').using(default=100,
                                        optional=True,
                                        validators=[
                                            ValueAtLeast(minimum=0),
                                        ]),
        Float.named('voltage').using(default=100,
                                     optional=True,
                                     validators=[
                                         ValueAtLeast(minimum=0),
                                     ]),
        Float.named('frequency').using(default=1e3,
                                       optional=True,
                                       validators=[
                                           ValueAtLeast(minimum=0),
                                       ]),
        Boolean.named('feedback_enabled').using(default=True, optional=True),
    )
    _feedback_fields = set(['feedback_enabled'])
    version = get_plugin_info(path(__file__).parent.parent).version

    def __init__(self):
        self.control_board = DmfControlBoard()
        self.name = get_plugin_info(path(__file__).parent.parent).plugin_name
        self.url = self.control_board.host_url()
        self.steps = []  # list of steps in the protocol
        self.feedback_options_controller = None
        self.feedback_results_controller = None
        self.feedback_calibration_controller = None
        self.initialized = False
        self.connection_status = "Not connected"
        self.n_voltage_adjustments = None
        self.amplifier_gain_initialized = False
        self.current_frequency = None
        self.edit_log_calibration_menu_item = gtk.MenuItem("Edit calibration")
        self.save_log_calibration_menu_item = \
            gtk.MenuItem("Save calibration to file")
        self.load_log_calibration_menu_item = \
            gtk.MenuItem("Load calibration from file")
        self.timeout_id = None

    def on_plugin_enable(self):
        if not self.initialized:
            self.feedback_options_controller = FeedbackOptionsController(self)
            self.feedback_results_controller = FeedbackResultsController(self)
            self.feedback_calibration_controller = \
                FeedbackCalibrationController(self)
            self.edit_log_calibration_menu_item.connect(
                "activate",
                self.feedback_calibration_controller.on_edit_log_calibration)
            self.save_log_calibration_menu_item.connect(
                "activate",
                self.feedback_calibration_controller.on_save_log_calibration)
            self.load_log_calibration_menu_item.connect(
                "activate",
                self.feedback_calibration_controller.on_load_log_calibration)

            experiment_log_controller = get_service_instance_by_name(
                "microdrop.gui.experiment_log_controller", "microdrop")
            if hasattr(experiment_log_controller, 'popup'):
                experiment_log_controller.popup.add_item(
                    self.edit_log_calibration_menu_item)
                experiment_log_controller.popup.add_item(
                    self.save_log_calibration_menu_item)
                experiment_log_controller.popup.add_item(
                    self.load_log_calibration_menu_item)

            app = get_app()
            self.control_board_menu_item = gtk.MenuItem("DMF control board")
            app.main_window_controller.menu_tools.append(
                self.control_board_menu_item)

            self.control_board_menu = gtk.Menu()
            self.control_board_menu.show()
            self.control_board_menu_item.set_submenu(self.control_board_menu)

            self.feedback_options_controller.on_plugin_enable()

            menu_item = gtk.MenuItem("Perform calibration")
            menu_item.connect(
                "activate",
                self.feedback_calibration_controller.on_perform_calibration)
            self.control_board_menu.append(menu_item)
            self.perform_calibration_menu_item = menu_item
            menu_item.show()

            menu_item = gtk.MenuItem("Load calibration from file")
            menu_item.connect("activate",
                              self.feedback_calibration_controller. \
                                  on_load_calibration_from_file)
            self.control_board_menu.append(menu_item)
            self.load_calibration_from_file_menu_item = menu_item
            menu_item.show()

            menu_item = gtk.MenuItem("Edit calibration settings")
            menu_item.connect("activate", self.on_edit_calibration)
            self.control_board_menu.append(menu_item)
            self.edit_calibration_menu_item = menu_item
            menu_item.show()

            menu_item = gtk.MenuItem("Reset calibration to default values")
            menu_item.connect("activate",
                              self.on_reset_calibration_to_default_values)
            self.control_board_menu.append(menu_item)
            self.reset_calibration_to_default_values_menu_item = menu_item
            menu_item.show()

            self.initialized = True

        super(DmfControlBoardPlugin, self).on_plugin_enable()
        self.check_device_name_and_version()
        self.control_board_menu_item.show()
        self.edit_log_calibration_menu_item.show()
        self.feedback_results_controller.feedback_results_menu_item.show()
        if get_app().protocol:
            self.on_step_run()
            pgc = get_service_instance(ProtocolGridController, env='microdrop')
            pgc.update_grid()

    def on_plugin_disable(self):
        self.feedback_options_controller.on_plugin_disable()
        self.control_board_menu_item.hide()
        self.edit_log_calibration_menu_item.hide()
        self.feedback_results_controller.window.hide()
        self.feedback_results_controller.feedback_results_menu_item.hide()
        if get_app().protocol:
            self.on_step_run()
            pgc = get_service_instance(ProtocolGridController, env='microdrop')
            pgc.update_grid()

    def on_app_options_changed(self, plugin_name):
        if plugin_name == self.name:
            app_values = self.get_app_values()
            if self.control_board.connected() and \
            self.control_board.port != app_values['serial_port']:
                self.connect()

    def connect(self):
        self.current_frequency = None
        self.amplifier_gain_initialized = False
        if len(DmfControlBoardPlugin.serial_ports_):
            app_values = self.get_app_values()
            # try to connect to the last successful port
            try:
                self.control_board.connect(str(app_values['serial_port']))
            except Exception, why:
                logger.warning(
                    'Could not connect to control board on port %s. '
                    'Checking other ports...' % app_values['serial_port'])
                self.control_board.connect()
            app_values['serial_port'] = self.control_board.port
            self.set_app_values(app_values)
        else:
Ejemplo n.º 12
0
    name = String.validated_by(Present())
    addresses = Address.using(default=1)


GuardianContentSearch = Dict.of(
    String.named('q').using(label='Search'
               ).validated_by(Converted()),
    String.named('tag').using(label='Tag filter', optional=True
               ).validated_by(Converted()),
    String.named('section').using(label='Section filter', optional=True
               ).validated_by(Converted()),
    DateYYYYMMDD.named('from-date').using(label='From Date filter', optional=True
               ).validated_by(Converted()),
    DateYYYYMMDD.named('to-date').using(label='To Date filter', optional=True
               ).validated_by(Converted()),
    Enum.named('order-by').valued(['newest', 'oldest', 'relevance']
               ).using(default='newest', optional=True),
    Integer.named('page').using(label='Page Index', default=1
               ).validated_by(Converted(), ValueGreaterThan(0)),
    Integer.named('page-size').using(label='Page Size', default=10
               ).validated_by(Converted(), ValueGreaterThan(0)),
    Enum.named('format').valued(['json', 'xml']).using(default='json'
               ).validated_by(Converted()),
    JoinedString.named('show-fields').using(label='Show fields', default=['all']
               ).validated_by(Converted()),
    JoinedString.named('show-tags').using(label='Show tabs', default=['all']
               ).validated_by(Converted()),
    JoinedString.named('show-refinements').using(label='Show refinements', default=['all']
               ).validated_by(Converted()),
    Integer.named('refinements-size').using(label='Refinement size', default=10
               ).validated_by(Converted(), ValueGreaterThan(0)),
)
Ejemplo n.º 13
0
class SyringePumpPlugin(Plugin, AppDataController, StepOptionsController):
    """
    This class is automatically registered with the PluginManager.
    """
    implements(IPlugin)
    version = get_plugin_info(path(__file__).parent).version
    plugin_name = get_plugin_info(path(__file__).parent).plugin_name

    serial_ports_ = [port for port in get_serial_ports()]
    if len(serial_ports_):
        default_port_ = serial_ports_[0]
    else:
        default_port_ = None
    '''
    AppFields
    ---------

    A flatland Form specifying application options for the current plugin.
    Note that nested Form objects are not supported.

    Since we subclassed AppDataController, an API is available to access and
    modify these attributes.  This API also provides some nice features
    automatically:
        -all fields listed here will be included in the app options dialog
            (unless properties=dict(show_in_gui=False) is used)
        -the values of these fields will be stored persistently in the microdrop
            config file, in a section named after this plugin's name attribute
    '''
    AppFields = Form.of(
        Enum.named('serial_port').using(default=default_port_,
                                        optional=True).valued(*serial_ports_),
        Float.named('steps_per_microliter').using(optional=True, default=1.0),
    )
    '''
    StepFields
    ---------

    A flatland Form specifying the per step options for the current plugin.
    Note that nested Form objects are not supported.

    Since we subclassed StepOptionsController, an API is available to access and
    modify these attributes.  This API also provides some nice features
    automatically:
        -all fields listed here will be included in the protocol grid view
            (unless properties=dict(show_in_gui=False) is used)
        -the values of these fields will be stored persistently for each step
    '''
    StepFields = Form.of(
        Float.named('microliters_per_min').using(
            optional=True,
            default=60,
            #validators=
            #[ValueAtLeast(minimum=0),
            # ValueAtMost(maximum=100000)]
        ),
        Float.named('microliters').using(
            optional=True,
            default=10,
            #validators=
            #[ValueAtLeast(minimum=0),
            # ValueAtMost(maximum=100000)]
        ),
    )

    def __init__(self):
        self.name = self.plugin_name
        self.proxy = None
        self.initialized = False  # Latch to, e.g., config menus, only once

    def connect(self):
        """ 
        Try to connect to the syring pump at the default serial
        port selected in the Microdrop application options.

        If unsuccessful, try to connect to the proxy on any
        available serial port, one-by-one.
        """

        from stepper_motor_controller import SerialProxy

        if len(SyringePumpPlugin.serial_ports_):
            app_values = self.get_app_values()
            # try to connect to the last successful port
            try:
                self.proxy = SerialProxy(port=str(app_values['serial_port']))
            except:
                logger.warning(
                    'Could not connect to the syringe pump on port %s. '
                    'Checking other ports...',
                    app_values['serial_port'],
                    exc_info=True)
                self.proxy = SerialProxy()
            app_values['serial_port'] = self.proxy.port
            self.set_app_values(app_values)
        else:
            raise Exception("No serial ports available.")

    def check_device_name_and_version(self):
        """
        Check to see if:

         a) The connected device is a what we are expecting
         b) The device firmware matches the host driver API version

        In the case where the device firmware version does not match, display a
        dialog offering to flash the device with the firmware version that
        matches the host driver API version.
        """
        try:
            self.connect()
            properties = self.proxy.properties
            package_name = properties['package_name']
            display_name = properties['display_name']
            if package_name != self.proxy.host_package_name:
                raise Exception("Device is not a %s" %
                                properties['display_name'])

            host_software_version = self.proxy.host_software_version
            remote_software_version = self.proxy.remote_software_version

            # Reflash the firmware if it is not the right version.
            if host_software_version != remote_software_version:
                response = yesno("The %s firmware version (%s) "
                                 "does not match the driver version (%s). "
                                 "Update firmware?" %
                                 (display_name, remote_software_version,
                                  host_software_version))
                if response == gtk.RESPONSE_YES:
                    self.on_flash_firmware()
        except Exception, why:
            logger.warning("%s" % why)
Ejemplo n.º 14
0
class ZmqHubPlugin(SingletonPlugin, AppDataController):
    """
    This class is automatically registered with the PluginManager.
    """
    implements(IPlugin)
    plugin_name = 'microdrop.zmq_hub_plugin'

    '''
    AppFields
    ---------

    A flatland Form specifying application options for the current plugin.
    Note that nested Form objects are not supported.

    Since we subclassed AppDataController, an API is available to access and
    modify these attributes.  This API also provides some nice features
    automatically:
        -all fields listed here will be included in the app options dialog
            (unless properties=dict(show_in_gui=False) is used)
        -the values of these fields will be stored persistently in the microdrop
            config file, in a section named after this plugin's name attribute
    '''
    AppFields = Form.of(
        String.named('hub_uri').using(optional=True, default='tcp://*:31000'),
        Enum.named('log_level').using(default='info', optional=True)
        .valued('debug', 'info', 'warning', 'error', 'critical'))

    def __init__(self):
        self.name = self.plugin_name
        self.hub_process = None
        #: ..versionadded:: 2.25
        self.exec_thread = None

    def on_plugin_enable(self):
        '''
        .. versionchanged:: 2.25
            Start asyncio event loop in background thread to process ZeroMQ hub
            execution requests.
        '''
        super(ZmqHubPlugin, self).on_plugin_enable()
        app_values = self.get_app_values()

        self.cleanup()
        self.hub_process = Process(target=_safe_run_hub,
                                   args=(MicroDropHub(app_values['hub_uri'],
                                                      self.name),
                                         getattr(logging,
                                                 app_values['log_level']
                                                 .upper())))
        # Set process as daemonic so it terminate when main process terminates.
        self.hub_process.daemon = True
        self.hub_process.start()
        _L().info('ZeroMQ hub process (pid=%s, daemon=%s)',
                  self.hub_process.pid, self.hub_process.daemon)

        zmq_ready = threading.Event()

        @asyncio.coroutine
        def _exec_task():
            self.zmq_plugin = ZmqPlugin('microdrop', get_hub_uri())
            self.zmq_plugin.reset()
            zmq_ready.set()

            event = asyncio.Event()
            try:
                yield asyncio.From(event.wait())
            except asyncio.CancelledError:
                _L().info('closing ZeroMQ execution event loop')

        self.zmq_exec_task = cancellable(_exec_task)
        self.exec_thread = threading.Thread(target=self.zmq_exec_task)
        self.exec_thread.deamon = True
        self.exec_thread.start()
        zmq_ready.wait()

    def cleanup(self):
        '''
        .. versionchanged:: 2.25
            Stop asyncio event loop.
        '''
        if self.hub_process is not None:
            self.hub_process.terminate()
            self.hub_process = None
        if self.exec_thread is not None:
            self.zmq_exec_task.cancel()
            self.exec_thread = None
Ejemplo n.º 15
0
class TestPlugin(Plugin, AppDataController, StepOptionsController):
    """
    This class is automatically registered with the PluginManager.
    """
    implements(IPlugin)
    version = get_plugin_info(path(__file__).parent).version
    plugins_name = get_plugin_info(path(__file__).parent).plugin_name
    '''
    AppFields
    ---------

    A flatland Form specifying application options for the current plugin.
    Note that nested Form objects are not supported.

    Since we subclassed AppDataController, an API is available to access and
    modify these attributes.  This API also provides some nice features
    automatically:
        -all fields listed here will be included in the app options dialog
            (unless properties=dict(show_in_gui=False) is used)
        -the values of these fields will be stored persistently in the microdrop
            config file, in a section named after this plugin's name attribute
    '''

    serial_ports_ = [port for port in serial_device.get_serial_ports()]
    if len(serial_ports_):
        default_port_ = serial_ports_[0]
    else:
        default_port_ = None

    AppFields = Form.of(
        Enum.named('serial_port').using(default=default_port_, optional=True)\
            .valued(*serial_ports_),
    )
    '''
    StepFields
    ---------

    A flatland Form specifying the per step options for the current plugin.
    Note that nested Form objects are not supported.

    Since we subclassed StepOptionsController, an API is available to access and
    modify these attributes.  This API also provides some nice features
    automatically:
        -all fields listed here will be included in the protocol grid view
            (unless properties=dict(show_in_gui=False) is used)
        -the values of these fields will be stored persistently for each step
    '''
    StepFields = Form.of(
        Boolean.named('led_on').using(default=False, optional=True), )

    def __init__(self):
        self.name = self.plugins_name
        self.proxy = None

    def on_plugin_enable(self):
        # We need to call AppDataController's on_plugin_enable() to update the
        # application options data.
        AppDataController.on_plugin_enable(self)
        self.on_app_init()
        app_values = self.get_app_values()
        try:
            self.proxy = SerialProxy(port=app_values['serial_port'])
            self.proxy.pin_mode(pin=13, mode=1)
            logger.info('Connected to %s on port %s',
                        self.proxy.properties.display_name,
                        app_values['serial_port'])
        except Exception, e:
            logger.error('Could not connect to base-node-rpc on port %s: %s.',
                         app_values['serial_port'], e)
        if get_app().protocol:
            pgc = get_service_instance(ProtocolGridController, env='microdrop')
            pgc.update_grid()