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 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
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
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
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
00166 self._mutex.acquire()
00167 self._update_time = rospy.get_time()
00168 try:
00169 temp = float(response)
00170 if temp < -1000000.0:
00171
00172
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
00189
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
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
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
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
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
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
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
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
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
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
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
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
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
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
00524 except KeyError:
00525 traceback.print_exc()
00526
00527 rospy.loginfo('Quiting projector monitor, shutting down node.')