hysteresis_sinesweep_plot2.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright (c) 2008, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions are met:
00008 #
00009 #     * Redistributions of source code must retain the above copyright
00010 #       notice, this list of conditions and the following disclaimer.
00011 #     * Redistributions in binary form must reproduce the above copyright
00012 #       notice, this list of conditions and the following disclaimer in the
00013 #       documentation and/or other materials provided with the distribution.
00014 #     * Neither the name of the Willow Garage, Inc. nor the names of its
00015 #       contributors may be used to endorse or promote products derived from
00016 #       this software without specific prior written permission.
00017 #
00018 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00019 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00020 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00021 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00022 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00023 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00024 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00025 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00026 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00027 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00028 # POSSIBILITY OF SUCH DAMAGE.
00029 
00030 ##\author Kevin Watts
00031 
00032 PKG = "qualification"
00033 import roslib; roslib.load_manifest(PKG)
00034 
00035 import numpy
00036 
00037 import rospy
00038 
00039 from pr2_self_test_msgs.srv import TestResult, TestResultRequest
00040 from qualification.analysis.hysteresis_analysis import *
00041 
00042 from joint_qualification_controllers.msg import HysteresisData2
00043 from std_msgs.msg import Bool
00044 
00045 import traceback
00046 
00047 class AnalysisApp:
00048   def __init__(self):
00049     self.data_sent = False
00050     self._motors_halted = True
00051     rospy.init_node("test_plotter")
00052     self.data_topic = rospy.Subscriber("/test_data", HysteresisData2, self._on_data)
00053     self.motors_topic = rospy.Subscriber("pr2_etherCAT/motors_halted", Bool, self.on_motor_cb)
00054     self.result_service = rospy.ServiceProxy('/test_result', TestResult)
00055     self.data = None
00056     
00057   def _on_data(self, msg):
00058     self.data = msg
00059 
00060   def has_data(self):
00061     return self.data is not None
00062     
00063   def on_motor_cb(self, msg):
00064     self._motors_halted = msg.data
00065 
00066   def test_failed_service_call(self, except_str = ''):
00067     rospy.logerr(except_str)
00068     r = TestResultRequest()
00069     r.html_result = except_str
00070     r.text_summary = 'Caught exception, automated test failure.'
00071     r.plots = []
00072     r.result = TestResultRequest.RESULT_FAIL
00073     self.send_results(r)
00074 
00075   def send_results(self, test_result):
00076     if not self.data_sent:
00077       rospy.wait_for_service('test_result')
00078       self.result_service.call(test_result)
00079       self.data_sent = True
00080 
00081   def hysteresis_plot(self):
00082     try:
00083       r = TestResultRequest()
00084       r.html_result = ''
00085       r.text_summary = 'No data.'
00086       r.plots = []
00087       r.result = TestResultRequest.RESULT_FAIL
00088 
00089       image_title = self.data.joint_name + "_hysteresis"
00090 
00091       params = HysteresisParameters()
00092       params.joint_name = self.data.joint_name
00093       params.pos_effort = self.data.arg_value[1]
00094       params.neg_effort = self.data.arg_value[0]
00095       params.range_max  = self.data.arg_value[3]
00096       params.range_min  = self.data.arg_value[2]
00097       params.tolerance  = self.data.arg_value[7]
00098       params.sd_max     = self.data.arg_value[8]
00099       params.slope      = self.data.arg_value[9]
00100       params.timeout    = self.data.arg_value[5]
00101       params.velocity   = self.data.arg_value[4]
00102       params.max_effort = self.data.arg_value[6]
00103 
00104       params.p_gain     = self.data.arg_value[10]
00105       params.i_gain     = self.data.arg_value[11]
00106       params.d_gain     = self.data.arg_value[12]
00107       params.i_clamp    = self.data.arg_value[13]
00108 
00109       r.params = params.get_test_params()
00110 
00111       if self._motors_halted:
00112         r.result = TestResultRequest.RESULT_FAIL
00113         r.text_summary = "Motors halted. Check runstop. Hysteresis results are invalid."
00114         r.html_result = "<p>Motors halted during test. Check estop and verify that power is on.</p>\n"
00115         self.send_results(r)
00116 
00117       # Check the num points, timeout
00118       min_len = min( len(d.position) for d in self.data.runs )
00119       if min_len < 20:
00120         r.result = TestResultRequest.RESULT_FAIL
00121         r.text_summary = "Not enough data points, bad encoder or bad gains."
00122         
00123         error_msg = "<p>Not enough data points, hysteresis controller may have malfunctioned. Check diagnostics.</p>"
00124         error_msg += "<p>Up data points: %d, down points: %d</p>\n" % (len(self.data.position_up), len(self.data.position_down))
00125         error_msg += "<p>The encoder may be missing ticks, check encoder.<br>\n"
00126         error_msg += "Make sure mechanism is free to move and not trapped or constrained<br>\n"
00127         error_msg += "Make sure motors are not in safety lockout (check diagnostics)<br>\n"
00128         error_msg += "Check controller gains if problem persists on components of same type.</p>\n"
00129         error_msg += "<p>Test status: <b>FAIL</b>.</p>"
00130         error_msg += '<p align=center><b>Test Parameters</b></p>\n'
00131         
00132         r.html_result = error_msg
00133         self.send_results(r)
00134         return
00135 
00136       if self.data.arg_value[5] == 0:
00137         r.result = TestResultRequest.RESULT_FAIL
00138         r.text_summary = 'Hysteresis controller timed out. Check diagnostics.'
00139         r.html_result = '<p>Hysteresis controller timed out. Check diagnostics. Controller gains may be bad, or motors may be in safety lockout. Did the device pass the visualizer? Is it calibrated?</p><p>Test status: <b>FAIL</b>.</p>'
00140         r.html_result += '<p align=center><b>Test Parameters</b></p>\n'
00141         self.send_results(r)
00142         return
00143 
00144       # Compute the encoder travel
00145       min_encoder = min( min(numpy.array(d.position)) for d in self.data.runs )
00146       max_encoder = max( max(numpy.array(d.position)) for d in self.data.runs )
00147 
00148       if abs(max_encoder - min_encoder) < 0.005:
00149         r.result = TestResultRequest.RESULT_FAIL
00150         r.text_summary = 'Mechanism didn\'t move. Check diagnostics.'
00151         r.html_result = "<p>No travel of mechanism, hysteresis did not complete. Check controller gains and encoder.</p><p>Test status: <b>FAIL</b>.</p>"
00152         r.html_result += '<p align=center><b>Test Parameters</b></p>\n'
00153         self.send_results(r)
00154         return
00155       
00156       hyst_data = HysteresisTestData2([ HysteresisDirectionData(d.position, d.effort, d.velocity) for d in self.data.runs ])
00157       
00158 #      # Make sure we have at least some points in both directions
00159       if min_len < 20:
00160         r.result = TestResultRequest.RESULT_FAIL
00161         r.text_summary = 'Incomplete data set. Check diagnostics.'
00162         r.html_result = "<p>Not enough data in one or more directions.</p><p>Test Status: <b>FAIL</b>.</p><p>Test status: <b>FAIL</b>.</p>"
00163         r.html_result += '<p align=center><b>Test Parameters</b></p>\n'
00164         self.send_results(r)
00165         return
00166 
00167       # Process HTML result
00168       range_result = range_analysis(params, hyst_data)
00169  
00170       if params.slope == 0:
00171         effort_result = effort_analysis(params, hyst_data)
00172 #      else:
00173 #        TODO: update this for newer data types
00174 #        effort_result = regression_analysis(params, hyst_data)
00175 # 
00176       vel_result = velocity_analysis(params, hyst_data)
00177       vel_html = vel_result.html
00178 
00179 
00180       effort_plots = plot_efforts(params, hyst_data)
00181       vel_plots = plot_velocities(params, hyst_data)
00182 
00183       # render data directly to a CSV plot
00184       csv = Plot()
00185       csv.image_format = "csv"
00186       csv.title = params.joint_name + "_hysteresis_csv"
00187       csv_data = "Run,Position,Effort,Velocity\n"
00188       for i, run in enumerate(self.data.runs):
00189         for j in range(len(run.position)):
00190           csv_data = csv_data + "%d,%f,%f,%f\n"%(i, run.position[j], run.effort[j], run.velocity[j])
00191       csv.image = csv_data
00192 
00193       r.plots = effort_plots + vel_plots + [csv]
00194 
00195       html = ""
00196       for p in effort_plots:
00197         html += '<img src=\"IMG_PATH/%s.png\", width = 640, height = 480/>' % p.title
00198       html += '<p><b>Legend:</b> Red - Average Effort. Yellow - Error Bars. Green - Expected Effort.</p>\n'
00199       html += '<p align=center><b>Range Data</b></p>' + range_result.html + '<hr size="2">\n'
00200       html += '<p align=center><b>Effort Data</b></p>' + effort_result.html + '<hr size="2">\n'
00201 
00202       html += '<p align=center><b>Velocity Data</b></p>' 
00203 
00204       for p in vel_plots:
00205         html += '<img src=\"IMG_PATH/%s.png\", width = 640, height = 480/>' % p.title
00206 
00207       html += vel_html + '<hr size="2">\n'
00208       html += '<p align=center><b>Test Parameters</b></p>\n'
00209 
00210 
00211       r.html_result = html
00212 
00213       r.values.extend(effort_result.values)
00214       r.values.extend(range_result.values)
00215       r.values.extend(vel_result.values)
00216      
00217       if range_result.result and effort_result.result:
00218         r.text_summary = 'Hysteresis result: OK'
00219         r.result = TestResultRequest.RESULT_PASS
00220       elif not range_result.result:
00221         r.text_summary = 'Hysteresis range failed. Joint did not complete range of motion.'
00222         r.result = TestResultRequest.RESULT_FAIL
00223       else:
00224         r.text_summary = 'Hysteresis result: Questionable. ' + range_result.summary + ' ' + effort_result.summary
00225         r.result = TestResultRequest.RESULT_HUMAN_REQUIRED
00226 
00227       self.send_results(r)
00228     except Exception, e:
00229       rospy.logerr('hysteresis_plot caught exception, returning test failure.')
00230       rospy.logerr(traceback.format_exc())
00231       self.test_failed_service_call(traceback.format_exc())
00232 
00233       
00234 if __name__ == "__main__":
00235   try:
00236     app = AnalysisApp()
00237     my_rate = rospy.Rate(5)
00238     while not rospy.is_shutdown() and not app.has_data():
00239       my_rate.sleep()
00240       
00241     if not rospy.is_shutdown():
00242       app.hysteresis_plot()
00243     rospy.spin()
00244   except KeyboardInterrupt:
00245     pass
00246   except Exception, e:
00247     traceback.print_exc()
00248     


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