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 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
00053
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 = {}
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
00161 def _diagnostics_callback(self, message):
00162 with self._mutex:
00163 self._messages.append(message)
00164
00165
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:
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:
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
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:
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
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()))