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
00038 from __future__ import with_statement
00039 PKG = 'life_test'
00040 import roslib
00041 roslib.load_manifest(PKG)
00042
00043 import rospy
00044
00045 import socket
00046 import os
00047 import sys
00048 import datetime
00049 import wx
00050 import time
00051 import traceback
00052 import threading
00053 from wx import xrc
00054 from wx import html
00055
00056 import threading
00057
00058
00059 from test_monitor_panel import TestMonitorPanel
00060 from test_param import *
00061 from test_bay import *
00062 from config_loader import *
00063
00064
00065 from pr2_self_test_msgs.msg import TestInfoArray
00066 from pr2_power_board.srv import PowerBoardCommand
00067 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
00068 from std_msgs.msg import Empty
00069
00070 from roslaunch_caller import roslaunch_caller
00071 from wg_invent_client import Invent
00072
00073 class TestManagerFrame(wx.Frame):
00074 def __init__(self, parent, debug = False):
00075 wx.Frame.__init__(self, parent, wx.ID_ANY, "Test Manager")
00076
00077 self._mutex = threading.Lock()
00078
00079 self.create_menu_bar()
00080
00081 self._debug = debug
00082
00083
00084 self._tests_by_serial = {}
00085
00086
00087 xrc_path = os.path.join(roslib.packages.get_pkg_dir('life_test'), 'xrc/gui.xrc')
00088 self._xrc = xrc.XmlResource(xrc_path)
00089
00090
00091 os.environ['ROSLAUNCH_SSH_UNKNOWN'] = '1'
00092
00093
00094 self._invent_client = None
00095
00096 self._active_bays = []
00097 self._active_boards = {}
00098
00099 self.load_tests_from_file()
00100 self.load_rooms_from_file()
00101
00102 self._main_panel = self._xrc.LoadPanel(self, 'manager_panel')
00103
00104 self._tab_ctrl = xrc.XRCCTRL(self._main_panel, 'test_tab_control')
00105 self._start_panel = xrc.XRCCTRL(self._tab_ctrl, 'start_panel')
00106 self._serial_text = xrc.XRCCTRL(self._start_panel, 'serial_text')
00107 self._start_button = xrc.XRCCTRL(self._start_panel, 'start_button')
00108 self._start_button.Bind(wx.EVT_BUTTON, self.on_start)
00109
00110 self._log_text = xrc.XRCCTRL(self._start_panel, 'log_text')
00111
00112 self.Bind(wx.EVT_CLOSE, self.on_close)
00113 self._serial_text.SetFocus()
00114
00115
00116 self._test_panels = {}
00117 self._active_serials = []
00118
00119 self._current_log = []
00120
00121 self._diag_sub = rospy.Subscriber('/diagnostics', DiagnosticArray, self._diag_cb)
00122 self._diags = []
00123
00124 self._power_node = None
00125 self._power_cmd = rospy.ServiceProxy('power_board/control', PowerBoardCommand)
00126
00127 self.log('Started Test Manager')
00128
00129 self._info_pub = rospy.Publisher('test_info', TestInfoArray)
00130
00131 self._heartbeat_pub = rospy.Publisher('/heartbeat', Empty)
00132
00133 self._info_timer = wx.Timer(self, 1)
00134 self.Bind(wx.EVT_TIMER, self.on_info_timer, self._info_timer)
00135 self._info_timer.Start(500)
00136
00137 self._heartbeat_timer = wx.Timer()
00138 self._heartbeat_timer.Bind(wx.EVT_TIMER, self.on_heartbeat_timer)
00139 self._heartbeat_timer.Start(1000)
00140
00141 def shutdown(self):
00142 if self._power_node is not None:
00143 self._power_node.shutdown()
00144 self._power_node = None
00145
00146 def __del__(self):
00147 self.shutdown()
00148
00149 @property
00150 def invent_client(self):
00151 return self._invent_client
00152
00153 def on_info_timer(self, event):
00154 array = TestInfoArray()
00155 for serial, panel in self._test_panels.iteritems():
00156 array.data.append(panel.on_status_check())
00157 self._info_pub.publish(array)
00158
00159 def on_heartbeat_timer(self, event):
00160 beat = Empty()
00161 self._heartbeat_pub.publish(beat)
00162
00163 def _diag_cb(self, msg):
00164 with self._mutex:
00165 self._diags.append(msg)
00166
00167 wx.CallAfter(self._new_diag)
00168
00169
00170
00171 def _new_diag(self):
00172 with self._mutex:
00173
00174 for msg in self._diags:
00175 for status in msg.status:
00176 if status.name.startswith("Power board"):
00177 board_sn = int(status.name.split()[2])
00178 if self._active_boards.has_key(board_sn):
00179 runstop = False
00180 runbutton = False
00181 for val in status.values:
00182 if val.key == "RunStop Button Status":
00183 runbutton = (val.value == "True")
00184 if val.key == "RunStop Status":
00185 runstop = (val.value == "True")
00186 estop = runstop and runbutton
00187
00188 for val in status.values:
00189 if val.key.startswith("Breaker "):
00190 for breaker in self._active_boards[board_sn].keys():
00191 if val.key == "Breaker %d State" % breaker:
00192 panel = self._active_boards[board_sn][breaker]
00193 self._test_panels[panel].update_board(val.value, estop)
00194
00195
00196
00197 self._diags = []
00198
00199
00200
00201
00202
00203 def load_test(self, test, serial):
00204
00205
00206 panel = wx.Panel(self._tab_ctrl, wx.ID_ANY)
00207
00208 self._tab_ctrl.AddPage(panel, test.get_title(serial), True)
00209
00210 test_panel = TestMonitorPanel(panel, self, test, serial)
00211 sizer = wx.BoxSizer(wx.VERTICAL)
00212 sizer.Add(test_panel, 1, wx.EXPAND)
00213 panel.SetSizer(sizer)
00214 panel.Fit()
00215 panel.Layout()
00216
00217 self._test_panels[serial] = test_panel
00218 self._active_serials.append(serial)
00219
00220 def close_tab(self, serial):
00221 if not serial in self._active_serials:
00222 rospy.logerr('Requested serial not in active serials: %s' % serial)
00223 return
00224
00225 idx = self._active_serials.index(serial)
00226
00227 if self._tab_ctrl.DeletePage(idx + 1):
00228 del self._test_panels[serial]
00229 del self._active_serials[idx]
00230
00231 def test_start_check(self, bay, serial):
00232 if bay in self._active_bays:
00233 return False
00234
00235 self._active_bays.append(bay)
00236
00237 if bay.board is not None:
00238 self._active_boards.setdefault(bay.board, {})[bay.breaker] = serial
00239
00240 if bay.board is not None and self._power_node is None:
00241 self._power_node = roslaunch_caller.ScriptRoslaunch('<launch><node pkg="pr2_power_board" type="power_node" name="power_board" respawn="true" /></launch>')
00242 self._power_node.start()
00243
00244 return True
00245
00246 def test_stop(self, bay):
00247 if bay in self._active_bays:
00248 idx = self._active_bays.index(bay)
00249 del self._active_bays[idx]
00250
00251 if bay.board is not None:
00252 del self._active_boards[bay.board][bay.breaker]
00253 if len(self._active_boards[bay.board].keys()) == 0:
00254 del self._active_boards[bay.board]
00255
00256 if len(self._active_boards.keys()) == 0 and self._power_node is not None:
00257 self._power_node.shutdown()
00258 self._power_node = None
00259
00260
00261 def _reset_power_disable(self, bay):
00262 try:
00263 rospy.wait_for_service('power_board/control', 5)
00264 resp = self._power_cmd(bay.board, bay.breaker, 'reset', 0)
00265 if resp.retval != 0:
00266 rospy.logerr('Failed to reset board %s, breaker %d. Retval: %s' % (bay.board, bay.breaker, resp.retval))
00267 return False
00268
00269 time.sleep(1)
00270 return True
00271 except:
00272 rospy.logerr(traceback.format_exc())
00273 return False
00274
00275
00276 def power_run(self, bay):
00277 try:
00278 if not self._reset_power_disable(bay):
00279 return False
00280
00281 resp = self._power_cmd(bay.board, bay.breaker, 'start', 0)
00282 return resp.retval == 0
00283 except:
00284 rospy.logerr(traceback.format_exc())
00285 return False
00286
00287 def power_standby(self, bay):
00288 try:
00289 if not self._reset_power_disable(bay):
00290 return False
00291
00292 resp = self._power_cmd(bay.board, bay.breaker, 'stop', 0)
00293 return resp.retval == 0
00294 except:
00295 return False
00296
00297 def power_disable(self, bay):
00298 try:
00299 rospy.wait_for_service('power_board/control', 5)
00300 resp = self._power_cmd(bay.board, bay.breaker, 'disable', 0)
00301 return resp.retval == 0
00302 except:
00303 return False
00304
00305 def load_invent_client(self):
00306
00307
00308 if self._invent_client:
00309 return True
00310
00311 invent = None
00312 dialog = self._xrc.LoadDialog(self, 'username_password_dialog')
00313 xrc.XRCCTRL(dialog, 'login_text').Wrap(300)
00314 dialog.Layout()
00315 dialog.Fit()
00316 username_ctrl = xrc.XRCCTRL(dialog, 'username')
00317 password_ctrl = xrc.XRCCTRL(dialog, 'password')
00318 username_ctrl.SetFocus()
00319
00320 username_ctrl.SetMinSize(wx.Size(200, -1))
00321 password_ctrl.SetMinSize(wx.Size(200, -1))
00322
00323 while not rospy.is_shutdown():
00324 if (dialog.ShowModal() == wx.ID_OK):
00325 username = username_ctrl.GetValue()
00326 password = password_ctrl.GetValue()
00327
00328 invent = Invent(username, password)
00329 if (invent.login() == False):
00330 wx.MessageBox('Please enter a valid username and password.',
00331 'Valid Login Required', wx.OK|wx.ICON_ERROR,
00332 self)
00333 else:
00334 self._invent_client = invent
00335 return True
00336 else:
00337 return False
00338
00339 return False
00340
00341
00342 def on_start(self, event):
00343 serial = self._serial_text.GetValue()
00344
00345 if serial in self._active_serials:
00346 wx.MessageBox('Current component is already testing.', 'Already Testing', wx.OK|wx.ICON_ERROR, self)
00347 return
00348
00349 if not self._debug and not self.load_invent_client():
00350 wx.MessageBox('You cannot proceed without a valid inventory login.', 'Valid Login Required', wx.OK|wx.ICON_ERROR, self)
00351 return
00352 elif self._debug and not self.load_invent_client():
00353 wx.MessageBox('Warning: Without inventory access, a lot of things won\'t really work.',
00354 'No Inventory Access', wx.OK|wx.ICON_ERROR, self)
00355 self._invent_client = None
00356
00357 if (not self._debug) and (not self._invent_client.check_serial_valid(serial)):
00358 wx.MessageBox('Serial number "%s" appears to be invalid. Re-enter the serial number and try again.' % (serial), 'Invalid Serial Number', wx.OK|wx.ICON_ERROR, self)
00359 return
00360
00361 if (not self._debug) and (not self._invent_client.get_test_status(serial)):
00362 wx.MessageBox('Component %s has not passed qualification. Please re-test component and try again' % serial,
00363 'Bad Component', wx.OK|wx.ICON_ERROR, self)
00364 return
00365
00366
00367
00368 test = self.select_test(serial)
00369
00370
00371 if not test:
00372 wx.MessageBox('No test defined for that serial number or no test selected. Please try again.', 'No test', wx.OK|wx.ICON_ERROR, self)
00373 return
00374
00375 self._serial_text.Clear()
00376 self.log('Starting test %s' % test._name)
00377
00378 self.load_test(test, serial)
00379
00380
00381 def load_rooms_from_file(self):
00382 my_rooms = load_rooms_from_file()
00383 if len(my_rooms.items()) == 0:
00384 wx.MessageBox('Unable to load test rooms and bays information from rooms file. Check the file and try again',
00385 'Unable to load rooms', wx.OK|wx.ICON_ERROR, self)
00386 return
00387 self._rooms = my_rooms
00388
00389 if self._rooms.has_key(socket.gethostname()):
00390 self.room = self._rooms[socket.gethostname()]
00391 else:
00392 self.room = TestRoom(socket.gethostname())
00393
00394
00395
00396 def load_tests_from_file(self, test_xml_path = os.path.join(roslib.packages.get_pkg_dir('life_test'), 'tests.xml')):
00397 my_tests = load_tests_from_file(test_xml_path)
00398
00399 if len(my_tests.items()) == 0:
00400 wx.MessageBox('Unable to load test from file %s. Check the file and try again.' % test_xml_path,
00401 'Unable to load', wx.OK|wx.ICON_ERROR, self)
00402 return
00403 self._tests = my_tests
00404
00405
00406
00407 def select_string_from_list(self, msg, lst):
00408
00409 dialog = self._xrc.LoadDialog(self, 'select_test_dialog')
00410 select_text = xrc.XRCCTRL(dialog, 'select_text')
00411 select_text.SetLabel(msg)
00412 test_box = xrc.XRCCTRL(dialog, 'test_list_box')
00413 test_box.InsertItems(lst, 0)
00414
00415 select_text.Wrap(250)
00416
00417 dialog.Layout()
00418 dialog.Fit()
00419
00420
00421 if (dialog.ShowModal() == wx.ID_OK):
00422 desc = test_box.GetStringSelection()
00423 dialog.Destroy()
00424 return desc
00425 else:
00426 dialog.Destroy()
00427 return None
00428
00429 def select_test(self, serial):
00430 short_serial = serial[0:7]
00431
00432 if not self._tests.has_key(short_serial):
00433 return None
00434 if len(self._tests[short_serial]) == 1:
00435 return self._tests[short_serial][0]
00436
00437
00438 tests_by_name = {}
00439 for test in self._tests[short_serial]:
00440 tests_by_name[test._name] = test
00441
00442 msg = 'Select test to run.'
00443
00444 descrips = dict.keys(tests_by_name)
00445 descrips.sort()
00446
00447 choice = self.select_string_from_list(msg, descrips)
00448 if choice is None:
00449 return None
00450 return tests_by_name[choice]
00451
00452 def log(self, msg):
00453 time_str = time.strftime("%m/%d/%Y %H:%M:%S: ", time.localtime(rospy.get_time()))
00454
00455 self._current_log.append(time_str + msg)
00456 self.update_log_display()
00457
00458 def log_test_entry(self, test_name, machine, message):
00459 if not machine:
00460 machine = 'NONE'
00461
00462 time_str = time.strftime("%m/%d/%Y %H:%M:%S: ", time.localtime(rospy.get_time()))
00463
00464 log_msg = time_str + 'Machine %s, Test %s. Message: %s' % (machine, test_name, message)
00465
00466 self._current_log.append(log_msg)
00467 self.update_log_display()
00468
00469 def update_log_display(self):
00470 for log in self._current_log:
00471 self._log_text.AppendText(log + '\n')
00472 self._current_log = []
00473
00474 def on_close(self, event):
00475 if event.CanVeto() and len(self._test_panels) > 0:
00476 wx.MessageBox('Unable to close Test Manager. All component windows must be shut down first.',
00477 'Unable to close', wx.OK|wx.ICON_ERROR, self)
00478 event.Veto()
00479 return
00480
00481
00482 for value in dict.values(self._test_panels):
00483 value.on_close(None)
00484
00485
00486 self.Destroy()
00487
00488
00489 def on_menu(self, event):
00490 if (event.GetEventObject() == self._file_menu):
00491 if (event.GetId() == wx.ID_EXIT):
00492 self.Close()
00493 return
00494 if (event.GetId() == 1001):
00495 self._invent_client = None
00496 self.load_invent_client()
00497 return
00498
00499 if (event.GetEventObject() == self._tests_menu):
00500 if (event.GetId() == 2001):
00501 self.load_tests_from_file()
00502 return
00503 if (event.GetId() == 2002):
00504 self.load_rooms_from_file()
00505 return
00506
00507 def create_menu_bar(self):
00508 menubar = wx.MenuBar()
00509
00510
00511 self._file_menu = wx.Menu()
00512 self._file_menu.Append(1001, "Invent Login\tCTRL+l")
00513 self._file_menu.Append(wx.ID_EXIT, "E&xit")
00514 menubar.Append(self._file_menu, "&File")
00515
00516 self._tests_menu = wx.Menu()
00517 self._tests_menu.Append(2001, "Reload tests (tests.xml)")
00518 self._tests_menu.Append(2002, "Reload rooms and bays (wg_test_rooms.xml)")
00519 menubar.Append(self._tests_menu, "&Tests")
00520
00521 self.SetMenuBar(menubar)
00522 self.Bind(wx.EVT_MENU, self.on_menu)
00523
00524
00525
00526