def _populate_app_fields(self): with WindowServiceProxy(59000) as w: self.video_mode_map = w.get_video_mode_map() if self.video_mode_map: self._video_available = True else: self._video_available = False self.video_mode_keys = sorted(self.video_mode_map.keys()) if self._video_available: self.device_key, self.devices = w.get_video_source_configs() field_list = [ Integer.named('overlay_opacity').using(default=50, optional=True), Directory.named('device_directory').using(default='', optional=True), String.named('transform_matrix').using(default='', optional=True, properties={'show_in_gui': False}), ] if self._video_available: video_mode_enum = Enum.named('video_mode').valued( *self.video_mode_keys).using(default=self.video_mode_keys[0], optional=True) video_enabled_boolean = Boolean.named('video_enabled').using( default=False, optional=True, properties={'show_in_gui': True}) recording_enabled_boolean = Boolean.named('recording_enabled').using( default=False, optional=True, properties={'show_in_gui': False}) field_list.append(video_mode_enum) field_list.append(video_enabled_boolean) field_list.append(recording_enabled_boolean) return Form.of(*field_list)
class PersonForm(FormView): schema_type = Dict.of( String.named('name'), Integer.named('age'), Boolean.named('friendly'), )
def small_form(values=None): SmallForm = Dict.of(String.named(u'field1'), String.named(u'field2'), Boolean.named(u'toggle1'), Boolean.named(u'toggle2'), Array.named(u'multi').of(String), DateYYYYMMDD.named(u'date1')) if values is None: values = { u'field1': u'val', u'toggle2': True, u'multi': [u'a', u'b'], u'date1': datetime.date(1999, 12, 31), } el = SmallForm(values) return {'form': el}
def small_form(values=None): SmallForm = Dict.of( String.named(u'field1'), String.named(u'field2'), Boolean.named(u'toggle1'), Boolean.named(u'toggle2'), Array.named(u'multi').of(String), DateYYYYMMDD.named(u'date1')) if values is None: values = { u'field1': u'val', u'toggle2': True, u'multi': [u'a', u'b'], u'date1': datetime.date(1999, 12, 31), } el = SmallForm(values) return {'form': el}
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))
def __init__(self): window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.set_title("Mpeg2-Player") window.set_default_size(640, 500) window.connect("destroy", self.on_destroy) vbox = gtk.VBox() window.add(vbox) hbox = gtk.HBox() vbox.pack_start(hbox, expand=False) video_mode_enum = Enum.named('video_mode').valued(*self.video_mode_keys) form = Form.of( video_mode_enum.using(default=self.video_mode_keys[0]), Filepath.named('output_path').using(default=''), Integer.named('bitrate').using(default=150, validators=[ValueAtLeast( minimum=25)], properties={'step': 25, 'label': 'Bitrate (KB/s)', }), String.named('transform_string').using(default='1,0,0,0,1,0,0,0,1'), Boolean.named('draw_cairo').using(default=False), ) self.video_mode_form_view = create_form_view(form) for field in ['video_mode', 'output_path', 'bitrate', 'transform_string', 'draw_cairo']: setattr(self, '%s_field' % field, self.video_mode_form_view.form\ .fields[field]) self.video_mode_field.proxy.connect('changed', self._on_mode_changed) self.video_source = None hbox.add(self.video_mode_form_view.widget) self.button = gtk.Button("Start") hbox.pack_start(self.button, False) self.button.connect("clicked", self.start_stop) self.aframe = gtk.AspectFrame(xalign=0.5, yalign=1.0, ratio=4.0 / 3.0, obey_child=False) self.pipeline = None self._proxy = None vbox.pack_start(self.aframe, expand=True) self.movie_view = GtkVideoView() self.movie_window = self.movie_view.widget self.aframe.add(self.movie_window) window.show_all() self.window = window
def dict_to_form(dict_): """ Generate a flatland form based on a pandas Series. """ from flatland import Boolean, Form, String, Integer, Float def is_float(v): try: return (float(str(v)), True)[1] except (ValueError, TypeError): return False def is_int(v): try: return (int(str(v)), True)[1] except (ValueError, TypeError): return False def is_bool(v): return v in (True, False) schema_entries = [] for k, v in dict_.iteritems(): if is_int(v): schema_entries.append( Integer.named(k).using(default=v, optional=True)) elif is_float(v): schema_entries.append( Float.named(k).using(default=v, optional=True)) elif is_bool(v): schema_entries.append( Boolean.named(k).using(default=v, optional=True)) elif type(v) == str: schema_entries.append( String.named(k).using(default=v, optional=True)) return Form.of(*schema_entries)
def dict_to_form(dict): ''' Generate a flatland form based on a pandas Series. ''' from flatland import Boolean, Form, String, Integer, Float def is_float(v): try: return (float(str(v)), True)[1] except (ValueError, TypeError): return False def is_int(v): try: return (int(str(v)), True)[1] except (ValueError, TypeError): return False def is_bool(v): return v in (True, False) schema_entries = [] for k, v in dict.iteritems(): if is_int(v): schema_entries.append(Integer.named(k).using(default=v, optional=True)) elif is_float(v): schema_entries.append(Float.named(k).using(default=v, optional=True)) elif is_bool(v): schema_entries.append(Boolean.named(k).using(default=v, optional=True)) elif type(v) == str: schema_entries.append(String.named(k).using(default=v, optional=True)) return Form.of(*schema_entries)
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:
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()
# TODO: CONTENT validation? can we do it here? *common_meta) UserMetaSchema = DuckDict.named('UserMetaSchema').of( String.named(keys.CONTENTTYPE).validated_by(user_contenttype_validator), String.named(keys.EMAIL).using(optional=True), String.named(keys.OPENID).using(optional=True), String.named(keys.ENC_PASSWORD).using(optional=True), String.named(keys.RECOVERPASS_KEY).using(optional=True), String.named(keys.THEME_NAME).using(optional=True), String.named(keys.TIMEZONE).using(optional=True), String.named(keys.LOCALE).using(optional=True), String.named(keys.CSS_URL).using(optional=True), Integer.named(keys.RESULTS_PER_PAGE).using(optional=True), Integer.named(keys.EDIT_ROWS).using(optional=True), Boolean.named(keys.DISABLED).using(optional=True), Boolean.named(keys.WANT_TRIVIAL).using(optional=True), Boolean.named(keys.SHOW_COMMENTS).using(optional=True), Boolean.named(keys.EDIT_ON_DOUBLECLICK).using(optional=True), Boolean.named(keys.SCROLL_PAGE_AFTER_EDIT).using(optional=True), Boolean.named(keys.MAILTO_AUTHOR).using(optional=True), List.named(keys.QUICKLINKS).of( String.named('quicklinks')).using(optional=True), List.named(keys.SUBSCRIPTIONS).of( String.named('subscription').validated_by( subscription_validator)).using(optional=True), List.named(keys.EMAIL_SUBSCRIBED_EVENTS).of( String.named('email_subscribed_event')).using(optional=True), # TODO: DuckDict.named('bookmarks').using(optional=True), *common_meta)
*common_meta ) UserMetaSchema = DuckDict.named('UserMetaSchema').of( String.named(keys.CONTENTTYPE).validated_by(user_contenttype_validator), String.named(keys.EMAIL).using(optional=True), String.named(keys.OPENID).using(optional=True), String.named(keys.ENC_PASSWORD).using(optional=True), String.named(keys.RECOVERPASS_KEY).using(optional=True), String.named(keys.THEME_NAME).using(optional=True), String.named(keys.TIMEZONE).using(optional=True), String.named(keys.LOCALE).using(optional=True), String.named(keys.CSS_URL).using(optional=True), Integer.named(keys.RESULTS_PER_PAGE).using(optional=True), Integer.named(keys.EDIT_ROWS).using(optional=True), Boolean.named(keys.DISABLED).using(optional=True), Boolean.named(keys.WANT_TRIVIAL).using(optional=True), Boolean.named(keys.SHOW_COMMENTS).using(optional=True), Boolean.named(keys.EDIT_ON_DOUBLECLICK).using(optional=True), Boolean.named(keys.SCROLL_PAGE_AFTER_EDIT).using(optional=True), Boolean.named(keys.MAILTO_AUTHOR).using(optional=True), List.named(keys.QUICKLINKS).of(String.named('quicklinks')).using(optional=True), List.named(keys.SUBSCRIPTIONS).of(String.named('subscription').validated_by(subscription_validator)).using(optional=True), List.named(keys.EMAIL_SUBSCRIBED_EVENTS).of(String.named('email_subscribed_event')).using(optional=True), # TODO: DuckDict.named('bookmarks').using(optional=True), *common_meta ) def validate_data(meta, data): """
#! -*- coding: utf-8 -*- from flatland import Array, Boolean, Integer from flatland.out import generic from flatland.out.generic import Context from tests._util import unicode_coercion_allowed, textstr Unspecified = object() Unique = object() schema = Integer.named(u'number') boolean_schema = Boolean.named(u'bool') partial_anon_schema = Array.named(u'array').of(Integer) full_anon_schema = Array.of(Integer) def assert_bound_transform(fn, tagname, given, expected, **kw): return assert_transform(fn, tagname, given, expected, **kw) def assert_unbound_transform(fn, tagname, given, expected, **kw): kw['bind'] = None return assert_transform(fn, tagname, given, expected, **kw) def assert_transform(fn, tagname, given, expected, context=Unspecified, bind=Unspecified, contents=Unspecified,
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
class MrBoxPeripheralBoardPlugin(AppDataController, StepOptionsController, Plugin): ''' This class is automatically registered with the PluginManager. ''' implements(IPlugin) plugin_name = str(ph.path(__file__).realpath().parent.name) try: version = __version__ except NameError: version = 'v0.0.0+unknown' AppFields = Form.of(Boolean.named('Use PMT y-axis SI units') .using(default=True, optional=True)) StepFields = Form.of(# PMT Fields Boolean.named('Measure_PMT') .using(default=False, optional=True), # Only allow PMT Duration to be set if `Measure_PMT` # field is set to `True`. Integer.named('Measurement_duration_(s)') .using(default=10, optional=True, validators=[ValueAtLeast(minimum=0)], properties={'mappers': [PropertyMapper ('sensitive', attr='Measure_PMT'), PropertyMapper ('editable', attr='Measure_PMT')]})) # Only allow ADC Gain to be set if `Measure_PMT` field # is set to `True`. # TODO Convert ADC Gain to dropdown list with # valid_values = (1,2,4,8,16) # Integer.named('ADC_Gain') # .using(default=1, optional=True, # validators=[ValueAtLeast(minimum=1), # ValueAtMost(maximum=16)], # properties={'mappers': # [PropertyMapper # ('sensitive', attr='Measure_PMT'), # PropertyMapper # ('editable', # attr='Measure_PMT')]}), def __init__(self): super(MrBoxPeripheralBoardPlugin, self).__init__() self.board = None # XXX `name` attribute is required in addition to `plugin_name` # # The `name` attribute is required in addition to the `plugin_name` # attribute because MicroDrop uses it for plugin labels in, for # example, the plugin manager dialog. self.name = self.plugin_name # Flag to indicate whether user has already been warned about the board # not being connected when trying to set board state. self._user_warned = False # `dropbot.SerialProxy` instance self.dropbot_remote = None # Latch to, e.g., config menus, only once self.initialized = False self.adc_gain_calibration = None self.adc_offset_calibration = None self.off_cal_val = None def reset_board_state(self): ''' Reset MR-Box peripheral board to default state. ''' # Reset user warned state (i.e., warn user next time board settings # are applied when board is not connected). self._user_warned = False if self.board is None: return # TODO Add reset method for each component (PMT) # TODO to respective `mr-box-peripheral-board.py` C++ classes code. # Set PMT control voltage to zero. self.board.pmt_set_pot(0) # Start the ADC and Perform ADC Calibration MAX11210_begin(self.board) def apply_step_options(self, step_options): ''' Apply the specified step options. Parameters ---------- step_options : dict Dictionary containing the MR-Box peripheral board plugin options for a protocol step. ''' app = get_app() app_values = self.get_app_values() if self.board: step_log = {} services_by_name = {service_i.name: service_i for service_i in PluginGlobals .env('microdrop.managed').services} step_label = None if 'wheelerlab.step_label_plugin' in services_by_name: # Step label is set for current step step_label_plugin = (services_by_name .get('wheelerlab.step_label_plugin')) step_label = (step_label_plugin.get_step_options() or {}).get('label') # Apply board hardware options. try: # PMT/ADC # ------- if step_options.get('Measure_PMT'): # Start the ADC and Perform ADC Calibration MAX11210_begin(self.board) if step_label.lower() == 'background': ''' Set PMT control voltage via digipot.''' # Divide the control voltage by the maximum 1100 mV and # convert it to digipot steps ''' Perform certain calibration steps only for the background measurement. Read from the 24bit Registries (SCGC, SCOC) and store their values for the rest of the measurements. ''' logger.warning('Open PMT shutter and close box lid') self.adc_gain_calibration = self.board.MAX11210_getSelfCalGain() self.adc_offset_calibration = self.board.MAX11210_getSelfCalOffset() self.board.MAX11210_setSysOffsetCal(0x00) self.board.MAX11210_send_command(0b10001000) reading_i = [] for i in range(0,20): self.board.MAX11210_setRate(120) reading_i.append(self.board.MAX11210_getData()) reading_avg = (sum(reading_i)* 1.0) / (len(reading_i) * 1.0) self.off_cal_val = int(reading_avg) - 1677 else: if not self.adc_gain_calibration: logger.warning('Missing ADC Calibration Values!' 'Please perform a Background measurement') else: logger.warning('Open PMT shutter and close box lid') self.board.MAX11210_setSelfCalGain(self.adc_gain_calibration) self.board.MAX11210_setSelfCalOffset(self.adc_offset_calibration) '''if (self.board.config.pmt_sys_offset_cal != 0): self.board.MAX11210_setSysOffsetCal(self.board.config.pmt_sys_offset_cal) else: self.board.MAX11210_setSysOffsetCal(self.off_cal_val) self.board.MAX11210_setSysGainCal(self.board.config.pmt_sys_gain_cal) self.board.MAX11210_send_command(0b10001000)''' adc_calibration = self.board.get_adc_calibration().to_dict() logger.info('ADC calibration:\n%s' % adc_calibration) step_log['ADC calibration'] = adc_calibration # Launch PMT measure dialog. delta_t = dt.timedelta(seconds=1) # Set sampling reset_board_state adc_rate = self.board.config.pmt_sampling_rate # Construct a function compatible with `measure_dialog` to # read from MAX11210 ADC. data_func = (mrbox.ui.gtk.measure_dialog .adc_data_func_factory(proxy=self.board, delta_t=delta_t, adc_rate=adc_rate)) # Use constructed function to launch measurement dialog for # the duration specified by the step options. duration_s = (step_options.get('Measurement_duration_(s)') + 1) use_si_prefixes = app_values.get('Use PMT y-axis SI ' 'prefixes') data = (mrbox.ui.gtk.measure_dialog .measure_dialog(data_func, duration_s=duration_s, auto_start=True, auto_close=False, si_units=use_si_prefixes)) if data is not None: # Append measured data as JSON line to [new-line # delimited JSON][1] file for step. # # Each line of results can be loaded using # `pandas.read_json(..., orient='split')`. # # [1]: http://ndjson.org/ filename = ph.path('PMT_readings-step%04d.ndjson' % app.protocol.current_step_number) log_dir = app.experiment_log.get_log_path() log_dir.makedirs_p() data.name = filename.namebase if step_label: # Set name of data series based on step label. data.name = step_label with log_dir.joinpath(filename).open('a') as output: # Write JSON data with `split` orientation, which # preserves the name of the Pandas series. data.to_json(output, orient='split') output.write('\n') step_log['data'] = data.to_dict() self.update_excel_results() logger.warning('Close PMT Shutter') except Exception: logger.error('[%s] Error applying step options.', __name__, exc_info=True) finally: app.experiment_log.add_data(step_log, self.name) elif not self._user_warned: logger.warning('[%s] Cannot apply board settings since board is ' 'not connected.', __name__, exc_info=True) # Do not warn user again until after the next connection attempt. self._user_warned = True def update_excel_results(self, launch=False): ''' Update output Excel results file. .. versionadded:: 0.19 Parameters ---------- launch : bool, optional If ``True``, launch Excel spreadsheet after writing. ''' app = get_app() log_dir = app.experiment_log.get_log_path() # Update Excel file with latest PMT results. output_path = log_dir.joinpath('PMT_readings.xlsx') data_files = list(log_dir.files('PMT_readings-*.ndjson')) if not data_files: logger.debug('No PMT readings files found.') return logger.info(TEMPLATE_PATH) def _threadsafe_write_results(): logger.info(launch) while True: try: _write_results(TEMPLATE_PATH, output_path, data_files) if launch: try: output_path.launch() except Exception: pass break except IOError as e: logger.info("I/O error({0}): {1}".format(e.errno, e.strerror)) response = yesno('Error writing PMT summary to Excel ' 'spreadsheet output path: `%s`.\n\nTry ' 'again?' %output_path) if response == gtk.RESPONSE_NO: break # Schedule writing of results to occur in main GTK # thread in case confirmation dialog needs to be # displayed. gobject.idle_add(_threadsafe_write_results) def open_board_connection(self): ''' Establish serial connection to MR-Box peripheral board. ''' # Try to connect to peripheral board through serial connection. # XXX Try to connect multiple times. # See [issue 1][1] on the [MR-Box peripheral board firmware # project][2]. # # [1]: https://github.com/wheeler-microfluidics/mr-box-peripheral-board.py/issues/1 # [2]: https://github.com/wheeler-microfluidics/mr-box-peripheral-board.py retry_count = 2 for i in xrange(retry_count): try: self.board.close() self.board = None except Exception: pass try: self.board = mrbox.SerialProxy() host_software_version = utility.Version.fromstring( str(self.board.host_software_version)) remote_software_version = utility.Version.fromstring( str(self.board.remote_software_version)) # Offer to reflash the firmware if the major and minor versions # are not not identical. If micro versions are different, # the firmware is assumed to be compatible. See [1] # # [1]: https://github.com/wheeler-microfluidics/base-node-rpc/ # issues/8 if any([host_software_version.major != remote_software_version.major, host_software_version.minor != remote_software_version.minor]): response = yesno("The MR-box peripheral board 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() # Serial connection to peripheral **successfully established**. logger.info('Serial connection to peripheral board ' '**successfully established**') logger.info('Peripheral board properties:\n%s', self.board.properties) logger.info('Reset board state to defaults.') break except (serial.SerialException, IOError): time.sleep(1) else: # Serial connection to peripheral **could not be established**. logger.warning('Serial connection to peripheral board could not ' 'be established.') def on_edit_configuration(self, widget=None, data=None): ''' Display a dialog to manually edit the configuration settings. ''' config = self.board.config form = dict_to_form(config) dialog = FormViewDialog(form, 'Edit configuration settings') valid, response = dialog.run() if valid: self.board.update_config(**response) def on_flash_firmware(self, widget=None, data=None): app = get_app() try: self.board.flash_firmware() app.main_window_controller.info("Firmware updated successfully.", "Firmware update") except Exception, why: logger.error("Problem flashing firmware. ""%s" % why)
#! -*- coding: utf-8 -*- from flatland import Array, Boolean, Integer from flatland.out import generic from flatland.out.generic import Context Unspecified = object() Unique = object() schema = Integer.named(u'number') boolean_schema = Boolean.named(u'bool') partial_anon_schema = Array.named(u'array').of(Integer) full_anon_schema = Array.of(Integer) def assert_bound_transform(fn, tagname, given, expected, **kw): return assert_transform(fn, tagname, given, expected, **kw) def assert_unbound_transform(fn, tagname, given, expected, **kw): kw['bind'] = None return assert_transform(fn, tagname, given, expected, **kw) def assert_transform(fn, tagname, given, expected, context=Unspecified, bind=Unspecified, contents=Unspecified, expected_contents=Unspecified): if context is Unspecified: context = Context() if bind is Unspecified:
def run(self, forms, initial_values=None): # Empty plugin form vbox # Get list of app option forms self.forms = forms self.form_views = {} self.clear_form() app = get_app() core_plugins_count = 0 for name, form in self.forms.iteritems(): # For each form, generate a pygtkhelpers formview and append the view # onto the end of the plugin vbox if len(form.field_schema) == 0: continue # Only include fields that do not have show_in_gui set to False in # 'properties' dictionary schema_entries = [f for f in form.field_schema\ if f.properties.get('show_in_gui', True)] gui_form = Form.of(*[Boolean.named(s.name).using(default=True, optional=True) for s in schema_entries]) FormView.schema_type = gui_form if not schema_entries: continue self.form_views[name] = FormView() if name in app.core_plugins: self.core_plugins_vbox.pack_start(self.form_views[name].widget) core_plugins_count += 1 else: expander = gtk.Expander() expander.set_label(name) expander.set_expanded(True) expander.add(self.form_views[name].widget) self.plugin_form_vbox.pack_start(expander) if core_plugins_count == 0: self.frame_core_plugins.hide() self.plugin_form_vbox.remove(self.frame_core_plugins) else: if not self.frame_core_plugins in self.plugin_form_vbox.children(): self.plugin_form_vbox.pack_start(self.frame_core_plugins) self.frame_core_plugins.show() if not initial_values: initial_values = {} for form_name, form in self.forms.iteritems(): if not form.field_schema: continue form_view = self.form_views[form_name] values = initial_values.get(form_name, {}) for name, field in form_view.form.fields.items(): if name in values or not initial_values: value = True else: value = False logger.debug('set %s to %s' % (name, value)) proxy = proxy_for(getattr(form_view, name)) proxy.set_widget_value(value) field.label_widget.set_text( re.sub(r'_', ' ', name).title()) self.dialog.show_all() response = self.dialog.run() if response == gtk.RESPONSE_OK: self.apply() elif response == gtk.RESPONSE_CANCEL: pass self.dialog.hide() return response
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()
def on_edit_calibration(self, widget=None, data=None): if not self.control_board.connected(): logging.error("A control board must be connected in order to " "edit calibration settings.") return hardware_version = utility.Version.fromstring( self.control_board.hardware_version()) schema_entries = [] settings = {} settings['amplifier_gain'] = self.control_board.amplifier_gain() schema_entries.append( Float.named('amplifier_gain').using( default=settings['amplifier_gain'], optional=True, validators=[ ValueAtLeast(minimum=0.01), ]), ) settings['auto_adjust_amplifier_gain'] = self.control_board \ .auto_adjust_amplifier_gain() schema_entries.append( Boolean.named('auto_adjust_amplifier_gain').using( default=settings['auto_adjust_amplifier_gain'], optional=True), ) settings['voltage_tolerance'] = \ self.control_board.voltage_tolerance() schema_entries.append( Float.named('voltage_tolerance').using( default=settings['voltage_tolerance'], optional=True, validators=[ ValueAtLeast(minimum=0), ]), ) if hardware_version.major == 1: settings['WAVEOUT_GAIN_1'] = self.control_board \ .eeprom_read(self.control_board.EEPROM_WAVEOUT_GAIN_1_ADDRESS) schema_entries.append( Integer.named('WAVEOUT_GAIN_1').using( default=settings['WAVEOUT_GAIN_1'], optional=True, validators=[ ValueAtLeast(minimum=0), ValueAtMost(maximum=255), ]), ) settings['VGND'] = self.control_board \ .eeprom_read(self.control_board.EEPROM_VGND_ADDRESS) schema_entries.append( Integer.named('VGND').using(default=settings['VGND'], optional=True, validators=[ ValueAtLeast(minimum=0), ValueAtMost(maximum=255), ]), ) else: settings['SWITCHING_BOARD_I2C_ADDRESS'] = self.control_board \ .eeprom_read(self.control_board.EEPROM_SWITCHING_BOARD_I2C_ADDRESS) schema_entries.append( Integer.named('SWITCHING_BOARD_I2C_ADDRESS').using( default=settings['SWITCHING_BOARD_I2C_ADDRESS'], optional=True, validators=[ ValueAtLeast(minimum=0), ValueAtMost(maximum=255), ]), ) settings['SIGNAL_GENERATOR_BOARD_I2C_ADDRESS'] = self.control_board \ .eeprom_read(self.control_board.EEPROM_SIGNAL_GENERATOR_BOARD_I2C_ADDRESS) schema_entries.append( Integer.named('SIGNAL_GENERATOR_BOARD_I2C_ADDRESS').using( default=settings['SIGNAL_GENERATOR_BOARD_I2C_ADDRESS'], optional=True, validators=[ ValueAtLeast(minimum=0), ValueAtMost(maximum=255), ]), ) for i in range(len(self.control_board.calibration.R_hv)): settings['R_hv_%d' % i] = self.control_board.calibration.R_hv[i] schema_entries.append( Float.named('R_hv_%d' % i).using(default=settings['R_hv_%d' % i], optional=True, validators=[ ValueAtLeast(minimum=0), ])) settings['C_hv_%d' % i] =\ self.control_board.calibration.C_hv[i]*1e12 schema_entries.append( Float.named('C_hv_%d' % i).using(default=settings['C_hv_%d' % i], optional=True, validators=[ ValueAtLeast(minimum=0), ])) for i in range(len(self.control_board.calibration.R_fb)): settings['R_fb_%d' % i] = self.control_board.calibration.R_fb[i] schema_entries.append( Float.named('R_fb_%d' % i).using(default=settings['R_fb_%d' % i], optional=True, validators=[ ValueAtLeast(minimum=0), ])) settings['C_fb_%d' % i] = \ self.control_board.calibration.C_fb[i]*1e12 schema_entries.append( Float.named('C_fb_%d' % i).using(default=settings['C_fb_%d' % i], optional=True, validators=[ ValueAtLeast(minimum=0), ])) form = Form.of(*schema_entries) dialog = FormViewDialog('Edit calibration settings') valid, response = dialog.run(form) if valid: for k, v in response.items(): if settings[k] != v: m = re.match('(R|C)_(hv|fb)_(\d)', k) if k == 'amplifier_gain': self.control_board.set_amplifier_gain(v) elif k == 'auto_adjust_amplifier_gain': self.control_board.set_auto_adjust_amplifier_gain(v) elif k == 'WAVEOUT_GAIN_1': self.control_board.eeprom_write( self.control_board.EEPROM_WAVEOUT_GAIN_1_ADDRESS, v) elif k == 'VGND': self.control_board.eeprom_write( self.control_board.EEPROM_VGND_ADDRESS, v) elif k == 'SWITCHING_BOARD_I2C_ADDRESS': self.control_board.eeprom_write( self.control_board. EEPROM_SWITCHING_BOARD_I2C_ADDRESS, v) elif k == 'SIGNAL_GENERATOR_BOARD_I2C_ADDRESS': self.control_board.eeprom_write( self.control_board. EEPROM_SIGNAL_GENERATOR_BOARD_I2C_ADDRESS, v) elif k == 'voltage_tolerance': self.control_board.set_voltage_tolerance(v) elif m: series_resistor = int(m.group(3)) if m.group(2) == 'hv': channel = 0 else: channel = 1 self.control_board.set_series_resistor_index( channel, series_resistor) if m.group(1) == 'R': self.control_board.set_series_resistance( channel, v) else: if v is None: v = 0 self.control_board.set_series_capacitance( channel, v / 1e12) # reconnect to update settings self.connect() if get_app().protocol: self.on_step_run()
class DmfDeviceUiPlugin(AppDataController, StepOptionsController, Plugin, pmh.BaseMqttReactor): """ 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 AppFields = Form.of( String.named('video_config').using(default='', optional=True, properties={'show_in_gui': False}), String.named('surface_alphas').using(default='', optional=True, properties={'show_in_gui': False}), String.named('canvas_corners').using(default='', optional=True, properties={'show_in_gui': False}), String.named('frame_corners').using(default='', optional=True, properties={'show_in_gui': False}), 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})) StepFields = Form.of( Boolean.named('video_enabled').using(default=True, optional=True, properties={'title': 'Video'})) def __init__(self): self.name = self.plugin_name self.gui_process = None self.gui_heartbeat_id = None self._gui_enabled = False self.alive_timestamp = None self.should_terminate = False pmh.BaseMqttReactor.__init__(self) self.start() def reset_gui(self): py_exe = sys.executable # Set allocation based on saved app values (i.e., remember window size # and position from last run). app_values = self.get_app_values() allocation_args = ['-a', json.dumps(app_values)] app = get_app() if app.config.data.get('advanced_ui', False): debug_args = ['-d'] else: debug_args = [] self.gui_process = Popen( [py_exe, '-m', 'dmf_device_ui.bin.device_view', '-n', self.name] + allocation_args + debug_args + ['fixed', get_hub_uri()], creationflags=CREATE_NEW_PROCESS_GROUP) self.gui_process.daemon = False self._gui_enabled = True def keep_alive(): if not self._gui_enabled: self.alive_timestamp = None return False elif self.gui_process.poll() == 0: # GUI process has exited. Restart. self.cleanup() self.reset_gui() return False else: self.alive_timestamp = datetime.now() # Keep checking. return True # Go back to Undo 613 for working corners self.step_video_settings = None # Get current video settings from UI. app_values = self.get_app_values() # Convert JSON settings to 0MQ plugin API Python types. ui_settings = self.json_settings_as_python(app_values) self.set_ui_settings(ui_settings, default_corners=True) self.gui_heartbeat_id = gobject.timeout_add(1000, keep_alive) def cleanup(self): if self.gui_heartbeat_id is not None: gobject.source_remove(self.gui_heartbeat_id) self.alive_timestamp = None def get_schedule_requests(self, function_name): """ Returns a list of scheduling requests (i.e., ScheduleRequest instances) for the function specified by function_name. """ if function_name == 'on_plugin_enable': return [ScheduleRequest('droplet_planning_plugin', self.name)] elif function_name == 'on_dmf_device_swapped': # XXX Schedule `on_app_exit` handling before `device_info_plugin`, # since `hub_execute` uses the `device_info_plugin` service to # submit commands to through the 0MQ plugin hub. return [ScheduleRequest('microdrop.device_info_plugin', self.name)] elif function_name == 'on_app_exit': # XXX Schedule `on_app_exit` handling before `device_info_plugin`, # since `hub_execute` uses the `device_info_plugin` service to # submit commands to through the 0MQ plugin hub. return [ScheduleRequest(self.name, 'microdrop.device_info_plugin')] return [] def on_app_exit(self): self.should_terminate = True self.mqtt_client.publish( 'microdrop/dmf-device-ui-plugin/get-video-settings', json.dumps(None)) def json_settings_as_python(self, json_settings): ''' Convert DMF device UI plugin settings from json format to Python types. Python types are expected by DMF device UI plugin 0MQ command API. Args ---- json_settings (dict) : DMF device UI plugin settings in JSON-compatible format (i.e., only basic Python data types). Returns ------- (dict) : DMF device UI plugin settings in Python types expected by DMF device UI plugin 0MQ commands. ''' py_settings = {} corners = dict([(k, json_settings.get(k)) for k in ('canvas_corners', 'frame_corners')]) if all(corners.values()): # Convert CSV corners lists for canvas and frame to # `pandas.DataFrame` instances for k, v in corners.iteritems(): # Prepend `'df_'` to key to indicate the type as a data frame. py_settings['df_' + k] = pd.read_csv(io.BytesIO(bytes(v)), index_col=0) for k in ('video_config', 'surface_alphas'): if k in json_settings: if not json_settings[k]: py_settings[k] = pd.Series(None) else: py_settings[k] = pd.Series(json.loads(json_settings[k])) return py_settings def save_ui_settings(self, video_settings): ''' Save specified DMF device UI 0MQ plugin settings to persistent Microdrop configuration (i.e., settings to be applied when Microdrop is launched). Args ---- video_settings (dict) : DMF device UI plugin settings in JSON-compatible format returned by `get_ui_json_settings` method (i.e., only basic Python data types). ''' app_values = self.get_app_values() # Select subset of app values that are present in `video_settings`. app_video_values = dict([(k, v) for k, v in app_values.iteritems() if k in video_settings.keys()]) # If the specified video settings differ from app values, update # app values. if app_video_values != video_settings: app_values.update(video_settings) self.set_app_values(app_values) def set_ui_settings(self, ui_settings, default_corners=False): ''' Set DMF device UI settings from settings dictionary. Args ---- ui_settings (dict) : DMF device UI plugin settings in format returned by `json_settings_as_python` method. ''' if 'video_config' in ui_settings: msg = {} msg['video_config'] = ui_settings['video_config'].to_json() self.mqtt_client.publish( 'microdrop/dmf-device-ui-plugin/set-video-config', payload=json.dumps(msg), retain=True) if 'surface_alphas' in ui_settings: # TODO: Make Clear retained messages after exit msg = {} msg['surface_alphas'] = ui_settings['surface_alphas'].to_json() self.mqtt_client.publish( 'microdrop/dmf-device-ui-plugin/set-surface-alphas', payload=json.dumps(msg), retain=True) if all((k in ui_settings) for k in ('df_canvas_corners', 'df_frame_corners')): # TODO: Test With Camera msg = {} msg['df_canvas_corners'] = ui_settings[ 'df_canvas_corners'].to_json() msg['df_frame_corners'] = ui_settings['df_frame_corners'].to_json() if default_corners: self.mqtt_client.publish( 'microdrop/dmf-device-ui-plugin/' 'set-default-corners', payload=json.dumps(msg), retain=True) else: self.mqtt_client.publish( 'microdrop/dmf-device-ui-plugin/' 'set-corners', payload=json.dumps(msg), retain=True) # ######################################################################### # # Plugin signal handlers def on_connect(self, client, userdata, flags, rc): self.mqtt_client.subscribe( 'microdrop/dmf-device-ui/get-video-settings') self.mqtt_client.subscribe('microdrop/dmf-device-ui/update-protocol') def on_message(self, client, userdata, msg): if msg.topic == 'microdrop/dmf-device-ui/get-video-settings': self.video_settings = json.loads(msg.payload) self.save_ui_settings(self.video_settings) if self.should_terminate: self.mqtt_client.publish( 'microdrop/dmf-device-ui-plugin/terminate') if msg.topic == 'microdrop/dmf-device-ui/update-protocol': self.update_protocol(json.loads(msg.payload)) def on_plugin_disable(self): self._gui_enabled = False self.cleanup() def on_plugin_enable(self): super(DmfDeviceUiPlugin, self).on_plugin_enable() self.reset_gui() form = flatlandToDict(self.StepFields) self.mqtt_client.publish('microdrop/dmf-device-ui-plugin/schema', json.dumps(form), retain=True) defaults = {} for k, v in form.iteritems(): defaults[k] = v['default'] # defaults = map(lambda (k,v): {k: v['default']}, form.iteritems()) self.mqtt_client.publish('microdrop/dmf-device-ui-plugin/step-options', json.dumps([defaults], cls=PandasJsonEncoder), retain=True) def on_step_removed(self, step_number, step): self.update_steps() def on_step_options_changed(self, plugin, step_number): self.update_steps() def on_step_run(self): ''' Handler called whenever a step is executed. Plugins that handle this signal must emit the on_step_complete signal once they have completed the step. The protocol controller will wait until all plugins have completed the current step before proceeding. ''' app = get_app() # TODO: Migrate video commands to mqtt!! # if (app.realtime_mode or app.running) and self.gui_process is not None: # step_options = self.get_step_options() # if not step_options['video_enabled']: # hub_execute(self.name, 'disable_video', # wait_func=lambda *args: refresh_gui(), timeout_s=5, # silent=True) # else: # hub_execute(self.name, 'enable_video', # wait_func=lambda *args: refresh_gui(), timeout_s=5, # silent=True) emit_signal('on_step_complete', [self.name, None]) def update_steps(self): app = get_app() num_steps = len(app.protocol.steps) protocol = [] for i in range(num_steps): protocol.append(self.get_step_options(i)) self.mqtt_client.publish('microdrop/dmf-device-ui-plugin/step-options', json.dumps(protocol, cls=PandasJsonEncoder), retain=True) def update_protocol(self, protocol): app = get_app() for i, s in enumerate(protocol): step = app.protocol.steps[i] prevData = step.get_data(self.plugin_name) values = {} for k, v in prevData.iteritems(): if k in s: values[k] = s[k] step.set_data(self.plugin_name, values) emit_signal('on_step_options_changed', [self.plugin_name, i], interface=IPlugin)
""" from flatland import Boolean, Form, String, Integer, Float def is_float(v): try: return (float(str(v)), True)[1] except (ValueError, TypeError), e: return False def is_int(v): try: return (int(str(v)), True)[1] except (ValueError, TypeError), e: return False def is_bool(v): return v in (True, False) schema_entries = [] for k, v in dict.iteritems(): if is_int(v): schema_entries.append(Integer.named(k).using(default=v, optional=True)) elif is_float(v): schema_entries.append(Float.named(k).using(default=v, optional=True)) elif is_bool(v): schema_entries.append(Boolean.named(k).using(default=v, optional=True)) elif type(v) == str: schema_entries.append(String.named(k).using(default=v, optional=True)) return Form.of(*schema_entries)
def run(self, forms, initial_values=None): # Empty plugin form vbox # Get list of app option forms self.forms = forms self.form_views = {} self.clear_form() app = get_app() core_plugins_count = 0 for name, form in self.forms.iteritems(): # For each form, generate a pygtkhelpers formview and append the view # onto the end of the plugin vbox if len(form.field_schema) == 0: continue # Only include fields that do not have show_in_gui set to False in # 'properties' dictionary schema_entries = [f for f in form.field_schema\ if f.properties.get('show_in_gui', True)] gui_form = Form.of(*[ Boolean.named(s.name).using(default=True, optional=True) for s in schema_entries ]) FormView.schema_type = gui_form if not schema_entries: continue self.form_views[name] = FormView() if name in app.core_plugins: self.core_plugins_vbox.pack_start(self.form_views[name].widget) core_plugins_count += 1 else: expander = gtk.Expander() expander.set_label(name) expander.set_expanded(True) expander.add(self.form_views[name].widget) self.plugin_form_vbox.pack_start(expander) if core_plugins_count == 0: self.frame_core_plugins.hide() self.plugin_form_vbox.remove(self.frame_core_plugins) else: if not self.frame_core_plugins in self.plugin_form_vbox.children(): self.plugin_form_vbox.pack_start(self.frame_core_plugins) self.frame_core_plugins.show() if not initial_values: initial_values = {} for form_name, form in self.forms.iteritems(): if not form.field_schema: continue form_view = self.form_views[form_name] values = initial_values.get(form_name, {}) for name, field in form_view.form.fields.items(): if name in values or not initial_values: value = True else: value = False logger.debug('set %s to %s' % (name, value)) proxy = proxy_for(getattr(form_view, name)) proxy.set_widget_value(value) field.label_widget.set_text(re.sub(r'_', ' ', name).title()) self.dialog.show_all() response = self.dialog.run() if response == gtk.RESPONSE_OK: self.apply() elif response == gtk.RESPONSE_CANCEL: pass self.dialog.hide() return response
class ZeroMQServicePlugin(Plugin, AppDataController, StepOptionsController): """ This class is automatically registered with the PluginManager. """ implements(IPlugin) version = get_plugin_info(path(__file__).parent.parent).version plugins_name = get_plugin_info(path(__file__).parent.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 ''' AppFields = Form.of( String.named('service_address').using(default='', optional=True), ) ''' 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('service_enabled').using(default=False, optional=True), Float.named('timeout_sec').using(default=5., optional=True), ) def __init__(self): self.name = self.plugins_name self.context = zmq.Context.instance() self.socks = OrderedDict() self.timeout_id = None self._start_time = 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.context = zmq.Context() self.reset_socks() if get_app().protocol: pgc = get_service_instance(ProtocolGridController, env='microdrop') pgc.update_grid() def close_socks(self): # Close any currently open sockets. for name, sock in self.socks.iteritems(): sock.close() self.socks = OrderedDict() def reset_socks(self): self.close_socks() app_values = self.get_app_values() if self.timeout_id is not None: gtk.timeout_remove(self.timeout_id) self.timeout_id = None if app_values['service_address']: # Service address is available self.socks['req'] = zmq.Socket(self.context, zmq.REQ) self.socks['req'].connect(app_values['service_address']) def on_app_options_changed(self, plugin_name): if plugin_name == self.name: self.reset_socks() def on_plugin_disable(self): self.close_socks() if get_app().protocol: pgc = get_service_instance(ProtocolGridController, env='microdrop') pgc.update_grid() def _on_check_service_response(self, timeout_sec): if not self.socks['req'].poll(timeout=11): # No response is ready yet. if timeout_sec < (datetime.now() - self._start_time).total_seconds(): # Timed out waiting for response. self.reset_socks() self.step_complete(return_value='Fail') self.timeout_id = None return False return True else: # Response is ready. response = self.socks['req'].recv() logger.info('[ZeroMQServicePlugin] Service response: %s', response) if response == 'completed': logger.info('[ZeroMQServicePlugin] Service completed task ' 'successfully.') self.step_complete() else: logger.error('[ZeroMQServicePlugin] Unexpected response: %s' % response) self.step_complete(return_value='Fail') self.timeout_id = None return False def step_complete(self, return_value=None): app = get_app() if app.running or app.realtime_mode: emit_signal('on_step_complete', [self.name, return_value]) def on_step_run(self): options = self.get_step_options() self.reset_socks() if options['service_enabled'] and self.socks['req'] is None: # Service is supposed to be called for this step, but the socket is # not ready. self.step_complete(return_value='Fail') elif options['service_enabled'] and self.socks['req'] is not None: logger.info('[ZeroMQServicePlugin] Send signal to service to ' 'start.') # Request start of service. self.socks['req'].send('start') if not self.socks['req'].poll(timeout=4000): self.reset_socks() logger.error('[ZeroMQServicePlugin] Timed-out waiting for ' 'a response.') else: # Response is ready. response = self.socks['req'].recv() if response == 'started': logger.info('[ZeroMQServicePlugin] Service started ' 'successfully.') self.socks['req'].send('notify_completion') self._start_time = datetime.now() self.timeout_id = gtk.timeout_add( 100, self._on_check_service_response, options['timeout_sec']) else: self.step_complete() def enable_service(self): pass
class DmfDeviceUiPlugin(AppDataController, StepOptionsController, Plugin): """ This class is automatically registered with the PluginManager. .. versionchanged:: 2.10 Set default window size and position according to **screen size** *and* **window titlebar size**. Also, force default window size if ``MICRODROP_FIRST_RUN`` environment variable is set to non-empty value. """ implements(IPlugin) version = get_plugin_info(path(__file__).parent).version plugin_name = get_plugin_info(path(__file__).parent).plugin_name AppFields = Form.of( String.named('video_config').using(default='', optional=True, properties={'show_in_gui': False}), String.named('surface_alphas').using(default='', optional=True, properties={'show_in_gui': False}), String.named('canvas_corners').using(default='', optional=True, properties={'show_in_gui': False}), String.named('frame_corners').using(default='', optional=True, properties={'show_in_gui': False}), Integer.named('x').using(default=.5 * SCREEN_WIDTH, optional=True, properties={'show_in_gui': False}), Integer.named('y').using(default=SCREEN_TOP, optional=True, properties={'show_in_gui': False}), Integer.named('width').using(default=.5 * SCREEN_WIDTH, optional=True, properties={'show_in_gui': False}), Integer.named('height').using(default=SCREEN_HEIGHT - 1.5 * TITLEBAR_HEIGHT, optional=True, properties={'show_in_gui': False})) StepFields = Form.of( Boolean.named('video_enabled').using(default=True, optional=True, properties={'title': 'Video'})) def __init__(self): self.name = self.plugin_name self.gui_process = None self.gui_heartbeat_id = None self._gui_enabled = False self.alive_timestamp = None def reset_gui(self): ''' .. versionchanged:: 2.2.2 Use :func:`pygtkhelpers.gthreads.gtk_threadsafe` decorator around function to wait for GUI process, rather than using :func:`gobject.idle_add`, to make intention clear. .. versionchanged:: 2.9 Refresh list of registered commands once device UI process has started. The list of registered commands is used to dynamically generate items in the device UI context menu. ''' py_exe = sys.executable # Set allocation based on saved app values (i.e., remember window size # and position from last run). app_values = self.get_app_values() if os.environ.get('MICRODROP_FIRST_RUN'): # Use default options for window allocation. default_app_values = self.get_default_app_options() for k in ('x', 'y', 'width', 'height'): app_values[k] = default_app_values[k] allocation_args = ['-a', json.dumps(app_values)] app = get_app() if app.config.data.get('advanced_ui', False): debug_args = ['-d'] else: debug_args = [] self.gui_process = Popen( [py_exe, '-m', 'dmf_device_ui.bin.device_view', '-n', self.name] + allocation_args + debug_args + ['fixed', get_hub_uri()], creationflags=CREATE_NEW_PROCESS_GROUP) self._gui_enabled = True def keep_alive(): if not self._gui_enabled: self.alive_timestamp = None return False elif self.gui_process.poll() == 0: # GUI process has exited. Restart. self.cleanup() self.reset_gui() return False else: self.alive_timestamp = datetime.now() # Keep checking. return True self.step_video_settings = None @gtk_threadsafe def _wait_for_gui(): self.wait_for_gui_process() # Get current video settings from UI. app_values = self.get_app_values() # Convert JSON settings to 0MQ plugin API Python types. ui_settings = self.json_settings_as_python(app_values) self.set_ui_settings(ui_settings, default_corners=True) self.gui_heartbeat_id = gobject.timeout_add(1000, keep_alive) # Refresh list of electrode and route commands. hub_execute('microdrop.command_plugin', 'get_commands') # Call as thread-safe function, since function uses GTK. _wait_for_gui() def cleanup(self): ''' .. versionchanged:: 2.2.2 Catch any exception encountered during GUI process termination. .. versionchanged:: 2.3.1 Use :func:`kill_process_tree` to terminate DMF device UI process. This ensures any child processes of the UI process (e.g., video input process) are also killed. See also: https://stackoverflow.com/a/44648162/345236 .. versionchanged:: 2.7 Only try to terminate the GUI process if it is still running. ''' logger.info('Stop DMF device UI keep-alive timer') if self.gui_heartbeat_id is not None: # Stop keep-alive polling of device UI process. gobject.source_remove(self.gui_heartbeat_id) if self.gui_process is not None and self.gui_process.poll() is None: logger.info('Terminate DMF device UI process') try: kill_process_tree(self.gui_process.pid) logger.info('Close DMF device UI process `%s`', self.gui_process.pid) except Exception: logger.info( 'Unexpected error closing DMF device UI process ' '`%s`', self.gui_process.pid, exc_info=True) else: logger.info('No active DMF device UI process') self.alive_timestamp = None def wait_for_gui_process(self, retry_count=20, retry_duration_s=1): ''' .. versionchanged:: 2.7.2 Do not execute `refresh_gui()` while waiting for response from `hub_execute()`. ''' start = datetime.now() for i in xrange(retry_count): try: hub_execute(self.name, 'ping', timeout_s=5, silent=True) except Exception: logger.debug('[wait_for_gui_process] failed (%d of %d)', i + 1, retry_count, exc_info=True) else: logger.info('[wait_for_gui_process] success (%d of %d)', i + 1, retry_count) self.alive_timestamp = datetime.now() return for j in xrange(10): time.sleep(retry_duration_s / 10.) refresh_gui() raise IOError('Timed out after %ss waiting for GUI process to connect ' 'to hub.' % si_format( (datetime.now() - start).total_seconds())) def get_schedule_requests(self, function_name): """ Returns a list of scheduling requests (i.e., ScheduleRequest instances) for the function specified by function_name. .. versionchanged:: 2.3.3 Do not submit ``on_app_exit`` schedule request. This is no longer necessary since ``hub_execute`` listening socket is no longer closed by ``microdrop.device_info_plugin`` during ``on_app_exit`` callback. .. versionadded:: 2.9 Enable _after_ command plugin and zmq hub plugin. """ if function_name == 'on_plugin_enable': return [ ScheduleRequest(p, self.name) for p in ('microdrop.zmq_hub_plugin', 'microdrop.command_plugin', 'droplet_planning_plugin') ] return [] def on_app_exit(self): logger.info('Get current video settings from DMF device UI plugin.') json_settings = self.get_ui_json_settings() self.save_ui_settings(json_settings) self._gui_enabled = False self.cleanup() # ######################################################################### # # DMF device UI 0MQ plugin settings def get_ui_json_settings(self): ''' Get current video settings from DMF device UI plugin. Returns ------- (dict) : DMF device UI plugin settings in JSON-compatible format (i.e., only basic Python data types). .. versionchanged:: 2.7.2 Do not execute `refresh_gui()` while waiting for response from `hub_execute()`. ''' video_settings = {} # Try to request video configuration. try: video_config = hub_execute(self.name, 'get_video_config', timeout_s=2) except IOError: logger.warning('Timed out waiting for device window size and ' 'position request.') else: if video_config is not None: video_settings['video_config'] = video_config.to_json() else: video_settings['video_config'] = '' # Try to request allocation to save in app options. try: data = hub_execute(self.name, 'get_corners', timeout_s=2) except IOError: logger.warning('Timed out waiting for device window size and ' 'position request.') else: if data: # Get window allocation settings (i.e., width, height, x, y). # Replace `df_..._corners` with CSV string named `..._corners` # (no `df_` prefix). for k in ('df_canvas_corners', 'df_frame_corners'): if k in data: data['allocation'][k[3:]] = data.pop(k).to_csv() video_settings.update(data['allocation']) # Try to request surface alphas. try: surface_alphas = hub_execute(self.name, 'get_surface_alphas', timeout_s=2) except IOError: logger.warning('Timed out waiting for surface alphas.') else: if surface_alphas is not None: video_settings['surface_alphas'] = surface_alphas.to_json() else: video_settings['surface_alphas'] = '' return video_settings def get_ui_settings(self): ''' Get current video settings from DMF device UI plugin. Returns ------- (dict) : DMF device UI plugin settings in Python types expected by DMF device UI plugin 0MQ commands. ''' json_settings = self.get_ui_json_settings() return self.json_settings_as_python(json_settings) def json_settings_as_python(self, json_settings): ''' Convert DMF device UI plugin settings from json format to Python types. Python types are expected by DMF device UI plugin 0MQ command API. Args ---- json_settings (dict) : DMF device UI plugin settings in JSON-compatible format (i.e., only basic Python data types). Returns ------- (dict) : DMF device UI plugin settings in Python types expected by DMF device UI plugin 0MQ commands. ''' py_settings = {} corners = dict([(k, json_settings.get(k)) for k in ('canvas_corners', 'frame_corners')]) if all(corners.values()): # Convert CSV corners lists for canvas and frame to # `pandas.DataFrame` instances for k, v in corners.iteritems(): # Prepend `'df_'` to key to indicate the type as a data frame. py_settings['df_' + k] = pd.read_csv(io.BytesIO(bytes(v)), index_col=0) for k in ('video_config', 'surface_alphas'): if k in json_settings: if not json_settings[k]: py_settings[k] = pd.Series(None) else: py_settings[k] = pd.Series(json.loads(json_settings[k])) return py_settings def save_ui_settings(self, video_settings): ''' Save specified DMF device UI 0MQ plugin settings to persistent Microdrop configuration (i.e., settings to be applied when Microdrop is launched). Args ---- video_settings (dict) : DMF device UI plugin settings in JSON-compatible format returned by `get_ui_json_settings` method (i.e., only basic Python data types). ''' app_values = self.get_app_values() # Select subset of app values that are present in `video_settings`. app_video_values = dict([(k, v) for k, v in app_values.iteritems() if k in video_settings.keys()]) # If the specified video settings differ from app values, update # app values. if app_video_values != video_settings: app_values.update(video_settings) self.set_app_values(app_values) def set_ui_settings(self, ui_settings, default_corners=False): ''' Set DMF device UI settings from settings dictionary. Args ---- ui_settings (dict) : DMF device UI plugin settings in format returned by `json_settings_as_python` method. .. versionchanged:: 2.7.2 Do not execute `refresh_gui()` while waiting for response from `hub_execute()`. ''' if self.alive_timestamp is None or self.gui_process is None: # Repeat until GUI process has started. raise IOError('GUI process not ready.') if 'video_config' in ui_settings: hub_execute(self.name, 'set_video_config', video_config=ui_settings['video_config'], timeout_s=5) if 'surface_alphas' in ui_settings: hub_execute(self.name, 'set_surface_alphas', surface_alphas=ui_settings['surface_alphas'], timeout_s=5) if all((k in ui_settings) for k in ('df_canvas_corners', 'df_frame_corners')): if default_corners: hub_execute(self.name, 'set_default_corners', canvas=ui_settings['df_canvas_corners'], frame=ui_settings['df_frame_corners'], timeout_s=5) else: hub_execute(self.name, 'set_corners', df_canvas_corners=ui_settings['df_canvas_corners'], df_frame_corners=ui_settings['df_frame_corners'], timeout_s=5) # ######################################################################### # # Plugin signal handlers def on_plugin_disable(self): self._gui_enabled = False self.cleanup() def on_plugin_enable(self): super(DmfDeviceUiPlugin, self).on_plugin_enable() self.reset_gui() def on_step_run(self): ''' Handler called whenever a step is executed. Plugins that handle this signal must emit the on_step_complete signal once they have completed the step. The protocol controller will wait until all plugins have completed the current step before proceeding. .. versionchanged:: 2.2.2 Emit ``on_step_complete`` signal within thread-safe function, since signal callbacks may use GTK. ''' app = get_app() if (app.realtime_mode or app.running) and self.gui_process is not None: step_options = self.get_step_options() if not step_options['video_enabled']: command = 'disable_video' else: command = 'enable_video' hub_execute(self.name, command) # Call as thread-safe function, since signal callbacks may use GTK. gtk_threadsafe(emit_signal)('on_step_complete', [self.name, None])
def on_edit_calibration(self, widget=None, data=None): if not self.control_board.connected(): logging.error("A control board must be connected in order to " "edit calibration settings.") return hardware_version = utility.Version.fromstring( self.control_board.hardware_version()) schema_entries = [] settings = {} settings['amplifier_gain'] = self.control_board.amplifier_gain() schema_entries.append( Float.named('amplifier_gain').using( default=settings['amplifier_gain'], optional=True, validators=[ValueAtLeast(minimum=0.01), ]), ) settings['auto_adjust_amplifier_gain'] = self.control_board \ .auto_adjust_amplifier_gain() schema_entries.append( Boolean.named('auto_adjust_amplifier_gain').using( default=settings['auto_adjust_amplifier_gain'], optional=True), ) settings['voltage_tolerance'] = \ self.control_board.voltage_tolerance(); schema_entries.append( Float.named('voltage_tolerance').using( default=settings['voltage_tolerance'], optional=True, validators=[ValueAtLeast(minimum=0),]), ) if hardware_version.major == 1: settings['WAVEOUT_GAIN_1'] = self.control_board \ .eeprom_read(self.control_board.EEPROM_WAVEOUT_GAIN_1_ADDRESS) schema_entries.append( Integer.named('WAVEOUT_GAIN_1').using( default=settings['WAVEOUT_GAIN_1'], optional=True, validators=[ValueAtLeast(minimum=0), ValueAtMost(maximum=255),]), ) settings['VGND'] = self.control_board \ .eeprom_read(self.control_board.EEPROM_VGND_ADDRESS) schema_entries.append( Integer.named('VGND').using( default=settings['VGND'], optional=True, validators=[ValueAtLeast(minimum=0), ValueAtMost(maximum=255),]), ) else: settings['SWITCHING_BOARD_I2C_ADDRESS'] = self.control_board \ .eeprom_read(self.control_board.EEPROM_SWITCHING_BOARD_I2C_ADDRESS) schema_entries.append( Integer.named('SWITCHING_BOARD_I2C_ADDRESS').using( default=settings['SWITCHING_BOARD_I2C_ADDRESS'], optional=True, validators=[ValueAtLeast(minimum=0), ValueAtMost(maximum=255),]), ) settings['SIGNAL_GENERATOR_BOARD_I2C_ADDRESS'] = self.control_board \ .eeprom_read(self.control_board.EEPROM_SIGNAL_GENERATOR_BOARD_I2C_ADDRESS) schema_entries.append( Integer.named('SIGNAL_GENERATOR_BOARD_I2C_ADDRESS').using( default=settings['SIGNAL_GENERATOR_BOARD_I2C_ADDRESS'], optional=True, validators=[ValueAtLeast(minimum=0), ValueAtMost(maximum=255),]), ) for i in range(len(self.control_board.calibration.R_hv)): settings['R_hv_%d' % i] = self.control_board.calibration.R_hv[i] schema_entries.append( Float.named('R_hv_%d' % i).using( default=settings['R_hv_%d' % i], optional=True, validators=[ValueAtLeast(minimum=0),])) settings['C_hv_%d' % i] =\ self.control_board.calibration.C_hv[i]*1e12 schema_entries.append( Float.named('C_hv_%d' % i).using( default=settings['C_hv_%d' % i], optional=True, validators=[ValueAtLeast(minimum=0),])) for i in range(len(self.control_board.calibration.R_fb)): settings['R_fb_%d' % i] = self.control_board.calibration.R_fb[i] schema_entries.append( Float.named('R_fb_%d' % i).using( default=settings['R_fb_%d' % i], optional=True, validators=[ValueAtLeast(minimum=0),])) settings['C_fb_%d' % i] = \ self.control_board.calibration.C_fb[i]*1e12 schema_entries.append( Float.named('C_fb_%d' % i).using( default=settings['C_fb_%d' % i], optional=True, validators=[ValueAtLeast(minimum=0),])) form = Form.of(*schema_entries) dialog = FormViewDialog('Edit calibration settings') valid, response = dialog.run(form) if valid: for k, v in response.items(): if settings[k] != v: m = re.match('(R|C)_(hv|fb)_(\d)', k) if k=='amplifier_gain': self.control_board.set_amplifier_gain(v) elif k=='auto_adjust_amplifier_gain': self.control_board.set_auto_adjust_amplifier_gain(v) elif k=='WAVEOUT_GAIN_1': self.control_board.eeprom_write( self.control_board.EEPROM_WAVEOUT_GAIN_1_ADDRESS, v) elif k=='VGND': self.control_board.eeprom_write( self.control_board.EEPROM_VGND_ADDRESS, v) elif k=='SWITCHING_BOARD_I2C_ADDRESS': self.control_board.eeprom_write( self.control_board.EEPROM_SWITCHING_BOARD_I2C_ADDRESS, v) elif k=='SIGNAL_GENERATOR_BOARD_I2C_ADDRESS': self.control_board.eeprom_write( self.control_board.EEPROM_SIGNAL_GENERATOR_BOARD_I2C_ADDRESS, v) elif k=='voltage_tolerance': self.control_board.set_voltage_tolerance(v) elif m: series_resistor = int(m.group(3)) if m.group(2)=='hv': channel = 0 else: channel = 1 self.control_board.set_series_resistor_index(channel, series_resistor) if m.group(1)=='R': self.control_board.set_series_resistance(channel, v) else: if v is None: v=0 self.control_board.set_series_capacitance(channel, v/1e12) # reconnect to update settings self.connect() if get_app().protocol: self.on_step_run()
List.named(keys.ITEMTRANSCLUSIONS).of(String.named('itemtransclusion').validated_by(wikiname_validator)).using(optional=True), # TODO: CONTENT validation? can we do it here? *common_meta ) UserMetaSchema = DuckDict.named('UserMetaSchema').of( String.named(keys.CONTENTTYPE).validated_by(user_contenttype_validator), String.named('email').using(optional=True), String.named('openid').using(optional=True), String.named('enc_password').using(optional=True), String.named('recoverpass_key').using(optional=True), String.named('theme_name').using(optional=True), String.named('timezone').using(optional=True), String.named('locale').using(optional=True), String.named('css_url').using(optional=True), Integer.named('results_per_page').using(optional=True), Integer.named('edit_rows').using(optional=True), Boolean.named('disabled').using(optional=True), Boolean.named('want_trivial').using(optional=True), Boolean.named('show_comments').using(optional=True), Boolean.named('edit_on_doubleclick').using(optional=True), Boolean.named('mailto_author').using(optional=True), List.named('quicklinks').of(String.named('quicklinks')).using(optional=True), List.named('subscribed_items').of(String.named('subscribed_item')).using(optional=True), List.named('email_subscribed_events').of(String.named('email_subscribed_event')).using(optional=True), #TODO: DuckDict.named('bookmarks').using(optional=True), *common_meta )