component_qual.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Software License Agreement (BSD License)
00004 #
00005 # Copyright (c) 2009, Willow Garage, Inc.
00006 # All rights reserved.
00007 #
00008 # Redistribution and use in source and binary forms, with or without
00009 # modification, are permitted provided that the following conditions
00010 # are met:
00011 #
00012 #  * Redistributions of source code must retain the above copyright
00013 #    notice, this list of conditions and the following disclaimer.
00014 #  * Redistributions in binary form must reproduce the above
00015 #    copyright notice, this list of conditions and the following
00016 #    disclaimer in the documentation and/or other materials provided
00017 #    with the distribution.
00018 #  * Neither the name of the Willow Garage nor the names of its
00019 #    contributors may be used to endorse or promote products derived
00020 #    from this software without specific prior written permission.
00021 #
00022 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033 # POSSIBILITY OF SUCH DAMAGE.
00034 
00035 ##\author Kevin Watts
00036 
00037 PKG = 'qualification'
00038 import roslib; roslib.load_manifest(PKG)
00039 
00040 import rospy
00041 
00042 import os
00043 import sys
00044 import socket
00045 import wx
00046 #import time
00047 from wx import xrc
00048 from wx import html
00049 
00050 from xml.dom import minidom
00051 
00052 from qualification.test import *
00053 from qualification.qual_frame import *
00054 
00055 from qualification.test_loader import load_configs_from_map, load_tests_from_map, load_wg_station_map
00056 
00057 TESTS_DIR = os.path.join(roslib.packages.get_pkg_dir(PKG), 'tests')
00058 CONFIG_DIR = os.path.join(roslib.packages.get_pkg_dir(PKG), 'config')
00059 
00060 class ConfigObject(QualTestObject):
00061   def __init__(self, name, serial):
00062     QualTestObject.__init__(self, name, serial)
00063     self._config = True
00064 
00065 class ComponentQualOptions(QualOptions):
00066   def __init__(self):
00067     QualOptions.__init__(self)
00068 
00069 
00070 ## Allows user to choose which component to test and which test to run
00071 class SerialPanel(wx.Panel):
00072   ##@param parent wxPanel: Parent frame
00073   ##@param resource: XRC resource to load panel
00074   ##@param qualification_frame: Qualfication frame to pass test, serial to
00075   def __init__(self, parent, resource, qualification_frame):
00076     wx.Panel.__init__(self, parent)
00077     
00078     self._manager = qualification_frame
00079     
00080     self._res = resource
00081 
00082     self._tests = {}
00083     self._debugs = [] # Hold any SN that can run in debug mode
00084     self._configs = {}
00085     loaded_ok = self.load_test_config_files()
00086     
00087     self._panel = resource.LoadPanel(self, 'serial_panel')
00088     self._sizer = wx.BoxSizer(wx.HORIZONTAL)
00089     
00090     self._sizer.Add(self._panel, 1, wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
00091     self.SetSizer(self._sizer)
00092     self.Layout()
00093     self.Fit()
00094 
00095     # Component qualification tab
00096     self._test_button = xrc.XRCCTRL(self._panel, 'test_button')
00097     self._serial_text = xrc.XRCCTRL(self._panel, 'serial_text')
00098     
00099     self._test_button.Bind(wx.EVT_BUTTON, self.on_test)
00100         
00101     # Config tab
00102     self._serial_text_conf = xrc.XRCCTRL(self._panel, 'serial_text_conf')
00103 
00104     self._config_button = xrc.XRCCTRL(self._panel, 'config_button')
00105     self._config_button.Bind(wx.EVT_BUTTON, self.on_config)
00106 
00107     # Starts on test cart panel
00108     self._notebook = xrc.XRCCTRL(self._panel, 'test_tab_control')
00109     self._notebook.ChangeSelection(0)
00110 
00111     self._panel.Bind(wx.EVT_CHAR, self.on_char)
00112     self._serial_text.SetFocus()
00113 
00114     if not loaded_ok:
00115       wx.MessageBox("Warning: Unable to load tests configuration files. May be corrupt. Revert any changes you have made to these files and retry", "Invalid Configuration Files", wx.OK|wx.ICON_ERROR, self)
00116 
00117   
00118   def load_test_config_files(self):
00119     """
00120     Called at startup or by menu option. Loads configuration files that define tests.
00121     """
00122     tests = {}
00123     debugs = [] # Hold any SN that can run in debug mode
00124     try:
00125       tests_ok = load_tests_from_map(tests, debugs) 
00126       if tests_ok:
00127         self._debugs = debugs
00128         self._tests = tests
00129     except Exception, e:
00130       tests_ok = False
00131 
00132     configs = {}
00133     try:
00134       configs_ok = load_configs_from_map(configs)
00135       if configs_ok:
00136         self._configs = configs
00137     except Exception, e:
00138       configs_ok = False
00139 
00140     return tests_ok and configs_ok
00141 
00142   def _is_debug_test(self, serial):
00143     return serial[0:7] in self._debugs
00144 
00145   ##\brief Checks that serial number is valid using invent_client
00146   ##\param serial str : Serial number to check
00147   ##\param check_pass bool : Check if component has passed qualification
00148   ##\return bool : True if OK. Pop-up box explains error to user if False
00149   def _check_serial_input(self, serial, check_pass = False):
00150     if self._manager.options.debug:
00151       return True
00152 
00153     if self._is_debug_test(serial):
00154       return True
00155 
00156     iv = self._manager.get_inventory_object()
00157     if not iv or not iv.login():
00158       wx.MessageBox("Unable to check serial number. Unable to login in Inventory system", "Error - Unable to check serial number", wx.OK|wx.ICON_ERROR, self)
00159       return False
00160     
00161     if check_pass and not iv.get_test_status(serial):
00162       wx.MessageBox("Component has not passed qualification. Please qualify the component.", "Error - Unqualified Component", wx.OK|wx.ICON_ERROR, self)
00163       return False
00164     
00165     return iv.check_serial_valid(serial)
00166 
00167   def on_config(self, event):
00168     # Get selected launch file
00169     serial = str(self._serial_text_conf.GetValue())
00170 
00171     if not self._check_serial_input(serial, True):
00172       wx.MessageBox('Invalid serial number, unable to configure. Check serial number and retry.','Error - Invalid serial number', wx.OK|wx.ICON_ERROR, self)
00173       return
00174 
00175     if not self.has_conf_script(serial):
00176       wx.MessageBox('No configuration procedure defined for that serial number.','Error - Invalid serial number', wx.OK|wx.ICON_ERROR, self)
00177       return
00178 
00179     config_test = self.select_conf_to_load(serial)
00180     if config_test is None:
00181       return
00182 
00183     if not config_test.validate():
00184       wx.MessageBox('Unable to load configuration data and parameters. Check file and try again.','Failed to load test', wx.OK|wx.ICON_ERROR, self)
00185       return 
00186     
00187     item = ConfigObject(config_test.get_name(), serial)
00188 
00189     self._manager.begin_test(config_test, item)
00190 
00191   def has_conf_script(self, serial):
00192     return self._configs.has_key(serial[0:7])
00193 
00194   def has_test(self, serial):
00195     return self._tests.has_key(serial[0:7])
00196 
00197   def _check_assembly(self, serial):
00198     if self._manager.options.debug:
00199       return True
00200 
00201     if self._is_debug_test(serial):
00202       return True
00203 
00204     iv = self._manager.get_inventory_object()
00205     
00206     return iv.check_assembled(serial, recursive = True)
00207 
00208   def on_test(self, event):
00209     serial = self._serial_text.GetValue()
00210     
00211     if not self._check_serial_input(serial):
00212       wx.MessageBox('Invalid serial number, unable to test component. Check serial number and retry.','Error - Invalid serial number', wx.OK|wx.ICON_ERROR, self)
00213       return
00214 
00215     if not self.has_test(serial):
00216       wx.MessageBox('No test defined for that serial number.','Error - Invalid serial number', wx.OK|wx.ICON_ERROR, self)
00217       return
00218 
00219     short_serial = serial[0:7]
00220 
00221     my_test = self.select_test_to_load(short_serial)
00222     
00223     if my_test is None:
00224       return
00225 
00226     if not my_test.validate():
00227       wx.MessageBox('Unable to load test data and parameters. Check file and try again.','Failed to load test', wx.OK|wx.ICON_ERROR, self)
00228       return 
00229 
00230     if my_test.check_assembly and not self._check_assembly(serial):
00231       wx.MessageBox('Component is not properly assembled in Invent. Use the "Assemble" page in Invent to verify component assembly.',
00232                     'Component Not Assembled', wx.OK|wx.ICON_ERROR, self)
00233       return
00234       
00235 
00236     # Item
00237     item = QualTestObject(my_test.get_name(), serial)
00238   
00239     self._manager.begin_test(my_test, item)
00240  
00241   # Select if we have multiple, etc
00242   def select_string_from_list(self, msg, lst):
00243     if len(lst) == 1:
00244       return lst[0]
00245 
00246     # Load robot selection dialog
00247     dialog = self._res.LoadDialog(self, 'select_test_dialog')
00248     select_text = xrc.XRCCTRL(dialog, 'select_text')
00249     select_text.SetLabel(msg)
00250     test_box = xrc.XRCCTRL(dialog, 'test_list_box')
00251     test_box.InsertItems(lst, 0)
00252     test_box.SetSelection(0)
00253     
00254     select_text.Wrap(270)
00255 
00256     dialog.Layout()
00257 
00258     # return string of test folder/file to run
00259     if (dialog.ShowModal() == wx.ID_OK):
00260       desc = test_box.GetStringSelection()
00261       dialog.Destroy()
00262       return desc
00263     else: 
00264       dialog.Destroy()
00265       return None
00266 
00267   def select_conf_to_load(self, serial):
00268     short_serial = serial[0:7]
00269     
00270     # Load select_test_dialog
00271     configs_by_descrip = {}
00272     for conf in self._configs[short_serial]:
00273       configs_by_descrip[conf.get_name()] = conf
00274       
00275     msg = 'Select configuration type'
00276 
00277     descrips = configs_by_descrip.keys()
00278     descrips.sort()
00279   
00280     choice = self.select_string_from_list(msg, descrips)
00281     if choice is None:
00282       return None
00283     return configs_by_descrip[choice]
00284     
00285   # If more than one test for that serial, calls up prompt to ask user to select
00286   def select_test_to_load(self, short_serial):
00287     # Load select_test_dialog
00288     tests_by_descrip = {}
00289     for test in self._tests[short_serial]:
00290       tests_by_descrip[test.get_name()] = test
00291     
00292     descrips = tests_by_descrip.keys()
00293     descrips.sort()
00294     
00295     msg = 'Select component or component type to qualify.'
00296 
00297     choice = self.select_string_from_list(msg, descrips)
00298     if choice is None:
00299       return None
00300     return tests_by_descrip[choice]
00301 
00302   def on_char(self, event):
00303     # 347 is the keycode sent at the beginning of a barcode
00304     if (event.GetKeyCode() == 347):
00305       # Clear the old contents and put focus in the serial box so the rest of input goes there
00306       self._serial_text.Clear()
00307       self._serial_text_conf.Clear()
00308 
00309 class ComponentQualFrame(QualificationFrame):
00310   def __init__(self, parent, options):
00311     self._serial_panel = None
00312     QualificationFrame.__init__(self, parent, options)
00313     
00314     self.load_wg_test_map()
00315     self.create_menubar()
00316 
00317   def get_loader_panel(self):
00318     if not self._serial_panel:
00319       self._serial_panel = SerialPanel(self._top_panel, self._res, self)
00320     return self._serial_panel
00321   
00322   ##\todo Overloaded in subclasses
00323   def create_menubar(self):
00324     menubar = wx.MenuBar()
00325     self._file_menu = wx.Menu()
00326     self._file_menu.Append(1001, "Invent Login\tCTRL+l")
00327     self._file_menu.Append(1002, "R&eload Test/Config Lists")
00328     self._file_menu.Append(1003, "Reload W&G Station Map")
00329     self._file_menu.Append(wx.ID_EXIT, "E&xit")
00330     menubar.Append(self._file_menu, "&File")
00331 
00332     self._options_menu = wx.Menu()
00333     self._options_menu.AppendCheckItem(2001, "Always Show Results")
00334     menubar.Append(self._options_menu, "&Options")
00335 
00336     self._powerboard_menu = wx.Menu()
00337     self._powerboard_menu.Append(3101, "Power Board\tCTRL+b")
00338     self._powerboard_menu.AppendCheckItem(3150, "Breaker 0\tCTRL+0")
00339     self._powerboard_menu.AppendCheckItem(3151, "Breaker 1\tCTRL+1")
00340     self._powerboard_menu.AppendCheckItem(3152, "Breaker 2\tCTRL+2")
00341     self._powerboard_menu.Check(3150, rospy.get_param('/qualification/powerboard/0'))
00342     self._powerboard_menu.Check(3151, rospy.get_param('/qualification/powerboard/1'))
00343     self._powerboard_menu.Check(3152, rospy.get_param('/qualification/powerboard/2'))
00344 
00345     menubar.Append(self._powerboard_menu, "&Powerboard")
00346 
00347     self._host_menu = wx.Menu()
00348     self._host_menu.Append(4001, "Select Test Host\tCTRL+h")
00349     menubar.Append(self._host_menu, "Test &Host")
00350 
00351     self._debug_menu = None
00352     if self.options.debug:
00353       self._debug_menu = wx.Menu()
00354       self._debug_menu.Append(5005, "Abort Subtest")
00355       self._debug_menu.Append(5001, "Continuous Testing")
00356       menubar.Append(self._debug_menu, "Debug Mode")
00357 
00358     self.SetMenuBar(menubar)
00359     self.Bind(wx.EVT_MENU, self.on_menu)
00360 
00361   def get_powerboard(self):
00362     while not rospy.is_shutdown():
00363       # Get powerboard from user
00364       serial = wx.GetTextFromUser('Enter last four digits of power board serial number', 'Select Power Board', rospy.get_param('/qualification/powerboard/serial', ''))
00365       if len(serial) == 0:
00366         return
00367       if len(serial) == 4 and unicode(serial).isnumeric():
00368         rospy.set_param('/qualification/powerboard/serial', serial)
00369         return
00370       are_you_sure = wx.MessageDialog(self, 'Invalid powerboard ID. Retry?', 'Invalid serial',
00371                                       wx.OK|wx.CANCEL)
00372       if are_you_sure.ShowModal() != wx.ID_OK:
00373         return
00374 
00375   def on_menu(self, event):
00376     if (event.GetEventObject() == self._file_menu):
00377       if (event.GetId() == wx.ID_EXIT):
00378         self.Close()
00379         return
00380       if (event.GetId() == 1001):
00381         self.login_to_invent()
00382         return
00383       if (event.GetId() == 1002):
00384         if not self._serial_panel.load_test_config_files():
00385           wx.MessageBox('Configuration files for loading tests are invalid. Check the files "tests/tests.xml" and "config/configs.xml" and retry.',
00386                         'Invalid Configuration Files', wx.OK|wx.ICON_ERROR, self)
00387         else:
00388           wx.MessageBox('Reloaded configuration files successfully.',
00389                         'Reloaded Configuration Files', wx.OK, self)
00390         return
00391       if (event.GetId() == 1003):
00392         if not self.load_wg_test_map():
00393           wx.MessageBox('Configuration file for loading WG station map is invalid. Check the file wg_map.xml" and retry.',
00394                         'Invalid Configuration File', wx.OK|wx.ICON_ERROR, self)
00395         else:
00396           wx.MessageBox('Reloaded WG station map configuration file successfully.',
00397                         'Reloaded Configuration File', wx.OK, self)
00398         return
00399 
00400     if (event.GetEventObject() == self._options_menu):
00401       if (event.GetId() == 2001):
00402         self.options.always_show_results = self._options_menu.IsChecked(2001)
00403 
00404     if (event.GetEventObject() == self._powerboard_menu):
00405       if (event.GetId() == 3101):
00406         self.get_powerboard()
00407       
00408       if (event.GetId() == 3150):
00409         rospy.set_param('/qualification/powerboard/0', self._powerboard_menu.IsChecked(3150))
00410       if (event.GetId() == 3151):
00411         rospy.set_param('/qualification/powerboard/1', self._powerboard_menu.IsChecked(3151))
00412       if (event.GetId() == 3152):
00413         rospy.set_param('/qualification/powerboard/2', self._powerboard_menu.IsChecked(3152))
00414 
00415     if (event.GetEventObject() == self._host_menu):
00416       if (event.GetId() == 4001):
00417         self.set_test_host()
00418 
00419     if (event.GetEventObject() == self._debug_menu):
00420       if (event.GetId() == 5005):
00421         self.abort_active_test(True)
00422       if (event.GetId() == 5001):
00423         self.start_continuous_testing()
00424 
00425 
00426 
00427   def abort_active_test(self, can_veto):
00428     if can_veto:
00429       are_you_sure = wx.MessageDialog(self, "Are you sure you want to abort the current operation?", 
00430                                       "Confirm Abort", wx.OK|wx.CANCEL)
00431       if are_you_sure.ShowModal() != wx.ID_OK:
00432         return
00433 
00434     if self._subtest_launch is not None:
00435       self.log('Aborting subtest')
00436       self.subtest_finished(subtest_timeout(0))
00437       return
00438 
00439     if self._prestartup_launch is not None:
00440       self.log('Aborting pretest')
00441       self.prestartup_finished(script_timeout(0))
00442       return
00443 
00444     if self._shutdown_launch is not None:
00445       self.log('Aborting shutdown script')
00446       self.shutdown_finished(script_timeout(0))
00447 
00448     wx.MessageBox('No subtests, shutdown scripts or pretests to abort. Press the "Cancel" button to terminate test', 'Unable to abort', wx.OK)
00449     
00450 
00451   def set_test_host(self):
00452     curr_host = os.environ['ROS_TEST_HOST']
00453     
00454     while not rospy.is_shutdown():
00455       host = wx.GetTextFromUser('Enter test host', 'Test Host', curr_host)
00456       
00457       if host == '':
00458         wx.MessageBox('Host name unchanged. Current hostname: "%s".' % curr_host, 'Test Host Unchanged', wx.OK)
00459         break
00460 
00461       try:
00462         machine_addr = socket.gethostbyname(host)
00463         os.environ['ROS_TEST_HOST'] = host
00464         break
00465       except socket.gaierror:
00466         wx.MessageBox('Hostname %s is invalid. Try again or click "Cancel".' % host, 'Test Host Invalid', wx.OK)
00467         
00468   def load_wg_test_map(self):
00469     wgstations = {}
00470     load_ok = load_wg_station_map(wgstations)
00471 
00472     gui_name = socket.gethostname()
00473 
00474     # Sets default host, powerboard to None
00475     rospy.set_param('/qualification/powerboard/serial', '0000')
00476     rospy.set_param('/qualification/powerboard/0', False)
00477     rospy.set_param('/qualification/powerboard/1', False)
00478     rospy.set_param('/qualification/powerboard/2', False)
00479     os.environ['ROS_TEST_HOST'] = gui_name
00480     
00481     if not load_ok:
00482       wx.MessageBox('Error: Unable to parse \'qualification/wg_map.xml\'. Please check the document and retry.','Unable to parse configuration', wx.OK|wx.ICON_ERROR, self)
00483 
00484       return False
00485 
00486     if not gui_name in wgstations:
00487       wx.MessageBox('Warning: Host %s not found in list of known hosts. Check file: \'qualification/wg_map.xml\'. You may be unable to run certain qualification tests' % gui_name,'Host not found', wx.OK|wx.ICON_ERROR, self)
00488       return False
00489 
00490     my_station = wgstations[gui_name]
00491     rospy.set_param('/qualification/powerboard/serial', my_station.powerboard)
00492     rospy.set_param('/qualification/powerboard/0', my_station.breaker0)
00493     rospy.set_param('/qualification/powerboard/1', my_station.breaker1)
00494     rospy.set_param('/qualification/powerboard/2', my_station.breaker2)
00495 
00496     my_station.set_envs()
00497 
00498     try:
00499       machine_addr = socket.gethostbyname(my_station.test_host)
00500       os.environ['ROS_TEST_HOST'] = my_station.test_host
00501     except socket.gaierror:
00502       wx.MessageBox('Unable to resolve remote test host %s. The file: \'qualification/wg_map.xml\' may be invalid.' % my_station.test_host, 'Remote Host Not Found', wx.OK|wx.ICON_ERROR, self)
00503       return False
00504 
00505     return True
00506     
00507 
00508 
00509 
00510       
00511       
00512 
00513 
00514 


qualification
Author(s): Kevin Watts (watts@willowgarage.com), Josh Faust (jfaust@willowgarage.com)
autogenerated on Sat Dec 28 2013 17:57:34