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