monitor_panel.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # Software License Agreement (BSD License)
00003 #
00004 # Copyright (c) 2008, Willow Garage, Inc.
00005 # All rights reserved.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions
00009 # are met:
00010 #
00011 #  * Redistributions of source code must retain the above copyright
00012 #    notice, this list of conditions and the following disclaimer.
00013 #  * Redistributions in binary form must reproduce the above
00014 #    copyright notice, this list of conditions and the following
00015 #    disclaimer in the documentation and/or other materials provided
00016 #    with the distribution.
00017 #  * Neither the name of the Willow Garage nor the names of its
00018 #    contributors may be used to endorse or promote products derived
00019 #    from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 # POSSIBILITY OF SUCH DAMAGE.
00033 #
00034 
00035 from __future__ import with_statement
00036 
00037 PKG = 'runtime_monitor'
00038 
00039 import roslib
00040 roslib.load_manifest(PKG)
00041 
00042 import sys
00043 import rospy
00044 
00045 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
00046 
00047 import wx
00048 from wx import xrc
00049 from wx import html
00050 
00051 import threading, time
00052 import cStringIO
00053 import copy
00054 
00055 class TreeItem(object):
00056   ##\param status DiagnosticsStatus : Diagnostic data of item
00057   ##\param tree_id wxTreeItemId : Tree ID of item in display
00058   ##\param stamp ros::Time : Stamp when message was received
00059   def __init__(self, status, tree_id, stamp = None):
00060     self.status = status
00061     self.mark = False
00062     self.stale = False
00063     self.tree_id = tree_id
00064     self.stamp = stamp
00065         
00066 class MonitorPanel(wx.Panel):
00067   def __init__(self, parent, topic="/diagnostics", rxbag=False):
00068     wx.Panel.__init__(self, parent, wx.ID_ANY)
00069     
00070     self._mutex = threading.Lock()
00071     
00072     xrc_path = roslib.packages.get_pkg_dir(PKG) + '/xrc/runtime_monitor_generated.xrc'
00073     
00074     self._xrc = xrc.XmlResource(xrc_path)
00075     self._real_panel = self._xrc.LoadPanel(self, 'RuntimeMonitorInnerPanel')
00076     sizer = wx.BoxSizer(wx.VERTICAL)
00077     sizer.Add(self._real_panel, 1, wx.EXPAND)
00078     self.SetSizer(sizer)
00079 
00080     self._tree_control = xrc.XRCCTRL(self._real_panel, "_tree_control")
00081     self._html_control = xrc.XRCCTRL(self._real_panel, "_html_control")
00082     
00083     image_list = wx.ImageList(16, 16)
00084     self._error_image_id = image_list.AddIcon(wx.ArtProvider.GetIcon(wx.ART_ERROR, wx.ART_OTHER, wx.Size(16, 16)))
00085     self._warning_image_id = image_list.AddIcon(wx.ArtProvider.GetIcon(wx.ART_WARNING, wx.ART_OTHER, wx.Size(16, 16)))
00086     self._ok_image_id = image_list.AddIcon(wx.ArtProvider.GetIcon(wx.ART_TICK_MARK, wx.ART_OTHER, wx.Size(16, 16)))
00087     self._tree_control.AssignImageList(image_list)
00088     self._root_id = self._tree_control.AddRoot("Root")
00089     self._stale_id = self._tree_control.AppendItem(self._root_id, "Stale (0)", self._error_image_id)
00090     self._errors_id = self._tree_control.AppendItem(self._root_id, "Errors (0)", self._error_image_id)
00091     self._warnings_id = self._tree_control.AppendItem(self._root_id, "Warnings (0)", self._warning_image_id)
00092     self._ok_id = self._tree_control.AppendItem(self._root_id, "Ok (0)", self._ok_image_id)
00093 
00094     if rxbag:
00095       wx.CallAfter(self._set_splitter_position)
00096 
00097     self._tree_control.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_item_selected)
00098     self._tree_control.Bind(wx.EVT_TREE_KEY_DOWN, self._on_item_key_down)
00099     
00100     self._name_to_item = {}
00101     
00102     self._new_errors_callback = None
00103     
00104     if not rxbag:
00105       self._subscriber = rospy.Subscriber(topic, DiagnosticArray, self._diagnostics_callback)
00106       self._timer = wx.Timer(self)
00107       self.Bind(wx.EVT_TIMER, self._on_timer)
00108       self._timer.Start(5000)
00109     else:
00110       self._subscriber = None
00111 
00112     self._messages = []
00113     self._used_items = 0
00114     
00115   def _set_splitter_position(self):
00116     self._splitter_control = xrc.XRCCTRL(self._real_panel, "m_splitter1")
00117     self._splitter_control.SetSashPosition(350)
00118     
00119   def __del__(self):
00120     if self._subscriber is not None:
00121       self._subscriber.unregister()
00122       
00123 
00124   def shutdown(self):
00125     """
00126     Unregisters diagnostics subscriber for clean shutdown
00127     """
00128     if rospy.is_shutdown():
00129       return
00130 
00131     if self._subscriber is not None:
00132       self._subscriber.unregister()
00133       self._subscriber = None
00134       
00135   def change_diagnostic_topic(self, topic):
00136     """
00137     Changes diagnostics topic name. Must be of type diagnostic_msgs/DiagnosticArray
00138     """
00139     if len(topic) == 0:
00140       self.reset_monitor()
00141       return
00142 
00143     if self._subscriber is not None:
00144       self._subscriber.unregister()
00145       self._subscriber = rospy.Subscriber(str(topic), DiagnosticArray, self._diagnostics_callback)
00146     self.reset_monitor()
00147 
00148   def reset_monitor(self):
00149     """
00150     Removes all values from monitor display, resets buffers
00151     """
00152     self._name_to_item = {} # Reset all stale topics
00153     self._messages = []
00154     self._clear_tree()
00155 
00156   def _clear_tree(self):
00157     self._tree_control.DeleteChildren(self._stale_id)
00158     self._tree_control.DeleteChildren(self._errors_id)
00159     self._tree_control.DeleteChildren(self._warnings_id)
00160     self._tree_control.DeleteChildren(self._ok_id)
00161     self._update_root_labels()
00162 
00163   def add_rxbag_msg(self, msg, stamp):
00164     """
00165     Add message from rxbag player
00166     """
00167     self._messages.append(msg)
00168 
00169     wx.CallAfter(self.new_message, stamp)
00170 
00171     # Clear stale messages
00172     wx.CallAfter(self._clear_old_rxbag_msgs, stamp)
00173     
00174   def _delete_item(self, item):
00175     self._tree_control.Delete(item.tree_id)
00176     del self._name_to_item[item.status.name]
00177   
00178   def _clear_old_rxbag_msgs(self, now_stamp):
00179     to_delete = []
00180     for name, item in self._name_to_item.iteritems():
00181       if (item != None):
00182         # No future messages
00183         if item.stamp > now_stamp:
00184           to_delete.append(item)
00185           continue
00186 
00187         if (now_stamp - item.stamp).to_sec() > 15.0:
00188           to_delete.append(item)
00189           continue
00190 
00191     for item in to_delete:
00192       self._delete_item(item)
00193 
00194     self._update_root_labels()
00195 
00196   def _diagnostics_callback(self, message):
00197     with self._mutex:
00198       self._messages.append(message)
00199         
00200     wx.CallAfter(self.new_message, rospy.get_rostime())
00201       
00202   def new_message(self, stamp = None):
00203     with self._mutex:
00204       had_errors = False
00205     
00206       for message in self._messages:
00207         for status in message.status:
00208           was_selected = False
00209           had_item = False
00210           if (self._name_to_item.has_key(status.name)):
00211             item = self._name_to_item[status.name]
00212             had_item = True
00213             if (self._tree_control.GetSelection() == item.tree_id):
00214               was_selected = True
00215             if (item.status.level == 2 and status.level != 2):
00216               had_errors = True
00217           
00218             self._update_item(item, status, was_selected, stamp)
00219           else:
00220             self._create_item(status, was_selected, True, stamp)
00221             if (status.level == 2):
00222               had_errors = True
00223     
00224       self._messages = []
00225     
00226     if (had_errors and self._new_errors_callback != None):
00227       self._new_errors_callback()
00228         
00229     self.Refresh()
00230       
00231   def _update_item(self, item, status, was_selected, stamp):
00232     change_parent = False
00233     if (item.status.level != status.level):
00234       change_parent = True
00235       
00236     if (change_parent):
00237       self._tree_control.Delete(item.tree_id)
00238       
00239       if (status.level == 0):
00240         parent_id = self._ok_id
00241       elif (status.level == 1):
00242         parent_id = self._warnings_id
00243       elif (status.level == -1):
00244         parent_id = self._stale_id
00245       else:
00246         parent_id = self._errors_id
00247       
00248       id = self._tree_control.AppendItem(parent_id, status.name + ": " + status.message)
00249       item.tree_id = id
00250       item.stamp = stamp 
00251       self._tree_control.SetPyData(id, item)
00252       
00253       if (status.level > 1 or status.level == -1):
00254         self._tree_control.Expand(parent_id)
00255       
00256       self._tree_control.SortChildren(parent_id)
00257         
00258       if (was_selected):
00259         self._tree_control.SelectItem(item.tree_id)
00260       
00261     else:
00262       self._tree_control.SetItemText(item.tree_id, status.name + ": " + status.message)
00263       
00264     item.status = status
00265     
00266     if (was_selected):
00267       self._fillout_info(item.tree_id)
00268       
00269     item.mark = True
00270     
00271   def _create_item(self, status, select, expand_if_error, stamp):
00272     if (status.level == 0):
00273       parent_id = self._ok_id
00274     elif (status.level == 1):
00275       parent_id = self._warnings_id
00276     elif (status.level == -1):
00277       parent_id = self._stale_id
00278     else:
00279       parent_id = self._errors_id
00280     
00281     id = self._tree_control.AppendItem(parent_id, status.name + ": " + status.message)
00282     item = TreeItem(status, id, stamp)
00283     self._tree_control.SetPyData(id, item)
00284     
00285     self._name_to_item[status.name] = item
00286     
00287     self._tree_control.SortChildren(parent_id)
00288     
00289     if (select):
00290       self._tree_control.SelectItem(id)
00291         
00292     if (expand_if_error and (status.level > 1 or status.level == -1)):
00293       self._tree_control.Expand(parent_id)
00294         
00295     self._update_root_labels()
00296     
00297     item.mark = True
00298     
00299     return item
00300           
00301   def _fillout_info(self, id):
00302     item = self._tree_control.GetPyData(id)
00303     if (item == None):
00304       return
00305       
00306     status = item.status
00307     
00308     self._html_control.Freeze()
00309     s = cStringIO.StringIO()
00310     
00311     s.write("<html><body>")
00312     s.write("<b>Component</b>: %s<br>\n" % (status.name))
00313     s.write("<b>Message</b>: %s<br>\n" % (status.message))
00314     s.write("<b>Hardware ID</b>: %s<br><br>\n\n" % (status.hardware_id)) 
00315     
00316     s.write('<table border="1" cellpadding="2" cellspacing="0">')
00317     for value in status.values:
00318       value.value = value.value.replace("\n", "<br>")
00319       s.write("<tr><td><b>%s</b></td> <td>%s</td></tr>\n" % (value.key, value.value))
00320         
00321     s.write("</table></body></html>")
00322         
00323     (x, y) = self._html_control.GetViewStart()
00324     self._html_control.SetPage(s.getvalue())
00325     self._html_control.Scroll(x, y)
00326         
00327     self._html_control.Thaw()
00328           
00329   def _on_item_selected(self, event):
00330     self._fillout_info(event.GetItem())
00331       
00332   def _on_item_key_down(self, event):
00333     id = self._tree_control.GetSelection()
00334     if (id == None):
00335       event.Skip()
00336       return
00337     
00338     key = event.GetKeyCode()
00339     
00340     if (key == wx.WXK_DELETE):
00341       item = self._tree_control.GetPyData(id)
00342       if (item != None):
00343           self._tree_control.Delete(id)
00344           del self._name_to_item[item.status.name]
00345     else:
00346       event.Skip()
00347       
00348     self._update_root_labels()
00349     self.Refresh()
00350       
00351       
00352   def _on_timer(self, event):
00353     for name, item in self._name_to_item.iteritems():
00354       id = item.tree_id
00355       if (item != None):
00356         if (not item.mark):
00357           was_selected = False
00358           if (self._tree_control.GetSelection() == id):
00359             was_selected = True
00360           
00361           new_status = copy.deepcopy(item.status)
00362           new_status.level = -1
00363           self._update_item(item, new_status, was_selected, item.stamp)
00364             
00365         item.mark = False
00366     
00367         
00368     self._update_root_labels()
00369     self.Refresh()
00370       
00371   def set_new_errors_callback(self, callback):
00372     self._new_errors_callback = callback
00373 
00374   def get_num_errors(self):
00375     return self._tree_control.GetChildrenCount(self._errors_id, False) + self._tree_control.GetChildrenCount(self._stale_id, False)
00376   
00377   def get_num_warnings(self):
00378     return self._tree_control.GetChildrenCount(self._warnings_id, False)
00379   
00380   def get_num_ok(self):
00381     return self._tree_control.GetChildrenCount(self._ok_id, False)
00382 
00383   def _update_root_labels(self):
00384     self._tree_control.SetItemText(self._ok_id, "Ok (%s)" % (self._tree_control.GetChildrenCount(self._ok_id, False)))
00385     self._tree_control.SetItemText(self._warnings_id, "Warnings (%s)" % (self._tree_control.GetChildrenCount(self._warnings_id, False)))
00386     self._tree_control.SetItemText(self._errors_id, "Errors (%s)" % (self._tree_control.GetChildrenCount(self._errors_id, False)))
00387     self._tree_control.SetItemText(self._stale_id, "Stale (%s)" % (self._tree_control.GetChildrenCount(self._stale_id, False)))


runtime_monitor
Author(s): Tully Foote
autogenerated on Sat Dec 28 2013 16:53:40