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 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
00057
00058
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 = {}
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
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
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)))