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()))