set_goal.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # Software License Agreement (BSD License)
00003 #
00004 # Copyright (c) 2008, Willow Garage, Inc.
00005 # All rights reserved.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions
00009 # are met:
00010 #
00011 #  * Redistributions of source code must retain the above copyright
00012 #    notice, this list of conditions and the following disclaimer.
00013 #  * Redistributions in binary form must reproduce the above
00014 #    copyright notice, this list of conditions and the following
00015 #    disclaimer in the documentation and/or other materials provided
00016 #    with the distribution.
00017 #  * Neither the name of the Willow Garage nor the names of its
00018 #    contributors may be used to endorse or promote products derived
00019 #    from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 # POSSIBILITY OF SUCH DAMAGE.
00033 #
00034 
00035 ## Gazebo test 2dnav stack
00036 
00037 PKG = 'test_pr2_2dnav_gazebo'
00038 NAME = 'test_set_goal'
00039 
00040 import math
00041 import roslib
00042 roslib.load_manifest(PKG)
00043 roslib.load_manifest('rostest')
00044 
00045 
00046 import sys, unittest
00047 import os, os.path, threading, time
00048 import rospy, rostest
00049 import actionlib
00050 from std_msgs.msg import String
00051 from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
00052 from geometry_msgs.msg import Pose,Quaternion,Point, PoseStamped, PoseWithCovariance, PoseWithCovarianceStamped
00053 from nav_msgs.msg import Odometry
00054 from actionlib_msgs.msg import GoalStatusArray
00055 from gazebo_msgs.msg import ContactState,ContactsState
00056 import tf.transformations as tft
00057 from numpy import float64
00058 
00059 FLOAT_TOL = 0.0001
00060 AMCL_TOL  = 0.5
00061 COV = [float64(0.5*0.5),float64(0)      ,float64(0),float64(0),float64(0),float64(0), \
00062        float64(0)      ,float64(0.5*0.5),float64(0),float64(0),float64(0),float64(0), \
00063        float64(0)      ,float64(0)      ,float64(0),float64(0),float64(0),float64(0), \
00064        float64(0)      ,float64(0)      ,float64(0),float64(0),float64(0),float64(0), \
00065        float64(0)      ,float64(0)      ,float64(0),float64(0),float64(0),float64(0), \
00066        float64(0)      ,float64(0)      ,float64(0),float64(0),float64(0),float64(math.pi/12.0*math.pi/12.0)  ]
00067 
00068 def normalize_angle_positive(angle):
00069     return math.fmod(math.fmod(angle, 2*math.pi) + 2*math.pi, 2*math.pi)
00070 
00071 def normalize_angle(angle):
00072     anorm = normalize_angle_positive(angle)
00073     if anorm > math.pi:
00074       anorm -= 2*math.pi
00075     return anorm
00076 
00077 def shortest_angular_distance(angle_from, angle_to):
00078     angle_diff = normalize_angle_positive(angle_to) - normalize_angle_positive(angle_from)
00079     if angle_diff > math.pi:
00080       angle_diff = -(2*math.pi - angle_diff)
00081     return normalize_angle(angle_diff)
00082 
00083 class NavStackTest(unittest.TestCase):
00084     def __init__(self, *args):
00085         super(NavStackTest, self).__init__(*args)
00086         self.bumped  = False
00087         self.success = False
00088 
00089         self.initialize_amcl = False
00090         self.initialpose_published = False
00091         self.initialpose = [25.7, 25.7, 0]
00092         self.goal_sent = False
00093 
00094         self.odom_x_initial = 0
00095         self.odom_y_initial = 0
00096         self.odom_q_initial = [0,0,0,1]
00097         self.odom_initialized = False;
00098 
00099         self.odom_x = 0
00100         self.odom_y = 0
00101         self.odom_q = [0,0,0,1]
00102 
00103         self.p3d_x_initial = 0
00104         self.p3d_y_initial = 0
00105         self.p3d_q_initial = [0,0,0,1]
00106         self.p3d_initialized = False;
00107 
00108         self.p3d_x = 0
00109         self.p3d_y = 0
00110         self.p3d_q = [1,0,0,0]
00111 
00112         # default parameters
00113         self.nav_t_tol    = 0.1
00114         self.nav_xy_tol   = 0.7
00115         self.odom_t_tol   = 0.1
00116         self.odom_xy_tol  = 0.1
00117         self.test_timeout = 50.0
00118         # note: starting position of the robot is 25.70, 25.70 (center of map)
00119         self.target_x =  25.70
00120         self.target_y =  25.70
00121         self.target_t =  0.0
00122 
00123         self.target_q =  [0,0,0,1]
00124 
00125         self.args = sys.argv
00126         
00127 
00128     def printBaseOdom(self, odom):
00129         print "odom received"
00130         print "odom pose " + "x: " + str(odom.pose.pose.position.x)
00131         print "odom pose " + "y: " + str(odom.pose.pose.position.y)
00132         print "odom pose " + "t: " + str(odom.pose.pose.position.z)
00133         print "odom twist " + "x: " + str(odom.twist.twist.linear.x)
00134         print "odom twist " + "y: " + str(odom.twist.twist.linear.y)
00135         print "odom twist " + "t: " + str(odom.twist.twist.angular.z)
00136 
00137     def printBaseP3D(self, p3d):
00138         print "base pose ground truth received"
00139         print "P3D pose translan: " + "x: " + str(p3d.pose.pose.position.x)
00140         print "                   " + "y: " + str(p3d.pose.pose.position.y)
00141         print "                   " + "z: " + str(p3d.pose.pose.position.z)
00142         print "P3D pose rotation: " + "x: " + str(p3d.pose.pose.orientation.x)
00143         print "                   " + "y: " + str(p3d.pose.pose.orientation.y)
00144         print "                   " + "z: " + str(p3d.pose.pose.orientation.z)
00145         print "                   " + "w: " + str(p3d.pose.pose.orientation.w)
00146         print "P3D rate translan: " + "x: " + str(p3d.twist.twist.linear.x)
00147         print "                   " + "y: " + str(p3d.twist.twist.linear.y)
00148         print "                   " + "z: " + str(p3d.twist.twist.linear.z)
00149         print "P3D rate rotation: " + "x: " + str(p3d.twist.twist.angular.x)
00150         print "                   " + "y: " + str(p3d.twist.twist.angular.y)
00151         print "                   " + "z: " + str(p3d.twist.twist.angular.z)
00152 
00153 
00154     def quaternionMsgToList(self, q):
00155       return [q.x, q.y, q.z, q.w]
00156 
00157     def odomInput(self, odom):
00158         #self.printBaseOdom(odom)
00159         # initialize odom
00160         if self.odom_initialized == False or self.p3d_initialized == False:
00161             self.odom_initialized = True
00162             self.odom_x_initial = odom.pose.pose.position.x
00163             self.odom_y_initial = odom.pose.pose.position.y
00164             self.odom_q_initial = self.quaternionMsgToList(odom.pose.pose.orientation)
00165         else:
00166             # update odom
00167             self.odom_x = odom.pose.pose.position.x
00168             self.odom_y = odom.pose.pose.position.y
00169             self.odom_q = self.quaternionMsgToList(odom.pose.pose.orientation)
00170 
00171     def p3dInput(self, p3d):
00172         #self.printBaseP3D(p3d)
00173         # initialize ground truth
00174         #print "init ", self.odom_initialized , " " , self.p3d_initialized, " " ,self.p3d_x
00175         if self.odom_initialized == False or self.p3d_initialized == False:
00176             self.p3d_initialized = True
00177             self.p3d_x_initial =  p3d.pose.pose.position.x
00178             self.p3d_y_initial =  p3d.pose.pose.position.y
00179             self.p3d_q_initial = self.quaternionMsgToList(p3d.pose.pose.orientation)
00180         else:
00181             # update ground truth
00182             self.p3d_x  =  p3d.pose.pose.position.x
00183             self.p3d_y  =  p3d.pose.pose.position.y
00184             self.p3d_q  = self.quaternionMsgToList(p3d.pose.pose.orientation)
00185 
00186     def bumpedInput(self, contactsState):
00187         if len(contactsState.states) > 0:
00188           for i in range(0,len(contactsState.states)):
00189             print "robot touched something! ", contactsState.states[i].info
00190             self.bumped = True
00191     
00192     def amclInput(self, amcl_pose):
00193         if self.initialize_amcl:
00194           print "/amcl_pose received, ",amcl_pose
00195           amcl_eul = tft.euler_from_quaternion([amcl_pose.pose.pose.orientation.x,\
00196                                                 amcl_pose.pose.pose.orientation.y,\
00197                                                 amcl_pose.pose.pose.orientation.z,\
00198                                                 amcl_pose.pose.pose.orientation.w])
00199           if abs(amcl_pose.pose.pose.position.x    - self.initialpose[0]) < AMCL_TOL and \
00200              abs(amcl_pose.pose.pose.position.y    - self.initialpose[1]) < AMCL_TOL and \
00201              abs(amcl_eul[2]                  - self.initialpose[2]) < AMCL_TOL:
00202             print "initial_pose matches, stop setPose begin publishing goal."
00203             self.initialize_amcl = False
00204           else:
00205             print "initial_pose mismatch, continue setPose."
00206 
00207     def cmd_velInput(self, cmd_vel):
00208           print "cmd_vel: ", cmd_vel.vel.vx, ",", cmd_vel.vel.vy, ",", cmd_vel.vel.vz \
00209                            , cmd_vel.ang_vel.vx, ",", cmd_vel.ang_vel.vy, ",", cmd_vel.ang_vel.vz
00210 
00211     def publish_initialpose(self, tmph):
00212       print "pub init pose"
00213       p = Point(self.initialpose[0], self.initialpose[1], 0)
00214       tmpq = tft.quaternion_from_euler(0,0,self.initialpose[2],'rxyz')
00215       q = Quaternion(tmpq[0],tmpq[1],tmpq[2],tmpq[3])
00216       pose = Pose(p,q)
00217       #print "publishing initialpose",tmph,p,COV[0], latched topic
00218       pub_pose = rospy.Publisher("/initialpose"  , PoseWithCovarianceStamped, None, False, True)
00219       pub_pose.publish(PoseWithCovarianceStamped(tmph, PoseWithCovariance(pose,COV)))
00220       self.initialpose_published = True
00221     
00222     def test_set_goal(self):
00223         print "LNK\n"
00224 
00225         #pub_base = rospy.Publisher("cmd_vel", BaseVel)
00226         rospy.Subscriber("/base_pose_ground_truth" , Odometry                   , self.p3dInput)
00227         rospy.Subscriber("/base_odometry/odom" , Odometry                   , self.odomInput)
00228         rospy.Subscriber("/base_bumper/state"      , ContactsState              , self.bumpedInput)
00229         rospy.Subscriber("/torso_lift_bumper/state", ContactsState              , self.bumpedInput)
00230         rospy.Subscriber("/amcl_pose"              , PoseWithCovarianceStamped  , self.amclInput)
00231 
00232         # below only for debugging build 303, base not moving
00233         #rospy.Subscriber("cmd_vel"               , PoseDot             , self.cmd_velInput)
00234 
00235         rospy.init_node(NAME, anonymous=True)
00236         print "debug1:",rospy.is_shutdown()," time(",time.time(),")"
00237         client = actionlib.SimpleActionClient('/move_base', MoveBaseAction)
00238         print "debug2:",rospy.is_shutdown()," time(",time.time(),")"
00239         client.wait_for_server()
00240         print "debug3:",rospy.is_shutdown()," time(",time.time(),")"
00241 
00242         # get goal from commandline
00243         print "------------------------"
00244         for i in range(0,len(self.args)):
00245           print " sys argv:", self.args[i]
00246           if self.args[i] == '-x':
00247             if len(self.args) > i+1:
00248               self.target_x = float(self.args[i+1])
00249               print "target x set to:",self.target_x
00250           if self.args[i] == '-y':
00251             if len(self.args) > i+1:
00252               self.target_y = float(self.args[i+1])
00253               print "target y set to:",self.target_y
00254           if self.args[i] == '-t':
00255             if len(self.args) > i+1:
00256               self.target_t = float(self.args[i+1])
00257               self.target_q =  tft.quaternion_from_euler(0,0,self.target_t,'rxyz')
00258               print "target t set to:",self.target_t
00259           if self.args[i] == '-nav_t_tol':
00260             if len(self.args) > i+1:
00261               self.nav_t_tol = float(self.args[i+1])
00262               if self.nav_t_tol == 0:
00263                 print "nav_t_tol check disabled"
00264               else:
00265                 print "nav_t_tol set to:",self.nav_t_tol
00266           if self.args[i] == '-nav_xy_tol':
00267             if len(self.args) > i+1:
00268               self.nav_xy_tol = float(self.args[i+1])
00269               if self.nav_xy_tol == 0:
00270                 print "nav_xy_tol check disabled"
00271               else:
00272                 print "nav_xy_tol set to:",self.nav_xy_tol
00273           if self.args[i] == '-odom_t_tol':
00274             if len(self.args) > i+1:
00275               self.odom_t_tol = float(self.args[i+1])
00276               if self.odom_t_tol == 0:
00277                 print "odom_t_tol check disabled"
00278               else:
00279                 print "odom_t_tol set to:",self.odom_t_tol
00280           if self.args[i] == '-odom_xy_tol':
00281             if len(self.args) > i+1:
00282               self.odom_xy_tol = float(self.args[i+1])
00283               if self.odom_xy_tol == 0:
00284                 print "odom_xy_tol check disabled"
00285               else:
00286                 print "odom_xy_tol set to:",self.odom_xy_tol
00287           if self.args[i] == '-timeout':
00288             if len(self.args) > i+1:
00289               self.test_timeout = float(self.args[i+1])
00290               print "test_timeout set to:",self.test_timeout
00291           if self.args[i] == '-amcl':
00292             if len(self.args) > i+3:
00293               self.initialize_amcl = True
00294               self.initialpose = [float(self.args[i+1]),float(self.args[i+2]),float(self.args[i+3])]
00295               print "using amcl, will try to initialize pose to: ",self.initialpose
00296             else:
00297               self.initialize_amcl = False
00298               print "using fake localization, need 3 arguments for amcl (x,y,th)"
00299 
00300         print " target:", self.target_x, self.target_y, self.target_t
00301         print "------------------------"
00302 
00303         timeout_t = time.time() + self.test_timeout
00304 
00305         print "rospy debug",rospy.is_shutdown(),self.success,time.time(),timeout_t
00306         # wait for result
00307         while not rospy.is_shutdown() and not self.success and time.time() < timeout_t:
00308 
00309             #create a temp header for publishers
00310             tmph = rospy.Header();
00311             tmph.stamp = rospy.get_rostime();
00312             tmph.frame_id = "/map"
00313             # publish initial pose once
00314             if not self.initialpose_published:
00315               self.publish_initialpose(tmph)
00316             else:
00317               if self.initialize_amcl:
00318                 print "waiting for amcl to initialize (/amcl_pose received]"
00319               else:
00320                 print "check move base"
00321                 if not self.goal_sent:
00322                     print "pub goal"
00323                     # construct goal point
00324                     tmpp = Point(self.target_x, self.target_y, 0)
00325                     tmpq0 = tft.quaternion_from_euler(0,0,self.target_t,'rxyz')
00326                     tmpq = Quaternion(tmpq0[0],tmpq0[1],tmpq0[2],tmpq0[3])
00327                     tmppose = Pose(tmpp,tmpq)
00328                     #fill in goal message
00329                     move_base_goal = MoveBaseGoal()
00330                     move_base_goal.target_pose.header = tmph
00331                     move_base_goal.target_pose.pose = tmppose
00332                     client.send_goal(move_base_goal)
00333                     #client.wait_for_result()
00334                     #print "publishing goal",h,p,q
00335                     self.goal_sent = True
00336 
00337             time.sleep(1.0) #controls rate of spam below
00338 
00339             # compute angular error between deltas in odom and p3d
00340 
00341             print "========================"
00342             print "time: remaining: ", timeout_t - time.time()
00343             # compute delta in odom from initial pose
00344             tmpoqi = tft.quaternion_inverse(self.odom_q_initial)
00345             odom_q_delta = tft.quaternion_multiply(tmpoqi,self.odom_q)
00346             #if self.odom_initialized == False:
00347             #  print "pr2 odometry/odom not received"
00348             #else:
00349             #  print "euler: odom delta:" , tft.euler_from_quaternion(odom_q_delta)
00350 
00351             # compute delta in p3d from initial pose
00352             tmppqi = tft.quaternion_inverse(self.p3d_q_initial)
00353             p3d_q_delta = tft.quaternion_multiply(tmppqi,self.p3d_q)
00354             #if self.p3d_initialized == False:
00355             #  print "base_pose_ground_truth not received"
00356             #else:
00357             #  print "euler: ground truth delta:" , tft.euler_from_quaternion(p3d_q_delta)
00358 
00359 
00360             # compute delta between odom and p3d
00361             tmpdqi = tft.quaternion_inverse(p3d_q_delta)
00362             delta = tft.quaternion_multiply(tmpdqi,odom_q_delta)
00363             delta_euler = tft.euler_from_quaternion(delta)
00364             odom_drift_dyaw = delta_euler[2]
00365             #if self.p3d_initialized == True and self.odom_initialized == True:
00366             #  print "euler: odom drift from ground truth:" , tft.euler_from_quaternion(delta)
00367 
00368             # compute delta between target and p3d
00369             tmptqi = tft.quaternion_inverse(self.target_q)
00370             navdq = tft.quaternion_multiply(tmptqi,self.p3d_q)
00371             navde = tft.euler_from_quaternion(navdq)
00372             nav_dyaw = navde[2]
00373             #print "euler: ground truth off target:" , navde
00374 
00375 
00376 
00377             # check odom error (odom error from ground truth)
00378             odom_xy_err =  max(abs(self.odom_x - self.p3d_x - self.odom_x_initial + self.p3d_x_initial ),abs(self.odom_y - self.p3d_y - self.odom_y_initial + self.p3d_y_initial ))
00379             odom_t_err  =  abs(odom_drift_dyaw)
00380 
00381             # check total error (difference between ground truth and target)
00382             nav_xy_err  =  max(abs(self.p3d_x - self.target_x),abs(self.p3d_y - self.target_y))
00383             nav_t_err   =  abs(nav_dyaw)
00384 
00385             print "odom drift error:"
00386             print "    translation: ",self.odom_x - self.p3d_x - self.odom_x_initial + self.p3d_x_initial,",",self.odom_y - self.p3d_y - self.odom_y_initial + self.p3d_y_initial
00387             print "    heading: " + str(odom_t_err) + " odom_t_tol: " + str(self.odom_t_tol)
00388 
00389             print "ground truth - target error:"
00390             print "    translation: ",self.p3d_x - self.target_x,",",self.p3d_y - self.target_y
00391             print "    heading: " + str(nav_t_err) + " nav_t_tol:" + str(self.nav_t_tol)
00392 
00393             print "summary:"
00394             self.success = True
00395             # check initialization state
00396             if self.odom_initialized == False:
00397                 print "waiting for first /base_odom/odom message to initialize test"
00398             if self.p3d_initialized == False:
00399                 print "waiting for first /base_pose_ground_truth message to initialize test"
00400             # check to see if collision happened
00401             if self.bumped == True:
00402                 self.success = False
00403                 print "    Hit the wall."
00404             # check to see if nav tolerance is ok
00405             if self.nav_t_tol > 0 and nav_t_err > self.nav_t_tol:
00406                 self.success = False
00407                 print "    Target heading out of tol."
00408             if self.nav_xy_tol > 0 and nav_xy_err > self.nav_xy_tol:
00409                 self.success = False
00410                 print "    Target xy out of tol."
00411             # check to see if odom drift from ground truth tolerance is ok
00412             if self.odom_t_tol > 0 and odom_t_err > self.odom_t_tol:
00413                 self.success = False
00414                 print "    Odom drift heading out of tol."
00415             if self.odom_xy_tol > 0 and odom_xy_err > self.odom_xy_tol:
00416                 print "    Odom drift xy out of tol."
00417                 self.success = False
00418 
00419         self.assert_(self.success)
00420         
00421 def print_usage(exit_code = 0):
00422     print '''Commands:
00423     -x <x target position>  - target x location
00424     -y <y target position>  - target y location
00425     -t <target yaw> - target robot yaw
00426     -nav_xy_tol <tol> - target xy location error tolerance, set to 0 to disable, default = 0.7 m
00427     -nav_t_tol <tol> - target yaw error tolerance, set to 0 to disable, default = 0.1 rad
00428     -odom_xy_tol <tol> - odom and ground truth xy drift error tolerance, set to 0 to disable, default = 0.1 m
00429     -odom_t_tol <tol> - odom and ground truth yaw drift error tolerance, set to 0 to disable, default = 0.1 rad
00430     -timeout <seconds> - test timeout in seconds. default to 50 seconds
00431     -amcl <x initial position> <y initial position> <initial yaw> - initial pose for amcl.  If unspecified, assume fake localization is used.
00432 '''
00433 
00434 if __name__ == '__main__':
00435     #print usage if not arguments
00436     if len(sys.argv) == 1:
00437       print_usage()
00438     else:
00439       rostest.run(PKG, sys.argv[0], NavStackTest, sys.argv) #, text_mode=True)
00440 
00441 
00442 


test_pr2_2dnav_gazebo
Author(s): John Hsu
autogenerated on Mon Jan 6 2014 12:07:43