robot_monitor.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2012, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 #
00033 # Author: Isaac Saito, Ze'ev Klapow, Austin Hendrix
00034 
00035 import os
00036 import rospkg
00037 
00038 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus
00039 from python_qt_binding import loadUi
00040 from python_qt_binding.QtCore import QTimer, Signal, Qt, Slot
00041 from python_qt_binding.QtGui import QPalette
00042 from python_qt_binding.QtWidgets import QWidget, QTreeWidgetItem
00043 import rospy
00044 
00045 from rqt_robot_monitor.inspector_window import InspectorWindow
00046 from rqt_robot_monitor.status_item import StatusItem
00047 from rqt_robot_monitor.timeline_pane import TimelinePane
00048 from rqt_robot_monitor.timeline import Timeline
00049 import rqt_robot_monitor.util_robot_monitor as util
00050 
00051 
00052 class RobotMonitorWidget(QWidget):
00053     """
00054     NOTE: RobotMonitorWidget.shutdown function needs to be called
00055     when the instance of this class terminates.
00056 
00057     RobotMonitorWidget itself doesn't store previous diagnostic states.
00058     It instead delegates that function to Timeline class.
00059     """
00060 
00061     _TREE_ALL = 1
00062     _TREE_WARN = 2
00063     _TREE_ERR = 3
00064 
00065     _message_updated = Signal(dict)
00066     _queue_updated = Signal()
00067 
00068     def __init__(self, context, topic=None):
00069         """
00070         :param context: plugin context hook to enable adding widgets as a
00071                         ROS_GUI pane, 'PluginContext'
00072         :param topic: Diagnostic topic to subscribe to 'str'
00073         """
00074 
00075         super(RobotMonitorWidget, self).__init__()
00076         rp = rospkg.RosPack()
00077         ui_file = os.path.join(rp.get_path('rqt_robot_monitor'), 'resource',
00078                                'robotmonitor_mainwidget.ui')
00079         loadUi(ui_file, self)
00080 
00081         obj_name = 'Robot Monitor'
00082         self.setObjectName(obj_name)
00083         self.setWindowTitle(obj_name)
00084 
00085         self._message_updated_processing = False
00086         self._queue_updated_processing = False
00087 
00088         # if we're given a topic, create a timeline. otherwise, don't
00089         #  this can be used later when writing an rqt_bag plugin
00090         if topic:
00091             # create timeline data structure
00092             self._timeline = Timeline(topic, DiagnosticArray)
00093             self._timeline.message_updated.connect(
00094                 self.message_updated, Qt.DirectConnection)
00095             self._timeline.queue_updated.connect(
00096                 self.queue_updated, Qt.DirectConnection)
00097             self._message_updated.connect(
00098                 self._signal_message_updated, Qt.QueuedConnection)
00099             self._queue_updated.connect(
00100                 self._signal_queue_updated, Qt.QueuedConnection)
00101 
00102             # create timeline pane widget
00103             self._timeline_pane = TimelinePane(self, self._timeline.paused)
00104             self._timeline_pane.pause_changed.connect(self._timeline.set_paused)
00105             self._timeline_pane.position_changed.connect(self._timeline.set_position)
00106             self._timeline.pause_changed.connect(self._timeline_pane.set_paused)
00107             self._timeline.position_changed.connect(self._timeline_pane.set_position)
00108 
00109             self.vlayout_top.addWidget(self._timeline_pane)
00110             self._timeline_pane.show()
00111         else:
00112             self._timeline = None
00113             self._timeline_pane = None
00114 
00115         self._inspectors = {}
00116 
00117         self.tree_all_devices.itemDoubleClicked.connect(self._tree_clicked)
00118         self.warn_flattree.itemDoubleClicked.connect(self._tree_clicked)
00119         self.err_flattree.itemDoubleClicked.connect(self._tree_clicked)
00120         # TODO: resize on itemCollapsed and itemExpanded
00121 
00122         self._is_stale = False
00123 
00124         self._timer = QTimer()
00125         self._timer.timeout.connect(self._update_message_state)
00126         self._timer.start(1000)
00127 
00128         palette = self.tree_all_devices.palette()
00129         self._original_base_color = palette.base().color()
00130         self._original_alt_base_color = palette.alternateBase().color()
00131 
00132         self._tree = StatusItem(self.tree_all_devices.invisibleRootItem())
00133         self._warn_tree = StatusItem(self.warn_flattree.invisibleRootItem())
00134         self._err_tree = StatusItem(self.err_flattree.invisibleRootItem())
00135 
00136     @Slot(dict)
00137     def message_updated(self, status):
00138         '''
00139         This method just calls _signal_message_updated in 'best effort' manner.
00140         This method should be called by signal with DirectConnection.
00141         '''
00142         if self._message_updated_processing:
00143             return
00144         self._message_updated_processing = True
00145         self._message_updated.emit(status)
00146 
00147     @Slot(dict)
00148     def _signal_message_updated(self, status):
00149         """ DiagnosticArray message callback """
00150 
00151         # Walk the status array and update the tree
00152         for name, status in status.items():
00153             # Compute path and walk to appropriate subtree
00154             path = name.split('/')
00155             if path[0] == '':
00156                 path = path[1:]
00157             tmp_tree = self._tree
00158             for p in path:
00159                 tmp_tree = tmp_tree[p]
00160             tmp_tree.update(status, util.get_resource_name(name))
00161 
00162             # Check for warnings
00163             if status.level == DiagnosticStatus.WARN:
00164                 self._warn_tree[name].update(status, name)
00165 
00166             # Check for errors
00167             if (status.level == DiagnosticStatus.ERROR or
00168                 status.level == DiagnosticStatus.STALE):
00169                 self._err_tree[name].update(status, name)
00170 
00171         # For any items in the tree that were not updated, remove them
00172         self._tree.prune()
00173         self._warn_tree.prune()
00174         self._err_tree.prune()
00175 
00176         # TODO(ahendrix): implement
00177         # Insight: for any item that is not OK, it only provides additional
00178         #          information if all of it's children are OK
00179         #
00180         #          otherwise, it's just an aggregation of its children
00181         #          and doesn't provide any additional value when added to
00182         #          the warning and error flat trees
00183 
00184         self.tree_all_devices.resizeColumnToContents(0)
00185         self.warn_flattree.resizeColumnToContents(0)
00186         self.err_flattree.resizeColumnToContents(0)
00187 
00188         self._message_updated_processing = False
00189 
00190     @Slot()
00191     def queue_updated(self):
00192         '''
00193         This method just calls _signal_queue_updated in 'best effort' manner.
00194         This method should be called by signal with DirectConnection.
00195         '''
00196         if self._queue_updated_processing:
00197             return
00198         self._queue_updated_processing = True
00199         self._queue_updated.emit()
00200 
00201     @Slot()
00202     def _signal_queue_updated(self):
00203         # update timeline pane
00204         # collect worst status levels for each history
00205         levels = [max([s.level for s in s.values()]) for s in self._timeline]
00206         self._timeline_pane.set_levels(levels)
00207         self._timeline_pane.redraw.emit()
00208         self._queue_updated_processing = False
00209 
00210     def resizeEvent(self, evt):
00211         """Overridden from QWidget"""
00212         rospy.logdebug('RobotMonitorWidget resizeEvent')
00213         if self._timeline_pane:
00214             self._timeline_pane.redraw.emit()
00215 
00216     @Slot(str)
00217     def _inspector_closed(self, name):
00218         """ Called when an inspector window is closed """
00219         if name in self._inspectors:
00220             del self._inspectors[name]
00221 
00222     @Slot(QTreeWidgetItem, int)
00223     def _tree_clicked(self, item, column):
00224         """
00225         Slot to QTreeWidget.itemDoubleClicked
00226 
00227         :type item: QTreeWidgetItem
00228         :type column: int
00229         """
00230         rospy.logdebug('RobotMonitorWidget _tree_clicked col=%d', column)
00231 
00232         if item.name in self._inspectors:
00233             self._inspectors[item.name].activateWindow()
00234         else:
00235             insp = InspectorWindow(None, item.name, self._timeline)
00236             insp.show()
00237             insp.closed.connect(self._inspector_closed)
00238             self._inspectors[item.name] = insp
00239 
00240     def _update_message_state(self):
00241         """ Update the display if it's stale """
00242         if self._timeline is not None:
00243             if self._timeline.has_messages:
00244                 previous_stale_state = self._is_stale
00245                 self._is_stale = self._timeline.is_stale
00246 
00247                 time_diff = int(self._timeline.data_age)
00248 
00249                 msg_template = "Last message received %s %s ago"
00250                 if time_diff == 1:
00251                     msg = msg_template % (time_diff, "second")
00252                 else:
00253                     msg = msg_template % (time_diff, "seconds")
00254                 self._timeline_pane._msg_label.setText(msg)
00255                 if previous_stale_state != self._is_stale:
00256                     self._update_background_color()
00257             else:
00258                 # no messages received yet
00259                 self._timeline_pane._msg_label.setText("No messages received")
00260 
00261     def _update_background_color(self):
00262         """ Update the background color based on staleness """
00263         p = self.tree_all_devices.palette()
00264         if self._is_stale:
00265             p.setColor(QPalette.Base, Qt.darkGray)
00266             p.setColor(QPalette.AlternateBase, Qt.lightGray)
00267         else:
00268             p.setColor(QPalette.Base, self._original_base_color)
00269             p.setColor(QPalette.AlternateBase, self._original_alt_base_color)
00270         self.tree_all_devices.setPalette(p)
00271         self.warn_flattree.setPalette(p)
00272         self.err_flattree.setPalette(p)
00273 
00274     def shutdown(self):
00275         """
00276         This needs to be called whenever this class terminates.
00277         This closes all the instances on all trees.
00278         Also unregisters ROS' subscriber, stops timer.
00279         """
00280         rospy.logdebug('RobotMonitorWidget in shutdown')
00281 
00282         names = self._inspectors.keys()
00283         for name in names:
00284             self._inspectors[name].close()
00285 
00286         if self._timeline:
00287             self._timeline.shutdown()
00288 
00289         self._timer.stop()
00290         del self._timer
00291 
00292     def save_settings(self, plugin_settings, instance_settings):
00293         instance_settings.set_value('splitter', self.splitter.saveState())
00294         # TODO(ahendrix): persist the device paths, positions and sizes of any
00295         #                 inspector windows
00296 
00297     def restore_settings(self, plugin_settings, instance_settings):
00298         if instance_settings.contains('splitter'):
00299             self.splitter.restoreState(instance_settings.value('splitter'))
00300         else:
00301             self.splitter.setSizes([100, 100, 200])
00302         # TODO(ahendrix): restore inspector windows


rqt_robot_monitor
Author(s): Austin Hendrix, Isaac Saito, Ze'ev Klapow, Kevin Watts, Josh Faust
autogenerated on Thu May 16 2019 03:10:09