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._timer = QTimer()
00096         self._timer.timeout.connect(self._on_timer)
00097         self._timer.start(5000)
00098 
00099         self._msg_timer = QTimer()
00100         self._msg_timer.timeout.connect(self._update_messages)
00101         self._msg_timer.start(100)
00102 
00103         self._messages = []
00104         self._used_items = 0
00105 
00106     def __del__(self):
00107         self.shutdown()
00108  
00109     def shutdown(self):
00110         """
00111         Unregisters subscriber and stops timers
00112         """
00113         self._msg_timer.stop()
00114         self._timer.stop()
00115 
00116         if rospy.is_shutdown():
00117             return
00118 
00119         if self._subscriber:
00120             self._subscriber.unregister()
00121             self._subscriber = None
00122 
00123     def change_diagnostic_topic(self, topic):
00124         """
00125         Changes diagnostics topic name. Must be of type diagnostic_msgs/DiagnosticArray
00126         """
00127         if not topic:
00128             self.reset_monitor()
00129             return
00130 
00131         if self._subscriber:
00132             self._subscriber.unregister()
00133             self._subscriber = rospy.Subscriber(str(topic), DiagnosticArray, self._diagnostics_callback)
00134         self.reset_monitor()
00135 
00136     def reset_monitor(self):
00137         """
00138         Removes all values from monitor display, resets buffers
00139         """
00140         self._name_to_item = {}  # Reset all stale topics
00141         self._messages = []
00142         self._clear_tree()
00143 
00144     def _clear_tree(self):
00145         for index in range(self._stale_node.childCount()):
00146             self._stale_node.removeChild(self._stale_node.child(index))
00147         for index in range(self._error_node.childCount()):
00148             self._error_node.removeChild(self._error_node.child(index))
00149         for index in range(self._warning_node.childCount()):
00150             self._warning_node.removeChild(self._warning_node.child(index))
00151         for index in range(self._ok_node.childCount()):
00152             self._ok_node.removeChild(self._ok_node.child(index))
00153         self._update_root_labels()
00154 
00155     # Diagnostics callbacks (subscriber thread)
00156     def _diagnostics_callback(self, message):
00157         with self._mutex:
00158             self._messages.append(message)
00159 
00160     # Update display of messages from main thread
00161     def _update_messages(self):
00162         with self._mutex:
00163             messages = self._messages[:]
00164             self._messages = []
00165 
00166         had_errors = False
00167         for message in messages:
00168             for status in message.status:
00169                 was_selected = False
00170                 if (self._name_to_item.has_key(status.name)):
00171                     item = self._name_to_item[status.name]
00172                     if self.tree_widget.isItemSelected(item.tree_node):
00173                         was_selected = True
00174                     if (item.status.level == DiagnosticStatus.ERROR and status.level != DiagnosticStatus.ERROR):
00175                         had_errors = True
00176                     self._update_item(item, status, was_selected)
00177                 else:
00178                     self._create_item(status, was_selected, True)
00179                     if (status.level == DiagnosticStatus.ERROR):
00180                         had_errors = True
00181 
00182         if (had_errors and self._new_errors_callback != None):
00183             self._new_errors_callback()
00184 
00185         self._update_root_labels()
00186         self.update()
00187         self._refresh_selection()
00188 
00189     def _update_item(self, item, status, was_selected):
00190         change_parent = False
00191         if (item.status.level != status.level):
00192             change_parent = True
00193         if (change_parent):
00194             if (item.status.level == DiagnosticStatus.OK):
00195                 self._ok_node.removeChild(item.tree_node)
00196             elif (item.status.level == DiagnosticStatus.WARN):
00197                 self._warning_node.removeChild(item.tree_node)
00198             elif (item.status.level == -1): 
00199                 self._stale_node.removeChild(item.tree_node)
00200             else: # ERROR
00201                 self._error_node.removeChild(item.tree_node)
00202 
00203             if (status.level == DiagnosticStatus.OK):
00204                 parent_node = self._ok_node
00205             elif (status.level == DiagnosticStatus.WARN):
00206                 parent_node = self._warning_node
00207             elif (status.level == -1):
00208                 parent_node = self._stale_node
00209             else: # ERROR
00210                 parent_node = self._error_node
00211 
00212             item.tree_node.setText(0, status.name + ": " + status.message)
00213             item.tree_node.setData(0, Qt.UserRole, item)
00214             parent_node.addChild(item.tree_node)
00215 
00216             # expand errors automatically
00217             if (status.level > 1 or status.level == -1):
00218                 parent_node.setExpanded(True)
00219 
00220             parent_node.sortChildren(0, Qt.AscendingOrder)
00221 
00222             if (was_selected):
00223                 self.tree_widget.setCurrentItem(item.tree_node)
00224 
00225         else:
00226             item.tree_node.setText(0, status.name + ": " + status.message)
00227 
00228         item.status = status
00229 
00230         if (was_selected):
00231             self._fillout_info(item.tree_node)
00232 
00233         item.mark = True
00234 
00235     def _create_item(self, status, select, expand_if_error):
00236         if (status.level == DiagnosticStatus.OK):
00237             parent_node = self._ok_node
00238         elif (status.level == DiagnosticStatus.WARN):
00239             parent_node = self._warning_node
00240         elif (status.level == -1):
00241             parent_node = self._stale_node
00242         else: # ERROR
00243             parent_node = self._error_node
00244 
00245         item = TreeItem(status, QTreeWidgetItem(parent_node, [status.name + ": " + status.message]))
00246         item.tree_node.setData(0, Qt.UserRole, item)
00247         parent_node.addChild(item.tree_node)
00248 
00249         self._name_to_item[status.name] = item
00250 
00251         parent_node.sortChildren(0, Qt.AscendingOrder)
00252 
00253         if (select):
00254             item.tree_node.setSelected(True)
00255 
00256         if (expand_if_error and (status.level > 1 or status.level == -1)):
00257             parent_node.setExpanded(True)
00258 
00259         item.mark = True
00260 
00261         return item
00262 
00263     def _fillout_info(self, node):
00264         item = node.data(0, Qt.UserRole)
00265         if not item:
00266             return
00267 
00268         scroll_value = self.html_browser.verticalScrollBar().value()
00269         status = item.status
00270 
00271         s = cStringIO.StringIO()
00272 
00273         s.write("<html><body>")
00274         s.write("<b>Component</b>: %s<br>\n" % (status.name))
00275         s.write("<b>Message</b>: %s<br>\n" % (status.message))
00276         s.write("<b>Hardware ID</b>: %s<br><br>\n\n" % (status.hardware_id))
00277 
00278         s.write('<table border="1" cellpadding="2" cellspacing="0">')
00279         for value in status.values:
00280             value.value = value.value.replace("\n", "<br>")
00281             s.write("<tr><td><b>%s</b></td> <td>%s</td></tr>\n" % (value.key, value.value))
00282 
00283         s.write("</table></body></html>")
00284 
00285         self.html_browser.setHtml(s.getvalue())
00286         if self.html_browser.verticalScrollBar().maximum() < scroll_value:
00287             scroll_value = self.html_browser.verticalScrollBar().maximum()
00288         self.html_browser.verticalScrollBar().setValue(scroll_value)
00289 
00290     def _refresh_selection(self):
00291         current_item = self.tree_widget.selectedItems()
00292         if current_item:
00293             self._fillout_info(current_item[0])
00294 
00295     def _on_key_press(self, event):
00296         key = event.key()
00297         if key == Qt.Key_Delete:
00298             nodes = self.tree_widget.selectedItems()
00299             if (nodes != [] and nodes[0] not in (self._ok_node, self._warning_node, self._stale_node, self._error_node)):
00300                 item = nodes[0].data(0, Qt.UserRole)
00301                 if (item.status.level == 0):
00302                     self._ok_node.removeChild(item.tree_node)
00303                 elif (item.status.level == 1):
00304                     self._warning_node.removeChild(item.tree_node)
00305                 elif (item.status.level == -1):
00306                     self._stale_node.removeChild(item.tree_node)
00307                 else:
00308                     self._error_node.removeChild(item.tree_node)
00309                 del self._name_to_item[item.status.name]
00310             self._update_root_labels()
00311             self.update()
00312             event.accept()
00313         else:
00314             event.ignore()
00315 
00316     def _on_timer(self):
00317         for name, item in self._name_to_item.iteritems():
00318             node = item.tree_node
00319             if (item != None):
00320                 if (not item.mark):
00321                     was_selected = False
00322                     selected = self.tree_widget.selectedItems()
00323                     if selected != [] and selected[0] == node:
00324                         was_selected = True
00325 
00326                     new_status = copy.deepcopy(item.status)
00327                     new_status.level = -1 # mark stale
00328                     self._update_item(item, new_status, was_selected)
00329                 item.mark = False
00330         self._update_root_labels()
00331         self.update()
00332 
00333     def set_new_errors_callback(self, callback):
00334         self._new_errors_callback = callback
00335 
00336     def get_num_errors(self):
00337         return self._error_node.childCount() + self._stale_node.childCount()
00338 
00339     def get_num_warnings(self):
00340         return self._warning_node.childCount()
00341 
00342     def get_num_ok(self):
00343         return self._ok_node.childCount()
00344 
00345     def _update_root_labels(self):
00346         self._stale_node.setText(0, "Stale (%s)" % (self._stale_node.childCount()))
00347         self._error_node.setText(0, "Errors (%s)" % (self._error_node.childCount()))
00348         self._warning_node.setText(0, "Warnings (%s)" % (self._warning_node.childCount()))
00349         self._ok_node.setText(0, "Ok (%s)" % (self._ok_node.childCount()))


rqt_runtime_monitor
Author(s): Aaron Blasdel
autogenerated on Mon Oct 6 2014 07:16:00