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
00036
00037 PKG = 'robot_monitor'
00038
00039 import roslib; roslib.load_manifest(PKG)
00040
00041 import sys, os
00042 import rospy
00043
00044 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
00045
00046 import wx
00047 from wx import xrc
00048
00049 import threading, time
00050
00051 from viewer_panel import StatusViewerFrame
00052 from robot_monitor_generated import MonitorPanelGenerated
00053 from message_timeline import MessageTimeline
00054
00055 color_dict = {0: wx.Colour(85, 178, 76), 1: wx.Colour(222, 213, 17), 2: wx.Colour(178, 23, 46), 3: wx.Colour(40, 23, 176)}
00056 error_levels = [2, 3]
00057
00058 def get_nice_name(status_name):
00059 return status_name.split('/')[-1]
00060
00061 def get_parent_name(status_name):
00062 return ('/'.join(status_name.split('/')[:-1])).strip()
00063
00064 class StatusItem(object):
00065 def __init__(self, status):
00066 self.tree_id = None
00067 self.warning_id = None
00068 self.error_id = None
00069 self.last_level = None
00070 self.update(status)
00071
00072 def update(self, status):
00073 self.status = status
00074
00075 class State(object):
00076 def __init__(self):
00077 self._items = {}
00078 self._msg = None
00079 self._has_warned_no_name = False
00080
00081 def reset(self):
00082 self._items = {}
00083 self._msg = None
00084
00085 def get_parent(self, item):
00086 parent_name = get_parent_name(item.status.name)
00087
00088 if (parent_name not in self._items):
00089 return None
00090
00091 return self._items[parent_name]
00092
00093 def get_descendants(self, item):
00094 child_keys = [k for k in self._items.iterkeys() if k.startswith(item.status.name + "/")]
00095 children = [self._items[k] for k in child_keys]
00096 return children
00097
00098 def get_items(self):
00099 return self._items
00100
00101 def update(self, msg):
00102 removed = []
00103 added = []
00104 items = {}
00105
00106
00107
00108 for s in msg.status:
00109
00110 if not s.name and not self._has_warned_no_name:
00111 rospy.logwarn('DiagnosticStatus message with no "name". Unable to add to robot monitor. Message: %s, hardware ID: %s, level: %d' % (s.message, s.hardware_id, s.level))
00112 self._has_warned_no_name = True
00113
00114 if not s.name:
00115 continue
00116
00117 if (len(s.name) > 0 and s.name[0] != '/'):
00118 s.name = '/' + s.name
00119
00120 if (s.name not in self._items):
00121 i = StatusItem(s)
00122 added.append(i)
00123 items[s.name] = i
00124 else:
00125 i = self._items[s.name]
00126 i.update(s)
00127 items[s.name] = i
00128
00129
00130
00131 to_add = []
00132 dummy_names = []
00133 for i in items.itervalues():
00134 parent = i.status.name
00135 while (len(parent) != 0):
00136 parent = get_parent_name(parent)
00137 if (len(parent) > 0 and parent not in items and parent not in dummy_names):
00138 pi = None
00139 if (parent not in self._items):
00140 s = DiagnosticStatus()
00141 s.name = parent
00142 s.message = ""
00143 pi = StatusItem(s)
00144 else:
00145 pi = self._items[parent]
00146
00147 to_add.append(pi)
00148 dummy_names.append(pi.status.name)
00149
00150 for a in to_add:
00151 if (a.status.name not in items):
00152 items[a.status.name] = a
00153
00154 if (a.status.name not in self._items):
00155 added.append(a)
00156
00157 for i in self._items.itervalues():
00158
00159 if (i.status.name not in items):
00160 removed.append(i)
00161
00162
00163 for r in removed:
00164 del self._items[r.status.name]
00165
00166 self._items = items
00167 self._msg = msg
00168
00169
00170 added.sort(cmp=lambda l,r: cmp(l.status.name, r.status.name))
00171
00172 removed.sort(cmp=lambda l,r: cmp(l.status.name, r.status.name), reverse=True)
00173
00174
00175
00176
00177
00178
00179 return (added, removed, self._items)
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189 class RobotMonitorPanel(MonitorPanelGenerated):
00190
00191 def __init__(self, parent, rxbag = False):
00192 MonitorPanelGenerated.__init__(self, parent)
00193
00194 self._frame = parent
00195 self._rxbag = rxbag
00196
00197 self._tree_ctrl.AddRoot("Root")
00198 self._error_tree_ctrl.AddRoot("Root")
00199 self._warning_tree_ctrl.AddRoot("Root")
00200
00201 self._tree_ctrl.SetToolTip(wx.ToolTip("Double click an item for details"))
00202 self._error_tree_ctrl.SetToolTip(wx.ToolTip("Double click an item for details"))
00203 self._warning_tree_ctrl.SetToolTip(wx.ToolTip("Double click an item for details"))
00204
00205
00206 image_list = wx.ImageList(16, 16)
00207 error_id = image_list.AddIcon(wx.ArtProvider.GetIcon(wx.ART_ERROR, wx.ART_OTHER, wx.Size(16, 16)))
00208 warn_id = image_list.AddIcon(wx.ArtProvider.GetIcon(wx.ART_WARNING, wx.ART_OTHER, wx.Size(16, 16)))
00209 ok_id = image_list.AddIcon(wx.ArtProvider.GetIcon(wx.ART_TICK_MARK, wx.ART_OTHER, wx.Size(16, 16)))
00210 stale_icon = wx.Icon(os.path.join(roslib.packages.get_pkg_dir(PKG), 'icons/stale.png'), wx.BITMAP_TYPE_PNG)
00211 stale_id = image_list.AddIcon(stale_icon)
00212 self._tree_ctrl.SetImageList(image_list)
00213 self._error_tree_ctrl.SetImageList(image_list)
00214 self._warning_tree_ctrl.SetImageList(image_list)
00215 self._image_list = image_list
00216
00217
00218 self._image_dict = { 0: ok_id, 1: warn_id, 2: error_id, 3: stale_id }
00219
00220
00221 self._tree_ctrl.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.on_all_item_activate)
00222 self._error_tree_ctrl.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.on_error_item_activate)
00223 self._warning_tree_ctrl.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.on_warning_item_activate)
00224 self._viewers = {}
00225
00226 self._state = State()
00227
00228
00229 self._have_message = False
00230 self._empty_id = None
00231 self.reset_monitor()
00232
00233
00234 self._timer = wx.Timer(self)
00235 self.Bind(wx.EVT_TIMER, self._update_message_state)
00236 self._timer.Start(1000)
00237
00238 self._timeline = MessageTimeline(self, 30, "diagnostics_agg", DiagnosticArray, self.new_message, self.get_color_for_message, self._on_pause)
00239 self._timeline.set_message_receipt_callback(self._on_new_message_received)
00240 self.GetSizer().Add(self._timeline, 0, wx.EXPAND)
00241
00242
00243
00244
00245
00246
00247
00248 def _set_initial_message_state(self):
00249 if self._rxbag:
00250 self._message_status_text.SetLabel("No message received")
00251 self._message_status_text.SetForegroundColour(color_dict[1])
00252 self._tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00253 self._error_tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00254 self._warning_tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00255 else:
00256 self._message_status_text.SetLabel("No message received")
00257 self._message_status_text.SetForegroundColour(color_dict[2])
00258 self._tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00259 self._error_tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00260 self._warning_tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00261
00262
00263 def _update_message_state(self, event = None):
00264 if not self._have_message:
00265 self._set_initial_message_state()
00266 return
00267 if self._rxbag:
00268 self._message_status_text.SetLabel("Receiving rxbag messages")
00269 self._message_status_text.SetForegroundColour(color_dict[0])
00270 self._tree_ctrl.SetBackgroundColour(wx.WHITE)
00271 self._error_tree_ctrl.SetBackgroundColour(wx.WHITE)
00272 self._warning_tree_ctrl.SetBackgroundColour(wx.WHITE)
00273 self._is_stale = False
00274 return
00275
00276 current_time = rospy.get_time()
00277 time_diff = current_time - self._last_message_time
00278 if (time_diff > 10.0):
00279 self._message_status_text.SetLabel("Last message received %s seconds ago"%(int(time_diff)))
00280 self._message_status_text.SetForegroundColour(color_dict[2])
00281 self._tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00282 self._error_tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00283 self._warning_tree_ctrl.SetBackgroundColour(wx.LIGHT_GREY)
00284 self._is_stale = True
00285 else:
00286 seconds_string = "seconds"
00287 if (int(time_diff) == 1):
00288 seconds_string = "second"
00289 self._message_status_text.SetLabel("Last message received %s %s ago"%(int(time_diff), seconds_string))
00290 self._message_status_text.SetForegroundColour(color_dict[0])
00291 self._tree_ctrl.SetBackgroundColour(wx.WHITE)
00292 self._error_tree_ctrl.SetBackgroundColour(wx.WHITE)
00293 self._warning_tree_ctrl.SetBackgroundColour(wx.WHITE)
00294 self._is_stale = False
00295
00296
00297
00298
00299
00300 def _on_new_message_received(self, msg):
00301 self._last_message_time = rospy.get_time()
00302
00303
00304 def reset_monitor(self):
00305
00306 if self._have_message:
00307 self._tree_ctrl.DeleteChildren(self._tree_ctrl.GetRootItem())
00308 self._error_tree_ctrl.DeleteChildren(self._error_tree_ctrl.GetRootItem())
00309 self._warning_tree_ctrl.DeleteChildren(self._warning_tree_ctrl.GetRootItem())
00310 self._state.reset()
00311
00312 if not self._empty_id:
00313 self._empty_id = self._tree_ctrl.AppendItem(self._tree_ctrl.GetRootItem(), "No data")
00314 self._tree_ctrl.SetItemImage(self._empty_id, self._image_dict[3])
00315
00316 self._is_stale = True
00317
00318 self._have_message = False
00319
00320 self._last_message_time = 0.0
00321 self._have_message = False
00322
00323 self._set_initial_message_state()
00324
00325
00326
00327
00328
00329
00330 def new_message(self, msg):
00331 self._tree_ctrl.Freeze()
00332
00333
00334 if not self._have_message:
00335 self._have_message = True
00336 self._tree_ctrl.Delete(self._empty_id)
00337 self._empty_id = None
00338
00339
00340 (added, removed, all) = self._state.update(msg)
00341
00342 for a in added:
00343 self._create_tree_item(a)
00344
00345 for r in removed:
00346 if (r.tree_id is not None):
00347 self._tree_ctrl.Delete(r.tree_id)
00348 if (r.error_id is not None):
00349 self._error_tree_ctrl.Delete(r.error_id)
00350 if (r.warning_id is not None):
00351 self._warning_tree_ctrl.Delete(r.warning_id)
00352
00353
00354 for k,v in self._viewers.iteritems():
00355 if (all.has_key(k)):
00356 v.set_status(all[k].status)
00357
00358 self._update_status_images()
00359
00360 self._update_error_tree()
00361 self._update_warning_tree()
00362
00363 self._update_labels()
00364
00365 self._tree_ctrl.Thaw()
00366
00367 def _on_pause(self, paused):
00368 if (not paused and len(self._viewers) > 0):
00369 msgs = self._timeline.get_messages()
00370 states = []
00371 for msg in msgs:
00372 state = State()
00373 state.update(msg)
00374 states.append(state)
00375
00376 for v in self._viewers.itervalues():
00377 if (paused):
00378 v.disable_timeline()
00379 else:
00380 v.enable_timeline()
00381 for state in states:
00382 all = state.get_items()
00383 if (all.has_key(v.get_name())):
00384 v.set_status(all[v.get_name()].status)
00385
00386 def _update_error_tree(self):
00387 for item in self._state.get_items().itervalues():
00388 level = item.status.level
00389 if (level not in error_levels and item.error_id is not None):
00390 self._error_tree_ctrl.Delete(item.error_id)
00391 item.error_id = None
00392 elif (level in error_levels and item.error_id is None):
00393 item.error_id = self._error_tree_ctrl.AppendItem(self._error_tree_ctrl.GetRootItem(), item.status.name)
00394 self._error_tree_ctrl.SetItemImage(item.error_id, self._image_dict[level])
00395 self._error_tree_ctrl.SetPyData(item.error_id, item)
00396
00397 self._error_tree_ctrl.SortChildren(self._error_tree_ctrl.GetRootItem())
00398
00399 def _update_warning_tree(self):
00400 for item in self._state.get_items().itervalues():
00401 level = item.status.level
00402 if (level != 1 and item.warning_id is not None):
00403 self._warning_tree_ctrl.Delete(item.warning_id)
00404 item.warning_id = None
00405 elif (level == 1 and item.warning_id is None):
00406 item.warning_id = self._warning_tree_ctrl.AppendItem(self._warning_tree_ctrl.GetRootItem(), item.status.name)
00407 self._warning_tree_ctrl.SetItemImage(item.warning_id, self._image_dict[level])
00408 self._warning_tree_ctrl.SetPyData(item.warning_id, item)
00409
00410 self._warning_tree_ctrl.SortChildren(self._warning_tree_ctrl.GetRootItem())
00411
00412 def _update_status_images(self):
00413 for item in self._state.get_items().itervalues():
00414 if (item.tree_id is not None):
00415 level = item.status.level
00416
00417 if (item.status.level != item.last_level):
00418 self._tree_ctrl.SetItemImage(item.tree_id, self._image_dict[level])
00419 item.last_level = level
00420
00421 def _update_labels(self):
00422 for item in self._state.get_items().itervalues():
00423 children = self._state.get_descendants(item)
00424 errors = 0
00425 warnings = 0
00426 for child in children:
00427 if (child.status.level == 2):
00428 errors = errors + 1
00429 elif (child.status.level == 1):
00430 warnings = warnings + 1
00431
00432 base_text = "%s : %s"%(get_nice_name(item.status.name), item.status.message)
00433 errwarn_text = "%s : %s"%(item.status.name, item.status.message)
00434
00435 if (item.tree_id is not None):
00436 text = base_text
00437 if (errors > 0 or warnings > 0):
00438 text = "(E: %s, W: %s) %s"%(errors, warnings, base_text)
00439 self._tree_ctrl.SetItemText(item.tree_id, text)
00440 if (item.error_id is not None):
00441 self._error_tree_ctrl.SetItemText(item.error_id, errwarn_text)
00442 if (item.warning_id is not None):
00443 self._warning_tree_ctrl.SetItemText(item.warning_id, errwarn_text)
00444
00445
00446 def _create_tree_item(self, item):
00447
00448 parent = self._state.get_parent(item)
00449
00450 parent_id = self._tree_ctrl.GetRootItem()
00451 if (parent is not None):
00452 parent_id = parent.tree_id
00453
00454
00455 short_name = get_nice_name(item.status.name)
00456 id = self._tree_ctrl.AppendItem(parent_id, short_name)
00457 item.tree_id = id
00458 self._tree_ctrl.SetPyData(id, item)
00459 self._tree_ctrl.SortChildren(parent_id)
00460
00461
00462
00463 def remove_viewer(self, name):
00464 if self._viewers.has_key(name):
00465 del self._viewers[name]
00466
00467 def on_all_item_activate(self, event):
00468 self._on_item_activate(event, self._tree_ctrl)
00469
00470 def on_error_item_activate(self, event):
00471 self._on_item_activate(event, self._error_tree_ctrl)
00472
00473 def on_warning_item_activate(self, event):
00474 self._on_item_activate(event, self._warning_tree_ctrl)
00475
00476 def _on_item_activate(self, event, tree_ctrl):
00477 id = event.GetItem()
00478 if id == None:
00479 event.Skip()
00480 return
00481
00482 if tree_ctrl.ItemHasChildren(id):
00483 tree_ctrl.Expand(id)
00484
00485 item = tree_ctrl.GetPyData(id)
00486 if not (item and item.status):
00487 event.Skip()
00488 return
00489
00490 name = item.status.name
00491
00492 if (self._viewers.has_key(name)):
00493 self._viewers[name].Raise()
00494 else:
00495 title = get_nice_name(name)
00496
00497
00498 viewer = StatusViewerFrame(self._frame, name, self, title)
00499 viewer.SetSize(wx.Size(500, 600))
00500 viewer.Layout()
00501 viewer.Center()
00502 viewer.Show(True)
00503 viewer.Raise()
00504
00505 self._viewers[name] = viewer
00506
00507 if (self._timeline.is_paused() or self._rxbag):
00508 viewer.disable_timeline()
00509 viewer.set_status(item.status)
00510 else:
00511 msgs = self._timeline.get_messages()
00512 states = []
00513 for msg in msgs:
00514 state = State()
00515 state.update(msg)
00516 states.append(state)
00517
00518 for state in states:
00519 all = state.get_items()
00520 if (all.has_key(item.status.name)):
00521 viewer.set_status(all[item.status.name].status)
00522
00523
00524
00525
00526
00527
00528
00529 def get_top_level_state(self):
00530 if (self._is_stale):
00531 return 3
00532
00533 level = -1
00534 min_level = 255
00535
00536 if len(self._state.get_items()) == 0:
00537 return level
00538
00539 for item in self._state.get_items().itervalues():
00540
00541 if self._state.get_parent(item) is not None:
00542 continue
00543
00544 if item.status.level > level:
00545 level = item.status.level
00546 if item.status.level < min_level:
00547 min_level = item.status.level
00548
00549
00550 if level > 2 and min_level <= 2:
00551 level = 2
00552
00553 return level
00554
00555 def get_color_for_message(self, msg):
00556 level = 0
00557 min_level = 255
00558
00559 lookup = {}
00560 for status in msg.status:
00561 lookup[status.name] = status
00562
00563 names = [status.name for status in msg.status]
00564 names = [name for name in names if len(get_parent_name(name)) == 0]
00565 for name in names:
00566 status = lookup[name]
00567 if (status.level > level):
00568 level = status.level
00569 if (status.level < min_level):
00570 min_level = status.level
00571
00572
00573 if (level > 2 and min_level <= 2):
00574 level = 2
00575
00576
00577 return color_dict[level]