00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
00019 
00020 
00021 
00022 
00023 
00024 
00025 
00026 
00027 
00028 
00029 
00030 
00031 
00032 
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     
00052     
00053     
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 = {}  
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()))