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
00035 import os
00036 import copy
00037 import random
00038
00039 import roslib;roslib.load_manifest('rqt_robot_monitor')
00040 import rospy
00041 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus
00042 from python_qt_binding import loadUi
00043 from python_qt_binding.QtCore import Qt, QTimer, Signal
00044 from python_qt_binding.QtGui import QIcon, QTextEdit, QWidget
00045
00046 from .abst_status_widget import AbstractStatusWidget
00047 from .chronologic_state import InstantaneousState, StatusItem
00048 from .time_pane import TimelinePane
00049 from .util_robot_monitor import Util
00050
00051 class RobotMonitorWidget(AbstractStatusWidget):
00052 """
00053 shutdown function needs to be called when the class terminates.
00054
00055 RobotMonitorWidget itself doesn't store previous states. It instead
00056 delegates that function to TimelinePane class.
00057 """
00058
00059 _sig_clear = Signal()
00060 _sig_tree_nodes_updated = Signal(int)
00061 _TREE_ALL = 1
00062 _TREE_WARN = 2
00063 _TREE_ERR = 3
00064
00065 '''
00066 @param context:
00067 @param topic:
00068 '''
00069 def __init__(self, context, topic):
00070 super(RobotMonitorWidget, self).__init__()
00071 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
00072 'rqt_robot_monitor_mainwidget.ui')
00073 loadUi(ui_file, self)
00074
00075 obj_name = 'Robot Monitor'
00076 self.setObjectName(obj_name)
00077 self.setWindowTitle(obj_name)
00078
00079 self._toplv_statusitems = []
00080 self._warn_statusitems = []
00081
00082 self._err_statusitems = []
00083
00084 self.tree_all_devices.itemDoubleClicked.connect(self._tree_clicked)
00085 self.warn_tree.itemDoubleClicked.connect(self._tree_clicked)
00086 self.err_tree.itemDoubleClicked.connect(self._tree_clicked)
00087
00088 self.tree_all_devices.resizeColumnToContents(0)
00089
00090 self._sig_clear.connect(self._clear)
00091 self._sig_tree_nodes_updated.connect(self._tree_nodes_updated)
00092
00093
00094
00095
00096
00097
00098 self.timeline_pane = TimelinePane(self, Util._SECONDS_TIMELINE,
00099 self._cb,
00100 self.get_color_for_value,
00101 self.on_pause)
00102 self.vlayout_top.addWidget(self.timeline_pane)
00103 self.timeline_pane.show()
00104
00105 self.paused = False
00106 self._is_stale = False
00107 self.last_message_time = 0.0
00108
00109 self._timer = QTimer()
00110
00111 self._timer.timeout.connect(self._update_message_state)
00112 self._timer.start(1000);
00113
00114 self.num_diag_msg_received = 0
00115
00116 self.topic = topic
00117 self._sub = rospy.Subscriber(
00118 self.topic,
00119 DiagnosticArray,
00120 self._cb)
00121
00122 def _cb(self, msg, is_forced = False):
00123 """
00124 @param msg: DiagnosticArray
00125 @param is_forced: bool. Intended for non-incoming-msg trigger
00126 (in particular, from child object like TimelinePane).
00127 @author: Isaac Saito
00128 """
00129 if not self.paused and not is_forced:
00130 self.timeline_pane.new_diagnostic(msg)
00131 self._update_devices_tree(msg)
00132 self._update_warns_errors(msg)
00133 self._on_new_message_received(msg)
00134
00135 self._notify_statitems(msg)
00136
00137 rospy.logdebug(' RobotMonitorWidget _cb stamp=%s',
00138 msg.header.stamp)
00139 elif is_forced:
00140 self._update_devices_tree(msg)
00141 self._update_warns_errors(msg)
00142
00143 self.num_diag_msg_received += 1
00144 rospy.logdebug(' RobotMonitorWidget _cb #%d',
00145 self.num_diag_msg_received)
00146
00147 def _notify_statitems(self, diag_arr):
00148 """
00149
00150 @summary: Notify new message arrival to all existing InespectorWindow
00151 instances that are encapsulated in StatusItem instances contained
00152 in self._toplv_statusitems.
00153 """
00154
00155 for statitem_new in diag_arr.status:
00156 corresp = Util.get_correspondent(statitem_new.name,
00157 self._toplv_statusitems)
00158 statitem_prev = corresp[Util._DICTKEY_STATITEM]
00159 if statitem_prev and statitem_prev.inspector:
00160
00161 rospy.loginfo(' RobotMonitorWidget _notify_statitems name=%s ' +
00162 'len toplv items=%d',
00163 statitem_new.name, len(self._toplv_statusitems))
00164 return
00165
00166 def resizeEvent(self, evt):
00167 rospy.logdebug('RobotMonitorWidget resizeEvent')
00168 self.timeline_pane.redraw()
00169
00170 def _tree_clicked(self, item, column):
00171 """
00172 @param item: QTreeWidgetItem
00173 @param column: int
00174 """
00175
00176 rospy.logdebug('RobotMonitorWidget _tree_clicked col=%d', column)
00177 item.on_click()
00178
00179 def _update_devices_tree(self, diag_array):
00180 """
00181 Update the tree from the bottom
00182 @param diag_array: DiagnosticArray
00183
00184 @todo: 11/5/2012 Currently, in case some devices disappear
00185 while running this program, there's no way to remove
00186 those from the device-tree.
00187 """
00188
00189 statusnames_curr_toplevel = [Util.get_nice_name(k.name)
00190 for k in self._toplv_statusitems]
00191
00192
00193
00194 for diagnostic_status_new in self._get_toplv_diagnosticstatus_from_new_msg(
00195 diag_array):
00196 name = Util.get_nice_name(diagnostic_status_new.name)
00197 rospy.logdebug('_update_devices_tree 0 name @ toplevel %s', name)
00198 dict_status = 0
00199 if name in statusnames_curr_toplevel:
00200
00201 statusitem = self._toplv_statusitems[
00202 statusnames_curr_toplevel.index(name)]
00203
00204 dict_status = statusitem.update_children(diagnostic_status_new,
00205 diag_array)
00206 times_errors = dict_status[Util._DICTKEY_TIMES_ERROR]
00207 times_warnings = dict_status[Util._DICTKEY_TIMES_WARN]
00208 Util._update_status_images(diagnostic_status_new, statusitem)
00209
00210
00211 base_text = Util.gen_headline_status_green(statusitem.status)
00212
00213
00214 rospy.logdebug('_update_devices_tree warn_id= %s\n\t\t\t' +
00215 'diagnostic_status.name = %s\n\t\t\t\t' +
00216 'Normal status diag_array = %s',
00217 statusitem.warning_id,
00218 diagnostic_status_new.name, base_text)
00219
00220 if (times_errors > 0 or times_warnings > 0):
00221 base_text = "(Err: %s, Wrn: %s) %s %s" % (
00222 times_errors,
00223 times_warnings,
00224 Util.get_nice_name(diagnostic_status_new.name),
00225 diagnostic_status_new.message)
00226 rospy.logdebug('_update_dev_tree 1 text to show=%s',
00227 base_text)
00228 statusitem.setText(0, base_text)
00229 statusitem.setText(1, diagnostic_status_new.message)
00230 else:
00231 rospy.logdebug('_update_dev_tree 2 text to show=%s',
00232 base_text)
00233 statusitem.setText(0, base_text)
00234 statusitem.setText(1, 'OK')
00235
00236 else:
00237 new_status_item = StatusItem(diagnostic_status_new)
00238
00239
00240 new_status_item.update_children(diagnostic_status_new,
00241 diag_array)
00242
00243
00244
00245
00246
00247 Util._update_status_images(diagnostic_status_new,
00248 new_status_item)
00249
00250 self._toplv_statusitems.append(new_status_item)
00251
00252
00253
00254
00255 rospy.logdebug(' _update_devices_tree 2 ' +
00256 'diagnostic_status_new.name %s',
00257 new_status_item.name)
00258 self.tree_all_devices.addTopLevelItem(new_status_item)
00259
00260 self._sig_tree_nodes_updated.emit(self._TREE_ALL)
00261
00262 def _tree_nodes_updated(self, tree_type):
00263 tree_obj = None
00264 if self._TREE_ALL == tree_type:
00265 tree_obj = self.tree_all_devices
00266 elif self._TREE_WARN == tree_type:
00267 tree_obj = self.warn_tree
00268 if self._TREE_ERR == tree_type:
00269 tree_obj = self.err_tree
00270 tree_obj.resizeColumnToContents(0)
00271
00272 def _get_toplv_diagnosticstatus_from_new_msg(self, diag_array):
00273 """
00274
00275 Return an array that contains DiagnosticStatus only at the top level of
00276 the given msg.
00277
00278 @param msg: DiagnosticArray
00279 @return array of 'status' (ie. array of DiagnosticStatus[])
00280 """
00281
00282 ret = []
00283 for diagnostic_status in diag_array.status:
00284 if len(diagnostic_status.name.split('/')) == 2:
00285 rospy.logdebug(" _get_toplv_diagnosticstatus_from_new_msg " +
00286 "TOP lev %s ", diagnostic_status.name)
00287 ret.append(diagnostic_status)
00288 else:
00289 rospy.logdebug(" _get_toplv_diagnosticstatus_from_new_msg " +
00290 "Not top lev %s ", diagnostic_status.name)
00291 return ret
00292
00293 """
00294 Update the warning and error trees.
00295
00296 Unlike _update_devices_tree function where all DiagnosticStatus need to be
00297 inspected constantly, this function is used in a trial to reduce
00298 unnecessary inspection of the status level for all DiagnosticStatus
00299 contained in the incoming DiagnosticArray msg.
00300
00301 @param msg: DiagnosticArray
00302 """
00303 def _update_warns_errors(self, diag_array):
00304 self._update_flat_tree(diag_array, self._toplv_statusitems)
00305
00306
00307 def pause(self, msg):
00308 """
00309 Ignored if already being paused.
00310
00311 @param msg: DiagnosticArray
00312 """
00313
00314 if not self.paused:
00315 self.paused = True
00316 self._cb(msg)
00317
00318 def unpause(self, msg = None):
00319 self.paused = False
00320
00321 def on_pause(self, paused, diagnostic_arr):
00322 """
00323 Check if InspectorWindows are set. If they are, pause them.
00324
00325 Pay attention not to confuse with _pause func.
00326
00327 @param paused: bool
00328 @param diagnostic_arr:
00329
00330 Copied from robot_monitor.
00331 """
00332
00333 if paused:
00334 self.pause(diagnostic_arr)
00335
00336 elif (len(self._toplv_statusitems) > 0):
00337 diag_array_queue = self.timeline_pane._get_diagnosticarray()
00338 statitems = []
00339 for diag_arr in diag_array_queue:
00340 state_instant = InstantaneousState()
00341 state_instant.update(diag_arr)
00342 statitems.append(state_instant)
00343
00344 for statitem_toplv in self._toplv_statusitems:
00345 if (paused):
00346 statitem_toplv.disable()
00347 else:
00348 statitem_toplv.enable()
00349 for state_instant in statitems:
00350 all = state_instant.get_items()
00351 if (all.has_key(statitem_toplv.get_name())):
00352 statitem_toplv.update(
00353 all[statitem_toplv.get_name()].status)
00354
00355 def _update_flat_tree(self, diag_arr, statusitems_curr_toplevel):
00356 """
00357
00358 Update the given flat tree (ie. tree that doesn't show children nodes -
00359 all of its elements will be shown on top level) with
00360 all the DiagnosticStatus instances contained in the given DiagnosticArray,
00361 regardless of the level of the device in a device category.
00362
00363 For both warn / error trees, StatusItem instances are newly generated.
00364
00365 @param diag_arr: a DiagnosticArray instance.
00366 @param statusitems_curr_toplevel: list of StatusItem.
00367
00368 @author: Isaac Saito
00369 """
00370
00371 for diag_stat_new in diag_arr.status:
00372
00373
00374
00375 stat_lv_new = diag_stat_new.level
00376 dev_name = diag_stat_new.name
00377 correspondent_warn_curr = Util.get_correspondent(
00378 Util.get_nice_name(dev_name),
00379 self._warn_statusitems)
00380 dev_index_warn_curr = correspondent_warn_curr[Util._DICTKEY_INDEX]
00381 rospy.logdebug('###_update_flat_tree dev_index_warn_curr=%s dev_name=%s',
00382 dev_index_warn_curr, dev_name)
00383 correspondent_err_curr = Util.get_correspondent(
00384 Util.get_nice_name(dev_name),
00385 self._err_statusitems)
00386 dev_index_err_curr = correspondent_err_curr[Util._DICTKEY_INDEX]
00387 headline = "%s" % diag_stat_new.name
00388 if DiagnosticStatus.OK == stat_lv_new:
00389 if 0 <= dev_index_warn_curr:
00390 rospy.logdebug('#_update_flat_tree QQQ dev_index_warn_curr=%s dev_name=%s, stat_lv_new=%d',
00391 dev_index_warn_curr, dev_name, stat_lv_new)
00392 statitem_curr = self._get_statitem(dev_index_warn_curr,
00393 self._warn_statusitems,
00394 self.warn_tree, 1)
00395 statitem_curr.warning_id = None
00396 elif 0 <= dev_index_err_curr:
00397 statitem_curr = self._get_statitem(dev_index_err_curr,
00398 self._err_statusitems,
00399 self.err_tree, 1)
00400 statitem_curr.error_id = None
00401 elif DiagnosticStatus.WARN == stat_lv_new:
00402 statitem = None
00403 if 0 <= dev_index_err_curr:
00404
00405
00406 statitem = self._get_statitem(dev_index_err_curr,
00407 self._err_statusitems,
00408 self.err_tree)
00409 self._add_statitem(statitem, self._warn_statusitems,
00410 self.warn_tree, headline,
00411 diag_stat_new.message, stat_lv_new)
00412 elif (dev_index_warn_curr < 0 and dev_index_err_curr < 0):
00413
00414
00415 statitem = StatusItem(diag_stat_new)
00416 self._add_statitem(statitem, self._warn_statusitems,
00417 self.warn_tree, headline,
00418 diag_stat_new.message, stat_lv_new)
00419 self._warn_statusitems.append(statitem)
00420 elif (0 < dev_index_warn_curr):
00421
00422
00423 statitem = self._get_statitem(dev_index_warn_curr,
00424 self._warn_statusitems)
00425
00426 if statitem:
00427
00428 dict_status = statitem.update_children(diag_stat_new,
00429 diag_arr)
00430 elif ((DiagnosticStatus.ERROR == stat_lv_new) or
00431 (DiagnosticStatus.STALE == stat_lv_new)):
00432 statitem = None
00433 if 0 <= dev_index_warn_curr:
00434
00435
00436 statitem = self._get_statitem(dev_index_warn_curr,
00437 self._warn_statusitems,
00438 self.warn_tree)
00439 self._add_statitem(statitem, self._err_statusitems,
00440 self.err_tree, headline,
00441 diag_stat_new.message, stat_lv_new)
00442 elif (dev_index_warn_curr < 0 and dev_index_err_curr < 0):
00443
00444
00445 statitem = StatusItem(diag_stat_new)
00446 self._add_statitem(statitem, self._err_statusitems,
00447 self.err_tree, headline,
00448 diag_stat_new.message, stat_lv_new)
00449 elif (0 < dev_index_err_curr):
00450
00451
00452 statitem = self._get_statitem(dev_index_err_curr,
00453 self._err_statusitems)
00454
00455 if statitem:
00456
00457 dict_status = statitem.update_children(diag_stat_new,
00458 diag_arr)
00459
00460 self._sig_tree_nodes_updated.emit(self._TREE_WARN)
00461 self._sig_tree_nodes_updated.emit(self._TREE_ERR)
00462
00463 '''
00464 @author: Isaac Saito
00465 '''
00466 def _add_statitem(self, statusitem, statitem_list,
00467 tree, headline, statusmsg, statlevel):
00468
00469 if 'Warning' == statusmsg or 'Error' == statusmsg:
00470 return
00471
00472 statusitem.setText(0, headline)
00473 statusitem.setText(1, statusmsg)
00474 statusitem.setIcon(0, Util._IMG_DICT[statlevel])
00475 statitem_list.append(statusitem)
00476 tree.addTopLevelItem(statusitem)
00477 rospy.logdebug(' _add_statitem statitem_list length=%d',
00478 len(statitem_list))
00479
00480 def _get_statitem(self, item_index, item_list, tree = None, mode = 2):
00481 """
00482 @param mode: 1 = remove from given list, 2 = w/o removing.
00483 @author: Isaac Saito
00484 """
00485 statitem_existing = item_list[item_index]
00486 if 1 == mode:
00487 tree.takeTopLevelItem(tree.indexOfTopLevelItem(statitem_existing))
00488 item_list.pop(item_index)
00489
00490 return statitem_existing
00491
00492 def _on_new_message_received(self, msg):
00493 """
00494 Copied from robot_monitor
00495
00496 \brief Called whenever a new message is received by the timeline.
00497 Different from new_message in that it
00498 is called even if the timeline is paused, and only when a new message is
00499 received, not when the timeline is scrubbed
00500 """
00501 self.last_message_time = rospy.get_time()
00502
00503 def _update_message_state(self):
00504 """
00505 @author: Isaac Saito (ported from robot_monitor)
00506 """
00507
00508 current_time = rospy.get_time()
00509 time_diff = current_time - self.last_message_time
00510 rospy.logdebug('_update_message_state time_diff= %s ' +
00511 'self.last_message_time=%s', time_diff,
00512 self.last_message_time)
00513 if (time_diff > 10.0):
00514 self.timeline_pane._msg_label.setText("Last message received " +
00515 "%s seconds ago"
00516 % (int(time_diff)))
00517 self._is_stale = True
00518 else:
00519 seconds_string = "seconds"
00520 if (int(time_diff) == 1):
00521 seconds_string = "second"
00522 self.timeline_pane._msg_label.setText(
00523 "Last message received %s %s ago" % (int(time_diff),
00524 seconds_string))
00525 self._is_stale = False
00526
00527
00528
00529
00530
00531
00532
00533
00534 '''
00535 This needs to be called whenever this class terminates.
00536 '''
00537 def shutdown(self):
00538 rospy.logdebug('RobotMonitorWidget in shutdown')
00539
00540
00541
00542
00543 for item in self._err_statusitems:
00544 item.close()
00545 for item in self._warn_statusitems:
00546 item.close()
00547 for item in self._toplv_statusitems:
00548 item.close()
00549
00550
00551 self._sub.unregister()
00552
00553
00554 self._timer.stop()
00555 del self._timer
00556
00557 def get_color_for_value(self, queue_diagnostic, color_index):
00558 """
00559 Taken from robot_monitor
00560
00561 Overridden.
00562
00563 @param color_index: int
00564 """
00565 len_q = len(queue_diagnostic)
00566 rospy.logdebug(' get_color_for_value color_index=%d len_q=%d',
00567 color_index, len_q)
00568
00569 if (color_index <= 2 and len_q == 0):
00570 return QColor('grey')
00571 return Util._get_color_for_message(queue_diagnostic[color_index - 1])
00572
00573
00574
00575
00576 def save_settings(self, plugin_settings, instance_settings):
00577 instance_settings.set_value('splitter', self.splitter.saveState())
00578
00579 def restore_settings(self, plugin_settings, instance_settings):
00580 if instance_settings.contains('splitter'):
00581 self.splitter.restoreState(instance_settings.value('splitter'))
00582 else:
00583 self.splitter.setSizes([100, 100, 200])
00584
00585 def _clear(self):
00586 rospy.logdebug(' RobotMonitorWidget _clear called ')
00587 self.err_tree.clear()
00588 self.warn_tree.clear()