runtime_monitor_widget.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Software License Agreement (BSD License)
3 #
4 # Copyright (c) 2012, Willow Garage, Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
10 #
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials provided
16 # with the distribution.
17 # * Neither the name of the Willow Garage nor the names of its
18 # contributors may be used to endorse or promote products derived
19 # from this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 #
34 import copy
35 try:
36  from cStringIO import StringIO
37 except ImportError:
38  from io import StringIO
39 import os
40 import rospkg
41 import threading
42 
43 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus
44 from python_qt_binding import loadUi
45 from python_qt_binding.QtGui import QIcon
46 from python_qt_binding.QtWidgets import QTreeWidgetItem, QWidget
47 from python_qt_binding.QtCore import Qt, QTimer, QObject
48 import rospy
49 
50 
51 class TreeItem(QObject):
52  ##\param status DiagnosticsStatus : Diagnostic data of item
53  ##\param tree_node wxTreeItemId : Tree ID of item in display
54  def __init__(self, status, tree_node):
55  super(TreeItem, self).__init__()
56  self.status = status
57  self.mark = False
58  self.stale = False
59  self.tree_node = tree_node
60 
61 
62 class RuntimeMonitorWidget(QWidget):
63  def __init__(self, topic="diagnostics"):
64  super(RuntimeMonitorWidget, self).__init__()
65  rp = rospkg.RosPack()
66  ui_file = os.path.join(rp.get_path('rqt_runtime_monitor'), 'resource', 'runtime_monitor_widget.ui')
67  loadUi(ui_file, self)
68  self.setObjectName('RuntimeMonitorWidget')
69 
70  self._mutex = threading.Lock()
71 
72  self._error_icon = QIcon.fromTheme('dialog-error')
73  self._warning_icon = QIcon.fromTheme('dialog-warning')
74  self._ok_icon = QIcon.fromTheme('dialog-information')
75 
76  self._stale_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Stale (0)'])
77  self._stale_node.setIcon(0, self._error_icon)
78  self.tree_widget.addTopLevelItem(self._stale_node)
79 
80  self._error_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Errors (0)'])
81  self._error_node.setIcon(0, self._error_icon)
82  self.tree_widget.addTopLevelItem(self._error_node)
83 
84  self._warning_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Warnings (0)'])
85  self._warning_node.setIcon(0, self._warning_icon)
86  self.tree_widget.addTopLevelItem(self._warning_node)
87 
88  self._ok_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Ok (0)'])
89  self._ok_node.setIcon(0, self._ok_icon)
90  self.tree_widget.addTopLevelItem(self._ok_node)
91  self.tree_widget.itemSelectionChanged.connect(self._refresh_selection)
93 
94  self._name_to_item = {}
96 
97  self._subscriber = rospy.Subscriber(topic, DiagnosticArray, self._diagnostics_callback)
98 
99  self._previous_ros_time = rospy.Time.now()
100  self._timer = QTimer()
101  self._timer.timeout.connect(self._on_timer)
102  self._timer.start(1000)
103 
104  self._msg_timer = QTimer()
105  self._msg_timer.timeout.connect(self._update_messages)
106  self._msg_timer.start(100)
107 
108  self._messages = []
109  self._used_items = 0
110 
111  def __del__(self):
112  self.shutdown()
113 
114  def shutdown(self):
115  """
116  Unregisters subscriber and stops timers
117  """
118  self._msg_timer.stop()
119  self._timer.stop()
120 
121  if rospy.is_shutdown():
122  return
123 
124  if self._subscriber:
125  self._subscriber.unregister()
126  self._subscriber = None
127 
128  def change_diagnostic_topic(self, topic):
129  """
130  Changes diagnostics topic name. Must be of type diagnostic_msgs/DiagnosticArray
131  """
132  if not topic:
133  self.reset_monitor()
134  return
135 
136  if self._subscriber:
137  self._subscriber.unregister()
138  self._subscriber = rospy.Subscriber(str(topic), DiagnosticArray, self._diagnostics_callback)
139  self.reset_monitor()
140 
141  def reset_monitor(self):
142  """
143  Removes all values from monitor display, resets buffers
144  """
145  self._name_to_item = {} # Reset all stale topics
146  self._messages = []
147  self._clear_tree()
148 
149  def _clear_tree(self):
150  for index in range(self._stale_node.childCount()):
151  self._stale_node.removeChild(self._stale_node.child(index))
152  for index in range(self._error_node.childCount()):
153  self._error_node.removeChild(self._error_node.child(index))
154  for index in range(self._warning_node.childCount()):
155  self._warning_node.removeChild(self._warning_node.child(index))
156  for index in range(self._ok_node.childCount()):
157  self._ok_node.removeChild(self._ok_node.child(index))
158  self._update_root_labels()
159 
160  # Diagnostics callbacks (subscriber thread)
161  def _diagnostics_callback(self, message):
162  with self._mutex:
163  self._messages.append(message)
164 
165  # Update display of messages from main thread
166  def _update_messages(self):
167  with self._mutex:
168  messages = self._messages[:]
169  self._messages = []
170 
171  had_errors = False
172  for message in messages:
173  for status in message.status:
174  was_selected = False
175  if (status.name in self._name_to_item):
176  item = self._name_to_item[status.name]
177  if item.tree_node.isSelected():
178  was_selected = True
179  if (item.status.level == DiagnosticStatus.ERROR and status.level != DiagnosticStatus.ERROR):
180  had_errors = True
181  self._update_item(item, status, was_selected)
182  else:
183  self._create_item(status, was_selected, True)
184  if (status.level == DiagnosticStatus.ERROR):
185  had_errors = True
186 
187  if (had_errors and self._new_errors_callback != None):
188  self._new_errors_callback()
189 
190  self._update_root_labels()
191  self.update()
192  self._refresh_selection()
193 
194  def _update_item(self, item, status, was_selected):
195  change_parent = False
196  if (item.status.level != status.level):
197  change_parent = True
198  if (change_parent):
199  if (item.status.level == DiagnosticStatus.OK):
200  self._ok_node.removeChild(item.tree_node)
201  elif (item.status.level == DiagnosticStatus.WARN):
202  self._warning_node.removeChild(item.tree_node)
203  elif (item.status.level == -1) or (item.status.level == DiagnosticStatus.STALE):
204  self._stale_node.removeChild(item.tree_node)
205  else: # ERROR
206  self._error_node.removeChild(item.tree_node)
207 
208  if (status.level == DiagnosticStatus.OK):
209  parent_node = self._ok_node
210  elif (status.level == DiagnosticStatus.WARN):
211  parent_node = self._warning_node
212  elif (status.level == -1) or (status.level == DiagnosticStatus.STALE):
213  parent_node = self._stale_node
214  else: # ERROR
215  parent_node = self._error_node
216 
217  item.tree_node.setText(0, status.name + ": " + status.message)
218  item.tree_node.setData(0, Qt.UserRole, item)
219  parent_node.addChild(item.tree_node)
220 
221  # expand errors automatically
222  if (status.level > 1 or status.level == -1):
223  parent_node.setExpanded(True)
224 
225  parent_node.sortChildren(0, Qt.AscendingOrder)
226 
227  if (was_selected):
228  self.tree_widget.setCurrentItem(item.tree_node)
229 
230  else:
231  item.tree_node.setText(0, status.name + ": " + status.message)
232 
233  item.status = status
234 
235  if (was_selected):
236  self._fillout_info(item.tree_node)
237 
238  item.mark = True
239 
240  def _create_item(self, status, select, expand_if_error):
241  if (status.level == DiagnosticStatus.OK):
242  parent_node = self._ok_node
243  elif (status.level == DiagnosticStatus.WARN):
244  parent_node = self._warning_node
245  elif (status.level == -1) or (status.level == DiagnosticStatus.STALE):
246  parent_node = self._stale_node
247  else: # ERROR
248  parent_node = self._error_node
249 
250  item = TreeItem(status, QTreeWidgetItem(parent_node, [status.name + ": " + status.message]))
251  item.tree_node.setData(0, Qt.UserRole, item)
252  parent_node.addChild(item.tree_node)
253 
254  self._name_to_item[status.name] = item
255 
256  parent_node.sortChildren(0, Qt.AscendingOrder)
257 
258  if (select):
259  item.tree_node.setSelected(True)
260 
261  if (expand_if_error and (status.level > 1 or status.level == -1)):
262  parent_node.setExpanded(True)
263 
264  item.mark = True
265 
266  return item
267 
268  def _fillout_info(self, node):
269  item = node.data(0, Qt.UserRole)
270  if not item:
271  return
272 
273  scroll_value = self.html_browser.verticalScrollBar().value()
274  status = item.status
275 
276  s = StringIO()
277 
278  s.write("<html><body>")
279  s.write("<b>Component</b>: %s<br>\n" % (status.name))
280  s.write("<b>Message</b>: %s<br>\n" % (status.message))
281  s.write("<b>Hardware ID</b>: %s<br><br>\n\n" % (status.hardware_id))
282 
283  s.write('<table border="1" cellpadding="2" cellspacing="0">')
284  for value in status.values:
285  value.value = value.value.replace("\n", "<br>")
286  s.write("<tr><td><b>%s</b></td> <td>%s</td></tr>\n" % (value.key, value.value))
287 
288  s.write("</table></body></html>")
289 
290  self.html_browser.setHtml(s.getvalue())
291  if self.html_browser.verticalScrollBar().maximum() < scroll_value:
292  scroll_value = self.html_browser.verticalScrollBar().maximum()
293  self.html_browser.verticalScrollBar().setValue(scroll_value)
294 
296  current_item = self.tree_widget.selectedItems()
297  if current_item:
298  self._fillout_info(current_item[0])
299 
300  def _on_key_press(self, event):
301  key = event.key()
302  if key == Qt.Key_Delete:
303  nodes = self.tree_widget.selectedItems()
304  if (nodes != [] and nodes[0] not in (self._ok_node, self._warning_node, self._stale_node, self._error_node)):
305  item = nodes[0].data(0, Qt.UserRole)
306  if (item.status.level == 0):
307  self._ok_node.removeChild(item.tree_node)
308  elif (item.status.level == 1):
309  self._warning_node.removeChild(item.tree_node)
310  elif (item.status.level == -1) or (item.status.level == DiagnosticStatus.STALE):
311  self._stale_node.removeChild(item.tree_node)
312  else:
313  self._error_node.removeChild(item.tree_node)
314  del self._name_to_item[item.status.name]
315  self._update_root_labels()
316  self.update()
317  event.accept()
318  else:
319  event.ignore()
320 
321  def _on_timer(self):
322  if self._previous_ros_time + rospy.Duration(5) > rospy.Time.now():
323  return
324  self._previous_ros_time = rospy.Time.now()
325  for name, item in self._name_to_item.items():
326  node = item.tree_node
327  if (item != None):
328  if (not item.mark):
329  was_selected = False
330  selected = self.tree_widget.selectedItems()
331  if selected != [] and selected[0] == node:
332  was_selected = True
333 
334  new_status = copy.deepcopy(item.status)
335  new_status.level = -1 # mark stale
336  self._update_item(item, new_status, was_selected)
337  item.mark = False
338  self._update_root_labels()
339  self.update()
340 
341  def set_new_errors_callback(self, callback):
342  self._new_errors_callback = callback
343 
345  self._stale_node.setText(0, "Stale (%s)" % (self._stale_node.childCount()))
346  self._error_node.setText(0, "Errors (%s)" % (self._error_node.childCount()))
347  self._warning_node.setText(0, "Warnings (%s)" % (self._warning_node.childCount()))
348  self._ok_node.setText(0, "Ok (%s)" % (self._ok_node.childCount()))


rqt_runtime_monitor
Author(s): Aaron Blasdel
autogenerated on Thu Jun 6 2019 19:36:28