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


rqt_runtime_monitor
Author(s): Aaron Blasdel
autogenerated on Mon May 1 2017 02:35:23