コード例 #1
0
class GroundStationWidget(QWidget):
    def __init__(self):
        super(GroundStationWidget, self).__init__()

        self._principle_layout = QBoxLayout(0)  # main layout is horizontal (0)
        self._principle_layout = QSplitter(Qt.Horizontal)
        self._map_layout = QVBoxLayout()
        map_widget = QWidget()
        map_widget.setLayout(self._map_layout)
        self._principle_layout.addWidget(map_widget)
        self._control_layout = QVBoxLayout()
        control_widget = QWidget()
        control_widget.setLayout(self._control_layout)
        self._principle_layout.addWidget(control_widget)

        self.setAcceptDrops(False)  # Dragging and Dropping not permitted
        self.setWindowTitle('ROSplane Ground Station')

        #=============================
        self._mw = MapWindow()
        self._map_layout.addWidget(self._mw)
        self._tv = PlotWidget()
        self._data_plot = DataPlot(self._tv)
        self._data_plot.set_autoscale(x=False)
        self._data_plot.set_autoscale(y=DataPlot.SCALE_EXTEND
                                      | DataPlot.SCALE_VISIBLE)
        self._data_plot.set_xlim([0, 10.0])
        self._tv.switch_data_plot_widget(self._data_plot)

        self._control_layout.addWidget(
            self._tv,
            1)  # ratio of these numbers determines window proportions
        self._ah = ArtificialHorizon()
        self._control_layout.addWidget(self._ah, 1)
        #=============================

        print('fake init')
        total_layout = QBoxLayout(QBoxLayout.TopToBottom)
        self._principle_layout.setHandleWidth(8)
        total_layout.addWidget(self._principle_layout)
        self.setLayout(total_layout)

        # Global timer for marble_map and artificial_horizon
        self.interval = 100  # in milliseconds, period of regular update
        self.timer = QTimer(self)
        self.timer.setInterval(self.interval)
        self.timer.timeout.connect(self._mw._marble_map.update)
        self.timer.timeout.connect(self._ah.update)
        self.timer.start()

    def closeEvent(self, event):
        self.timer.stop()

    def save_settings(self, plugin_settings,
                      instance_settings):  # have a file to read and write from
        print('fake save')  # < prints to terminal

    def restore_settings(self, plugin_settings, instance_settings):
        print('fake restore')
コード例 #2
0
ファイル: visualizer.py プロジェクト: team-aisaac/consai2
class Visualizer(Plugin):
    def __init__(self, context):
        super(Visualizer, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('Visualizer')

        # Process standalone plugin command-line arguments
        # from argparse import ArgumentParser
        # parser = ArgumentParser()
        # Add argument(s) to the parser.
        # parser.add_argument("-q", "--quiet", action="store_true",
        #       dest="quiet",
        #       help="Put plugin in silent mode")
        # args, unknowns = parser.parse_known_args(context.argv())
        # if not args.quiet:
        #     print 'arguments: ', args
        #     print 'unknowns: ', unknowns

        # Create QWidget
        self._widget = QWidget()
        # Get path to UI file which should be in the "resource" folder of this package
        ui_file = os.path.join(rospkg.RosPack().get_path('consai2_gui'),
                               'resource', 'Visualizer.ui')
        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget, {"PaintWidget": PaintWidget})
        # Give QObjects reasonable names
        self._widget.setObjectName('VisualizerUi')
        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        self._redraw_timer = QTimer()
        self._redraw_timer.setInterval(16)
        self._redraw_timer.timeout.connect(self._widget.update)
        self._redraw_timer.start()

    def shutdown_plugin(self):
        pass

    def save_settings(self, plugin_settings, instance_settings):
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        pass
コード例 #3
0
class RosPlot(FigureCanvas):
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
    def __init__(self, parent, label_name, topic_name, topic_type, topic_field,
                 buffer_size):

        self.label_name = label_name
        self.topic_type = topic_type
        self.topic_name = topic_name
        self.topic_field = topic_field
        self.buffer_size = buffer_size

        self.timer = QTimer()
        self.timer.setInterval(100)
        self.timer.timeout.connect(self.update_figure)

        fig = Figure(figsize=(5, 4), dpi=100)
        self.axes = fig.add_subplot(111)
        # We want the axes cleared every time plot() is called
        self.axes.hold(False)

        self.compute_initial_figure()
        self.buffer = collections.deque(maxlen=self.buffer_size)

        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

        self.subscriber = RosHelper.create_subscriber_from_type(
            self.topic_name, self.topic_type, self.on_message)
        self.timer.start()

    def compute_initial_figure(self):
        pass

    def on_message(self, msg):
        r = msg
        for subfields in self.topic_field.split(".")[1:]:
            r = getattr(r, subfields)
        self.buffer.append(r)

    def update_figure(self):
        x = np.array(range(0, len(self.buffer)))
        y = np.array(self.buffer)
        self.axes.plot(x, y.T, 'r')
        self.draw()
コード例 #4
0
ファイル: RosPlot.py プロジェクト: AliquesTomas/FroboMind
class RosPlot(FigureCanvas):
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
    def __init__(self, parent, label_name,topic_name,topic_type,topic_field,buffer_size):
        
        self.label_name= label_name
        self.topic_type = topic_type
        self.topic_name = topic_name
        self.topic_field = topic_field
        self.buffer_size = buffer_size
        
        self.timer = QTimer()
        self.timer.setInterval(100)
        self.timer.timeout.connect(self.update_figure)
        
        fig = Figure(figsize=(5, 4), dpi=100)
        self.axes = fig.add_subplot(111)
        # We want the axes cleared every time plot() is called
        self.axes.hold(False)

        self.compute_initial_figure()
        self.buffer = collections.deque(maxlen=self.buffer_size)

        
        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)
        
        self.subscriber = RosHelper.create_subscriber_from_type(self.topic_name,self.topic_type,self.on_message)
        self.timer.start()

    def compute_initial_figure(self):
        pass
    
    def on_message(self,msg):
        r = msg
        for subfields in self.topic_field.split(".")[1:]:
            r = getattr(r,subfields) 
        self.buffer.append(r)
        
    def update_figure(self):
        x = np.array(range(0,len(self.buffer)))
        y = np.array(self.buffer)
        self.axes.plot(x, y.T, 'r')
        self.draw()
コード例 #5
0
class Visualizer(Plugin):
    def __init__(self, context):
        super(Visualizer, self).__init__(context)
        self.setObjectName("Visualizer")

        self._widget = QWidget()

        ui_file = os.path.join(rospkg.RosPack().get_path("sai_visualizer"), \
                "resource", "visualizer_widget.ui")
        loadUi(ui_file, self._widget, {"PaintWidget": PaintWidget})
        self._widget.setObjectName("VisualizerUi")

        self._widget.setWindowTitle("SAI Visualizer")

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        self._redraw_timer = QTimer()
        self._redraw_timer.setInterval(16)
        self._redraw_timer.timeout.connect(self._widget.update)
        self._redraw_timer.start()

    def shutdown_plugin(self):
        # TODO unregister all publishers here
        pass

    def save_settings(self, plugin_settings, instance_settings):
        # TODO save intrinsic configuration, usually using:
        # instance_settings.set_value(k, v)
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass
コード例 #6
0
ファイル: conman.py プロジェクト: kuka-isir/conman
class Conman(Plugin):
    update_graph_sig = Signal(str)

    def __init__(self, context):
        super(Conman, self).__init__(context)

        self._dotcode_sub = None
        self._topic_dict = {}
        self._update_thread = WorkerThread(self._update_thread_run,
                                           self._update_finished)
        # Give QObjects reasonable names
        self.setObjectName('Conman')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()
        # Add argument(s) to the parser.
        parser.add_argument("-q",
                            "--quiet",
                            action="store_true",
                            dest="quiet",
                            help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        if not args.quiet:
            print 'arguments: ', args
            print 'unknowns: ', unknowns

        # Create QWidget
        self._widget = QWidget()
        # Get path to UI file which is a sibling of this file
        # in this example the .ui and .py file are in the same folder
        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                               'rqt_conman.ui')
        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget)
        # Give QObjects reasonable names

        self._widget.ns_refresh_button.setIcon(QIcon.fromTheme('view-refresh'))
        self._widget.setObjectName('ConmanPluginUi')
        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))

        # Add widget to the user interface
        context.add_widget(self._widget)
        palette = QPalette()
        palette.setColor(QPalette.Background, Qt.white)
        self._widget.setPalette(palette)

        #self._widget.subscribe_button.setCheckable(True)
        self._widget.namespace_input.currentIndexChanged.connect(
            self._handle_refresh_clicked)
        self._widget.ns_refresh_button.clicked.connect(self.refresh_combo_box)
        self._widget.refresh_button.clicked[bool].connect(
            self._handle_refresh_clicked)
        self._widget.commit_button.clicked[bool].connect(
            self._handle_commit_clicked)

        #self._widget.xdot_widget.connect(
        #self._widget.xdot_widget, SIGNAL('_update_graph'), self._widget.xdot_widget.set_dotcode)
        self.update_graph_sig.connect(self._update_graph)

        self.blocks = {}
        self.groups = {}

        self._ns = ""
        self._actions_connected = False
        self.enable_widgets(False)
        self.new_dotcode_data = ''

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(50)
        self.update_timer.timeout.connect(self._update_widgets)
        #self.update_timer.start()

        self._get_blocks = None
        self._set_blocks = None

        self._blocks_model = QStandardItemModel(0, 4)
        self._blocks_model.setHeaderData(0, Qt.Horizontal, "")
        self._blocks_model.setHeaderData(1, Qt.Horizontal, "Action")
        self._blocks_model.setHeaderData(2, Qt.Horizontal, "State")
        self._blocks_model.setHeaderData(3, Qt.Horizontal, "Block")
        self._widget.blocks_table.setModel(self._blocks_model)
        self._blocks_delegate = BlocksDelegate(self)
        self._widget.blocks_table.setItemDelegate(self._blocks_delegate)
        self._blocks_model.itemChanged.connect(self.block_changed)

        self._groups_model = QStandardItemModel(0, 4)
        self._groups_model.setHeaderData(0, Qt.Horizontal, "")
        self._groups_model.setHeaderData(1, Qt.Horizontal, "")
        self._groups_model.setHeaderData(2, Qt.Horizontal, "")
        self._groups_model.setHeaderData(3, Qt.Horizontal, "Group")
        self._widget.groups_table.setModel(self._groups_model)
        self._groups_delegate = GroupsDelegate(self)
        self._widget.groups_table.setItemDelegate(self._groups_delegate)

        self.refresh_combo_box()

    def block_changed(self, item):
        row = item.row()
        name = self._blocks_model.item(row, 3).text()
        block = self.blocks[name]
        checked = item.checkState() == Qt.Checked

    def shutdown_plugin(self):
        # TODO unregister all publishers here
        self._update_thread.kill()
        pass

    def save_settings(self, plugin_settings, instance_settings):
        # TODO save intrinsic configuration, usually using:
        # instance_settings.set_value(k, v)
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass

    #def trigger_configuration(self):
    # Comment in to signal that the plugin has a way to configure
    # This will enable a setting button (gear icon) in each dock widget title bar
    # Usually used to open a modal configuration dialog

    @Slot()
    def refresh_combo_box(self):
        self._update_thread.kill()
        self._widget.namespace_input.setEnabled(False)
        self._widget.namespace_input.setEditText('updating...')
        self._update_thread.start()

    def _update_thread_run(self):
        _, _, topic_types = rospy.get_master().getTopicTypes()
        self._topic_dict = dict(topic_types)
        keys = list(self._topic_dict.keys())
        namespaces = list()
        for i in keys:
            if i.endswith("get_blocks_action/goal"):
                namespaces.append(i[0:i.index("get_blocks_action/goal")])

        self._widget.namespace_input.setItems.emit(namespaces)

    @Slot()
    def _update_finished(self):
        self._widget.namespace_input.setEnabled(True)

    def _get_result_cb(self, status, res):
        rospy.loginfo("got result!")
        self._blocks_model.setRowCount(0)
        self._blocks_model.setRowCount(len(res.blocks))

        for (i, block) in zip(range(len(res.blocks)), res.blocks):

            # Store in dict
            self.blocks[block.name] = block

            cb = QStandardItem(True)
            cb.setCheckable(True)
            cb.setCheckState(Qt.Checked if block.state.value == conman_msgs.
                             msg.TaskState.RUNNING else Qt.Unchecked)
            cb.setTextAlignment(Qt.AlignHCenter)
            cb.setTristate(True)

            action = QStandardItem(True)
            action.setText("")

            state = QStandardItem(True)
            state.setText(state_map[block.state.value])

            name = QStandardItem(True)
            name.setText(str(block.name))

            self._blocks_model.setItem(i, 0, cb)
            self._blocks_model.setItem(i, 1, action)
            self._blocks_model.setItem(i, 2, state)
            self._blocks_model.setItem(i, 3, name)

        for (i, group) in zip(range(len(res.groups)), res.groups):

            self.groups[group.name] = group

            cb = QStandardItem(True)
            cb.setCheckable(True)
            cb.setCheckState(Qt.Checked)
            cb.setTextAlignment(Qt.AlignHCenter)
            cb.setEnabled(False)

            name = QStandardItem(True)
            name.setText(str(group.name))

            self._groups_model.setItem(i, 0, cb)
            self._groups_model.setItem(i, 3, name)

        self._update_groups()
        self._update_blocks()

        self._widget.blocks_table.resizeColumnsToContents()
        self._widget.blocks_table.horizontalHeader().setStretchLastSection(
            True)
        self._widget.groups_table.resizeColumnsToContents()
        self._widget.groups_table.horizontalHeader().setStretchLastSection(
            True)

    def _update_blocks(self):

        for (name, block) in self.blocks.items():
            items = self._blocks_model.findItems(name, column=3)
            if len(items) > 0:
                item = items[0]
            else:
                continue

            row = item.row()
            checked = self._blocks_model.item(row,
                                              0).checkState() == Qt.Checked
            if checked and block.state.value != conman_msgs.msg.TaskState.RUNNING:
                self._blocks_model.item(row, 1).setText("ENABLE")
            elif not checked and block.state.value == conman_msgs.msg.TaskState.RUNNING:
                self._blocks_model.item(row, 1).setText("DISABLE")
            else:
                self._blocks_model.item(row, 1).setText("")
        self._update_groups()

    def _enable_group(self, index, enable):
        name = self._groups_model.item(index, 3).text()
        group = self.groups[name]

        for member in group.members:
            items = self._blocks_model.findItems(member, column=3)
            if len(items) > 0:
                item = items[0]
            else:
                continue

            row = item.row()
            self._blocks_model.item(
                row, 0).setCheckState(Qt.Checked if enable else Qt.Unchecked)

        self._update_blocks()

    def _update_groups(self):
        for (name, group) in self.groups.items():
            group_items = self._groups_model.findItems(name, column=3)
            if len(group_items) > 0:
                group_item = group_items[0]
            else:
                continue
            group_row = group_item.row()

            members_checked = []

            for member in group.members:
                items = self._blocks_model.findItems(member, column=3)
                if len(items) > 0:
                    item = items[0]
                else:
                    continue
                row = item.row()
                members_checked.append(
                    self._blocks_model.item(row, 0).checkState() == Qt.Checked)

            if all(members_checked):
                check = Qt.Checked
            else:
                check = Qt.Unchecked

            self._groups_model.item(group_row, 0).setCheckState(check)

    def _query_blocks(self):
        if self._get_blocks and self._actions_connected:
            if self._get_blocks.simple_state == actionlib.SimpleGoalState.DONE:
                rospy.loginfo("Getting blocks...")
                goal = conman_msgs.msg.GetBlocksGoal()
                goal.publish_flow_graph = self._widget.generate_graph_checkbox.isChecked(
                )
                self._get_blocks.send_goal(goal, done_cb=self._get_result_cb)

    def enable_widgets(self, enable):

        #self._widget.generate_graph_checkbox.setEnabled(enable)
        self._widget.force_enable_checkbox.setEnabled(enable)
        #self._widget.disable_unused_button.setEnabled(enable)
        self._widget.xdot_widget.setEnabled(enable)
        self._widget.blocks_table.setEnabled(enable)
        self._widget.groups_table.setEnabled(enable)
        self._widget.regenerate_graph_button.setEnabled(enable)

    def _handle_refresh_clicked(self, checked):
        ns = self._widget.namespace_input.currentText()

        if len(ns) > 0:
            if self._ns != ns:
                self._actions_connected = False
                self._ns = ns
                self.enable_widgets(False)

                self._dotcode_sub = rospy.Subscriber(ns + '/dotcode',
                                                     std_msgs.msg.String,
                                                     self._dotcode_msg_cb)

                self._get_blocks = actionlib.SimpleActionClient(
                    ns + '/get_blocks_action', conman_msgs.msg.GetBlocksAction)

                self._set_blocks = actionlib.SimpleActionClient(
                    ns + '/set_blocks_action', conman_msgs.msg.SetBlocksAction)

                rospy.loginfo("Creating action clients on namespace '%s'..." %
                              ns)

                if not self._get_blocks.wait_for_server(rospy.Duration(2)):
                    rospy.loginfo("Timed out waiting for %s." %
                                  self._get_blocks.action_client.ns)
                    return
                if not self._set_blocks.wait_for_server(rospy.Duration(2)):
                    rospy.loginfo("Timed out waiting for %s." %
                                  self._set_blocks.action_client.ns)
                    return

                rospy.loginfo("Action clients created.")
                self._actions_connected = True
                self.enable_widgets(True)

            self._query_blocks()

    def _handle_commit_clicked(self, checked):
        if self._set_blocks and self._actions_connected:
            if self._get_blocks.simple_state == actionlib.SimpleGoalState.DONE:
                rospy.loginfo("Setting blocks...")
                goal = conman_msgs.msg.SetBlocksGoal()
                goal.diff = True
                goal.force = True

                for i in range(self._blocks_model.rowCount()):
                    name = self._blocks_model.item(i, 3).text()
                    action = self._blocks_model.item(i, 1).text()

                    if action == 'DISABLE':
                        goal.disable.append(name)
                    elif action == 'ENABLE':
                        goal.enable.append(name)

                self._set_blocks.send_goal(goal,
                                           done_cb=self._get_set_result_cb)

    def _get_set_result_cb(self, status, res):

        self._query_blocks()

    @Slot(str)
    def _update_graph(self, dotcode):
        global initialized
        if initialized:
            self._widget.xdot_widget.set_dotcode(dotcode, center=False)
        else:
            self._widget.xdot_widget.set_dotcode(dotcode, center=True)
            self._widget.xdot_widget.zoom_to_fit()
            initialized = 1

    def _dotcode_msg_cb(self, msg):
        #self.new_dotcode_data = msg.data
        self.update_graph_sig.emit(msg.data)

    def _update_widgets(self):
        self._update_groups()
        self._update_blocks()
コード例 #7
0
class Top(Plugin):
    NODE_FIELDS = [
        'pid', 'get_cpu_percent', 'get_memory_percent', 'get_num_threads'
    ]
    OUT_FIELDS = [
        'node_name', 'pid', 'cpu_percent', 'memory_percent', 'num_threads'
    ]
    FORMAT_STRS = ['%s', '%s', '%0.2f', '%0.2f', '%s']
    NODE_LABELS = ['Node', 'PID', 'CPU %', 'Mem %', 'Num Threads']
    SORT_TYPE = [str, str, float, float, float]
    TOOLTIPS = {
        0: ('cmdline', lambda x: '\n'.join(textwrap.wrap(' '.join(x)))),
        3: ('memory_info', lambda x:
            ('Resident: %0.2f MiB, Virtual: %0.2f MiB' %
             (x[0] / 2**20, x[1] / 2**20)))
    }

    _node_info = NodeInfo()

    name_filter = re.compile('')

    def __init__(self, context):
        super(Top, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('Top')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()
        # Add argument(s) to the parser.
        parser.add_argument("-q",
                            "--quiet",
                            action="store_true",
                            dest="quiet",
                            help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        # if not args.quiet:
        #     print 'arguments: ', args
        #     print 'unknowns: ', unknowns

        self._selected_node = ''
        self._selected_node_lock = RLock()

        # Setup the toolbar
        self._toolbar = QToolBar()
        self._filter_box = QLineEdit()
        self._regex_box = QCheckBox()
        self._regex_box.setText('regex')
        self._toolbar.addWidget(QLabel('Filter'))
        self._toolbar.addWidget(self._filter_box)
        self._toolbar.addWidget(self._regex_box)

        self._filter_box.returnPressed.connect(self.update_filter)
        self._regex_box.stateChanged.connect(self.update_filter)

        # Create a container widget and give it a layout
        self._container = QWidget()
        self._container.setWindowTitle('Process Monitor')
        self._layout = QVBoxLayout()
        self._container.setLayout(self._layout)

        self._layout.addWidget(self._toolbar)

        # Create the table widget
        self._table_widget = QTreeWidget()
        self._table_widget.setObjectName('TopTable')
        self._table_widget.setColumnCount(len(self.NODE_LABELS))
        self._table_widget.setHeaderLabels(self.NODE_LABELS)
        self._table_widget.itemClicked.connect(self._tableItemClicked)
        self._table_widget.setSortingEnabled(True)
        self._table_widget.setAlternatingRowColors(True)

        self._layout.addWidget(self._table_widget)
        context.add_widget(self._container)

        # Add a button for killing nodes
        self._kill_button = QPushButton('Kill Node')
        self._layout.addWidget(self._kill_button)
        self._kill_button.clicked.connect(self._kill_node)

        # Update twice since the first cpu% lookup will always return 0
        self.update_table()
        self.update_table()

        self._table_widget.resizeColumnToContents(0)

        # Start a timer to trigger updates
        self._update_timer = QTimer()
        self._update_timer.setInterval(1000)
        self._update_timer.timeout.connect(self.update_table)
        self._update_timer.start()

    def _tableItemClicked(self, item, column):
        with self._selected_node_lock:
            self._selected_node = item.text(0)

    def update_filter(self, *args):
        if self._regex_box.isChecked():
            expr = self._filter_box.text()
        else:
            expr = re.escape(self._filter_box.text())
        self.name_filter = re.compile(expr)
        self.update_table()

    def _kill_node(self):
        self._node_info.kill_node(self._selected_node)

    def update_one_item(self, row, info):
        twi = TopWidgetItem()
        for col, field in enumerate(self.OUT_FIELDS):
            val = info[field]
            twi.setText(col, self.FORMAT_STRS[col] % val)
        self._table_widget.insertTopLevelItem(row, twi)

        for col, (key, func) in self.TOOLTIPS.items():
            twi.setToolTip(col, func(info[key]))

        with self._selected_node_lock:
            if twi.text(0) == self._selected_node:
                twi.setSelected(True)

        twi.setHidden(len(self.name_filter.findall(info['node_name'])) == 0)

    def update_table(self):
        self._table_widget.clear()
        infos = self._node_info.get_all_node_fields(self.NODE_FIELDS)
        for nx, info in enumerate(infos):
            self.update_one_item(nx, info)

    def shutdown_plugin(self):
        self._update_timer.stop()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('filter_text', self._filter_box.text())
        instance_settings.set_value('is_regex',
                                    int(self._regex_box.checkState()))

    def restore_settings(self, plugin_settings, instance_settings):
        self._filter_box.setText(instance_settings.value('filter_text'))
        is_regex_int = instance_settings.value('is_regex')
        if is_regex_int:
            self._regex_box.setCheckState(Qt.CheckState(is_regex_int))
        else:
            self._regex_box.setCheckState(Qt.CheckState(0))
        self.update_filter()
コード例 #8
0
class MotionEditorWidget(QWidget):

    position_renamed = Signal(QListWidgetItem)

    def __init__(self, motion_publisher, robot_config):
        super(MotionEditorWidget, self).__init__()
        self.robot_config = robot_config
        self._motion_publisher = motion_publisher
        self._motion_data = MotionData(robot_config)
        self._filter_pattern = ''
        self._playback_marker = None
        self._playback_timer = None

        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                               'motion_editor.ui')
        loadUi(ui_file, self)
        self.list_widgets = {}
        for group_type in robot_config.group_types():
            list_widget = QListWidget()
            list_widget.setSortingEnabled(True)
            list_widget.setDragDropMode(QAbstractItemView.DragOnly)
            list_widget.setContextMenuPolicy(Qt.CustomContextMenu)

            list_widget.customContextMenuRequested.connect(
                lambda pos, _group_type=group_type: self.
                positions_list_context_menu(_group_type, pos))
            list_widget.itemChanged.connect(self.on_list_item_changed)

            self.position_lists_layout.addWidget(list_widget)
            self.list_widgets[group_type] = list_widget

        self._timeline_widget = TimelineWidget()
        for track_name in self.robot_config.sorted_groups():
            track_type = self.robot_config.groups[track_name].group_type
            track = self._timeline_widget.add_track(track_name, track_type)

            list_widget = self.list_widgets[track_type]
            palette = list_widget.palette()
            palette.setColor(QPalette.Base, track._colors['track'])
            list_widget.setPalette(palette)

        self.timeline_group.layout().addWidget(self._timeline_widget)

        for group_type in robot_config.group_types():
            label = QLabel(group_type)
            label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.group_label_layout.addWidget(label)

        self.update_motion_name_combo()

        self.stop_motion_button.pressed.connect(self.on_motion_stop_pressed)

    def on_list_item_changed(self, item):
        print 'Position name changed from', item._text, 'to', item.text()
        self.position_renamed.emit(item)

    def on_motion_stop_pressed(self):
        self._clear_playback_marker()
        self._motion_publisher.stop_motion()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('splitter', self.splitter.saveState())
        instance_settings.set_value('filter_pattern',
                                    self.filter_pattern_edit.text())
        instance_settings.set_value('filter_motions_checked',
                                    self.filter_motions_check.isChecked())
        instance_settings.set_value('filter_positions_checked',
                                    self.filter_positions_check.isChecked())

    def restore_settings(self, plugin_settings, instance_settings):
        if instance_settings.contains('splitter'):
            self.splitter.restoreState(instance_settings.value('splitter'))
        else:
            self.splitter.setSizes([300, 100])
        self.filter_pattern_edit.setText(
            instance_settings.value('filter_pattern', ''))
        self.filter_motions_check.setChecked(
            instance_settings.value('filter_motions_checked', False) in (
                1, True, 'true'))
        self.filter_positions_check.setChecked(
            instance_settings.value('filter_positions_checked', False) in (
                1, True, 'true'))

    @Slot()
    def on_clear_motion_button_clicked(self):
        self._timeline_widget.scene().clear()

    @Slot()
    def on_delete_motion_button_clicked(self):
        motion_name = str(self.motion_name_combo.currentText())
        if len(motion_name) == 0:
            QMessageBox.warning(self, 'Error', 'No motion selected to delete.')
            return
        if motion_name not in self._motion_data:
            QMessageBox.warning(
                self, 'Error', 'Motion "%s" not in motion list.' % motion_name)
            return
        self._motion_data.delete(motion_name)
        self.update_motion_name_combo()

    @Slot()
    def on_save_motion_button_clicked(self):
        motion_name = str(self.motion_name_combo.currentText())
        if len(motion_name) == 0:
            QMessageBox.warning(self, 'Error',
                                'No name given to save this motion.')
            return
        self._motion_data.save(motion_name, self.get_motion_from_timeline())
        self.update_motion_name_combo()

    @Slot()
    def on_load_motion_button_clicked(self):
        motion_name = str(self.motion_name_combo.currentText())
        if len(motion_name) == 0:
            QMessageBox.warning(self, 'Error', 'No motion selected to load.')
            return
        if motion_name not in self._motion_data:
            QMessageBox.warning(
                self, 'Error', 'Motion "%s" not in motion list.' % motion_name)
            return
        self.on_clear_motion_button_clicked()
        motion = self._motion_data[motion_name]
        for group_name, poses in motion.items():
            for pose in poses:
                data = self.robot_config.groups[group_name].adapt_to_side(
                    pose['positions'])
                self._timeline_widget.scene().add_clip(group_name,
                                                       pose['name'],
                                                       pose['starttime'],
                                                       pose['duration'], data)

    @Slot()
    def on_null_motion_button_clicked(self):
        self._clear_playback_marker()
        for group_name in self.robot_config.groups:
            target_position = [0] * len(
                self.robot_config.groups[group_name].joints)
            self._motion_publisher.move_to_position(
                group_name, target_position, self.time_factor_spin.value())

    @Slot()
    def on_run_motion_button_clicked(self):
        motion_name = str(self.motion_name_combo.currentText())
        if len(motion_name) == 0:
            QMessageBox.warning(self, 'Error', 'No motion selected to run.')
            return
        if motion_name not in self._motion_data:
            QMessageBox.warning(
                self, 'Error', 'Motion "%s" not in motion list.' % motion_name)
            return
        self._clear_playback_marker()
        self._motion_publisher.publish_motion(self._motion_data[motion_name],
                                              self.time_factor_spin.value())
        print '[Motion Editor] Running motion:', motion_name

    @Slot(str)
    def on_filter_pattern_edit_textChanged(self, pattern):
        self._filter_pattern = pattern
        self._apply_filter_to_position_lists()
        self.update_motion_name_combo()

    def _apply_filter_to_position_lists(self):
        for group_type in self.robot_config.group_types():
            list_widget = self.list_widgets[group_type]
            for row in range(list_widget.count()):
                item = list_widget.item(row)
                hidden = self.filter_positions_check.isChecked() and re.search(
                    self._filter_pattern, item.text()) is None
                item.setHidden(hidden)

    @Slot(bool)
    def on_filter_positions_check_toggled(self, checked):
        self._apply_filter_to_position_lists()

    @Slot(bool)
    def on_filter_motions_check_toggled(self, checked):
        self.update_motion_name_combo()

    def update_motion_name_combo(self):
        combo = self.motion_name_combo
        # remember selected motion name
        motion_name = str(combo.currentText())
        # update motion names
        combo.clear()
        motion_names = self._motion_data.keys()
        if self.filter_motions_check.isChecked():
            motion_names = [
                name for name in motion_names
                if re.search(self._filter_pattern, name) is not None
            ]
        combo.addItems(sorted(motion_names))
        # restore previously selected motion name
        motion_index = combo.findText(motion_name)
        combo.setCurrentIndex(motion_index)

    def update_positions_lists(self, positions):
        for group_type in self.robot_config.group_types():
            list_widget = self.list_widgets[group_type]
            list_widget.clear()
            for name, position in positions[group_type].items():
                item = QListWidgetItem(name)
                item._data = position
                item._text = name
                item._type = group_type
                item.setFlags(item.flags() | Qt.ItemIsEditable)
                list_widget.addItem(item)
        self._apply_filter_to_position_lists()

    def positions_list_context_menu(self, group_type, pos):
        list_widget = self.list_widgets[group_type]
        list_item = list_widget.itemAt(pos)
        if list_item is None:
            return

        menu = QMenu()
        move_to = {}
        for group in self.robot_config.group_list():
            if list_item._type == group.group_type:
                move_to[menu.addAction('move "%s"' %
                                       group.name)] = [group.name]
        # move_to[menu.addAction('move all')] = list(move_to.itervalues())
        move_to[menu.addAction('move all')] = [
            group_list[0] for group_list in list(move_to.itervalues())
        ]
        result = menu.exec_(list_widget.mapToGlobal(pos))
        if result in move_to:
            group_names = move_to[result]
            for group_name in group_names:
                target_positions = self.robot_config.groups[
                    group_name].adapt_to_side(list_item._data)
                self._motion_publisher.move_to_position(
                    group_name, target_positions,
                    self.time_factor_spin.value())
                print '[Motion Editor] Moving %s to: %s' % (
                    group_name,
                    zip(self.robot_config.groups[group_name].joints_sorted(),
                        target_positions))

    def get_motion_from_timeline(self):
        motion = {}
        for group_name, clips in self._timeline_widget.scene().clips().items():
            motion[group_name] = []
            for clip in clips:
                pose = {
                    'name':
                    clip.text(),
                    'starttime':
                    clip.starttime(),
                    'duration':
                    clip.duration(),
                    'positions':
                    self.robot_config.groups[group_name].adapt_to_side(
                        clip.data()),
                }
                motion[group_name].append(pose)
        return motion

    @Slot()
    def on_run_timeline_button_clicked(self):
        print '[Motion Editor] Running timeline.'
        self._playback_duration = 0.0
        for clips in self._timeline_widget.scene().clips().values():
            if len(clips) > 0 and self._playback_duration < clips[-1].endtime(
            ):
                self._playback_duration = clips[-1].endtime()
        self._playback_time_factor = self.time_factor_spin.value()

        self._clear_playback_marker()
        self._playback_marker = self._timeline_widget.scene().add_marker(0.0)

        self._playback_timer = QTimer()
        self._playback_timer.setInterval(30)
        self._playback_timer.timeout.connect(self._playback_update)

        self._playback_start = rospy.get_time()
        self._motion_publisher.publish_motion(self.get_motion_from_timeline(),
                                              self.time_factor_spin.value())
        self._playback_timer.start()

    def _clear_playback_marker(self):
        if self._playback_timer is not None:
            self._playback_timer.stop()
        if self._playback_marker is not None:
            self._playback_marker.remove()

    @Slot()
    def _playback_update(self):
        duration = (rospy.get_time() -
                    self._playback_start) / self._playback_time_factor
        if duration > self._playback_duration:
            self._clear_playback_marker()
        else:
            self._playback_marker.set_time(duration)
コード例 #9
0
ファイル: gui.py プロジェクト: team-diana/rqt_tf_rot
class View(QWidget):
    def __init__(self, parent=None):
        super(View, self).__init__()
        self.unit = 'deg'

        self.timer = QTimer(self)
        self.timer.timeout.connect(lambda: self.publishRequested.emit())

        pkg_path = rospkg.RosPack().get_path('rqt_tf_rot')
        ui_file_path = os.path.join(pkg_path, 'res', 'PluginUI.ui')

        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file_path, self)
        self.initUI()

    publishRequested = Signal()
        
    def initUI(self):
        btnPublish = self.findChild(QAbstractButton, 'btnPublish')
        btnPublish.clicked.connect(lambda: self.publishRequested.emit())

        self.txtChild = self.findChild(QLineEdit, 'txtChild')
        self.cboParent = self.findChild(QComboBox, 'cboParent')
        self.spnRollRad = self.findChild(QDoubleSpinBox, 'spnRollRad')
        self.spnPitchRad = self.findChild(QDoubleSpinBox, 'spnPitchRad')
        self.spnYawRad = self.findChild(QDoubleSpinBox, 'spnYawRad')
        
        spnTimeout = self.findChild(QSpinBox, 'spnTimeout')
        spnTimeout.valueChanged.connect(
            lambda timeout: self.timer.setInterval(timeout))
        
        chkTimer = self.findChild(QCheckBox, 'chkTimer')
        chkTimer.stateChanged.connect(self.onChkTimerChanged)

        for suffix in ['Yaw', 'Pitch', 'Roll']:
            degSpin = self.findChild(QDoubleSpinBox, 'spn'+suffix+'Deg')
            radSpin = self.findChild(QDoubleSpinBox, 'spn'+suffix+'Rad')
            dial = self.findChild(QDial, 'dial'+suffix)

            # May the Python gods forgive me
            # The lambda default parameters are there only to simulate
            # what would happen if lambdas remembered what objects
            # they were trying to capture instead of just the names of
            # the variables in the closure
            dial.valueChanged.connect(
                lambda value, degSpin=degSpin, radSpin=radSpin: 
                self.onDialChanged(value,
                                   deg_spinbox=degSpin,
                                   rad_spinbox=radSpin))

            degSpin.valueChanged.connect(
                lambda value, dial=dial, radSpin=radSpin: 
                self.onDegSpinChanged(
                    value,
                    slider=dial,
                    rad_spinbox=radSpin))

            radSpin.valueChanged.connect(
                lambda value, dial=dial, degSpin=degSpin: 
                self.onRadSpinChanged(value,
                                      slider=dial,
                                      deg_spinbox=degSpin))
            
    # --- Slots (not all strictly Qt slots)
    def onDialChanged(self, value, deg_spinbox=None, rad_spinbox=None): 
        setValueWithoutSignals(deg_spinbox, float(value))
        setValueWithoutSignals(rad_spinbox, 
                               convertUnit(float(value), 'deg', 'rad')) 

    def onDegSpinChanged(self, value, slider=None, rad_spinbox=None): 
        setValueWithoutSignals(slider, int(value))
        setValueWithoutSignals(rad_spinbox, 
                               convertUnit(float(value), 'deg', 'rad')) 

    def onRadSpinChanged(self, value, slider=None, deg_spinbox=None): 
        setValueWithoutSignals(slider, int(value))
        setValueWithoutSignals(deg_spinbox, 
                               convertUnit(float(value), 'rad', 'deg')) 

    def onChkTimerChanged(self, state):
        if state == Qt.Checked:
            self.timer.start()
        else:
            self.timer.stop()
        
    @Slot(str)
    def addLinkToList(self, child_id):
        if self.cboParent != None:
            self.cboParent.addItem(child_id)

    # --- Accessors
    def childName(self):
        if self.txtChild != None:
            return self.txtChild.text()
        else:
            return ''

    def parentName(self):
        if self.cboParent != None:
            return self.cboParent.currentText()
        else:
            return ''

    def roll(self):
        return self.spnRollRad.value()
    def pitch(self):
        return self.spnPitchRad.value()
    def yaw(self):
        return self.spnYawRad.value()
コード例 #10
0
class LifeFrame(QFrame):
    STATE_STOPPED = "stopped"
    STATE_RUN = "running"
    STATE_IDLE = "idle"
    
    def __init__(self, parent=None):
        super(LifeFrame, self).__init__(parent)
        self._ui = Ui_life_frame()
        self._motion = Rotate('/mobile_base/commands/velocity')
        self._motion_thread = None
        self._timer = QTimer()
        #self._timer.setInterval(60000) #60s
        self._timer.setInterval(250) #60s
        QObject.connect(self._timer, SIGNAL('timeout()'), self, SLOT('update_progress_callback()'))
        self._state = LifeFrame.STATE_STOPPED
        self._is_alive = False # Used to indicate whether the frame is alive or not (see hibernate/restore methods)

    def setupUi(self):
        self._ui.setupUi(self)
        self._ui.start_button.setEnabled(True)
        self._ui.stop_button.setEnabled(False)
        self._motion.init(self._ui.angular_speed_spinbox.value())

    def shutdown(self):
        '''
          Used to terminate the plugin
        '''
        rospy.loginfo("Kobuki TestSuite: life frame shutdown")
        self._motion.shutdown()

    ##########################################################################
    # Widget Management
    ##########################################################################
        
    def hibernate(self):
        '''
          This gets called when the frame goes out of focus (tab switch). 
          Disable everything to avoid running N tabs in parallel when in
          reality we are only running one.
        '''
        pass
    
    def restore(self):
        '''
          Restore the frame after a hibernate.
        '''
        pass


    ##########################################################################
    # Motion Callbacks
    ##########################################################################

    def start(self):
        self._state = LifeFrame.STATE_RUN
        self._ui.run_progress.reset()
        self._ui.idle_progress.reset()
        self._motion_thread = WorkerThread(self._motion.execute, None)
        self._motion_thread.start()

    def stop(self):
        self._state = LifeFrame.STATE_STOPPED
        self._motion.stop()
        if self._motion_thread:
            self._motion_thread.wait()
        
    ##########################################################################
    # Qt Callbacks
    ##########################################################################
    @Slot()
    def on_start_button_clicked(self):
        self._ui.start_button.setEnabled(False)
        self._ui.stop_button.setEnabled(True)
        self._timer.start()
        self.start()
        
    @Slot()
    def on_stop_button_clicked(self):
        self.stop()
        self._timer.stop()
        self._ui.start_button.setEnabled(True)
        self._ui.stop_button.setEnabled(False)
        
    @pyqtSlot(float)
    def on_angular_speed_spinbox_valueChanged(self, value):
        self._motion.init(self._ui.angular_speed_spinbox.value())

    ##########################################################################
    # Timer Callbacks
    ##########################################################################

    @Slot()
    def update_progress_callback(self):
        if self._state == LifeFrame.STATE_RUN:
            new_value = self._ui.run_progress.value()+1
            if new_value == self._ui.run_progress.maximum():
                print("  Switching to idle")
                self._motion.stop()
                self._state = LifeFrame.STATE_IDLE
            else:
                self._ui.run_progress.setValue(new_value)
        if self._state == LifeFrame.STATE_IDLE:
            new_value = self._ui.idle_progress.value()+1
            if new_value == self._ui.idle_progress.maximum():
                print("  Switching to run")
                self.start()
            else:
                self._ui.idle_progress.setValue(new_value)
コード例 #11
0
class ControllerManager(Plugin):
    """
    Graphical frontend for managing ros_control controllers.
    """
    _cm_update_freq = 1  # Hz

    def __init__(self, context):
        super(ControllerManager, self).__init__(context)
        self.setObjectName('ControllerManager')

        # Create QWidget and extend it with all the attributes and children
        # from the UI file
        self._widget = QWidget()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_controller_manager'),
                               'resource',
                               'controller_manager.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('ControllerManagerUi')

        # Pop-up that displays controller information
        self._popup_widget = QWidget()
        ui_file = os.path.join(rp.get_path('rqt_controller_manager'),
                               'resource',
                               'controller_info.ui')
        loadUi(ui_file, self._popup_widget)
        self._popup_widget.setObjectName('ControllerInfoUi')

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Initialize members
        self._cm_ns = []  # Namespace of the selected controller manager
        self._controllers = []  # State of each controller
        self._table_model = None
        self._controller_lister = None

        # Controller manager service proxies
        self._load_srv = None
        self._unload_srv = None
        self._switch_srv = None

        # Controller state icons
        rospack = rospkg.RosPack()
        path = rospack.get_path('rqt_controller_manager')
        self._icons = {'running': QIcon(path + '/resource/led_green.png'),
                       'stopped': QIcon(path + '/resource/led_red.png'),
                       'uninitialized': QIcon(path + '/resource/led_off.png'),
                       'initialized': QIcon(path + '/resource/led_red.png')}

        # Controllers display
        table_view = self._widget.table_view
        table_view.setContextMenuPolicy(Qt.CustomContextMenu)
        table_view.customContextMenuRequested.connect(self._on_ctrl_menu)

        table_view.doubleClicked.connect(self._on_ctrl_info)

        header = table_view.horizontalHeader()
        header.setSectionResizeMode(QHeaderView.ResizeToContents)
        header.setContextMenuPolicy(Qt.CustomContextMenu)
        header.customContextMenuRequested.connect(self._on_header_menu)

        # Timer for controller manager updates
        self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 /
                                               self._cm_update_freq)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        # Timer for running controller updates
        self._update_ctrl_list_timer = QTimer(self)
        self._update_ctrl_list_timer.setInterval(1000.0 /
                                                 self._cm_update_freq)
        self._update_ctrl_list_timer.timeout.connect(self._update_controllers)
        self._update_ctrl_list_timer.start()

        # Signal connections
        w = self._widget
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

    def shutdown_plugin(self):
        self._update_cm_list_timer.stop()
        self._update_ctrl_list_timer.stop()
        self._popup_widget.hide()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('cm_ns', self._cm_ns)

    def restore_settings(self, plugin_settings, instance_settings):
        # Restore last session's controller_manager, if present
        self._update_cm_list()
        cm_ns = instance_settings.value('cm_ns')
        cm_combo = self._widget.cm_combo
        cm_list = [cm_combo.itemText(i) for i in range(cm_combo.count())]
        try:
            idx = cm_list.index(cm_ns)
            cm_combo.setCurrentIndex(idx)
        except (ValueError):
            pass

    # def trigger_configuration(self):
        # Comment in to signal that the plugin has a way to configure
        # This will enable a setting button (gear icon) in each dock widget
        # title bar
        # Usually used to open a modal configuration dialog

    def _update_cm_list(self):
        update_combo(self._widget.cm_combo, self._list_cm())

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns

        # Setup services for communicating with the selected controller manager
        self._set_cm_services(cm_ns)

        # Controller lister for the selected controller manager
        if cm_ns:
            self._controller_lister = ControllerLister(cm_ns)
            self._update_controllers()
        else:
            self._controller_lister = None

    def _set_cm_services(self, cm_ns):
        if cm_ns:
            # NOTE: Persistent services are used for performance reasons.
            # If the controller manager dies, we detect it and disconnect from
            # it anyway
            load_srv_name = _append_ns(cm_ns, 'load_controller')
            self._load_srv = rospy.ServiceProxy(load_srv_name,
                                                LoadController,
                                                persistent=True)
            unload_srv_name = _append_ns(cm_ns, 'unload_controller')
            self._unload_srv = rospy.ServiceProxy(unload_srv_name,
                                                  UnloadController,
                                                  persistent=True)
            switch_srv_name = _append_ns(cm_ns, 'switch_controller')
            self._switch_srv = rospy.ServiceProxy(switch_srv_name,
                                                  SwitchController,
                                                  persistent=True)
        else:
            self._load_srv = None
            self._unload_srv = None
            self._switch_srv = None

    def _update_controllers(self):
        # Find controllers associated to the selected controller manager
        controllers = self._list_controllers()

        # Update controller display, if necessary
        if self._controllers != controllers:
            self._controllers = controllers
            self._show_controllers()  # NOTE: Model is recomputed from scratch

    def _list_controllers(self):
        """
        @return List of controllers associated to a controller manager
        namespace. Contains both stopped/running controllers, as returned by
        the C{list_controllers} service, plus uninitialized controllers with
        configurations loaded in the parameter server.
        @rtype [str]
        """
        if not self._cm_ns:
            return []

        # Add loaded controllers first
        controllers = self._controller_lister()

        # Append potential controller configs found in the parameter server
        all_ctrls_ns = _resolve_controllers_ns(self._cm_ns)
        for name in get_rosparam_controller_names(all_ctrls_ns):
            add_ctrl = not any(name == ctrl.name for ctrl in controllers)
            if add_ctrl:
                type_str = _rosparam_controller_type(all_ctrls_ns, name)
                uninit_ctrl = ControllerState(name=name,
                                              type=type_str,
                                              state='uninitialized')
                controllers.append(uninit_ctrl)
        return controllers

    def _show_controllers(self):
        table_view = self._widget.table_view
        self._table_model = ControllerTable(self._controllers, self._icons)
        table_view.setModel(self._table_model)

    def _on_ctrl_menu(self, pos):
        # Get data of selected controller
        row = self._widget.table_view.rowAt(pos.y())
        if row < 0:
            return  # Cursor is not under a valid item

        ctrl = self._controllers[row]

        # Show context menu
        menu = QMenu(self._widget.table_view)
        if ctrl.state == 'running':
            action_stop = menu.addAction(self._icons['stopped'], 'Stop')
            action_kill = menu.addAction(self._icons['uninitialized'],
                                         'Stop and Unload')
        elif ctrl.state == 'stopped':
            action_start = menu.addAction(self._icons['running'],
                                          'Start again')
            action_unload = menu.addAction(self._icons['uninitialized'],
                                           'Unload')
        elif ctrl.state == 'initialized':
            action_start = menu.addAction(self._icons['running'], 'Start')
            action_unload = menu.addAction(self._icons['uninitialized'],
                                           'Unload')
        elif ctrl.state == 'uninitialized':
            action_load = menu.addAction(self._icons['stopped'], 'Load')
            action_spawn = menu.addAction(self._icons['running'],
                                          'Load and Start')

        action = menu.exec_(self._widget.table_view.mapToGlobal(pos))

        # Evaluate user action
        if ctrl.state == 'running':
            if action is action_stop:
                self._stop_controller(ctrl.name)
            elif action is action_kill:
                self._stop_controller(ctrl.name)
                self._unload_controller(ctrl.name)
        elif ctrl.state == 'stopped' or ctrl.state == 'initialized':
            if action is action_start:
                self._start_controller(ctrl.name)
            elif action is action_unload:
                self._unload_controller(ctrl.name)
        elif ctrl.state == 'uninitialized':
            if action is action_load:
                self._load_controller(ctrl.name)
            if action is action_spawn:
                self._load_controller(ctrl.name)
                self._start_controller(ctrl.name)

    def _on_ctrl_info(self, index):
        popup = self._popup_widget

        ctrl = self._controllers[index.row()]
        popup.ctrl_name.setText(ctrl.name)
        popup.ctrl_type.setText(ctrl.type)

        res_model = QStandardItemModel()
        model_root = QStandardItem('Claimed Resources')
        res_model.appendRow(model_root)
        for hw_res in ctrl.claimed_resources:
            hw_iface_item = QStandardItem(hw_res.hardware_interface)
            model_root.appendRow(hw_iface_item)
            for res in hw_res.resources:
                res_item = QStandardItem(res)
                hw_iface_item.appendRow(res_item)

        popup.resource_tree.setModel(res_model)
        popup.resource_tree.setItemDelegate(FontDelegate(popup.resource_tree))
        popup.resource_tree.expandAll()
        popup.move(QCursor.pos())
        popup.show()

    def _on_header_menu(self, pos):
        header = self._widget.table_view.horizontalHeader()

        # Show context menu
        menu = QMenu(self._widget.table_view)
        action_toggle_auto_resize = menu.addAction('Toggle Auto-Resize')
        action = menu.exec_(header.mapToGlobal(pos))

        # Evaluate user action
        if action is action_toggle_auto_resize:
            if header.resizeMode(0) == QHeaderView.ResizeToContents:
                header.setSectionResizeMode(QHeaderView.Interactive)
            else:
                header.setSectionResizeMode(QHeaderView.ResizeToContents)

    def _load_controller(self, name):
        self._load_srv.call(LoadControllerRequest(name=name))

    def _unload_controller(self, name):
        self._unload_srv.call(UnloadControllerRequest(name=name))

    def _start_controller(self, name):
        strict = SwitchControllerRequest.STRICT
        req = SwitchControllerRequest(start_controllers=[name],
                                      stop_controllers=[],
                                      strictness=strict)
        self._switch_srv.call(req)

    def _stop_controller(self, name):
        strict = SwitchControllerRequest.STRICT
        req = SwitchControllerRequest(start_controllers=[],
                                      stop_controllers=[name],
                                      strictness=strict)
        self._switch_srv.call(req)
コード例 #12
0
class BagPlayerWidget(QWidget):
    def __init__(self):
        super(BagPlayerWidget, self).__init__()
        rospkg_pack = rospkg.RosPack()
        ui_file = os.path.join(rospkg_pack.get_path('bag_browser'), 'resource',
                               'BagPlayer.ui')
        loadUi(ui_file, self)

        self._play_icon = QIcon.fromTheme('media-playback-start')
        self._pause_icon = QIcon.fromTheme('media-playback-pause')
        self.qt_play_btn.setIcon(self._play_icon)

        self._playing = False
        self._current_msec = 0
        self._max_msec = 1000000
        self.qt_seekbar_slider.setTracking(False)

        self._play_timer = QTimer()
        self._play_timer.timeout.connect(self.on_image_update)
        self._play_timer.setInterval(1)  # [ms]

        self._gui_timer = QTimer()
        self._gui_timer.timeout.connect(self.on_gui_update)
        self._gui_timer.setInterval(100)  # [ms]
        self._gui_timer.start()

    def close(self):
        self._play_timer.stop()
        self._gui_timer.stop()
        super(BagPlayerWidget, self).close()

    def show(self, bagpath):
        def converter(rosimg):
            return QPixmap.fromImage(
                BagPlayer.cvimg2qimg(BagPlayer.rosimg2cvimg(rosimg)))

        self._bag_player = BagPlayer(bagpath, converter)
        self.qt_filename_label.setText(os.path.basename(bagpath))

        self._max_msec = self._bag_player.get_length_msec()
        self.qt_seekbar_slider.setMinimum(0)
        self.qt_seekbar_slider.setMaximum(self._max_msec - 1)
        self.qt_seekbar_slider.setValue(0)
        self.qt_time_numer_label.setText(str(0))
        self.qt_time_denom_label.setText('/{0:.1f}[s]'.format(
            int(self._max_msec * 0.001)))

        self.image_widgets = {}
        for i, name in enumerate(self._bag_player.get_names()):
            self.image_widgets[name] = ImageViewWidget(title=name)
            self.qt_imgs_gridlayout.addWidget(self.image_widgets[name], i / 2,
                                              i % 2)
        self.qt_imgs_gridlayout.setSpacing(3)

        timestamps = self._bag_player.get_timestamps()
        self._timestamps = [t - timestamps[0] for t in timestamps]
        self._diff_msecs = [
            a - b for a, b in zip(self._timestamps[1:], self._timestamps[:-1])
        ]
        self.counter = 0
        self.qt_seekbar_slider.setValue(0)
        self._current_msec = 0
        self.update_images(self.qt_seekbar_slider.value())
        super(BagPlayerWidget, self).show()

    def on_image_update(self):
        if len(self._diff_msecs) > self.counter + 1:
            self._current_msec = self._current_msec + self._diff_msecs[
                self.counter]
            self.counter += 1
        else:
            self._current_msec = 0
            self.counter = 0
        self._play_timer.setInterval(self._diff_msecs[self.counter])
        self.update_images(self._current_msec)

    def on_gui_update(self):
        t = self._current_msec
        self.qt_seekbar_slider.setValue(t)
        self.qt_time_numer_label.setText('{0:.1f}'.format(t * 0.001))

    def update_images(self, t):
        imgs, names = self._bag_player.get_imgs(t)
        for img, name in zip(imgs, names):
            if img is None:
                continue
            self.image_widgets[name].set_image(img)

    @Slot()
    def on_qt_seekbar_slider_sliderPressed(self):
        self._play_timer.stop()
        self._gui_timer.stop()
        self.qt_seekbar_slider.setTracking(True)

    @Slot()
    def on_qt_seekbar_slider_sliderReleased(self):
        val = self.qt_seekbar_slider.value()
        for i, ts in enumerate(self._timestamps):
            if ts > val:
                self._current_msec = self._timestamps[max(i - 1, 0)]
                self.counter = i - 1
                break
        self.update_images(self._current_msec)
        self.qt_seekbar_slider.setTracking(False)
        if self._playing:
            self._play_timer.start()
        else:
            self._play_timer.stop()
        self._gui_timer.start()

    @Slot()
    def on_qt_play_btn_clicked(self):
        if not self._playing:
            self.qt_play_btn.setIcon(self._pause_icon)
            self._play_timer.start()
        else:
            self.qt_play_btn.setIcon(self._play_icon)
            self._play_timer.stop()
        self._playing = not self._playing
コード例 #13
0
class ROSData(object):
    """
    Subscriber to ROS topic that buffers incoming data
    """
    def __init__(self, topic_code, topic_item, start_time):
        self.name = topic_code + '/' + topic_item
        self.start_time = start_time
        self.error = None

        self.lock = threading.Lock()
        self.buff_x = []
        self.buff_y = []

        self.interval = 100  # in milliseconds, period of regular update
        self.timer = QTimer()
        self.timer.setInterval(self.interval)

        self.code = topic_code
        self.item = topic_item

        # go through options and decide what your self.data will be, given the ros subscribers
        if topic_code == 's':
            if topic_item == 'chi':
                self.timer.timeout.connect(self.state_chi_cb)
            elif topic_item == 'phi':
                self.timer.timeout.connect(self.state_phi_cb)
            elif topic_item == 'theta':
                self.timer.timeout.connect(self.state_theta_cb)
            elif topic_item == 'Va':
                self.timer.timeout.connect(self.state_Va_cb)
        elif topic_code == 'ci':
            if topic_item == 'phi_c':
                self.timer.timeout.connect(self.conin_phi_c_cb)
            elif topic_item == 'theta_c':
                self.timer.timeout.connect(self.conin_theta_c_cb)
        elif topic_code == 'cc':
            if topic_item == 'chi_c':
                self.timer.timeout.connect(self.concom_chi_c_cb)
            elif topic_item == 'Va_c':
                self.timer.timeout.connect(self.concom_Va_c_cb)

        self.timer.start()

    def close(self):
        self.timer.stop()

    def state_chi_cb(self):
        self.buff_x.append(rospy.get_time() - self.start_time)
        self.buff_y.append(StateSub.chi)

    def state_phi_cb(self):
        self.buff_x.append(rospy.get_time() - self.start_time)
        self.buff_y.append(StateSub.phi)

    def state_theta_cb(self):
        self.buff_x.append(rospy.get_time() - self.start_time)
        self.buff_y.append(StateSub.theta)

    def state_Va_cb(self):
        self.buff_x.append(rospy.get_time() - self.start_time)
        self.buff_y.append(StateSub.Va)

    def conin_phi_c_cb(self):
        self.buff_x.append(rospy.get_time() - self.start_time)
        self.buff_y.append(ConInSub.phi_c)

    def conin_theta_c_cb(self):
        self.buff_x.append(rospy.get_time() - self.start_time)
        self.buff_y.append(ConInSub.theta_c)

    def concom_chi_c_cb(self):
        self.buff_x.append(rospy.get_time() - self.start_time)
        self.buff_y.append(ConComSub.chi_c)

    def concom_Va_c_cb(self):
        self.buff_x.append(rospy.get_time() - self.start_time)
        self.buff_y.append(ConComSub.Va_c)

    def next(self):
        """
        Get the next data in the series
        :returns: [xdata], [ydata]
        """
        if self.error:
            raise self.error
        try:
            self.lock.acquire()
            buff_x = self.buff_x
            buff_y = self.buff_y
            self.buff_x = []
            self.buff_y = []
        finally:
            self.lock.release()
        return buff_x, buff_y
コード例 #14
0
class Top(Plugin):

    NODE_FIELDS   = [             'pid', 'get_cpu_percent', 'get_memory_percent', 'get_num_threads']
    OUT_FIELDS    = ['node_name', 'pid', 'cpu_percent',     'memory_percent',     'num_threads'    ]
    FORMAT_STRS   = ['%s',        '%s',  '%0.2f',           '%0.2f',              '%s'             ]
    NODE_LABELS   = ['Node',      'PID', 'CPU %',           'Mem %',              'Num Threads'    ]
    SORT_TYPE     = [str,         str,   float,             float,                float            ]
    TOOLTIPS      = {
        0: ('cmdline', lambda x: '\n'.join(textwrap.wrap(' '.join(x)))),
        3: ('memory_info', lambda x: ('Resident: %0.2f MiB, Virtual: %0.2f MiB' % (x[0]/2**20, x[1]/2**20)))
    }

    _node_info = NodeInfo()

    name_filter = re.compile('')

    def __init__(self, context):
        super(Top, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('Top')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()
        # Add argument(s) to the parser.
        parser.add_argument("-q", "--quiet", action="store_true",
                      dest="quiet",
                      help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        # if not args.quiet:
        #     print 'arguments: ', args
        #     print 'unknowns: ', unknowns

        self._selected_node = ''
        self._selected_node_lock = RLock()

        # Setup the toolbar
        self._toolbar = QToolBar()
        self._filter_box = QLineEdit()
        self._regex_box  = QCheckBox()
        self._regex_box.setText('regex')
        self._toolbar.addWidget(QLabel('Filter'))
        self._toolbar.addWidget(self._filter_box)
        self._toolbar.addWidget(self._regex_box)

        self._filter_box.returnPressed.connect(self.update_filter)
        self._regex_box.stateChanged.connect(self.update_filter)

        # Create a container widget and give it a layout
        self._container = QWidget()
        self._container.setWindowTitle('Process Monitor')
        self._layout    = QVBoxLayout()
        self._container.setLayout(self._layout)

        self._layout.addWidget(self._toolbar)

        # Create the table widget
        self._table_widget = QTreeWidget()
        self._table_widget.setObjectName('TopTable')
        self._table_widget.setColumnCount(len(self.NODE_LABELS))
        self._table_widget.setHeaderLabels(self.NODE_LABELS)
        self._table_widget.itemClicked.connect(self._tableItemClicked)
        self._table_widget.setSortingEnabled(True)
        self._table_widget.setAlternatingRowColors(True)

        self._layout.addWidget(self._table_widget)
        context.add_widget(self._container)

        # Add a button for killing nodes
        self._kill_button = QPushButton('Kill Node')
        self._layout.addWidget(self._kill_button)
        self._kill_button.clicked.connect(self._kill_node)

        # Update twice since the first cpu% lookup will always return 0
        self.update_table()
        self.update_table()

        self._table_widget.resizeColumnToContents(0)

        # Start a timer to trigger updates
        self._update_timer = QTimer()
        self._update_timer.setInterval(1000)
        self._update_timer.timeout.connect(self.update_table)
        self._update_timer.start()

    def _tableItemClicked(self, item, column):
        with self._selected_node_lock:
            self._selected_node = item.text(0)

    def update_filter(self, *args):
        if self._regex_box.isChecked():
            expr = self._filter_box.text()
        else:
            expr = re.escape(self._filter_box.text())
        self.name_filter = re.compile(expr)
        self.update_table()

    def _kill_node(self):
        self._node_info.kill_node(self._selected_node)

    def update_one_item(self, row, info):
        twi = TopWidgetItem()
        for col, field in enumerate(self.OUT_FIELDS):
            val = info[field]
            twi.setText(col, self.FORMAT_STRS[col] % val)
        self._table_widget.insertTopLevelItem(row, twi)

        for col, (key, func) in self.TOOLTIPS.iteritems():
            twi.setToolTip(col, func(info[key]))

        with self._selected_node_lock:
            if twi.text(0) == self._selected_node:
                twi.setSelected(True)

        self._table_widget.setItemHidden(twi, len(self.name_filter.findall(info['node_name'])) == 0)

    def update_table(self):
        self._table_widget.clear()
        infos = self._node_info.get_all_node_fields(self.NODE_FIELDS)
        for nx, info in enumerate(infos):
            self.update_one_item(nx, info)

    def shutdown_plugin(self):
        self._update_timer.stop()

    def save_settings(self, plugin_settings, instance_settings):        
        instance_settings.set_value('filter_text', self._filter_box.text())
        instance_settings.set_value('is_regex', int(self._regex_box.checkState()))

    def restore_settings(self, plugin_settings, instance_settings):
        self._filter_box.setText(instance_settings.value('filter_text'))
        is_regex_int = instance_settings.value('is_regex')
        if is_regex_int:
            self._regex_box.setCheckState(Qt.CheckState(is_regex_int))
        else:
            self._regex_box.setCheckState(Qt.CheckState(0))
        self.update_filter()
コード例 #15
0
class WorldMeshWorker(QObject):
    finished = Signal()  # class variable shared by all instances

    def __init__(self,
                 finished_callback=None,
                 node_name='world_mesh_worker',
                 frame_id='local_origin',
                 marker_topic='world_mesh_marker',
                 refresh_ms=None,
                 new_node=False,
                 parent=None):
        super(WorldMeshWorker, self).__init__(parent)

        if new_node:
            rospy.init_node(node_name, anonymous=True)

        if finished_callback is not None:
            self.finished.connect(finished_callback)

        #TODO([email protected]) - how to shut down?
        self.is_running = True

        self.frame_id = frame_id
        self.marker_pub = rospy.Publisher(marker_topic, Marker, queue_size=100)

        self.file_resource = None
        # if marker disappears, this will refresh it
        # Rviz is only slow to load a mesh the first time
        if refresh_ms is not None:
            self.timer = QTimer()
            self.timer.setInterval(refresh_ms)  # in milliseconds
            #self.timer.setTimerType(Qt.PreciseTimer)
            self.timer.timeout.connect(self.on_timer_callback)
            self.timer.start()

    def publish_marker(self, file_resource):
        self.file_resource = file_resource

        marker = Marker()
        marker.id = 0
        marker.ns = 'world_mesh'
        marker.type = marker.MESH_RESOURCE
        marker.header.frame_id = self.frame_id
        marker.action = marker.ADD
        #marker.action = marker.DELETE
        marker.pose.orientation.x = 0
        marker.pose.orientation.y = 0
        marker.pose.orientation.z = 0
        marker.pose.orientation.w = 1
        marker.pose.position.x = 0
        marker.pose.position.y = 0
        marker.pose.position.z = 0
        marker.scale.x = 1
        marker.scale.y = 1
        marker.scale.z = 1
        #marker.color.r = 1
        #marker.color.g = 1
        #marker.color.b = 1
        #marker.color.a = 1
        marker.lifetime = rospy.Duration(0)
        marker.mesh_resource = file_resource
        marker.mesh_use_embedded_materials = True

        self.marker_pub.publish(marker)

    def stop(self):
        self.is_running = False

    def on_timer_callback(self):
        if self.file_resource is None:
            return
        self.publish_marker(self.file_resource)
コード例 #16
0
class EnhancedLineEdit(QLineEdit):

    stop_signal = Signal()
    ''' stop button was pressed '''

    refresh_signal = Signal(str)
    ''' sends a refresh signal with current text. This signal is emmited if text was changed or button was pressed '''
    def __init__(self, parent=None):
        QLineEdit.__init__(self, parent=None)
        self.process_active = False
        # create a reload button with icon
        self.button_reload = button_reload = QToolButton(self)
        icon = QIcon.fromTheme("view-refresh",
                               nm.settings().icon('oxygen_view_refresh.png'))
        button_reload.setIcon(icon)
        button_reload.setCursor(Qt.ArrowCursor)
        button_reload.setStyleSheet(
            "QToolButton { border: none; padding: 0px; }")

        # create a stop button with icon
        self.button_stop = button_stop = QToolButton(self)
        icon = QIcon.fromTheme("process-stop",
                               nm.settings().icon('oxygen_view_refresh.png'))
        button_stop.setIcon(icon)
        button_stop.setCursor(Qt.ArrowCursor)
        button_stop.setStyleSheet(
            "QToolButton { border: none; padding: 0px; }")
        button_stop.hide()

        # signals, clear lineEdit if btn pressed; change btn visibility on input
        button_reload.clicked.connect(self._emit_refresh_text)
        self.textChanged[str].connect(self.update_close_button)
        button_stop.clicked.connect(self._process_stop)

        frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
        self.setStyleSheet("QLineEdit { padding-right: %dpx; } " %
                           (button_reload.sizeHint().width() + frameWidth + 1))
        msz = self.minimumSizeHint()
        self.setMinimumSize(
            max(msz.width(),
                button_reload.sizeHint().height() + frameWidth * 2 + 2),
            max(msz.height(),
                button_reload.sizeHint().height() + frameWidth * 2 + 2))
        self._timer = QTimer(self)
        self._timer.setSingleShot(True)
        self._timer.setInterval(500)
        self._timer.timeout.connect(self._emit_refresh_text)

    def resizeEvent(self, event):
        sz = self.button_reload.sizeHint()
        frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
        self.button_reload.move(self.rect().right() - frameWidth - sz.width(),
                                (self.rect().bottom() + 1 - sz.height()) / 2)
        self.button_stop.move(self.rect().right() - frameWidth - sz.width(),
                              (self.rect().bottom() + 1 - sz.height()) / 2)

    def update_close_button(self, text):
        self._timer.stop()
        self._timer.start()
        # self.button_reload.setVisible(True if text else False)

    def set_process_active(self, state):
        if self.process_active != state:
            self.process_active = state
            self.button_reload.setVisible(not state)
            self.button_stop.setVisible(state)

    def _process_stop(self):
        self.stop_signal.emit()

    def _emit_refresh_text(self):
        self.set_process_active(True)
        self.refresh_signal.emit(self.text())

    def keyPressEvent(self, event):
        '''
        Enable the ESC handling
        '''
        if event.key() == Qt.Key_Escape and self.text():
            self.setText('')
            self._timer.stop()
        elif event.key() in [Qt.Key_Return, Qt.Key_Enter]:
            self._timer.stop()
            self._emit_refresh_text()
        else:
            event.accept()
            QLineEdit.keyPressEvent(self, event)
コード例 #17
0
class JointTrajectoryController(Plugin):
    """
    Graphical frontend for a C{JointTrajectoryController}.

    There are two modes for interacting with a controller:
        1. B{Monitor mode} Joint displays are updated with the state reported
          by the controller. This is a read-only mode and does I{not} send
          control commands. Every time a new controller is selected, it starts
          in monitor mode for safety reasons.

        2. B{Control mode} Joint displays update the control command that is
        sent to the controller. Commands are sent periodically evan if the
        displays are not being updated by the user.

    To control the aggressiveness of the motions, the maximum speed of the
    sent commands can be scaled down using the C{Speed scaling control}

    This plugin is able to detect and keep track of all active controller
    managers, as well as the JointTrajectoryControllers that are I{running}
    in each controller manager.
    For a controller to be compatible with this plugin, it must comply with
    the following requisites:
        - The controller type contains the C{JointTrajectoryController}
        substring, e.g., C{position_controllers/JointTrajectoryController}
        - The controller exposes the C{command} and C{state} topics in its
        ROS interface.

    Additionally, there must be a URDF loaded with a valid joint limit
    specification, namely position (when applicable) and velocity limits.

    A reference implementation of the C{JointTrajectoryController} is
    available in the C{joint_trajectory_controller} ROS package.
    """
    _cmd_pub_freq = 10.0  # Hz
    _widget_update_freq = 30.0  # Hz
    _ctrlrs_update_freq = 1  # Hz
    _min_traj_dur = 5.0 / _cmd_pub_freq  # Minimum trajectory duration

    jointStateChanged = Signal([dict])
    _state_sub = None

    def __init__(self, context):
        super(JointTrajectoryController, self).__init__(context)
        self.setObjectName('JointTrajectoryController')

        # Initialize members
        self._jtc_name = []  # Name of selected joint trajectory controller
        self._cm_ns = []  # Namespace of the selected controller manager
        self._joint_pos = {}  # name->pos map for joints of selected controller
        self._joint_names = []  # Ordered list of selected controller joints
        self._jtc_joints_info = {}  # Lazily evaluated as needed
        self._robot_joint_limits = {}  # Lazily evaluated on first use

        self._node = context.node

        self._widget = QWidget()
        _, package_path = get_resource('packages',
                                       'rqt_joint_trajectory_controller')
        ui_file = os.path.join(package_path, 'share',
                               'rqt_joint_trajectory_controller', 'resource',
                               'joint_trajectory_controller.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('JointTrajectoryControllerUi')

        # Setup speed scaler
        speed_scaling = DoubleEditor(1.0, 100.0)
        speed_scaling.spin_box.setSuffix('%')
        speed_scaling.spin_box.setValue(50.0)
        speed_scaling.spin_box.setDecimals(0)
        speed_scaling.setEnabled(False)
        self._widget.speed_scaling_layout.addWidget(speed_scaling)
        self._speed_scaling_widget = speed_scaling
        speed_scaling.valueChanged.connect(self._on_speed_scaling_change)
        self._on_speed_scaling_change(speed_scaling.value())

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Timer for sending commands to active controller
        self._update_cmd_timer = QTimer(self)
        self._update_cmd_timer.setInterval(1000.0 / self._cmd_pub_freq)
        self._update_cmd_timer.timeout.connect(self._update_cmd_cb)

        # Timer for updating the joint widgets from the controller state
        self._update_act_pos_timer = QTimer(self)
        self._update_act_pos_timer.setInterval(1000.0 /
                                               self._widget_update_freq)
        self._update_act_pos_timer.timeout.connect(self._update_joint_widgets)

        # Timer for controller manager updates
        self._list_cm = ['/controller_manager']
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 /
                                               self._ctrlrs_update_freq)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        # Timer for running controller updates
        self._update_jtc_list_timer = QTimer(self)
        self._update_jtc_list_timer.setInterval(1000.0 /
                                                self._ctrlrs_update_freq)
        self._update_jtc_list_timer.timeout.connect(self._update_jtc_list)
        self._update_jtc_list_timer.start()

        # Signal connections
        w = self._widget
        w.enable_button.toggled.connect(self._on_jtc_enabled)
        w.jtc_combo.currentIndexChanged[str].connect(self._on_jtc_change)
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

        self._cmd_pub = None  # Controller command publisher
        self._state_sub = None  # Controller state subscriber

        self._state_sub = self._node.create_subscription(
            JointTrajectoryControllerState, '/state', self._state_cb, 10)

        self._list_controllers = None

    def shutdown_plugin(self):
        self._update_cmd_timer.stop()
        self._update_act_pos_timer.stop()
        self._update_cm_list_timer.stop()
        self._update_jtc_list_timer.stop()
        self._unregister_state_sub()
        self._unregister_cmd_pub()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('cm_ns', self._cm_ns)
        instance_settings.set_value('jtc_name', self._jtc_name)

    def restore_settings(self, plugin_settings, instance_settings):
        # Restore last session's controller_manager, if present
        self._update_cm_list()
        cm_ns = instance_settings.value('cm_ns')
        cm_combo = self._widget.cm_combo
        cm_list = [cm_combo.itemText(i) for i in range(cm_combo.count())]
        try:
            idx = cm_list.index(cm_ns)
            cm_combo.setCurrentIndex(idx)
            # Resore last session's controller, if running
            self._update_jtc_list()
            jtc_name = instance_settings.value('jtc_name')
            jtc_combo = self._widget.jtc_combo
            jtc_list = [
                jtc_combo.itemText(i) for i in range(jtc_combo.count())
            ]
            try:
                idx = jtc_list.index(jtc_name)
                jtc_combo.setCurrentIndex(idx)
            except (ValueError):
                pass
        except (ValueError):
            pass

    # def trigger_configuration(self):
    # Comment in to signal that the plugin has a way to configure
    # This will enable a setting button (gear icon) in each dock widget
    # title bar
    # Usually used to open a modal configuration dialog

    def _update_cm_list(self):
        update_combo(self._widget.cm_combo, self._list_cm)

    def _update_jtc_list(self):
        # Clear controller list if no controller information is available
        if not self._list_controllers:
            self._widget.jtc_combo.clear()
            return

        # List of running controllers with a valid joint limits specification
        # for _all_ their joints
        running_jtc = self._running_jtc_info()
        if running_jtc and not self._robot_joint_limits:
            self._robot_joint_limits = get_joint_limits(
                n=self._node)  # Lazy evaluation
        valid_jtc = []
        for jtc_info in running_jtc:
            has_limits = all(
                name in self._robot_joint_limits
                for name in self._jtc_joint_names(jtc_name=jtc_info.name))
            if has_limits:
                valid_jtc.append(jtc_info)
        valid_jtc_names = [data.name for data in valid_jtc]

        # Update widget
        update_combo(self._widget.jtc_combo, sorted(valid_jtc_names))

    def _on_speed_scaling_change(self, val):
        self._speed_scale = val / self._speed_scaling_widget.slider.maximum()

    def _on_joint_state_change(self, actual_pos):
        #assert(len(actual_pos) == len(self._joint_pos))
        for name in actual_pos.keys():
            try:
                self._joint_pos[name]['position'] = actual_pos[name]
            except (KeyError):
                pass

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns
        if cm_ns:
            self._list_controllers = list_controllers(self._node,
                                                      cm_ns).controller
            # NOTE: Clear below is important, as different controller managers
            # might have controllers with the same name but different
            # configurations. Clearing forces controller re-discovery
            self._widget.jtc_combo.clear()
            self._update_jtc_list()
        else:
            self._list_controllers = None

    def _on_jtc_change(self, jtc_name):
        self._unload_jtc()
        self._jtc_name = jtc_name
        if self._jtc_name:
            self._load_jtc()

    def _on_jtc_enabled(self, val):

        # Don't allow enabling if there are no controllers selected
        if not self._jtc_name:
            self._widget.enable_button.setChecked(False)
            return

        # Enable/disable joint displays
        for joint_widget in self._joint_widgets():
            joint_widget.setEnabled(val)

        # Enable/disable speed scaling
        self._speed_scaling_widget.setEnabled(val)

        if val:
            # Widgets send desired position commands to controller
            self._update_act_pos_timer.stop()
            self._update_cmd_timer.start()
        else:
            # Controller updates widgets with actual position
            self._update_cmd_timer.stop()
            self._update_act_pos_timer.start()

    def _load_jtc(self):
        # Initialize joint data corresponding to selected controller
        running_jtc = self._running_jtc_info()
        self._joint_names = next(
            self._jtc_joint_names(x.name) for x in running_jtc
            if x.name == self._jtc_name)
        for name in self._joint_names:
            self._joint_pos[name] = {}

        # Update joint display
        try:
            layout = self._widget.joint_group.layout()
            for name in self._joint_names:
                limits = self._robot_joint_limits[name]
                joint_widget = DoubleEditor(limits['min_position'],
                                            limits['max_position'])
                layout.addRow(name, joint_widget)
                # NOTE: Using partial instead of a lambda because lambdas
                # "will not evaluate/look up the argument values before it is
                # effectively called, breaking situations like using a loop
                # variable inside it"
                from functools import partial
                par = partial(self._update_single_cmd_cb, name=name)
                joint_widget.valueChanged.connect(par)
        except:
            # TODO: Can we do better than swallow the exception?
            from sys import exc_info
            print('Unexpected error:', exc_info()[0])

        # Enter monitor mode (sending commands disabled)
        self._on_jtc_enabled(False)

        # Setup ROS interfaces
        jtc_ns = self._resolve_controller_ns(self._cm_ns, self._jtc_name)

        state_topic = '/state'
        cmd_topic = jtc_ns + '/joint_trajectory'

        #        self._state_sub = self._node.create_subscription(
        #                    JointTrajectoryControllerState,
        #                    '/state',
        #                    self._state_cb,
        #                    10)
        #        self._state_sub
        #        print("state sub set up")
        self._cmd_pub = self._node.create_publisher(JointTrajectory, cmd_topic,
                                                    1)

        # Start updating the joint positions
        self.jointStateChanged.connect(self._on_joint_state_change)

    def _unload_jtc(self):
        # Stop updating the joint positions
        try:
            self.jointStateChanged.disconnect(self._on_joint_state_change)
        except:
            pass

        # Reset ROS interfaces
        #self._unregister_state_sub()
        self._unregister_cmd_pub()

        # Clear joint widgets
        # NOTE: Implementation is a workaround for:
        # https://bugreports.qt-project.org/browse/QTBUG-15990 :(

        layout = self._widget.joint_group.layout()
        if layout is not None:
            while layout.count():
                layout.takeAt(0).widget().deleteLater()
            # Delete existing layout by reparenting to temporary
            QWidget().setLayout(layout)
        self._widget.joint_group.setLayout(QFormLayout())

        # Reset joint data
        self._joint_names = []
        self._joint_pos = {}

        # Enforce monitor mode (sending commands disabled)
        self._widget.enable_button.setChecked(False)

    def _running_jtc_info(self):
        controller_list = self._list_controllers
        jtc_list = [
            c for c in controller_list if 'JointTrajectoryController' in c.type
        ]
        running_jtc_list = [c for c in jtc_list if c.state == 'active']
        return running_jtc_list

    def _unregister_cmd_pub(self):
        if self._cmd_pub is not None:
            self._node.destroy_publisher(self._cmd_pub)
            self._state_sub = None

    def _unregister_state_sub(self):
        if self._state_sub is not None:
            self._node.destroy_subscription(self._state_sub)
            self._state_sub = None

    def _state_cb(self, msg):
        actual_pos = {}
        for i in range(len(msg.joint_names)):
            joint_name = msg.joint_names[i]
            joint_pos = msg.actual.positions[i]
            actual_pos[joint_name] = joint_pos
        self.jointStateChanged.emit(actual_pos)

    def _update_single_cmd_cb(self, val, name):
        self._joint_pos[name]['command'] = val

    def _update_cmd_cb(self):
        dur = []
        traj = JointTrajectory()
        traj.joint_names = self._joint_names
        point = JointTrajectoryPoint()
        for name in traj.joint_names:
            pos = self._joint_pos[name]['position']
            cmd = pos
            try:
                cmd = self._joint_pos[name]['command']
            except (KeyError):
                pass
            max_vel = self._robot_joint_limits[name]['max_velocity']
            dur.append(max(abs(cmd - pos) / max_vel, self._min_traj_dur))
            point.positions.append(cmd)

        point.time_from_start = Duration(seconds=(max(dur) /
                                                  self._speed_scale)).to_msg()
        traj.points.append(point)

        self._cmd_pub.publish(traj)

    def _update_joint_widgets(self):
        rclpy.spin_once(self._node)

        joint_widgets = self._joint_widgets()
        for id in range(len(joint_widgets)):
            joint_name = self._joint_names[id]
            try:
                joint_pos = self._joint_pos[joint_name]['position']
                joint_widgets[id].setValue(joint_pos)
            except (KeyError):
                pass  # Can happen when first connected to controller

    def _joint_widgets(self):  # TODO: Cache instead of compute every time?
        widgets = []
        layout = self._widget.joint_group.layout()
        for row_id in range(layout.rowCount()):
            widgets.append(
                layout.itemAt(row_id, QFormLayout.FieldRole).widget())
        return widgets

    def _jtc_joint_names(self, jtc_name):
        # NOTE: We assume that there is at least one hardware interface that
        # claims resources (there should be), and the resource list is fetched
        # from the first available interface
        if jtc_name not in self._jtc_joints_info:
            self._jtc_joints_info[jtc_name] = call_get_parameters(
                node=self._node,
                node_name=jtc_name,
                parameter_names=['joints']).values[0].string_array_value

        return self._jtc_joints_info[jtc_name]

    def _resolve_controller_ns(self, cm_ns, controller_name):
        """
        Resolve a controller's namespace from that of the controller manager.
        Controllers are assumed to live one level above the controller
        manager, e.g.

            >>> _resolve_controller_ns('/path/to/controller_manager', 'foo')
            '/path/to/foo'

        In the particular case in which the controller manager is not
        namespaced, the controller is assumed to live in the root namespace

            >>> _resolve_controller_ns('/', 'foo')
            '/foo'
            >>> _resolve_controller_ns('', 'foo')
            '/foo'
        @param cm_ns Controller manager namespace (can be an empty string)
        @type cm_ns str
        @param controller_name Controller name (non-empty string)
        @type controller_name str
        @return Controller namespace
        @rtype str
        """
        assert (controller_name)
        ns = cm_ns.rsplit('/', 1)[0]
        if ns != '/':
            ns += '/'
        ns += controller_name
        return ns
コード例 #18
0
ファイル: conman.py プロジェクト: nasnysom/conman
class Conman(Plugin):
    update_graph_sig = Signal(str)

    def __init__(self, context):
        super(Conman, self).__init__(context)

        self._dotcode_sub = None

        # Give QObjects reasonable names
        self.setObjectName('Conman')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()
        # Add argument(s) to the parser.
        parser.add_argument("-q", "--quiet", action="store_true",
                      dest="quiet",
                      help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        if not args.quiet:
            print 'arguments: ', args
            print 'unknowns: ', unknowns

        # Create QWidget
        self._widget = QWidget()
        # Get path to UI file which is a sibling of this file
        # in this example the .ui and .py file are in the same folder
        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'rqt_conman.ui')
        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget)
        # Give QObjects reasonable names

        self._widget.setObjectName('ConmanPluginUi')
        # Show _widget.windowTitle on left-top of each plugin (when 
        # it's set in _widget). This is useful when you open multiple 
        # plugins at once. Also if you open multiple instances of your 
        # plugin at once, these lines add number to make it easy to 
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))

        # Add widget to the user interface
        context.add_widget(self._widget)
        palette = QPalette ()
        palette.setColor(QPalette.Background, Qt.white)
        self._widget.setPalette(palette)

        #self._widget.subscribe_button.setCheckable(True)

        self._widget.refresh_button.clicked[bool].connect(self._handle_refresh_clicked)
        self._widget.commit_button.clicked[bool].connect(self._handle_commit_clicked)

        #self._widget.xdot_widget.connect(
                #self._widget.xdot_widget, SIGNAL('_update_graph'), self._widget.xdot_widget.set_dotcode)
        self.update_graph_sig.connect(self._update_graph)

        self.blocks = { }
        self.groups = { }

        self._ns = ""
        self._actions_connected = False
        self.enable_widgets(False)
        self.new_dotcode_data = ''

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(50)
        self.update_timer.timeout.connect(self._update_widgets)
        #self.update_timer.start()


        self._get_blocks = None
        self._set_blocks = None

        self._blocks_model = QStandardItemModel(0,4)
        self._blocks_model.setHeaderData(0, Qt.Horizontal, "")
        self._blocks_model.setHeaderData(1, Qt.Horizontal, "Action")
        self._blocks_model.setHeaderData(2, Qt.Horizontal, "State")
        self._blocks_model.setHeaderData(3, Qt.Horizontal, "Block")
        self._widget.blocks_table.setModel(self._blocks_model)
        self._blocks_delegate = BlocksDelegate(self)
        self._widget.blocks_table.setItemDelegate(self._blocks_delegate)
        self._blocks_model.itemChanged.connect(self.block_changed)

        self._groups_model = QStandardItemModel(0,4)
        self._groups_model.setHeaderData(0, Qt.Horizontal, "")
        self._groups_model.setHeaderData(1, Qt.Horizontal, "")
        self._groups_model.setHeaderData(2, Qt.Horizontal, "")
        self._groups_model.setHeaderData(3, Qt.Horizontal, "Group")
        self._widget.groups_table.setModel(self._groups_model)
        self._groups_delegate = GroupsDelegate(self)
        self._widget.groups_table.setItemDelegate(self._groups_delegate)

    
    def block_changed(self, item):
        row = item.row()
        name = self._blocks_model.item(row,3).text()
        block = self.blocks[name]
        checked = item.checkState() == Qt.Checked

    def shutdown_plugin(self):
        # TODO unregister all publishers here
        pass

    def save_settings(self, plugin_settings, instance_settings):
        # TODO save intrinsic configuration, usually using:
        # instance_settings.set_value(k, v)
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass

    #def trigger_configuration(self):
        # Comment in to signal that the plugin has a way to configure
        # This will enable a setting button (gear icon) in each dock widget title bar
        # Usually used to open a modal configuration dialog

    def _get_result_cb(self, status, res):
        rospy.loginfo("got result!")
        self._blocks_model.setRowCount(0)
        self._blocks_model.setRowCount(len(res.blocks))

        for (i,block) in zip(range(len(res.blocks)),res.blocks):

            # Store in dict
            self.blocks[block.name] = block

            cb = QStandardItem(True)
            cb.setCheckable(True)
            cb.setCheckState(Qt.Checked if block.state.value == conman_msgs.msg.TaskState.RUNNING else Qt.Unchecked)
            cb.setTextAlignment(Qt.AlignHCenter)
            cb.setTristate(True)

            action = QStandardItem(True)
            action.setText("")

            state = QStandardItem(True)
            state.setText(state_map[block.state.value])

            name = QStandardItem(True)
            name.setText(str(block.name))

            self._blocks_model.setItem(i,0,cb)
            self._blocks_model.setItem(i,1,action)
            self._blocks_model.setItem(i,2,state)
            self._blocks_model.setItem(i,3,name)

        for (i,group) in zip(range(len(res.groups)),res.groups):

            self.groups[group.name] = group

            cb = QStandardItem(True)
            cb.setCheckable(True)
            cb.setCheckState(Qt.Checked)
            cb.setTextAlignment(Qt.AlignHCenter)
            cb.setEnabled(False)

            name = QStandardItem(True)
            name.setText(str(group.name))

            self._groups_model.setItem(i,0,cb)
            self._groups_model.setItem(i,3,name)

        self._update_groups()
        self._update_blocks()

        self._widget.blocks_table.resizeColumnsToContents()
        self._widget.blocks_table.horizontalHeader().setStretchLastSection(True)
        self._widget.groups_table.resizeColumnsToContents()
        self._widget.groups_table.horizontalHeader().setStretchLastSection(True)

    def _update_blocks(self):
        
        for (name, block) in self.blocks.items():
            items = self._blocks_model.findItems(name, column=3)
            if len(items) > 0:
                item = items[0]
            else:
                continue
            
            row = item.row()
            checked = self._blocks_model.item(row,0).checkState() == Qt.Checked
            if checked and block.state.value != conman_msgs.msg.TaskState.RUNNING:
                self._blocks_model.item(row,1).setText("ENABLE")
            elif not checked and block.state.value == conman_msgs.msg.TaskState.RUNNING:
                self._blocks_model.item(row,1).setText("DISABLE")
            else:
                self._blocks_model.item(row,1).setText("")
        self._update_groups()

    def _enable_group(self, index, enable):
        name = self._groups_model.item(index, 3).text()
        group = self.groups[name]

        for member in group.members:
            items = self._blocks_model.findItems(member, column=3)
            if len(items) > 0:
                item = items[0]
            else:
                continue
            
            row = item.row()
            self._blocks_model.item(row,0).setCheckState(Qt.Checked if enable else Qt.Unchecked)

        self._update_blocks()
        
    def _update_groups(self):
        for (name, group) in self.groups.items():
            group_items = self._groups_model.findItems(name, column=3)
            if len(group_items) > 0:
                group_item = group_items[0]
            else:
                continue
            group_row = group_item.row()

            members_checked = []

            for member in group.members:
                items = self._blocks_model.findItems(member, column=3)
                if len(items) > 0:
                    item = items[0]
                else:
                    continue
                row = item.row()
                members_checked.append(self._blocks_model.item(row,0).checkState() == Qt.Checked)

            if all(members_checked):
                check = Qt.Checked
            else:
                check = Qt.Unchecked

            self._groups_model.item(group_row,0).setCheckState(check)


    def _query_blocks(self):
        if self._get_blocks and self._actions_connected:
            if self._get_blocks.simple_state == actionlib.SimpleGoalState.DONE:
                rospy.loginfo("Getting blocks...")
                goal = conman_msgs.msg.GetBlocksGoal()
                goal.publish_flow_graph = self._widget.generate_graph_checkbox.isChecked()
                self._get_blocks.send_goal(goal, done_cb=self._get_result_cb)


    def enable_widgets(self, enable):
        
        #self._widget.generate_graph_checkbox.setEnabled(enable)
        self._widget.force_enable_checkbox.setEnabled(enable)
        #self._widget.disable_unused_button.setEnabled(enable)
        self._widget.xdot_widget.setEnabled(enable)
        self._widget.blocks_table.setEnabled(enable)
        self._widget.groups_table.setEnabled(enable)
        self._widget.regenerate_graph_button.setEnabled(enable)

    def _handle_refresh_clicked(self, checked):
        ns = self._widget.namespace_input.text()

        if len(ns) > 0:
            if self._ns != ns:
                self._actions_connected = False
                self._ns = ns
                self.enable_widgets(False)

                self._dotcode_sub = rospy.Subscriber(
                        ns+'/dotcode',
                        std_msgs.msg.String,
                        self._dotcode_msg_cb)

                self._get_blocks = actionlib.SimpleActionClient(
                        ns+'/get_blocks_action',
                        conman_msgs.msg.GetBlocksAction)

                self._set_blocks = actionlib.SimpleActionClient(
                        ns+'/set_blocks_action',
                        conman_msgs.msg.SetBlocksAction)

                rospy.loginfo("Creating action clients on namespace '%s'..." % ns)

                if not self._get_blocks.wait_for_server(rospy.Duration(2)):
                    rospy.loginfo("Timed out waiting for %s." % self._get_blocks.action_client.ns)
                    return
                if not self._set_blocks.wait_for_server(rospy.Duration(2)):
                    rospy.loginfo("Timed out waiting for %s." % self._set_blocks.action_client.ns)
                    return

                rospy.loginfo("Action clients created.")
                self._actions_connected = True
                self.enable_widgets(True)

            self._query_blocks()


    def _handle_commit_clicked(self, checked):
        if self._set_blocks and self._actions_connected:
            if self._get_blocks.simple_state == actionlib.SimpleGoalState.DONE:
                rospy.loginfo("Setting blocks...")
                goal = conman_msgs.msg.SetBlocksGoal()
                goal.diff = True
                goal.force = True

                for i in range(self._blocks_model.rowCount()):
                    name = self._blocks_model.item(i,3).text()
                    action = self._blocks_model.item(i,1).text()

                    if action == 'DISABLE':
                        goal.disable.append(name)
                    elif action == 'ENABLE':
                        goal.enable.append(name)

                self._set_blocks.send_goal(goal, done_cb=self._get_set_result_cb)

    def _get_set_result_cb(self, status, res):

        self._query_blocks()

    @Slot(str)
    def _update_graph(self,dotcode):
        self._widget.xdot_widget.set_dotcode(dotcode, center=False)

    def _dotcode_msg_cb(self, msg):
        #self.new_dotcode_data = msg.data
        self.update_graph_sig.emit(msg.data)
    
    def _update_widgets(self):
        self._update_groups()
        self._update_blocks()
コード例 #19
0
class BarrettDashboard(Plugin):
    def __init__(self, context):
        super(BarrettDashboard, self).__init__(context)

        self._joint_sub = None

        # Give QObjects reasonable names
        self.setObjectName('BarrettDashboard')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()
        # Add argument(s) to the parser.
        parser.add_argument("-q",
                            "--quiet",
                            action="store_true",
                            dest="quiet",
                            help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        if not args.quiet:
            print 'arguments: ', args
            print 'unknowns: ', unknowns

        # Create QWidget
        self._widget = QWidget()
        # Get path to UI file which is a sibling of this file
        # in this example the .ui and .py file are in the same folder
        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                               'barrett_dashboard.ui')
        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget)
        # Give QObjects reasonable names

        self._widget.setObjectName('BarrettDashboardPluginUi')
        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))

        # Add widget to the user interface
        context.add_widget(self._widget)
        self.context = context

        jp_widgets = [getattr(self._widget, 'jp_%d' % i) for i in range(7)]
        jn_widgets = [getattr(self._widget, 'jn_%d' % i) for i in range(7)]
        self.joint_widgets = zip(jp_widgets, jn_widgets)

        tp_widgets = [getattr(self._widget, 'tp_%d' % i) for i in range(7)]
        tn_widgets = [getattr(self._widget, 'tn_%d' % i) for i in range(7)]
        self.torque_widgets = zip(tp_widgets, tn_widgets)

        self.joint_signals = []
        self.torque_signals = []

        for (tp, tn) in self.torque_widgets:
            tp.setRange(0.0, 1.0, False)
            tn.setRange(1.0, 0.0, False)
            tp.setValue(0.0)
            tn.setValue(0.0)
            # set random values for testing
            #v = (2.0*random.random()) - 1.0
            #tp.setValue(v if v >=0 else 0)
            #tn.setValue(-v if v <0 else 0)

        for (jp, jn) in self.joint_widgets:
            jp.setRange(0.0, 1.0, False)
            jn.setRange(1.0, 0.0, False)
            jp.setValue(0.0)
            jn.setValue(0.0)
            # set random values for testing
            #v = (2.0*random.random()) - 1.0
            #jp.setValue(v if v >=0 else 0)
            #jn.setValue(-v if v <0 else 0)

        self.barrett_green = QColor(87, 186, 142)
        self.barrett_green_dark = self.barrett_green.darker()
        self.barrett_green_light = self.barrett_green.lighter()
        self.barrett_blue = QColor(80, 148, 204)
        self.barrett_blue_dark = self.barrett_blue.darker()
        self.barrett_blue_light = self.barrett_blue.lighter()
        self.barrett_red = QColor(232, 47, 47)
        self.barrett_red_dark = self.barrett_red.darker()
        self.barrett_red_light = self.barrett_red.lighter()
        self.barrett_orange = QColor(255, 103, 43)
        self.barrett_orange_dark = self.barrett_orange.darker()

        #background_color = p.mid().color()
        joint_bg_color = self.barrett_blue_dark.darker()
        joint_fill_color = self.barrett_blue
        joint_alarm_color = self.barrett_blue  #self.barrett_blue_light
        torque_bg_color = self.barrett_green_dark.darker()
        torque_fill_color = self.barrett_green
        torque_alarm_color = self.barrett_orange  #self.barrett_green_light
        temp_bg_color = self.barrett_red_dark.darker()
        temp_fill_color = self.barrett_orange
        temp_alarm_color = self.barrett_red

        for w in jp_widgets + jn_widgets:
            w.setAlarmLevel(0.80)
            w.setFillColor(joint_fill_color)
            w.setAlarmColor(joint_alarm_color)
            p = w.palette()
            p.setColor(tp.backgroundRole(), joint_bg_color)
            w.setPalette(p)

        for w in tp_widgets + tn_widgets:
            w.setAlarmLevel(0.66)
            w.setFillColor(torque_fill_color)
            w.setAlarmColor(torque_alarm_color)
            p = w.palette()
            p.setColor(tp.backgroundRole(), torque_bg_color)
            w.setPalette(p)

        self._widget.hand_temp.setAlarmLevel(0.66)
        self._widget.hand_temp.setFillColor(temp_fill_color)
        self._widget.hand_temp.setAlarmColor(temp_alarm_color)
        p = self._widget.hand_temp.palette()
        p.setColor(self._widget.hand_temp.backgroundRole(), temp_bg_color)
        self._widget.hand_temp.setPalette(p)
        self._widget.hand_temp.setOrientation(Qt.Horizontal,
                                              QwtThermo.RightScale)

        self._widget.jf_0.setStyleSheet(
            "QLabel { background-color : rgb(%d,%d,%d); color : rgb(%d,%d,%d); }"
            % (joint_bg_color.red(), joint_bg_color.green(),
               joint_bg_color.blue(), joint_fill_color.red(),
               joint_fill_color.green(), joint_fill_color.blue()))

        self.urdf = rospy.get_param('robot_description')
        self.robot = URDF()
        self.robot = self.robot.from_xml_string(self.urdf)

        self.pos_norm = [0] * 7
        self.torque_norm = [0] * 7

        self._joint_sub = rospy.Subscriber('joint_states',
                                           sensor_msgs.msg.JointState,
                                           self._joint_state_cb)

        self._status_sub = rospy.Subscriber('status',
                                            oro_barrett_msgs.msg.BarrettStatus,
                                            self._status_cb)

        self._hand_status_sub = rospy.Subscriber(
            'hand/status', oro_barrett_msgs.msg.BHandStatus,
            self._hand_status_cb)

        self._update_status(-1)
        self.safety_mode = -1
        self.run_mode = 0
        self.hand_run_mode = 0
        self.hand_min_temperature = 40.0
        self.hand_max_temperature = 65.0
        self.hand_temperature = 0.0

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(50)
        self.update_timer.timeout.connect(self._update_widget_values)
        self.update_timer.start()

        self.set_home_client = actionlib.SimpleActionClient(
            'wam/set_home_action', oro_barrett_msgs.msg.SetHomeAction)
        self.set_mode_client = actionlib.SimpleActionClient(
            'wam/set_mode_action', oro_barrett_msgs.msg.SetModeAction)

        self._widget.button_set_home.clicked[bool].connect(
            self._handle_set_home_clicked)
        self._widget.button_idle_wam.clicked[bool].connect(
            self._handle_idle_wam_clicked)
        self._widget.button_run_wam.clicked[bool].connect(
            self._handle_run_wam_clicked)

        self.bhand_init_client = actionlib.SimpleActionClient(
            'hand/initialize_action', oro_barrett_msgs.msg.BHandInitAction)
        self.bhand_set_mode_client = actionlib.SimpleActionClient(
            'hand/set_mode_action', oro_barrett_msgs.msg.BHandSetModeAction)
        self.grasp_client = actionlib.SimpleActionClient(
            'grasp', BHandGraspAction)
        self.release_client = actionlib.SimpleActionClient(
            'release', BHandReleaseAction)
        self.spread_client = actionlib.SimpleActionClient(
            'spread', BHandSpreadAction)

        self._widget.button_initialize_hand.clicked[bool].connect(
            self._handle_initialize_hand_clicked)
        self._widget.button_idle_hand.clicked[bool].connect(
            self._handle_idle_hand_clicked)
        self._widget.button_run_hand.clicked[bool].connect(
            self._handle_run_hand_clicked)

        self._widget.button_release_hand.clicked[bool].connect(
            self._handle_release_hand_clicked)
        self._widget.button_grasp_hand.clicked[bool].connect(
            self._handle_grasp_hand_clicked)
        self._widget.button_set_spread.clicked[bool].connect(
            self._handle_spread_hand_clicked)

        self._widget.resizeEvent = self._handle_resize

    def _handle_resize(self, event):
        for i, ((w1, w2), (w3, w4)) in enumerate(
                zip(self.joint_widgets, self.torque_widgets)):
            width = 0.8 * getattr(self._widget,
                                  'jcc_%d' % i).contentsRect().width()
            w1.setPipeWidth(width)
            w2.setPipeWidth(width)
            w3.setPipeWidth(width)
            w4.setPipeWidth(width)

    def _handle_set_home_clicked(self, checked):
        if checked:
            self.set_home()

    def _update_status(self, status):
        if status == -1:
            self._widget.safety_mode.setText('UNKNOWN SAFETY MODE')
            self._widget.safety_mode.setToolTip(
                'The WAM is in an unknown state. Proceed with caution.')
            color = QColor(200, 200, 200)
            self._widget.button_set_home.setEnabled(False)
            self._widget.button_idle_wam.setEnabled(False)
            self._widget.button_run_wam.setEnabled(False)
        elif status == 0:
            self._widget.safety_mode.setText('E-STOP')
            self._widget.safety_mode.setToolTip(
                'The WAM is stopped and unpowered. Determine the source of the fault and reset the arm to continue using it.'
            )
            self._widget.button_set_home.setEnabled(False)
            self._widget.button_idle_wam.setEnabled(False)
            self._widget.button_run_wam.setEnabled(False)
            color = self.barrett_red
        else:
            if not self.homed:
                self._widget.safety_mode.setText('UNCALIBRATED')
                self._widget.safety_mode.setToolTip(
                    'The WAM is not calibrated. Please place it in the calibration posture and click the "Calibrate" button.'
                )
                self._widget.button_set_home.setEnabled(True)
                self._widget.button_idle_wam.setEnabled(False)
                self._widget.button_run_wam.setEnabled(False)
                color = self.barrett_orange
            else:
                if status == 1:
                    self._widget.safety_mode.setText('IDLE')
                    self._widget.safety_mode.setToolTip(
                        'The WAM is running but all joints are passive. It is safe to home the arm.'
                    )
                    self._widget.button_set_home.setEnabled(True)
                    self._widget.button_idle_wam.setEnabled(True)
                    self._widget.button_run_wam.setEnabled(True)
                    color = self.barrett_blue
                elif status == 2:
                    self._widget.safety_mode.setText('ACTIVE')
                    self._widget.safety_mode.setToolTip(
                        'The WAM is running and all joints are active. Proceed with caution.'
                    )
                    self._widget.button_set_home.setEnabled(False)
                    self._widget.button_idle_wam.setEnabled(False)
                    self._widget.button_run_wam.setEnabled(False)
                    color = self.barrett_green

        darker = color.darker()
        lighter = color.lighter()
        self._widget.safety_mode.setStyleSheet(
            "QLabel { background-color : rgb(%d,%d,%d); color : rgb(%d,%d,%d); }"
            % (color.red(), color.green(), color.blue(), lighter.red(),
               lighter.green(), lighter.blue()))

    def _update_widget_values(self):

        if self.safety_mode in (oro_barrett_msgs.msg.SafetyMode.ACTIVE,
                                oro_barrett_msgs.msg.SafetyMode.IDLE):
            for (v, (tp, tn)) in zip(self.torque_norm, self.torque_widgets):
                tp.setEnabled(True)
                tn.setEnabled(True)
                tp.setValue(v if v >= 0 else 0)
                tn.setValue(-v if v < 0 else 0)

            for (v, (jp, jn)) in zip(self.pos_norm, self.joint_widgets):
                jp.setEnabled(True)
                jn.setEnabled(True)
                jp.setValue(v if v >= 0 else 0)
                jn.setValue(-v if v < 0 else 0)
        else:
            for (p, n) in self.joint_widgets + self.torque_widgets:
                p.setEnabled(True)
                n.setEnabled(True)

        self._widget.hand_temp.setValue(
            (self.hand_temperature - self.hand_min_temperature) /
            (self.hand_max_temperature - self.hand_min_temperature))

        self._update_status(self.safety_mode)
        self._update_buttons(self.run_mode, self.hand_run_mode)

    def _update_buttons(self, run_mode, hand_run_mode):
        if run_mode == oro_barrett_msgs.msg.RunMode.IDLE:
            self._widget.button_idle_wam.setChecked(True)
            self._widget.button_run_wam.setChecked(False)
        else:
            self._widget.button_idle_wam.setChecked(False)
            self._widget.button_run_wam.setChecked(True)

        if hand_run_mode == oro_barrett_msgs.msg.RunMode.RUN:
            self._widget.button_idle_hand.setChecked(False)
            self._widget.button_run_hand.setChecked(True)
        else:
            self._widget.button_idle_hand.setChecked(True)
            self._widget.button_run_hand.setChecked(False)

    def shutdown_plugin(self):
        # TODO unregister all publishers here
        pass

    def save_settings(self, plugin_settings, instance_settings):
        # TODO save intrinsic configuration, usually using:
        # instance_settings.set_value(k, v)
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass

    #def trigger_configuration(self):
    # Comment in to signal that the plugin has a way to configure
    # This will enable a setting button (gear icon) in each dock widget title bar
    # Usually used to open a modal configuration dialog

    def _joint_state_cb(self, msg):
        joint_pos_map = dict(zip(msg.name, msg.position))

        for (name, pos, torque, i) in zip(msg.name, msg.position, msg.effort,
                                          range(7)):
            joint = self.robot.joint_map[name]
            self.pos_norm[i] = 2.0 * (
                (pos - joint.limit.lower) /
                (joint.limit.upper - joint.limit.lower)) - 1.0
            self.torque_norm[i] = torque / joint.limit.effort

    def _status_cb(self, msg):
        self.safety_mode = msg.safety_mode.value
        self.run_mode = msg.run_mode.value
        self.homed = msg.homed

        if self.safety_mode == oro_barrett_msgs.msg.SafetyMode.ACTIVE:
            self._widget.button_initialize_hand.setEnabled(False)
        elif self.safety_mode == oro_barrett_msgs.msg.SafetyMode.ESTOP:
            self._widget.button_initialize_hand.setEnabled(False)
            self._widget.button_idle_hand.setEnabled(False)
            self._widget.button_run_hand.setEnabled(False)

    def _hand_status_cb(self, msg):
        self.hand_initialized = msg.initialized
        self.hand_temperature = max(msg.temperature)
        self.hand_run_mode = msg.run_mode.value

    def _handle_set_home_clicked(self, checked):
        goal = oro_barrett_msgs.msg.SetHomeGoal()
        self.set_home_client.send_goal(goal)

    def _handle_idle_wam_clicked(self, checked):
        if checked:
            goal = oro_barrett_msgs.msg.SetModeGoal()
            goal.mode.value = oro_barrett_msgs.msg.RunMode.IDLE
            self.set_mode_client.send_goal(goal)

    def _handle_run_wam_clicked(self, checked):
        if checked:
            goal = oro_barrett_msgs.msg.SetModeGoal()
            goal.mode.value = oro_barrett_msgs.msg.RunMode.RUN
            self.set_mode_client.send_goal(goal)

    def _handle_initialize_hand_clicked(self, checked):
        if self.safety_mode == oro_barrett_msgs.msg.SafetyMode.IDLE:
            goal = oro_barrett_msgs.msg.BHandInitGoal()
            self.bhand_init_client.send_goal(goal)

    def _handle_run_hand_clicked(self, checked):
        if checked:
            goal = oro_barrett_msgs.msg.BHandSetModeGoal()
            goal.run_mode.value = oro_barrett_msgs.msg.RunMode.RUN
            self.bhand_set_mode_client.send_goal(goal)

    def _handle_idle_hand_clicked(self, checked):
        if checked:
            goal = oro_barrett_msgs.msg.BHandSetModeGoal()
            goal.run_mode.value = oro_barrett_msgs.msg.RunMode.IDLE
            self.bhand_set_mode_client.send_goal(goal)

    def _get_grasp_mask(self):
        return [
            self._widget.button_use_f1.isChecked(),
            self._widget.button_use_f2.isChecked(),
            self._widget.button_use_f3.isChecked()
        ]

    def _handle_release_hand_clicked(self, checked):
        goal = BHandReleaseGoal()
        goal.release_mask = self._get_grasp_mask()
        goal.release_speed = [3.0, 3.0, 3.0]

        self.release_client.send_goal(goal)

    def _handle_grasp_hand_clicked(self, checked):
        goal = BHandGraspGoal()
        goal.grasp_mask = self._get_grasp_mask()
        goal.grasp_speed = [1.0, 1.0, 1.0]
        goal.grasp_effort = [1.0, 1.0, 1.0]
        goal.min_fingertip_radius = 0.0

        self.grasp_client.send_goal(goal)

    def _handle_spread_hand_clicked(self, checked):
        goal = BHandSpreadGoal()
        goal.spread_position = self._widget.spread_slider.value(
        ) / 1000.0 * 3.141

        self.spread_client.send_goal(goal)
コード例 #20
0
class DynamicTimeline(QGraphicsScene):
    """
    BagTimeline contains bag files, all information required to display the bag data visualization on the screen
    Also handles events
    """
    status_bar_changed_signal = Signal()
    selected_region_changed = Signal(rospy.Time, rospy.Time)
    timeline_updated = Signal()
    Topic = collections.namedtuple('Topic', ['subscriber', 'queue'])
    Message = collections.namedtuple('Message', ['stamp', 'message'])

    def __init__(self, context, publish_clock):
        """
        :param context: plugin context hook to enable adding rqt_bag plugin widgets as ROS_GUI snapin panes, ''PluginContext''
        """
        super(DynamicTimeline, self).__init__()
        # key is topic name, value is a named tuple of type Topic. The deque
        # contains named tuples of type Message
        self._topics = {}
        # key is the data type, value is a list of topics with that type
        self._datatypes = {}
        self._topic_lock = threading.RLock()

        self.background_task = None  # Display string
        self.background_task_cancel = False

        # Playing / Recording
        self._playhead_lock = threading.RLock()
        self._max_play_speed = 1024.0  # fastest X play speed
        self._min_play_speed = 1.0 / 1024.0  # slowest X play speed
        self._play_speed = 0.0
        self._play_all = False
        self._playhead_positions_cvs = {}
        self._playhead_positions = {}  # topic -> position
        self._message_loaders = {}
        self._messages_cvs = {}
        self._messages = {}  # topic -> msg_data
        self._message_listener_threads = {
        }  # listener -> MessageListenerThread
        self._player = False
        self._publish_clock = publish_clock
        self._recorder = None
        self.last_frame = None
        self.last_playhead = None
        self.desired_playhead = None
        self.wrap = True  # should the playhead wrap when it reaches the end?
        self.stick_to_end = False  # should the playhead stick to the end?
        self._play_timer = QTimer()
        self._play_timer.timeout.connect(self.on_idle)
        self._play_timer.setInterval(3)
        self._redraw_timer = None  # timer which can be used to periodically redraw the timeline

        # Plugin popup management
        self._context = context
        self.popups = {}
        self._views = []
        self._listeners = {}

        # Initialize scene
        # the timeline renderer fixes use of black pens and fills, so ensure we fix white here for contrast.
        # otherwise a dark qt theme will default it to black and the frame render pen will be unreadable
        self.setBackgroundBrush(Qt.white)
        self._timeline_frame = DynamicTimelineFrame(self)
        self._timeline_frame.setPos(0, 0)
        self.addItem(self._timeline_frame)

        self.background_progress = 0
        self.__closed = False

        # timer to periodically redraw the timeline every so often
        self._start_redraw_timer()

    def get_context(self):
        """
        :returns: the ROS_GUI context, 'PluginContext'
        """
        return self._context

    def _start_redraw_timer(self):
        if not self._redraw_timer:
            self._redraw_timer = rospy.Timer(rospy.Duration(0.5),
                                             self._redraw_timeline)

    def _stop_redraw_timer(self):
        if self._redraw_timer:
            self._redraw_timer.shutdown()
            self._redraw_timer = None

    def handle_close(self):
        """
        Cleans up the timeline, subscribers and any threads
        """
        if self.__closed:
            return
        else:
            self.__closed = True
        self._play_timer.stop()
        for topic in self._get_topics():
            self.stop_publishing(topic)
            self._message_loaders[topic].stop()
        if self._player:
            self._player.stop()
        if self._recorder:
            self._recorder.stop()
        if self.background_task is not None:
            self.background_task_cancel = True
        self._timeline_frame.handle_close()
        for topic in self._topics:
            self._topics[topic][0].unregister()  # unregister the subscriber
        for frame in self._views:
            if frame.parent():
                self._context.remove_widget(frame)

    def _redraw_timeline(self, timer):
        # save the playhead so that the redraw doesn't move it
        playhead = self._timeline_frame._playhead
        if playhead is None:
            start = self._timeline_frame.play_region[0] is None
            end = self._timeline_frame.play_region[1] is None
        else:
            start = True if playhead <= self._timeline_frame.play_region[
                0] else False
            end = True if playhead >= self._timeline_frame.play_region[
                1] else False

        # do not keep setting this if you want the timeline to just grow.
        self._timeline_frame._start_stamp = self._get_start_stamp()
        self._timeline_frame._end_stamp = self._get_end_stamp()

        self._timeline_frame.reset_zoom()

        if end:
            # use play region instead of time.now to stop playhead going past
            # the end of the region, which causes problems with
            # navigate_previous
            self._timeline_frame._set_playhead(
                self._timeline_frame.play_region[1])
        elif start:
            self._timeline_frame._set_playhead(
                self._timeline_frame.play_region[0])
        else:
            if playhead:
                self._timeline_frame._set_playhead(playhead)

        self.timeline_updated.emit()

    def topic_callback(self, msg, topic):
        """
        Called whenever a message is received on any of the subscribed topics

        :param topic: the topic on which the message was received
        :param msg: the message received

        """
        message = self.Message(stamp=rospy.Time.now(), message=msg)
        with self._topic_lock:
            self._topics[topic].queue.append(message)

        # Invalidate entire cache for this topic
        with self._timeline_frame.index_cache_cv:
            self._timeline_frame.invalidated_caches.add(topic)
            # if topic in self._timeline_frame.index_cache:
            #     del self._timeline_frame.index_cache[topic]
            self._timeline_frame.index_cache_cv.notify()

    def add_topic(self, topic, type, num_msgs=20):
        """Creates an indexing thread for the new topic. Fixes the borders and notifies
        the indexing thread to index the new items bags

        :param topic: a topic to listen to
        :param type: type of the topic to listen to
        :param num_msgs: number of messages to retain on this topic. If this
            value is exceeded, the oldest messages will be dropped

        :return: false if topic already in the timeline, true otherwise

        """
        # first item in each sub-list is the name, second is type
        if topic not in self._topics:
            self._topics[topic] = self.Topic(
                subscriber=rospy.Subscriber(topic,
                                            type,
                                            queue_size=1,
                                            callback=self.topic_callback,
                                            callback_args=topic),
                queue=collections.deque(maxlen=num_msgs))
            self._datatypes.setdefault(type, []).append(topic)
        else:
            return False

        self._playhead_positions_cvs[topic] = threading.Condition()
        self._messages_cvs[topic] = threading.Condition()
        self._message_loaders[topic] = MessageLoaderThread(self, topic)

        self._timeline_frame._start_stamp = self._get_start_stamp()
        self._timeline_frame._end_stamp = self._get_end_stamp()
        self._timeline_frame.topics = self._get_topics()
        self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype(
        )
        # If this is the first bag, reset the timeline
        self._timeline_frame.reset_zoom()

        # Invalidate entire cache for this topic
        with self._timeline_frame.index_cache_cv:
            self._timeline_frame.invalidated_caches.add(topic)
            if topic in self._timeline_frame.index_cache:
                del self._timeline_frame.index_cache[topic]
            self._timeline_frame.index_cache_cv.notify()

        return True

    # TODO Rethink API and if these need to be visible
    def _get_start_stamp(self):
        """

        :return: stamp of the first message received on any of the topics, or none if no messages have been received, ''rospy.Time''
        """
        with self._topic_lock:
            start_stamp = None
            for unused_topic_name, topic_tuple in self._topics.items():
                topic_start_stamp = topic_helper.get_start_stamp(topic_tuple)
                if topic_start_stamp is not None and (
                        start_stamp is None
                        or topic_start_stamp < start_stamp):
                    start_stamp = topic_start_stamp
            return start_stamp

    def _get_end_stamp(self):
        """

        :return: stamp of the last message received on any of the topics, or none if no messages have been received, ''rospy.Time''
        """
        with self._topic_lock:
            end_stamp = None
            for unused_topic_name, topic_tuple in self._topics.items():
                topic_end_stamp = topic_helper.get_end_stamp(topic_tuple)
                if topic_end_stamp is not None and (
                        end_stamp is None or topic_end_stamp > end_stamp):
                    end_stamp = topic_end_stamp
            return end_stamp

    def _get_topics(self):
        """
        :return: sorted list of topic names, ''list(str)''
        """
        with self._topic_lock:
            topics = []
            for topic in self._topics:
                topics.append(topic)
            return sorted(topics)

    def _get_topics_by_datatype(self):
        """
        :return: dict of list of topics for each datatype, ''dict(datatype:list(topic))''
        """
        with self._topic_lock:
            return self._datatypes

    def get_datatype(self, topic):
        """
        :return: datatype associated with a topic, ''str''
        :raises: if there are multiple datatypes assigned to a single topic, ''Exception''
        """
        with self._topic_lock:
            topic_types = []
            for datatype in self._datatypes:
                if topic in self._datatypes[datatype]:
                    if len(topic_types) == 1:
                        raise Exception(
                            "Topic {0} had multiple datatypes ({1}) associated with it"
                            .format(topic, str(topic_types)))
                    topic_types.append(datatype._type)

            if not topic_types:
                return None
            else:
                return topic_types[0]

    def get_entries(self, topics, start_stamp, end_stamp):
        """
        generator function for topic entries
        :param topics: list of topics to query, ''list(str)''
        :param start_stamp: stamp to start at, ''rospy.Time''
        :param end_stamp: stamp to end at, ''rospy,Time''
        :returns: messages on the given topics in chronological order, ''msg''
        """
        with self._topic_lock:
            topic_entries = []
            # make sure that we can handle a single topic as well
            for topic in topics:
                if not topic in self._topics:
                    rospy.logwarn(
                        "Dynamic Timeline : Topic {0} was not in the topic list. Skipping."
                        .format(topic))
                    continue

                # don't bother with topics if they don't overlap the requested time range
                topic_start_time = topic_helper.get_start_stamp(
                    self._topics[topic])
                if topic_start_time is not None and topic_start_time > end_stamp:
                    continue

                topic_end_time = topic_helper.get_end_stamp(
                    self._topics[topic])
                if topic_end_time is not None and topic_end_time < start_stamp:
                    continue

                topic_queue = self._topics[topic].queue
                start_ind, first_entry = self._entry_at(
                    start_stamp, topic_queue)
                # entry returned might be the latest one before the start stamp
                # if there isn't one exactly on the stamp - if so, start from
                # the next entry
                if first_entry.stamp < start_stamp:
                    start_ind += 1

                # entry at always returns entry at or before the given stamp, so
                # no manipulation needed.
                end_ind, last_entry = self._entry_at(end_stamp, topic_queue)

                topic_entries.extend(
                    list(itertools.islice(topic_queue, start_ind,
                                          end_ind + 1)))

            for entry in sorted(topic_entries, key=lambda x: x.stamp):
                yield entry

    def get_entries_with_bags(self, topic, start_stamp, end_stamp):
        """
        generator function for bag entries
        :param topics: list of topics to query, ''list(str)''
        :param start_stamp: stamp to start at, ''rospy.Time''
        :param end_stamp: stamp to end at, ''rospy,Time''
        :returns: tuple of (bag, entry) for the entries in the bag file, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            from rosbag import bag  # for _mergesort

            bag_entries = []
            bag_by_iter = {}
            for b in self._bags:
                bag_start_time = bag_helper.get_start_stamp(b)
                if bag_start_time is not None and bag_start_time > end_stamp:
                    continue

                bag_end_time = bag_helper.get_end_stamp(b)
                if bag_end_time is not None and bag_end_time < start_stamp:
                    continue

                connections = list(b._get_connections(topic))
                it = iter(b._get_entries(connections, start_stamp, end_stamp))
                bag_by_iter[it] = b
                bag_entries.append(it)

            for entry, it in bag._mergesort(bag_entries,
                                            key=lambda entry: entry.time):
                yield bag_by_iter[it], entry

    def _entry_at(self, t, queue):
        """Get the entry and index in the queue at the given time.

        :param ``rospy.Time`` t: time to check
        :param ``collections.deque`` queue: deque to look at

        :return: (index, Message) tuple. If there is no message in the queue at
            the exact time, the previous index is returned. If the time is
            before or after the first and last message times, the first or last
            index and message are returned

        """
        # Gives the index to insert into to retain a sorted queue. The topic queues
        # should always be sorted due to time passing.

        # ind = bisect.bisect(queue, self.Message(stamp=t, message=''))
        # Can't use bisect here in python3 unless the correct operators
        # are defined for sorting, so do it manually
        try:
            ind = next(i for i, msg in enumerate(queue) if t < msg.stamp)
        except StopIteration:
            ind = len(queue)

        # first or last indices
        if ind == len(queue):
            return (ind - 1, queue[-1])
        elif ind == 0:
            return (0, queue[0])

        # non-end indices
        cur = queue[ind]
        if cur.stamp == t:
            return (ind, cur)
        else:
            return (ind - 1, queue[ind - 1])

    def get_entry(self, t, topic):
        """Get a message in the queues for a specific topic
        :param ``rospy.Time`` t: time of the message to retrieve
        :param str topic: the topic to be accessed
        :return: message corresponding to time t and topic. If there is no
            corresponding entry at exactly the given time, the latest entry
            before the given time is returned. If the topic does not exist, or
            there is no entry, None.

        """
        with self._topic_lock:
            entry = None
            if topic in self._topics:
                _, entry = self._entry_at(t, self._topics[topic].queue)

            return entry

    def get_entry_before(self, t):
        """
        Get the latest message before the given time on any of the topics
        :param t: time, ``rospy.Time``
        :return: tuple of (topic, entry) corresponding to time t, ``(str, msg)``
        """
        with self._topic_lock:
            entry_topic, entry = None, None
            for topic in self._topics:
                _, topic_entry = self._entry_at(t - rospy.Duration(0, 1),
                                                self._topics[topic].queue)
                if topic_entry and (not entry
                                    or topic_entry.stamp > entry.stamp):
                    entry_topic, entry = topic, topic_entry

            return entry_topic, entry

    def get_entry_after(self, t):
        """
        Get the earliest message on any topic after the given time
        :param t: time, ''rospy.Time''
        :return: tuple of (bag, entry) corresponding to time t, ''(rosbag.bag, msg)''
        """
        with self._topic_lock:
            entry_topic, entry = None, None
            for topic in self._topics:
                ind, _ = self._entry_at(t, self._topics[topic].queue)
                # ind is the index of the entry at (if it exists) or before time
                # t - we want the one after this. Make sure that the given index
                # isn't out of bounds
                ind = ind + 1 if ind + 1 < len(
                    self._topics[topic].queue) else ind
                topic_entry = self._topics[topic].queue[ind]
                if topic_entry and (not entry
                                    or topic_entry.stamp < entry.stamp):
                    entry_topic, entry = topic, topic_entry

            return entry_topic, entry

    def get_next_message_time(self):
        """
        :return: time of the message after the current playhead position,''rospy.Time''
        """
        if self._timeline_frame.playhead is None:
            return None

        _, entry = self.get_entry_after(self._timeline_frame.playhead)
        if entry is None:
            return self._timeline_frame._end_stamp

        return entry.stamp

    def get_previous_message_time(self):
        """
        :return: time of the message before the current playhead position,''rospy.Time''
        """
        if self._timeline_frame.playhead is None:
            return None

        _, entry = self.get_entry_before(self._timeline_frame.playhead)
        if entry is None:
            return self._timeline_frame._start_stamp

        return entry.stamp

    def resume(self):
        if (self._player):
            self._player.resume()

    ### Copy messages to...

    def start_background_task(self, background_task):
        """
        Verify that a background task is not currently running before starting a new one
        :param background_task: name of the background task, ''str''
        """
        if self.background_task is not None:
            QMessageBox(
                QMessageBox.Warning, 'Exclamation',
                'Background operation already running:\n\n%s' %
                self.background_task, QMessageBox.Ok).exec_()
            return False

        self.background_task = background_task
        self.background_task_cancel = False
        return True

    def stop_background_task(self):
        self.background_task = None

    def copy_region_to_bag(self, filename):
        if len(self._bags) > 0:
            self._export_region(filename, self._timeline_frame.topics,
                                self._timeline_frame.play_region[0],
                                self._timeline_frame.play_region[1])

    def _export_region(self, path, topics, start_stamp, end_stamp):
        """
        Starts a thread to save the current selection to a new bag file
        :param path: filesystem path to write to, ''str''
        :param topics: topics to write to the file, ''list(str)''
        :param start_stamp: start of area to save, ''rospy.Time''
        :param end_stamp: end of area to save, ''rospy.Time''
        """
        if not self.start_background_task('Copying messages to "%s"' % path):
            return
        # TODO implement a status bar area with information on the current save status
        bag_entries = list(
            self.get_entries_with_bags(topics, start_stamp, end_stamp))

        if self.background_task_cancel:
            return

        # Get the total number of messages to copy
        total_messages = len(bag_entries)

        # If no messages, prompt the user and return
        if total_messages == 0:
            QMessageBox(QMessageBox.Warning, 'rqt_bag', 'No messages found',
                        QMessageBox.Ok).exec_()
            self.stop_background_task()
            return

        # Open the path for writing
        try:
            export_bag = rosbag.Bag(path, 'w')
        except Exception:
            QMessageBox(QMessageBox.Warning, 'rqt_bag',
                        'Error opening bag file [%s] for writing' % path,
                        QMessageBox.Ok).exec_()
            self.stop_background_task()
            return

        # Run copying in a background thread
        self._export_thread = threading.Thread(target=self._run_export_region,
                                               args=(export_bag, topics,
                                                     start_stamp, end_stamp,
                                                     bag_entries))
        self._export_thread.start()

    def _run_export_region(self, export_bag, topics, start_stamp, end_stamp,
                           bag_entries):
        """
        Threaded function that saves the current selection to a new bag file
        :param export_bag: bagfile to write to, ''rosbag.bag''
        :param topics: topics to write to the file, ''list(str)''
        :param start_stamp: start of area to save, ''rospy.Time''
        :param end_stamp: end of area to save, ''rospy.Time''
        """
        total_messages = len(bag_entries)
        update_step = max(1, total_messages / 100)
        message_num = 1
        progress = 0
        # Write out the messages
        for bag, entry in bag_entries:
            if self.background_task_cancel:
                break
            try:
                topic, msg, t = self.read_message(bag, entry.position)
                export_bag.write(topic, msg, t)
            except Exception as ex:
                qWarning('Error exporting message at position %s: %s' %
                         (str(entry.position), str(ex)))
                export_bag.close()
                self.stop_background_task()
                return

            if message_num % update_step == 0 or message_num == total_messages:
                new_progress = int(100.0 *
                                   (float(message_num) / total_messages))
                if new_progress != progress:
                    progress = new_progress
                    if not self.background_task_cancel:
                        self.background_progress = progress
                        self.status_bar_changed_signal.emit()

            message_num += 1

        # Close the bag
        try:
            self.background_progress = 0
            self.status_bar_changed_signal.emit()
            export_bag.close()
        except Exception as ex:
            QMessageBox(
                QMessageBox.Warning, 'rqt_bag',
                'Error closing bag file [%s]: %s' %
                (export_bag.filename, str(ex)), QMessageBox.Ok).exec_()
        self.stop_background_task()

    def read_message(self, topic, position):
        with self._topic_lock:
            return self.get_entry(position, topic).message

    def on_mouse_down(self, event):
        """
        When the user clicks down in the timeline.
        """
        if event.buttons() == Qt.LeftButton:
            self._timeline_frame.on_left_down(event)
        elif event.buttons() == Qt.MidButton:
            self._timeline_frame.on_middle_down(event)
        elif event.buttons() == Qt.RightButton:
            topic = self._timeline_frame.map_y_to_topic(event.y())
            TimelinePopupMenu(self, event, topic)

    def on_mouse_up(self, event):
        self._timeline_frame.on_mouse_up(event)

    def on_mouse_move(self, event):
        self._timeline_frame.on_mouse_move(event)

    def on_mousewheel(self, event):
        self._timeline_frame.on_mousewheel(event)

    # Zooming

    def zoom_in(self):
        self._timeline_frame.zoom_in()

    def zoom_out(self):
        self._timeline_frame.zoom_out()

    def reset_zoom(self):
        self._timeline_frame.reset_zoom()

    def translate_timeline_left(self):
        self._timeline_frame.translate_timeline_left()

    def translate_timeline_right(self):
        self._timeline_frame.translate_timeline_right()

    ### Publishing
    def is_publishing(self, topic):
        return self._player and self._player.is_publishing(topic)

    def start_publishing(self, topic):
        if not self._player and not self._create_player():
            return False

        self._player.start_publishing(topic)
        return True

    def stop_publishing(self, topic):
        if not self._player:
            return False

        self._player.stop_publishing(topic)
        return True

    def _create_player(self):
        if not self._player:
            try:
                self._player = Player(self)
                if self._publish_clock:
                    self._player.start_clock_publishing()
            except Exception as ex:
                qWarning('Error starting player; aborting publish: %s' %
                         str(ex))
                return False

        return True

    def set_publishing_state(self, start_publishing):
        if start_publishing:
            for topic in self._timeline_frame.topics:
                if not self.start_publishing(topic):
                    break
        else:
            for topic in self._timeline_frame.topics:
                self.stop_publishing(topic)

    # property: play_all
    def _get_play_all(self):
        return self._play_all

    def _set_play_all(self, play_all):
        if play_all == self._play_all:
            return

        self._play_all = not self._play_all

        self.last_frame = None
        self.last_playhead = None
        self.desired_playhead = None

    play_all = property(_get_play_all, _set_play_all)

    def toggle_play_all(self):
        self.play_all = not self.play_all

    ### Playing
    def on_idle(self):
        self._step_playhead()

    def _step_playhead(self):
        """
        moves the playhead to the next position based on the desired position
        """
        # Reset when the playing mode switchs
        if self._timeline_frame.playhead != self.last_playhead:
            self.last_frame = None
            self.last_playhead = None
            self.desired_playhead = None

        if self._play_all:
            self.step_next_message()
        else:
            self.step_fixed()

    def step_fixed(self):
        """
        Moves the playhead a fixed distance into the future based on the current play speed
        """
        if self.play_speed == 0.0 or not self._timeline_frame.playhead:
            self.last_frame = None
            self.last_playhead = None
            return

        now = rospy.Time.from_sec(time.time())
        if self.last_frame:
            # Get new playhead
            if self.stick_to_end:
                new_playhead = self.end_stamp
            else:
                new_playhead = self._timeline_frame.playhead + rospy.Duration.from_sec(
                    (now - self.last_frame).to_sec() * self.play_speed)

                start_stamp, end_stamp = self._timeline_frame.play_region

                if new_playhead > end_stamp:
                    if self.wrap:
                        if self.play_speed > 0.0:
                            new_playhead = start_stamp
                        else:
                            new_playhead = end_stamp
                    else:
                        new_playhead = end_stamp

                        if self.play_speed > 0.0:
                            self.stick_to_end = True

                elif new_playhead < start_stamp:
                    if self.wrap:
                        if self.play_speed < 0.0:
                            new_playhead = end_stamp
                        else:
                            new_playhead = start_stamp
                    else:
                        new_playhead = start_stamp

            # Update the playhead
            self._timeline_frame.playhead = new_playhead

        self.last_frame = now
        self.last_playhead = self._timeline_frame.playhead

    def step_next_message(self):
        """
        Move the playhead to the next message
        """
        if self.play_speed <= 0.0 or not self._timeline_frame.playhead:
            self.last_frame = None
            self.last_playhead = None
            return

        if self.last_frame:
            if not self.desired_playhead:
                self.desired_playhead = self._timeline_frame.playhead
            else:
                delta = rospy.Time.from_sec(time.time()) - self.last_frame
                if delta > rospy.Duration.from_sec(0.1):
                    delta = rospy.Duration.from_sec(0.1)
                self.desired_playhead += delta

            # Get the occurrence of the next message
            next_message_time = self.get_next_message_time()

            if next_message_time < self.desired_playhead:
                self._timeline_frame.playhead = next_message_time
            else:
                self._timeline_frame.playhead = self.desired_playhead

        self.last_frame = rospy.Time.from_sec(time.time())
        self.last_playhead = self._timeline_frame.playhead

    # Recording
    def record_bag(self, filename, all=True, topics=[], regex=False, limit=0):
        try:
            self._recorder = Recorder(filename,
                                      bag_lock=self._bag_lock,
                                      all=all,
                                      topics=topics,
                                      regex=regex,
                                      limit=limit)
        except Exception as ex:
            qWarning('Error opening bag for recording [%s]: %s' %
                     (filename, str(ex)))
            return

        self._recorder.add_listener(self._message_recorded)

        self.add_bag(self._recorder.bag)

        self._recorder.start()

        self.wrap = False
        self._timeline_frame._index_cache_thread.period = 0.1

        self.update()

    def toggle_recording(self):
        if self._recorder:
            self._recorder.toggle_paused()
            self.update()

    def _message_recorded(self, topic, msg, t):
        if self._timeline_frame._start_stamp is None:
            self._timeline_frame._start_stamp = t
            self._timeline_frame._end_stamp = t
            self._timeline_frame._playhead = t
        elif self._timeline_frame._end_stamp is None or t > self._timeline_frame._end_stamp:
            self._timeline_frame._end_stamp = t

        if not self._timeline_frame.topics or topic not in self._timeline_frame.topics:
            self._timeline_frame.topics = self._get_topics()
            self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype(
            )

            self._playhead_positions_cvs[topic] = threading.Condition()
            self._messages_cvs[topic] = threading.Condition()
            self._message_loaders[topic] = MessageLoaderThread(self, topic)

        if self._timeline_frame._stamp_left is None:
            self.reset_zoom()

        # Notify the index caching thread that it has work to do
        with self._timeline_frame.index_cache_cv:
            self._timeline_frame.invalidated_caches.add(topic)
            self._timeline_frame.index_cache_cv.notify()

        if topic in self._listeners:
            for listener in self._listeners[topic]:
                try:
                    listener.timeline_changed()
                except Exception as ex:
                    qWarning('Error calling timeline_changed on %s: %s' %
                             (type(listener), str(ex)))

    # Views / listeners
    def add_view(self, topic, frame):
        self._views.append(frame)

    def has_listeners(self, topic):
        return topic in self._listeners

    def add_listener(self, topic, listener):
        self._listeners.setdefault(topic, []).append(listener)

        self._message_listener_threads[(topic,
                                        listener)] = MessageListenerThread(
                                            self, topic, listener)
        # Notify the message listeners
        self._message_loaders[topic].reset()
        with self._playhead_positions_cvs[topic]:
            self._playhead_positions_cvs[topic].notify_all()

        self.update()

    def remove_listener(self, topic, listener):
        topic_listeners = self._listeners.get(topic)
        if topic_listeners is not None and listener in topic_listeners:
            topic_listeners.remove(listener)

            if len(topic_listeners) == 0:
                del self._listeners[topic]

            # Stop the message listener thread
            if (topic, listener) in self._message_listener_threads:
                self._message_listener_threads[(topic, listener)].stop()
                del self._message_listener_threads[(topic, listener)]
            self.update()

    ### Playhead

    # property: play_speed
    def _get_play_speed(self):
        if self._timeline_frame._paused:
            return 0.0
        return self._play_speed

    def _set_play_speed(self, play_speed):
        if play_speed == self._play_speed:
            return

        if play_speed > 0.0:
            self._play_speed = min(self._max_play_speed,
                                   max(self._min_play_speed, play_speed))
        elif play_speed < 0.0:
            self._play_speed = max(-self._max_play_speed,
                                   min(-self._min_play_speed, play_speed))
        else:
            self._play_speed = play_speed

        if self._play_speed < 1.0:
            self.stick_to_end = False

        self.update()

    play_speed = property(_get_play_speed, _set_play_speed)

    def toggle_play(self):
        if self._play_speed != 0.0:
            self.play_speed = 0.0
        else:
            self.play_speed = 1.0

    def navigate_play(self):
        self.play_speed = 1.0
        self.last_frame = rospy.Time.from_sec(time.time())
        self.last_playhead = self._timeline_frame.playhead
        self._play_timer.start()

    def navigate_stop(self):
        self.play_speed = 0.0
        self._play_timer.stop()

    def navigate_previous(self):
        self.navigate_stop()
        self._timeline_frame.playhead = self.get_previous_message_time()
        self.last_playhead = self._timeline_frame.playhead

    def navigate_next(self):
        self.navigate_stop()
        self._timeline_frame.playhead = self.get_next_message_time()
        self.last_playhead = self._timeline_frame.playhead

    def navigate_rewind(self):
        if self._play_speed < 0.0:
            new_play_speed = self._play_speed * 2.0
        elif self._play_speed == 0.0:
            new_play_speed = -1.0
        else:
            new_play_speed = self._play_speed * 0.5

        self.play_speed = new_play_speed

    def navigate_fastforward(self):
        if self._play_speed > 0.0:
            new_play_speed = self._play_speed * 2.0
        elif self._play_speed == 0.0:
            new_play_speed = 2.0
        else:
            new_play_speed = self._play_speed * 0.5

        self.play_speed = new_play_speed

    def navigate_start(self):
        self._timeline_frame.playhead = self._timeline_frame.play_region[0]

    def navigate_end(self):
        self._timeline_frame.playhead = self._timeline_frame.play_region[1]
コード例 #21
0
ファイル: bag_timeline.py プロジェクト: matansar/rqt_mlp
class BagTimeline(QGraphicsScene):
    """
    BagTimeline contains bag files, all information required to display the bag data visualization on the screen
    Also handles events
    """
    status_bar_changed_signal = Signal()
    selected_region_changed = Signal(rospy.Time, rospy.Time)
    make_pop_up = Signal()

    def __init__(self, context, publish_clock):
        """
        :param context: plugin context hook to enable adding rqt_bag plugin widgets as ROS_GUI snapin panes, ''PluginContext''
        """
        super(BagTimeline, self).__init__()
        self._bags = []
        self._bag_lock = threading.RLock()

        self.background_task = None  # Display string
        self.background_task_cancel = False

        # Playing / Recording
        self._playhead_lock = threading.RLock()
        self._max_play_speed = 1024.0  # fastest X play speed
        self._min_play_speed = 1.0 / 1024.0  # slowest X play speed
        self._play_speed = 0.0
        self._play_all = False
        self._playhead_positions_cvs = {}
        self._playhead_positions = {}  # topic -> (bag, position)
        self._message_loaders = {}
        self._messages_cvs = {}
        self._messages = {}  # topic -> (bag, msg_data)
        self._message_listener_threads = {
        }  # listener -> MessageListenerThread
        self._player = False
        self._publish_clock = publish_clock
        self._recorder = None
        self.last_frame = None
        self.last_playhead = None
        self.desired_playhead = None
        self.wrap = True  # should the playhead wrap when it reaches the end?
        self.stick_to_end = False  # should the playhead stick to the end?
        self._play_timer = QTimer()
        self._play_timer.timeout.connect(self.on_idle)
        self._play_timer.setInterval(3)

        # Plugin popup management
        self._context = context
        self.popups = {}
        self._views = []
        self._listeners = {}

        # Initialize scene
        # the timeline renderer fixes use of black pens and fills, so ensure we fix white here for contrast.
        # otherwise a dark qt theme will default it to black and the frame render pen will be unreadable
        self.setBackgroundBrush(Qt.white)
        self._timeline_frame = TimelineFrame(self)
        self._timeline_frame.setPos(0, 0)
        self.addItem(self._timeline_frame)

        self.background_progress = 0
        self.__closed = False

    def get_context(self):
        """
        :returns: the ROS_GUI context, 'PluginContext'
        """
        return self._context

    def handle_close(self):
        """
        Cleans up the timeline, bag and any threads
        """
        # with open("/var/tmp/test.txt", "a") as myfile:
        #     myfile.write("dan\n")

        if self.__closed:
            return
        else:
            self.__closed = True
        self._play_timer.stop()
        for topic in self._get_topics():
            self.stop_publishing(topic)
            self._message_loaders[topic].stop()
        if self._player:
            self._player.stop()
        if self._recorder:
            self._recorder.stop()
        if self.background_task is not None:
            self.background_task_cancel = True
        self._timeline_frame.handle_close()
        for bag in self._bags:
            bag.close()
        for frame in self._views:
            if frame.parent():
                self._context.remove_widget(frame)

    # Bag Management and access
    def add_bag(self, bag):
        """
        creates an indexing thread for each new topic in the bag
        fixes the boarders and notifies the indexing thread to index the new items bags
        :param bag: ros bag file, ''rosbag.bag''
        """
        self._bags.append(bag)

        bag_topics = bag_helper.get_topics(bag)

        new_topics = set(bag_topics) - set(self._timeline_frame.topics)

        for topic in new_topics:
            self._playhead_positions_cvs[topic] = threading.Condition()
            self._messages_cvs[topic] = threading.Condition()
            self._message_loaders[topic] = MessageLoaderThread(self, topic)

        self._timeline_frame._start_stamp = self._get_start_stamp()
        self._timeline_frame._end_stamp = self._get_end_stamp()
        self._timeline_frame.topics = self._get_topics()
        self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype(
        )
        # If this is the first bag, reset the timeline
        if self._timeline_frame._stamp_left is None:
            self._timeline_frame.reset_timeline()

        # Invalidate entire index cache for all topics in this bag
        with self._timeline_frame.index_cache_cv:
            for topic in bag_topics:
                self._timeline_frame.invalidated_caches.add(topic)
                if topic in self._timeline_frame.index_cache:
                    del self._timeline_frame.index_cache[topic]

            self._timeline_frame.index_cache_cv.notify()

    def file_size(self):
        with self._bag_lock:
            return sum(b.size for b in self._bags)

    #TODO Rethink API and if these need to be visible
    def _get_start_stamp(self):
        """
        :return: first stamp in the bags, ''rospy.Time''
        """
        with self._bag_lock:
            start_stamp = None
            for bag in self._bags:
                bag_start_stamp = bag_helper.get_start_stamp(bag)
                if bag_start_stamp is not None and (
                        start_stamp is None or bag_start_stamp < start_stamp):
                    start_stamp = bag_start_stamp
            return start_stamp

    def _get_end_stamp(self):
        """
        :return: last stamp in the bags, ''rospy.Time''
        """
        with self._bag_lock:
            end_stamp = None
            for bag in self._bags:
                bag_end_stamp = bag_helper.get_end_stamp(bag)
                if bag_end_stamp is not None and (end_stamp is None or
                                                  bag_end_stamp > end_stamp):
                    end_stamp = bag_end_stamp
            return end_stamp

    def _get_topics(self):
        """
        :return: sorted list of topic names, ''list(str)''
        """
        with self._bag_lock:
            topics = set()
            for bag in self._bags:
                for topic in bag_helper.get_topics(bag):
                    topics.add(topic)
            return sorted(topics)

    def _get_topics_by_datatype(self):
        """
        :return: dict of list of topics for each datatype, ''dict(datatype:list(topic))''
        """
        with self._bag_lock:
            topics_by_datatype = {}
            for bag in self._bags:
                for datatype, topics in bag_helper.get_topics_by_datatype(
                        bag).items():
                    topics_by_datatype.setdefault(datatype, []).extend(topics)
            return topics_by_datatype

    def get_datatype(self, topic):
        """
        :return: datatype associated with a topic, ''str''
        :raises: if there are multiple datatypes assigned to a single topic, ''Exception''
        """
        with self._bag_lock:
            datatype = None
            for bag in self._bags:
                bag_datatype = bag_helper.get_datatype(bag, topic)
                if datatype and bag_datatype and (bag_datatype != datatype):
                    raise Exception(
                        'topic %s has multiple datatypes: %s and %s' %
                        (topic, datatype, bag_datatype))
                if bag_datatype:
                    datatype = bag_datatype
            return datatype

    def get_entries(self, topics, start_stamp, end_stamp):
        """
        generator function for bag entries
        :param topics: list of topics to query, ''list(str)''
        :param start_stamp: stamp to start at, ''rospy.Time''
        :param end_stamp: stamp to end at, ''rospy,Time''
        :returns: entries the bag file, ''msg''
        """
        with self._bag_lock:
            from rosbag import bag  # for _mergesort
            bag_entries = []
            for b in self._bags:
                bag_start_time = bag_helper.get_start_stamp(b)
                if bag_start_time is not None and bag_start_time > end_stamp:
                    continue

                bag_end_time = bag_helper.get_end_stamp(b)
                if bag_end_time is not None and bag_end_time < start_stamp:
                    continue

                connections = list(b._get_connections(topics))
                bag_entries.append(
                    b._get_entries(connections, start_stamp, end_stamp))

            for entry, _ in bag._mergesort(bag_entries,
                                           key=lambda entry: entry.time):
                yield entry

    def get_entries_with_bags(self, topic, start_stamp, end_stamp):
        """
        generator function for bag entries
        :param topics: list of topics to query, ''list(str)''
        :param start_stamp: stamp to start at, ''rospy.Time''
        :param end_stamp: stamp to end at, ''rospy,Time''
        :returns: tuple of (bag, entry) for the entries in the bag file, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            from rosbag import bag  # for _mergesort

            bag_entries = []
            bag_by_iter = {}
            for b in self._bags:
                bag_start_time = bag_helper.get_start_stamp(b)
                if bag_start_time is not None and bag_start_time > end_stamp:
                    continue

                bag_end_time = bag_helper.get_end_stamp(b)
                if bag_end_time is not None and bag_end_time < start_stamp:
                    continue

                connections = list(b._get_connections(topic))
                it = iter(b._get_entries(connections, start_stamp, end_stamp))
                bag_by_iter[it] = b
                bag_entries.append(it)

            for entry, it in bag._mergesort(bag_entries,
                                            key=lambda entry: entry.time):
                yield bag_by_iter[it], entry

    def get_entry(self, t, topic):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :param topic: the topic to be accessed, ''str''
        :return: tuple of (bag, entry) corisponding to time t and topic, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry(t, bag._get_connections(topic))
                if bag_entry and (not entry or bag_entry.time > entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_entry_before(self, t):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :return: tuple of (bag, entry) corresponding to time t, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry(t - rospy.Duration(0, 1),
                                           bag._get_connections())
                if bag_entry and (not entry or bag_entry.time < entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_entry_after(self, t):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :return: tuple of (bag, entry) corisponding to time t, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry_after(t, bag._get_connections())
                if bag_entry and (not entry or bag_entry.time < entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_next_message_time(self):
        """
        :return: time of the next message after the current playhead position,''rospy.Time''
        """
        if self._timeline_frame.playhead is None:
            return None

        _, entry = self.get_entry_after(self._timeline_frame.playhead)
        if entry is None:
            return self._timeline_frame._start_stamp

        return entry.time

    # def get_previous_message_time(self):
    #     """
    #     :return: time of the next message before the current playhead position,''rospy.Time''
    #     """
    #     if self._timeline_frame.playhead is None:
    #         return None
    #
    #     _, entry = self.get_entry_before(self._timeline_frame.playhead)
    #     if entry is None:
    #         return self._timeline_frame._end_stamp
    #
    #     return entry.time

    def resume(self):
        if (self._player):
            self._player.resume()

    ### Copy messages to...

    def start_background_task(self, background_task):
        """
        Verify that a background task is not currently running before starting a new one
        :param background_task: name of the background task, ''str''
        """
        if self.background_task is not None:
            QMessageBox(
                QMessageBox.Warning, 'Exclamation',
                'Background operation already running:\n\n%s' %
                self.background_task, QMessageBox.Ok).exec_()
            return False

        self.background_task = background_task
        self.background_task_cancel = False
        return True

    def stop_background_task(self):
        self.background_task = None

    def copy_region_to_bag(self, filename):
        if len(self._bags) > 0:
            self._export_region(filename, self._timeline_frame.topics,
                                self._timeline_frame.play_region[0],
                                self._timeline_frame.play_region[1])

    def _export_region(self, path, topics, start_stamp, end_stamp):
        """
        Starts a thread to save the current selection to a new bag file
        :param path: filesystem path to write to, ''str''
        :param topics: topics to write to the file, ''list(str)''
        :param start_stamp: start of area to save, ''rospy.Time''
        :param end_stamp: end of area to save, ''rospy.Time''
        """
        if not self.start_background_task('Copying messages to "%s"' % path):
            return
        # TODO implement a status bar area with information on the current save status
        bag_entries = list(
            self.get_entries_with_bags(topics, start_stamp, end_stamp))

        if self.background_task_cancel:
            return

        # Get the total number of messages to copy
        total_messages = len(bag_entries)

        # If no messages, prompt the user and return
        if total_messages == 0:
            QMessageBox(QMessageBox.Warning, 'rqt_bag', 'No messages found',
                        QMessageBox.Ok).exec_()
            self.stop_background_task()
            return

        # Open the path for writing
        try:
            export_bag = rosbag.Bag(path, 'w')
        except Exception:
            QMessageBox(QMessageBox.Warning, 'rqt_bag',
                        'Error opening bag file [%s] for writing' % path,
                        QMessageBox.Ok).exec_()
            self.stop_background_task()
            return

        # Run copying in a background thread
        self._export_thread = threading.Thread(target=self._run_export_region,
                                               args=(export_bag, topics,
                                                     start_stamp, end_stamp,
                                                     bag_entries))
        self._export_thread.start()

    def _run_export_region(self, export_bag, topics, start_stamp, end_stamp,
                           bag_entries):
        """
        Threaded function that saves the current selection to a new bag file
        :param export_bag: bagfile to write to, ''rosbag.bag''
        :param topics: topics to write to the file, ''list(str)''
        :param start_stamp: start of area to save, ''rospy.Time''
        :param end_stamp: end of area to save, ''rospy.Time''
        """
        total_messages = len(bag_entries)
        update_step = max(1, total_messages / 100)
        message_num = 1
        progress = 0
        # Write out the messages
        for bag, entry in bag_entries:
            if self.background_task_cancel:
                break
            try:
                topic, msg, t = self.read_message(bag, entry.position)
                export_bag.write(topic, msg, t)
            except Exception as ex:
                qWarning('Error exporting message at position %s: %s' %
                         (str(entry.position), str(ex)))
                export_bag.close()
                self.stop_background_task()
                return

            if message_num % update_step == 0 or message_num == total_messages:
                new_progress = int(100.0 *
                                   (float(message_num) / total_messages))
                if new_progress != progress:
                    progress = new_progress
                    if not self.background_task_cancel:
                        self.background_progress = progress
                        self.status_bar_changed_signal.emit()

            message_num += 1

        # Close the bag
        try:
            self.background_progress = 0
            self.status_bar_changed_signal.emit()
            export_bag.close()
        except Exception as ex:
            QMessageBox(
                QMessageBox.Warning, 'rqt_bag',
                'Error closing bag file [%s]: %s' %
                (export_bag.filename, str(ex)), QMessageBox.Ok).exec_()
        self.stop_background_task()

    def read_message(self, bag, position):
        with self._bag_lock:
            return bag._read_message(position)

    ### Mouse events
    def on_mouse_down(self, event):
        if event.buttons() == Qt.LeftButton:
            self._timeline_frame.on_left_down(event)
        elif event.buttons() == Qt.MidButton:
            self._timeline_frame.on_middle_down(event)
        elif event.buttons() == Qt.RightButton:
            topic = self._timeline_frame.map_y_to_topic(
                self.views()[0].mapToScene(event.pos()).y())
            TimelinePopupMenu(self, event, topic)

    def on_mouse_up(self, event):
        self._timeline_frame.on_mouse_up(event)

    def on_mouse_move(self, event):
        self._timeline_frame.on_mouse_move(event)

    def on_mousewheel(self, event):
        self._timeline_frame.on_mousewheel(event)

    # Zooming

    def zoom_in(self):
        self._timeline_frame.zoom_in()

    def zoom_out(self):
        self._timeline_frame.zoom_out()

    def reset_zoom(self):
        self._timeline_frame.reset_zoom()

    def translate_timeline_left(self):
        self._timeline_frame.translate_timeline_left()

    def translate_timeline_right(self):
        self._timeline_frame.translate_timeline_right()

    ### Publishing
    def is_publishing(self, topic):
        return self._player and self._player.is_publishing(topic)

    def start_publishing(self, topic):
        if not self._player and not self._create_player():
            return False

        self._player.start_publishing(topic)
        return True

    def stop_publishing(self, topic):
        if not self._player:
            return False

        self._player.stop_publishing(topic)
        return True

    def _create_player(self):
        if not self._player:
            try:
                self._player = Player(self)
                if self._publish_clock:
                    self._player.start_clock_publishing()
            except Exception as ex:
                qWarning('Error starting player; aborting publish: %s' %
                         str(ex))
                return False

        return True

    def set_publishing_state(self, start_publishing):
        if start_publishing:
            for topic in self._timeline_frame.topics:
                if not self.start_publishing(topic):
                    break
        else:
            for topic in self._timeline_frame.topics:
                self.stop_publishing(topic)

    # property: play_all
    def _get_play_all(self):
        return self._play_all

    def _set_play_all(self, play_all):
        if play_all == self._play_all:
            return

        self._play_all = not self._play_all

        self.last_frame = None
        self.last_playhead = None
        self.desired_playhead = None

    play_all = property(_get_play_all, _set_play_all)

    def toggle_play_all(self):
        self.play_all = not self.play_all

    ### Playing
    def on_idle(self):
        self._step_playhead()

    def _step_playhead(self):
        """
        moves the playhead to the next position based on the desired position
        """
        # Reset when the playing mode switchs
        if self._timeline_frame.playhead != self.last_playhead:
            self.last_frame = None
            self.last_playhead = None
            self.desired_playhead = None

        if self._play_all:
            self.step_next_message()
        else:
            self.step_fixed()

    def step_fixed(self):
        """
        Moves the playhead a fixed distance into the future based on the current play speed
        """
        if self.play_speed == 0.0 or not self._timeline_frame.playhead:
            self.last_frame = None
            self.last_playhead = None
            return

        now = rospy.Time.from_sec(time.time())
        if self.last_frame:
            # Get new playhead
            if self.stick_to_end:
                new_playhead = self.end_stamp
            else:
                new_playhead = self._timeline_frame.playhead + rospy.Duration.from_sec(
                    (now - self.last_frame).to_sec() * self.play_speed)

                start_stamp, end_stamp = self._timeline_frame.play_region

                if new_playhead > end_stamp:
                    if self.wrap:
                        if self.play_speed > 0.0:
                            new_playhead = start_stamp
                        else:
                            new_playhead = end_stamp
                    else:
                        new_playhead = end_stamp

                        if self.play_speed > 0.0:
                            self.stick_to_end = True

                elif new_playhead < start_stamp:
                    if self.wrap:
                        if self.play_speed < 0.0:
                            new_playhead = end_stamp
                        else:
                            new_playhead = start_stamp
                    else:
                        new_playhead = start_stamp

            # Update the playhead
            self._timeline_frame.playhead = new_playhead

        self.last_frame = now
        self.last_playhead = self._timeline_frame.playhead

    def step_next_message(self):
        """
        Move the playhead to the next message
        """
        if self.play_speed <= 0.0 or not self._timeline_frame.playhead:
            self.last_frame = None
            self.last_playhead = None
            return

        if self.last_frame:
            if not self.desired_playhead:
                self.desired_playhead = self._timeline_frame.playhead
            else:
                delta = rospy.Time.from_sec(time.time()) - self.last_frame
                if delta > rospy.Duration.from_sec(0.1):
                    delta = rospy.Duration.from_sec(0.1)
                self.desired_playhead += delta

            # Get the occurrence of the next message
            next_message_time = self.get_next_message_time()

            if next_message_time < self.desired_playhead:
                self._timeline_frame.playhead = next_message_time
            else:
                self._timeline_frame.playhead = self.desired_playhead

        self.last_frame = rospy.Time.from_sec(time.time())
        self.last_playhead = self._timeline_frame.playhead

    ### Recording

    def record_bag(self, filename, all=True, topics=[], regex=False, limit=0):
        try:
            self._recorder = Recorder(filename,
                                      bag_lock=self._bag_lock,
                                      all=all,
                                      topics=topics,
                                      regex=regex,
                                      limit=limit)
        except Exception as ex:
            qWarning('Error opening bag for recording [%s]: %s' %
                     (filename, str(ex)))
            return

        self._recorder.add_listener(self._message_recorded)

        self.add_bag(self._recorder.bag)

        self._recorder.start()

        self.wrap = False
        self._timeline_frame._index_cache_thread.period = 0.1

        self.update()

    def restart_recording(self, reindex_bag):
        self._BagWidget.apply_restart(reindex_bag)
        import os
        # self._BagWidget.record_button.setIcon(QIcon.fromTheme('view-refresh'))
        # self._BagWidget.record_button.setToolTip("Refresh Screen")
        # self._BagWidget.restart_button.setEnabled(False)
        #
        # self._BagWidget.load_button.setEnabled(True)
        # self._BagWidget.history_button.setEnabled(True)
        #
        # self._BagWidget._restarting = True
        # self.handle_close()
        # if path != "":
        #     os.remove(path)
        # if restart_flag:

        # else:
        #     self.make_pop_up.emit()

    # def apply_record_icon(self):
    #     self._BagWidget.record_button.setIcon(QIcon.fromTheme('media-record'))

    def setBagWidget(self, BagWidget):
        self._BagWidget = BagWidget

    def toggle_recording(self):
        if self._recorder:
            self._recorder.toggle_paused(self._BagWidget)
            self.update()

    def _message_recorded(self, topic, msg, t):
        if self._timeline_frame._start_stamp is None:
            self._timeline_frame._start_stamp = t
            self._timeline_frame._end_stamp = t
            self._timeline_frame._playhead = t
        elif self._timeline_frame._end_stamp is None or t > self._timeline_frame._end_stamp:
            self._timeline_frame._end_stamp = t

        if not self._timeline_frame.topics or topic not in self._timeline_frame.topics:
            self._timeline_frame.topics = self._get_topics()
            self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype(
            )

            self._playhead_positions_cvs[topic] = threading.Condition()
            self._messages_cvs[topic] = threading.Condition()
            self._message_loaders[topic] = MessageLoaderThread(self, topic)

        if self._timeline_frame._stamp_left is None:
            self.reset_zoom()

        # Notify the index caching thread that it has work to do
        with self._timeline_frame.index_cache_cv:
            self._timeline_frame.invalidated_caches.add(topic)
            self._timeline_frame.index_cache_cv.notify()

        if topic in self._listeners:
            for listener in self._listeners[topic]:
                try:
                    listener.timeline_changed()
                except Exception as ex:
                    qWarning('Error calling timeline_changed on %s: %s' %
                             (type(listener), str(ex)))

    ### Views / listeners
    def add_view(self, topic, frame):
        self._views.append(frame)

    def has_listeners(self, topic):
        return topic in self._listeners

    def add_listener(self, topic, listener):
        self._listeners.setdefault(topic, []).append(listener)

        self._message_listener_threads[(topic,
                                        listener)] = MessageListenerThread(
                                            self, topic, listener)
        # Notify the message listeners
        self._message_loaders[topic].reset()
        with self._playhead_positions_cvs[topic]:
            self._playhead_positions_cvs[topic].notify_all()

        self.update()

    def remove_listener(self, topic, listener):
        topic_listeners = self._listeners.get(topic)
        if topic_listeners is not None and listener in topic_listeners:
            topic_listeners.remove(listener)

            if len(topic_listeners) == 0:
                del self._listeners[topic]

            # Stop the message listener thread
            if (topic, listener) in self._message_listener_threads:
                self._message_listener_threads[(topic, listener)].stop()
                del self._message_listener_threads[(topic, listener)]
            self.update()

    ### Playhead

    # property: play_speed
    def _get_play_speed(self):
        if self._timeline_frame._paused:
            return 0.0
        return self._play_speed

    def _set_play_speed(self, play_speed):
        if play_speed == self._play_speed:
            return

        if play_speed > 0.0:
            self._play_speed = min(self._max_play_speed,
                                   max(self._min_play_speed, play_speed))
        elif play_speed < 0.0:
            self._play_speed = max(-self._max_play_speed,
                                   min(-self._min_play_speed, play_speed))
        else:
            self._play_speed = play_speed

        if self._play_speed < 1.0:
            self.stick_to_end = False

        self.update()

    play_speed = property(_get_play_speed, _set_play_speed)

    def toggle_play(self):
        if self._play_speed != 0.0:
            self.play_speed = 0.0
        else:
            self.play_speed = 1.0

    # def navigate_play(self):
    #     self.play_speed = 1.0
    #     self.last_frame = rospy.Time.from_sec(time.time())
    #     self.last_playhead = self._timeline_frame.playhead
    #     self._play_timer.start()

    # def navigate_stop(self):
    #     self.play_speed = 0.0
    #     self._play_timer.stop()

    # def navigate_previous(self):
    #     self.navigate_stop()
    #     self._timeline_frame.playhead = self.get_previous_message_time()
    #     self.last_playhead = self._timeline_frame.playhead

    # def navigate_next(self):
    #     self.navigate_stop()
    #     self._timeline_frame.playhead = self.get_next_message_time()
    #     self.last_playhead = self._timeline_frame.playhead

    # def navigate_rewind(self):
    #     if self._play_speed < 0.0:
    #         new_play_speed = self._play_speed * 2.0
    #     elif self._play_speed == 0.0:
    #         new_play_speed = -1.0
    #     else:
    #         new_play_speed = self._play_speed * 0.5
    #
    #     self.play_speed = new_play_speed

    def navigate_fastforward(self):
        if self._play_speed > 0.0:
            new_play_speed = self._play_speed * 2.0
        elif self._play_speed == 0.0:
            new_play_speed = 2.0
        else:
            new_play_speed = self._play_speed * 0.5

        self.play_speed = new_play_speed
コード例 #22
0
class BarrettDashboard(Plugin):

    def __init__(self, context):
        super(BarrettDashboard, self).__init__(context)

        self._joint_sub = None

        # Give QObjects reasonable names
        self.setObjectName('BarrettDashboard')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()
        # Add argument(s) to the parser.
        parser.add_argument("-q", "--quiet", action="store_true",
                      dest="quiet",
                      help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        if not args.quiet:
            print 'arguments: ', args
            print 'unknowns: ', unknowns

        # Create QWidget
        self._widget = QWidget()
        # Get path to UI file which is a sibling of this file
        # in this example the .ui and .py file are in the same folder
        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'barrett_dashboard.ui')
        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget)
        # Give QObjects reasonable names

        self._widget.setObjectName('BarrettDashboardPluginUi')
        # Show _widget.windowTitle on left-top of each plugin (when 
        # it's set in _widget). This is useful when you open multiple 
        # plugins at once. Also if you open multiple instances of your 
        # plugin at once, these lines add number to make it easy to 
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))

        # Add widget to the user interface
        context.add_widget(self._widget)
        self.context = context

        jp_widgets = [getattr(self._widget,'jp_%d' % i) for i in range(7)]
        jn_widgets = [getattr(self._widget,'jn_%d' % i) for i in range(7)]
        self.joint_widgets = zip(jp_widgets,jn_widgets)

        tp_widgets = [getattr(self._widget,'tp_%d' % i) for i in range(7)]
        tn_widgets = [getattr(self._widget,'tn_%d' % i) for i in range(7)]
        self.torque_widgets = zip(tp_widgets,tn_widgets)

        self.joint_signals = []
        self.torque_signals = []

        for (tp,tn) in self.torque_widgets:
            tp.setRange(0.0,1.0,False)
            tn.setRange(1.0,0.0,False)
            tp.setValue(0.0)
            tn.setValue(0.0)
            # set random values for testing
            #v = (2.0*random.random()) - 1.0
            #tp.setValue(v if v >=0 else 0)
            #tn.setValue(-v if v <0 else 0)

        for (jp,jn) in self.joint_widgets:
            jp.setRange(0.0,1.0,False)
            jn.setRange(1.0,0.0,False)
            jp.setValue(0.0)
            jn.setValue(0.0)
            # set random values for testing
            #v = (2.0*random.random()) - 1.0
            #jp.setValue(v if v >=0 else 0)
            #jn.setValue(-v if v <0 else 0)

        self.barrett_green = QColor(87,186,142)
        self.barrett_green_dark = self.barrett_green.darker()
        self.barrett_green_light = self.barrett_green.lighter()
        self.barrett_blue = QColor(80,148,204)
        self.barrett_blue_dark = self.barrett_blue.darker()
        self.barrett_blue_light = self.barrett_blue.lighter()
        self.barrett_red = QColor(232,47,47)
        self.barrett_red_dark = self.barrett_red.darker()
        self.barrett_red_light = self.barrett_red.lighter()
        self.barrett_orange = QColor(255,103,43)
        self.barrett_orange_dark = self.barrett_orange.darker()

        #background_color = p.mid().color()
        joint_bg_color = self.barrett_blue_dark.darker()
        joint_fill_color = self.barrett_blue
        joint_alarm_color = self.barrett_blue #self.barrett_blue_light
        torque_bg_color = self.barrett_green_dark.darker()
        torque_fill_color = self.barrett_green
        torque_alarm_color = self.barrett_orange #self.barrett_green_light
        temp_bg_color = self.barrett_red_dark.darker()
        temp_fill_color = self.barrett_orange
        temp_alarm_color = self.barrett_red

        for w in jp_widgets + jn_widgets:
            w.setAlarmLevel(0.80)
            w.setFillColor(joint_fill_color)
            w.setAlarmColor(joint_alarm_color)
            p = w.palette()
            p.setColor(tp.backgroundRole(), joint_bg_color)
            w.setPalette(p)

        for w in tp_widgets + tn_widgets:
            w.setAlarmLevel(0.66)
            w.setFillColor(torque_fill_color)
            w.setAlarmColor(torque_alarm_color)
            p = w.palette()
            p.setColor(tp.backgroundRole(), torque_bg_color)
            w.setPalette(p)

        self._widget.hand_temp.setAlarmLevel(0.66)
        self._widget.hand_temp.setFillColor(temp_fill_color)
        self._widget.hand_temp.setAlarmColor(temp_alarm_color)
        p = self._widget.hand_temp.palette()
        p.setColor(self._widget.hand_temp.backgroundRole(), temp_bg_color)
        self._widget.hand_temp.setPalette(p)
        self._widget.hand_temp.setOrientation(Qt.Horizontal, QwtThermo.RightScale)

        self._widget.jf_0.setStyleSheet("QLabel { background-color : rgb(%d,%d,%d); color : rgb(%d,%d,%d); }" % (
            joint_bg_color.red(), joint_bg_color.green(), joint_bg_color.blue(), joint_fill_color.red(), joint_fill_color.green(), joint_fill_color.blue()))

        self.urdf = rospy.get_param('robot_description')
        self.robot = URDF()
        self.robot = self.robot.from_xml_string(self.urdf)

        self.pos_norm = [0] * 7
        self.torque_norm = [0] * 7

        self._joint_sub = rospy.Subscriber(
                'joint_states',
                sensor_msgs.msg.JointState,
                self._joint_state_cb)

        self._status_sub = rospy.Subscriber(
                'status',
                oro_barrett_msgs.msg.BarrettStatus,
                self._status_cb)

        self._hand_status_sub = rospy.Subscriber(
                'hand/status',
                oro_barrett_msgs.msg.BHandStatus,
                self._hand_status_cb)

        self._update_status(-1)
        self.safety_mode = -1
        self.run_mode = 0
        self.hand_run_mode = 0
        self.hand_min_temperature = 40.0
        self.hand_max_temperature = 65.0
        self.hand_temperature = 0.0

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(50)
        self.update_timer.timeout.connect(self._update_widget_values)
        self.update_timer.start()


        self.set_home_client = actionlib.SimpleActionClient(
                'wam/set_home_action',
                oro_barrett_msgs.msg.SetHomeAction)
        self.set_mode_client = actionlib.SimpleActionClient(
                'wam/set_mode_action',
                oro_barrett_msgs.msg.SetModeAction)

        self._widget.button_set_home.clicked[bool].connect(self._handle_set_home_clicked)
        self._widget.button_idle_wam.clicked[bool].connect(self._handle_idle_wam_clicked)
        self._widget.button_run_wam.clicked[bool].connect(self._handle_run_wam_clicked)

        self.bhand_init_client = actionlib.SimpleActionClient(
                'hand/initialize_action',
                oro_barrett_msgs.msg.BHandInitAction)
        self.bhand_set_mode_client = actionlib.SimpleActionClient(
                'hand/set_mode_action',
                oro_barrett_msgs.msg.BHandSetModeAction)
        self.grasp_client = actionlib.SimpleActionClient(
                'grasp', BHandGraspAction)
        self.release_client = actionlib.SimpleActionClient(
                'release', BHandReleaseAction)
        self.spread_client = actionlib.SimpleActionClient(
                'spread', BHandSpreadAction)

        self._widget.button_initialize_hand.clicked[bool].connect(self._handle_initialize_hand_clicked)
        self._widget.button_idle_hand.clicked[bool].connect(self._handle_idle_hand_clicked)
        self._widget.button_run_hand.clicked[bool].connect(self._handle_run_hand_clicked)

        self._widget.button_release_hand.clicked[bool].connect(self._handle_release_hand_clicked)
        self._widget.button_grasp_hand.clicked[bool].connect(self._handle_grasp_hand_clicked)
        self._widget.button_set_spread.clicked[bool].connect(self._handle_spread_hand_clicked)

        self._widget.resizeEvent = self._handle_resize

    def _handle_resize(self, event):
        for i,((w1,w2),(w3,w4)) in enumerate(zip(self.joint_widgets, self.torque_widgets)):
            width = 0.8*getattr(self._widget, 'jcc_%d'%i).contentsRect().width()
            w1.setPipeWidth(width)
            w2.setPipeWidth(width)
            w3.setPipeWidth(width)
            w4.setPipeWidth(width)

    def _handle_set_home_clicked(self, checked):
        if checked:
            self.set_home()

    def _update_status(self, status):
        if status == -1:
            self._widget.safety_mode.setText('UNKNOWN SAFETY MODE')
            self._widget.safety_mode.setToolTip('The WAM is in an unknown state. Proceed with caution.')
            color = QColor(200,200,200)
            self._widget.button_set_home.setEnabled(False)
            self._widget.button_idle_wam.setEnabled(False)
            self._widget.button_run_wam.setEnabled(False)
        elif status == 0:
            self._widget.safety_mode.setText('E-STOP')
            self._widget.safety_mode.setToolTip('The WAM is stopped and unpowered. Determine the source of the fault and reset the arm to continue using it.')
            self._widget.button_set_home.setEnabled(False)
            self._widget.button_idle_wam.setEnabled(False)
            self._widget.button_run_wam.setEnabled(False)
            color = self.barrett_red
        else:
            if not self.homed:
                self._widget.safety_mode.setText('UNCALIBRATED')
                self._widget.safety_mode.setToolTip('The WAM is not calibrated. Please place it in the calibration posture and click the "Calibrate" button.')
                self._widget.button_set_home.setEnabled(True)
                self._widget.button_idle_wam.setEnabled(False)
                self._widget.button_run_wam.setEnabled(False)
                color = self.barrett_orange
            else:
                if status == 1:
                    self._widget.safety_mode.setText('IDLE')
                    self._widget.safety_mode.setToolTip('The WAM is running but all joints are passive. It is safe to home the arm.')
                    self._widget.button_set_home.setEnabled(True)
                    self._widget.button_idle_wam.setEnabled(True)
                    self._widget.button_run_wam.setEnabled(True)
                    color = self.barrett_blue
                elif status == 2:
                    self._widget.safety_mode.setText('ACTIVE')
                    self._widget.safety_mode.setToolTip('The WAM is running and all joints are active. Proceed with caution.')
                    self._widget.button_set_home.setEnabled(False)
                    self._widget.button_idle_wam.setEnabled(False)
                    self._widget.button_run_wam.setEnabled(False)
                    color = self.barrett_green

        darker = color.darker()
        lighter = color.lighter()
        self._widget.safety_mode.setStyleSheet("QLabel { background-color : rgb(%d,%d,%d); color : rgb(%d,%d,%d); }" % (
            color.red(), color.green(), color.blue(), lighter.red(), lighter.green(), lighter.blue()))

    def _update_widget_values(self):

        if self.safety_mode in (oro_barrett_msgs.msg.SafetyMode.ACTIVE, oro_barrett_msgs.msg.SafetyMode.IDLE):
            for (v,(tp,tn)) in zip(self.torque_norm,self.torque_widgets):
                tp.setEnabled(True)
                tn.setEnabled(True)
                tp.setValue(v if v >=0 else 0)
                tn.setValue(-v if v <0 else 0)


            for (v,(jp,jn)) in zip(self.pos_norm,self.joint_widgets):
                jp.setEnabled(True)
                jn.setEnabled(True)
                jp.setValue(v if v >=0 else 0)
                jn.setValue(-v if v <0 else 0)
        else:
            for (p,n) in self.joint_widgets + self.torque_widgets:
                p.setEnabled(True)
                n.setEnabled(True)

        self._widget.hand_temp.setValue((self.hand_temperature-self.hand_min_temperature)/(self.hand_max_temperature-self.hand_min_temperature))

        self._update_status(self.safety_mode)
        self._update_buttons(self.run_mode, self.hand_run_mode)

    def _update_buttons(self, run_mode, hand_run_mode):
        if run_mode == oro_barrett_msgs.msg.RunMode.IDLE:
            self._widget.button_idle_wam.setChecked(True)
            self._widget.button_run_wam.setChecked(False)
        else:
            self._widget.button_idle_wam.setChecked(False)
            self._widget.button_run_wam.setChecked(True)

        if hand_run_mode == oro_barrett_msgs.msg.RunMode.RUN:
            self._widget.button_idle_hand.setChecked(False)
            self._widget.button_run_hand.setChecked(True)
        else:
            self._widget.button_idle_hand.setChecked(True)
            self._widget.button_run_hand.setChecked(False)


    def shutdown_plugin(self):
        # TODO unregister all publishers here
        pass

    def save_settings(self, plugin_settings, instance_settings):
        # TODO save intrinsic configuration, usually using:
        # instance_settings.set_value(k, v)
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass

    #def trigger_configuration(self):
        # Comment in to signal that the plugin has a way to configure
        # This will enable a setting button (gear icon) in each dock widget title bar
        # Usually used to open a modal configuration dialog

    def _joint_state_cb(self, msg):
        joint_pos_map = dict(zip(msg.name, msg.position))

        for (name,pos,torque,i) in zip(msg.name,msg.position,msg.effort,range(7)):
            joint = self.robot.joint_map[name]
            self.pos_norm[i] = 2.0*((pos-joint.limit.lower)/(joint.limit.upper-joint.limit.lower)) - 1.0
            self.torque_norm[i] = torque/joint.limit.effort

    def _status_cb(self, msg):
        self.safety_mode = msg.safety_mode.value
        self.run_mode = msg.run_mode.value
        self.homed = msg.homed

        if self.safety_mode == oro_barrett_msgs.msg.SafetyMode.ACTIVE:
            self._widget.button_initialize_hand.setEnabled(False)
        elif self.safety_mode == oro_barrett_msgs.msg.SafetyMode.ESTOP:
            self._widget.button_initialize_hand.setEnabled(False)
            self._widget.button_idle_hand.setEnabled(False)
            self._widget.button_run_hand.setEnabled(False)

    def _hand_status_cb(self, msg):
        self.hand_initialized = msg.initialized
        self.hand_temperature = max(msg.temperature)
        self.hand_run_mode = msg.run_mode.value
            
    def _handle_set_home_clicked(self, checked):
        goal = oro_barrett_msgs.msg.SetHomeGoal()
        self.set_home_client.send_goal(goal)

    def _handle_idle_wam_clicked(self, checked):
        if checked:
            goal = oro_barrett_msgs.msg.SetModeGoal()
            goal.mode.value = oro_barrett_msgs.msg.RunMode.IDLE
            self.set_mode_client.send_goal(goal)

    def _handle_run_wam_clicked(self, checked):
        if checked:
            goal = oro_barrett_msgs.msg.SetModeGoal()
            goal.mode.value = oro_barrett_msgs.msg.RunMode.RUN
            self.set_mode_client.send_goal(goal)

    def _handle_initialize_hand_clicked(self, checked):
        if self.safety_mode ==  oro_barrett_msgs.msg.SafetyMode.IDLE:
            goal = oro_barrett_msgs.msg.BHandInitGoal()
            self.bhand_init_client.send_goal(goal)

    def _handle_run_hand_clicked(self, checked):
        if checked:
            goal = oro_barrett_msgs.msg.BHandSetModeGoal()
            goal.run_mode.value = oro_barrett_msgs.msg.RunMode.RUN
            self.bhand_set_mode_client.send_goal(goal)

    def _handle_idle_hand_clicked(self, checked):
        if checked:
            goal = oro_barrett_msgs.msg.BHandSetModeGoal()
            goal.run_mode.value = oro_barrett_msgs.msg.RunMode.IDLE
            self.bhand_set_mode_client.send_goal(goal)

    def _get_grasp_mask(self):
        return [ self._widget.button_use_f1.isChecked(),
                self._widget.button_use_f2.isChecked(),
                self._widget.button_use_f3.isChecked()]

    def _handle_release_hand_clicked(self, checked):
        goal = BHandReleaseGoal()
        goal.release_mask = self._get_grasp_mask()
        goal.release_speed = [3.0, 3.0, 3.0]

        self.release_client.send_goal(goal)

    def _handle_grasp_hand_clicked(self, checked):
        goal = BHandGraspGoal()
        goal.grasp_mask = self._get_grasp_mask()
        goal.grasp_speed = [1.0, 1.0, 1.0]
        goal.grasp_effort = [1.0, 1.0, 1.0]
        goal.min_fingertip_radius = 0.0

        self.grasp_client.send_goal(goal)
        
    def _handle_spread_hand_clicked(self, checked):
        goal = BHandSpreadGoal()
        goal.spread_position = self._widget.spread_slider.value()/1000.0*3.141

        self.spread_client.send_goal(goal)
コード例 #23
0
class BagTimeline(QGraphicsScene):
    """
    BagTimeline contains bag files, all information required to display the bag data visualization on the screen
    Also handles events
    """
    status_bar_changed_signal = Signal()
    selected_region_changed = Signal(rospy.Time, rospy.Time)

    def __init__(self, context, publish_clock):
        """
        :param context: plugin context hook to enable adding rqt_bag plugin widgets as ROS_GUI snapin panes, ''PluginContext''
        """
        super(BagTimeline, self).__init__()
        self._bags = []
        self._bag_lock = threading.RLock()

        self.background_task = None  # Display string
        self.background_task_cancel = False

        # Playing / Recording
        self._playhead_lock = threading.RLock()
        self._max_play_speed = 1024.0  # fastest X play speed
        self._min_play_speed = 1.0 / 1024.0  # slowest X play speed
        self._play_speed = 0.0
        self._play_all = False
        self._playhead_positions_cvs = {}
        self._playhead_positions = {}  # topic -> (bag, position)
        self._message_loaders = {}
        self._messages_cvs = {}
        self._messages = {}  # topic -> (bag, msg_data)
        self._message_listener_threads = {}  # listener -> MessageListenerThread
        self._player = False
        self._publish_clock = publish_clock
        self._recorder = None
        self.last_frame = None
        self.last_playhead = None
        self.desired_playhead = None
        self.wrap = True  # should the playhead wrap when it reaches the end?
        self.stick_to_end = False  # should the playhead stick to the end?
        self._play_timer = QTimer()
        self._play_timer.timeout.connect(self.on_idle)
        self._play_timer.setInterval(3)

        # Plugin popup management
        self._context = context
        self.popups = {}
        self._views = []
        self._listeners = {}

        # Initialize scene
        # the timeline renderer fixes use of black pens and fills, so ensure we fix white here for contrast.
        # otherwise a dark qt theme will default it to black and the frame render pen will be unreadable
        self.setBackgroundBrush(Qt.white)
        self._timeline_frame = TimelineFrame(self)
        self._timeline_frame.setPos(0, 0)
        self.addItem(self._timeline_frame)

        self.background_progress = 0
        self.__closed = False

    def get_context(self):
        """
        :returns: the ROS_GUI context, 'PluginContext'
        """
        return self._context

    def handle_close(self):
        """
        Cleans up the timeline, bag and any threads
        """
        if self.__closed:
            return
        else:
            self.__closed = True
        self._play_timer.stop()
        for topic in self._get_topics():
            self.stop_publishing(topic)
            self._message_loaders[topic].stop()
        if self._player:
            self._player.stop()
        if self._recorder:
            self._recorder.stop()
        if self.background_task is not None:
            self.background_task_cancel = True
        self._timeline_frame.handle_close()
        for bag in self._bags:
            bag.close()
        for frame in self._views:
            if frame.parent():
                self._context.remove_widget(frame)

    # Bag Management and access
    def add_bag(self, bag):
        """
        creates an indexing thread for each new topic in the bag
        fixes the boarders and notifies the indexing thread to index the new items bags
        :param bag: ros bag file, ''rosbag.bag''
        """
        self._bags.append(bag)

        bag_topics = bag_helper.get_topics(bag)

        new_topics = set(bag_topics) - set(self._timeline_frame.topics)

        for topic in new_topics:
            self._playhead_positions_cvs[topic] = threading.Condition()
            self._messages_cvs[topic] = threading.Condition()
            self._message_loaders[topic] = MessageLoaderThread(self, topic)

        self._timeline_frame._start_stamp = self._get_start_stamp()
        self._timeline_frame._end_stamp = self._get_end_stamp()
        self._timeline_frame.topics = self._get_topics()
        self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype()
        # If this is the first bag, reset the timeline
        if self._timeline_frame._stamp_left is None:
            self._timeline_frame.reset_timeline()

        # Invalidate entire index cache for all topics in this bag
        with self._timeline_frame.index_cache_cv:
            for topic in bag_topics:
                self._timeline_frame.invalidated_caches.add(topic)
                if topic in self._timeline_frame.index_cache:
                    del self._timeline_frame.index_cache[topic]

            self._timeline_frame.index_cache_cv.notify()

    def file_size(self):
        with self._bag_lock:
            return sum(b.size for b in self._bags)

    #TODO Rethink API and if these need to be visible
    def _get_start_stamp(self):
        """
        :return: first stamp in the bags, ''rospy.Time''
        """
        with self._bag_lock:
            start_stamp = None
            for bag in self._bags:
                bag_start_stamp = bag_helper.get_start_stamp(bag)
                if bag_start_stamp is not None and (start_stamp is None or bag_start_stamp < start_stamp):
                    start_stamp = bag_start_stamp
            return start_stamp

    def _get_end_stamp(self):
        """
        :return: last stamp in the bags, ''rospy.Time''
        """
        with self._bag_lock:
            end_stamp = None
            for bag in self._bags:
                bag_end_stamp = bag_helper.get_end_stamp(bag)
                if bag_end_stamp is not None and (end_stamp is None or bag_end_stamp > end_stamp):
                    end_stamp = bag_end_stamp
            return end_stamp

    def _get_topics(self):
        """
        :return: sorted list of topic names, ''list(str)''
        """
        with self._bag_lock:
            topics = set()
            for bag in self._bags:
                for topic in bag_helper.get_topics(bag):
                    topics.add(topic)
            return sorted(topics)

    def _get_topics_by_datatype(self):
        """
        :return: dict of list of topics for each datatype, ''dict(datatype:list(topic))''
        """
        with self._bag_lock:
            topics_by_datatype = {}
            for bag in self._bags:
                for datatype, topics in bag_helper.get_topics_by_datatype(bag).items():
                    topics_by_datatype.setdefault(datatype, []).extend(topics)
            return topics_by_datatype

    def get_datatype(self, topic):
        """
        :return: datatype associated with a topic, ''str''
        :raises: if there are multiple datatypes assigned to a single topic, ''Exception''
        """
        with self._bag_lock:
            datatype = None
            for bag in self._bags:
                bag_datatype = bag_helper.get_datatype(bag, topic)
                if datatype and bag_datatype and (bag_datatype != datatype):
                    raise Exception('topic %s has multiple datatypes: %s and %s' % (topic, datatype, bag_datatype))
                if bag_datatype:
                    datatype = bag_datatype
            return datatype

    def get_entries(self, topics, start_stamp, end_stamp):
        """
        generator function for bag entries
        :param topics: list of topics to query, ''list(str)''
        :param start_stamp: stamp to start at, ''rospy.Time''
        :param end_stamp: stamp to end at, ''rospy,Time''
        :returns: entries the bag file, ''msg''
        """
        with self._bag_lock:
            from rosbag import bag  # for _mergesort
            bag_entries = []
            for b in self._bags:
                bag_start_time = bag_helper.get_start_stamp(b)
                if bag_start_time is not None and bag_start_time > end_stamp:
                    continue

                bag_end_time = bag_helper.get_end_stamp(b)
                if bag_end_time is not None and bag_end_time < start_stamp:
                    continue

                connections = list(b._get_connections(topics))
                bag_entries.append(b._get_entries(connections, start_stamp, end_stamp))

            for entry, _ in bag._mergesort(bag_entries, key=lambda entry: entry.time):
                yield entry

    def get_entries_with_bags(self, topic, start_stamp, end_stamp):
        """
        generator function for bag entries
        :param topics: list of topics to query, ''list(str)''
        :param start_stamp: stamp to start at, ''rospy.Time''
        :param end_stamp: stamp to end at, ''rospy,Time''
        :returns: tuple of (bag, entry) for the entries in the bag file, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            from rosbag import bag  # for _mergesort

            bag_entries = []
            bag_by_iter = {}
            for b in self._bags:
                bag_start_time = bag_helper.get_start_stamp(b)
                if bag_start_time is not None and bag_start_time > end_stamp:
                    continue

                bag_end_time = bag_helper.get_end_stamp(b)
                if bag_end_time is not None and bag_end_time < start_stamp:
                    continue

                connections = list(b._get_connections(topic))
                it = iter(b._get_entries(connections, start_stamp, end_stamp))
                bag_by_iter[it] = b
                bag_entries.append(it)

            for entry, it in bag._mergesort(bag_entries, key=lambda entry: entry.time):
                yield bag_by_iter[it], entry

    def get_entry(self, t, topic):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :param topic: the topic to be accessed, ''str''
        :return: tuple of (bag, entry) corisponding to time t and topic, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry(t, bag._get_connections(topic))
                if bag_entry and (not entry or bag_entry.time > entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_entry_before(self, t):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :return: tuple of (bag, entry) corresponding to time t, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry(t-rospy.Duration(0,1), bag._get_connections())
                if bag_entry and (not entry or bag_entry.time < entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_entry_after(self, t):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :return: tuple of (bag, entry) corisponding to time t, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry_after(t, bag._get_connections())
                if bag_entry and (not entry or bag_entry.time < entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_next_message_time(self):
        """
        :return: time of the next message after the current playhead position,''rospy.Time''
        """
        if self._timeline_frame.playhead is None:
            return None

        _, entry = self.get_entry_after(self._timeline_frame.playhead)
        if entry is None:
            return self._timeline_frame._start_stamp

        return entry.time

    def get_previous_message_time(self):
        """
        :return: time of the next message before the current playhead position,''rospy.Time''
        """
        if self._timeline_frame.playhead is None:
            return None

        _, entry = self.get_entry_before(self._timeline_frame.playhead)
        if entry is None:
            return self._timeline_frame._end_stamp

        return entry.time

    def resume(self):
        if (self._player):
            self._player.resume()

    ### Copy messages to...

    def start_background_task(self, background_task):
        """
        Verify that a background task is not currently running before starting a new one
        :param background_task: name of the background task, ''str''
        """
        if self.background_task is not None:
            QMessageBox(QMessageBox.Warning, 'Exclamation', 'Background operation already running:\n\n%s' % self.background_task, QMessageBox.Ok).exec_()
            return False

        self.background_task = background_task
        self.background_task_cancel = False
        return True

    def stop_background_task(self):
        self.background_task = None

    def copy_region_to_bag(self, filename):
        if len(self._bags) > 0:
            self._export_region(filename, self._timeline_frame.topics, self._timeline_frame.play_region[0], self._timeline_frame.play_region[1])

    def _export_region(self, path, topics, start_stamp, end_stamp):
        """
        Starts a thread to save the current selection to a new bag file
        :param path: filesystem path to write to, ''str''
        :param topics: topics to write to the file, ''list(str)''
        :param start_stamp: start of area to save, ''rospy.Time''
        :param end_stamp: end of area to save, ''rospy.Time''
        """
        if not self.start_background_task('Copying messages to "%s"' % path):
            return
        # TODO implement a status bar area with information on the current save status
        bag_entries = list(self.get_entries_with_bags(topics, start_stamp, end_stamp))

        if self.background_task_cancel:
            return

        # Get the total number of messages to copy
        total_messages = len(bag_entries)

        # If no messages, prompt the user and return
        if total_messages == 0:
            QMessageBox(QMessageBox.Warning, 'rqt_bag', 'No messages found', QMessageBox.Ok).exec_()
            self.stop_background_task()
            return

        # Open the path for writing
        try:
            export_bag = rosbag.Bag(path, 'w')
        except Exception:
            QMessageBox(QMessageBox.Warning, 'rqt_bag', 'Error opening bag file [%s] for writing' % path, QMessageBox.Ok).exec_()
            self.stop_background_task()
            return

        # Run copying in a background thread
        self._export_thread = threading.Thread(target=self._run_export_region, args=(export_bag, topics, start_stamp, end_stamp, bag_entries))
        self._export_thread.start()

    def _run_export_region(self, export_bag, topics, start_stamp, end_stamp, bag_entries):
        """
        Threaded function that saves the current selection to a new bag file
        :param export_bag: bagfile to write to, ''rosbag.bag''
        :param topics: topics to write to the file, ''list(str)''
        :param start_stamp: start of area to save, ''rospy.Time''
        :param end_stamp: end of area to save, ''rospy.Time''
        """
        total_messages = len(bag_entries)
        update_step = max(1, total_messages / 100)
        message_num = 1
        progress = 0
        # Write out the messages
        for bag, entry in bag_entries:
            if self.background_task_cancel:
                break
            try:
                topic, msg, t = self.read_message(bag, entry.position)
                export_bag.write(topic, msg, t)
            except Exception as ex:
                qWarning('Error exporting message at position %s: %s' % (str(entry.position), str(ex)))
                export_bag.close()
                self.stop_background_task()
                return

            if message_num % update_step == 0 or message_num == total_messages:
                new_progress = int(100.0 * (float(message_num) / total_messages))
                if new_progress != progress:
                    progress = new_progress
                    if not self.background_task_cancel:
                        self.background_progress = progress
                        self.status_bar_changed_signal.emit()

            message_num += 1

        # Close the bag
        try:
            self.background_progress = 0
            self.status_bar_changed_signal.emit()
            export_bag.close()
        except Exception as ex:
            QMessageBox(QMessageBox.Warning, 'rqt_bag', 'Error closing bag file [%s]: %s' % (export_bag.filename, str(ex)), QMessageBox.Ok).exec_()
        self.stop_background_task()

    def read_message(self, bag, position):
        with self._bag_lock:
            return bag._read_message(position)

    ### Mouse events
    def on_mouse_down(self, event):
        if event.buttons() == Qt.LeftButton:
            self._timeline_frame.on_left_down(event)
        elif event.buttons() == Qt.MidButton:
            self._timeline_frame.on_middle_down(event)
        elif event.buttons() == Qt.RightButton:
            topic = self._timeline_frame.map_y_to_topic(self.views()[0].mapToScene(event.pos()).y())
            TimelinePopupMenu(self, event, topic)

    def on_mouse_up(self, event):
        self._timeline_frame.on_mouse_up(event)

    def on_mouse_move(self, event):
        self._timeline_frame.on_mouse_move(event)

    def on_mousewheel(self, event):
        self._timeline_frame.on_mousewheel(event)

    # Zooming

    def zoom_in(self):
        self._timeline_frame.zoom_in()

    def zoom_out(self):
        self._timeline_frame.zoom_out()

    def reset_zoom(self):
        self._timeline_frame.reset_zoom()

    def translate_timeline_left(self):
        self._timeline_frame.translate_timeline_left()

    def translate_timeline_right(self):
        self._timeline_frame.translate_timeline_right()

    ### Publishing
    def is_publishing(self, topic):
        return self._player and self._player.is_publishing(topic)

    def start_publishing(self, topic):
        if not self._player and not self._create_player():
            return False

        self._player.start_publishing(topic)
        return True

    def stop_publishing(self, topic):
        if not self._player:
            return False

        self._player.stop_publishing(topic)
        return True

    def _create_player(self):
        if not self._player:
            try:
                self._player = Player(self)
                if self._publish_clock:
                    self._player.start_clock_publishing()
            except Exception as ex:
                qWarning('Error starting player; aborting publish: %s' % str(ex))
                return False

        return True

    def set_publishing_state(self, start_publishing):
        if start_publishing:
            for topic in self._timeline_frame.topics:
                if not self.start_publishing(topic):
                    break
        else:
            for topic in self._timeline_frame.topics:
                self.stop_publishing(topic)

    # property: play_all
    def _get_play_all(self):
        return self._play_all

    def _set_play_all(self, play_all):
        if play_all == self._play_all:
            return

        self._play_all = not self._play_all

        self.last_frame = None
        self.last_playhead = None
        self.desired_playhead = None

    play_all = property(_get_play_all, _set_play_all)

    def toggle_play_all(self):
        self.play_all = not self.play_all

    ### Playing
    def on_idle(self):
        self._step_playhead()

    def _step_playhead(self):
        """
        moves the playhead to the next position based on the desired position
        """
        # Reset when the playing mode switchs
        if self._timeline_frame.playhead != self.last_playhead:
            self.last_frame = None
            self.last_playhead = None
            self.desired_playhead = None

        if self._play_all:
            self.step_next_message()
        else:
            self.step_fixed()

    def step_fixed(self):
        """
        Moves the playhead a fixed distance into the future based on the current play speed
        """
        if self.play_speed == 0.0 or not self._timeline_frame.playhead:
            self.last_frame = None
            self.last_playhead = None
            return

        now = rospy.Time.from_sec(time.time())
        if self.last_frame:
            # Get new playhead
            if self.stick_to_end:
                new_playhead = self.end_stamp
            else:
                new_playhead = self._timeline_frame.playhead + rospy.Duration.from_sec((now - self.last_frame).to_sec() * self.play_speed)

                start_stamp, end_stamp = self._timeline_frame.play_region

                if new_playhead > end_stamp:
                    if self.wrap:
                        if self.play_speed > 0.0:
                            new_playhead = start_stamp
                        else:
                            new_playhead = end_stamp
                    else:
                        new_playhead = end_stamp

                        if self.play_speed > 0.0:
                            self.stick_to_end = True

                elif new_playhead < start_stamp:
                    if self.wrap:
                        if self.play_speed < 0.0:
                            new_playhead = end_stamp
                        else:
                            new_playhead = start_stamp
                    else:
                        new_playhead = start_stamp

            # Update the playhead
            self._timeline_frame.playhead = new_playhead

        self.last_frame = now
        self.last_playhead = self._timeline_frame.playhead

    def step_next_message(self):
        """
        Move the playhead to the next message
        """
        if self.play_speed <= 0.0 or not self._timeline_frame.playhead:
            self.last_frame = None
            self.last_playhead = None
            return

        if self.last_frame:
            if not self.desired_playhead:
                self.desired_playhead = self._timeline_frame.playhead
            else:
                delta = rospy.Time.from_sec(time.time()) - self.last_frame
                if delta > rospy.Duration.from_sec(0.1):
                    delta = rospy.Duration.from_sec(0.1)
                self.desired_playhead += delta

            # Get the occurrence of the next message
            next_message_time = self.get_next_message_time()

            if next_message_time < self.desired_playhead:
                self._timeline_frame.playhead = next_message_time
            else:
                self._timeline_frame.playhead = self.desired_playhead

        self.last_frame = rospy.Time.from_sec(time.time())
        self.last_playhead = self._timeline_frame.playhead

    ### Recording

    def record_bag(self, filename, all=True, topics=[], regex=False, limit=0):
        try:
            self._recorder = Recorder(filename, bag_lock=self._bag_lock, all=all, topics=topics, regex=regex, limit=limit)
        except Exception as ex:
            qWarning('Error opening bag for recording [%s]: %s' % (filename, str(ex)))
            return

        self._recorder.add_listener(self._message_recorded)

        self.add_bag(self._recorder.bag)

        self._recorder.start()

        self.wrap = False
        self._timeline_frame._index_cache_thread.period = 0.1

        self.update()

    def toggle_recording(self):
        if self._recorder:
            self._recorder.toggle_paused()
            self.update()

    def _message_recorded(self, topic, msg, t):
        if self._timeline_frame._start_stamp is None:
            self._timeline_frame._start_stamp = t
            self._timeline_frame._end_stamp = t
            self._timeline_frame._playhead = t
        elif self._timeline_frame._end_stamp is None or t > self._timeline_frame._end_stamp:
            self._timeline_frame._end_stamp = t

        if not self._timeline_frame.topics or topic not in self._timeline_frame.topics:
            self._timeline_frame.topics = self._get_topics()
            self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype()

            self._playhead_positions_cvs[topic] = threading.Condition()
            self._messages_cvs[topic] = threading.Condition()
            self._message_loaders[topic] = MessageLoaderThread(self, topic)

        if self._timeline_frame._stamp_left is None:
            self.reset_zoom()

        # Notify the index caching thread that it has work to do
        with self._timeline_frame.index_cache_cv:
            self._timeline_frame.invalidated_caches.add(topic)
            self._timeline_frame.index_cache_cv.notify()

        if topic in self._listeners:
            for listener in self._listeners[topic]:
                try:
                    listener.timeline_changed()
                except Exception as ex:
                    qWarning('Error calling timeline_changed on %s: %s' % (type(listener), str(ex)))

    ### Views / listeners
    def add_view(self, topic, frame):
        self._views.append(frame)

    def has_listeners(self, topic):
        return topic in self._listeners

    def add_listener(self, topic, listener):
        self._listeners.setdefault(topic, []).append(listener)

        self._message_listener_threads[(topic, listener)] = MessageListenerThread(self, topic, listener)
        # Notify the message listeners
        self._message_loaders[topic].reset()
        with self._playhead_positions_cvs[topic]:
            self._playhead_positions_cvs[topic].notify_all()

        self.update()

    def remove_listener(self, topic, listener):
        topic_listeners = self._listeners.get(topic)
        if topic_listeners is not None and listener in topic_listeners:
            topic_listeners.remove(listener)

            if len(topic_listeners) == 0:
                del self._listeners[topic]

            # Stop the message listener thread
            if (topic, listener) in self._message_listener_threads:
                self._message_listener_threads[(topic, listener)].stop()
                del self._message_listener_threads[(topic, listener)]
            self.update()

    ### Playhead

    # property: play_speed
    def _get_play_speed(self):
        if self._timeline_frame._paused:
            return 0.0
        return self._play_speed

    def _set_play_speed(self, play_speed):
        if play_speed == self._play_speed:
            return

        if play_speed > 0.0:
            self._play_speed = min(self._max_play_speed, max(self._min_play_speed, play_speed))
        elif play_speed < 0.0:
            self._play_speed = max(-self._max_play_speed, min(-self._min_play_speed, play_speed))
        else:
            self._play_speed = play_speed

        if self._play_speed < 1.0:
            self.stick_to_end = False

        self.update()
    play_speed = property(_get_play_speed, _set_play_speed)

    def toggle_play(self):
        if self._play_speed != 0.0:
            self.play_speed = 0.0
        else:
            self.play_speed = 1.0

    def navigate_play(self):
        self.play_speed = 1.0
        self.last_frame = rospy.Time.from_sec(time.time())
        self.last_playhead = self._timeline_frame.playhead
        self._play_timer.start()

    def navigate_stop(self):
        self.play_speed = 0.0
        self._play_timer.stop()

    def navigate_previous(self):
        self.navigate_stop()
        self._timeline_frame.playhead = self.get_previous_message_time()
        self.last_playhead = self._timeline_frame.playhead

    def navigate_next(self):
        self.navigate_stop()
        self._timeline_frame.playhead = self.get_next_message_time()
        self.last_playhead = self._timeline_frame.playhead

    def navigate_rewind(self):
        if self._play_speed < 0.0:
            new_play_speed = self._play_speed * 2.0
        elif self._play_speed == 0.0:
            new_play_speed = -1.0
        else:
            new_play_speed = self._play_speed * 0.5

        self.play_speed = new_play_speed

    def navigate_fastforward(self):
        if self._play_speed > 0.0:
            new_play_speed = self._play_speed * 2.0
        elif self._play_speed == 0.0:
            new_play_speed = 2.0
        else:
            new_play_speed = self._play_speed * 0.5

        self.play_speed = new_play_speed

    def navigate_start(self):
        self._timeline_frame.playhead = self._timeline_frame.play_region[0]

    def navigate_end(self):
        self._timeline_frame.playhead = self._timeline_frame.play_region[1]
コード例 #24
0
ファイル: ros_helpers.py プロジェクト: quest-fw/quest
class AnimateWorker(QObject):
    finished = Signal()  # class variable shared by all instances

    def __init__(self,
                 eval_callback,
                 dt=0.01,
                 finished_callback=None,
                 node_name='animate_worker',
                 frame_id='animation',
                 parent_frame_id='world',
                 marker_topic='animation_marker',
                 new_node=False,
                 slowdown=1,
                 parent=None):
        super(AnimateWorker, self).__init__(parent)

        if new_node:
            rospy.init_node(node_name, anonymous=True)

        if finished_callback is not None:
            self.finished.connect(finished_callback)

        #TODO([email protected]) - how to shut down?
        self.is_running = True

        self.publish = True
        self.animate = False
        self.eval_callback = eval_callback
        self.t = 0.
        self.t_max = 0.
        self.dt = dt

        if slowdown <= 0.001:  # prevent speeding up by more than 1000x
            slowdown = 1
        self.slowdown = slowdown

        self.R_old = None

        self.frame_id = frame_id
        self.parent_frame_id = parent_frame_id
        self.tf_br = tf.TransformBroadcaster()
        self.marker_pub = rospy.Publisher(marker_topic,
                                          MarkerArray,
                                          queue_size=1)
        self.refresh_marker()

        print("init animate")
        self.timer = QTimer()
        self.timer.setInterval(self.dt * 1000 *
                               self.slowdown)  # in milliseconds
        #self.timer.setTimerType(Qt.PreciseTimer)
        self.timer.timeout.connect(self.on_timer_callback)
        self.timer.start()

    def refresh_marker(self):
        quad_marker = build_quad_marker_template()
        quad_marker.id = 0
        quad_marker.header.frame_id = self.frame_id
        quad_marker.frame_locked = True
        quad_marker.action = quad_marker.ADD
        quad_marker.pose.orientation.w = 1
        quad_marker.pose.position.x = 0.0
        quad_marker.pose.position.y = 0.0
        quad_marker.pose.position.z = 0.0
        quad_marker.lifetime = rospy.Duration(0)

        marker_array = MarkerArray()
        marker_array.markers.append(quad_marker)
        self.marker_pub.publish(marker_array)

    def on_timer_callback(self):
        if not self.publish:
            return

        try:
            if self.animate:
                self.t += self.dt
                if self.t > self.t_max:
                    self.t = 0.
            else:

                self.t = 0.

            (t_max, pos_vec, qw, qx, qy, qz, yaw, vel_vec,
             acc_vec) = self.eval_callback(self.t)
            self.t_max = t_max

            self.tf_br.sendTransform(
                (pos_vec[0], pos_vec[1], pos_vec[2]), (qx, qy, qz, qw),
                rospy.Time.now(), self.frame_id, self.parent_frame_id)
        #except Exception as e:
        #print("Error in AnimateWorker")
        #print(e)
        except:
            # print("Unknown error in AnimateWorker")
            return

    def start_animate(self):
        print("Ros helper start animation")
        self.animate = True
        self.refresh_marker()

    def stop_animate(self):
        self.animate = False

    def start_publish(self):
        self.t = 0
        self.publish = True
        self.refresh_marker()

    def stop_publish(self):
        self.publish = False
コード例 #25
0
ファイル: gui.py プロジェクト: maximkulkin/rqt_joint_control
class JointControlPlugin(Plugin):
    CM_UPDATE_FREQ = 1

    CONTROLLERS = {
        'position_controllers/JointPositionController':
        JointPositionControllerWidget,
    }

    def __init__(self, context):
        super(JointControlPlugin, self).__init__(context)
        self.setObjectName('JointControlPlugin')

        self._widget = loadUi('JointControl.ui')
        self._widget.setObjectName('JointControlPluginUi')

        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))

        context.add_widget(self._widget)

        self._widget.robot_descriptions_list.currentIndexChanged[str].connect(
            self._on_robot_descriptions_list_change)
        self._widget.cm_list.currentIndexChanged[str].connect(
            self._on_cm_change)

        self._urdf = None
        self._cm_ns = None
        self._controllers = []

        self._update_robot_descriptions()

        self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 / self.CM_UPDATE_FREQ)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        self._list_controllers = lambda: []

    def shutdown_plugin(self):
        pass

    def save_settings(self, plugin_settings, instance_settings):
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        pass

    def _update_robot_descriptions(self):
        update_combo(self._widget.robot_descriptions_list,
                     get_robot_description_ns_list())

    def _update_cm_list(self):
        update_combo(self._widget.cm_list, self._list_cm())

    def _on_robot_descriptions_list_change(self, robot_description_ns):
        desc = rosparam.get_param(robot_description_ns)
        try:
            xml = etree.fromstring(desc)
            self._urdf = xml.xpath('/robot')[0]
        except:
            traceback.print_exc()
            self._urdf = None
        self._update_controllers_list()

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns
        self._list_controllers = ControllerLister(cm_ns) if cm_ns else (
            lambda: [])
        self._update_controllers_list()

    def _update_controllers_list(self):
        controllers = self._list_controllers()

        if controllers == self._controllers:
            return

        for _ in xrange(self._widget.controllers.count()):
            self._widget.controllers.takeAt(0)

        self._controllers = controllers

        joint_state_controller = find(
            lambda c: c.type == 'joint_state_controller/JointStateController',
            controllers)

        for controller in controllers:
            self._add_controller(controller)

    def _add_controller(self, controller):
        if controller.type not in self.CONTROLLERS:
            return

        widget = self.CONTROLLERS[controller.type](self._cm_ns, controller,
                                                   self._urdf)
        self._widget.controllers.addWidget(widget)
コード例 #26
0
class JointTrajectoryController(Plugin):
    """
    Graphical frontend for a C{JointTrajectoryController}.

    There are two modes for interacting with a controller:
        1. B{Monitor mode} Joint displays are updated with the state reported
          by the controller. This is a read-only mode and does I{not} send
          control commands. Every time a new controller is selected, it starts
          in monitor mode for safety reasons.

        2. B{Control mode} Joint displays update the control command that is
        sent to the controller. Commands are sent periodically evan if the
        displays are not being updated by the user.

    To control the aggressiveness of the motions, the maximum speed of the
    sent commands can be scaled down using the C{Speed scaling control}

    This plugin is able to detect and keep track of all active controller
    managers, as well as the JointTrajectoryControllers that are I{running}
    in each controller manager.
    For a controller to be compatible with this plugin, it must comply with
    the following requisites:
        - The controller type contains the C{JointTrajectoryController}
        substring, e.g., C{position_controllers/JointTrajectoryController}
        - The controller exposes the C{command} and C{state} topics in its
        ROS interface.

    Additionally, there must be a URDF loaded with a valid joint limit
    specification, namely position (when applicable) and velocity limits.

    A reference implementation of the C{JointTrajectoryController} is
    available in the C{joint_trajectory_controller} ROS package.
    """
    _cmd_pub_freq = 10.0  # Hz
    _widget_update_freq = 30.0  # Hz
    _ctrlrs_update_freq = 1  # Hz
    _min_traj_dur = 5.0 / _cmd_pub_freq  # Minimum trajectory duration

    jointStateChanged = Signal([dict])

    def __init__(self, context):
        super(JointTrajectoryController, self).__init__(context)
        self.setObjectName('JointTrajectoryController')

        # Create QWidget and extend it with all the attributes and children
        # from the UI file
        self._widget = QWidget()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_joint_trajectory_controller'),
                               'resource',
                               'joint_trajectory_controller.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('JointTrajectoryControllerUi')

        # Setup speed scaler
        speed_scaling = DoubleEditor(1.0, 100.0)
        speed_scaling.spin_box.setSuffix('%')
        speed_scaling.spin_box.setValue(50.0)
        speed_scaling.spin_box.setDecimals(0)
        speed_scaling.setEnabled(False)
        self._widget.speed_scaling_layout.addWidget(speed_scaling)
        self._speed_scaling_widget = speed_scaling
        speed_scaling.valueChanged.connect(self._on_speed_scaling_change)
        self._on_speed_scaling_change(speed_scaling.value())

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Initialize members
        self._jtc_name = []  # Name of selected joint trajectory controller
        self._cm_ns = []  # Namespace of the selected controller manager
        self._joint_pos = {}  # name->pos map for joints of selected controller
        self._joint_names = []  # Ordered list of selected controller joints
        self._robot_joint_limits = {} # Lazily evaluated on first use

        # Timer for sending commands to active controller
        self._update_cmd_timer = QTimer(self)
        self._update_cmd_timer.setInterval(1000.0 / self._cmd_pub_freq)
        self._update_cmd_timer.timeout.connect(self._update_cmd_cb)

        # Timer for updating the joint widgets from the controller state
        self._update_act_pos_timer = QTimer(self)
        self._update_act_pos_timer.setInterval(1000.0 /
                                               self._widget_update_freq)
        self._update_act_pos_timer.timeout.connect(self._update_joint_widgets)

        # Timer for controller manager updates
        self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 /
                                               self._ctrlrs_update_freq)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        # Timer for running controller updates
        self._update_jtc_list_timer = QTimer(self)
        self._update_jtc_list_timer.setInterval(1000.0 /
                                                self._ctrlrs_update_freq)
        self._update_jtc_list_timer.timeout.connect(self._update_jtc_list)
        self._update_jtc_list_timer.start()

        # Signal connections
        w = self._widget
        w.enable_button.toggled.connect(self._on_jtc_enabled)
        w.jtc_combo.currentIndexChanged[str].connect(self._on_jtc_change)
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

        self._cmd_pub = None    # Controller command publisher
        self._state_sub = None  # Controller state subscriber

        self._list_controllers = None

    def shutdown_plugin(self):
        self._update_cmd_timer.stop()
        self._update_act_pos_timer.stop()
        self._update_cm_list_timer.stop()
        self._update_jtc_list_timer.stop()
        self._unregister_state_sub()
        self._unregister_cmd_pub()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('cm_ns', self._cm_ns)
        instance_settings.set_value('jtc_name', self._jtc_name)

    def restore_settings(self, plugin_settings, instance_settings):
        # Restore last session's controller_manager, if present
        self._update_cm_list()
        cm_ns = instance_settings.value('cm_ns')
        cm_combo = self._widget.cm_combo
        cm_list = [cm_combo.itemText(i) for i in range(cm_combo.count())]
        try:
            idx = cm_list.index(cm_ns)
            cm_combo.setCurrentIndex(idx)
            # Resore last session's controller, if running
            self._update_jtc_list()
            jtc_name = instance_settings.value('jtc_name')
            jtc_combo = self._widget.jtc_combo
            jtc_list = [jtc_combo.itemText(i) for i in range(jtc_combo.count())]
            try:
                idx = jtc_list.index(jtc_name)
                jtc_combo.setCurrentIndex(idx)
            except (ValueError):
                pass
        except (ValueError):
            pass

    # def trigger_configuration(self):
        # Comment in to signal that the plugin has a way to configure
        # This will enable a setting button (gear icon) in each dock widget
        # title bar
        # Usually used to open a modal configuration dialog

    def _update_cm_list(self):
        update_combo(self._widget.cm_combo, self._list_cm())

    def _update_jtc_list(self):
        # Clear controller list if no controller information is available
        if not self._list_controllers:
            self._widget.jtc_combo.clear()
            return

        # List of running controllers with a valid joint limits specification
        # for _all_ their joints
        running_jtc = self._running_jtc_info()
        if running_jtc and not self._robot_joint_limits:
            self._robot_joint_limits = get_joint_limits()  # Lazy evaluation
        valid_jtc = []
        for jtc_info in running_jtc:
            has_limits = all(name in self._robot_joint_limits
                             for name in _jtc_joint_names(jtc_info))
            if has_limits:
                valid_jtc.append(jtc_info);
        valid_jtc_names = [data.name for data in valid_jtc]

        # Update widget
        update_combo(self._widget.jtc_combo, sorted(valid_jtc_names))

    def _on_speed_scaling_change(self, val):
        self._speed_scale = val / self._speed_scaling_widget.slider.maximum()

    def _on_joint_state_change(self, actual_pos):
        assert(len(actual_pos) == len(self._joint_pos))
        for name in actual_pos.keys():
            self._joint_pos[name]['position'] = actual_pos[name]

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns
        if cm_ns:
            self._list_controllers = ControllerLister(cm_ns)
            # NOTE: Clear below is important, as different controller managers
            # might have controllers with the same name but different
            # configurations. Clearing forces controller re-discovery
            self._widget.jtc_combo.clear()
            self._update_jtc_list()
        else:
            self._list_controllers = None

    def _on_jtc_change(self, jtc_name):
        self._unload_jtc()
        self._jtc_name = jtc_name
        if self._jtc_name:
            self._load_jtc()

    def _on_jtc_enabled(self, val):
        # Don't allow enabling if there are no controllers selected
        if not self._jtc_name:
            self._widget.enable_button.setChecked(False)
            return

        # Enable/disable joint displays
        for joint_widget in self._joint_widgets():
            joint_widget.setEnabled(val)

        # Enable/disable speed scaling
        self._speed_scaling_widget.setEnabled(val)

        if val:
            # Widgets send desired position commands to controller
            self._update_act_pos_timer.stop()
            self._update_cmd_timer.start()
        else:
            # Controller updates widgets with actual position
            self._update_cmd_timer.stop()
            self._update_act_pos_timer.start()

    def _load_jtc(self):
        # Initialize joint data corresponding to selected controller
        running_jtc = self._running_jtc_info()
        self._joint_names = next(_jtc_joint_names(x) for x in running_jtc
                                 if x.name == self._jtc_name)
        for name in self._joint_names:
            self._joint_pos[name] = {}

        # Update joint display
        try:
            layout = self._widget.joint_group.layout()
            for name in self._joint_names:
                limits = self._robot_joint_limits[name]
                joint_widget = DoubleEditor(limits['min_position'],
                                            limits['max_position'])
                layout.addRow(name, joint_widget)
                # NOTE: Using partial instead of a lambda because lambdas
                # "will not evaluate/look up the argument values before it is
                # effectively called, breaking situations like using a loop
                # variable inside it"
                from functools import partial
                par = partial(self._update_single_cmd_cb, name=name)
                joint_widget.valueChanged.connect(par)
        except:
            # TODO: Can we do better than swallow the exception?
            from sys import exc_info
            print 'Unexpected error:', exc_info()[0]

        # Enter monitor mode (sending commands disabled)
        self._on_jtc_enabled(False)

        # Setup ROS interfaces
        jtc_ns = _resolve_controller_ns(self._cm_ns, self._jtc_name)
        state_topic = jtc_ns + '/state'
        cmd_topic = jtc_ns + '/command'
        self._state_sub = rospy.Subscriber(state_topic,
                                           JointTrajectoryControllerState,
                                           self._state_cb,
                                           queue_size=1)
        self._cmd_pub = rospy.Publisher(cmd_topic,
                                        JointTrajectory,
                                        queue_size=1)

        # Start updating the joint positions
        self.jointStateChanged.connect(self._on_joint_state_change)

    def _unload_jtc(self):
        # Stop updating the joint positions
        try:
            self.jointStateChanged.disconnect(self._on_joint_state_change)
        except:
            pass

        # Reset ROS interfaces
        self._unregister_state_sub()
        self._unregister_cmd_pub()

        # Clear joint widgets
        # NOTE: Implementation is a workaround for:
        # https://bugreports.qt-project.org/browse/QTBUG-15990 :(
        layout = self._widget.joint_group.layout()
        if layout is not None:
            while layout.count():
                layout.takeAt(0).widget().deleteLater()
            # Delete existing layout by reparenting to temporary
            QWidget().setLayout(layout)
        self._widget.joint_group.setLayout(QFormLayout())

        # Reset joint data
        self._joint_names = []
        self._joint_pos = {}

        # Enforce monitor mode (sending commands disabled)
        self._widget.enable_button.setChecked(False)

    def _running_jtc_info(self):
        from controller_manager_msgs.utils\
            import filter_by_type, filter_by_state

        controller_list = self._list_controllers()
        jtc_list = filter_by_type(controller_list,
                                  'JointTrajectoryController',
                                  match_substring=True)
        running_jtc_list = filter_by_state(jtc_list, 'running')
        return running_jtc_list

    def _unregister_cmd_pub(self):
        if self._cmd_pub is not None:
            self._cmd_pub.unregister()
            self._cmd_pub = None

    def _unregister_state_sub(self):
        if self._state_sub is not None:
            self._state_sub.unregister()
            self._state_sub = None

    def _state_cb(self, msg):
        actual_pos = {}
        for i in range(len(msg.joint_names)):
            joint_name = msg.joint_names[i]
            joint_pos = msg.actual.positions[i]
            actual_pos[joint_name] = joint_pos
        self.jointStateChanged.emit(actual_pos)

    def _update_single_cmd_cb(self, val, name):
        self._joint_pos[name]['command'] = val

    def _update_cmd_cb(self):
        dur = []
        traj = JointTrajectory()
        traj.joint_names = self._joint_names
        point = JointTrajectoryPoint()
        for name in traj.joint_names:
            pos = self._joint_pos[name]['position']
            cmd = pos
            try:
                cmd = self._joint_pos[name]['command']
            except (KeyError):
                pass
            max_vel = self._robot_joint_limits[name]['max_velocity']
            dur.append(max(abs(cmd - pos) / max_vel, self._min_traj_dur))
            point.positions.append(cmd)
        point.time_from_start = rospy.Duration(max(dur) / self._speed_scale)
        traj.points.append(point)

        self._cmd_pub.publish(traj)

    def _update_joint_widgets(self):
        joint_widgets = self._joint_widgets()
        for id in range(len(joint_widgets)):
            joint_name = self._joint_names[id]
            try:
                joint_pos = self._joint_pos[joint_name]['position']
                joint_widgets[id].setValue(joint_pos)
            except (KeyError):
                pass  # Can happen when first connected to controller

    def _joint_widgets(self):  # TODO: Cache instead of compute every time?
        widgets = []
        layout = self._widget.joint_group.layout()
        for row_id in range(layout.rowCount()):
            widgets.append(layout.itemAt(row_id,
                                         QFormLayout.FieldRole).widget())
        return widgets
コード例 #27
0
class MotionEditorWidget(QWidget):

    position_renamed = Signal(QListWidgetItem)

    def __init__(self, motion_publisher, robot_config):
        super(MotionEditorWidget, self).__init__()
        self.robot_config = robot_config
        self._motion_publisher = motion_publisher
        self._motion_data = MotionData(robot_config)
        self._filter_pattern = ''
        self._playback_marker = None
        self._playback_timer = None

        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'motion_editor.ui')
        loadUi(ui_file, self)
        self.list_widgets = {}
        for group_type in robot_config.group_types():
            list_widget = QListWidget()
            list_widget.setSortingEnabled(True)
            list_widget.setDragDropMode(QAbstractItemView.DragOnly)
            list_widget.setContextMenuPolicy(Qt.CustomContextMenu)

            list_widget.customContextMenuRequested.connect(
                lambda pos, _group_type=group_type: self.positions_list_context_menu(_group_type, pos)
            )
            list_widget.itemChanged.connect(self.on_list_item_changed)

            self.position_lists_layout.addWidget(list_widget)
            self.list_widgets[group_type] = list_widget

        self._timeline_widget = TimelineWidget()
        for track_name in self.robot_config.sorted_groups():
            track_type = self.robot_config.groups[track_name].group_type
            track = self._timeline_widget.add_track(track_name, track_type)

            list_widget = self.list_widgets[track_type]
            palette = list_widget.palette()
            palette.setColor(QPalette.Base, track._colors['track'])
            list_widget.setPalette(palette)

        self.timeline_group.layout().addWidget(self._timeline_widget)

        for group_type in robot_config.group_types():
            label = QLabel(group_type)
            label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.group_label_layout.addWidget(label)

        self.update_motion_name_combo()

        self.stop_motion_button.pressed.connect(self.on_motion_stop_pressed)

    def on_list_item_changed(self, item):
        print 'Position name changed from', item._text, 'to', item.text()
        self.position_renamed.emit(item)

    def on_motion_stop_pressed(self):
        self._clear_playback_marker()
        self._motion_publisher.stop_motion()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('splitter', self.splitter.saveState())
        instance_settings.set_value('filter_pattern', self.filter_pattern_edit.text())
        instance_settings.set_value('filter_motions_checked', self.filter_motions_check.isChecked())
        instance_settings.set_value('filter_positions_checked', self.filter_positions_check.isChecked())

    def restore_settings(self, plugin_settings, instance_settings):
        if instance_settings.contains('splitter'):
            self.splitter.restoreState(instance_settings.value('splitter'))
        else:
            self.splitter.setSizes([300, 100])
        self.filter_pattern_edit.setText(instance_settings.value('filter_pattern', ''))
        self.filter_motions_check.setChecked(instance_settings.value('filter_motions_checked', False) in (1, True, 'true'))
        self.filter_positions_check.setChecked(instance_settings.value('filter_positions_checked', False) in (1, True, 'true'))

    @Slot()
    def on_clear_motion_button_clicked(self):
        self._timeline_widget.scene().clear()

    @Slot()
    def on_delete_motion_button_clicked(self):
        motion_name = str(self.motion_name_combo.currentText())
        if len(motion_name) == 0:
            QMessageBox.warning(self, 'Error', 'No motion selected to delete.')
            return
        if motion_name not in self._motion_data:
            QMessageBox.warning(self, 'Error', 'Motion "%s" not in motion list.' % motion_name)
            return
        self._motion_data.delete(motion_name)
        self.update_motion_name_combo()

    @Slot()
    def on_save_motion_button_clicked(self):
        motion_name = str(self.motion_name_combo.currentText())
        if len(motion_name) == 0:
            QMessageBox.warning(self, 'Error', 'No name given to save this motion.')
            return
        self._motion_data.save(motion_name, self.get_motion_from_timeline())
        self.update_motion_name_combo()

    @Slot()
    def on_load_motion_button_clicked(self):
        motion_name = str(self.motion_name_combo.currentText())
        if len(motion_name) == 0:
            QMessageBox.warning(self, 'Error', 'No motion selected to load.')
            return
        if motion_name not in self._motion_data:
            QMessageBox.warning(self, 'Error', 'Motion "%s" not in motion list.' % motion_name)
            return
        self.on_clear_motion_button_clicked()
        motion = self._motion_data[motion_name]
        for group_name, poses in motion.items():
            for pose in poses:
                data = self.robot_config.groups[group_name].adapt_to_side(pose['positions'])
                self._timeline_widget.scene().add_clip(group_name, pose['name'], pose['starttime'], pose['duration'], data)

    @Slot()
    def on_null_motion_button_clicked(self):
        self._clear_playback_marker()
        for group_name in self.robot_config.groups:
            target_position = [0] * len(self.robot_config.groups[group_name].joints)
            self._motion_publisher.move_to_position(group_name, target_position, self.time_factor_spin.value())

    @Slot()
    def on_run_motion_button_clicked(self):
        motion_name = str(self.motion_name_combo.currentText())
        if len(motion_name) == 0:
            QMessageBox.warning(self, 'Error', 'No motion selected to run.')
            return
        if motion_name not in self._motion_data:
            QMessageBox.warning(self, 'Error', 'Motion "%s" not in motion list.' % motion_name)
            return
        self._clear_playback_marker()
        self._motion_publisher.publish_motion(self._motion_data[motion_name], self.time_factor_spin.value())
        print '[Motion Editor] Running motion:', motion_name

    @Slot(str)
    def on_filter_pattern_edit_textChanged(self, pattern):
        self._filter_pattern = pattern
        self._apply_filter_to_position_lists()
        self.update_motion_name_combo()

    def _apply_filter_to_position_lists(self):
        for group_type in self.robot_config.group_types():
            list_widget = self.list_widgets[group_type]
            for row in range(list_widget.count()):
                item = list_widget.item(row)
                hidden = self.filter_positions_check.isChecked() and re.search(self._filter_pattern, item.text()) is None
                item.setHidden(hidden)

    @Slot(bool)
    def on_filter_positions_check_toggled(self, checked):
        self._apply_filter_to_position_lists()

    @Slot(bool)
    def on_filter_motions_check_toggled(self, checked):
        self.update_motion_name_combo()

    def update_motion_name_combo(self):
        combo = self.motion_name_combo
        # remember selected motion name
        motion_name = str(combo.currentText())
        # update motion names
        combo.clear()
        motion_names = self._motion_data.keys()
        if self.filter_motions_check.isChecked():
            motion_names = [name for name in motion_names if re.search(self._filter_pattern, name) is not None]
        combo.addItems(sorted(motion_names))
        # restore previously selected motion name
        motion_index = combo.findText(motion_name)
        combo.setCurrentIndex(motion_index)

    def update_positions_lists(self, positions):
        for group_type in self.robot_config.group_types():
            list_widget = self.list_widgets[group_type]
            list_widget.clear()
            for name, position in positions[group_type].items():
                item = QListWidgetItem(name)
                item._data = position
                item._text = name
                item._type = group_type
                item.setFlags(item.flags() | Qt.ItemIsEditable)
                list_widget.addItem(item)
        self._apply_filter_to_position_lists()

    def positions_list_context_menu(self, group_type, pos):
        list_widget = self.list_widgets[group_type]
        list_item = list_widget.itemAt(pos)
        if list_item is None:
            return

        menu = QMenu()
        move_to = {}
        for group in self.robot_config.group_list():
            if list_item._type == group.group_type:
                move_to[menu.addAction('move "%s"' % group.name)] = [group.name]
        # move_to[menu.addAction('move all')] = list(move_to.itervalues())
        move_to[menu.addAction('move all')] = [group_list[0] for group_list in list(move_to.itervalues())]
        result = menu.exec_(list_widget.mapToGlobal(pos))
        if result in move_to:
            group_names = move_to[result]
            for group_name in group_names:
                target_positions = self.robot_config.groups[group_name].adapt_to_side(list_item._data)
                self._motion_publisher.move_to_position(group_name, target_positions, self.time_factor_spin.value())
                print '[Motion Editor] Moving %s to: %s' % (group_name, zip(self.robot_config.groups[group_name].joints_sorted(), target_positions))

    def get_motion_from_timeline(self):
        motion = {}
        for group_name, clips in self._timeline_widget.scene().clips().items():
            motion[group_name] = []
            for clip in clips:
                pose = {
                    'name': clip.text(),
                    'starttime': clip.starttime(),
                    'duration': clip.duration(),
                    'positions': self.robot_config.groups[group_name].adapt_to_side(clip.data()),
                }
                motion[group_name].append(pose)
        return motion

    @Slot()
    def on_run_timeline_button_clicked(self):
        print '[Motion Editor] Running timeline.'
        self._playback_duration = 0.0
        for clips in self._timeline_widget.scene().clips().values():
            if len(clips) > 0 and self._playback_duration < clips[-1].endtime():
                self._playback_duration = clips[-1].endtime()
        self._playback_time_factor = self.time_factor_spin.value()

        self._clear_playback_marker()
        self._playback_marker = self._timeline_widget.scene().add_marker(0.0)

        self._playback_timer = QTimer()
        self._playback_timer.setInterval(30)
        self._playback_timer.timeout.connect(self._playback_update)

        self._playback_start = rospy.get_time()
        self._motion_publisher.publish_motion(self.get_motion_from_timeline(), self.time_factor_spin.value())
        self._playback_timer.start()

    def _clear_playback_marker(self):
        if self._playback_timer is not None:
            self._playback_timer.stop()
        if self._playback_marker is not None:
            self._playback_marker.remove()

    @Slot()
    def _playback_update(self):
        duration = (rospy.get_time() - self._playback_start) / self._playback_time_factor
        if duration > self._playback_duration:
            self._clear_playback_marker()
        else:
            self._playback_marker.set_time(duration)
コード例 #28
0
class JointTrajectoryController(Plugin):
    """
    Graphical frontend for a C{JointTrajectoryController}.

    There are two modes for interacting with a controller:
        1. B{Monitor mode} Joint displays are updated with the state reported
          by the controller. This is a read-only mode and does I{not} send
          control commands. Every time a new controller is selected, it starts
          in monitor mode for safety reasons.

        2. B{Control mode} Joint displays update the control command that is
        sent to the controller. Commands are sent periodically evan if the
        displays are not being updated by the user.

    To control the aggressiveness of the motions, the maximum speed of the
    sent commands can be scaled down using the C{Speed scaling control}

    This plugin is able to detect and keep track of all active controller
    managers, as well as the JointTrajectoryControllers that are I{running}
    in each controller manager.
    For a controller to be compatible with this plugin, it must comply with
    the following requisites:
        - The controller type contains the C{JointTrajectoryController}
        substring, e.g., C{position_controllers/JointTrajectoryController}
        - The controller exposes the C{command} and C{state} topics in its
        ROS interface.

    Additionally, there must be a URDF loaded with a valid joint limit
    specification, namely position (when applicable) and velocity limits.

    A reference implementation of the C{JointTrajectoryController} is
    available in the C{joint_trajectory_controller} ROS package.
    """

    _cmd_pub_freq = 10.0  # Hz
    _widget_update_freq = 30.0  # Hz
    _ctrlrs_update_freq = 1  # Hz
    _min_traj_dur = 5.0 / _cmd_pub_freq  # Minimum trajectory duration

    jointStateChanged = Signal([dict])

    def __init__(self, context):
        super(JointTrajectoryController, self).__init__(context)
        self.setObjectName('JointTrajectoryController')

        self._node = context.node

        # Get the robot_description via a topic
        qos_profile = QoSProfile(depth=1,
                                 history=HistoryPolicy.KEEP_LAST,
                                 durability=DurabilityPolicy.TRANSIENT_LOCAL)
        self._robot_description_sub = self._node.create_subscription(
            String, 'robot_description',
            self._robot_description_cb, qos_profile)
        self._robot_description = None

        self._publisher = None
        self._widget = QWidget()

        _, package_path = get_resource('packages', 'rqt_joint_trajectory_controller')
        ui_file = os.path.join(package_path, 'share', 'rqt_joint_trajectory_controller', 'resource', 'joint_trajectory_controller.ui')
        loadUi(ui_file, self._widget)

        self._widget.setObjectName('JointTrajectoryControllerUi')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(
                self._widget.windowTitle() + (' (%d)' % context.serial_number()))
        context.add_widget(self._widget)

        ns = self._node.get_namespace()
        self._widget.controller_group.setTitle('ns: ' + ns)

        # Setup speed scaler
        speed_scaling = DoubleEditor(1.0, 100.0)
        speed_scaling.spin_box.setSuffix('%')
        speed_scaling.spin_box.setValue(50.0)
        speed_scaling.spin_box.setDecimals(0)
        speed_scaling.setEnabled(False)
        self._widget.speed_scaling_layout.addWidget(speed_scaling)
        self._speed_scaling_widget = speed_scaling
        speed_scaling.valueChanged.connect(self._on_speed_scaling_change)
        self._on_speed_scaling_change(speed_scaling.value())

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Initialize members
        self._jtc_name = []  # Name of selected joint trajectory controller
        self._cm_ns = []  # Namespace of the selected controller manager
        self._joint_pos = {}  # name->pos map for joints of selected controller
        self._joint_names = []  # Ordered list of selected controller joints
        self._robot_joint_limits = {} # Lazily evaluated on first use

        # Timer for sending commands to active controller
        self._update_cmd_timer = QTimer(self)
        self._update_cmd_timer.setInterval(1000.0 / self._cmd_pub_freq)
        self._update_cmd_timer.timeout.connect(self._update_cmd_cb)

        # Timer for updating the joint widgets from the controller state
        self._update_act_pos_timer = QTimer(self)
        self._update_act_pos_timer.setInterval(1000.0 /
                                               self._widget_update_freq)
        self._update_act_pos_timer.timeout.connect(self._update_joint_widgets)

        # Timer for controller manager updates
        # TODO: self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 /
                                               self._ctrlrs_update_freq)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        # Timer for running controller updates
        self._update_jtc_list_timer = QTimer(self)
        self._update_jtc_list_timer.setInterval(1000.0 /
                                                self._ctrlrs_update_freq)
        self._update_jtc_list_timer.timeout.connect(self._update_jtc_list)
        self._update_jtc_list_timer.start()

        # Signal connections
        w = self._widget
        w.enable_button.toggled.connect(self._on_jtc_enabled)
        w.jtc_combo.currentIndexChanged[str].connect(self._on_jtc_change)
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

        self._cmd_pub = None    # Controller command publisher
        self._state_sub = None  # Controller state subscriber

        self._list_controllers = None

        self._traj_ns_topic_dict = None

    def shutdown_plugin(self):
        self._update_cmd_timer.stop()
        self._update_act_pos_timer.stop()
        self._update_cm_list_timer.stop()
        self._update_jtc_list_timer.stop()
        self._unregister_state_sub()
        self._unregister_cmd_pub()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('cm_ns', self._cm_ns)
        instance_settings.set_value('jtc_name', self._jtc_name)

    def restore_settings(self, plugin_settings, instance_settings):
        # Restore last session's controller_manager, if present
        self._update_cm_list()
        cm_ns = instance_settings.value('cm_ns')
        cm_combo = self._widget.cm_combo
        cm_list = [cm_combo.itemText(i) for i in range(cm_combo.count())]
        try:
            idx = cm_list.index(cm_ns)
            cm_combo.setCurrentIndex(idx)
            # Resore last session's controller, if running
            self._update_jtc_list()
            jtc_name = instance_settings.value('jtc_name')
            jtc_combo = self._widget.jtc_combo
            jtc_list = [jtc_combo.itemText(i) for i in range(jtc_combo.count())]
            try:
                idx = jtc_list.index(jtc_name)
                jtc_combo.setCurrentIndex(idx)
            except (ValueError):
                pass
        except (ValueError):
            pass

    # def trigger_configuration(self):
        # Comment in to signal that the plugin has a way to configure
        # This will enable a setting button (gear icon) in each dock widget
        # title bar
        # Usually used to open a modal configuration dialog

    def _update_cm_list(self):
        trajectory_topics = _search_for_trajectory_topics(self._node)

        ## TODO: remove test code
        #trajectory_topics.append('/my_test/controller')
        #trajectory_topics.append('/no_namespace')
        #trajectory_topics.append('no_root')

        self._traj_ns_topic_dict = {}
        for full_topic in trajectory_topics:
            ns, topic = _split_namespace_from_topic(full_topic)
            self._traj_ns_topic_dict.setdefault(ns, []).append(topic)

        update_combo(self._widget.cm_combo, list(self._traj_ns_topic_dict.keys()))

    def _update_jtc_list(self):
        # Clear controller list if no controller information is available
        if self._traj_ns_topic_dict is None:
            self._widget.jtc_combo.clear()
            return

        #print(get_joint_limits(self._robot_description))

        ## List of running controllers with a valid joint limits specification
        ## for _all_ their joints
        #running_jtc = self._running_jtc_info()
        #if running_jtc and not self._robot_joint_limits:
        #    self._robot_joint_limits = get_joint_limits()  # Lazy evaluation
        #valid_jtc = []
        #for jtc_info in running_jtc:
        #    has_limits = all(name in self._robot_joint_limits
        #                     for name in _jtc_joint_names(jtc_info))
        #    if has_limits:
        #        valid_jtc.append(jtc_info);
        #valid_jtc_names = [data.name for data in valid_jtc]

        # Get the currently selected namespace
        curr_ns = self._widget.cm_combo.currentText()
        topics = self._traj_ns_topic_dict[curr_ns]
        update_combo(self._widget.jtc_combo, topics)

    def _on_speed_scaling_change(self, val):
        self._speed_scale = val / self._speed_scaling_widget.slider.maximum()

    def _on_joint_state_change(self, actual_pos):
        assert(len(actual_pos) == len(self._joint_pos))
        for name in actual_pos.keys():
            self._joint_pos[name]['position'] = actual_pos[name]

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns
        if cm_ns:
            self._widget.jtc_combo.clear()
            self._update_jtc_list()
        else:
            self._list_controllers = None

    def _on_jtc_change(self, jtc_name):
        self._unload_jtc()
        self._jtc_name = jtc_name
        if self._jtc_name:
            self._load_jtc()

    def _on_jtc_enabled(self, val):
        # Don't allow enabling if there are no controllers selected
        if not self._jtc_name:
            self._widget.enable_button.setChecked(False)
            return

        # Enable/disable joint displays
        for joint_widget in self._joint_widgets():
            joint_widget.setEnabled(val)

        # Enable/disable speed scaling
        self._speed_scaling_widget.setEnabled(val)

        if val:
            # Widgets send desired position commands to controller
            self._update_act_pos_timer.stop()
            self._update_cmd_timer.start()
        else:
            # Controller updates widgets with actual position
            self._update_cmd_timer.stop()
            self._update_act_pos_timer.start()

    def _load_jtc(self):
        self._robot_joint_limits = get_joint_limits(self._robot_description)
        self._joint_names = list(self._robot_joint_limits.keys())
        self._joint_pos = { name: {} for name in self._joint_names }

        # Update joint display
        try:
            layout = self._widget.joint_group.layout()
            for name in self._joint_names:
                limits = self._robot_joint_limits[name]
                joint_widget = DoubleEditor(limits['min_position'],
                                            limits['max_position'])
                layout.addRow(name, joint_widget)
                # NOTE: Using partial instead of a lambda because lambdas
                # "will not evaluate/look up the argument values before it is
                # effectively called, breaking situations like using a loop
                # variable inside it"
                from functools import partial
                par = partial(self._update_single_cmd_cb, name=name)
                joint_widget.valueChanged.connect(par)
        except:
            # TODO: Can we do better than swallow the exception?
            from sys import exc_info
            print('Unexpected error:', exc_info()[0])

        # Enter monitor mode (sending commands disabled)
        self._on_jtc_enabled(False)

        # Setup ROS interfaces
        jtc_ns = _resolve_controller_ns(self._cm_ns, self._jtc_name)
        #state_topic = jtc_ns + '/state'
        state_topic = 'state'
        #cmd_topic = jtc_ns + '/command'
        cmd_topic = '/joint_trajectory_controller/joint_trajectory'
        #self._state_sub = rospy.Subscriber(state_topic,
        #                                   JointTrajectoryControllerState,
        #                                   self._state_cb,
        #                                   queue_size=1)

        qos_profile = QoSProfile(depth=1,
                                 history=HistoryPolicy.KEEP_LAST,
                                 durability=DurabilityPolicy.TRANSIENT_LOCAL)

        self._state_sub = self._node.create_subscription(
            JointTrajectoryControllerState, state_topic, self._state_cb,
            qos_profile)

        self._cmd_pub = self._node.create_publisher(JointTrajectory, cmd_topic, 1)

        # Start updating the joint positions
        self.jointStateChanged.connect(self._on_joint_state_change)

    def _unload_jtc(self):
        # Stop updating the joint positions
        try:
            self.jointStateChanged.disconnect(self._on_joint_state_change)
        except:
            pass

        # Reset ROS interfaces
        self._unregister_state_sub()
        self._unregister_cmd_pub()

        # Clear joint widgets
        # NOTE: Implementation is a workaround for:
        # https://bugreports.qt-project.org/browse/QTBUG-15990 :(

        layout = self._widget.joint_group.layout()
        if layout is not None:
            while layout.count():
                layout.takeAt(0).widget().deleteLater()
            # Delete existing layout by reparenting to temporary
            QWidget().setLayout(layout)
        self._widget.joint_group.setLayout(QFormLayout())

        # Reset joint data
        self._joint_names = []
        self._joint_pos = {}

        # Enforce monitor mode (sending commands disabled)
        self._widget.enable_button.setChecked(False)

    def _running_jtc_info(self):
        from controller_manager_msgs.utils\
            import filter_by_type, filter_by_state

        controller_list = self._list_controllers()
        jtc_list = filter_by_type(controller_list,
                                  'JointTrajectoryController',
                                  match_substring=True)
        running_jtc_list = filter_by_state(jtc_list, 'running')
        return running_jtc_list

    def _unregister_cmd_pub(self):
        if self._cmd_pub is not None:
            self._node.destroy_publisher(self._cmd_pub)
            self._cmd_pub = None

    def _unregister_state_sub(self):
        if self._state_sub is not None:
            self._node.destroy_subscription(self._state_sub)
            self._state_sub = None

    def _robot_description_cb(self, msg):
        self._robot_description = msg.data

    def _state_cb(self, msg):
        actual_pos = {}
        for i in range(len(msg.joint_names)):
            joint_name = msg.joint_names[i]
            joint_pos = msg.actual.positions[i]
            actual_pos[joint_name] = joint_pos
        self.jointStateChanged.emit(actual_pos)

    def _update_single_cmd_cb(self, val, name):
        self._joint_pos[name]['command'] = val

    def _update_cmd_cb(self):
        dur = []
        traj = JointTrajectory()
        traj.joint_names = self._joint_names
        point = JointTrajectoryPoint()
        for name in traj.joint_names:
            pos = self._joint_pos[name]['position']
            cmd = pos
            try:
                cmd = self._joint_pos[name]['command']
            except (KeyError):
                pass
            max_vel = self._robot_joint_limits[name]['max_velocity']
            dur.append(max(abs(cmd - pos) / max_vel, self._min_traj_dur))
            point.positions.append(cmd)

        max_duration_scaled = max(dur) / self._speed_scale
        seconds = floor(max_duration_scaled)
        nanoseconds = (max_duration_scaled - seconds) * 1e9
        duration = Duration(seconds=seconds, nanoseconds=nanoseconds)
        point.time_from_start = duration.to_msg()
        traj.points.append(point)

        self._cmd_pub.publish(traj)

    def _update_joint_widgets(self):
        joint_widgets = self._joint_widgets()
        for id in range(len(joint_widgets)):
            joint_name = self._joint_names[id]
            try:
                joint_pos = self._joint_pos[joint_name]['position']
                joint_widgets[id].setValue(joint_pos)
            except (KeyError):
                pass  # Can happen when first connected to controller

    def _joint_widgets(self):  # TODO: Cache instead of compute every time?
        widgets = []
        layout = self._widget.joint_group.layout()
        for row_id in range(layout.rowCount()):
            widgets.append(layout.itemAt(row_id,
                                         QFormLayout.FieldRole).widget())
        return widgets
コード例 #29
0
class ControllerManager(Plugin):
    """
    Graphical frontend for managing ros_control controllers.
    """
    _cm_update_freq = 1  # Hz

    def __init__(self, context):
        super(ControllerManager, self).__init__(context)
        self.setObjectName('ControllerManager')

        # Create QWidget and extend it with all the attributes and children
        # from the UI file
        self._widget = QWidget()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_controller_manager'),
                               'resource',
                               'controller_manager.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('ControllerManagerUi')

        # Pop-up that displays controller information
        self._popup_widget = QWidget()
        ui_file = os.path.join(rp.get_path('rqt_controller_manager'),
                               'resource',
                               'controller_info.ui')
        loadUi(ui_file, self._popup_widget)
        self._popup_widget.setObjectName('ControllerInfoUi')

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Initialize members
        self._cm_ns = []  # Namespace of the selected controller manager
        self._controllers = []  # State of each controller
        self._table_model = None
        self._controller_lister = None

        # Controller manager service proxies
        self._load_srv = None
        self._unload_srv = None
        self._switch_srv = None

        # Controller state icons
        rospack = rospkg.RosPack()
        path = rospack.get_path('rqt_controller_manager')
        self._icons = {'running': QIcon(path + '/resource/led_green.png'),
                       'stopped': QIcon(path + '/resource/led_red.png'),
                       'uninitialized': QIcon(path + '/resource/led_off.png')}

        # Controllers display
        table_view = self._widget.table_view
        table_view.setContextMenuPolicy(Qt.CustomContextMenu)
        table_view.customContextMenuRequested.connect(self._on_ctrl_menu)

        table_view.doubleClicked.connect(self._on_ctrl_info)

        header = table_view.horizontalHeader()
        header.setSectionResizeMode(QHeaderView.ResizeToContents)
        header.setContextMenuPolicy(Qt.CustomContextMenu)
        header.customContextMenuRequested.connect(self._on_header_menu)

        # Timer for controller manager updates
        self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 /
                                               self._cm_update_freq)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        # Timer for running controller updates
        self._update_ctrl_list_timer = QTimer(self)
        self._update_ctrl_list_timer.setInterval(1000.0 /
                                                 self._cm_update_freq)
        self._update_ctrl_list_timer.timeout.connect(self._update_controllers)
        self._update_ctrl_list_timer.start()

        # Signal connections
        w = self._widget
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

    def shutdown_plugin(self):
        self._update_cm_list_timer.stop()
        self._update_ctrl_list_timer.stop()
        self._popup_widget.hide()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('cm_ns', self._cm_ns)

    def restore_settings(self, plugin_settings, instance_settings):
        # Restore last session's controller_manager, if present
        self._update_cm_list()
        cm_ns = instance_settings.value('cm_ns')
        cm_combo = self._widget.cm_combo
        cm_list = [cm_combo.itemText(i) for i in range(cm_combo.count())]
        try:
            idx = cm_list.index(cm_ns)
            cm_combo.setCurrentIndex(idx)
        except (ValueError):
            pass

    # def trigger_configuration(self):
        # Comment in to signal that the plugin has a way to configure
        # This will enable a setting button (gear icon) in each dock widget
        # title bar
        # Usually used to open a modal configuration dialog

    def _update_cm_list(self):
        update_combo(self._widget.cm_combo, self._list_cm())

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns

        # Setup services for communicating with the selected controller manager
        self._set_cm_services(cm_ns)

        # Controller lister for the selected controller manager
        if cm_ns:
            self._controller_lister = ControllerLister(cm_ns)
            self._update_controllers()
        else:
            self._controller_lister = None

    def _set_cm_services(self, cm_ns):
        if cm_ns:
            # NOTE: Persistent services are used for performance reasons.
            # If the controller manager dies, we detect it and disconnect from
            # it anyway
            load_srv_name = _append_ns(cm_ns, 'load_controller')
            self._load_srv = rospy.ServiceProxy(load_srv_name,
                                                LoadController,
                                                persistent=True)
            unload_srv_name = _append_ns(cm_ns, 'unload_controller')
            self._unload_srv = rospy.ServiceProxy(unload_srv_name,
                                                  UnloadController,
                                                  persistent=True)
            switch_srv_name = _append_ns(cm_ns, 'switch_controller')
            self._switch_srv = rospy.ServiceProxy(switch_srv_name,
                                                  SwitchController,
                                                  persistent=True)
        else:
            self._load_srv = None
            self._unload_srv = None
            self._switch_srv = None

    def _update_controllers(self):
        # Find controllers associated to the selected controller manager
        controllers = self._list_controllers()

        # Update controller display, if necessary
        if self._controllers != controllers:
            self._controllers = controllers
            self._show_controllers()  # NOTE: Model is recomputed from scratch

    def _list_controllers(self):
        """
        @return List of controllers associated to a controller manager
        namespace. Contains both stopped/running controllers, as returned by
        the C{list_controllers} service, plus uninitialized controllers with
        configurations loaded in the parameter server.
        @rtype [str]
        """
        if not self._cm_ns:
            return []

        # Add loaded controllers first
        controllers = self._controller_lister()

        # Append potential controller configs found in the parameter server
        all_ctrls_ns = _resolve_controllers_ns(self._cm_ns)
        for name in get_rosparam_controller_names(all_ctrls_ns):
            add_ctrl = not any(name == ctrl.name for ctrl in controllers)
            if add_ctrl:
                type_str = _rosparam_controller_type(all_ctrls_ns, name)
                uninit_ctrl = ControllerState(name=name,
                                              type=type_str,
                                              state='uninitialized')
                controllers.append(uninit_ctrl)
        return controllers

    def _show_controllers(self):
        table_view = self._widget.table_view
        self._table_model = ControllerTable(self._controllers, self._icons)
        table_view.setModel(self._table_model)

    def _on_ctrl_menu(self, pos):
        # Get data of selected controller
        row = self._widget.table_view.rowAt(pos.y())
        if row < 0:
            return  # Cursor is not under a valid item

        ctrl = self._controllers[row]

        # Show context menu
        menu = QMenu(self._widget.table_view)
        if ctrl.state == 'running':
            action_stop = menu.addAction(self._icons['stopped'], 'Stop')
            action_kill = menu.addAction(self._icons['uninitialized'],
                                         'Stop and Unload')
        elif ctrl.state == 'stopped':
            action_start = menu.addAction(self._icons['running'], 'Start')
            action_unload = menu.addAction(self._icons['uninitialized'],
                                           'Unload')
        elif ctrl.state == 'uninitialized':
            action_load = menu.addAction(self._icons['stopped'], 'Load')
            action_spawn = menu.addAction(self._icons['running'],
                                          'Load and Start')

        action = menu.exec_(self._widget.table_view.mapToGlobal(pos))

        # Evaluate user action
        if ctrl.state == 'running':
            if action is action_stop:
                self._stop_controller(ctrl.name)
            elif action is action_kill:
                self._stop_controller(ctrl.name)
                self._unload_controller(ctrl.name)
        elif ctrl.state == 'stopped':
            if action is action_start:
                self._start_controller(ctrl.name)
            elif action is action_unload:
                self._unload_controller(ctrl.name)
        elif ctrl.state == 'uninitialized':
            if action is action_load:
                self._load_controller(ctrl.name)
            if action is action_spawn:
                self._load_controller(ctrl.name)
                self._start_controller(ctrl.name)

    def _on_ctrl_info(self, index):
        popup = self._popup_widget

        ctrl = self._controllers[index.row()]
        popup.ctrl_name.setText(ctrl.name)
        popup.ctrl_type.setText(ctrl.type)

        res_model = QStandardItemModel()
        model_root = QStandardItem('Claimed Resources')
        res_model.appendRow(model_root)
        for hw_res in ctrl.claimed_resources:
            hw_iface_item = QStandardItem(hw_res.hardware_interface)
            model_root.appendRow(hw_iface_item)
            for res in hw_res.resources:
                res_item = QStandardItem(res)
                hw_iface_item.appendRow(res_item)

        popup.resource_tree.setModel(res_model)
        popup.resource_tree.setItemDelegate(FontDelegate(popup.resource_tree))
        popup.resource_tree.expandAll()
        popup.move(QCursor.pos())
        popup.show()

    def _on_header_menu(self, pos):
        header = self._widget.table_view.horizontalHeader()

        # Show context menu
        menu = QMenu(self._widget.table_view)
        action_toggle_auto_resize = menu.addAction('Toggle Auto-Resize')
        action = menu.exec_(header.mapToGlobal(pos))

        # Evaluate user action
        if action is action_toggle_auto_resize:
            if header.resizeMode(0) == QHeaderView.ResizeToContents:
                header.setSectionResizeMode(QHeaderView.Interactive)
            else:
                header.setSectionResizeMode(QHeaderView.ResizeToContents)

    def _load_controller(self, name):
        self._load_srv.call(LoadControllerRequest(name=name))

    def _unload_controller(self, name):
        self._unload_srv.call(UnloadControllerRequest(name=name))

    def _start_controller(self, name):
        strict = SwitchControllerRequest.STRICT
        req = SwitchControllerRequest(start_controllers=[name],
                                      stop_controllers=[],
                                      strictness=strict)
        self._switch_srv.call(req)

    def _stop_controller(self, name):
        strict = SwitchControllerRequest.STRICT
        req = SwitchControllerRequest(start_controllers=[],
                                      stop_controllers=[name],
                                      strictness=strict)
        self._switch_srv.call(req)
コード例 #30
0
class LifeFrame(QFrame):
    STATE_STOPPED = "stopped"
    STATE_RUN = "running"
    STATE_IDLE = "idle"

    def __init__(self, parent=None):
        super(LifeFrame, self).__init__(parent)
        self._ui = Ui_life_frame()
        self._motion = Rotate('/mobile_base/commands/velocity')
        self._motion_thread = None
        self._timer = QTimer()
        #self._timer.setInterval(60000) #60s
        self._timer.setInterval(250)  #60s
        QObject.connect(self._timer, SIGNAL('timeout()'), self,
                        SLOT('update_progress_callback()'))
        self._state = LifeFrame.STATE_STOPPED
        self._is_alive = False  # Used to indicate whether the frame is alive or not (see hibernate/restore methods)

    def setupUi(self):
        self._ui.setupUi(self)
        self._ui.start_button.setEnabled(True)
        self._ui.stop_button.setEnabled(False)
        self._motion.init(self._ui.angular_speed_spinbox.value())

    def shutdown(self):
        '''
          Used to terminate the plugin
        '''
        rospy.loginfo("Kobuki TestSuite: life frame shutdown")
        self._motion.shutdown()

    ##########################################################################
    # Widget Management
    ##########################################################################

    def hibernate(self):
        '''
          This gets called when the frame goes out of focus (tab switch). 
          Disable everything to avoid running N tabs in parallel when in
          reality we are only running one.
        '''
        pass

    def restore(self):
        '''
          Restore the frame after a hibernate.
        '''
        pass

    ##########################################################################
    # Motion Callbacks
    ##########################################################################

    def start(self):
        self._state = LifeFrame.STATE_RUN
        self._ui.run_progress.reset()
        self._ui.idle_progress.reset()
        self._motion_thread = WorkerThread(self._motion.execute, None)
        self._motion_thread.start()

    def stop(self):
        self._state = LifeFrame.STATE_STOPPED
        self._motion.stop()
        if self._motion_thread:
            self._motion_thread.wait()

    ##########################################################################
    # Qt Callbacks
    ##########################################################################
    @Slot()
    def on_start_button_clicked(self):
        self._ui.start_button.setEnabled(False)
        self._ui.stop_button.setEnabled(True)
        self._timer.start()
        self.start()

    @Slot()
    def on_stop_button_clicked(self):
        self.stop()
        self._timer.stop()
        self._ui.start_button.setEnabled(True)
        self._ui.stop_button.setEnabled(False)

    @pyqtSlot(float)
    def on_angular_speed_spinbox_valueChanged(self, value):
        self._motion.init(self._ui.angular_speed_spinbox.value())

    ##########################################################################
    # Timer Callbacks
    ##########################################################################

    @Slot()
    def update_progress_callback(self):
        if self._state == LifeFrame.STATE_RUN:
            new_value = self._ui.run_progress.value() + 1
            if new_value == self._ui.run_progress.maximum():
                print("  Switching to idle")
                self._motion.stop()
                self._state = LifeFrame.STATE_IDLE
            else:
                self._ui.run_progress.setValue(new_value)
        if self._state == LifeFrame.STATE_IDLE:
            new_value = self._ui.idle_progress.value() + 1
            if new_value == self._ui.idle_progress.maximum():
                print("  Switching to run")
                self.start()
            else:
                self._ui.idle_progress.setValue(new_value)
コード例 #31
0
class JointTrajectoryController(Plugin):
    """
    Graphical frontend for a C{JointTrajectoryController}.

    There are two modes for interacting with a controller:
        1. B{Monitor mode} Joint displays are updated with the state reported
          by the controller. This is a read-only mode and does I{not} send
          control commands. Every time a new controller is selected, it starts
          in monitor mode for safety reasons.

        2. B{Control mode} Joint displays update the control command that is
        sent to the controller. Commands are sent periodically evan if the
        displays are not being updated by the user.

    To control the aggressiveness of the motions, the maximum speed of the
    sent commands can be scaled down using the C{Speed scaling control}

    This plugin is able to detect and keep track of all active controller
    managers, as well as the JointTrajectoryControllers that are I{running}
    in each controller manager.
    For a controller to be compatible with this plugin, it must comply with
    the following requisites:
        - The controller type contains the C{JointTrajectoryController}
        substring, e.g., C{position_controllers/JointTrajectoryController}
        - The controller exposes the C{command} and C{state} topics in its
        ROS interface.

    Additionally, there must be a URDF loaded with a valid joint limit
    specification, namely position (when applicable) and velocity limits.

    A reference implementation of the C{JointTrajectoryController} is
    available in the C{joint_trajectory_controller} ROS package.
    """
    _cmd_pub_freq = 10.0  # Hz
    _widget_update_freq = 30.0  # Hz
    _ctrlrs_update_freq = 1  # Hz
    _min_traj_dur = 5.0 / _cmd_pub_freq  # Minimum trajectory duration

    jointStateChanged = Signal([dict])

    def __init__(self, context):
        super(JointTrajectoryController, self).__init__(context)
        self.setObjectName('JointTrajectoryController')

        # Create QWidget and extend it with all the attributes and children
        # from the UI file
        self._widget = QWidget()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_joint_trajectory_controller'),
                               'resource', 'joint_trajectory_controller.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('JointTrajectoryControllerUi')

        # Setup speed scaler
        speed_scaling = DoubleEditor(1.0, 100.0)
        speed_scaling.spin_box.setSuffix('%')
        speed_scaling.spin_box.setValue(50.0)
        speed_scaling.spin_box.setDecimals(0)
        speed_scaling.setEnabled(False)
        self._widget.speed_scaling_layout.addWidget(speed_scaling)
        self._speed_scaling_widget = speed_scaling
        speed_scaling.valueChanged.connect(self._on_speed_scaling_change)
        self._on_speed_scaling_change(speed_scaling.value())

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Initialize members
        self._jtc_name = []  # Name of selected joint trajectory controller
        self._cm_ns = []  # Namespace of the selected controller manager
        self._joint_pos = {}  # name->pos map for joints of selected controller
        self._joint_names = []  # Ordered list of selected controller joints
        self._robot_joint_limits = get_joint_limits()  # For all robot joints

        # Timer for sending commands to active controller
        self._update_cmd_timer = QTimer(self)
        self._update_cmd_timer.setInterval(1000.0 / self._cmd_pub_freq)
        self._update_cmd_timer.timeout.connect(self._update_cmd_cb)

        # Timer for updating the joint widgets from the controller state
        self._update_act_pos_timer = QTimer(self)
        self._update_act_pos_timer.setInterval(1000.0 /
                                               self._widget_update_freq)
        self._update_act_pos_timer.timeout.connect(self._update_joint_widgets)

        # Timer for controller manager updates
        self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 /
                                               self._ctrlrs_update_freq)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        # Timer for running controller updates
        self._update_jtc_list_timer = QTimer(self)
        self._update_jtc_list_timer.setInterval(1000.0 /
                                                self._ctrlrs_update_freq)
        self._update_jtc_list_timer.timeout.connect(self._update_jtc_list)
        self._update_jtc_list_timer.start()

        # Signal connections
        w = self._widget
        w.enable_button.toggled.connect(self._on_jtc_enabled)
        w.jtc_combo.currentIndexChanged[str].connect(self._on_jtc_change)
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

        self._cmd_pub = None  # Controller command publisher
        self._state_sub = None  # Controller state subscriber

        self._list_controllers = None

    def shutdown_plugin(self):
        self._update_cmd_timer.stop()
        self._update_act_pos_timer.stop()
        self._update_cm_list_timer.stop()
        self._update_jtc_list_timer.stop()
        self._unregister_state_sub()
        self._unregister_cmd_pub()

    def save_settings(self, plugin_settings, instance_settings):
        # TODO save intrinsic configuration, usually using:
        # instance_settings.set_value(k, v)
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass

    # def trigger_configuration(self):
    # Comment in to signal that the plugin has a way to configure
    # This will enable a setting button (gear icon) in each dock widget
    # title bar
    # Usually used to open a modal configuration dialog

    def _update_cm_list(self):
        update_combo(self._widget.cm_combo, self._list_cm())

    def _update_jtc_list(self):
        # Clear controller list if no controller information is available
        if not self._list_controllers:
            self._widget.jtc_combo.clear()
            return

        # Update widget
        running_jtc = self._running_jtc_info()
        running_jtc_names = [data.name for data in running_jtc]
        update_combo(self._widget.jtc_combo, sorted(running_jtc_names))

    def _on_speed_scaling_change(self, val):
        self._speed_scale = val / self._speed_scaling_widget.slider.maximum()

    def _on_joint_state_change(self, actual_pos):
        assert (len(actual_pos) == len(self._joint_pos))
        for name in actual_pos.keys():
            self._joint_pos[name]['position'] = actual_pos[name]

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns
        if cm_ns:
            self._list_controllers = ControllerLister(cm_ns)
            self._update_jtc_list()
        else:
            self._list_controllers = None

    def _on_jtc_change(self, jtc_name):
        self._unload_jtc()
        self._jtc_name = jtc_name
        if self._jtc_name:
            self._load_jtc()

    def _on_jtc_enabled(self, val):
        # Don't allow enabling if there are no controllers selected
        if not self._jtc_name:
            self._widget.enable_button.setChecked(False)
            return

        # Enable/disable joint displays
        for joint_widget in self._joint_widgets():
            joint_widget.setEnabled(val)

        # Enable/disable speed scaling
        self._speed_scaling_widget.setEnabled(val)

        if val:
            # Widgets send desired position commands to controller
            self._update_act_pos_timer.stop()
            self._update_cmd_timer.start()
        else:
            # Controller updates widgets with actual position
            self._update_cmd_timer.stop()
            self._update_act_pos_timer.start()

    def _load_jtc(self):
        # Initialize joint data corresponding to selected controller
        running_jtc = self._running_jtc_info()
        self._joint_names = next(x.resources for x in running_jtc
                                 if x.name == self._jtc_name)
        for name in self._joint_names:
            self._joint_pos[name] = {}

        # Update joint display
        try:
            layout = self._widget.joint_group.layout()
            for name in self._joint_names:
                limits = self._robot_joint_limits[name]
                joint_widget = DoubleEditor(limits['min_position'],
                                            limits['max_position'])
                layout.addRow(name, joint_widget)
                # NOTE: Using partial instead of a lambda because lambdas
                # "will not evaluate/look up the argument values before it is
                # effectively called, breaking situations like using a loop
                # variable inside it"
                from functools import partial
                par = partial(self._update_single_cmd_cb, name=name)
                joint_widget.valueChanged.connect(par)
        except:
            # TODO: Can we do better than swallow the exception?
            from sys import exc_info
            print 'Unexpected error:', exc_info()[0]

        # Enter monitor mode (sending commands disabled)
        self._on_jtc_enabled(False)

        # Setup ROS interfaces
        jtc_ns = _resolve_controller_ns(self._cm_ns, self._jtc_name)
        state_topic = jtc_ns + '/state'
        cmd_topic = jtc_ns + '/command'
        self._state_sub = rospy.Subscriber(state_topic,
                                           JointTrajectoryControllerState,
                                           self._state_cb,
                                           queue_size=1)
        self._cmd_pub = rospy.Publisher(cmd_topic,
                                        JointTrajectory,
                                        queue_size=1)

        # Start updating the joint positions
        self.jointStateChanged.connect(self._on_joint_state_change)

    def _unload_jtc(self):
        # Stop updating the joint positions
        try:
            self.jointStateChanged.disconnect(self._on_joint_state_change)
        except:
            pass

        # Reset ROS interfaces
        self._unregister_state_sub()
        self._unregister_cmd_pub()

        # Clear joint widgets
        # NOTE: Implementation is a workaround for:
        # https://bugreports.qt-project.org/browse/QTBUG-15990 :(
        layout = self._widget.joint_group.layout()
        if layout is not None:
            while layout.count():
                layout.takeAt(0).widget().deleteLater()
            # Delete existing layout by reparenting to temporary
            QWidget().setLayout(layout)
        self._widget.joint_group.setLayout(QFormLayout())

        # Reset joint data
        self._joint_names = []
        self._joint_pos = {}

        # Enforce monitor mode (sending commands disabled)
        self._widget.enable_button.setChecked(False)

    def _running_jtc_info(self):
        from controller_manager_msgs.utils\
            import filter_by_type, filter_by_state

        controller_list = self._list_controllers()
        jtc_list = filter_by_type(controller_list,
                                  'JointTrajectoryController',
                                  match_substring=True)
        running_jtc_list = filter_by_state(jtc_list, 'running')
        return running_jtc_list

    def _unregister_cmd_pub(self):
        if self._cmd_pub is not None:
            self._cmd_pub.unregister()
            self._cmd_pub = None

    def _unregister_state_sub(self):
        if self._state_sub is not None:
            self._state_sub.unregister()
            self._state_sub = None

    def _state_cb(self, msg):
        actual_pos = {}
        for i in range(len(msg.joint_names)):
            joint_name = msg.joint_names[i]
            joint_pos = msg.actual.positions[i]
            actual_pos[joint_name] = joint_pos
        self.jointStateChanged.emit(actual_pos)

    def _update_single_cmd_cb(self, val, name):
        self._joint_pos[name]['command'] = val

    def _update_cmd_cb(self):
        dur = []
        traj = JointTrajectory()
        traj.joint_names = self._joint_names
        point = JointTrajectoryPoint()
        for name in traj.joint_names:
            pos = self._joint_pos[name]['position']
            cmd = pos
            try:
                cmd = self._joint_pos[name]['command']
            except (KeyError):
                pass
            max_vel = self._robot_joint_limits[name]['max_velocity']
            dur.append(max(abs(cmd - pos) / max_vel, self._min_traj_dur))
            point.positions.append(cmd)
        point.time_from_start = rospy.Duration(max(dur) / self._speed_scale)
        traj.points.append(point)

        self._cmd_pub.publish(traj)

    def _update_joint_widgets(self):
        joint_widgets = self._joint_widgets()
        for id in range(len(joint_widgets)):
            joint_name = self._joint_names[id]
            try:
                joint_pos = self._joint_pos[joint_name]['position']
                joint_widgets[id].setValue(joint_pos)
            except (KeyError):
                pass  # Can happen when first connected to controller

    def _joint_widgets(self):  # TODO: Cache instead of compute every time?
        widgets = []
        layout = self._widget.joint_group.layout()
        for row_id in range(layout.rowCount()):
            widgets.append(
                layout.itemAt(row_id, QFormLayout.FieldRole).widget())
        return widgets