$search
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)))