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             if len(self._state.get_descendants(item)) == 0:
00389                 level = item.status.level
00390                 if (level not in error_levels and item.error_id is not None):
00391                     self._error_tree_ctrl.Delete(item.error_id)
00392                     item.error_id = None
00393                 elif (level in error_levels and item.error_id is None):
00394                     item.error_id = self._error_tree_ctrl.AppendItem(self._error_tree_ctrl.GetRootItem(), item.status.name)
00395                     self._error_tree_ctrl.SetItemImage(item.error_id, self._image_dict[level])
00396                     self._error_tree_ctrl.SetPyData(item.error_id, item)
00397                 
00398         self._error_tree_ctrl.SortChildren(self._error_tree_ctrl.GetRootItem())
00399                 
00400     def _update_warning_tree(self):
00401         for item in self._state.get_items().itervalues():
00402             if len(self._state.get_descendants(item)) == 0:
00403                 level = item.status.level
00404                 if (level != 1 and item.warning_id is not None):
00405                     self._warning_tree_ctrl.Delete(item.warning_id)
00406                     item.warning_id = None
00407                 elif (level == 1 and item.warning_id is None):
00408                     item.warning_id = self._warning_tree_ctrl.AppendItem(self._warning_tree_ctrl.GetRootItem(), item.status.name)
00409                     self._warning_tree_ctrl.SetItemImage(item.warning_id, self._image_dict[level])
00410                     self._warning_tree_ctrl.SetPyData(item.warning_id, item)
00411                 
00412         self._warning_tree_ctrl.SortChildren(self._warning_tree_ctrl.GetRootItem())
00413         
00414     def _update_status_images(self):
00415         for item in self._state.get_items().itervalues():
00416             if (item.tree_id is not None):
00417                 level = item.status.level
00418                 
00419                 if (item.status.level != item.last_level):
00420                     self._tree_ctrl.SetItemImage(item.tree_id, self._image_dict[level])
00421                     item.last_level = level
00422     
00423     def _update_labels(self):
00424         for item in self._state.get_items().itervalues():
00425             children = self._state.get_descendants(item)
00426             errors = 0
00427             warnings = 0
00428             for child in children:
00429                 if (child.status.level == 2):
00430                     errors = errors + 1
00431                 elif (child.status.level == 1):
00432                     warnings = warnings + 1
00433             
00434             base_text = "%s : %s"%(get_nice_name(item.status.name), item.status.message)
00435             errwarn_text = "%s : %s"%(item.status.name, item.status.message)
00436             
00437             if (item.tree_id is not None):
00438                 text = base_text
00439                 if (errors > 0 or warnings > 0):
00440                     text = "(E: %s, W: %s) %s"%(errors, warnings, base_text)
00441                 self._tree_ctrl.SetItemText(item.tree_id, text)
00442             if (item.error_id is not None):
00443                 self._error_tree_ctrl.SetItemText(item.error_id, errwarn_text)
00444             if (item.warning_id is not None):
00445                 self._warning_tree_ctrl.SetItemText(item.warning_id, errwarn_text)
00446                 
00447           
00448     def _create_tree_item(self, item):
00449         
00450         parent = self._state.get_parent(item)
00451         
00452         parent_id = self._tree_ctrl.GetRootItem()
00453         if (parent is not None):
00454             parent_id = parent.tree_id
00455         
00456         
00457         short_name = get_nice_name(item.status.name)
00458         id = self._tree_ctrl.AppendItem(parent_id, short_name)
00459         item.tree_id = id
00460         self._tree_ctrl.SetPyData(id, item)
00461         self._tree_ctrl.SortChildren(parent_id)
00462 
00463     
00464     
00465     def remove_viewer(self, name):
00466         if self._viewers.has_key(name):
00467             del self._viewers[name]
00468 
00469     def on_all_item_activate(self, event):
00470         self._on_item_activate(event, self._tree_ctrl)
00471         
00472     def on_error_item_activate(self, event):
00473         self._on_item_activate(event, self._error_tree_ctrl)
00474         
00475     def on_warning_item_activate(self, event):
00476         self._on_item_activate(event, self._warning_tree_ctrl)
00477         
00478     def _on_item_activate(self, event, tree_ctrl):
00479         id = event.GetItem()
00480         if id == None:
00481             event.Skip()
00482             return
00483 
00484         if tree_ctrl.ItemHasChildren(id):
00485             tree_ctrl.Expand(id)
00486 
00487         item = tree_ctrl.GetPyData(id)
00488         if not (item and item.status):
00489             event.Skip()
00490             return
00491 
00492         name = item.status.name
00493         
00494         if (self._viewers.has_key(name)):
00495             self._viewers[name].Raise()
00496         else:
00497             title = get_nice_name(name)
00498             
00499             
00500             viewer = StatusViewerFrame(self._frame, name, self, title)
00501             viewer.SetSize(wx.Size(500, 600))
00502             viewer.Layout()
00503             viewer.Center()
00504             viewer.Show(True)
00505             viewer.Raise()
00506     
00507             self._viewers[name] = viewer
00508     
00509             if (self._timeline.is_paused() or self._rxbag):
00510                 viewer.disable_timeline()
00511                 viewer.set_status(item.status)
00512             else:
00513                 msgs = self._timeline.get_messages()
00514                 states = []
00515                 for msg in msgs:
00516                     state = State()
00517                     state.update(msg)
00518                     states.append(state)
00519                     
00520                 for state in states:
00521                     all = state.get_items()
00522                     if (all.has_key(item.status.name)):
00523                         viewer.set_status(all[item.status.name].status)
00524                         
00525             
00526 
00527     
00528     
00529     
00530     
00531     def get_top_level_state(self):
00532         if (self._is_stale):
00533             return 3
00534         
00535         level = -1
00536         min_level = 255
00537 
00538         if len(self._state.get_items()) == 0:
00539             return level
00540 
00541         for item in self._state.get_items().itervalues():
00542             
00543             if self._state.get_parent(item) is not None:
00544                 continue
00545 
00546             if item.status.level > level:
00547                 level = item.status.level
00548             if item.status.level < min_level:
00549                 min_level = item.status.level
00550               
00551         
00552         if level > 2 and min_level <= 2:
00553             level = 2
00554 
00555         return level
00556 
00557     def get_color_for_message(self, msg):
00558         level = 0
00559         min_level = 255
00560         
00561         lookup = {}
00562         for status in msg.status:
00563             lookup[status.name] = status
00564             
00565         names = [status.name for status in msg.status]
00566         names = [name for name in names if len(get_parent_name(name)) == 0]
00567         for name in names:
00568             status = lookup[name]
00569             if (status.level > level):
00570                 level = status.level
00571             if (status.level < min_level):
00572                 min_level = status.level
00573 
00574         
00575         if (level > 2 and min_level <= 2):
00576             level = 2
00577 
00578                 
00579         return color_dict[level]