Exemple #1
0
class TemplateView(HasPrivateTraits):
    """ A feature-based Traits UI plug-in for viewing templates.
    """

    #-- Public Traits ----------------------------------------------------------

    # The name of the plugin:
    name = Str('Template View')

    # The data context supplying the data to be viewed:
    context = Instance(ITemplateDataContext, connect='to: data context')

    # The name of the file containing a template view:
    file_name = File(drop_file=DropFile(
        extensions=['.py', '.tv', '.las'],
        tooltip='Drop a LAS data file, a saved view template '
        'or a Python source file containing a view '
        'template here.'),
                     connect='to: template view')

    # The template to view:
    template = Instance(ITemplate)

    #-- Private Traits ---------------------------------------------------------

    # The name of the file to save the template in:
    save_file_name = File

    # The current data names:
    data_names = Instance(TemplateDataNames)

    # The TemplateDataNames or Message being viewed:
    names_view = Any(no_context)

    # The name of the most recently loaded template file:
    template_file_name = File

    # The template or message being viewed:
    template_view = Any(no_template)

    # The options view for the currently active template:
    options_view = Any(no_options)

    # The event fired when the user wants to save the template:
    save_template = Button('Save Template')

    #-- Traits View Definitions ------------------------------------------------

    view = View(VGroup(
        HGroup(Item('file_name', show_label=False, width=350),
               TButton('save_template',
                       label='Save Template',
                       enabled_when='template is not None'),
               group_theme=Theme('@GFB', margins=(-7, -5))),
        Tabbed(VGroup('8',
                      Label('Data Bindings',
                            item_theme=Theme('@GBB', alignment='center')),
                      Item('names_view',
                           style='custom',
                           resizable=True,
                           editor=InstanceEditor(),
                           export='DockWindowShell',
                           item_theme=Theme('@GFB', margins=(-5, -1))),
                      label='Data Bindings',
                      show_labels=False),
               Item('template_view',
                    label='Template View',
                    style='custom',
                    resizable=True,
                    editor=InstanceEditor(view='template_view'),
                    export='DockWindowShell'),
               Item('options_view',
                    label='Template Options',
                    style='custom',
                    resizable=True,
                    editor=InstanceEditor(view='options_view'),
                    export='DockWindowShell'),
               id='tabbed',
               dock='horizontal',
               show_labels=False),
    ),
                id='template.test.template_view.TemplateView')

    #-- Trait Event Handlers ---------------------------------------------------

    def _context_changed(self, context):
        """ Handles the 'context' trait being changed.
        """
        if context is None:
            self.names_view = no_context
        elif self.template is None:
            self.names_view = no_template
        else:
            self._create_view()

    def _template_changed(self, template):
        """ Handles the 'template' trait being changed.
        """
        if self.context is None:
            self.template_view = no_context
        else:
            self._create_view()

    def _file_name_changed(self, file_name):
        """ Handles the 'file_name' trait being changed.
        """
        ext = splitext(file_name)[1]
        if ext == '.py':
            self._load_python_template(file_name)
        elif ext == '.tv':
            self._load_pickled_template(file_name)
        elif ext == '.las':
            self._load_las_file(file_name)
        else:
            # fixme: Display an informational message here...
            pass

    def _save_template_changed(self):
        """ Handles the user clicking the 'Save Template' button.
        """
        self._save_pickled_template()

    @on_trait_change('data_names.unresolved_data_names')
    def _on_unresolved_data_names(self):
        if len(self.data_names.unresolved_data_names) == 0:
            self._create_object_view()
        elif not isinstance(self.template_view, Message):
            self.template_view = no_bindings
            self.options_view = no_options

    @on_trait_change('template.template_mutated?')
    def _on_template_mutated(self):
        """ Handles a mutable template changing.
        """
        if self.context is not None:
            self._create_view()

    #-- Private Methods --------------------------------------------------------

    def _load_python_template(self, file_name):
        """ Attempts to load a template from a Python source file.
        """
        path, name = split(file_name)
        sys.path[0:0] = [path]
        try:
            ###values = {}
            module_name, ext = splitext(name)
            module = __import__(module_name)
            values = module.__dict__
            ###execfile( file_name, values )
            template = values.get('template')
            if template is None:
                templates = []
                for value in list(values.values()):
                    try:
                        if (issubclass(value, Template) and
                                ###(value.__module__ == '__builtin__')):
                            (value.__module__ == module_name)):
                            templates.append(value)
                    except:
                        pass

                for i, template in enumerate(templates):
                    for t in templates[i + 1:]:
                        if issubclass(template, t):
                            break
                    else:
                        break
                else:
                    self.template_view = no_template_found
                    return

            if not isinstance(template, Template):
                template = template()

            self.template = template
            self.template_file_name = file_name

        except Exception as excp:
            self.template_view = Message(str(excp))

        # Clean up the Python path:
        del sys.path[0]

    def _load_pickled_template(self, file_name):
        """ Attempts to load a template from a pickle.
        """
        # fixme: Implement this...load template from .tv pickle file.
        fh = None
        delete = False
        try:
            fh = open(file_name, 'rb')
            file_name = load(fh)
            path, name = split(file_name)
            sys.path[0:0] = [path]
            delete = True
            module_name, ext = splitext(name)
            module = __import__(module_name)
            self.template = load(fh)
            self.template_file_name = file_name
        except Exception as excp:
            import traceback
            traceback.print_exc()
            self.template_view = Message(str(excp))

        if fh is not None:
            fh.close()

        if delete:
            del sys.path[0]

    def _load_las_file(self, file_name):
        """ Creates a data context from the specified LAS file.
        """
        try:
            self.context = import_log_files(file_name, 'las')
        except Exception as excp:
            self.names_view = Message(str(excp))

    def _save_pickled_template(self):
        file_name = self.save_file_name or self.file_name
        fd = FileDialog(action='save as', default_path=file_name)
        #wildcard     = 'Template files (*.tv)|*.tv|' )
        if fd.open() == OK:
            self.save_file_name = file_name = fd.path
            fh = None
            try:
                fh = open(file_name, 'wb')
                dump(self.template_file_name, fh, -1)
                dump(self.template.template_from_object(), fh, -1)
            except:
                # fixme: Display an informational message here...
                import traceback
                traceback.print_exc()

            if fh is not None:
                fh.close()

    def _create_view(self):
        """ Begins the process of creating a live view from a template and
            data context object.
        """
        self.data_names = self.names_view = nv = TemplateDataNames(
            context=self.context,
            data_names=self.template.names_from_template())

        if len(nv.unresolved_data_names) == 0:
            self._create_object_view()
        else:
            self.template_view = no_bindings

    def _create_object_view(self):
        """ Create the object view from the current template.
        """
        self.template.object_from_template()
        self.template_view = self.options_view = self.template
Exemple #2
0
 def traits_view(self):
     v = View(
         UItem('plot_panel',
               editor=InstanceEditor(view='summary_view'),
               style='custom'))
     return v
Exemple #3
0
    def traits_view(self):
        multicollect_grp = VGroup(
            Group(
                Item('multicollect_counts',
                     label='Counts',
                     tooltip='Number of data points to collect')),
            UItem('fit_block', editor=EnumEditor(name='fit_blocks')),
            UItem('fit_block', style='custom', editor=InstanceEditor()),
            #                                  UItem('active_detectors',
            #                                        style='custom',
            #                                        editor=TableEditor(
            #                                         sortable=False,
            #                                         reorderable=False,
            #                                         columns=[
            #                                         ObjectColumn(name='label',
            #                                                      editable=False,
            #                                                      label='Det.'),
            #                                         CheckboxColumn(name='use', label='Use'),
            #                                         ObjectColumn(name='isotope',
            #                                                      editor=EnumEditor(name='isotopes')
            #                                                      ),
            #                                         ObjectColumn(name='fit',
            #                                                      editor=EnumEditor(values=[NULL_STR] + FIT_TYPES)
            #                                                      ),

            #                                                                    ])
            #                                        ),
            label='Multicollect',
            show_border=True)
        baseline_grp = Group(
            Item('baseline_before', label='Baselines at Start'),
            Item('baseline_after', label='Baselines at End'),
            Item('baseline_counts',
                 tooltip='Number of baseline data points to collect',
                 label='Counts'),
            Item('baseline_detector', label='Detector'),
            Item(
                'baseline_settling_time',
                label='Delay (s)',
                tooltip=
                'Wait "Delay" seconds after setting magnet to baseline position'
            ),
            Item('baseline_mass', label='Mass'),
            label='Baseline',
            show_border=True)

        peak_center_grp = Group(
            Item('peak_center_before', label='Peak Center at Start'),
            Item('peak_center_after', label='Peak Center at End'),
            Item('peak_center_detector',
                 label='Detector',
                 enabled_when='peak_center_before or peak_center_after'),
            Item('peak_center_isotope',
                 label='Isotope',
                 enabled_when='peak_center_before or peak_center_after'),
            label='Peak Center',
            show_border=True)

        equilibration_grp = Group(
            Item('eq_time', label='Time (s)'),
            Item('eq_outlet', label='Ion Pump Valve'),
            Item(
                'eq_delay',
                label='Delay (s)',
                tooltip='Wait "Delay" seconds before opening the Inlet Valve'),
            Item('eq_inlet', label='Inlet Valve'),
            label='Equilibration',
            show_border=True)

        peak_hop_group = VGroup(
            Group(Item('ncycles'), Item('baseline_ncycles')),
            HGroup(Spring(springy=False, width=28), Label('position'),
                   Spring(springy=False, width=10), Label('Detectors'),
                   Spring(springy=False, width=188), Label('counts'), spring),
            UItem('hops',
                  editor=ListEditor(style='custom', editor=InstanceEditor())),
            label='Peak Hop',
            visible_when='use_peak_hop',
            show_border=True)

        action_editor = TableEditor(
            columns=[
                CheckboxColumn(name='use', width=20),
                ObjectColumn(name='key', label='Key'),
                ObjectColumn(name='comparator', label='Comp.'),
                ObjectColumn(name='criterion', label='Crit.'),
                ObjectColumn(name='start_count', label='Start'),
                #                                       ObjectColumn(name='frequency',
                #                                                    label='Freq.'
                #                                                    ),
                CheckboxColumn(name='resume', label='Resume'),
            ],
            deletable=True,
            row_factory=self._new_action)

        truncation_editor = TableEditor(columns=[
            CheckboxColumn(name='use', width=20),
            ObjectColumn(name='key', label='Key'),
            ObjectColumn(name='comparator', label='Comp.'),
            ObjectColumn(name='criterion', label='Crit.'),
            ObjectColumn(name='start_count', label='Start'),
        ],
                                        deletable=True,
                                        row_factory=self._new_truncation)

        termination_editor = TableEditor(columns=[
            CheckboxColumn(name='use', width=20),
            ObjectColumn(name='key', label='Key'),
            ObjectColumn(name='comparator', label='Comp.'),
            ObjectColumn(name='criterion', label='Crit.'),
            ObjectColumn(name='start_count', label='Start'),
        ],
                                         deletable=True,
                                         row_factory=self._new_termination)

        cond_grp = VGroup(Group(UItem('actions',
                                      editor=action_editor,
                                      style='custom'),
                                label='Actions'),
                          Group(UItem('truncations',
                                      editor=truncation_editor,
                                      style='custom'),
                                label='Truncations'),
                          Group(UItem('terminations',
                                      editor=termination_editor,
                                      style='custom'),
                                label='Terminations'),
                          label='Conditions')

        v = View(
            VGroup(
                Item('use_peak_hop'),
                peak_hop_group,
                Group(multicollect_grp,
                      cond_grp,
                      baseline_grp,
                      peak_center_grp,
                      equilibration_grp,
                      layout='tabbed',
                      visible_when='not use_peak_hop'),
            ))
        return v
    def traits_view(self):
        main_grp = Group(UItem('main_view',
                               style='custom',
                               editor=InstanceEditor()),
                         label='Main')

        teditor = myTabularEditor(adapter=self.isotope_adapter,
                                  drag_enabled=False,
                                  stretch_last_section=False,
                                  editable=False,
                                  multi_select=True,
                                  selected='selected',
                                  dclicked='dclicked',
                                  refresh='refresh_needed')
        ieditor = myTabularEditor(adapter=self.intermediate_adapter,
                                  editable=False,
                                  drag_enabled=False,
                                  stretch_last_section=False,
                                  refresh='refresh_needed')
        isotope_grp = Group(UItem(
            'isotopes',
            editor=teditor,
        ),
                            UItem('isotopes',
                                  editor=ieditor,
                                  visible_when='show_intermediate'),
                            label='Isotopes')

        history_grp = Group(UItem('history_view',
                                  style='custom',
                                  editor=InstanceEditor()),
                            label='History')

        experiment_grp = Group(UItem('experiment_view',
                                     style='custom',
                                     editor=InstanceEditor()),
                               defined_when='experiment_view',
                               label='Experiment')
        interference_grp = Group(UItem('interference_view',
                                       style='custom',
                                       editor=InstanceEditor()),
                                 defined_when='interference_view',
                                 label='Interference')
        measurement_grp = Group(UItem('measurement_view',
                                      style='custom',
                                      editor=InstanceEditor()),
                                defined_when='measurement_view',
                                label='Measurement')
        extraction_grp = Group(UItem('extraction_view',
                                     style='custom',
                                     editor=InstanceEditor()),
                               defined_when='extraction_view',
                               label='Extraction')
        snapshot_grp = Group(UItem('snapshot_view',
                                   style='custom',
                                   editor=InstanceEditor()),
                             defined_when='snapshot_view',
                             label='Snapshot')
        detector_ic_grp = Group(UItem('detector_ic_view',
                                      style='custom',
                                      editor=InstanceEditor()),
                                defined_when='detector_ic_view',
                                label='DetectorIC')
        spectrometer_grp = Group(UItem('spectrometer_view',
                                       style='custom',
                                       editor=InstanceEditor()),
                                 defined_when='spectrometer_view',
                                 label='Spectrometer')

        peak_center_grp = Group(UItem('peak_center_view',
                                      style='custom',
                                      editor=InstanceEditor()),
                                defined_when='peak_center_view',
                                label='Peak_center')
        v = View(VGroup(
            Spring(springy=False, height=-10),
            Tabbed(main_grp, isotope_grp, history_grp, experiment_grp,
                   extraction_grp, measurement_grp, peak_center_grp,
                   interference_grp, spectrometer_grp, detector_ic_grp,
                   snapshot_grp)),
                 handler=AnalysisViewHandler())
        return v
Exemple #5
0
 def traits_view(self):
     ctrl_grp = VGroup(
         Item('path', show_label=False),
         Item('highlight_bands',
              editor=ListEditor(mutable=False,
                                style='custom',
                                editor=InstanceEditor())))
     v = View(
         ctrl_grp,
         Item('container', show_label=False, editor=ComponentEditor()),
         #
         title='Color Inspector',
         resizable=True,
         height=800,
         width=900)
     return v
     #    def traits_view(self):
     #        lgrp = VGroup(Item('low'),
     #                      Item('low', show_label=False, editor=RangeEditor(mode='slider', low=0, high_name='high')))
     #        hgrp = VGroup(Item('high'),
     #                      Item('high', show_label=False, editor=RangeEditor(mode='slider', low_name='low', high=255)))
     #        savegrp = HGroup(Item('save_button', show_label=False),
     #                         Item('save_mode', show_label=False))
     #        ctrlgrp = VGroup(
     #                         Item('path', show_label=False),
     #                         HGroup(Item('use_threshold'), Item('contrast_equalize'),
     #                                HGroup(Item('contrast_low'), Item('contrast_high'), enabled_when='contrast_equalize'),
     #                                Item('histogram_equalize')
     #                                ),
     #                         HGroup(Item('highlight'), Item('highlight_threshold')),
     #                         HGroup(spring,
     #                                lgrp,
     #                                hgrp,
     #                                VGroup(savegrp,
     #                                       Item('calc_area_value', label='Calc. Area For.',
     #                                                     tooltip='Calculate %area for all pixels with this value'
     #                                                     ),
     #                                       Item('calc_area_threshold', label='Threshold +/- px',
     #                                            tooltip='bandwidth= calc_value-threshold to calc_value+threshold'
     #                                            )
     #
     #                                       )
     #                                ),
     #                         HGroup(spring, Item('area', style='readonly', width= -200)),
     #                         HGroup(
     #                                Item('colormap_name_1', show_label=False,
     #                                      editor=EnumEditor(values=color_map_name_dict.keys())),
     #                                spring,
     #                                Item('colormap_name_2', show_label=False,
     #                                     editor=EnumEditor(values=color_map_name_dict.keys()))),
     #                       )
     #        v = View(ctrlgrp,
     #                 Item('container', show_label=False,
     #                       editor=ComponentEditor()),
     #
     #                 title='Color Inspector',
     #                 resizable=True,
     #                 height=800,
     #                 width=900
     #
     #                 )
     return v
Exemple #6
0
 def traits_view(self):
     v = View(VSplit(UItem('graph', style='custom', height=0.8, editor=InstanceEditor()),
                     UItem('results',
                           height=0.2,
                           editor=TabularEditor(adapter=PeakCenterResultsAdapter()))))
     return v
class Actor2D(Component):
    # The version of this class.  Used for persistence.
    __version__ = 0

    # The mapper.
    mapper = Instance(tvtk.AbstractMapper, record=True)

    # The actor.
    actor = Instance(tvtk.Prop, record=True)

    # The actor's property.
    property = Instance(tvtk.Property2D, record=True)

    ########################################
    # View related traits.

    # The Actor's view group.
    _actor_group = Group(Item(name='visibility'),
                         Item(name='height'),
                         Item(name='width'),
                         show_border=True,
                         label='Actor')

    # The View for this object.
    view = View(
        Group(Item(name='actor',
                   style='custom',
                   editor=InstanceEditor(view=View(_actor_group))),
              show_labels=False,
              label='Actor'),
        Group(Item(name='mapper', style='custom', resizable=True),
              show_labels=False,
              label='Mapper'),
        Group(Item(name='property', style='custom', resizable=True),
              show_labels=False,
              label='Property'),
        resizable=True,
    )

    ######################################################################
    # `Component` interface
    ######################################################################
    def setup_pipeline(self):
        """Override this method so that it *creates* its tvtk
        pipeline.

        This method is invoked when the object is initialized via
        `__init__`.  Note that at the time this method is called, the
        tvtk data pipeline will *not* yet be setup.  So upstream data
        will not be available.  The idea is that you simply create the
        basic objects and setup those parts of the pipeline not
        dependent on upstream sources and filters.
        """
        if self.mapper is None:
            self.mapper = tvtk.TextMapper()
        self.actor = tvtk.Actor2D()
        self.property = self.actor.property

    def update_pipeline(self):
        """Override this method so that it *updates* the tvtk pipeline
        when data upstream is known to have changed.

        This method is invoked (automatically) when the input fires a
        `pipeline_changed` event.
        """
        if (len(self.inputs) == 0) or \
               (len(self.inputs[0].outputs) == 0):
            return
        self.configure_connection(self.mapper, self.inputs[0])
        self.render()

    def update_data(self):
        """Override this method to do what is necessary when upstream
        data changes.

        This method is invoked (automatically) when any of the inputs
        sends a `data_changed` event.
        """
        # Invoke render to update any changes.
        self.render()

    ######################################################################
    # Non-public interface.
    ######################################################################
    def _setup_handlers(self, old, new):
        if old is not None:
            old.on_trait_change(self.render, remove=True)
        new.on_trait_change(self.render)

    def _mapper_changed(self, old, new):
        # Setup the handlers.
        self._setup_handlers(old, new)
        # Setup the inputs to the mapper.
        if (len(self.inputs) > 0) and (len(self.inputs[0].outputs) > 0):
            self.configure_connection(new, self.inputs[0])
        # Setup the actor's mapper.
        actor = self.actor
        if actor is not None:
            actor.mapper = new
        self.render()

    def _actor_changed(self, old, new):
        # Setup the handlers.
        self._setup_handlers(old, new)
        # Set the mapper.
        mapper = self.mapper
        if mapper is not None:
            new.mapper = mapper
        # Set the property.
        prop = self.property
        if prop is not None:
            new.property = prop
        # Setup the `actors` trait.
        self.actors = [new]

    def _property_changed(self, old, new):
        # Setup the handlers.
        self._setup_handlers(old, new)
        # Setup the actor.
        actor = self.actor
        if new is not actor.property:
            actor.property = new

    def _foreground_changed_for_scene(self, old, new):
        # Change the default color for the actor.
        self.property.color = new
        self.render()

    def _scene_changed(self, old, new):
        super(Actor2D, self)._scene_changed(old, new)
        self._foreground_changed_for_scene(None, new.foreground)
class ViewHandlerMixin(HasTraits):
    """
    Useful bits for view handlers. 
    """

    # the view for the current plot
    current_plot_view = \
        View(
            HGroup(
                Item('plot_names_by',
                     editor = TextEditor(),
                     style = "readonly",
                     show_label = False),
                Item('current_plot',
                     editor = TabListEditor(name = 'plot_names'),
                     style = 'custom',
                     show_label = False)))

    plot_params_traits = View(
        Item('plot_params',
             editor=InstanceEditor(),
             style='custom',
             show_label=False))

    context = Instance(WorkflowItem)

    conditions_names = Property(depends_on="context.conditions")
    previous_conditions_names = Property(
        depends_on="context.previous_wi.conditions")
    statistics_names = Property(depends_on="context.statistics")
    numeric_statistics_names = Property(depends_on="context.statistics")

    # MAGIC: gets value for property "conditions_names"
    def _get_conditions_names(self):
        if self.context and self.context.conditions:
            return sorted(list(self.context.conditions.keys()))
        else:
            return []

    # MAGIC: gets value for property "previous_conditions_names"
    def _get_previous_conditions_names(self):
        if self.context and self.context.previous_wi and self.context.previous_wi.conditions:
            return sorted(list(self.context.previous_wi.conditions.keys()))
        else:
            return []

    # MAGIC: gets value for property "statistics_names"
    def _get_statistics_names(self):
        if self.context and self.context.statistics:
            return sorted(list(self.context.statistics.keys()))
        else:
            return []

    # MAGIC: gets value for property "numeric_statistics_names"
    def _get_numeric_statistics_names(self):
        if self.context and self.context.statistics:
            return sorted([
                x for x in list(self.context.statistics.keys())
                if util.is_numeric(self.context.statistics[x])
            ])
        else:
            return []

    @on_trait_change('context.view_error_trait', dispatch='ui', post_init=True)
    def _view_trait_error(self):

        # check if we're getting called on the local or remote process
        if self.info is None or self.info.ui is None:
            return

        for ed in self.info.ui._editors:

            if ed.name == self.context.view_error_trait:
                err_state = True
            else:
                err_state = False

            if not ed.label_control:
                continue

            item = ed.label_control

            if not err_state and not hasattr(item, '_ok_color'):
                continue

            pal = QtGui.QPalette(item.palette())  # @UndefinedVariable

            if err_state:
                setattr(item, '_ok_color',
                        QtGui.QColor(pal.color(
                            item.backgroundRole())))  # @UndefinedVariable
                pal.setColor(item.backgroundRole(),
                             QtGui.QColor(255, 145, 145))  # @UndefinedVariable
                item.setAutoFillBackground(True)
                item.setPalette(pal)
            else:
                pal.setColor(item.backgroundRole(), item._ok_color)
                delattr(item, '_ok_color')
                item.setAutoFillBackground(False)
                item.setPalette(pal)
Exemple #9
0
class SwiftConsole(HasTraits):
    """Traits-defined Swift Console.

    link : object
      Serial driver
    update : bool
      Update the firmware
    log_level_filter : str
      Syslog string, one of "ERROR", "WARNING", "INFO", "DEBUG".
    """

    link = Instance(sbpc.Handler)
    console_output = Instance(OutputList())
    python_console_env = Dict
    device_serial = Str('')
    dev_id = Str('')
    tracking_view = Instance(TrackingView)
    solution_view = Instance(SolutionView)
    baseline_view = Instance(BaselineView)
    skyplot_view = Instance(SkyplotView)
    observation_view = Instance(ObservationView)
    networking_view = Instance(SbpRelayView)
    observation_view_base = Instance(ObservationView)
    system_monitor_view = Instance(SystemMonitorView)
    settings_view = Instance(SettingsView)
    update_view = Instance(UpdateView)
    ins_view = Instance(INSView)
    mag_view = Instance(MagView)
    spectrum_analyzer_view = Instance(SpectrumAnalyzerView)
    log_level_filter = Enum(list(SYSLOG_LEVELS.values()))
    """"
  mode : baseline and solution view - SPP, Fixed or Float
  num_sat : baseline and solution view - number of satellites
  port : which port is Swift Device is connected to
  directory_name : location of logged files
  json_logging : enable JSON logging
  csv_logging : enable CSV logging

  """
    pos_mode = Str('')
    rtk_mode = Str('')
    ins_status_string = Str('')
    num_sats_str = Str('')
    cnx_desc = Str('')
    age_of_corrections = Str('')
    uuid = Str('')
    directory_name = Directory
    json_logging = Bool(True)
    csv_logging = Bool(False)
    show_csv_log = Bool(False)
    cnx_icon = Str('')
    heartbeat_count = Int()
    last_timer_heartbeat = Int()
    driver_data_rate = Str()
    solid_connection = Bool(False)

    csv_logging_button = SVGButton(
        toggle=True,
        label='CSV log',
        tooltip='start CSV logging',
        toggle_tooltip='stop CSV logging',
        filename=resource_filename('console/images/iconic/pause.svg'),
        toggle_filename=resource_filename('console/images/iconic/play.svg'),
        orientation='vertical',
        width=2,
        height=2,
    )
    json_logging_button = SVGButton(
        toggle=True,
        label='JSON log',
        tooltip='start JSON logging',
        toggle_tooltip='stop JSON logging',
        filename=resource_filename('console/images/iconic/pause.svg'),
        toggle_filename=resource_filename('console/images/iconic/play.svg'),
        orientation='vertical',
        width=2,
        height=2,
    )
    paused_button = SVGButton(
        label='',
        tooltip='Pause console update',
        toggle_tooltip='Resume console update',
        toggle=True,
        filename=resource_filename('console/images/iconic/pause.svg'),
        toggle_filename=resource_filename('console/images/iconic/play.svg'),
        width=8,
        height=8)
    clear_button = SVGButton(
        label='',
        tooltip='Clear console buffer',
        filename=resource_filename('console/images/iconic/x.svg'),
        width=8,
        height=8)

    view = View(VSplit(
        Tabbed(Tabbed(Item('tracking_view',
                           style='custom',
                           label='Signals',
                           show_label=False),
                      Item('skyplot_view',
                           style='custom',
                           label='Sky Plot',
                           show_label=False),
                      label="Tracking"),
               Item('solution_view', style='custom', label='Solution'),
               Item('baseline_view', style='custom', label='Baseline'),
               VSplit(
                   Item('observation_view', style='custom', show_label=False),
                   Item('observation_view_base',
                        style='custom',
                        show_label=False),
                   label='Observations',
               ),
               Item('settings_view', style='custom', label='Settings'),
               Item('update_view', style='custom', label='Update'),
               Tabbed(Item('system_monitor_view',
                           style='custom',
                           label='System Monitor'),
                      Item('ins_view', style='custom', label='INS'),
                      Item('mag_view', style='custom', label='Magnetometer'),
                      Item('networking_view',
                           label='Networking',
                           style='custom',
                           show_label=False),
                      Item('spectrum_analyzer_view',
                           label='Spectrum Analyzer',
                           style='custom'),
                      label='Advanced',
                      show_labels=False),
               show_labels=False),
        VGroup(
            VGroup(
                HGroup(
                    Spring(width=4, springy=False),
                    Item('paused_button',
                         show_label=False,
                         padding=0,
                         width=8,
                         height=8),
                    Item('clear_button', show_label=False, width=8, height=8),
                    Item('', label='Console Log', emphasized=True),
                    Item('csv_logging_button',
                         emphasized=True,
                         show_label=False,
                         visible_when='show_csv_log',
                         width=12,
                         height=-30,
                         padding=0),
                    Item('json_logging_button',
                         emphasized=True,
                         show_label=False,
                         width=12,
                         height=-30,
                         padding=0),
                    Item(
                        'directory_name',
                        show_label=False,
                        springy=True,
                        tooltip=
                        'Choose location for file logs. Default is home/SwiftNav.',
                        height=-25,
                        enabled_when='not(json_logging or csv_logging)',
                        editor_args={'auto_set': True}),
                    UItem(
                        'log_level_filter',
                        style='simple',
                        padding=0,
                        height=8,
                        show_label=True,
                        tooltip=
                        'Show log levels up to and including the selected level of severity.\nThe CONSOLE log level is always visible.'
                    ),
                ),
                Item('console_output',
                     style='custom',
                     editor=InstanceEditor(),
                     height=125,
                     show_label=False,
                     full_size=True),
            ),
            HGroup(
                Spring(width=4, springy=False),
                Item('',
                     label='Port:',
                     emphasized=True,
                     tooltip='Interface for communicating with Swift device'),
                Item('cnx_desc', show_label=False, style='readonly'),
                Item('',
                     label='Pos:',
                     emphasized=True,
                     tooltip='Device Position Mode: SPS, DGNSS, or RTK'),
                Item('pos_mode', show_label=False, style='readonly'),
                Item('',
                     label='RTK:',
                     emphasized=True,
                     tooltip='Device RTK Mode: Float or Fixed'),
                Item('rtk_mode', show_label=False, style='readonly'),
                Item('',
                     label='Sats:',
                     emphasized=True,
                     tooltip='Number of satellites used in solution'),
                Item('num_sats_str',
                     padding=2,
                     show_label=False,
                     style='readonly'),
                Item('',
                     label='Corr Age:',
                     emphasized=True,
                     tooltip=
                     'Age of corrections (-- means invalid / not present)'),
                Item('age_of_corrections',
                     padding=2,
                     show_label=False,
                     style='readonly'),
                Item('',
                     label='INS:',
                     emphasized=True,
                     tooltip='INS Status String'),
                Item('ins_status_string',
                     padding=2,
                     show_label=False,
                     style='readonly',
                     width=6),
                Spring(springy=True),
                Item('driver_data_rate', style='readonly', show_label=False),
                Item('cnx_icon',
                     show_label=False,
                     padding=0,
                     width=8,
                     height=8,
                     visible_when='solid_connection',
                     springy=False,
                     editor=ImageEditor(
                         allow_clipping=False,
                         image=ImageResource(
                             resource_filename(
                                 'console/images/iconic/arrows_blue.png')))),
                Item('cnx_icon',
                     show_label=False,
                     padding=0,
                     width=8,
                     height=8,
                     visible_when='not solid_connection',
                     springy=False,
                     editor=ImageEditor(
                         allow_clipping=False,
                         image=ImageResource(
                             resource_filename(
                                 'console/images/iconic/arrows_grey.png')))),
                Spring(width=4, height=-2, springy=False),
            ),
            Spring(height=1, springy=False),
        ),
    ),
                icon=icon,
                resizable=True,
                width=800,
                height=600,
                handler=ConsoleHandler(),
                title=CONSOLE_TITLE)

    def print_message_callback(self, sbp_msg, **metadata):
        try:
            encoded = sbp_msg.payload.encode('ascii', 'ignore')
            for eachline in reversed(encoded.split('\n')):
                self.console_output.write_level(
                    eachline, str_to_log_level(eachline.split(':')[0]))
        except UnicodeDecodeError as e:
            print("Error encoding msg_print: {}".format(e))

    def log_message_callback(self, sbp_msg, **metadata):
        encoded = sbp_msg.text.decode('utf8')
        for eachline in reversed(encoded.split('\n')):
            self.console_output.write_level(eachline, sbp_msg.level)

    def ext_event_callback(self, sbp_msg, **metadata):
        e = MsgExtEvent(sbp_msg)
        print(
            'External event: %s edge on pin %d at wn=%d, tow=%d, time qual=%s'
            % ("Rising" if
               (e.flags &
                (1 << 0)) else "Falling", e.pin, e.wn, e.tow, "good" if
               (e.flags & (1 << 1)) else "unknown"))

    def cmd_resp_callback(self, sbp_msg, **metadata):
        r = MsgCommandResp(sbp_msg)
        print("Received a command response message with code {0}".format(
            r.code))

    def _paused_button_fired(self):
        self.console_output.paused = not self.console_output.paused

    def _log_level_filter_changed(self):
        """
        Takes log level enum and translates into the mapped integer.
        Integer stores the current filter value inside OutputList.
        """
        self.console_output.log_level_filter = str_to_log_level(
            self.log_level_filter)

    def _clear_button_fired(self):
        self.console_output.clear()

    def _directory_name_changed(self):
        if self.baseline_view and self.solution_view:
            self.baseline_view.directory_name_b = self.directory_name
            self.solution_view.directory_name_p = self.directory_name
            self.solution_view.directory_name_v = self.directory_name
        if self.observation_view and self.observation_view_base:
            self.observation_view.dirname = self.directory_name
            self.observation_view_base.dirname = self.directory_name

    def update_on_heartbeat(self, sbp_msg, **metadata):
        self.heartbeat_count += 1

    def check_heartbeat(self):
        # if our heartbeat hasn't changed since the last timer interval the connection must have dropped
        if self.heartbeat_count == self.last_timer_heartbeat and self.heartbeat_count != 0:
            self.solid_connection = False
            self.ins_status_string = "None"
            self.pos_mode = "None"
            self.ins_mode = "None"
            self.num_sats_str = EMPTY_STR
            self.last_timer_heartbeat = self.heartbeat_count
        else:
            self.solid_connection = True
        self.last_timer_heartbeat = self.heartbeat_count

        bytes_diff = self.driver.bytes_read_since(self.last_driver_bytes_read)
        # 1024 bytes per KiloByte
        self.driver_data_rate = "{0:.2f} KB/s".format(
            (bytes_diff) / (HEARTBEAT_CHECK_PERIOD_SECONDS * 1024))
        self.last_driver_bytes_read = self.driver.total_bytes_read

        # if we aren't getting heartbeats and we have at some point gotten heartbeats, we don't have
        # a good connection and we should age out the data from the views
        if not self.solid_connection or self.driver_data_rate == 0:
            return
        # -- grab current time to use in logic
        current_time = monotonic()
        # --- determining which mode, llh or baseline, to show in the status bar ---
        llh_display_mode = "None"
        llh_num_sats = 0
        llh_is_rtk = False

        baseline_display_mode = "None"

        ins_status_string = EMPTY_STR

        # determine the latest llh solution mode
        if self.solution_view and (current_time -
                                   self.solution_view.last_stime_update) < 1:
            llh_solution_mode = self.solution_view.last_pos_mode
            llh_display_mode = pos_mode_dict.get(llh_solution_mode, EMPTY_STR)
            if llh_solution_mode > 0 and self.solution_view.last_soln:
                llh_num_sats = self.solution_view.last_soln.n_sats
            llh_is_rtk = (llh_solution_mode in RTK_MODES)
            if getattr(self.solution_view, 'ins_used',
                       False) and llh_solution_mode != DR_MODE:
                llh_display_mode += "+INS"

        # determine the latest baseline solution mode
        if (self.baseline_view and self.settings_view
                and self.settings_view.dgnss_enabled
                and (current_time - self.baseline_view.last_btime_update) < 1):
            baseline_solution_mode = self.baseline_view.last_mode
            baseline_display_mode = rtk_mode_dict.get(baseline_solution_mode,
                                                      EMPTY_STR)

        # if baseline solution mode is empty and POS mode is RTK, get the RTK mode from pos
        if baseline_display_mode not in rtk_mode_dict.values() and llh_is_rtk:
            baseline_display_mode = rtk_mode_dict.get(llh_solution_mode)

        # determine the latest INS mode
        if self.solution_view and (
                current_time -
                self.solution_view.last_ins_status_receipt_time) < 1:
            ins_flags = self.solution_view.ins_status_flags
            ins_mode = ins_flags & 0x7
            ins_type = (ins_flags >> 29) & 0x7
            odo_status = (ins_flags >> 8) & 0x3
            # ins_status has bug in odo.
            # If it says no odo, check if we have had tics in last 10 seconds from INS_UPDATES
            if odo_status != 1:
                if (current_time -
                        self.solution_view.last_odo_update_time) < 10:
                    odo_status = 1
            ins_error = (ins_flags >> 4) & 0xF
            if ins_error != 0:
                ins_status_string = ins_error_dict.get(ins_error, "Unk Error")
            else:
                ins_status_string = ins_type_dict.get(ins_type, "unk") + "-"
                ins_status_string += ins_mode_dict.get(ins_mode, "unk")
                if odo_status == 1:
                    ins_status_string += "+Odo"

        # get age of corrections from baseline view
        if self.baseline_view:
            if (self.baseline_view.age_corrections is not None
                    and (current_time -
                         self.baseline_view.last_age_corr_receipt_time) < 1):
                self.age_of_corrections = "{0} s".format(
                    self.baseline_view.age_corrections)
            else:
                self.age_of_corrections = EMPTY_STR

        # populate modes and #sats on status bar
            self.ins_status_string = ins_status_string
            self.rtk_mode = baseline_display_mode
            self.pos_mode = llh_display_mode
            self.num_sats_str = "{}".format(llh_num_sats)

        # --- end of status bar mode determination section ---

        if self.settings_view:  # for auto populating surveyed fields
            self.settings_view.lat = self.solution_view.latitude
            self.settings_view.lon = self.solution_view.longitude
            self.settings_view.alt = self.solution_view.altitude

    def _csv_logging_button_action(self):
        if self.csv_logging and self.baseline_view.logging_b and self.solution_view.logging_p and self.solution_view.logging_v:
            print("Stopped CSV logging")
            self.csv_logging = False
            self.baseline_view.logging_b = False
            self.solution_view.logging_p = False
            self.solution_view.logging_v = False

        else:
            print("Started CSV logging at %s" % self.directory_name)
            self.csv_logging = True
            self.baseline_view.logging_b = True
            self.solution_view.logging_p = True
            self.solution_view.logging_v = True

    def _start_json_logging(self, override_filename=None):
        if override_filename:
            filename = override_filename
        else:
            filename = time.strftime("swift-gnss-%Y%m%d-%H%M%S.sbp.json",
                                     time.localtime())
            filename = os.path.normpath(
                os.path.join(self.directory_name, filename))
        self.logger = s.get_logger(True, filename, self.expand_json)
        self.forwarder = sbpc.Forwarder(self.link, self.logger)
        self.forwarder.start()
        if self.settings_view:
            self.settings_view._settings_read_all()

    def _stop_json_logging(self):
        fwd = self.forwarder
        fwd.stop()
        self.logger.flush()
        self.logger.close()

    def _json_logging_button_action(self):
        if self.first_json_press and self.json_logging:
            print(
                "JSON Logging initiated via CMD line.  Please press button again to stop logging"
            )
        elif self.json_logging:
            self._stop_json_logging()
            self.json_logging = False
            print("Stopped JSON logging")
        else:
            self._start_json_logging()
            self.json_logging = True
        self.first_json_press = False

    def _json_logging_button_fired(self):
        if not os.path.exists(self.directory_name) and not self.json_logging:
            print(
                "The selected logging directory does not exist and will be created."
            )
        self._json_logging_button_action()

    def _csv_logging_button_fired(self):
        if not os.path.exists(self.directory_name) and not self.csv_logging:
            print(
                "The selected logging directory does not exist and will be created."
            )
        self._csv_logging_button_action()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.console_output.close()

    def __init__(self,
                 link,
                 driver,
                 update,
                 log_level_filter,
                 error=False,
                 cnx_desc=None,
                 json_logging=False,
                 show_csv_log=False,
                 log_dirname=None,
                 override_filename=None,
                 log_console=False,
                 connection_info=None,
                 expand_json=False,
                 hide_legend=False):
        self.error = error
        self.cnx_desc = cnx_desc
        self.connection_info = connection_info
        self.show_csv_log = show_csv_log
        self.dev_id = cnx_desc
        self.num_sats_str = EMPTY_STR
        self.mode = ''
        self.ins_status_string = "None"
        self.forwarder = None
        self.age_of_corrections = '--'
        self.expand_json = expand_json
        # if we have passed a logfile, we set our directory to it
        override_filename = override_filename
        self.last_status_update_time = 0
        self.last_driver_bytes_read = 0
        self.driver = driver

        if log_dirname:
            self.directory_name = log_dirname
            if override_filename:
                override_filename = os.path.join(log_dirname,
                                                 override_filename)
        else:
            self.directory_name = swift_path

        # Start swallowing sys.stdout and sys.stderr
        self.console_output = OutputList(tfile=log_console,
                                         outdir=self.directory_name)
        sys.stdout = self.console_output
        self.console_output.write("Console: " + CONSOLE_VERSION +
                                  " starting...")
        if not error:
            sys.stderr = self.console_output

        self.log_level_filter = log_level_filter
        self.console_output.log_level_filter = str_to_log_level(
            log_level_filter)
        try:
            self.link = link
            self.link.add_callback(self.print_message_callback,
                                   SBP_MSG_PRINT_DEP)
            self.link.add_callback(self.log_message_callback, SBP_MSG_LOG)
            self.link.add_callback(self.ext_event_callback, SBP_MSG_EXT_EVENT)
            self.link.add_callback(self.cmd_resp_callback,
                                   SBP_MSG_COMMAND_RESP)
            self.link.add_callback(self.update_on_heartbeat, SBP_MSG_HEARTBEAT)
            self.dep_handler = DeprecatedMessageHandler(link)
            settings_read_finished_functions = []
            self.tracking_view = TrackingView(self.link,
                                              legend_visible=(not hide_legend))
            self.solution_view = SolutionView(self.link,
                                              dirname=self.directory_name)
            self.baseline_view = BaselineView(self.link,
                                              dirname=self.directory_name)
            self.skyplot_view = SkyplotView(self.link, self.tracking_view)
            self.observation_view = ObservationView(
                self.link,
                name='Local',
                relay=False,
                dirname=self.directory_name,
                tracking_view=self.tracking_view)
            self.observation_view_base = ObservationView(
                self.link,
                name='Remote',
                relay=True,
                dirname=self.directory_name)
            self.system_monitor_view = SystemMonitorView(self.link)
            self.update_view = UpdateView(self.link,
                                          download_dir=swift_path,
                                          prompt=update,
                                          connection_info=self.connection_info)
            self.ins_view = INSView(self.link)
            self.mag_view = MagView(self.link)
            self.spectrum_analyzer_view = SpectrumAnalyzerView(self.link)
            settings_read_finished_functions.append(
                self.update_view.compare_versions)
            self.networking_view = SbpRelayView(self.link)
            self.json_logging = json_logging
            self.csv_logging = False
            self.first_json_press = True
            if json_logging:
                self._start_json_logging(override_filename)
                self.json_logging = True
            # we set timer interval to 1200 milliseconds because we expect a heartbeat each second
            self.timer_cancel = call_repeatedly(HEARTBEAT_CHECK_PERIOD_SECONDS,
                                                self.check_heartbeat)

            # Once we have received the settings, update device_serial with
            # the Swift serial number which will be displayed in the window
            # title. This callback will also update the header route as used
            # by the networking view.

            def update_serial():
                mfg_id = None
                try:
                    self.uuid = self.settings_view.settings['system_info'][
                        'uuid'].value
                    mfg_id = self.settings_view.settings['system_info'][
                        'serial_number'].value
                except KeyError:
                    pass
                if mfg_id:
                    self.device_serial = 'PK' + str(mfg_id)

            skip_settings_read = False
            if 'mode' in self.connection_info:
                if self.connection_info['mode'] == 'file':
                    skip_settings_read = True

            settings_read_finished_functions.append(update_serial)
            self.settings_view = SettingsView(self.link,
                                              settings_read_finished_functions,
                                              skip_read=skip_settings_read)
            self.update_view.settings = self.settings_view.settings
            self.python_console_env = {
                'send_message': self.link,
                'link': self.link,
            }
            self.python_console_env.update(
                self.tracking_view.python_console_cmds)
            self.python_console_env.update(
                self.solution_view.python_console_cmds)
            self.python_console_env.update(
                self.baseline_view.python_console_cmds)
            self.python_console_env.update(
                self.skyplot_view.python_console_cmds)
            self.python_console_env.update(
                self.observation_view.python_console_cmds)
            self.python_console_env.update(
                self.networking_view.python_console_cmds)
            self.python_console_env.update(
                self.system_monitor_view.python_console_cmds)
            self.python_console_env.update(
                self.update_view.python_console_cmds)
            self.python_console_env.update(self.ins_view.python_console_cmds)
            self.python_console_env.update(self.mag_view.python_console_cmds)
            self.python_console_env.update(
                self.settings_view.python_console_cmds)
            self.python_console_env.update(
                self.spectrum_analyzer_view.python_console_cmds)

        except:  # noqa
            import traceback
            traceback.print_exc()
            if self.error:
                os._exit(1)
Exemple #10
0
class TableFilterEditor(HasTraits):
    """ An editor that manages table filters.
    """

    #---------------------------------------------------------------------------
    #  Trait definitions:
    #---------------------------------------------------------------------------

    # TableEditor this editor is associated with
    editor = Instance(TableEditor)

    # The list of filters
    filters = List(TableFilter)

    # The list of available templates from which filters can be created
    templates = Property(List(TableFilter), depends_on='filters')

    # The currently selected filter template
    selected_template = Instance(TableFilter)

    # The currently selected filter
    selected_filter = Instance(TableFilter, allow_none=True)

    # The view to use for the current filter
    selected_filter_view = Property(depends_on='selected_filter')

    # Buttons for add/removing filters
    add_button = Button('New')
    remove_button = Button('Delete')

    # The default view for this editor
    view = View(Group(Group(Group(Item('add_button',
                                       enabled_when='selected_template'),
                                  Item('remove_button',
                                       enabled_when='len(templates) > 1 and ' \
                                           'selected_filter is not None'),
                                  orientation='horizontal',
                                  show_labels=False),
                            Label('Base filter for new filters:'),
                            Item('selected_template',
                                 editor=EnumEditor(name='templates')),
                            Item('selected_filter',
                                 style='custom',
                                 editor=EnumEditor(name='filters',
                                                   mode='list')),
                            show_labels=False),
                      Item('selected_filter',
                           width=0.75,
                           style='custom',
                           editor=InstanceEditor(view_name='selected_filter_view')),
                      id='TableFilterEditorSplit',
                      show_labels=False,
                      layout='split',
                      orientation='horizontal'),
                id='traitsui.qt4.table_editor.TableFilterEditor',
                buttons=[ 'OK', 'Cancel' ],
                kind='livemodal',
                resizable=True, width=800, height=400,
                title='Customize filters')

    #---------------------------------------------------------------------------
    #  Private methods:
    #---------------------------------------------------------------------------

    #-- Trait Property getter/setters ------------------------------------------

    @cached_property
    def _get_selected_filter_view(self):
        view = None
        if self.selected_filter:
            model = self.editor.model
            index = model.mapToSource(model.index(0, 0))
            if index.isValid():
                obj = self.editor.items()[index.row()]
            else:
                obj = None
            view = self.selected_filter.edit_view(obj)
        return view

    @cached_property
    def _get_templates(self):
        templates = [f for f in self.editor.factory.filters if f.template]
        templates.extend(self.filters)
        return templates

    #-- Trait Change Handlers --------------------------------------------------

    def _editor_changed(self):
        self.filters = [
            f.clone_traits() for f in self.editor.factory.filters
            if not f.template
        ]
        self.selected_template = self.templates[0]

    def _add_button_fired(self):
        """ Create a new filter based on the selected template and select it.
        """
        new_filter = self.selected_template.clone_traits()
        new_filter.template = False
        new_filter.name = new_filter._name = 'New filter'
        self.filters.append(new_filter)
        self.selected_filter = new_filter

    def _remove_button_fired(self):
        """ Delete the currently selected filter.
        """
        if self.selected_template == self.selected_filter:
            self.selected_template = self.templates[0]

        index = self.filters.index(self.selected_filter)
        del self.filters[index]
        if index < len(self.filters):
            self.selected_filter = self.filters[index]
        else:
            self.selected_filter = None

    @on_trait_change('selected_filter:name')
    def _update_filter_list(self):
        """ A hack to make the EnumEditor watching the list of filters refresh
            their text when the name of the selected filter changes.
        """
        filters = self.filters
        self.filters = []
        self.filters = filters
Exemple #11
0
    def init(self, parent):
        """Finishes initializing the editor by creating the underlying toolkit
        widget."""

        factory = self.factory
        self.columns = factory.columns[:]
        if factory.table_view_factory is not None:
            self.table_view = factory.table_view_factory(editor=self)
        if factory.source_model_factory is not None:
            self.source_model = factory.source_model_factory(editor=self)
        if factory.model_factory is not None:
            self.model = factory.model_factory(editor=self)

        # Create the table view and model
        self.model.setDynamicSortFilter(True)
        self.model.setSourceModel(self.source_model)
        self.table_view.setModel(self.model)

        # Create the vertical header context menu and connect to its signals
        self.header_menu = QtGui.QMenu(self.table_view)
        signal = QtCore.SIGNAL('triggered()')
        insertable = factory.row_factory is not None and not factory.auto_add
        if factory.editable:
            if insertable:
                action = self.header_menu.addAction('Insert new item')
                QtCore.QObject.connect(action, signal, self._on_context_insert)
            if factory.deletable:
                action = self.header_menu.addAction('Delete item')
                QtCore.QObject.connect(action, signal, self._on_context_remove)
        if factory.reorderable:
            if factory.editable and (insertable or factory.deletable):
                self.header_menu.addSeparator()
            self.header_menu_up = self.header_menu.addAction('Move item up')
            QtCore.QObject.connect(self.header_menu_up, signal,
                                   self._on_context_move_up)
            self.header_menu_down = self.header_menu.addAction(
                'Move item down')
            QtCore.QObject.connect(self.header_menu_down, signal,
                                   self._on_context_move_down)

        # Create the empty space context menu and connect its signals
        self.empty_menu = QtGui.QMenu(self.table_view)
        action = self.empty_menu.addAction('Add new item')
        QtCore.QObject.connect(action, signal, self._on_context_append)

        # When sorting is enabled, the first column is initially displayed with
        # the triangle indicating it is the sort index, even though no sorting
        # has actually been done. Sort here for UI/model consistency.
        if self.factory.sortable and not self.factory.reorderable:
            self.model.sort(0, QtCore.Qt.AscendingOrder)

        # Connect to the mode specific selection handler and select the first
        # row/column/cell. Do this before creating the edit_view to make sure
        # that it has a valid item to use when constructing its view.
        smodel = self.table_view.selectionModel()
        signal = QtCore.SIGNAL(
            'selectionChanged(QItemSelection, QItemSelection)')
        mode_slot = getattr(self, '_on_%s_selection' % factory.selection_mode)
        QtCore.QObject.connect(smodel, signal, mode_slot)
        self.table_view.setCurrentIndex(self.model.index(0, 0))

        # Create the toolbar if necessary
        if factory.show_toolbar and len(factory.filters) > 0:
            main_view = QtGui.QWidget()
            layout = QtGui.QVBoxLayout(main_view)
            layout.setContentsMargins(0, 0, 0, 0)
            self.toolbar_ui = self.edit_traits(
                parent=parent,
                kind='subpanel',
                view=View(Group(Item('filter{View}',
                                     editor=factory._filter_editor),
                                Item('filter_summary{Results}',
                                     style='readonly'),
                                spring,
                                orientation='horizontal'),
                          resizable=True))
            self.toolbar_ui.parent = self.ui
            layout.addWidget(self.toolbar_ui.control)
            layout.addWidget(self.table_view)
        else:
            main_view = self.table_view

        # Create auxillary editor and encompassing splitter if necessary
        mode = factory.selection_mode
        if (factory.edit_view == ' ') or not mode in ('row', 'rows'):
            self.control = main_view
        else:
            self.control = QtGui.QSplitter(QtCore.Qt.Vertical)
            self.control.setSizePolicy(QtGui.QSizePolicy.Expanding,
                                       QtGui.QSizePolicy.Expanding)
            self.control.addWidget(main_view)
            self.control.setStretchFactor(0, 2)

            # Create the row editor below the table view
            editor = InstanceEditor(view=factory.edit_view, kind='subpanel')
            self._ui = self.edit_traits(
                parent=self.control,
                kind='subpanel',
                view=View(Item('selected_row',
                               style='custom',
                               editor=editor,
                               show_label=False,
                               resizable=True,
                               width=factory.edit_view_width,
                               height=factory.edit_view_height),
                          resizable=True,
                          handler=factory.edit_view_handler))
            self._ui.parent = self.ui
            self.control.addWidget(self._ui.control)
            self.control.setStretchFactor(1, 1)

        # Connect to the click and double click handlers
        signal = QtCore.SIGNAL('clicked(QModelIndex)')
        QtCore.QObject.connect(self.table_view, signal, self._on_click)
        signal = QtCore.SIGNAL('doubleClicked(QModelIndex)')
        QtCore.QObject.connect(self.table_view, signal, self._on_dclick)

        # Make sure we listen for 'items' changes as well as complete list
        # replacements
        self.context_object.on_trait_change(self.update_editor,
                                            self.extended_name + '_items',
                                            dispatch='ui')

        # Listen for changes to traits on the objects in the list
        self.context_object.on_trait_change(self.refresh_editor,
                                            self.extended_name + '.-',
                                            dispatch='ui')

        # Listen for changes on column definitions
        self.on_trait_change(self._update_columns, 'columns', dispatch='ui')
        self.on_trait_change(self._update_columns,
                             'columns_items',
                             dispatch='ui')

        # Set up the required externally synchronized traits
        is_list = (mode in ('rows', 'columns', 'cells'))
        self.sync_value(factory.click, 'click', 'to')
        self.sync_value(factory.dclick, 'dclick', 'to')
        self.sync_value(factory.columns_name, 'columns', is_list=True)
        self.sync_value(factory.selected, 'selected', is_list=is_list)
        self.sync_value(factory.selected_indices,
                        'selected_indices',
                        is_list=is_list)
        self.sync_value(factory.filter_name, 'filter', 'from')
        self.sync_value(factory.filtered_indices, 'filtered_indices', 'to')
        self.sync_value(factory.update_filter_name, 'update_filter', 'from')

        self.auto_size = self.factory.auto_size

        # Initialize the ItemDelegates for each column
        self._update_columns()
class DataBlockTablePlot(HasTraits):

    # the ParametersTable holding the actual array
    dataset = Instance(DataBlockTable)

    # a local reference to the data array in the dataset, for convenience
    selected = Property(Any, depends_on='dataset.selected')

    # ArrayPlotData for plot
    plot_data = Instance(ArrayPlotData, ())

    # Chaco Plot instance
    plot = Instance(Plot)

    traits_view = View(
        VGroup(
            Item('dataset',
                 show_label=False,
                 style='custom',
                 editor=InstanceEditor()),
            Item('plot',
                 show_label=False,
                 editor=ComponentEditor(),
                 width=0.25,
                 height=0.35),
        ),
        width=0.60,
        height=0.60,
        resizable=True,
        title='Parameters Plot',
    )

    def cview(self):
        return View(
            VGroup(
                Item('dataset',
                     show_label=False,
                     style='custom',
                     editor=InstanceEditor()),
                Item('plot',
                     show_label=False,
                     editor=ComponentEditor(),
                     width=0.25,
                     height=0.35),
            ),
            width=0.60,
            height=0.60,
            resizable=True,
            title='Raw Data Plot',
        )

    @cached_property
    def _get_selected(self):
        return self.dataset.selected

    @on_trait_change('selected')
    def _data_source_change(self):
        """Handler for changes of ther selected table row
        """
        if self.selected is None:
            return

        x = np.linspace(0, \
                        len(self.selected['raw_data']), \
                        len(self.selected['raw_data']))

        self.plot_data.set_data('x', x)
        self.plot_data.set_data('y', self.selected['raw_data'])

    def _plot_data_default(self):
        """ This creates a list of ArrayPlotData instances,
        one for each species.
        """
        plot_data = ArrayPlotData()

        plot_data.set_data('x', [])
        plot_data.set_data('y', [])

        return plot_data

    def _plot_default(self):
        """ This creates the default value for the plot.
        """
        # create the main plot object
        plot = Plot(self.plot_data)

        plot.plot(('x', 'y'), type='line', name='raw data', color='lightgreen')

        # add the additional information
        plot.title = 'Row Data'
        plot.x_axis.title = 'Samples'
        plot.y_axis.title = 'Amplitude'

        # tools for basic interactivity
        plot.tools.append(PanTool(plot))
        plot.tools.append(ZoomTool(plot))
        plot.tools.append(DragZoom(plot, drag_button="right"))

        return plot
Exemple #13
0
class SourceWidget(Component):

    # The version of this class.  Used for persistence.
    __version__ = 0

    # The actual poly data source widget.
    widget = Instance(tvtk.ThreeDWidget, record=True)

    # Specifies the updation mode of the poly_data attribute.  There
    # are three modes: 1) 'interactive' -- the poly_data attribute is
    # updated as the widget is interacted with, 2) 'semi-interactive'
    # -- poly_data attribute is updated when the traits of the widget
    # change and when the widget interaction is complete, 3)
    # 'non-interactive' -- poly_data is updated only explicitly at
    # users request by calling `object.update_poly_data`.
    update_mode = Trait('interactive', TraitPrefixList(['interactive',
                                                        'semi-interactive',
                                                        'non-interactive']),
                        desc='the speed at which the poly data is updated')

    # A list of predefined glyph sources that can be used.
    widget_list = List(tvtk.Object, record=False)

    # The poly data that the widget manages.
    poly_data = Instance(tvtk.PolyData, args=())

    ########################################
    # Private traits.

    _first = Bool(True)
    _busy = Bool(False)
    _unpickling = Bool(False)

    ########################################
    # View related traits.

    view = View(Group(Item(name='widget', style='custom', resizable=True,
                           editor=InstanceEditor(name='widget_list')),
                      label='Source Widget',
                      show_labels=False,
                      ),
                resizable=True,
                )

    ######################################################################
    # `Base` interface
    ######################################################################
    def __get_pure_state__(self):
        d = super(SourceWidget, self).__get_pure_state__()
        for attr in ('poly_data', '_unpickling', '_first', '_busy'):
            d.pop(attr, None)
        return d

    def __set_pure_state__(self, state):
        self._unpickling = True
        # First create all the allowed widgets in the widget_list attr.
        handle_children_state(self.widget_list, state.widget_list)
        # Now set their state.
        set_state(self, state, first=['widget_list'], ignore=['*'])
        # Set the widget attr depending on value saved.
        m = [x.__class__.__name__ for x in self.widget_list]
        w_c_name = state.widget.__metadata__['class_name']
        w = self.widget = self.widget_list[m.index(w_c_name)]
        # Set the input.
        if len(self.inputs) > 0:
            w.input = self.inputs[0].outputs[0]
        # Fix for the point widget.
        if w_c_name == 'PointWidget':
            w.place_widget()
        # Set state of rest of the attributes ignoring the widget_list.
        set_state(self, state, ignore=['widget_list'])
        # Some widgets need some cajoling to get their setup right.
        w.update_traits()
        if w_c_name == 'PlaneWidget':
            w.origin = state.widget.origin
            w.normal = state.widget.normal
            w.update_placement()
            w.get_poly_data(self.poly_data)
        elif w_c_name == 'SphereWidget':
            # XXX: This hack is necessary because the sphere widget
            # does not update its poly data even when its ivars are
            # set (plus it does not have an update_placement method
            # which is a bug).  So we force this by creating a similar
            # sphere source and copy its output.
            s = tvtk.SphereSource(center=w.center, radius=w.radius,
                                  theta_resolution=w.theta_resolution,
                                  phi_resolution=w.phi_resolution,
                                  lat_long_tessellation=True)
            s.update()
            self.poly_data.shallow_copy(s.output)
        else:
            w.get_poly_data(self.poly_data)
        self._unpickling = False
        # Set the widgets trait so that the widget is rendered if needed.
        self.widgets = [w]

    ######################################################################
    # `Component` interface
    ######################################################################
    def setup_pipeline(self):
        """Override this method so that it *creates* the tvtk
        pipeline.

        This method is invoked when the object is initialized via
        `__init__`.  Note that at the time this method is called, the
        tvtk data pipeline will *not* yet be setup.  So upstream data
        will not be available.  The idea is that you simply create the
        basic objects and setup those parts of the pipeline not
        dependent on upstream sources and filters.  You should also
        set the `actors` attribute up at this point.
        """
        # Setup the glyphs.
        sources = [tvtk.SphereWidget(theta_resolution=8, phi_resolution=6),
                   tvtk.LineWidget(clamp_to_bounds=False),
                   tvtk.PlaneWidget(),
                   tvtk.PointWidget(outline=False, x_shadows=False,
                                    y_shadows=False, z_shadows=False),
                   ]
        self.widget_list = sources
        # The 'widgets' trait is set in the '_widget_changed' handler.
        self.widget = sources[0]

        for s in sources:
            self._connect(s)

    def update_pipeline(self):
        """Override this method so that it *updates* the tvtk pipeline
        when data upstream is known to have changed.

        This method is invoked (automatically) when any of the inputs
        sends a `pipeline_changed` event.
        """
        if len(self.inputs) == 0:
            return
        inp = self.inputs[0].outputs[0]
        w = self.widget
        w.input = inp

        if self._first:
            w.place_widget()
            self._first = False

        # If the dataset is effectively 2D switch to using the line
        # widget since that works best.
        b = inp.bounds
        l = [(b[1]-b[0]), (b[3]-b[2]), (b[5]-b[4])]
        max_l = max(l)
        for i, x in enumerate(l):
            if x/max_l < 1.0e-6:
                w = self.widget = self.widget_list[1]
                w.clamp_to_bounds = True
                w.align = ['z_axis', 'z_axis', 'y_axis'][i]
                break

        # Set our output.
        w.get_poly_data(self.poly_data)
        self.outputs = [self.poly_data]

        self.pipeline_changed = True

    def update_data(self):
        """Override this method so that it flushes the vtk pipeline if
        that is necessary.

        This method is invoked (automatically) when any of the inputs
        sends a `data_changed` event.
        """
        self.data_changed = True

    ######################################################################
    # `SourceWidget` interface
    ######################################################################
    def update_poly_data(self):
        self.widget.get_poly_data(self.poly_data)

    ######################################################################
    # Non-public traits.
    ######################################################################
    def _widget_changed(self, value):
        # If we are being unpickled do nothing.
        if self._unpickling:
            return
        if value not in self.widget_list:
            classes = [o.__class__ for o in self.widget_list]
            vc = value.__class__
            self._connect(value)
            if vc in classes:
                self.widget_list[classes.index(vc)] = value
            else:
                self.widget_list.append(value)

        recorder = self.recorder
        if recorder is not None:
            idx = self.widget_list.index(value)
            name = recorder.get_script_id(self)
            lhs = '%s.widget'%name
            rhs = '%s.widget_list[%d]'%(name, idx)
            recorder.record('%s = %s'%(lhs, rhs))

        if len(self.inputs) > 0:
            value.input = self.inputs[0].outputs[0]
            value.place_widget()

        value.on_trait_change(self.render)
        self.widgets = [value]

    def _update_mode_changed(self, value):
        if value in ['interactive', 'semi-interactive']:
            self.update_poly_data()
            self.render()

    def _on_interaction_event(self, obj, event):
        if (not self._busy) and (self.update_mode == 'interactive'):
            self._busy = True
            self.update_poly_data()
            self._busy = False

    def _on_widget_trait_changed(self):
        if (not self._busy) and (self.update_mode != 'non-interactive'):
            self._busy = True
            # This render call forces any changes to the trait to be
            # rendered only then will updating the poly data make
            # sense.
            self.render()
            self.update_poly_data()
            self._busy = False

    def _on_alignment_set(self):
        w = self.widget
        w.place_widget()
        w.update_traits()

    def _connect(self, obj):
        """Wires up all the event handlers."""
        obj.add_observer('InteractionEvent',
                         self._on_interaction_event)
        if isinstance(obj, tvtk.PlaneWidget):
            obj.on_trait_change(self._on_alignment_set, 'normal_to_x_axis')
            obj.on_trait_change(self._on_alignment_set, 'normal_to_y_axis')
            obj.on_trait_change(self._on_alignment_set, 'normal_to_z_axis')
        elif isinstance(obj, tvtk.LineWidget):
            obj.on_trait_change(self._on_alignment_set, 'align')

        # Setup the widgets colors.
        fg = (1,1,1)
        if self.scene is not None:
            fg = self.scene.foreground
        self._setup_widget_colors(obj, fg)

        obj.on_trait_change(self._on_widget_trait_changed)
        obj.on_trait_change(self.render)

    def _setup_widget_colors(self, widget, color):
        trait_names = widget.trait_names()
        props = [x for x in trait_names
                 if 'property' in x and 'selected' not in x]
        sel_props = [x for x in trait_names
                     if 'property' in x and 'selected' in x]
        for p in props:
            setattr(getattr(widget, p), 'color', color)
            setattr(getattr(widget, p), 'line_width', 2)
        for p in sel_props:
            # Set the selected color to 'red'.
            setattr(getattr(widget, p), 'color', (1,0,0))
            setattr(getattr(widget, p), 'line_width', 2)
        self.render()

    def _foreground_changed_for_scene(self, old, new):
        # Change the default color for the actor.
        for w in self.widget_list:
            self._setup_widget_colors(w, new)
        self.render()

    def _scene_changed(self, old, new):
        super(SourceWidget, self)._scene_changed(old, new)
        self._foreground_changed_for_scene(None, new.foreground)
Exemple #14
0
    def init(self, parent):
        """Finishes initializing the editor by creating the underlying toolkit
        widget."""

        factory = self.factory
        self.filter = factory.filter

        columns = factory.columns[:]
        if (len(columns) == 0) and (len(self.value) > 0):
            columns = [
                ObjectColumn(name=name)
                for name in self.value[0].editable_traits()
            ]
        self.columns = columns

        if factory.table_view_factory is not None:
            self.table_view = factory.table_view_factory(editor=self)
        if factory.source_model_factory is not None:
            self.source_model = factory.source_model_factory(editor=self)
        if factory.model_factory is not None:
            self.model = factory.model_factory(editor=self)

        # Create the table view and model
        self.model.setDynamicSortFilter(True)
        self.model.setSourceModel(self.source_model)
        self.table_view.setModel(self.model)

        # Create the vertical header context menu and connect to its signals
        self.header_menu = QtGui.QMenu(self.table_view)
        insertable = factory.row_factory is not None
        if factory.editable:
            if insertable:
                action = self.header_menu.addAction("Insert new item")
                action.triggered.connect(self._on_context_insert)
            if factory.deletable:
                action = self.header_menu.addAction("Delete item")
                action.triggered.connect(self._on_context_remove)
        if factory.reorderable:
            if factory.editable and (insertable or factory.deletable):
                self.header_menu.addSeparator()
            self.header_menu_up = self.header_menu.addAction("Move item up")
            self.header_menu_up.triggered.connect(self._on_context_move_up)
            self.header_menu_down = self.header_menu.addAction(
                "Move item down"
            )
            self.header_menu_down.triggered.connect(self._on_context_move_down)

        # Create the empty space context menu and connect its signals
        self.empty_menu = QtGui.QMenu(self.table_view)
        action = self.empty_menu.addAction("Add new item")
        action.triggered.connect(self._on_context_append)

        # When sorting is enabled, the first column is initially displayed with
        # the triangle indicating it is the sort index, even though no sorting
        # has actually been done. Sort here for UI/model consistency.
        if self.factory.sortable and not self.factory.reorderable:
            self.model.sort(0, QtCore.Qt.AscendingOrder)

        # Connect to the mode specific selection handler and select the first
        # row/column/cell. Do this before creating the edit_view to make sure
        # that it has a valid item to use when constructing its view.
        smodel = self.table_view.selectionModel()
        mode_slot = getattr(self, "_on_%s_selection" % factory.selection_mode)
        smodel.selectionChanged.connect(mode_slot)
        self.table_view.setCurrentIndex(self.model.index(0, 0))

        # Create the toolbar if necessary
        if factory.show_toolbar and len(factory.filters) > 0:
            main_view = QtGui.QWidget()
            layout = QtGui.QVBoxLayout(main_view)
            layout.setContentsMargins(0, 0, 0, 0)
            self.toolbar_ui = self.edit_traits(
                parent=parent,
                kind="subpanel",
                view=View(
                    Group(
                        Item("filter{View}", editor=factory._filter_editor),
                        Item("filter_summary{Results}", style="readonly"),
                        spring,
                        orientation="horizontal",
                    ),
                    resizable=True,
                ),
            )
            self.toolbar_ui.parent = self.ui
            layout.addWidget(self.toolbar_ui.control)
            layout.addWidget(self.table_view)
        else:
            main_view = self.table_view

        # Create auxiliary editor and encompassing splitter if necessary
        mode = factory.selection_mode
        if (factory.edit_view == " ") or mode not in {"row", "rows"}:
            self.control = main_view
        else:
            if factory.orientation == "horizontal":
                self.control = QtGui.QSplitter(QtCore.Qt.Horizontal)
            else:
                self.control = QtGui.QSplitter(QtCore.Qt.Vertical)
            self.control.setSizePolicy(
                QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding
            )
            self.control.addWidget(main_view)
            self.control.setStretchFactor(0, 2)

            # Create the row editor below the table view
            editor = InstanceEditor(view=factory.edit_view, kind="subpanel")
            self._ui = self.edit_traits(
                parent=self.control,
                kind="subpanel",
                view=View(
                    Item(
                        "selected_row",
                        style="custom",
                        editor=editor,
                        show_label=False,
                        resizable=True,
                        width=factory.edit_view_width,
                        height=factory.edit_view_height,
                    ),
                    resizable=True,
                    handler=factory.edit_view_handler,
                ),
            )
            self._ui.parent = self.ui
            self.control.addWidget(self._ui.control)
            self.control.setStretchFactor(1, 1)

        # Connect to the click and double click handlers
        self.table_view.clicked.connect(self._on_click)
        self.table_view.doubleClicked.connect(self._on_dclick)

        # Make sure we listen for 'items' changes as well as complete list
        # replacements
        self.context_object.on_trait_change(
            self.update_editor, self.extended_name + "_items", dispatch="ui"
        )

        # Listen for changes to traits on the objects in the list
        self.context_object.on_trait_change(
            self.refresh_editor, self.extended_name + ".-", dispatch="ui"
        )

        # Listen for changes on column definitions
        self.on_trait_change(self._update_columns, "columns", dispatch="ui")
        self.on_trait_change(
            self._update_columns, "columns_items", dispatch="ui"
        )

        # Set up the required externally synchronized traits
        is_list = mode in ("rows", "columns", "cells")
        self.sync_value(factory.click, "click", "to")
        self.sync_value(factory.dclick, "dclick", "to")
        self.sync_value(factory.columns_name, "columns", is_list=True)
        self.sync_value(factory.selected, "selected", is_list=is_list)
        self.sync_value(
            factory.selected_indices, "selected_indices", is_list=is_list
        )
        self.sync_value(factory.filter_name, "filter", "from")
        self.sync_value(factory.filtered_indices, "filtered_indices", "to")
        self.sync_value(factory.update_filter_name, "update_filter", "from")

        self.auto_size = self.factory.auto_size

        # Initialize the ItemDelegates for each column
        self._update_columns()
Exemple #15
0
    def traits_view(self):
        cali_grp = VGroup(UItem(
            'tray_manager.calibrate',
            enabled_when='stage_manager.stage_map_name',
            editor=ButtonEditor(label_value='tray_manager.calibration_step')),
                          HGroup(
                              Readonly('tray_manager.x', format_str='%0.3f'),
                              Readonly('tray_manager.y', format_str='%0.3f')),
                          Readonly('tray_manager.rotation',
                                   format_str='%0.3f'),
                          Readonly('tray_manager.scale', format_str='%0.4f'),
                          Readonly('tray_manager.error', format_str='%0.2f'),
                          UItem('tray_manager.calibrator',
                                style='custom',
                                editor=InstanceEditor()),
                          CustomLabel('tray_manager.calibration_help',
                                      color='green',
                                      height=75,
                                      width=300),
                          show_border=True,
                          label='Calibration')

        # c_grp = VGroup(HGroup(Item('setpoint'),
        #                       UItem('setpoint_readback')),
        #                UItem('setpoint_readback',
        #                      enabled_when='0',
        #                      editor=RangeEditor(mode='slider',
        #                                         low_name='setpoint_readback_min',
        #                                         high_name='setpoint_readback_max')),
        #
        #                label='Controller', show_border=True)
        c_grp = VGroup(Item('setpoint'),
                       VGroup(UItem('setpoint_readback', editor=LCDEditor())),
                       label='Controller',
                       show_border=True)
        d_grp = VGroup(
            Item('stage_manager.calibrated_position_entry', label='Hole'),
            Item('stage_manager.sample_linear_holder.position',
                 editor=RangeEditor(
                     mode='slider',
                     low_name='stage_manager.sample_linear_holder.min_value',
                     high_name='stage_manager.sample_linear_holder.max_value',
                 )),
            HGroup(
                UItem('pane.dump_sample_button',
                      tooltip='Complete sample dumping procedure'),
                icon_button_editor('pane.funnel_up_button',
                                   'arrow_up',
                                   enabled_when='pane.funnel_up_enabled',
                                   editor_kw={'label': 'Funnel Up'}),
                icon_button_editor('pane.funnel_down_button',
                                   'arrow_down',
                                   enabled_when='pane.funnel_down_enabled',
                                   editor_kw={'label': 'Funnel Down'}),
                spring),
            UItem('dumper_canvas', editor=ComponentEditor()),
            # UItem('pane.lower_funnel_button', enabled_when='stage_manager.funnel.min_limit'),
            # UItem('pane.raise_funnel_button', enabled_when='stage_manager.funnel.max_limit'),
            label='Dumper',
            show_border=True)

        v = View(VGroup(c_grp, HGroup(Tabbed(d_grp, cali_grp))))
        return v
Exemple #16
0
class Text(Module):
    # The version of this class.  Used for persistence.
    __version__ = 0

    # The tvtk TextActor.
    actor = Instance(tvtk.TextActor, allow_none=False, record=True)

    # The property of the axes (color etc.).
    property = Property(record=True)

    # The text to be displayed.  Note that this should really be `Str`
    # but wxGTK only returns unicode.
    text = Str('Text', desc='the text to be displayed')

    # The x-position of this actor.
    x_position = Float(0.0, desc='the x-coordinate of the text')

    # The y-position of this actor.
    y_position = Float(0.0, desc='the y-coordinate of the text')

    # The z-position of this actor.
    z_position = Float(0.0, desc='the z-coordinate of the text')

    # Shadow the positions as ranges for 2D. Simply using a RangeEditor
    # does not work as it resets the 3D positions to 1 when the dialog is
    # loaded.
    _x_position_2d = Range(0.,
                           1.,
                           0.,
                           enter_set=True,
                           auto_set=False,
                           desc='the x-coordinate of the text')
    _y_position_2d = Range(0.,
                           1.,
                           0.,
                           enter_set=True,
                           auto_set=False,
                           desc='the y-coordinate of the text')

    # 3D position
    position_in_3d = Bool(
        False,
        desc='whether the position of the object is given in 2D or in 3D')

    # The width of the text.
    width = Range(0.0,
                  1.0,
                  0.4,
                  enter_set=True,
                  auto_set=False,
                  desc='the width of the text as a fraction of the viewport')

    input_info = PipelineInfo(datasets=['any'],
                              attribute_types=['any'],
                              attributes=['any'])

    ########################################
    # The view of this object.

    if VTK_VER > '5.1':
        _text_actor_group = Group(Item(name='visibility'),
                                  Item(name='text_scale_mode'),
                                  Item(name='alignment_point'),
                                  Item(name='minimum_size'),
                                  Item(name='maximum_line_height'),
                                  show_border=True,
                                  label='Text Actor')
    else:
        _text_actor_group = Group(Item(name='visibility'),
                                  Item(name='scaled_text'),
                                  Item(name='alignment_point'),
                                  Item(name='minimum_size'),
                                  Item(name='maximum_line_height'),
                                  show_border=True,
                                  label='Text Actor')

    _position_group_2d = Group(Item(name='_x_position_2d', label='X position'),
                               Item(name='_y_position_2d', label='Y position'),
                               visible_when='not position_in_3d')

    _position_group_3d = Group(Item(name='x_position', label='X',
                                    springy=True),
                               Item(name='y_position', label='Y',
                                    springy=True),
                               Item(name='z_position', label='Z',
                                    springy=True),
                               show_border=True,
                               label='Position',
                               orientation='horizontal',
                               visible_when='position_in_3d')

    view = View(Group(Group(Item(name='text'),
                            Item(name='position_in_3d'),
                            _position_group_2d,
                            _position_group_3d,
                            Item(name='width',
                                 enabled_when='object.actor.scaled_text'),
                            ),
                      Group(Item(name='actor', style='custom',
                                 editor=\
                                 InstanceEditor(view=View(_text_actor_group))
                                 ),
                            show_labels=False),
                      label='TextActor',
                      show_labels=False
                      ),
                Group(Item(name='_property', style='custom', resizable=True),
                      label='TextProperty',
                      show_labels=False),
                )

    ########################################
    # Private traits.
    _updating = Bool(False)
    _property = Instance(tvtk.TextProperty)

    ######################################################################
    # `object` interface
    ######################################################################
    def __set_pure_state__(self, state):
        self._updating = True
        state_pickler.set_state(self,
                                state,
                                first=['actor'],
                                ignore=['_updating'])
        self._updating = False

    ######################################################################
    # `Module` interface
    ######################################################################
    def setup_pipeline(self):
        """Override this method so that it *creates* the tvtk
        pipeline.

        This method is invoked when the object is initialized via
        `__init__`.  Note that at the time this method is called, the
        tvtk data pipeline will *not* yet be setup.  So upstream data
        will not be available.  The idea is that you simply create the
        basic objects and setup those parts of the pipeline not
        dependent on upstream sources and filters.  You should also
        set the `actors` attribute up at this point.
        """
        actor = self.actor = tvtk.TextActor(input=str(self.text))
        if VTK_VER > '5.1':
            actor.trait_set(text_scale_mode='prop', width=0.4, height=1.0)
        else:
            actor.trait_set(scaled_text=True, width=0.4, height=1.0)

        c = actor.position_coordinate
        c.trait_set(coordinate_system='normalized_viewport',
                    value=(self.x_position, self.y_position, 0.0))
        c = actor.position2_coordinate
        c.trait_set(coordinate_system='normalized_viewport')

        self._property.opacity = 1.0

        self._text_changed(self.text)
        self._width_changed(self.width)
        self._shadow_positions(True)

    def update_pipeline(self):
        """Override this method so that it *updates* the tvtk pipeline
        when data upstream is known to have changed.

        This method is invoked (automatically) when any of the inputs
        sends a `pipeline_changed` event.
        """
        self.pipeline_changed = True

    def update_data(self):
        """Override this method so that it flushes the vtk pipeline if
        that is necessary.

        This method is invoked (automatically) when any of the inputs
        sends a `data_changed` event.
        """
        # Just set data_changed, the component should do the rest.
        self.data_changed = True

    ######################################################################
    # Non-public interface
    ######################################################################
    def _text_changed(self, value):
        actor = self.actor
        if actor is None:
            return
        if self._updating:
            return
        actor.input = str(value)
        self.render()

    def _shadow_positions(self, value):
        self.sync_trait('x_position',
                        self,
                        '_x_position_2d',
                        remove=(not value))
        self.sync_trait('y_position',
                        self,
                        '_y_position_2d',
                        remove=(not value))
        if not value:
            self._x_position_2d = self.x_position
            self._y_position_2d = self.y_position

    def _position_in_3d_changed(self, value):
        if value:
            self.actor.position_coordinate.coordinate_system = 'world'
            self.actor.position2_coordinate.coordinate_system = 'world'
        else:
            self.actor.position2_coordinate.coordinate_system=\
                                            'normalized_viewport'
            self.actor.position_coordinate.coordinate_system=\
                                            'normalized_viewport'
            x = self.x_position
            y = self.y_position
            if x < 0:
                x = 0
            elif x > 1:
                x = 1
            if y < 0:
                y = 0
            elif y > 1:
                y = 1
            self.trait_set(x_position=x,
                           y_position=y,
                           trait_change_notify=False)
        self._shadow_positions(not value)
        self._change_position()
        self.actor._width_changed(self.width, self.width)
        self.pipeline_changed = True

    def _change_position(self):
        """ Callback for _x_position, _y_position, and z_position.
        """
        actor = self.actor
        if actor is None:
            return
        if self._updating:
            return
        x = self.x_position
        y = self.y_position
        z = self.z_position
        if self.position_in_3d:
            actor.position_coordinate.value = x, y, z
        else:
            actor.position = x, y
        self.render()

    _x_position_changed = _change_position

    _y_position_changed = _change_position

    _z_position_changed = _change_position

    def _width_changed(self, value):
        actor = self.actor
        if actor is None:
            return
        if self._updating:
            return
        actor.width = value
        self.render()

    def _update_traits(self):
        self._updating = True
        try:
            actor = self.actor
            self.text = actor.input
            pos = actor.position
            self.x_position, self.y_position = pos
            self.width = actor.width
        finally:
            self._updating = False

    def _get_property(self):
        return self._property

    def _actor_changed(self, old, new):
        if old is not None:
            for obj in (old, self._property):
                obj.on_trait_change(self.render, remove=True)
            old.on_trait_change(self._update_traits, remove=True)

        self._property = new.text_property
        for obj in (new, self._property):
            obj.on_trait_change(self.render)
        new.on_trait_change(self._update_traits)

        self.actors = [new]
        self.render()

    def _foreground_changed_for_scene(self, old, new):
        # Change the default color for the actor.
        self.property.color = new
        self.render()

    def _scene_changed(self, old, new):
        super(Text, self)._scene_changed(old, new)
        self._foreground_changed_for_scene(None, new.foreground)
Exemple #17
0
class SceneModel(TVTKScene):

    ########################################
    # TVTKScene traits.

    light_manager = Property

    picker = Property

    ########################################
    # SceneModel traits.

    # A convenient dictionary based interface to add/remove actors and widgets.
    # This is similar to the interface provided for the ActorEditor.
    actor_map = Dict()

    # This is used primarily to implement the add_actor/remove_actor methods.
    actor_list = List()

    # The actual scene being edited.
    scene_editor = Instance(TVTKScene)

    do_render = Event()

    # Fired when this is activated.
    activated = Event()

    # Fired when this widget is closed.
    closing = Event()

    # This exists just to mirror the TVTKWindow api.
    scene = Property

    ###################################
    # View related traits.

    # Render_window's view.
    _stereo_view = Group(Item(name='stereo_render'),
                         Item(name='stereo_type'),
                         show_border=True,
                         label='Stereo rendering',
                         )

    # The default view of this object.
    default_view = View(Group(
                            Group(Item(name='background'),
                                  Item(name='foreground'),
                                  Item(name='parallel_projection'),
                                  Item(name='disable_render'),
                                  Item(name='off_screen_rendering'),
                                  Item(name='jpeg_quality'),
                                  Item(name='jpeg_progressive'),
                                  Item(name='magnification'),
                                  Item(name='anti_aliasing_frames'),
                                  ),
                            Group(Item(name='render_window',
                                       style='custom',
                                       visible_when='object.stereo',
                                       editor=InstanceEditor(view=View(_stereo_view)),                                       show_label=False),
                                  ),
                            label='Scene'),
                        Group(Item(name='light_manager',
                                   style='custom',
                                   editor=InstanceEditor(),
                                   show_label=False),
                                   label='Lights')
                        )

    ###################################
    # Private traits.

    # Used by the editor to determine if the widget was enabled or not.
    enabled_info = Dict()

    def __init__(self, parent=None, **traits):
        """ Initializes the object. """
        # Base class constructor.  We call TVTKScene's super here on purpose.
        # Calling TVTKScene's init will create a new window which we do not
        # want.
        super(TVTKScene, self).__init__(**traits)
        self.control = None

    ######################################################################
    # TVTKScene API.
    ######################################################################
    def render(self):
        """ Force the scene to be rendered. Nothing is done if the
        `disable_render` trait is set to True."""

        self.do_render = True

    def add_actors(self, actors):
        """ Adds a single actor or a tuple or list of actors to the
        renderer."""
        if hasattr(actors, '__iter__'):
            self.actor_list.extend(actors)
        else:
            self.actor_list.append(actors)

    def remove_actors(self, actors):
        """ Removes a single actor or a tuple or list of actors from
        the renderer."""
        my_actors = self.actor_list
        if hasattr(actors, '__iter__'):
            for actor in actors:
                my_actors.remove(actor)
        else:
            my_actors.remove(actors)

    # Conevenience methods.
    add_actor = add_actors
    remove_actor = remove_actors

    def add_widgets(self, widgets, enabled=True):
        """Adds widgets to the renderer.
        """
        if not hasattr(widgets, '__iter__'):
            widgets = [widgets]
        for widget in widgets:
            self.enabled_info[widget] = enabled
        self.add_actors(widgets)

    def remove_widgets(self, widgets):
        """Removes widgets from the renderer."""
        if not hasattr(widgets, '__iter__'):
            widgets = [widgets]
        self.remove_actors(widgets)
        for widget in widgets:
            del self.enabled_info[widget]

    def reset_zoom(self):
        """Reset the camera so everything in the scene fits."""
        if self.scene_editor is not None:
            self.scene_editor.reset_zoom()

    def save(self, file_name, size=None, **kw_args):
        """Saves rendered scene to one of several image formats
        depending on the specified extension of the filename.

        If an additional size (2-tuple) argument is passed the window
        is resized to the specified size in order to produce a
        suitably sized output image.  Please note that when the window
        is resized, the window may be obscured by other widgets and
        the camera zoom is not reset which is likely to produce an
        image that does not reflect what is seen on screen.

        Any extra keyword arguments are passed along to the respective
        image format's save method.
        """
        self._check_scene_editor()
        self.scene_editor.save(file_name, size, **kw_args)

    def save_ps(self, file_name):
        """Saves the rendered scene to a rasterized PostScript image.
        For vector graphics use the save_gl2ps method."""
        self._check_scene_editor()
        self.scene_editor.save_ps(file_name)

    def save_bmp(self, file_name):
        """Save to a BMP image file."""
        self._check_scene_editor()
        self.scene_editor.save_bmp(file_name)

    def save_tiff(self, file_name):
        """Save to a TIFF image file."""
        self._check_scene_editor()
        self.scene_editor.save_tiff(file_name)

    def save_png(self, file_name):
        """Save to a PNG image file."""
        self._check_scene_editor()
        self.scene_editor.save_png(file_name)

    def save_jpg(self, file_name, quality=None, progressive=None):
        """Arguments: file_name if passed will be used, quality is the
        quality of the JPEG(10-100) are valid, the progressive
        arguments toggles progressive jpegs."""
        self._check_scene_editor()
        self.scene_editor.save_jpg(file_name, quality, progressive)

    def save_iv(self, file_name):
        """Save to an OpenInventor file."""
        self._check_scene_editor()
        self.scene_editor.save_iv(file_name)

    def save_vrml(self, file_name):
        """Save to a VRML file."""
        self._check_scene_editor()
        self.scene_editor.save_vrml(file_name)

    def save_oogl(self, file_name):
        """Saves the scene to a Geomview OOGL file. Requires VTK 4 to
        work."""
        self._check_scene_editor()
        self.scene_editor.save_oogl(file_name)

    def save_rib(self, file_name, bg=0, resolution=None, resfactor=1.0):
        """Save scene to a RenderMan RIB file.

        Keyword Arguments:

        file_name -- File name to save to.

        bg -- Optional background option.  If 0 then no background is
        saved.  If non-None then a background is saved.  If left alone
        (defaults to None) it will result in a pop-up window asking
        for yes/no.

        resolution -- Specify the resolution of the generated image in
        the form of a tuple (nx, ny).

        resfactor -- The resolution factor which scales the resolution.
        """
        self._check_scene_editor()
        self.scene_editor.save_rib(file_name, bg, resolution, resfactor)

    def save_wavefront(self, file_name):
        """Save scene to a Wavefront OBJ file.  Two files are
        generated.  One with a .obj extension and another with a .mtl
        extension which contains the material proerties.

        Keyword Arguments:

        file_name -- File name to save to
        """
        self._check_scene_editor()
        self.scene_editor.save_wavefront(file_name)

    def save_gl2ps(self, file_name, exp=None):
        """Save scene to a vector PostScript/EPS/PDF/TeX file using
        GL2PS.  If you choose to use a TeX file then note that only
        the text output is saved to the file.  You will need to save
        the graphics separately.

        Keyword Arguments:

        file_name -- File name to save to.

        exp -- Optionally configured vtkGL2PSExporter object.
        Defaults to None and this will use the default settings with
        the output file type chosen based on the extention of the file
        name.
        """
        self._check_scene_editor()
        self.scene_editor.save_gl2ps(file_name, exp)

    def get_size(self):
        """Return size of the render window."""
        self._check_scene_editor()
        return self.scene_editor.get_size()

    def set_size(self, size):
        """Set the size of the window."""
        self._check_scene_editor()
        self.scene_editor.set_size(size)

    def _update_view(self, x, y, z, vx, vy, vz):
        """Used internally to set the view."""
        if self.scene_editor is not None:
            self.scene_editor._update_view(x, y, z, vx, vy, vz)

    def _check_scene_editor(self):
        if self.scene_editor is None:
            msg = """
            This method requires that there be an active scene editor.
            To do this, you will typically need to invoke::
              object.edit_traits()
            where object is the object that contains the SceneModel.
            """
            raise SceneModelError(msg)

    def _scene_editor_changed(self, old, new):
        if new is None:
            self._renderer = None
            self._renwin = None
            self._interactor = None
        else:
            self._renderer = new._renderer
            self._renwin = new._renwin
            self._interactor = new._interactor

    def _get_picker(self):
        """Getter for the picker."""
        se = self.scene_editor
        if se is not None and hasattr(se, 'picker'):
            return se.picker
        return None

    def _get_light_manager(self):
        """Getter for the light manager."""
        se = self.scene_editor
        if se is not None:
            return se.light_manager
        return None

    ######################################################################
    # SceneModel API.
    ######################################################################
    def _get_scene(self):
        """Getter for the scene property."""
        return self
class UpdateView(HasTraits):

    piksi_stm_vers = String('Waiting for Piksi to send settings...',
                            width=COLUMN_WIDTH)
    newest_stm_vers = String('Downloading Newest Firmware info...')
    piksi_nap_vers = String('Waiting for Piksi to send settings...')
    newest_nap_vers = String('Downloading Newest Firmware info...')
    local_console_vers = String('v' + CONSOLE_VERSION)
    newest_console_vers = String('Downloading Newest Console info...')

    erase_stm = Bool(True)
    erase_en = Bool(True)

    update_stm_firmware = Button(label='Update STM')
    update_nap_firmware = Button(label='Update NAP')
    update_full_firmware = Button(label='Update Piksi STM and NAP Firmware')

    updating = Bool(False)
    update_stm_en = Bool(False)
    update_nap_en = Bool(False)
    update_en = Bool(False)

    download_firmware = Button(label='Download Newest Firmware Files')
    download_stm = Button(label='Download', height=HT)
    download_nap = Button(label='Download', height=HT)
    downloading = Bool(False)
    download_fw_en = Bool(True)

    stm_fw = Instance(IntelHexFileDialog)
    nap_fw = Instance(IntelHexFileDialog)

    stream = Instance(OutputStream)

    view = View(
      VGroup(
        HGroup(
          VGroup(
            Item('piksi_stm_vers', label='Current', resizable=True),
            Item('newest_stm_vers', label='Latest', resizable=True),
            Item('stm_fw', style='custom', show_label=True, \
                 label="Local File", enabled_when='download_fw_en'),
            HGroup(Item('update_stm_firmware', show_label=False, \
                       enabled_when='update_stm_en'),
                  Item('erase_stm', label='Erase STM flash\n(recommended)', \
                        enabled_when='erase_en', show_label=True)),
            show_border=True, label="STM Firmware Version"
          ),
          VGroup(
            Item('piksi_nap_vers', label='Current', resizable=True),
            Item('newest_nap_vers', label='Latest', resizable=True),
            Item('nap_fw', style='custom', show_label=True, \
                 label="Local File", enabled_when='download_fw_en'),
            HGroup(Item('update_nap_firmware', show_label=False, \
                        enabled_when='update_nap_en'),
                   Item(width=50, label="                  ")),
            show_border=True, label="NAP Firmware Version"
            ),
          VGroup(
            Item('local_console_vers', label='Current', resizable=True),
            Item('newest_console_vers', label='Latest'),
            label="Piksi Console Version", show_border=True),
            ),
        UItem('download_firmware', enabled_when='download_fw_en'),
        UItem('update_full_firmware', enabled_when='update_en'),
        Item(
          'stream',
          style='custom',
          editor=InstanceEditor(),
          show_label=False,
        ),
      )
    )

    def __init__(self, link, prompt=True):
        """
    Traits tab with UI for updating Piksi firmware.

    Parameters
    ----------
    link : sbp.client.handler.Handler
      Link for SBP transfer to/from Piksi.
    prompt : bool
      Prompt user to update console/firmware if out of date.
    """
        self.link = link
        self.settings = {}
        self.prompt = prompt
        self.python_console_cmds = {'update': self}
        self.update_dl = None
        self.erase_en = True
        self.stm_fw = IntelHexFileDialog('STM')
        self.stm_fw.on_trait_change(self._manage_enables, 'status')
        self.nap_fw = IntelHexFileDialog('M25')
        self.nap_fw.on_trait_change(self._manage_enables, 'status')
        self.stream = OutputStream()
        self.get_latest_version_info()

    def _manage_enables(self):
        """ Manages whether traits widgets are enabled in the UI or not. """
        if self.updating == True or self.downloading == True:
            self.update_stm_en = False
            self.update_nap_en = False
            self.update_en = False
            self.download_fw_en = False
            self.erase_en = False
        else:
            self.download_fw_en = True
            self.erase_en = True
            if self.stm_fw.ihx is not None:
                self.update_stm_en = True
            else:
                self.update_stm_en = False
                self.update_en = False
            if self.nap_fw.ihx is not None:
                self.update_nap_en = True
            else:
                self.update_nap_en = False
                self.update_en = False
            if self.nap_fw.ihx is not None and self.stm_fw.ihx is not None:
                self.update_en = True

    def _updating_changed(self):
        """ Handles self.updating trait being changed. """
        self._manage_enables()

    def _downloading_changed(self):
        """ Handles self.downloading trait being changed. """
        self._manage_enables()

    def _write(self, text):
        """
    Stream style write function. Allows flashing debugging messages to be
    routed to embedded text console.

    Parameters
    ----------
    text : string
      Text to be written to screen.
    """
        self.stream.write(text)
        self.stream.write('\n')
        self.stream.flush()

    def _update_stm_firmware_fired(self):
        """
    Handle update_stm_firmware button. Starts thread so as not to block the GUI
    thread.
    """
        try:
            if self._firmware_update_thread.is_alive():
                return
        except AttributeError:
            pass

        self._firmware_update_thread = Thread(
            target=self.manage_firmware_updates, args=("STM", ))
        self._firmware_update_thread.start()

    def _update_nap_firmware_fired(self):
        """
    Handle update_nap_firmware button. Starts thread so as not to block the GUI
    thread.
    """
        try:
            if self._firmware_update_thread.is_alive():
                return
        except AttributeError:
            pass

        self._firmware_update_thread = Thread(
            target=self.manage_firmware_updates, args=("M25", ))
        self._firmware_update_thread.start()

    def _update_full_firmware_fired(self):
        """
    Handle update_full_firmware button. Starts thread so as not to block the GUI
    thread.
    """
        try:
            if self._firmware_update_thread.is_alive():
                return
        except AttributeError:
            pass

        self._firmware_update_thread = Thread(
            target=self.manage_firmware_updates, args=("ALL", ))
        self._firmware_update_thread.start()

    def _download_firmware(self):
        """ Download latest firmware from swiftnav.com. """
        self._write('')

        # Check that we received the index file from the website.
        if self.update_dl == None:
            self._write("Error: Can't download firmware files")
            return

        self.downloading = True

        status = 'Downloading Newest Firmware...'
        self.nap_fw.clear(status)
        self.stm_fw.clear(status)
        self._write(status)

        # Get firmware files from Swift Nav's website, save to disk, and load.
        try:
            self._write('Downloading Newest NAP firmware')
            filepath = self.update_dl.download_nap_firmware()
            self._write('Saved file to %s' % filepath)
            self.nap_fw.load_ihx(filepath)
        except AttributeError:
            self.nap_fw.clear("Error downloading firmware")
            self._write(
                "Error downloading firmware: index file not downloaded yet")
        except KeyError:
            self.nap_fw.clear("Error downloading firmware")
            self._write("Error downloading firmware: URL not present in index")
        except URLError:
            self.nap_fw.clear("Error downloading firmware")
            self._write(
                "Error: Failed to download latest NAP firmware from Swift Navigation's website"
            )

        try:
            self._write('Downloading Newest STM firmware')
            filepath = self.update_dl.download_stm_firmware()
            self._write('Saved file to %s' % filepath)
            self.stm_fw.load_ihx(filepath)
        except AttributeError:
            self.stm_fw.clear("Error downloading firmware")
            self._write(
                "Error downloading firmware: index file not downloaded yet")
        except KeyError:
            self.stm_fw.clear("Error downloading firmware")
            self._write("Error downloading firmware: URL not present in index")
        except URLError:
            self.stm_fw.clear("Error downloading firmware")
            self._write(
                "Error: Failed to download latest STM firmware from Swift Navigation's website"
            )

        self.downloading = False

    def _download_firmware_fired(self):
        """
    Handle download_firmware button. Starts thread so as not to block the GUI
    thread.
    """
        try:
            if self._download_firmware_thread.is_alive():
                return
        except AttributeError:
            pass

        self._download_firmware_thread = Thread(target=self._download_firmware)
        self._download_firmware_thread.start()

    def compare_versions(self):
        """
    To be called after latest Piksi firmware info has been received from
    device, to decide if current firmware on Piksi is out of date. Starts a
    thread so as not to block GUI thread.
    """
        try:
            if self._compare_versions_thread.is_alive():
                return
        except AttributeError:
            pass

        self._compare_versions_thread = Thread(target=self._compare_versions)
        self._compare_versions_thread.start()

    def _compare_versions(self):
        """
    Compares version info between received firmware version / current console
    and firmware / console info from website to decide if current firmware or
    console is out of date. Prompt user to update if so.
    """
        # Check that settings received from Piksi contain FW versions.
        try:
            self.piksi_stm_vers = \
              self.settings['system_info']['firmware_version'].value
            self.piksi_nap_vers = \
              self.settings['system_info']['nap_version'].value
        except KeyError:
            self._write(
                "\nError: Settings received from Piksi don't contain firmware version keys. Please contact Swift Navigation.\n"
            )
            return

        # Check that we received the index file from the website.
        if self.update_dl == None:
            self._write(
                "Error: No website index to use to compare versions with local firmware"
            )
            return

        # Check if console is out of date and notify user if so.
        if self.prompt:
            local_console_version = parse_version(CONSOLE_VERSION)
            remote_console_version = parse_version(self.newest_console_vers)
            self.console_outdated = remote_console_version > local_console_version

            if self.console_outdated:
                console_outdated_prompt = \
                    prompt.CallbackPrompt(
                                          title="Piksi Console Outdated",
                                          actions=[prompt.close_button],
                                         )

                console_outdated_prompt.text = \
                    "Your Piksi Console is out of date and may be incompatible\n" + \
                    "with current firmware. We highly recommend upgrading to\n" + \
                    "ensure proper behavior.\n\n" + \
                    "Please visit http://downloads.swiftnav.com to\n" + \
                    "download the newest version.\n\n" + \
                    "Local Console Version :\n\t" + \
                        "v" + CONSOLE_VERSION + \
                    "\nNewest Console Version :\n\t" + \
                        self.update_dl.index['piksi_v2.3.1']['console']['version'] + "\n"

                console_outdated_prompt.run()

        # For timing aesthetics between windows popping up.
        sleep(0.5)

        # Check if firmware is out of date and notify user if so.
        if self.prompt:
            local_stm_version = parse_version(
                self.settings['system_info']['firmware_version'].value)
            remote_stm_version = parse_version(self.newest_stm_vers)

            local_nap_version = parse_version(
                self.settings['system_info']['nap_version'].value)
            remote_nap_version = parse_version(self.newest_nap_vers)

            self.fw_outdated = remote_nap_version > local_nap_version or \
                               remote_stm_version > local_stm_version

            if self.fw_outdated:
                fw_update_prompt = \
                    prompt.CallbackPrompt(
                                          title='Firmware Update',
                                          actions=[prompt.close_button]
                                         )

                fw_update_prompt.text = \
                    "New Piksi firmware available.\n\n" + \
                    "Please use the Firmware Update tab to update.\n\n" + \
                    "Newest STM Version :\n\t%s\n\n" % \
                        self.update_dl.index['piksi_v2.3.1']['stm_fw']['version'] + \
                    "Newest SwiftNAP Version :\n\t%s\n\n" % \
                        self.update_dl.index['piksi_v2.3.1']['nap_fw']['version']

                fw_update_prompt.run()

    def get_latest_version_info(self):
        """
    Get latest firmware / console version from website. Starts thread so as not
    to block the GUI thread.
    """
        try:
            if self._get_latest_version_info_thread.is_alive():
                return
        except AttributeError:
            pass

        self._get_latest_version_info_thread = Thread(
            target=self._get_latest_version_info)
        self._get_latest_version_info_thread.start()

    def _get_latest_version_info(self):
        """ Get latest firmware / console version from website. """
        try:
            self.update_dl = UpdateDownloader()
        except URLError:
            self._write(
                "\nError: Failed to download latest file index from Swift Navigation's website. Please visit our website to check that you're running the latest Piksi firmware and Piksi console.\n"
            )
            return

        # Make sure index contains all keys we are interested in.
        try:
            self.newest_stm_vers = self.update_dl.index['piksi_v2.3.1'][
                'stm_fw']['version']
            self.newest_nap_vers = self.update_dl.index['piksi_v2.3.1'][
                'nap_fw']['version']
            self.newest_console_vers = self.update_dl.index['piksi_v2.3.1'][
                'console']['version']
        except KeyError:
            self._write(
                "\nError: Index downloaded from Swift Navigation's website (%s) doesn't contain all keys. Please contact Swift Navigation.\n"
                % INDEX_URL)
            return

    def manage_stm_firmware_update(self):
        # Erase all of STM's flash (other than bootloader) if box is checked.
        if self.erase_stm:
            text = "Erasing STM"
            self._write(text)
            self.create_flash("STM")
            sectors_to_erase = set(range(self.pk_flash.n_sectors)).difference(
                set(self.pk_flash.restricted_sectors))
            progress_dialog = PulsableProgressDialog(len(sectors_to_erase),
                                                     False)
            progress_dialog.title = text
            GUI.invoke_later(progress_dialog.open)
            erase_count = 0
            for s in sorted(sectors_to_erase):
                progress_dialog.progress(erase_count)
                self._write('Erasing %s sector %d' %
                            (self.pk_flash.flash_type, s))
                self.pk_flash.erase_sector(s)
                erase_count += 1
            self.stop_flash()
            self._write("")
            progress_dialog.close()

        # Flash STM.
        text = "Updating STM"
        self._write(text)
        self.create_flash("STM")
        stm_n_ops = self.pk_flash.ihx_n_ops(self.stm_fw.ihx, \
                                            erase = not self.erase_stm)
        progress_dialog = PulsableProgressDialog(stm_n_ops, True)
        progress_dialog.title = text
        GUI.invoke_later(progress_dialog.open)
        # Don't erase sectors if we've already done so above.
        self.pk_flash.write_ihx(self.stm_fw.ihx, self.stream, mod_print=0x40, \
                                elapsed_ops_cb = progress_dialog.progress, \
                                erase = not self.erase_stm)
        self.stop_flash()
        self._write("")
        progress_dialog.close()

    def manage_nap_firmware_update(self, check_version=False):
        # Flash NAP if out of date.
        try:
            local_nap_version = parse_version(
                self.settings['system_info']['nap_version'].value)
            remote_nap_version = parse_version(self.newest_nap_vers)
            nap_out_of_date = local_nap_version != remote_nap_version
        except KeyError:
            nap_out_of_date = True
        if nap_out_of_date or check_version == False:
            text = "Updating NAP"
            self._write(text)
            self.create_flash("M25")
            nap_n_ops = self.pk_flash.ihx_n_ops(self.nap_fw.ihx)
            progress_dialog = PulsableProgressDialog(nap_n_ops, True)
            progress_dialog.title = text
            GUI.invoke_later(progress_dialog.open)
            self.pk_flash.write_ihx(self.nap_fw.ihx, self.stream, mod_print=0x40, \
                                    elapsed_ops_cb = progress_dialog.progress)
            self.stop_flash()
            self._write("")
            progress_dialog.close()
            return True
        else:
            text = "NAP is already to latest version, not updating!"
            self._write(text)
            self._write("")
            return False

    # Executed in GUI thread, called from Handler.
    def manage_firmware_updates(self, device):
        """
    Update Piksi firmware. Erase entire STM flash (other than bootloader)
    if so directed. Flash NAP only if new firmware is available.
    """
        self.updating = True
        update_nap = False
        self._write('')

        if device == "STM":
            self.manage_stm_firmware_update()
        elif device == "M25":
            update_nap = self.manage_nap_firmware_update()
        else:
            self.manage_stm_firmware_update()
            update_nap = self.manage_nap_firmware_update(check_version=True)

        # Must tell Piksi to jump to application after updating firmware.
        if device == "STM" or update_nap:
            self.link(MsgBootloaderJumpToApp(jump=0))
            self._write("Firmware update finished.")
            self._write("")

        self.updating = False

    def create_flash(self, flash_type):
        """
    Create flash.Flash instance and set Piksi into bootloader mode, prompting
    user to reset if necessary.

    Parameter
    ---------
    flash_type : string
      Either "STM" or "M25".
    """
        # Reset device if the application is running to put into bootloader mode.
        self.link(MsgReset())

        self.pk_boot = bootload.Bootloader(self.link)

        self._write("Waiting for bootloader handshake message from Piksi ...")
        reset_prompt = None
        handshake_received = self.pk_boot.handshake(1)

        # Prompt user to reset Piksi if we don't receive the handshake message
        # within a reasonable amount of tiime (firmware might be corrupted).
        while not handshake_received:
            reset_prompt = \
              prompt.CallbackPrompt(
                                    title="Please Reset Piksi",
                                    actions=[prompt.close_button],
                                   )

            reset_prompt.text = \
                  "You must press the reset button on your Piksi in order\n" + \
                  "to update your firmware.\n\n" + \
                  "Please press it now.\n\n"

            reset_prompt.run(block=False)

            while not reset_prompt.closed and not handshake_received:
                handshake_received = self.pk_boot.handshake(1)

            reset_prompt.kill()
            reset_prompt.wait()

        self._write("received bootloader handshake message.")
        self._write("Piksi Onboard Bootloader Version: " +
                    self.pk_boot.version)

        self.pk_flash = flash.Flash(self.link, flash_type,
                                    self.pk_boot.sbp_version)

    def stop_flash(self):
        """
    Stop Flash and Bootloader instances (removes callback from SerialLink).
    """
        self.pk_flash.stop()
        self.pk_boot.stop()
Exemple #19
0
 def traits_view(self):
     tgrp = HGroup(icon_button_editor('editor.stop_button', 'stop', tooltip='Stop the current scan'))
     v = View(VGroup(tgrp, UItem('graph', style='custom', editor=InstanceEditor())))
     return v
Exemple #20
0
class UpdateView(HasTraits):
    piksi_hw_rev = String('piksi_multi')
    is_v2 = Bool(False)

    piksi_stm_vers = String('Waiting for Piksi to send settings...',
                            width=COLUMN_WIDTH)
    newest_stm_vers = String('Downloading Latest Firmware info...')
    piksi_nap_vers = String('Waiting for Piksi to send settings...')
    newest_nap_vers = String('Downloading Latest Firmware info...')
    local_console_vers = String('v' + CONSOLE_VERSION)
    newest_console_vers = String('Downloading Latest Console info...')

    erase_stm = Bool(True)
    erase_en = Bool(True)

    update_stm_firmware = Button(label='Update FW')
    update_nap_firmware = Button(label='Update NAP')
    update_full_firmware = Button(label='Update Piksi STM and NAP Firmware')

    updating = Bool(False)
    update_stm_en = Bool(False)
    update_nap_en = Bool(False)
    update_en = Bool(False)
    serial_upgrade = Bool(False)
    upgrade_steps = String("Firmware upgrade steps:")

    download_firmware = Button(label='Download Latest Firmware')
    download_directory = Directory(
        "  Please choose a directory for downloaded firmware files...")
    download_stm = Button(label='Download', height=HT)
    download_nap = Button(label='Download', height=HT)
    downloading = Bool(False)
    download_fw_en = Bool(True)

    stm_fw = Instance(FirmwareFileDialog)
    nap_fw = Instance(FirmwareFileDialog)

    stream = Instance(OutputStream)

    view = View(
      VGroup(
        Item('piksi_hw_rev', label='Hardware Revision',
             editor_args={'enabled': False}, resizable=True),
        HGroup(
          VGroup(
            Item('piksi_stm_vers', label='Current', resizable=True,
                   editor_args={'enabled': False}),
            Item('newest_stm_vers', label='Latest', resizable=True,
                   editor_args={'enabled': False,
                                'readonly_allow_selection': True}),
            Item('stm_fw', style='custom', show_label=True, \
                 label="Local File", enabled_when='download_fw_en',
                 visible_when='serial_upgrade',
                 editor_args={'enabled': False}),
            HGroup(Item('update_stm_firmware', show_label=False, \
                       enabled_when='update_stm_en', visible_when='serial_upgrade'),
                  Item('erase_stm', label='Erase STM flash\n(recommended)', \
                        enabled_when='erase_en', show_label=True, visible_when='is_v2')),
            show_border=True, label="Firmware Version"
          ),
          VGroup(
            Item('piksi_nap_vers', label='Current', resizable=True,
                 editor_args={'enabled': False}),
            Item('newest_nap_vers', label='Latest', resizable=True,
                 editor_args={'enabled': False}),
            Item('nap_fw', style='custom', show_label=True, \
                 label="Local File", enabled_when='download_fw_en',
                 editor_args={'enabled': False}),
            HGroup(Item('update_nap_firmware', show_label=False, \
                        enabled_when='update_nap_en', visible_when='serial_upgrade'),
                   Item(width=50, label="                  ")),
            show_border=True, label="NAP Version",
            visible_when='is_v2'
            ),
          VGroup(
            Item('local_console_vers', label='Current', resizable=True,
                 editor_args={'enabled': False}),
            Item('newest_console_vers', label='Latest',
                  editor_args={'enabled': False}),
            label="Swift Console Version", show_border=True),
            ),
        UItem('download_directory', enabled_when='download_fw_en'),
        UItem('download_firmware', enabled_when='download_fw_en'),
        UItem('update_full_firmware', enabled_when='update_en', visible_when='is_v2'),
        VGroup(
          UItem('upgrade_steps',
                visible_when='not serial_upgrade', style='readonly'),
          Item(
            'stream',
            style='custom',
            editor=InstanceEditor(),
            show_label=False,
          ),
        show_border=True,
        )
      )
    )

    def __init__(self, link, prompt=True, serial_upgrade=False):
        """
    Traits tab with UI for updating Piksi firmware.

    Parameters
    ----------
    link : sbp.client.handler.Handler
      Link for SBP transfer to/from Piksi.
    prompt : bool
      Prompt user to update console/firmware if out of date.
    """
        self.link = link
        self.settings = {}
        self.prompt = prompt
        self.python_console_cmds = {'update': self}
        try:
            self.update_dl = UpdateDownloader()
        except URLError:
            self.update_dl = None
            pass
        self.erase_en = True
        self.stm_fw = FirmwareFileDialog('bin')
        self.stm_fw.on_trait_change(self._manage_enables, 'status')
        self.nap_fw = FirmwareFileDialog('M25')
        self.nap_fw.on_trait_change(self._manage_enables, 'status')
        self.stream = OutputStream()
        self.serial_upgrade = serial_upgrade
        self.last_call_fw_version = None
        if not self.serial_upgrade:
            self._write(
                "1. Insert the USB flash drive provided with your Piki Multi into "
                "your computer.  Select the flash drive root directory as the "
                "firmware download destination using the \"Please "
                "choose a directory for downloaded firmware files\" directory "
                "chooser above.  Press the \"Download Latest Firmware\" button.  "
                "This will download the latest Piksi Multi firmware file onto the "
                "USB flashdrive.\n"
                "2. Eject the drive from your computer and plug it "
                "into the Piksi Multi evaluation board.\n"
                "3. Reset your Piksi Multi and it will upgrade to the version "
                "on the USB flash drive. This should take less than 5 minutes.\n"
                "4. When the upgrade completes you will be prompted to remove the "
                "USB flash drive and reset your Piksi Multi.\n"
                "5. Verify that the firmware version has upgraded via inspection "
                "of the Current Firmware Version box on the Firmware Update Tab "
                "of the Swift Console.\n")

    def _manage_enables(self):
        """ Manages whether traits widgets are enabled in the UI or not. """
        if self.updating == True or self.downloading == True:
            self.update_stm_en = False
            self.update_nap_en = False
            self.update_en = False
            self.download_fw_en = False
            self.erase_en = False
        else:
            self.download_fw_en = True
            self.erase_en = True
            if self.stm_fw.ihx is not None or self.stm_fw.blob is not None:
                self.update_stm_en = True
            else:
                self.update_stm_en = False
                self.update_en = False
            if self.nap_fw.ihx is not None:
                self.update_nap_en = True
            else:
                self.update_nap_en = False
                self.update_en = False
            if self.nap_fw.ihx is not None and self.stm_fw.ihx is not None:
                self.update_en = True

    def _download_directory_changed(self):
        if self.update_dl:
            self.update_dl.set_root_path(self.download_directory)

    def _updating_changed(self):
        """ Handles self.updating trait being changed. """
        self._manage_enables()

    def _downloading_changed(self):
        """ Handles self.downloading trait being changed. """
        self._manage_enables()

    def _write(self, text):
        """
    Stream style write function. Allows flashing debugging messages to be
    routed to embedded text console.

    Parameters
    ----------
    text : string
      Text to be written to screen.
    """
        self.stream.write(text)
        self.stream.write('\n')
        self.stream.flush()

    def _update_stm_firmware_fired(self):
        """
    Handle update_stm_firmware button. Starts thread so as not to block the GUI
    thread.
    """
        try:
            if self._firmware_update_thread.is_alive():
                return
        except AttributeError:
            pass

        self._firmware_update_thread = Thread(
            target=self.manage_firmware_updates, args=("STM", ))
        self._firmware_update_thread.start()

    def _update_nap_firmware_fired(self):
        """
    Handle update_nap_firmware button. Starts thread so as not to block the GUI
    thread.
    """
        try:
            if self._firmware_update_thread.is_alive():
                return
        except AttributeError:
            pass

        self._firmware_update_thread = Thread(
            target=self.manage_firmware_updates, args=("M25", ))
        self._firmware_update_thread.start()

    def _update_full_firmware_fired(self):
        """
    Handle update_full_firmware button. Starts thread so as not to block the GUI
    thread.
    """
        try:
            if self._firmware_update_thread.is_alive():
                return
        except AttributeError:
            pass

        self._firmware_update_thread = Thread(
            target=self.manage_firmware_updates, args=("ALL", ))
        self._firmware_update_thread.start()

    def _download_firmware(self):
        """ Download latest firmware from swiftnav.com. """
        self._write('')

        # Check that we received the index file from the website.
        if self.update_dl == None:
            self._write("Error: Can't download firmware files")
            return

        self.downloading = True
        status = 'Downloading Latest Firmware...'
        self.nap_fw.clear(status)
        self.stm_fw.clear(status)
        self._write(status)

        # Get firmware files from Swift Nav's website, save to disk, and load.
        if self.update_dl.index[self.piksi_hw_rev].has_key('fw'):
            try:
                self._write('Downloading Latest Multi firmware')
                filepath = self.update_dl.download_multi_firmware(
                    self.piksi_hw_rev)
                self._write('Saved file to %s' % filepath)
                self.stm_fw.load_bin(filepath)
            except AttributeError:
                self.nap_fw.clear("Error downloading firmware")
                self._write(
                    "Error downloading firmware: index file not downloaded yet"
                )
            except KeyError:
                self.nap_fw.clear("Error downloading firmware")
                self._write(
                    "Error downloading firmware: URL not present in index")
            except URLError:
                self.nap_fw.clear("Error downloading firmware")
                self._write(
                    "Error: Failed to download latest NAP firmware from Swift Navigation's website"
                )
            self.downloading = False
            return

        try:
            self._write('Downloading Latest NAP firmware')
            filepath = self.update_dl.download_nap_firmware(self.piksi_hw_rev)
            self._write('Saved file to %s' % filepath)
            self.nap_fw.load_ihx(filepath)
        except AttributeError:
            self.nap_fw.clear("Error downloading firmware")
            self._write(
                "Error downloading firmware: index file not downloaded yet")
        except KeyError:
            self.nap_fw.clear("Error downloading firmware")
            self._write("Error downloading firmware: URL not present in index")
        except URLError:
            self.nap_fw.clear("Error downloading firmware")
            self._write(
                "Error: Failed to download latest NAP firmware from Swift Navigation's website"
            )

        try:
            self._write('Downloading Latest STM firmware')
            filepath = self.update_dl.download_stm_firmware(self.piksi_hw_rev)
            self._write('Saved file to %s' % filepath)
            self.stm_fw.load_ihx(filepath)
        except AttributeError:
            self.stm_fw.clear("Error downloading firmware")
            self._write(
                "Error downloading firmware: index file not downloaded yet")
        except KeyError:
            self.stm_fw.clear("Error downloading firmware")
            self._write("Error downloading firmware: URL not present in index")
        except URLError:
            self.stm_fw.clear("Error downloading firmware")
            self._write(
                "Error: Failed to download latest STM firmware from Swift Navigation's website"
            )

        self.downloading = False

    def _download_firmware_fired(self):
        """
    Handle download_firmware button. Starts thread so as not to block the GUI
    thread.
    """
        try:
            if self._download_firmware_thread.is_alive():
                return
        except AttributeError:
            pass

        self._download_firmware_thread = Thread(target=self._download_firmware)
        self._download_firmware_thread.start()

    def compare_versions(self):
        """
    To be called after latest Piksi firmware info has been received from
    device, to decide if current firmware on Piksi is out of date. Also informs
    user if the firmware was successfully upgraded. Starts a thread so as not
    to block GUI thread.
    """
        try:
            if self._compare_versions_thread.is_alive():
                return
        except AttributeError:
            pass

        self._compare_versions_thread = Thread(target=self._compare_versions)
        self._compare_versions_thread.start()

    def _compare_versions(self):
        """
    Compares version info between received firmware version / current console
    and firmware / console info from website to decide if current firmware or
    console is out of date. Prompt user to update if so. Informs user if
    firmware successfully upgraded.
    """
        # Check that settings received from Piksi contain FW versions.
        try:
            self.piksi_hw_rev = \
              HW_REV_LOOKUP[self.settings['system_info']['hw_revision'].value]
            self.piksi_stm_vers = \
              self.settings['system_info']['firmware_version'].value
        except KeyError:
            self._write(
                "\nError: Settings received from Piksi don't contain firmware version keys. Please contact Swift Navigation.\n"
            )
            return

        self.is_v2 = self.piksi_hw_rev.startswith('piksi_v2')
        if self.is_v2:
            self.stm_fw.set_flash_type('STM')
            self.serial_upgrade = True
        else:
            self.stm_fw.set_flash_type('bin')

        self._get_latest_version_info()

        # Check that we received the index file from the website.
        if self.update_dl == None:
            self._write(
                "Error: No website index to use to compare versions with local firmware"
            )
            return

        # Check if console is out of date and notify user if so.
        if self.prompt:
            local_console_version = parse_version(CONSOLE_VERSION)
            remote_console_version = parse_version(self.newest_console_vers)
            self.console_outdated = remote_console_version > local_console_version

            # we want to warn users using v2 regardless of version logic
            if self.console_outdated or self.is_v2:
                if not self.is_v2:
                    console_outdated_prompt = \
                    prompt.CallbackPrompt(
                                          title="Swift Console Outdated",
                                          actions=[prompt.close_button],
                                          )
                    console_outdated_prompt.text = \
                        "Your console is out of date and may be incompatible\n" + \
                        "with current firmware. We highly recommend upgrading to\n" + \
                        "ensure proper behavior.\n\n" + \
                        "Please visit http://support.swiftnav.com to\n" + \
                        "download the latest version.\n\n" + \
                        "Local Console Version :\n\t" + \
                            "v" + CONSOLE_VERSION + \
                        "\nLatest Console Version :\n\t" + \
                            self.update_dl.index[self.piksi_hw_rev]['console']['version'] + "\n"
                else:
                    console_outdated_prompt = \
                    prompt.CallbackPrompt(
                                          title="Swift Console Incompatible",
                                          actions=[prompt.close_button],
                                          )
                    console_outdated_prompt.text = \
                        "Your console is incompatible with your hardware revision.\n" + \
                        "We highly recommend using a compatible console version\n" + \
                        "to ensure proper behavior.\n\n" + \
                        "Please visit http://support.swiftnav.com to\n" + \
                        "download the latest compatible version.\n\n" + \
                        "Current Hardware revision :\n\t" + \
                             self.piksi_hw_rev + \
                        "\nLast supported Console Version: \n\t" + \
                            self.update_dl.index[self.piksi_hw_rev]['console']['version'] + "\n"

                console_outdated_prompt.run()

            # For timing aesthetics between windows popping up.
            sleep(0.5)

            # Check if firmware is out of date and notify user if so.
            local_stm_version = self.settings['system_info'][
                'firmware_version'].value
            remote_stm_version = self.newest_stm_vers

            self.fw_outdated = remote_stm_version > local_stm_version

            if self.fw_outdated:
                fw_update_prompt = \
                    prompt.CallbackPrompt(
                                          title='Firmware Update',
                                          actions=[prompt.close_button]
                                         )

                if self.update_dl.index[self.piksi_hw_rev].has_key('fw'):
                    fw_update_prompt.text = \
                      "New Piksi firmware available.\n\n" + \
                      "Please use the Firmware Update tab to update.\n\n" + \
                      "Newest Firmware Version :\n\t%s\n\n" % \
                          self.update_dl.index[self.piksi_hw_rev]['fw']['version']
                else:
                    fw_update_prompt.text = \
                      "New Piksi firmware available.\n\n" + \
                      "Please use the Firmware Update tab to update.\n\n" + \
                      "Newest STM Version :\n\t%s\n\n" % \
                          self.update_dl.index[self.piksi_hw_rev]['stm_fw']['version'] + \
                      "Newest SwiftNAP Version :\n\t%s\n\n" % \
                          self.update_dl.index[self.piksi_hw_rev]['nap_fw']['version']

                fw_update_prompt.run()

        # Check if firmware successfully upgraded and notify user if so.
        if self.last_call_fw_version is not None and \
            self.last_call_fw_version != local_stm_version:
            fw_success_str = "Firmware successfully upgraded from %s to %s." % \
                             (self.last_call_fw_version, local_stm_version)
            print fw_success_str
            self._write(fw_success_str)

        # Record firmware version reported each time this callback is called.
        self.last_call_fw_version = local_stm_version

    def _get_latest_version_info(self):
        """ Get latest firmware / console version from website. """
        try:
            self.update_dl = UpdateDownloader()
        except URLError:
            self._write(
                "\nError: Failed to download latest file index from Swift Navigation's website. Please visit our website to check that you're running the latest Piksi firmware and Piksi console.\n"
            )
            return

        # Make sure index contains all keys we are interested in.
        try:
            if self.update_dl.index[self.piksi_hw_rev].has_key('fw'):
                self.newest_stm_vers = self.update_dl.index[
                    self.piksi_hw_rev]['fw']['version']
            else:
                self.newest_stm_vers = self.update_dl.index[
                    self.piksi_hw_rev]['stm_fw']['version']
                self.newest_nap_vers = self.update_dl.index[
                    self.piksi_hw_rev]['nap_fw']['version']
            self.newest_console_vers = self.update_dl.index[
                self.piksi_hw_rev]['console']['version']
        except KeyError:
            self._write(
                "\nError: Index downloaded from Swift Navigation's website (%s) doesn't contain all keys. Please contact Swift Navigation.\n"
                % INDEX_URL)
            return

    def manage_stm_firmware_update(self):
        # Erase all of STM's flash (other than bootloader) if box is checked.
        if self.erase_stm:
            text = "Erasing STM"
            self._write(text)
            self.create_flash("STM")
            sectors_to_erase = set(range(self.pk_flash.n_sectors)).difference(
                set(self.pk_flash.restricted_sectors))
            progress_dialog = PulsableProgressDialog(len(sectors_to_erase),
                                                     False)
            progress_dialog.title = text
            GUI.invoke_later(progress_dialog.open)
            erase_count = 0
            for s in sorted(sectors_to_erase):
                progress_dialog.progress(erase_count)
                self._write('Erasing %s sector %d' %
                            (self.pk_flash.flash_type, s))
                self.pk_flash.erase_sector(s)
                erase_count += 1
            self.stop_flash()
            self._write("")
            try:
                progress_dialog.close()
            except AttributeError:
                pass
        # Flash STM.
        text = "Updating STM"
        self._write(text)
        self.create_flash("STM")
        stm_n_ops = self.pk_flash.ihx_n_ops(self.stm_fw.ihx, \
                                            erase = not self.erase_stm)
        progress_dialog = PulsableProgressDialog(stm_n_ops, True)
        progress_dialog.title = text
        GUI.invoke_later(progress_dialog.open)
        # Don't erase sectors if we've already done so above.
        self.pk_flash.write_ihx(self.stm_fw.ihx, self.stream, mod_print=0x40, \
                                elapsed_ops_cb = progress_dialog.progress, \
                                erase = not self.erase_stm)
        self.stop_flash()
        self._write("")
        try:
            progress_dialog.close()
        except AttributeError:
            pass

    def manage_nap_firmware_update(self, check_version=False):
        # Flash NAP if out of date.
        try:
            local_nap_version = parse_version(
                self.settings['system_info']['nap_version'].value)
            remote_nap_version = parse_version(self.newest_nap_vers)
            nap_out_of_date = local_nap_version != remote_nap_version
        except KeyError:
            nap_out_of_date = True
        if nap_out_of_date or check_version == False:
            text = "Updating NAP"
            self._write(text)
            self.create_flash("M25")
            nap_n_ops = self.pk_flash.ihx_n_ops(self.nap_fw.ihx)
            progress_dialog = PulsableProgressDialog(nap_n_ops, True)
            progress_dialog.title = text
            GUI.invoke_later(progress_dialog.open)
            self.pk_flash.write_ihx(self.nap_fw.ihx, self.stream, mod_print=0x40, \
                                    elapsed_ops_cb = progress_dialog.progress)
            self.stop_flash()
            self._write("")
            try:
                progress_dialog.close()
            except AttributeError:
                pass
            return True
        else:
            text = "NAP is already to latest version, not updating!"
            self._write(text)
            self._write("")
            return False

    def manage_multi_firmware_update(self):
        # Set up progress dialog and transfer file to Piksi using SBP FileIO
        progress_dialog = PulsableProgressDialog(len(self.stm_fw.blob))
        progress_dialog.title = "Transferring image file"
        GUI.invoke_later(progress_dialog.open)
        self._write("Transferring image file...")
        try:
            FileIO(self.link).write("upgrade.image_set.bin",
                                    self.stm_fw.blob,
                                    progress_cb=progress_dialog.progress)
        except Exception as e:
            self._write("Failed to transfer image file to Piksi: %s\n" % e)
            progress_dialog.close()
            return
        try:
            progress_dialog.close()
        except AttributeError:
            pass

        # Setup up pulsed progress dialog and commit to flash
        progress_dialog = PulsableProgressDialog(100, True)
        progress_dialog.title = "Committing to flash"
        GUI.invoke_later(progress_dialog.open)
        self._write("Committing file to flash...")

        def log_cb(msg, **kwargs):
            self._write(msg.text)

        self.link.add_callback(log_cb, SBP_MSG_LOG)
        code = shell_command(self.link,
                             "upgrade_tool upgrade.image_set.bin",
                             600,
                             progress_cb=progress_dialog.progress)
        self.link.remove_callback(log_cb, SBP_MSG_LOG)
        progress_dialog.close()

        if code != 0:
            self._write('Failed to perform upgrade (code = %d)' % code)
            if code == -255:
                self._write('Shell command timed out.  Please try again.')
            return
        self._write('Resetting Piksi...')
        self.link(MsgReset(flags=0))

    # Executed in GUI thread, called from Handler.
    def manage_firmware_updates(self, device):
        """
    Update Piksi firmware. Erase entire STM flash (other than bootloader)
    if so directed. Flash NAP only if new firmware is available.
    """
        self.updating = True
        update_nap = False
        self._write('')
        if not self.is_v2:
            self.manage_multi_firmware_update()
            self.updating = False
            return
        elif device == "STM":
            self.manage_stm_firmware_update()
        elif device == "M25":
            update_nap = self.manage_nap_firmware_update()
        else:
            self.manage_stm_firmware_update()
            update_nap = self.manage_nap_firmware_update(check_version=True)

        # Must tell Piksi to jump to application after updating firmware.
        if device == "STM" or update_nap:
            self.link(MsgBootloaderJumpToApp(jump=0))
            self._write("Firmware update finished.")
            self._write("")

        self.updating = False

    def create_flash(self, flash_type):
        """
    Create flash.Flash instance and set Piksi into bootloader mode, prompting
    user to reset if necessary.

    Parameter
    ---------
    flash_type : string
      Either "STM" or "M25".
    """
        # Reset device if the application is running to put into bootloader mode.
        self.link(MsgReset(flags=0))

        self.pk_boot = bootload.Bootloader(self.link)

        self._write("Waiting for bootloader handshake message from Piksi ...")
        reset_prompt = None
        handshake_received = self.pk_boot.handshake(1)

        # Prompt user to reset Piksi if we don't receive the handshake message
        # within a reasonable amount of tiime (firmware might be corrupted).
        while not handshake_received:
            reset_prompt = \
              prompt.CallbackPrompt(
                                    title="Please Reset Piksi",
                                    actions=[prompt.close_button],
                                   )

            reset_prompt.text = \
                  "You must press the reset button on your Piksi in order\n" + \
                  "to update your firmware.\n\n" + \
                  "Please press it now.\n\n"

            reset_prompt.run(block=False)

            while not reset_prompt.closed and not handshake_received:
                handshake_received = self.pk_boot.handshake(1)

            reset_prompt.kill()
            reset_prompt.wait()

        self._write("received bootloader handshake message.")
        self._write("Piksi Onboard Bootloader Version: " +
                    self.pk_boot.version)

        self.pk_flash = flash.Flash(self.link, flash_type,
                                    self.pk_boot.sbp_version)

    def stop_flash(self):
        """
    Stop Flash and Bootloader instances (removes callback from SerialLink).
    """
        self.pk_flash.stop()
        self.pk_boot.stop()
Exemple #21
0
class SwiftConsole(HasTraits):
    link = Instance(serial_link.SerialLink)
    console_output = Instance(OutputStream)
    python_console_env = Dict
    a = Int
    b = Int
    tracking_view = Instance(TrackingView)
    almanac_view = Instance(AlmanacView)
    solution_view = Instance(SolutionView)
    baseline_view = Instance(BaselineView)
    observation_view = Instance(ObservationView)
    observation_view_base = Instance(ObservationView)
    system_monitor_view = Instance(SystemMonitorView)
    simulator_view = Instance(SimulatorView)
    settings_view = Instance(SettingsView)

    view = View(VSplit(
        Tabbed(Item('tracking_view', style='custom', label='Tracking'),
               Item('almanac_view', style='custom', label='Almanac'),
               Item('solution_view', style='custom', label='Solution'),
               Item('baseline_view', style='custom', label='Baseline'),
               VSplit(
                   Item('observation_view', style='custom', show_label=False),
                   Item('observation_view_base',
                        style='custom',
                        show_label=False),
                   label='Observations',
               ),
               Item('simulator_view', style='custom', label='Simulator'),
               Item('settings_view', style='custom', label='Settings'),
               Item('system_monitor_view',
                    style='custom',
                    label='System Monitor'),
               Item('python_console_env',
                    style='custom',
                    label='Python Console',
                    editor=ShellEditor()),
               show_labels=False),
        Item(
            'console_output',
            style='custom',
            editor=InstanceEditor(),
            height=0.3,
            show_label=False,
        ),
    ),
                icon=icon,
                resizable=True,
                width=800,
                height=600,
                title='Piksi Console, Version: ' + CONSOLE_VERSION)

    def print_message_callback(self, data):
        try:
            self.console_output.write(data.encode('ascii', 'ignore'))
        except UnicodeDecodeError:
            print "Oh crap!"

    def debug_var_callback(self, data):
        x = struct.unpack('<d', data[:8])[0]
        name = data[8:]
        print "VAR: %s = %d" % (name, x)

    def __init__(self, *args, **kwargs):
        try:
            update = kwargs.pop('update')
        except KeyError:
            update = True

        self.console_output = OutputStream()
        sys.stdout = self.console_output
        sys.stderr = self.console_output

        try:
            self.link = serial_link.SerialLink(*args, **kwargs)
            self.link.add_callback(ids.PRINT, self.print_message_callback)

            self.link.add_callback(ids.DEBUG_VAR, self.debug_var_callback)

            settings_read_finished_functions = []

            self.tracking_view = TrackingView(self.link)
            self.almanac_view = AlmanacView(self.link)
            self.solution_view = SolutionView(self.link)
            self.baseline_view = BaselineView(self.link)
            self.observation_view = ObservationView(self.link,
                                                    name='Rover',
                                                    relay=False)
            self.observation_view_base = ObservationView(self.link,
                                                         name='Base',
                                                         relay=True)
            self.system_monitor_view = SystemMonitorView(self.link)
            self.simulator_view = SimulatorView(self.link)
            self.settings_view = SettingsView(self.link)

            if update:
                self.ocu = OneClickUpdate(self.link, self.console_output)
                settings_read_finished_functions.append(self.ocu.start)

            self.settings_view = \
                SettingsView(self.link, settings_read_finished_functions)

            if update:
                self.ocu.point_to_settings(self.settings_view.settings)

            self.python_console_env = {
                'send_message': self.link.send_message,
                'link': self.link,
            }
            self.python_console_env.update(
                self.tracking_view.python_console_cmds)
            self.python_console_env.update(
                self.almanac_view.python_console_cmds)
            self.python_console_env.update(
                self.solution_view.python_console_cmds)
            self.python_console_env.update(
                self.baseline_view.python_console_cmds)
            self.python_console_env.update(
                self.observation_view.python_console_cmds)
            self.python_console_env.update(
                self.system_monitor_view.python_console_cmds)
        except:
            import traceback
            traceback.print_exc()

    def stop(self):
        self.link.close()
Exemple #22
0
 def traits_view(self):
     v = View(
         UItem('graph',
               style='custom',
               editor=InstanceEditor(view='panel_view')))
     return v
Exemple #23
0
class Mayavi(HasTraits):

    # The scene model.
    scene = Instance(MlabSceneModel, ())

    # The mayavi engine view.
    engine_view = Instance(EngineView)

    # The current selection in the engine tree view.
    current_selection = Property

    ######################
    view = View(HSplit(
        VSplit(
            Item(name='engine_view',
                 style='custom',
                 resizable=True,
                 show_label=False),
            Item(name='current_selection',
                 editor=InstanceEditor(),
                 enabled_when='current_selection is not None',
                 style='custom',
                 springy=True,
                 show_label=False),
        ),
        Item(name='scene',
             editor=SceneEditor(),
             show_label=False,
             resizable=True,
             height=500,
             width=500),
    ),
                resizable=True,
                scrollable=True)

    def __init__(self, **traits):
        HasTraits.__init__(self, **traits)
        self.engine_view = EngineView(engine=self.scene.engine)

        # Hook up the current_selection to change when the one in the engine
        # changes.  This is probably unnecessary in Traits3 since you can show
        # the UI of a sub-object in T3.
        self.scene.engine.on_trait_change(self._selection_change,
                                          'current_selection')

        self.generate_data_mayavi()

    def generate_data_mayavi(self):
        """Shows how you can generate data using mayavi instead of mlab."""
        from mayavi.sources.api import ParametricSurface
        from mayavi.modules.api import Outline, Surface
        e = self.scene.engine
        s = ParametricSurface()
        e.add_source(s)
        e.add_module(Outline())
        e.add_module(Surface())

    def _selection_change(self, old, new):
        self.trait_property_changed('current_selection', old, new)

    def _get_current_selection(self):
        return self.scene.engine.current_selection

# -------------------------------------------------------------------------
#  Define the View to use:
# -------------------------------------------------------------------------

view = View(
    Group(
        [Item('company', editor=tree_editor, resizable=True), '|<>'],
        Group(
            [
                '{Employee of the Month}@',
                Item(
                    'eom@',
                    editor=InstanceEditor(values=[
                        InstanceDropChoice(klass=Employee, selectable=True)
                    ]),
                    resizable=True,
                ),
                '|<>',
            ],
            [
                '{Department of the Month}@',
                Item(
                    'dom@',
                    editor=InstanceEditor(
                        values=[InstanceDropChoice(klass=Department)]),
                    resizable=True,
                ),
                '|<>',
            ],
Exemple #25
0
class Scene(TVTKScene, Widget):
    """A VTK interactor scene widget for pyface and PyQt.

    This widget uses a RenderWindowInteractor and therefore supports
    interaction with VTK widgets.  The widget uses TVTK.  In addition
    to the features that the base TVTKScene provides this widget
    supports:

    - saving the rendered scene to the clipboard.

    - picking data on screen.  Press 'p' or 'P' when the mouse is over
      a point that you need to pick.

    - The widget also uses a light manager to manage the lighting of
      the scene.  Press 'l' or 'L' to activate a GUI configuration
      dialog for the lights.

    - Pressing the left, right, up and down arrow let you rotate the
      camera in those directions.  When shift-arrow is pressed then
      the camera is panned.  Pressing the '+' (or '=')  and '-' keys
      let you zoom in and out.

    - full screen rendering via the full_screen button on the UI.

    """

    # The version of this class.  Used for persistence.
    __version__ = 0

    ###########################################################################
    # Traits.
    ###########################################################################

    # Turn on full-screen rendering.
    full_screen = Button('Full Screen')

    # The picker handles pick events.
    picker = Instance(picker.Picker)

    ########################################

    # Render_window's view.
    _stereo_view = Group(Item(name='stereo_render'),
                         Item(name='stereo_type'),
                         show_border=True,
                         label='Stereo rendering',
                         )

    # The default view of this object.
    default_view = View(Group(
                            Group(Item(name='background'),
                                  Item(name='foreground'),
                                  Item(name='parallel_projection'),
                                  Item(name='disable_render'),
                                  Item(name='off_screen_rendering'),
                                  Item(name='jpeg_quality'),
                                  Item(name='jpeg_progressive'),
                                  Item(name='magnification'),
                                  Item(name='anti_aliasing_frames'),
                                  Item(name='full_screen',
                                       show_label=False),
                                  ),
                            Group(Item(name='render_window',
                                       style='custom',
                                       visible_when='object.stereo',
                                       editor=InstanceEditor(view=View(_stereo_view)),
                                       show_label=False),
                                  ),
                            label='Scene'),
                         Group( Item(name='light_manager',
                                style='custom', show_label=False),
                                label='Lights'),
                         Group(
                             Item(
                                 name='movie_maker',
                                 style='custom', show_label=False
                             ),
                             label='Movie'),
                         buttons=['OK', 'Cancel']
                        )

    ########################################
    # Private traits.

    _vtk_control = Instance(_VTKRenderWindowInteractor)
    _fullscreen = Any(False)

    ###########################################################################
    # 'object' interface.
    ###########################################################################
    def __init__(self, parent=None, **traits):
        """ Initializes the object. """

        # Base class constructor.
        super(Scene, self).__init__(parent, **traits)

        # Setup the default picker.
        self.picker = picker.Picker(self)

        # The light manager needs creating.
        self.light_manager = None

        self._cursor = QtCore.Qt.ArrowCursor

    def __get_pure_state__(self):
        """Allows us to pickle the scene."""
        # The control attribute is not picklable since it is a VTK
        # object so we remove it.
        d = super(Scene, self).__get_pure_state__()
        for x in ['_vtk_control', '_fullscreen', '_cursor']:
            d.pop(x, None)
        return d

    ###########################################################################
    # 'Scene' interface.
    ###########################################################################
    def render(self):
        """ Force the scene to be rendered. Nothing is done if the
        `disable_render` trait is set to True."""
        if not self.disable_render:
            self._vtk_control.Render()

    def get_size(self):
        """Return size of the render window."""
        sz = self._vtk_control.size()

        return (sz.width(), sz.height())

    def set_size(self, size):
        """Set the size of the window."""
        self._vtk_control.resize(*size)

    def hide_cursor(self):
        """Hide the cursor."""
        self._cursor = self._vtk_control.cursor().shape()
        self._vtk_control.setCursor(QtCore.Qt.BlankCursor)

    def show_cursor(self):
        """Show the cursor."""
        self._vtk_control.setCursor(self._cursor)

    ###########################################################################
    # 'TVTKScene' interface.
    ###########################################################################
    def save_to_clipboard(self):
        """Saves a bitmap of the scene to the clipboard."""
        handler, name = tempfile.mkstemp()
        self.save_bmp(name)
        QtGui.QApplication.clipboard().setImage(QtGui.QImage(name))
        os.close(handler)
        os.unlink(name)

    ###########################################################################
    # 'event' interface.
    ###########################################################################
    def _closed_fired(self):
        super(Scene, self)._closed_fired()
        self.picker = None
        self._vtk_control = None
        self.control = None

    ###########################################################################
    # Non-public interface.
    ###########################################################################
    def _create_control(self, parent):
        """ Create the toolkit-specific control that represents the widget. """

        # Create the VTK widget.
        self._vtk_control = window = _VTKRenderWindowInteractor(
            self, parent, stereo=self.stereo)

        # Switch the default interaction style to the trackball one.
        window.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

        # Grab the renderwindow.
        renwin = self._renwin = tvtk.to_tvtk(window.GetRenderWindow())
        renwin.trait_set(point_smoothing=self.point_smoothing,
                   line_smoothing=self.line_smoothing,
                   polygon_smoothing=self.polygon_smoothing)
        # Create a renderer and add it to the renderwindow
        self._renderer = tvtk.Renderer()
        renwin.add_renderer(self._renderer)
        # Save a reference to our camera so it is not GC'd -- needed for
        # the sync_traits to work.
        self._camera = self.camera

        # Sync various traits.
        self._renderer.background = self.background
        self.sync_trait('background', self._renderer)
        self.renderer.on_trait_change(self.render, 'background')
        renwin.off_screen_rendering = self.off_screen_rendering
        self._camera.parallel_projection = self.parallel_projection
        self.sync_trait('parallel_projection', self._camera)
        self.sync_trait('off_screen_rendering', self._renwin)
        self.render_window.on_trait_change(self.render, 'off_screen_rendering')
        self.render_window.on_trait_change(self.render, 'stereo_render')
        self.render_window.on_trait_change(self.render, 'stereo_type')
        self.camera.on_trait_change(self.render, 'parallel_projection')

        self._interactor = tvtk.to_tvtk(window._Iren)

        return window

    def _lift(self):
        """Lift the window to the top. Useful when saving screen to an
        image."""
        if self.render_window.off_screen_rendering:
            # Do nothing if off screen rendering is being used.
            return

        self._vtk_control.window().raise_()
        QtCore.QCoreApplication.processEvents()

    def _full_screen_fired(self):
        fs = self._fullscreen
        if fs:
            self._vtk_control.window().showNormal()
            self._fullscreen = False
        else:
            self._vtk_control.window().showFullScreen()
            self._fullscreen = True

    def _disable_fullscreen(self):
        fs = self._fullscreen
        if fs:
            self._vtk_control.window().showNormal()
            self._fullscreen = False

    def _busy_changed(self, val):
        GUI.set_busy(val)
Exemple #26
0
from traitsui.api import (View, Group, Item, InstanceEditor,
                          DropEditor, Tabbed)
from tvtk.api import tvtk
from tvtk.common import vtk_major_version

VTK_VER = tvtk.Version().vtk_version

# The properties view group.
_prop_base_group = Group(Item(name='representation'),
                         Item(name='color'),
                         Item(name='line_width'),
                         Item(name='point_size'),
                         Item(name='opacity'))

_prop_group = Group(Item(name='property', style='custom', show_label=False,
                         editor=InstanceEditor(view=View(_prop_base_group))),
                    Item(name='property',
                         show_label=False,
                         editor=InstanceEditor(label='More options ...')),
                    show_border=True, label='Property')


# The mapper's view group.
if VTK_VER[:3] in ['4.2', '4.4']:
    _mapper_base_group = Group(Item(name='scalar_visibility'))
else:
    _mapper_base_group = Group(
        Item(name='scalar_visibility'),
        Item(name='interpolate_scalars_before_mapping'),
    )
 def traits_view(self):
     v = View(UItem('editor', style='custom', editor=InstanceEditor()))
     return v
Exemple #28
0
class UpdateView(HasTraits):
    piksi_hw_rev = String('piksi_multi')
    is_v2 = Bool(False)

    piksi_stm_vers = String('Waiting for Piksi to send settings...',
                            width=COLUMN_WIDTH)
    newest_stm_vers = String('Downloading Latest Firmware info...')
    piksi_nap_vers = String('Waiting for Piksi to send settings...')
    newest_nap_vers = String('Downloading Latest Firmware info...')
    local_console_vers = String('v' + CONSOLE_VERSION)
    newest_console_vers = String('Downloading Latest Console info...')
    download_directory_label = String('Firmware Download Directory:')

    update_stm_firmware = Button(label='Update Firmware')

    updating = Bool(False)
    update_stm_en = Bool(False)

    download_firmware = Button(label='Download Latest Firmware')
    download_directory = String()
    choose_dir = Button(label='...', padding=-1)
    download_stm = Button(label='Download', height=HT)
    downloading = Bool(False)
    download_fw_en = Bool(False)

    stm_fw = Instance(FirmwareFileDialog)

    stream = Instance(OutputStream)

    view = View(
        VGroup(Item('piksi_hw_rev',
                    label='Hardware Revision',
                    editor_args={'enabled': False},
                    resizable=True),
               HGroup(
                   VGroup(Item('piksi_stm_vers',
                               label='Current',
                               resizable=True,
                               editor_args={'enabled': False}),
                          Item('newest_stm_vers',
                               label='Latest',
                               resizable=True,
                               editor_args={
                                   'enabled': False,
                                   'readonly_allow_selection': True
                               }),
                          Item('stm_fw',
                               style='custom',
                               show_label=True,
                               label="Local File"),
                          Item('update_stm_firmware',
                               show_label=False,
                               enabled_when='update_stm_en'),
                          show_border=True,
                          label="Firmware Version"),
                   VGroup(Item('local_console_vers',
                               label='Current',
                               resizable=True,
                               editor_args={'enabled': False}),
                          Item('newest_console_vers',
                               label='Latest',
                               editor_args={'enabled': False}),
                          label="Swift Console Version",
                          show_border=True),
               ),
               HGroup(
                   VGroup(HGroup(
                       Item('download_directory',
                            label="Directory",
                            resizable=True),
                       UItem('choose_dir', width=-0.1),
                   ),
                          HGroup(
                              Spring(width=50, springy=False),
                              Item('download_firmware',
                                   enabled_when='download_fw_en',
                                   show_label=False,
                                   resizable=True,
                                   springy=True)),
                          label="Firmware Download",
                          show_border=True),
                   VGroup(Item(
                       'stream',
                       style='custom',
                       editor=InstanceEditor(),
                       show_label=False,
                   ),
                          show_border=True,
                          label="Firmware Upgrade Status"),
               ),
               show_border=True), )

    def __init__(self,
                 link,
                 download_dir=None,
                 prompt=True,
                 connection_info={'mode': 'unknown'}):
        """
        Traits tab with UI for updating Piksi firmware.

        Parameters
        ----------
        link : sbp.client.handler.Handler
          Link for SBP transfer to/from Piksi.
        prompt : bool
          Prompt user to update console/firmware if out of date.
        """
        self.link = link
        self.connection_info = connection_info
        self.settings = {}
        self.prompt = prompt
        self.python_console_cmds = {'update': self}
        self.download_directory = download_dir
        try:
            self.update_dl = UpdateDownloader(root_dir=self.download_directory)
        except RuntimeError:
            self.update_dl = None
        self.stm_fw = FirmwareFileDialog(self.download_directory)
        self.stm_fw.on_trait_change(self._manage_enables, 'status')
        self.stream = OutputStream()
        self.stream.max_len = 1000
        self.last_call_fw_version = None
        self.link.add_callback(self.log_cb, SBP_MSG_LOG)

    def _choose_dir_fired(self):
        dialog = DirectoryDialog(label='Choose Download location',
                                 action='open',
                                 default_directory=self.download_directory)
        dialog.open()
        if dialog.return_code == OK:
            self.download_directory = dialog.path
        else:
            self._write('Error while selecting firmware download location')

    def _manage_enables(self):
        """ Manages whether traits widgets are enabled in the UI or not. """
        if self.updating or self.downloading:
            self.update_stm_en = False
            self.download_fw_en = False
        else:
            if getattr(self.stm_fw, 'blob', None) is not None:
                self.update_stm_en = True
            else:
                self.update_stm_en = False
            if self.download_directory != '':
                self.download_fw_en = True

    def _download_directory_changed(self):
        if getattr(self, 'update_dl', None):
            self.update_dl.set_root_path(self.download_directory)
        self._manage_enables()

    def _updating_changed(self):
        """ Handles self.updating trait being changed. """
        self._manage_enables()

    def _downloading_changed(self):
        """ Handles self.downloading trait being changed. """
        self._manage_enables()

    def _clear_stream(self):
        self.stream.reset()

    def _write(self, text):
        """
        Stream style write function. Allows flashing debugging messages to be
        routed to embedded text console.

        Parameters
        ----------
        text : string
          Text to be written to screen.
        """
        self.stream.write(text)
        self.stream.write('\n')
        self.stream.flush()

    def _update_stm_firmware_fired(self):
        """
        Handle update_stm_firmware button. Starts thread so as not to block the GUI
        thread.
        """

        if self.connection_info['mode'] != 'TCP/IP':
            self._write(
                "\n"
                "-----------------------------------------------\n"
                "USB Flashdrive Upgrade Procedure\n"
                "-----------------------------------------------\n"
                "\n"
                "1.\tInsert the USB flash drive provided with your Piksi Multi into your computer.\n"
                "  \tSelect the flash drive root directory as the firmware download destination using the directory chooser above.\n"
                "  \tPress the \"Download Latest Firmware\" button. This will download the latest Piksi Multi firmware file onto\n"
                "  \tthe USB flashdrive.\n"
                "2.\tEject the drive from your computer and plug it into the USB Host port of the Piksi Multi evaluation board.\n"
                "3.\tReset your Piksi Multi and it will upgrade to the version on the USB flash drive.\n"
                "  \tThis should take less than 5 minutes.\n"
                "4.\tWhen the upgrade completes you will be prompted to remove the USB flash drive and reset your Piksi Multi.\n"
                "5.\tVerify that the firmware version has upgraded via inspection of the Current Firmware Version box\n"
                "  \ton the Update Tab of the Swift Console.\n")

            confirm_prompt = prompt.CallbackPrompt(
                title="Update device over serial connection?",
                actions=[
                    prompt.close_button, prompt.continue_via_serial_button
                ],
                callback=self._update_stm_firmware_fn)
            confirm_prompt.text = "\n" \
                                  + "    Upgrading your device via UART / RS232 may take up to 30 minutes.     \n" \
                                  + "                                                                          \n" \
                                  + "    If the device you are upgrading has an accessible USB host port, it   \n" \
                                    "    is recommended to instead  follow the \'USB Flashdrive Upgrade        \n" \
                                    "    Procedure\' that now appears in the Firmware upgrade status box.      \n" \
                                  + "\n" \
                                  + "    Are you sure you want to continue upgrading over serial?"
            confirm_prompt.run(block=False)
        else:
            self._update_stm_firmware_fn()

    def _replace_with_version_2(self):
        self.downloading = True
        self._write('Downloading Multi firmware v2.0.0')
        filepath = self.update_dl._download_file_from_url(V2_LINK)
        self._write('Saved file to %s' % filepath)
        self.stm_fw.load_bin(filepath)
        self.downloading = False

    def _update_stm_firmware_fn(self):
        try:
            if self._firmware_update_thread.is_alive():
                return
        except AttributeError:
            pass

        current_fw_version = parse_version(self.piksi_stm_vers)
        re_result = re.search('[a-zA-Z0-9]*-(v[0-9]*\.[0-9]*\.[0-9])',
                              self.stm_fw.status)
        intended_version = parse_version(re_result.group(1))
        # If the current firmware is not yet beyond 2.0.0, and we are loading beyond 2.0.0
        # warn the user that this upgrade is not possible
        if (current_fw_version < pkparse_version("v2.0.0")
                and intended_version > pkparse_version("v2.0.0")):
            confirm_prompt = prompt.CallbackPrompt(
                title="Update to v2.0.0",
                actions=[prompt.close_button, prompt.ok_button],
                callback=self._replace_with_version_2)
            confirm_prompt.text = "\n" \
                                  + "    Upgrading to firmware v2.1.0 or later requires that the device be     \n" \
                                  + "    running firmware v2.0.0 or later. Please upgrade to firmware          \n" \
                                  + "    version 2.0.0.                                                        \n" \
                                  + "                                                                          \n" \
                                  + "    Would you like to download firmware version v2.0.0 now?               \n" \
                                  + "                                                                          \n"
            confirm_prompt.run(block=False)
            return
        self._firmware_update_thread = Thread(
            target=self.manage_firmware_updates, args=("STM", ))
        self._firmware_update_thread.start()

    def _download_firmware(self):
        """ Download latest firmware from swiftnav.com. """
        self._write('')

        # Check that we received the index file from the website.
        if self.update_dl is None or self.update_dl.index is None:
            self._write("Error: Can't download firmware files")
            return

        self.downloading = True
        status = 'Downloading Latest Firmware...'
        self.stm_fw.clear(status)
        self._write(status)

        # Get firmware files from Swift Nav's website, save to disk, and load.
        if 'fw' in self.update_dl.index[self.piksi_hw_rev]:
            try:
                self._write('Downloading Latest Multi firmware')
                filepath = self.update_dl.download_multi_firmware(
                    self.piksi_hw_rev)
                self._write('Saved file to %s' % filepath)
                self.stm_fw.load_bin(filepath)
            except AttributeError:
                self._write(
                    "Error downloading firmware: index file not downloaded yet"
                )
            except RuntimeError as e:
                self._write(
                    "RunTimeError: unable to download firmware to path {0}: {1}"
                    .format(self.download_directory, e))
            except IOError as e:
                if e.errno == errno.EACCES or e.errno == errno.EPERM:
                    self._write("IOError: unable to write to path %s. "
                                "Verify that the path is writable." %
                                self.download_directory)
                else:
                    raise (e)
            except KeyError:
                self._write(
                    "Error downloading firmware: URL not present in index")
            except URLError:
                self.nap_fw.clear("Error downloading firmware")
                self._write(
                    "Error: Failed to download latest NAP firmware from Swift Navigation's website"
                )
            self.downloading = False
            return

    def _download_firmware_fired(self):
        """
        Handle download_firmware button. Starts thread so as not to block the GUI
        thread.
        """
        try:
            if self._download_firmware_thread.is_alive():
                return
        except AttributeError:
            pass

        self._download_firmware_thread = Thread(target=self._download_firmware)
        self._download_firmware_thread.start()

    def compare_versions(self):
        """
        To be called after latest Piksi firmware info has been received from
        device, to decide if current firmware on Piksi is out of date. Also informs
        user if the firmware was successfully upgraded. Starts a thread so as not
        to block GUI thread.
        """
        try:
            if self._compare_versions_thread.is_alive():
                return
        except AttributeError:
            pass

        self._compare_versions_thread = Thread(target=self._compare_versions)
        self._compare_versions_thread.start()

    def _compare_versions(self):
        """
        Compares version info between received firmware version / current console
        and firmware / console info from website to decide if current firmware or
        console is out of date. Prompt user to update if so. Informs user if
        firmware successfully upgraded.
        """
        # Check that settings received from Piksi contain FW versions.
        try:
            self.piksi_hw_rev = HW_REV_LOOKUP[self.settings['system_info']
                                              ['hw_revision'].value]
            self.piksi_stm_vers = self.settings['system_info'][
                'firmware_version'].value
        except KeyError:
            self._write(
                "\nError: Settings received from Piksi don't contain firmware version keys. Please contact Swift Navigation.\n"
            )
            return

        self.is_v2 = self.piksi_hw_rev.startswith('piksi_v2')

        self._get_latest_version_info()

        # Check that we received the index file from the website.
        if self.update_dl is None:
            self._write(
                "Error: No website index to use to compare versions with local firmware"
            )
            return
        # Get local stm version
        local_stm_version = None
        local_serial_number = None
        try:
            local_stm_version = self.settings['system_info'][
                'firmware_version'].value
            local_serial_number = self.settings['system_info'][
                'serial_number'].value
        except:  # noqa
            pass
        # Check if console is out of date and notify user if so.
        if self.prompt:
            local_console_version = parse_version(CONSOLE_VERSION)
            remote_console_version = parse_version(self.newest_console_vers)
            self.console_outdated = remote_console_version > local_console_version

            # we want to warn users using v2 regardless of version logic
            if self.console_outdated or self.is_v2:
                if not self.is_v2:
                    console_outdated_prompt = \
                        prompt.CallbackPrompt(
                            title="Swift Console Outdated",
                            actions=[prompt.close_button],
                        )
                    console_outdated_prompt.text = \
                        "Your console is out of date and may be incompatible\n" + \
                        "with current firmware. We highly recommend upgrading to\n" + \
                        "ensure proper behavior.\n\n" + \
                        "Please visit http://support.swiftnav.com to\n" + \
                        "download the latest version.\n\n" + \
                        "Local Console Version :\n\t" + \
                        "v" + CONSOLE_VERSION + \
                        "\nLatest Console Version :\n\t" + \
                        self.update_dl.index[self.piksi_hw_rev]['console']['version'] + "\n"
                else:
                    console_outdated_prompt = \
                        prompt.CallbackPrompt(
                            title="Swift Console Incompatible",
                            actions=[prompt.close_button],
                        )
                    console_outdated_prompt.text = \
                        "Your console is incompatible with your hardware revision.\n" + \
                        "We highly recommend using a compatible console version\n" + \
                        "to ensure proper behavior.\n\n" + \
                        "Please visit http://support.swiftnav.com to\n" + \
                        "download the latest compatible version.\n\n" + \
                        "Current Hardware revision :\n\t" + \
                        self.piksi_hw_rev + \
                        "\nLast supported Console Version: \n\t" + \
                        self.update_dl.index[self.piksi_hw_rev]['console']['version'] + "\n"

                console_outdated_prompt.run()

            # For timing aesthetics between windows popping up.
            sleep(0.5)

            # Check if firmware is out of date and notify user if so.
            remote_stm_version = self.newest_stm_vers

            self.fw_outdated = remote_stm_version > local_stm_version
            if local_stm_version.startswith('DEV'):
                self.fw_outdated = False

            if self.fw_outdated:
                fw_update_prompt = \
                    prompt.CallbackPrompt(
                        title='Firmware Update',
                        actions=[prompt.close_button]
                    )

                if 'fw' in self.update_dl.index[self.piksi_hw_rev]:
                    fw_update_prompt.text = \
                        "New Piksi firmware available.\n\n" + \
                        "Please use the Update tab to update.\n\n" + \
                        "Newest Firmware Version :\n\t%s\n\n" % \
                        self.update_dl.index[self.piksi_hw_rev]['fw']['version']
                else:
                    fw_update_prompt.text = \
                        "New Piksi firmware available.\n\n" + \
                        "Please use the Update tab to update.\n\n" + \
                        "Newest STM Version :\n\t%s\n\n" % \
                        self.update_dl.index[self.piksi_hw_rev]['stm_fw']['version'] + \
                        "Newest SwiftNAP Version :\n\t%s\n\n" % \
                        self.update_dl.index[self.piksi_hw_rev]['nap_fw']['version']

                fw_update_prompt.run()

        # Check if firmware successfully upgraded and notify user if so.
        if ((self.last_call_fw_version is not None
             and self.last_call_fw_version != local_stm_version)
                and (self.last_call_sn is None or local_serial_number is None
                     or self.last_call_sn == local_serial_number)):
            fw_success_str = "Firmware successfully upgraded from %s to %s." % \
                             (self.last_call_fw_version, local_stm_version)
            print(fw_success_str)
            self._write(fw_success_str)

        # Record firmware version reported each time this callback is called.
        self.last_call_fw_version = local_stm_version
        self.last_call_sn = local_serial_number

    def _get_latest_version_info(self):
        """ Get latest firmware / console version from website. """
        try:
            self.update_dl = UpdateDownloader(root_dir=self.download_directory)
        except RuntimeError:
            self._write(
                "\nError: Failed to download latest file index from Swift Navigation's website. Please visit our website to check that you're running the latest Piksi firmware and Piksi console.\n"
            )
            self.update_dl = None
            return

        # Make sure index contains all keys we are interested in.
        try:
            if 'fw' in self.update_dl.index[self.piksi_hw_rev]:
                self.newest_stm_vers = self.update_dl.index[
                    self.piksi_hw_rev]['fw']['version']
            else:
                self.newest_stm_vers = self.update_dl.index[
                    self.piksi_hw_rev]['stm_fw']['version']
                self.newest_nap_vers = self.update_dl.index[
                    self.piksi_hw_rev]['nap_fw']['version']
            self.newest_console_vers = self.update_dl.index[
                self.piksi_hw_rev]['console']['version']
        except KeyError:
            self._write(
                "\nError: Index downloaded from Swift Navigation's website (%s) doesn't contain all keys. Please contact Swift Navigation.\n"
                % INDEX_URL)
            return

    def file_transfer_progress_cb(self, arg):
        new_pcent = float(arg) / float(self.blob_size) * 100
        if new_pcent - self.pcent_complete > 0.1:
            self.pcent_complete = new_pcent
            self.stream.scrollback_write(
                "{:2.1f} % of {:2.1f} MB transferred.".format(
                    self.pcent_complete, self.blob_size * 1e-6))

    def log_cb(self, msg, **kwargs):
        for regex in UPGRADE_WHITELIST:
            if re.match(regex, msg.text):
                self.stream.scrollback_write(msg.text.split("\n")[-1])

    def manage_multi_firmware_update(self):
        self.blob_size = float(len(self.stm_fw.blob))
        self.pcent_complete = 0
        # Set up progress dialog and transfer file to Piksi using SBP FileIO
        self._clear_stream()
        self._write(
            "Transferring image to device...\n\n00.0 of {:2.1f} MB trasnferred"
            .format(self.blob_size * 1e-6))
        try:
            FileIO(self.link).write("upgrade.image_set.bin",
                                    self.stm_fw.blob,
                                    progress_cb=self.file_transfer_progress_cb)
        except Exception as e:
            self._write("Failed to transfer image file to Piksi: %s\n" % e)
            self._write("Upgrade Aborted.")
            import traceback
            print(traceback.format_exc())
            return -1

        self.stream.scrollback_write(
            "Image transfer complete: {:2.1f} MB transferred.\n".format(
                self.blob_size * 1e-6))
        # Setup up pulsed progress dialog and commit to flash
        self._write("Committing file to Flash...\n")
        self.link.add_callback(self.log_cb, SBP_MSG_LOG)
        code = shell_command(self.link, "upgrade_tool upgrade.image_set.bin",
                             200)
        self.link.remove_callback(self.log_cb, SBP_MSG_LOG)

        if code != 0:
            self._write('Failed to perform upgrade (code = %d)' % code)
            if code == -255:
                self._write('Shell command timed out.  Please try again.')
            return
        self._write("Upgrade Complete.")
        self._write('Resetting Piksi...')
        self.link(MsgReset(flags=0))

    # Executed in GUI thread, called from Handler.
    def manage_firmware_updates(self, device):
        """
        Update Piksi firmware. Erase entire STM flash (other than bootloader)
        if so directed. Flash NAP only if new firmware is available.
        """
        self.updating = True
        self._write('')
        if not self.is_v2:
            self.manage_multi_firmware_update()
        else:
            self._write(
                'Unable to upgrade piksi v2; please use the last supported v2 console version.'
            )
            self._write("")
        self.updating = False
Exemple #29
0
class ExecutionLayerTemplate(BaseTemplate):

    # ------------------
    # Regular Attributes
    # ------------------

    #: Template referring to the primary surfactant data
    primary_surfactant_template = Instance(IngredientTemplate)

    #: Template referring to the secondary surfactant data
    secondary_surfactant_template = Instance(IngredientTemplate)

    #: A list of possible surfactant ingredient to choose from
    surfactant_template_list = List(IngredientTemplate)

    #: An empty surfactant Template
    empty_surfactant_template = Instance(IngredientTemplate)

    #: Template referring to the chloride ion data
    salt_template = Instance(IngredientTemplate)

    #: Template referring to the solvent data
    solvent_template = Instance(IngredientTemplate)

    #: Internal database of available Ingredient objects
    _gromacs_database = Instance(GromacsDatabase)

    # ------------------
    #     Properties
    # ------------------

    #: Factory ID for Workflow
    id = Property(Unicode, depends_on='plugin_id')

    #: List containing all fragment templates to be used in
    #: a generated Workflow
    ingredient_templates = Property(List(IngredientTemplate),
                                    depends_on='primary_surfactant_template,'
                                    'secondary_surfactant_template')

    #: Number of Ingredients
    n_ingredients = Property(Int, depends_on='ingredient_templates[]')

    # ------------------
    #     Properties
    # ------------------

    #: A list of possible secondary surfactant ingredient, including an
    #: empty ingredient option
    secondary_surfactant_list = Property(List(IngredientTemplate),
                                         depends_on='surfactant_template_list,'
                                         'primary_surfactant_template')

    # ------------------
    #        View
    # ------------------

    traits_view = View(
        HGroup(
            Group(Heading("Primary Surfactant"),
                  Item("primary_surfactant_template",
                       editor=InstanceEditor(name='surfactant_template_list'),
                       style='custom'),
                  show_labels=False),
            Group(Heading("Secondary Surfactant"),
                  Item("secondary_surfactant_template",
                       editor=InstanceEditor(name='secondary_surfactant_list'),
                       style='custom'),
                  show_labels=False)))

    # ------------------
    #      Defaults
    # ------------------

    def _empty_surfactant_template_default(self):
        return IngredientTemplate(plugin_id=self.id)

    def __gromacs_database_default(self):
        return GromacsDatabase()

    def _surfactant_template_list_default(self):

        surfactants = self._gromacs_database.get_role('Surfactant')

        surfactant_templates = [
            IngredientTemplate(plugin_id=self.id, ingredient=surfactant)
            for surfactant in surfactants
        ]

        return surfactant_templates

    def _primary_surfactant_template_default(self):
        return self.surfactant_template_list[0]

    def _secondary_surfactant_template_default(self):
        return self.empty_surfactant_template

    def _salt_template_default(self):

        salt = self._gromacs_database.get_ingredient('Sodium Chloride')

        return IngredientTemplate(plugin_id=self.id, ingredient=salt)

    def _solvent_template_default(self):

        water = self._gromacs_database.get_ingredient('Water')

        return IngredientTemplate(plugin_id=self.id, ingredient=water)

    # ------------------
    #      Listeners
    # ------------------

    def _get_id(self):
        return '.'.join([self.plugin_id, "factory"])

    @cached_property
    def _get_ingredient_templates(self):
        templates = [
            self.primary_surfactant_template, self.salt_template,
            self.solvent_template
        ]

        if self.secondary_surfactant_template.ingredient:
            templates.insert(1, self.secondary_surfactant_template)

        return templates

    @cached_property
    def _get_n_ingredients(self):
        return len(self.ingredient_templates)

    @cached_property
    def _get_secondary_surfactant_list(self):

        secondary_list = [self.empty_surfactant_template]
        secondary_list += [
            surfactant for surfactant in self.surfactant_template_list
            if surfactant != self.primary_surfactant_template
        ]

        return secondary_list

    @on_trait_change('primary_surfactant_template')
    def update_secondary_surfactant_template(self):
        if (self.primary_surfactant_template ==
                self.secondary_surfactant_template):
            self.secondary_surfactant_template = (
                self._secondary_surfactant_template_default())

    @on_trait_change('plugin_id')
    def update_plugin_id(self):
        for surfactant_template in self.surfactant_template_list:
            surfactant_template.plugin_id = self.id
        self.empty_surfactant_template.plugin_id = self.id
        self.salt_template.plugin_id = self.id
        self.solvent_template.plugin_id = self.id

    # ------------------
    #   Public Methods
    # ------------------

    def create_database_templates(self):

        templates = []

        for ingredient_template in self.ingredient_templates:
            template = ingredient_template.create_template()
            templates.append(template)

        return templates

    def create_formulation_template(self):

        input_slot_info = []

        for template in self.ingredient_templates:
            if template.ingredient.role != 'Solvent':
                input_slot_info.append(
                    {"name": f"{template.variable_name}_ingredient"})
                input_slot_info.append(
                    {"name": f"{template.variable_name}_conc"})
            else:
                input_slot_info.append(
                    {"name": f"{template.variable_name}_ingredient"})

        return {
            "id": f"{self.id}.formulation",
            "model_data": {
                "n_surfactants": self.n_ingredients - 2,
                "input_slot_info": input_slot_info,
                "output_slot_info": [{
                    "name": "formulation"
                }]
            }
        }

    def create_simulation_template(self):

        return {
            "id": f"{self.id}.simulation",
            "model_data": {
                "name": "surfactant_experiment",
                "size": 500,
                "dry_run": False,
                "input_slot_info": [{
                    "name": "formulation"
                }],
                "output_slot_info": [{
                    "name": "results"
                }]
            }
        }

    def create_micelle_template(self):

        surfactants = [
            template.ingredient for template in self.ingredient_templates
            if template.ingredient.role == 'Surfactant'
        ]

        fragment_symbols = [
            surfactant.fragments[0].symbol for surfactant in surfactants
        ]

        return {
            "id": f"{self.id}.micelle",
            "model_data": {
                "method": 'atomic',
                "fragment_symbols": fragment_symbols,
                "r_thresh": 0.98,
                "input_slot_info": [{
                    "name": "formulation"
                }, {
                    "name": "results"
                }],
                "output_slot_info": [{
                    "name": "micelle"
                }]
            }
        }

    def create_viscosity_template(self):
        return {
            "id": f"{self.id}.viscosity",
            "model_data": {
                "input_slot_info": [{
                    "name": "results"
                }],
                "output_slot_info": [{
                    "name": "viscosity"
                }]
            }
        }

    def create_cost_template(self):
        return {
            "id": f"{self.id}.cost",
            "model_data": {
                "input_slot_info": [{
                    "name": "formulation"
                }],
                "output_slot_info": [{
                    "name": "cost"
                }]
            }
        }

    def create_template(self):
        return [{
            'data_sources': self.create_database_templates()
        }, {
            'data_sources': [self.create_formulation_template()]
        }, {
            'data_sources': [self.create_simulation_template()]
        }, {
            'data_sources':
            [self.create_micelle_template(),
             self.create_cost_template()]
        }]
Exemple #30
0
class Workflow(HasStrictTraits):

    workflow = List(WorkflowItem)
    backup_workflow = List(WorkflowItem)  # for the TASBE task
    selected = Instance(WorkflowItem)

    modified = Bool
    debug = Bool

    single_operation = View(
        Item('selected',
             editor=InstanceEditor(view='operation_traits'),
             style='custom',
             show_label=False))

    # a view for the entire workflow's list of operations
    operations_traits = View(Item('workflow',
                                  editor=VerticalNotebookEditor(
                                      view='operation_traits',
                                      page_name='.name',
                                      page_description='.friendly_id',
                                      page_icon='.icon',
                                      delete=True,
                                      page_deletable='.deletable',
                                      selected='selected',
                                      multiple_open=False),
                                  show_label=False),
                             scrollable=True)

    # a view showing the selected workflow item's current view
    selected_view_traits = View(Item(
        'selected',
        editor=InstanceEditor(view='current_view_traits'),
        style='custom',
        show_label=False),
                                Spring(),
                                Item('apply_calls',
                                     style='readonly',
                                     visible_when='debug'),
                                Item('plot_calls',
                                     style='readonly',
                                     visible_when='debug'),
                                kind='panel',
                                scrollable=True)

    # the view for the center pane
    plot_view = View(
        Item('selected',
             editor=InstanceEditor(view='current_plot_view'),
             style='custom',
             show_label=False))

    plot_params_traits = View(
        Item('selected',
             editor=InstanceEditor(view='plot_params_traits'),
             style='custom',
             show_label=False))

    recv_thread = Instance(threading.Thread)
    send_thread = Instance(threading.Thread)
    log_thread = Instance(threading.Thread)
    remote_process_thread = Instance(threading.Thread)
    message_q = Instance(Queue, ())

    # the Pipe connection object to pass to the matplotlib canvas
    child_matplotlib_conn = Any

    # count the number of times the remote process calls apply() or plot().
    # useful for debugging
    apply_calls = Int(0)
    plot_calls = Int(0)

    # evaluate an expression in the remote process.  useful for debugging.
    eval_event = Instance(threading.Event, ())
    eval_result = Any

    # evaluate an expression in the remote process.  useful for debugging.
    eval_event = Instance(threading.Event, ())
    eval_result = Any

    def __init__(self, remote_connection, **kwargs):
        super(Workflow, self).__init__(**kwargs)

        child_workflow_conn, self.child_matplotlib_conn, log_q = remote_connection

        self.recv_thread = threading.Thread(target=self.recv_main,
                                            name="local workflow recv",
                                            args=[child_workflow_conn])
        self.recv_thread.daemon = True
        self.recv_thread.start()

        self.send_thread = threading.Thread(target=self.send_main,
                                            name="local workflow send",
                                            args=[child_workflow_conn])
        self.send_thread.daemon = True
        self.send_thread.start()

        self.log_thread = threading.Thread(target=self.log_main,
                                           name="log listener thread",
                                           args=[log_q])
        self.log_thread.daemon = True
        self.log_thread.start()

    def recv_main(self, child_conn):
        while child_conn.poll(None):
            try:
                (msg, payload) = child_conn.recv()
            except EOFError:
                return

            logging.debug("LocalWorkflow.recv_main :: {}".format(msg))

            try:
                if msg == Msg.UPDATE_WI:
                    (idx, name, new) = payload
                    wi = self.workflow[idx]

                    if not wi.trait(name).status:
                        raise RuntimeError(
                            "Tried to set a local non-status wi trait")

                    with wi.lock:
                        wi.trait_set(**{name: new})

                elif msg == Msg.UPDATE_OP:
                    (idx, name, new) = payload
                    wi = self.workflow[idx]

                    if not wi.operation.trait(name).status:
                        raise RuntimeError(
                            "Tried to set a local non-status trait")

                    with wi.lock:
                        wi.operation.trait_set(**{name: new})

                elif msg == Msg.UPDATE_VIEW:
                    (idx, view_id, name, new) = payload
                    wi = self.workflow[idx]
                    view = next((x for x in wi.views if x.id == view_id))

                    if not view.trait(name).status:
                        raise RuntimeError(
                            "Tried to set a local non-status trait")

                    with wi.lock:
                        view.trait_set(**{name: new})

                elif msg == Msg.APPLY_CALLED:
                    self.apply_calls = payload

                elif msg == Msg.PLOT_CALLED:
                    self.plot_calls = payload

                elif msg == Msg.EVAL:
                    self.eval_result = payload
                    self.eval_event.set()

                else:
                    raise RuntimeError("Bad message from remote")

            except Exception:
                log_exception()

    def send_main(self, child_conn):
        try:
            while True:
                msg = self.message_q.get()
                child_conn.send(msg)
        except Exception:
            log_exception()

    def log_main(self, log_q):
        # from http://plumberjack.blogspot.com/2010/09/using-logging-with-multiprocessing.html

        while True:
            try:
                record = log_q.get()
                if record is None:  # We send this as a sentinel to tell the listener to quit.
                    break
                logger = logging.getLogger(record.name)
                logger.handle(
                    record)  # No level or filter logic applied - just do it!
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                print('Whoops! Problem:', file=sys.stderr)
                traceback.print_exc(file=sys.stderr)

    def shutdown_remote_process(self):
        self.message_q.put((Msg.SHUTDOWN, None))

    def run_all(self):
        self.message_q.put((Msg.RUN_ALL, None))

    @on_trait_change('workflow')
    def _on_new_workflow(self, obj, name, old, new):
        logging.debug("LocalWorkflow._on_new_workflow")

        self.selected = None

        # send the new workflow to the child process
        self.message_q.put((Msg.NEW_WORKFLOW, self.workflow))

    @on_trait_change('workflow_items')
    def _on_workflow_add_remove_items(self, event):
        logging.debug(
            "LocalWorkflow._on_workflow_add_remove_items :: {}".format(
                (event.index, event.removed, event.added)))

        idx = event.index
        self.modified = True

        # remove deleted items from the linked list
        if event.removed:
            assert len(event.removed) == 1
            removed = event.removed[0]
            if removed.previous_wi:
                removed.previous_wi.next_wi = removed.next_wi

            if removed.next_wi:
                removed.next_wi.previous_wi = removed.previous_wi

            self.message_q.put((Msg.REMOVE_ITEMS, idx))

            if removed == self.selected:
                self.selected = None

        # add new items to the linked list
        if event.added:
            assert len(event.added) == 1

            if idx > 0:
                # populate the new wi with metadata from the old one
                self.workflow[idx].channels = list(self.workflow[idx -
                                                                 1].channels)
                self.workflow[idx].conditions = dict(
                    self.workflow[idx - 1].conditions)
                self.workflow[idx].metadata = dict(self.workflow[idx -
                                                                 1].metadata)
                self.workflow[idx].statistics = dict(
                    self.workflow[idx - 1].statistics)

                self.workflow[idx - 1].next_wi = self.workflow[idx]
                self.workflow[idx].previous_wi = self.workflow[idx - 1]

            if idx < len(self.workflow) - 1:
                self.workflow[idx].next_wi = self.workflow[idx + 1]
                self.workflow[idx + 1].previous_wi = self.workflow[idx]

            self.message_q.put((Msg.ADD_ITEMS, (idx, event.added[0])))

    @on_trait_change('selected')
    def _on_selected_changed(self, obj, name, old, new):
        logging.debug("LocalWorkflow._on_selected_changed :: {}".format(
            (obj, name, old, new)))

        if new is None:
            idx = -1
        else:
            idx = self.workflow.index(new)

        self.message_q.put((Msg.SELECT, idx))

    @on_trait_change('workflow:operation:+')
    def _operation_changed(self, obj, name, old, new):
        logging.debug("LocalWorkflow._operation_changed :: {}".format(
            (obj, name, old, new)))

        if not obj.trait(name).transient and not obj.trait(name).status:
            wi = next((x for x in self.workflow if x.operation == obj))
            idx = self.workflow.index(wi)
            self.message_q.put((Msg.UPDATE_OP, (idx, name, new)))
            self.modified = True

    @on_trait_change('workflow:operation:changed')
    def _operation_changed_event(self, obj, _, new):
        logging.debug("LocalWorkflow._operation_changed_event:: {}".format(
            (obj, new)))

        (_, (name, new)) = new

        wi = next((x for x in self.workflow if x.operation == obj))
        idx = self.workflow.index(wi)
        self.message_q.put((Msg.UPDATE_OP, (idx, name, new)))
        self.modified = True

    @on_trait_change('workflow:views:+')
    def _view_changed(self, obj, name, old, new):
        logging.debug("LocalWorkflow._view_changed :: {}".format(
            (obj, name, old, new)))

        if not obj.trait(name).transient and not obj.trait(name).status:
            wi = next((x for x in self.workflow if obj in x.views))
            idx = self.workflow.index(wi)
            self.message_q.put((Msg.UPDATE_VIEW, (idx, obj.id, name, new)))
            self.modified = True

    @on_trait_change('workflow:views:changed')
    def _view_changed_event(self, obj, _, new):
        logging.debug("LocalWorkflow._view_changed_event:: {}".format(
            (obj, new)))

        (_, (_, name, new)) = new

        wi = next((x for x in self.workflow if obj in x.views))
        idx = self.workflow.index(wi)
        self.message_q.put((Msg.UPDATE_VIEW, (idx, obj.id, name, new)))
        self.modified = True

    @on_trait_change('workflow:current_view')
    def _on_current_view_changed(self, obj, name, old, new):
        logging.debug("LocalWorkflow._on_current_view_changed :: {}".format(
            (obj, name, old, new)))

        idx = self.workflow.index(obj)
        view = obj.current_view
        self.message_q.put((Msg.CHANGE_CURRENT_VIEW, (idx, view)))

    @on_trait_change('workflow:current_plot')
    def _on_current_plot_changed(self, obj, name, old, new):
        logging.debug("LocalWorkflow._on_current_plot_changed :: {}".format(
            (obj, name, old, new)))

        idx = self.workflow.index(obj)
        plot = obj.current_plot
        self.message_q.put((Msg.CHANGE_CURRENT_PLOT, (idx, plot)))

    @on_trait_change('workflow:operation:do_estimate')
    def _on_estimate(self, obj, name, old, new):
        logging.debug("LocalWorkflow._on_estimate :: {}".format(
            (obj, name, old, new)))

        wi = next((x for x in self.workflow if x.operation == obj))
        idx = self.workflow.index(wi)
        self.message_q.put((Msg.ESTIMATE, idx))

    def remote_eval(self, expr):
        self.eval_event.clear()
        self.message_q.put((Msg.EVAL, expr))

        self.eval_event.wait()
        return self.eval_result

    def remote_exec(self, expr):
        self.message_q.put((Msg.EXEC, expr))