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 roslib
00035 import rospy
00036 import os
00037 import sys
00038 
00039 from python_qt_binding import loadUi
00040 from python_qt_binding.QtGui import QIcon, QTreeWidgetItem, QWidget
00041 from python_qt_binding.QtCore import Qt, QTimer, QObject
00042 
00043 from diagnostic_msgs.msg import DiagnosticArray
00044 
00045 import threading, time
00046 import cStringIO
00047 import copy
00048 
00049 
00050 class TreeItem(QObject):
00051     ##\param status DiagnosticsStatus : Diagnostic data of item
00052     ##\param tree_node wxTreeItemId : Tree ID of item in display
00053     ##\param stamp ros::Time : Stamp when message was received
00054     def __init__(self, status, tree_node, stamp=None):
00055         super(TreeItem, self).__init__()
00056         self.status = status
00057         self.mark = False
00058         self.stale = False
00059         self.tree_node = tree_node
00060         self.stamp = stamp
00061 
00062 
00063 class RuntimeMonitorWidget(QWidget):
00064     def __init__(self, topic="/diagnostics"):
00065         super(RuntimeMonitorWidget, self).__init__()
00066         ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'runtime_monitor_widget.ui')
00067         loadUi(ui_file, self)
00068         self.setObjectName('RuntimeMonitorWidget')
00069 
00070         self._mutex = threading.Lock()
00071     
00072         self._error_icon = QIcon.fromTheme('dialog-error')
00073         self._warning_icon = QIcon.fromTheme('dialog-warning')
00074         self._ok_icon = QIcon.fromTheme('dialog-information')
00075 
00076         self._stale_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Stale (0)'])
00077         self._stale_node.setIcon(0, self._error_icon)
00078         self.tree_widget.addTopLevelItem(self._stale_node)
00079 
00080         self._error_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Errors (0)'])
00081         self._error_node.setIcon(0, self._error_icon)
00082         self.tree_widget.addTopLevelItem(self._error_node)
00083 
00084         self._warning_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Warnings (0)'])
00085         self._warning_node.setIcon(0, self._warning_icon)
00086         self.tree_widget.addTopLevelItem(self._warning_node)
00087 
00088         self._ok_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Ok (0)'])
00089         self._ok_node.setIcon(0, self._ok_icon)
00090         self.tree_widget.addTopLevelItem(self._ok_node)
00091         self.tree_widget.itemSelectionChanged.connect(self._on_item_selected)
00092         self.keyPressEvent = self._on_key_press
00093 
00094         self._name_to_item = {}
00095         self._new_errors_callback = None
00096 
00097         self._subscriber = rospy.Subscriber(topic, DiagnosticArray, self._diagnostics_callback)
00098         self._timer = QTimer()
00099         self._timer.timeout.connect(self._on_timer)
00100         self._timer.start(5000)
00101 
00102         self._messages = []
00103         self._used_items = 0
00104 
00105     def __del__(self):
00106         if self._subscriber is not None:
00107             self._subscriber.unregister()
00108             self._subscriber = None
00109 
00110     def shutdown(self):
00111         """
00112         Unregisters diagnostics subscriber for clean shutdown
00113         """
00114         if rospy.is_shutdown():
00115             return
00116 
00117         if self._subscriber is not None:
00118             self._subscriber.unregister()
00119             self._subscriber = None
00120 
00121     def change_diagnostic_topic(self, topic):
00122         """
00123         Changes diagnostics topic name. Must be of type diagnostic_msgs/DiagnosticArray
00124         """
00125         if len(topic) == 0:
00126             self.reset_monitor()
00127             return
00128 
00129         if self._subscriber is not None:
00130             self._subscriber.unregister()
00131             self._subscriber = rospy.Subscriber(str(topic), DiagnosticArray, self._diagnostics_callback)
00132         self.reset_monitor()
00133 
00134     def reset_monitor(self):
00135         """
00136         Removes all values from monitor display, resets buffers
00137         """
00138         self._name_to_item = {}  # Reset all stale topics
00139         self._messages = []
00140         self._clear_tree()
00141 
00142     def _clear_tree(self):
00143         for index in range(self._stale_node.childCount()):
00144             self._stale_node.removeChild(self._stale_node.child(index))
00145         for index in range(self._error_node.childCount()):
00146             self._error_node.removeChild(self._error_node.child(index))
00147         for index in range(self._warning_node.childCount()):
00148             self._warning_node.removeChild(self._warning_node.child(index))
00149         for index in range(self._ok_node.childCount()):
00150             self._ok_node.removeChild(self._ok_node.child(index))
00151         self._update_root_labels()
00152 
00153     def _diagnostics_callback(self, message):
00154         with self._mutex:
00155             self._messages.append(message)
00156 
00157         self.new_message(rospy.get_rostime())
00158 
00159     def new_message(self, stamp=None):
00160         with self._mutex:
00161             had_errors = False
00162 
00163             for message in self._messages:
00164                 for status in message.status:
00165                     was_selected = False
00166                     if (self._name_to_item.has_key(status.name)):
00167                         item = self._name_to_item[status.name]
00168                         if self.tree_widget.selectedItems() != [] and self.tree_widget.selectedItems()[0] == item:
00169                             was_selected = True
00170                         if (item.status.level == 2 and status.level != 2):
00171                             had_errors = True
00172                         self._update_item(item, status, was_selected, stamp)
00173                     else:
00174                         self._create_item(status, was_selected, True, stamp)
00175                         if (status.level == 2):
00176                             had_errors = True
00177             self._messages = []
00178 
00179         if (had_errors and self._new_errors_callback != None):
00180             self._new_errors_callback()
00181 
00182         self._update_root_labels()
00183         self.update()
00184 
00185     def _update_item(self, item, status, was_selected, stamp):
00186         change_parent = False
00187         if (item.status.level != status.level):
00188             change_parent = True
00189         if (change_parent):
00190             if (item.status.level == 0):
00191                 self._ok_node.removeChild(item.tree_node)
00192             elif (item.status.level == 1):
00193                 self._warning_node.removeChild(item.tree_node)
00194             elif (item.status.level == -1):
00195                 self._stale_node.removeChild(item.tree_node)
00196             else:
00197                 self._error_node.removeChild(item.tree_node)
00198 
00199             if (status.level == 0):
00200                 parent_node = self._ok_node
00201             elif (status.level == 1):
00202                 parent_node = self._warning_node
00203             elif (status.level == -1):
00204                 parent_node = self._stale_node
00205             else:
00206                 parent_node = self._error_node
00207 
00208             item.tree_node.setText(0, status.name + ": " + status.message)
00209             item.stamp = stamp
00210             item.tree_node.setData(0, Qt.UserRole, item)
00211             parent_node.addChild(item.tree_node)
00212 
00213             if (status.level > 1 or status.level == -1):
00214                 parent_node.setExpanded(True)
00215 
00216             parent_node.sortChildren(0, Qt.AscendingOrder)
00217 
00218             if (was_selected):
00219                 item.tree_node.setSelected(True)
00220 
00221         else:
00222             item.tree_node.setText(0, status.name + ": " + status.message)
00223 
00224         item.status = status
00225 
00226         if (was_selected):
00227             self._fillout_info(item.tree_node)
00228 
00229         item.mark = True
00230 
00231     def _create_item(self, status, select, expand_if_error, stamp):
00232         if (status.level == 0):
00233             parent_node = self._ok_node
00234         elif (status.level == 1):
00235             parent_node = self._warning_node
00236         elif (status.level == -1):
00237             parent_node = self._stale_node
00238         else:
00239             parent_node = self._error_node
00240 
00241         item = TreeItem(status, QTreeWidgetItem(parent_node, [status.name + ": " + status.message]), stamp)
00242         item.tree_node.setData(0, Qt.UserRole, item)
00243         parent_node.addChild(item.tree_node)
00244 
00245         self._name_to_item[status.name] = item
00246 
00247         parent_node.sortChildren(0, Qt.AscendingOrder)
00248 
00249         if (select):
00250             item.tree_node.setSelected(True)
00251 
00252         if (expand_if_error and (status.level > 1 or status.level == -1)):
00253             parent_node.setExpanded(True)
00254 
00255         item.mark = True
00256 
00257         return item
00258 
00259     def _fillout_info(self, node):
00260         item = node.data(0, Qt.UserRole)
00261         if (item == None):
00262             return
00263 
00264         status = item.status
00265 
00266         s = cStringIO.StringIO()
00267 
00268         s.write("<html><body>")
00269         s.write("<b>Component</b>: %s<br>\n" % (status.name))
00270         s.write("<b>Message</b>: %s<br>\n" % (status.message))
00271         s.write("<b>Hardware ID</b>: %s<br><br>\n\n" % (status.hardware_id))
00272 
00273         s.write('<table border="1" cellpadding="2" cellspacing="0">')
00274         for value in status.values:
00275             value.value = value.value.replace("\n", "<br>")
00276             s.write("<tr><td><b>%s</b></td> <td>%s</td></tr>\n" % (value.key, value.value))
00277 
00278         s.write("</table></body></html>")
00279 
00280         self.html_browser.setHtml(s.getvalue())
00281 
00282     def _on_item_selected(self):
00283         current_item = self.tree_widget.selectedItems()
00284         if current_item is not None:
00285             self._fillout_info(current_item[0])
00286 
00287     def _on_key_press(self, event):
00288         key = event.key()
00289         if key == Qt.Key_Delete:
00290             nodes = self.tree_widget.selectedItems()
00291             if (nodes != [] and nodes[0] not in (self._ok_node, self._warning_node, self._stale_node, self._error_node)):
00292                 item = nodes[0].data(0, Qt.UserRole)
00293                 if (item.status.level == 0):
00294                     self._ok_node.removeChild(item.tree_node)
00295                 elif (item.status.level == 1):
00296                     self._warning_node.removeChild(item.tree_node)
00297                 elif (item.status.level == -1):
00298                     self._stale_node.removeChild(item.tree_node)
00299                 else:
00300                     self._error_node.removeChild(item.tree_node)
00301                 del self._name_to_item[item.status.name]
00302         else:
00303             event.Skip()
00304         self._update_root_labels()
00305         self.update()
00306 
00307     def _on_timer(self):
00308         for name, item in self._name_to_item.iteritems():
00309             node = item.tree_node
00310             if (item != None):
00311                 if (not item.mark):
00312                     was_selected = False
00313                     selected = self.tree_widget.selectedItems()
00314                     if selected != [] and selected[0] == node:
00315                         was_selected = True
00316 
00317                     new_status = copy.deepcopy(item.status)
00318                     new_status.level = -1
00319                     self._update_item(item, new_status, was_selected, item.stamp)
00320                 item.mark = False
00321         self._update_root_labels()
00322         self.update()
00323 
00324     def set_new_errors_callback(self, callback):
00325         self._new_errors_callback = callback
00326 
00327     def get_num_errors(self):
00328         return self._error_node.childCount() + self._stale_node.childCount()
00329 
00330     def get_num_warnings(self):
00331         return self._warning_node.childCount()
00332 
00333     def get_num_ok(self):
00334         return self._ok_node.childCount()
00335 
00336     def _update_root_labels(self):
00337         self._stale_node.setText(0, "Stale (%s)" % (self._stale_node.childCount()))
00338         self._error_node.setText(0, "Errors (%s)" % (self._error_node.childCount()))
00339         self._warning_node.setText(0, "Warnings (%s)" % (self._warning_node.childCount()))
00340         self._ok_node.setText(0, "Ok (%s)" % (self._ok_node.childCount()))


rqt_runtime_monitor
Author(s): Aaron Blasdel
autogenerated on Fri Jan 3 2014 11:54:38