projector_monitor.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Software License Agreement (BSD License)
00004 #
00005 # Copyright (c) 2008, 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 Willow Garage, Inc. 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 
00036 ##\author Derek King
00037 ##\brief Listens to /diagnostics, makes sure projector LED is operating properly
00038 PKG = 'qualification'
00039 
00040 import roslib
00041 roslib.load_manifest(PKG)
00042 
00043 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
00044 from std_srvs.srv import *
00045 
00046 from pr2_self_test_msgs.srv import TestResult, TestResultRequest
00047 
00048 import rospy
00049 import threading
00050 import thread
00051 import traceback
00052 from time import sleep
00053 import wx
00054 import socket
00055 import re
00056 
00057 class Statistic:
00058     def __init__(self):
00059         self._sum = 0.0
00060         self._count = 0
00061     def get_avg(self):
00062         if self._count <= 0:
00063             #float('NaN')
00064             return None
00065         return (self._sum / self._count)
00066     def get_max(self):
00067        if self._count <= 0:
00068            return None
00069        return self._max
00070     def get_min(self):
00071         if self._count <= 0:
00072             return None
00073         return self._min
00074     def sample(self, value):
00075         if self._count == 0:
00076             self._max = value
00077             self._min = value
00078         else:
00079             self._max = max([self._max, value])
00080             self._min = min([self._min, value])
00081         self._sum += value
00082         self._count += 1
00083     def summary(self, name):
00084         if self._count == 0:
00085             return "%s : no data samples" % (name)
00086         else:
00087             return "%s : average=%f, min=%f, max=%f" %(name, self.get_avg(), self.get_min(), self.get_max())
00088 
00089 
00090 class TemperatureMonitor:
00091     def __init__(self):
00092         self._mutex = threading.Lock()
00093         self._mutex.acquire()
00094 
00095         self._start_time = rospy.get_time()
00096         self._temp_stat = Statistic()
00097         self._connected = False
00098 
00099         self._update_time = 0
00100         self._ok = True
00101         self._done = False
00102         self._temperature_rise = None
00103         self._summary = "No temperature data..."
00104         self._html = "No temperature data..."
00105 
00106         self._start_temp = None
00107 
00108         # test is based on parameters taken from test node's namespace
00109         self._dmm_address = rospy.get_param("~dmm_address")
00110         self._max_acceptable_temperature = rospy.get_param("~max_acceptable_temperature")
00111         self._max_temperature_rise = rospy.get_param("~max_temperature_rise")
00112         self._temperature_rise_time = rospy.get_param("~temperature_rise_time")
00113 
00114         thread.start_new_thread(self._temperature_thread_func, ())
00115         self._mutex.release()
00116 
00117     def _read_line(self, sock):
00118         msg = ""
00119         while True:
00120             msg += sock.recv(1024)
00121             end = msg.find("\n")
00122             if end == len(msg)-1:
00123                 return msg
00124             elif end != -1:
00125                 print "warning, dropping %d byte tail of message. Tail='%s'"%(len(msg)-end, msg[end:])
00126                 return msg[0:end-1]
00127 
00128     def _temperature_thread_func(self):
00129         try:
00130             self._temperature_thread_func_internal()
00131         except Exception, e:        
00132             traceback.print_exc()
00133 
00134     def _temperature_thread_func_internal(self):
00135         print "Temperature Thread Started..."
00136         self._mutex.acquire()        
00137         dmm_address = self._dmm_address
00138         max_acceptable_temperature = self._max_acceptable_temperature
00139         max_temperature_rise = self._max_temperature_rise
00140         self._mutex.release()
00141 
00142         try :
00143             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
00144             lxi_port = 5025
00145             sock.connect((dmm_address, 5025))
00146         except socket.error :
00147             self._mutex.acquire()        
00148             print "Socket error"
00149             self._ok = False
00150             self._html = "Error getting connection to DMM. Address %s" % dmm_address
00151             self._mutex.release()
00152             return
00153         
00154         # We are now connected to scope
00155         self._mutex.acquire()        
00156         self._connected = True
00157         self._mutex.release()
00158         print "Connected to DMM"
00159 
00160         try : 
00161             sock.sendall("unit:temperature C\n")
00162             while self._ok:
00163                 sock.sendall("measure:temperature? thermistor, 10000, 1, 100\n")
00164                 response = self._read_line(sock)
00165                 #print "Got DMM response = '%s'" % response
00166                 self._mutex.acquire()
00167                 self._update_time = rospy.get_time()
00168                 try:
00169                     temp = float(response)
00170                     if temp < -1000000.0:
00171                         # DMM returns large negative number when temperature sensor is not plugged in.
00172                         # Look for this situation and provide sensible error message
00173                         print "Temperature sensor is unplugged"
00174                         if self._ok:
00175                             self._ok = False
00176                             self._html = "Temperature sensor is not plugged in."
00177                     else :
00178                         self._temp_stat.sample(temp)
00179                         if self._start_temp == None:
00180                             self._start_temp = temp
00181                         if temp > max_acceptable_temperature:
00182                             print "Overtemp"
00183                             if self._ok:
00184                                 self._ok = False
00185                                 self._html = "Measured temperature of %f celcius is higher than limit of %f celcius." % (temp, max_acceptable_temperature)
00186                         if not self._done:
00187                             if (self._update_time-self._start_time) > self._temperature_rise_time:
00188                                 # Got temperature rise over duration, now that test is complete.  
00189                                 # However, keep montioring temperature, to make sure temperature stays < max temperature
00190                                 self._done = True
00191                                 self._temperature_rise = temp-self._start_temp;
00192                             elif (temp-self._start_temp) > max_temperature_rise:
00193                                 if self._ok:
00194                                     self._ok = False
00195                                     self._html =  "Temperature has risen too much. \n"
00196                                     self._html += "Temperature started at %f C and increased to %f C after %f seconds. \n" % (self._start_temp, temp, (self._update_time-self._start_time))
00197                                     self._html += "Expected temperature to increase by less than %f C after %f seconds of operation. \n" % (max_temperature_rise, self._temperature_rise_time)
00198                 except ValueError:
00199                     if self._ok :
00200                         self._html = "Couldn't convert value returned by DMM : %s" % (response)
00201                         self._ok = False
00202                 self._mutex.release()
00203         except RuntimeError:
00204             self._mutex.acquire()
00205             print "Runtime error"
00206             if self._ok :
00207                 self._html = "Communication problem"
00208                 self._ok = False
00209             self._mutex.release()
00210             
00211         
00212     def check_ok(self):
00213         self._mutex.acquire()
00214         if self._ok:
00215             current_time = rospy.get_time()
00216             # Fail if there is no temperature data within a couple seconds
00217             if (self._update_time == 0) and ((current_time - self._start_time) > 3):
00218                 if self._connected:
00219                     self._html = "DMM has not produced any temperature data."
00220                 else:
00221                     self._html = "Could not connect to DMM : %s ." % (self._dmm_address)
00222                 self._ok = False
00223                 
00224             # Fail if data has stopped coming in
00225             if (self._update_time != 0) and ((current_time - self._update_time) > 3):
00226                 self._html = "DMM stopped providing temperature data."
00227                 self._ok = False
00228 
00229         ok = self._ok
00230         self._mutex.release()
00231         return ok
00232 
00233 
00234     def is_done(self):
00235         self._mutex.acquire()
00236         done = self._done
00237         self._mutex.release()
00238         return done
00239 
00240     def collect_result(self, r):
00241         self._mutex.acquire()  
00242 
00243         if self._ok:
00244             if self._update_time == 0:
00245                 self._html = "Test was stopped before any temperature data was collected."
00246                 self._ok = False
00247             elif not self._done:
00248                 self._html = "Test was stopped before temperature rise was determined."
00249                 self._ok = False
00250 
00251         if self._ok:
00252             self._html= "PASS"
00253 
00254         if self._done:
00255             self._html+="<br>\n Temperature increased by %f C in %f seconds" % (self._temperature_rise, self._temperature_rise_time)
00256 
00257         self._html += "<br>\n %s" % self._temp_stat.summary("LED Temperature (Celcius)")
00258                     
00259         test_name = "Temperature monitor"
00260         summary = test_name + " " + ("passed" if (self._ok) else "failed") + ". "
00261         if not self._ok:
00262             r.result = TestResultRequest.RESULT_FAIL
00263         r.text_summary += summary
00264         r.html_result += "<h3>" + test_name + "</h3>\n" + self._html + "\n"
00265         self._mutex.release()
00266 
00267 
00268 
00269 
00270 class FocusDialog:
00271     def __init__(self):
00272         self._mutex = threading.Lock()
00273         self._mutex.acquire()
00274 
00275         self._done = False
00276         self._ok = True
00277 
00278         thread.start_new_thread(self._focus_thread_func, ())
00279 
00280         self._mutex.release()
00281 
00282     def _focus_thread_func(self):
00283         ret = wx.MessageBox("Did you successfully manage to focus the projector?", "Projector Focus", wx.YES_NO)
00284         self._mutex.acquire()        
00285         self._done = True
00286         if ret == wx.NO:
00287             self._ok = False
00288         self._mutex.release()
00289         wx.MessageBox("Waiting for other tests to complete", "Waiting", wx.YES_NO)
00290 
00291     def check_ok(self):
00292         self._mutex.acquire()
00293         ok = self._ok
00294         self._mutex.release()
00295         return ok
00296     
00297     def is_done(self):
00298         self._mutex.acquire()
00299         done = self._done
00300         self._mutex.release()
00301         #print "focus done = ", done
00302         return done
00303 
00304     def collect_result(self, r):
00305         self._mutex.acquire()  
00306         msg = "Focusing passed\n"
00307         if not self._done:
00308             msg = "Focusing did not complete\n"
00309             self._ok = False
00310         elif not self._ok:
00311             msg = "Focusing failed\n"
00312 
00313         test_name = "Focusing step"
00314         summary = test_name + " " + ("passed" if (self._ok) else "failed") + ". "
00315         if not self._ok:
00316             r.result = TestResultRequest.RESULT_FAIL
00317         r.text_summary += summary
00318         r.html_result += "<h3>" + test_name + "</h3>\n" + msg + "\n"
00319         self._mutex.release()
00320 
00321 
00322 class ProjectorMonitor:
00323     def __init__(self):
00324         self._mutex = threading.Lock()
00325         self._mutex.acquire()
00326 
00327         self._start_time = rospy.get_time()
00328         self._voltage_stat = Statistic()
00329 
00330         self._update_time = 0
00331         self._ok = True
00332         self._summary = "No diagnostic data..."
00333         self._html = "No diagnostic data..."
00334 
00335         
00336         self._hardware_regex = re.compile("68-05021-\d\d\d\d\d");
00337         self._unused_wg021_actuator_name = None
00338 
00339         # test is based on parameters taken from test node's namespace
00340         self._actuator = rospy.get_param("~actuator")
00341         self._duration = rospy.get_param("~duration")
00342         self._short_circuit_voltage = rospy.get_param("~short_circuit_voltage")
00343         self._projector_voltage = rospy.get_param("~projector_voltage")
00344         self._diode_clamp_voltage = rospy.get_param("~diode_clamp_voltage")
00345         self._measured_current = rospy.get_param("~measured_current")
00346         self._allowed_current_error = rospy.get_param("~allowed_current_error")
00347 
00348         self._diag_sub = rospy.Subscriber('/diagnostics', DiagnosticArray, self._diag_callback)
00349 
00350         self._mutex.release()
00351 
00352             
00353 
00354     def _diag_callback(self, msg):
00355         self._mutex.acquire()
00356         self._diag_callback_with_mutex(msg)
00357         self._mutex.release()
00358 
00359     def _diag_callback_with_mutex(self, msg):        
00360         # Don't don't need to update once status is no longer OK
00361         if not self._ok:
00362             return 
00363 
00364         name = 'EtherCAT Device (%s)' % self._actuator
00365         for stat in msg.status:
00366             if stat.name == name :
00367                 if not self._hardware_regex.match(stat.hardware_id):
00368                     self._html  = "Found device that has correct actuator name '%s' but is not WG021.<br>" % stat.name
00369                     self._html += "Hardware ID = %s" % stat.hardware_id
00370                     self._ok = False;
00371                     return
00372 
00373                 self._update_time = rospy.get_time()
00374 
00375                 #Do diagnostics report any error conditions?
00376                 if stat.level != 0 :
00377                     self._html  = "Projector MCB Error. "
00378                     self._html += "Error level %d; Error Message '%s'" % (stat.level, stat.message)
00379                     self._ok = False
00380                     return
00381 
00382                 # Find LED voltage and measured current in data array?
00383                 voltage = None
00384                 current = None
00385                 for kv in stat.values:
00386                     if kv.key == "LED voltage":
00387                         voltage = float(kv.value)
00388                     elif kv.key == "Measured current":
00389                         current = float(kv.value)
00390                 if voltage == None:
00391                     self._html = "LED voltage could not be found in diagnostics data."
00392                     self._ok = False
00393                     return 
00394                 if current == None:
00395                     self._html = "Measured current could not be found in diagnostics data."
00396                     self._ok = False 
00397                     return 
00398                 self._voltage_stat.sample(voltage)
00399                 
00400                 # Verify voltage and current values
00401                 if voltage < self._short_circuit_voltage :
00402                     self._html  = "Measured LED voltage of %f is below minimum LED voltage. <br>\n" % voltage
00403                     self._html += "A voltage below %fV indicates a short circuit." % self._short_circuit_voltage
00404                     self._ok = False
00405                 elif voltage < self._projector_voltage :
00406                     # LED voltage is good, check current
00407                     current_error = current - self._measured_current;
00408                     if (abs(current_error) > self._allowed_current_error):
00409                         self._html  = "Measured current is  %fA, expected value close to %fA. <br>\n" % (current, self._measured_current)
00410                         self._html += "Measured current error is %fA.  Max acceptable error is %fA." % (current_error, self._allowed_current_error)
00411                         self._ok = False
00412                 elif voltage < self._diode_clamp_voltage:
00413                     self._html  = "A voltage measurement of %f volts is too high. <br>\n" % voltage
00414                     self._html += "A voltage between %fV and %fV indicates projector is not attached or power leads are reversed" % (self._projector_voltage, self._diode_clamp_voltage) 
00415                     self._ok = False
00416                 else:
00417                     self._html  = "LED voltage measurement of %f volts is too high. <br>\n" % voltage
00418                     self._html += "A voltage value above of %fV indicates that neither clamping diode or LED is attached" % self._diode_clamp_voltage
00419                     self._ok = False
00420                     
00421                 # Stop looping through data once correct actuator is found
00422                 break
00423             elif self._hardware_regex.match(stat.hardware_id):
00424                 self._unused_wg021_actuator_name = stat.name
00425 
00426         return
00427     
00428 
00429     def check_ok(self):
00430         self._mutex.acquire()
00431         if self._ok:
00432             current_time = rospy.get_time()
00433             # Fail if there are no diagnostics within a couple seconds
00434             if (self._update_time == 0) and ((current_time - self._start_time) > 3):
00435                 self._ok = False
00436                 if (self._unused_wg021_actuator_name != None):
00437                     self._html =  "Did not see diagnostics for actuator with name '%s'.<br>" % self._actuator
00438                     self._html += "However, found WG021 with name '%s'.<br>"%self._unused_wg021_actuator_name
00439                     self._html += "Use motorconf to properly configure WG021."
00440                 else:
00441                     self._html = "Did not receive any diagnostics after starting"
00442                 
00443             # Fail if there is not diagnostics data has stopped coming in
00444             if (self._update_time != 0) and ((current_time - self._update_time) > 3):
00445                 self._html = "Diagnostics are stale"
00446                 self._ok = False
00447 
00448         ok = self._ok
00449         self._mutex.release()
00450         return ok
00451 
00452     def is_done(self):
00453         self._mutex.acquire()
00454         done = ((rospy.get_time() - self._start_time) > self._duration)
00455         self._mutex.release()
00456         #print "monitor done = ", done
00457         return done
00458 
00459     def collect_result(self, r):
00460         self._mutex.acquire()  
00461 
00462         if self._ok and (self._update_time == 0):
00463             self._html = "Test was stopped before any diagnostics data was collected."
00464             self._ok = False
00465 
00466         if self._ok:
00467             self._html= "PASS"
00468 
00469         self._html += "<br> %s" % self._voltage_stat.summary("LED voltage (volts)")            
00470         
00471         test_name = "Electrical test"
00472         summary = test_name + " " + ("passed" if (self._ok) else "failed") + ". "
00473         if not self._ok:
00474             r.result = TestResultRequest.RESULT_FAIL
00475         r.text_summary += summary
00476         r.html_result += "<h3>" + test_name + "</h3>\n" + self._html + "\n"
00477         self._mutex.release()
00478         
00479 
00480 if __name__ == '__main__':
00481     try:
00482         rospy.init_node('projector_monitor')
00483         start_time = rospy.get_time()
00484         app = wx.PySimpleApp()
00485         result_service = rospy.ServiceProxy('test_result', TestResult)
00486         display_focus_dialog = rospy.get_param("~focus_dialog", False)        
00487         focus = None
00488         if (display_focus_dialog) :
00489             focus = FocusDialog()
00490         temperature = TemperatureMonitor()
00491         rospy.sleep(2.0)
00492         monitor = ProjectorMonitor()
00493         while not rospy.is_shutdown():
00494             if (not monitor.check_ok()):
00495                 print "monitor failed"
00496                 break
00497             if (not temperature.check_ok()):
00498                 print "temperature failed"
00499                 break
00500             if (focus != None) and (not focus.check_ok()):
00501                 print "focus failed"
00502                 break            
00503             if (monitor.is_done()) and (temperature.is_done()) and ((focus == None) or (focus.is_done())):
00504                 break
00505             rospy.sleep(0.2)
00506  
00507         r = TestResultRequest()
00508         r.html_result = ''
00509         r.text_summary = ''
00510         r.plots = []
00511         r.result = TestResultRequest.RESULT_PASS
00512 
00513         monitor.collect_result(r)
00514         temperature.collect_result(r)
00515         if focus != None :
00516             focus.collect_result(r)
00517         print r
00518         rospy.wait_for_service('test_result', 2)
00519         result_service.call(r)
00520         
00521     except Exception, e:        
00522         traceback.print_exc()
00523         #sleep(100)
00524     except KeyError:
00525         traceback.print_exc()
00526         #sleep(100)
00527     rospy.loginfo('Quiting projector monitor, shutting down node.')


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