runtime_monitor_widget.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # Software License Agreement (BSD License)
00003 #
00004 # Copyright (c) 2012, Willow Garage, Inc.
00005 # All rights reserved.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions
00009 # are met:
00010 #
00011 #  * Redistributions of source code must retain the above copyright
00012 #    notice, this list of conditions and the following disclaimer.
00013 #  * Redistributions in binary form must reproduce the above
00014 #    copyright notice, this list of conditions and the following
00015 #    disclaimer in the documentation and/or other materials provided
00016 #    with the distribution.
00017 #  * Neither the name of the Willow Garage nor the names of its
00018 #    contributors may be used to endorse or promote products derived
00019 #    from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 # POSSIBILITY OF SUCH DAMAGE.
00033 #
00034 import copy
00035 import cStringIO
00036 import os
00037 import rospkg
00038 import threading
00039 
00040 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus
00041 from python_qt_binding import loadUi
00042 from python_qt_binding.QtGui import QIcon, QTreeWidgetItem, QWidget
00043 from python_qt_binding.QtCore import Qt, QTimer, QObject
00044 import rospy
00045 
00046 
00047 class TreeItem(QObject):
00048     ##\param status DiagnosticsStatus : Diagnostic data of item
00049     ##\param tree_node wxTreeItemId : Tree ID of item in display
00050     def __init__(self, status, tree_node):
00051         super(TreeItem, self).__init__()
00052         self.status = status
00053         self.mark = False
00054         self.stale = False
00055         self.tree_node = tree_node
00056 
00057 
00058 class RuntimeMonitorWidget(QWidget):
00059     def __init__(self, topic="diagnostics"):
00060         super(RuntimeMonitorWidget, self).__init__()
00061         rp = rospkg.RosPack()
00062         ui_file = os.path.join(rp.get_path('rqt_runtime_monitor'), 'resource', 'runtime_monitor_widget.ui')
00063         loadUi(ui_file, self)
00064         self.setObjectName('RuntimeMonitorWidget')
00065 
00066         self._mutex = threading.Lock()
00067 
00068         self._error_icon = QIcon.fromTheme('dialog-error')
00069         self._warning_icon = QIcon.fromTheme('dialog-warning')
00070         self._ok_icon = QIcon.fromTheme('dialog-information')
00071 
00072         self._stale_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Stale (0)'])
00073         self._stale_node.setIcon(0, self._error_icon)
00074         self.tree_widget.addTopLevelItem(self._stale_node)
00075 
00076         self._error_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Errors (0)'])
00077         self._error_node.setIcon(0, self._error_icon)
00078         self.tree_widget.addTopLevelItem(self._error_node)
00079 
00080         self._warning_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Warnings (0)'])
00081         self._warning_node.setIcon(0, self._warning_icon)
00082         self.tree_widget.addTopLevelItem(self._warning_node)
00083 
00084         self._ok_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Ok (0)'])
00085         self._ok_node.setIcon(0, self._ok_icon)
00086         self.tree_widget.addTopLevelItem(self._ok_node)
00087         self.tree_widget.itemSelectionChanged.connect(self._refresh_selection)
00088         self.keyPressEvent = self._on_key_press
00089 
00090         self._name_to_item = {}
00091         self._new_errors_callback = None
00092 
00093         self._subscriber = rospy.Subscriber(topic, DiagnosticArray, self._diagnostics_callback)
00094 
00095         self._previous_ros_time = rospy.Time.now()
00096         self._timer = QTimer()
00097         self._timer.timeout.connect(self._on_timer)
00098         self._timer.start(1000)
00099 
00100         self._msg_timer = QTimer()
00101         self._msg_timer.timeout.connect(self._update_messages)
00102         self._msg_timer.start(100)
00103 
00104         self._messages = []
00105         self._used_items = 0
00106 
00107     def __del__(self):
00108         self.shutdown()
00109  
00110     def shutdown(self):
00111         """
00112         Unregisters subscriber and stops timers
00113         """
00114         self._msg_timer.stop()
00115         self._timer.stop()
00116 
00117         if rospy.is_shutdown():
00118             return
00119 
00120         if self._subscriber:
00121             self._subscriber.unregister()
00122             self._subscriber = None
00123 
00124     def change_diagnostic_topic(self, topic):
00125         """
00126         Changes diagnostics topic name. Must be of type diagnostic_msgs/DiagnosticArray
00127         """
00128         if not topic:
00129             self.reset_monitor()
00130             return
00131 
00132         if self._subscriber:
00133             self._subscriber.unregister()
00134             self._subscriber = rospy.Subscriber(str(topic), DiagnosticArray, self._diagnostics_callback)
00135         self.reset_monitor()
00136 
00137     def reset_monitor(self):
00138         """
00139         Removes all values from monitor display, resets buffers
00140         """
00141         self._name_to_item = {}  # Reset all stale topics
00142         self._messages = []
00143         self._clear_tree()
00144 
00145     def _clear_tree(self):
00146         for index in range(self._stale_node.childCount()):
00147             self._stale_node.removeChild(self._stale_node.child(index))
00148         for index in range(self._error_node.childCount()):
00149             self._error_node.removeChild(self._error_node.child(index))
00150         for index in range(self._warning_node.childCount()):
00151             self._warning_node.removeChild(self._warning_node.child(index))
00152         for index in range(self._ok_node.childCount()):
00153             self._ok_node.removeChild(self._ok_node.child(index))
00154         self._update_root_labels()
00155 
00156     # Diagnostics callbacks (subscriber thread)
00157     def _diagnostics_callback(self, message):
00158         with self._mutex:
00159             self._messages.append(message)
00160 
00161     # Update display of messages from main thread
00162     def _update_messages(self):
00163         with self._mutex:
00164             messages = self._messages[:]
00165             self._messages = []
00166 
00167         had_errors = False
00168         for message in messages:
00169             for status in message.status:
00170                 was_selected = False
00171                 if (self._name_to_item.has_key(status.name)):
00172                     item = self._name_to_item[status.name]
00173                     if self.tree_widget.isItemSelected(item.tree_node):
00174                         was_selected = True
00175                     if (item.status.level == DiagnosticStatus.ERROR and status.level != DiagnosticStatus.ERROR):
00176                         had_errors = True
00177                     self._update_item(item, status, was_selected)
00178                 else:
00179                     self._create_item(status, was_selected, True)
00180                     if (status.level == DiagnosticStatus.ERROR):
00181                         had_errors = True
00182 
00183         if (had_errors and self._new_errors_callback != None):
00184             self._new_errors_callback()
00185 
00186         self._update_root_labels()
00187         self.update()
00188         self._refresh_selection()
00189 
00190     def _update_item(self, item, status, was_selected):
00191         change_parent = False
00192         if (item.status.level != status.level):
00193             change_parent = True
00194         if (change_parent):
00195             if (item.status.level == DiagnosticStatus.OK):
00196                 self._ok_node.removeChild(item.tree_node)
00197             elif (item.status.level == DiagnosticStatus.WARN):
00198                 self._warning_node.removeChild(item.tree_node)
00199             elif (item.status.level == -1) or (item.status.level == 3):
00200                 self._stale_node.removeChild(item.tree_node)
00201             else: # ERROR
00202                 self._error_node.removeChild(item.tree_node)
00203 
00204             if (status.level == DiagnosticStatus.OK):
00205                 parent_node = self._ok_node
00206             elif (status.level == DiagnosticStatus.WARN):
00207                 parent_node = self._warning_node
00208             elif (status.level == -1) or (status.level == 3):
00209                 parent_node = self._stale_node
00210             else: # ERROR
00211                 parent_node = self._error_node
00212 
00213             item.tree_node.setText(0, status.name + ": " + status.message)
00214             item.tree_node.setData(0, Qt.UserRole, item)
00215             parent_node.addChild(item.tree_node)
00216 
00217             # expand errors automatically
00218             if (status.level > 1 or status.level == -1):
00219                 parent_node.setExpanded(True)
00220 
00221             parent_node.sortChildren(0, Qt.AscendingOrder)
00222 
00223             if (was_selected):
00224                 self.tree_widget.setCurrentItem(item.tree_node)
00225 
00226         else:
00227             item.tree_node.setText(0, status.name + ": " + status.message)
00228 
00229         item.status = status
00230 
00231         if (was_selected):
00232             self._fillout_info(item.tree_node)
00233 
00234         item.mark = True
00235 
00236     def _create_item(self, status, select, expand_if_error):
00237         if (status.level == DiagnosticStatus.OK):
00238             parent_node = self._ok_node
00239         elif (status.level == DiagnosticStatus.WARN):
00240             parent_node = self._warning_node
00241         elif (status.level == -1) or (status.level == 3):
00242             parent_node = self._stale_node
00243         else: # ERROR
00244             parent_node = self._error_node
00245 
00246         item = TreeItem(status, QTreeWidgetItem(parent_node, [status.name + ": " + status.message]))
00247         item.tree_node.setData(0, Qt.UserRole, item)
00248         parent_node.addChild(item.tree_node)
00249 
00250         self._name_to_item[status.name] = item
00251 
00252         parent_node.sortChildren(0, Qt.AscendingOrder)
00253 
00254         if (select):
00255             item.tree_node.setSelected(True)
00256 
00257         if (expand_if_error and (status.level > 1 or status.level == -1)):
00258             parent_node.setExpanded(True)
00259 
00260         item.mark = True
00261 
00262         return item
00263 
00264     def _fillout_info(self, node):
00265         item = node.data(0, Qt.UserRole)
00266         if not item:
00267             return
00268 
00269         scroll_value = self.html_browser.verticalScrollBar().value()
00270         status = item.status
00271 
00272         s = cStringIO.StringIO()
00273 
00274         s.write("<html><body>")
00275         s.write("<b>Component</b>: %s<br>\n" % (status.name))
00276         s.write("<b>Message</b>: %s<br>\n" % (status.message))
00277         s.write("<b>Hardware ID</b>: %s<br><br>\n\n" % (status.hardware_id))
00278 
00279         s.write('<table border="1" cellpadding="2" cellspacing="0">')
00280         for value in status.values:
00281             value.value = value.value.replace("\n", "<br>")
00282             s.write("<tr><td><b>%s</b></td> <td>%s</td></tr>\n" % (value.key, value.value))
00283 
00284         s.write("</table></body></html>")
00285 
00286         self.html_browser.setHtml(s.getvalue())
00287         if self.html_browser.verticalScrollBar().maximum() < scroll_value:
00288             scroll_value = self.html_browser.verticalScrollBar().maximum()
00289         self.html_browser.verticalScrollBar().setValue(scroll_value)
00290 
00291     def _refresh_selection(self):
00292         current_item = self.tree_widget.selectedItems()
00293         if current_item:
00294             self._fillout_info(current_item[0])
00295 
00296     def _on_key_press(self, event):
00297         key = event.key()
00298         if key == Qt.Key_Delete:
00299             nodes = self.tree_widget.selectedItems()
00300             if (nodes != [] and nodes[0] not in (self._ok_node, self._warning_node, self._stale_node, self._error_node)):
00301                 item = nodes[0].data(0, Qt.UserRole)
00302                 if (item.status.level == 0):
00303                     self._ok_node.removeChild(item.tree_node)
00304                 elif (item.status.level == 1):
00305                     self._warning_node.removeChild(item.tree_node)
00306                 elif (item.status.level == -1) or (item.status.level == 3):
00307                     self._stale_node.removeChild(item.tree_node)
00308                 else:
00309                     self._error_node.removeChild(item.tree_node)
00310                 del self._name_to_item[item.status.name]
00311             self._update_root_labels()
00312             self.update()
00313             event.accept()
00314         else:
00315             event.ignore()
00316 
00317     def _on_timer(self):
00318         if self._previous_ros_time + rospy.Duration(5) > rospy.Time.now():
00319             return
00320         self._previous_ros_time = rospy.Time.now()
00321         for name, item in self._name_to_item.iteritems():
00322             node = item.tree_node
00323             if (item != None):
00324                 if (not item.mark):
00325                     was_selected = False
00326                     selected = self.tree_widget.selectedItems()
00327                     if selected != [] and selected[0] == node:
00328                         was_selected = True
00329 
00330                     new_status = copy.deepcopy(item.status)
00331                     new_status.level = -1 # mark stale
00332                     self._update_item(item, new_status, was_selected)
00333                 item.mark = False
00334         self._update_root_labels()
00335         self.update()
00336 
00337     def set_new_errors_callback(self, callback):
00338         self._new_errors_callback = callback
00339 
00340     def _update_root_labels(self):
00341         self._stale_node.setText(0, "Stale (%s)" % (self._stale_node.childCount()))
00342         self._error_node.setText(0, "Errors (%s)" % (self._error_node.childCount()))
00343         self._warning_node.setText(0, "Warnings (%s)" % (self._warning_node.childCount()))
00344         self._ok_node.setText(0, "Ok (%s)" % (self._ok_node.childCount()))


rqt_runtime_monitor
Author(s): Aaron Blasdel
autogenerated on Fri Aug 28 2015 12:50:39