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 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
00049
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 = {}
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
00156 def _diagnostics_callback(self, message):
00157 with self._mutex:
00158 self._messages.append(message)
00159
00160
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:
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:
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
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:
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
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()))