robot_monitor.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2012, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 #
33 # Author: Isaac Saito, Ze'ev Klapow, Austin Hendrix
34 
35 import os
36 import rospkg
37 
38 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus
39 from python_qt_binding import loadUi
40 from python_qt_binding.QtCore import QTimer, Signal, Qt, Slot
41 from python_qt_binding.QtGui import QPalette
42 from python_qt_binding.QtWidgets import QWidget, QTreeWidgetItem
43 import rospy
44 
45 from rqt_robot_monitor.inspector_window import InspectorWindow
46 from rqt_robot_monitor.status_item import StatusItem
47 from rqt_robot_monitor.timeline_pane import TimelinePane
48 from rqt_robot_monitor.timeline import Timeline
50 
51 
52 class RobotMonitorWidget(QWidget):
53  """
54  NOTE: RobotMonitorWidget.shutdown function needs to be called
55  when the instance of this class terminates.
56 
57  RobotMonitorWidget itself doesn't store previous diagnostic states.
58  It instead delegates that function to Timeline class.
59  """
60 
61  _TREE_ALL = 1
62  _TREE_WARN = 2
63  _TREE_ERR = 3
64 
65  _message_updated = Signal(dict)
66  _queue_updated = Signal()
67 
68  def __init__(self, context, topic=None):
69  """
70  :param context: plugin context hook to enable adding widgets as a
71  ROS_GUI pane, 'PluginContext'
72  :param topic: Diagnostic topic to subscribe to 'str'
73  """
74 
75  super(RobotMonitorWidget, self).__init__()
76  rp = rospkg.RosPack()
77  ui_file = os.path.join(rp.get_path('rqt_robot_monitor'), 'resource',
78  'robotmonitor_mainwidget.ui')
79  loadUi(ui_file, self)
80 
81  obj_name = 'Robot Monitor'
82  self.setObjectName(obj_name)
83  self.setWindowTitle(obj_name)
84 
87 
88  # if we're given a topic, create a timeline. otherwise, don't
89  # this can be used later when writing an rqt_bag plugin
90  if topic:
91  # create timeline data structure
92  self._timeline = Timeline(topic, DiagnosticArray)
93  self._timeline.message_updated.connect(
94  self.message_updated, Qt.DirectConnection)
95  self._timeline.queue_updated.connect(
96  self.queue_updated, Qt.DirectConnection)
97  self._message_updated.connect(
98  self._signal_message_updated, Qt.QueuedConnection)
99  self._queue_updated.connect(
100  self._signal_queue_updated, Qt.QueuedConnection)
101 
102  # create timeline pane widget
103  self._timeline_pane = TimelinePane(self, self._timeline.paused)
104  self._timeline_pane.pause_changed.connect(self._timeline.set_paused)
105  self._timeline_pane.position_changed.connect(self._timeline.set_position)
106  self._timeline.pause_changed.connect(self._timeline_pane.set_paused)
107  self._timeline.position_changed.connect(self._timeline_pane.set_position)
108 
109  self.vlayout_top.addWidget(self._timeline_pane)
110  self._timeline_pane.show()
111  else:
112  self._timeline = None
113  self._timeline_pane = None
114 
115  self._inspectors = {}
116 
117  self.tree_all_devices.itemDoubleClicked.connect(self._tree_clicked)
118  self.warn_flattree.itemDoubleClicked.connect(self._tree_clicked)
119  self.err_flattree.itemDoubleClicked.connect(self._tree_clicked)
120  # TODO: resize on itemCollapsed and itemExpanded
121 
122  self._is_stale = False
123 
124  self._timer = QTimer()
125  self._timer.timeout.connect(self._update_message_state)
126  self._timer.start(1000)
127 
128  palette = self.tree_all_devices.palette()
129  self._original_base_color = palette.base().color()
130  self._original_alt_base_color = palette.alternateBase().color()
131 
132  self._tree = StatusItem(self.tree_all_devices.invisibleRootItem())
133  self._warn_tree = StatusItem(self.warn_flattree.invisibleRootItem())
134  self._err_tree = StatusItem(self.err_flattree.invisibleRootItem())
135 
136  @Slot(dict)
137  def message_updated(self, status):
138  '''
139  This method just calls _signal_message_updated in 'best effort' manner.
140  This method should be called by signal with DirectConnection.
141  '''
143  return
144  self._message_updated_processing = True
145  self._message_updated.emit(status)
146 
147  @Slot(dict)
148  def _signal_message_updated(self, status):
149  """ DiagnosticArray message callback """
150 
151  # Walk the status array and update the tree
152  for name, status in status.items():
153  # Compute path and walk to appropriate subtree
154  path = name.split('/')
155  if path[0] == '':
156  path = path[1:]
157  tmp_tree = self._tree
158  for p in path:
159  tmp_tree = tmp_tree[p]
160  tmp_tree.update(status, util.get_resource_name(name))
161 
162  # Check for warnings
163  if status.level == DiagnosticStatus.WARN:
164  self._warn_tree[name].update(status, name)
165 
166  # Check for errors
167  if (status.level == DiagnosticStatus.ERROR or
168  status.level == DiagnosticStatus.STALE):
169  self._err_tree[name].update(status, name)
170 
171  # For any items in the tree that were not updated, remove them
172  self._tree.prune()
173  self._warn_tree.prune()
174  self._err_tree.prune()
175 
176  # TODO(ahendrix): implement
177  # Insight: for any item that is not OK, it only provides additional
178  # information if all of it's children are OK
179  #
180  # otherwise, it's just an aggregation of its children
181  # and doesn't provide any additional value when added to
182  # the warning and error flat trees
183 
184  self.tree_all_devices.resizeColumnToContents(0)
185  self.warn_flattree.resizeColumnToContents(0)
186  self.err_flattree.resizeColumnToContents(0)
187 
188  self._message_updated_processing = False
189 
190  @Slot()
191  def queue_updated(self):
192  '''
193  This method just calls _signal_queue_updated in 'best effort' manner.
194  This method should be called by signal with DirectConnection.
195  '''
197  return
198  self._queue_updated_processing = True
199  self._queue_updated.emit()
200 
201  @Slot()
203  # update timeline pane
204  # collect worst status levels for each history
205  levels = [max([s.level for s in s.values()]) for s in self._timeline]
206  self._timeline_pane.set_levels(levels)
207  self._timeline_pane.redraw.emit()
208  self._queue_updated_processing = False
209 
210  def resizeEvent(self, evt):
211  """Overridden from QWidget"""
212  rospy.logdebug('RobotMonitorWidget resizeEvent')
213  if self._timeline_pane:
214  self._timeline_pane.redraw.emit()
215 
216  @Slot(str)
217  def _inspector_closed(self, name):
218  """ Called when an inspector window is closed """
219  if name in self._inspectors:
220  del self._inspectors[name]
221 
222  @Slot(QTreeWidgetItem, int)
223  def _tree_clicked(self, item, column):
224  """
225  Slot to QTreeWidget.itemDoubleClicked
226 
227  :type item: QTreeWidgetItem
228  :type column: int
229  """
230  rospy.logdebug('RobotMonitorWidget _tree_clicked col=%d', column)
231 
232  if item.name in self._inspectors:
233  self._inspectors[item.name].activateWindow()
234  else:
235  insp = InspectorWindow(None, item.name, self._timeline)
236  insp.show()
237  insp.closed.connect(self._inspector_closed)
238  self._inspectors[item.name] = insp
239 
241  """ Update the display if it's stale """
242  if self._timeline is not None:
243  if self._timeline.has_messages:
244  previous_stale_state = self._is_stale
245  self._is_stale = self._timeline.is_stale
246 
247  time_diff = int(self._timeline.data_age)
248 
249  msg_template = "Last message received %s %s ago"
250  if time_diff == 1:
251  msg = msg_template % (time_diff, "second")
252  else:
253  msg = msg_template % (time_diff, "seconds")
254  self._timeline_pane._msg_label.setText(msg)
255  if previous_stale_state != self._is_stale:
257  else:
258  # no messages received yet
259  self._timeline_pane._msg_label.setText("No messages received")
260 
262  """ Update the background color based on staleness """
263  p = self.tree_all_devices.palette()
264  if self._is_stale:
265  p.setColor(QPalette.Base, Qt.darkGray)
266  p.setColor(QPalette.AlternateBase, Qt.lightGray)
267  else:
268  p.setColor(QPalette.Base, self._original_base_color)
269  p.setColor(QPalette.AlternateBase, self._original_alt_base_color)
270  self.tree_all_devices.setPalette(p)
271  self.warn_flattree.setPalette(p)
272  self.err_flattree.setPalette(p)
273 
274  def shutdown(self):
275  """
276  This needs to be called whenever this class terminates.
277  This closes all the instances on all trees.
278  Also unregisters ROS' subscriber, stops timer.
279  """
280  rospy.logdebug('RobotMonitorWidget in shutdown')
281 
282  names = self._inspectors.keys()
283  for name in names:
284  self._inspectors[name].close()
285 
286  if self._timeline:
287  self._timeline.shutdown()
288 
289  self._timer.stop()
290  del self._timer
291 
292  def save_settings(self, plugin_settings, instance_settings):
293  instance_settings.set_value('splitter', self.splitter.saveState())
294  # TODO(ahendrix): persist the device paths, positions and sizes of any
295  # inspector windows
296 
297  def restore_settings(self, plugin_settings, instance_settings):
298  if instance_settings.contains('splitter'):
299  self.splitter.restoreState(instance_settings.value('splitter'))
300  else:
301  self.splitter.setSizes([100, 100, 200])
302  # TODO(ahendrix): restore inspector windows
def save_settings(self, plugin_settings, instance_settings)
def restore_settings(self, plugin_settings, instance_settings)


rqt_robot_monitor
Author(s): Austin Hendrix, Isaac Saito, Ze'ev Klapow, Kevin Watts, Josh Faust
autogenerated on Thu May 16 2019 02:14:30