00001 #!/usr/bin/python 00002 # Software License Agreement (BSD License) 00003 # 00004 # Copyright (c) 2009, 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 # author: Kaijen Hsiao 00035 00036 ## @package interpolated_ik_motion_planner 00037 # Server to provide interpolated IK motion plans 00038 # relevant params: 00039 # num_steps: the number of steps to use when interpolating including start and finish (0 means use pos_spacing and 00040 # rot_spacing to calculate the number of steps, anything ==1 or <0 will become 2, start and finish) 00041 # consistent_angle: the max angle distance (in any joint) before we declare that the path is inconsistent 00042 # collision_check_resolution: how many steps between collision checks (0 or 1 is check every step; 2 is every other, etc.) 00043 # steps_before_abort: the number of steps in the plan (starting from the end and going backwards) that can be invalid 00044 # due to collisions or inconsistency before aborting (0 is abort as soon as you find one, 1 is allow one and still 00045 # continue, -1 or >=num_steps to never abort early) 00046 # pos_spacing: the max translation (m) to move the wrist between waypoints (only used if num_steps is 0) 00047 # rot_spacing: the max rotation (rad) to move the wrist between waypoints (only used if num_steps is 0) 00048 # collision_aware: if this is 0, collisions won't be checked for (returns non-collision aware IK solutions) 00049 # start_from_end: if this is 1, the planner searches for an IK solution for the end first, then works backwards from there 00050 # max_joint_vels: a list of maximum joint velocities to use when computing times and velocities for the joint trajectory (defaults to [.1]*7 if left empty) 00051 # max_joint_accs: a list of maximum accelerations to use when computing times and velocities for the joint trajectory (defaults to [.25]*7 if left empty) 00052 00053 # Parameters can be changed by calling the r_interpolated_ik_motion_plan_set_params service (or l_inter...) 00054 00055 00056 # The main service message is GetMotionPlan.srv, in arm_navigation_msgs. Parts that were hijacked 00057 # for the relevant inputs and outputs: 00058 00059 # inputs: 00060 # req.motion_plan_request.start_state.joint_state.name: the names of the arm joints in order (defaults to those provided by IKQuery, if not specified) 00061 # req.motion_plan_request.start_state.joint_state.position: a set of joint angles to stay sort of close to if possible (defaults to current joint angles if not specified) 00062 00063 # req.motion_plan_request.start_state.multi_dof_joint_state.pose: the start pose for the r_wrist_roll_link 00064 # req.motion_plan_request.start_state.multi_dof_joint_state.child_frame_id: the link to put at that pose (better be 'r_wrist_roll_link' or 'l_wrist_roll_link') 00065 # req.motion_plan_request.start_state.multi_dof_joint_state.frame_id: the frame for the start pose 00066 00067 # req.motion_plan_request.goal_constraints.position_constraints[0].position: the (x,y,z) goal position of the same link as the start pose 00068 # req.motion_plan_request.goal_constraints.position_constraints[0].header.frame_id: the frame that goal position is in 00069 # req.motion_plan_request.goal_constraints.orientation_constraints[0].orientation: the (x,y,z,w) goal orientation of the same link as the start pose 00070 # req.motion_plan_request.goal_constraints.orientation_constraints[0].header.frame_id: the frame that goal orientation is in 00071 00072 # req.motion_plan_request.workspace_parameters.ordered_collision_operations: the list of collision operations to modify the collision space 00073 # req.motion_plan_request.link_padding: links and collision-space padding to change from the default dynamically 00074 00075 # outputs: 00076 # res.trajectory.joint_trajectory.joint_names: the arm joint names in the normal order, as spit out by IKQuery 00077 # res.trajectory.joint_trajectory.points: a list of JointTrajectoryPoints whose 'positions' fields are lists of joint angles in a trajectory that gets you from start to goal (positions will be all 0s if a point on the path is out of reach), 00078 # 'velocities' fields are reasonable joint velocities, and 'time_from_start' fields are reasonable times to get to each waypoint. ('accelerations' will be empty.) 00079 # res.trajectory_error_codes: a list of ArmNavigationErrorCodes messages the same length as the trajectory, 00080 # with values for each trajectory point as follows: 00081 # ArmNavigationErrorCodes.SUCCESS (1): no problem 00082 # ArmNavigationErrorCodes.COLLISION_CONSTRAINTS_VIOLATED (-23): no non-colliding IK solution (colliding solution provided) 00083 # ArmNavigationErrorCodes.PATH_CONSTRAINTS_VIOLATED (-22): inconsistency in path between this point and the next point 00084 # ArmNavigationErrorCodes.JOINT_LIMITS_VIOLATED (-21): out of reach (no colliding solution) 00085 # ArmNavigationErrorCodes.PLANNING_FAILED (-1): aborted before getting to this point 00086 00087 # Everything else is completely ignored. 00088 00089 00090 import roslib; roslib.load_manifest('interpolated_ik_motion_planner') 00091 import rospy 00092 import interpolated_ik_motion_planner.ik_utilities as ik_utilities 00093 import math 00094 import sys 00095 import pdb 00096 from arm_navigation_msgs.srv import GetMotionPlan, GetMotionPlanResponse 00097 from arm_navigation_msgs.msg import ArmNavigationErrorCodes, RobotState 00098 from geometry_msgs.msg import PoseStamped, PointStamped, QuaternionStamped, Pose, Point, Quaternion 00099 from trajectory_msgs.msg import JointTrajectoryPoint 00100 from interpolated_ik_motion_planner.srv import SetInterpolatedIKMotionPlanParams, SetInterpolatedIKMotionPlanParamsResponse 00101 from sensor_msgs.msg import JointState 00102 00103 # class to provide the interpolated ik motion planner service 00104 class InterpolatedIKService: 00105 00106 def __init__(self, which_arm): #which_arm is 'r', 'l', or None 00107 self.which_arm = which_arm 00108 self.node_name = 'interpolated_ik_motion_planner_server' 00109 if self.which_arm != None: 00110 self.node_name = which_arm+'_'+self.node_name 00111 rospy.init_node(self.node_name) 00112 00113 #get parameters from the parameter server 00114 00115 #the number of steps to use when interpolating (including start and finish) 00116 self.num_steps = rospy.get_param(self.node_name+'/num_steps', 6) 00117 00118 #the max angle distance (in any joint) before we declare that the path is inconsistent 00119 self.consistent_angle = rospy.get_param(self.node_name+'/consistent_angle', math.pi/9) 00120 00121 #how many steps between collision checks (0 or 1 is check every step; 2 is every other, etc.) 00122 self.collision_check_resolution = rospy.get_param(self.node_name+'/collision_check_resolution', 1) 00123 00124 #how many steps in the plan can be invalid before aborting 00125 self.steps_before_abort = rospy.get_param(self.node_name+'/steps_before_abort', 0) 00126 00127 #the max translation (m) to move the wrist between waypoints (only used if num_steps is 0) 00128 self.pos_spacing = rospy.get_param(self.node_name+'/pos_spacing', 0.01) 00129 00130 #the max rotation (rad) to move the wrist between waypoints (only used if num_steps is 0) 00131 self.rot_spacing = rospy.get_param(self.node_name+'/rot_spacing', .1) 00132 00133 #whether to check for collisions or not 00134 self.collision_aware = rospy.get_param(self.node_name+'/collision_aware', 1) 00135 00136 #if 1, starts the IK calculation from the end and works backwards (if 0, starts from the start) 00137 self.start_from_end = rospy.get_param(self.node_name+'/start_from_end', 0) 00138 00139 #max joint velocities to use when calculating times and vels for the trajectory 00140 self.max_joint_vels = rospy.get_param(self.node_name+'/max_joint_vels', [.1]*7) 00141 00142 #max joint accelerations to use when calculating times and vels for the trajectory 00143 self.max_joint_accs = rospy.get_param(self.node_name+'/max_joint_accs', [.25]*7) 00144 00145 #initialize an IKUtilities class object 00146 if which_arm == 'r': 00147 self.ik_utils = ik_utilities.IKUtilities('right') 00148 elif which_arm == 'l': 00149 self.ik_utils = ik_utilities.IKUtilities('left') 00150 else: 00151 self.ik_utils = ik_utilities.IKUtilities() 00152 00153 #advertise interpolated IK service 00154 service_name = 'interpolated_ik_motion_plan' 00155 if which_arm != None: 00156 service_name = which_arm+'_'+service_name 00157 s1 = rospy.Service(service_name, GetMotionPlan, self.interpolated_ik_motion_planner_callback) 00158 00159 #advertise param changing service 00160 s2 = rospy.Service(service_name+'_set_params', \ 00161 SetInterpolatedIKMotionPlanParams, self.set_params_callback) 00162 00163 00164 ##add a header to a message with a 0 timestamp (good for getting the latest TF transform) 00165 def add_header(self, msg, frame): 00166 msg.header.frame_id = frame 00167 msg.header.stamp = rospy.Time() 00168 return msg 00169 00170 00171 ##pretty-print list to string 00172 def pplist(self, list): 00173 return ' '.join(['%2.3f'%x for x in list]) 00174 00175 00176 ##callback for the set_params service 00177 def set_params_callback(self, req): 00178 self.num_steps = req.num_steps 00179 self.consistent_angle = req.consistent_angle 00180 self.collision_check_resolution= req.collision_check_resolution 00181 self.steps_before_abort = req.steps_before_abort 00182 self.pos_spacing = req.pos_spacing 00183 self.rot_spacing = req.rot_spacing 00184 self.collision_aware = req.collision_aware 00185 self.start_from_end = req.start_from_end 00186 self.max_joint_vels = req.max_joint_vels 00187 self.max_joint_accs = req.max_joint_accs 00188 return SetInterpolatedIKMotionPlanParamsResponse() 00189 00190 00191 ##callback for get_interpolated_ik_motion_plan service 00192 def interpolated_ik_motion_planner_callback(self, req): 00193 00194 #names and angles for the joints in their desired order 00195 joint_names = req.motion_plan_request.start_state.joint_state.name 00196 start_angles = req.motion_plan_request.start_state.joint_state.position 00197 00198 #sanity-checking: joint_names and start_angles should be the same length, if any start_angles are specified 00199 if start_angles and len(joint_names) != len(start_angles): 00200 rospy.logerr("start_state.joint_state.name needs to be the same length as start_state.joint_state.position! Quitting") 00201 return 0 00202 00203 #reorder the start angles to the order needed by IK 00204 reordered_start_angles = [] 00205 00206 #get the desired start angles for each IK arm joint in turn from start_state.joint_state.position 00207 #(use the current angle if not specified) 00208 for joint_name in self.ik_utils.joint_names: 00209 00210 #desired start angle specified 00211 if joint_name in joint_names and start_angles: 00212 index = joint_names.index(joint_name) 00213 reordered_start_angles.append(start_angles[index]) 00214 else: 00215 rospy.logerr("missing joint angle, can't deal") 00216 return 0 00217 00218 #get additional desired joint angles (such as for the gripper) to pass through to IK 00219 additional_joint_angles = [] 00220 additional_joint_names = [] 00221 for (ind, joint_name) in enumerate(joint_names): 00222 if joint_name not in self.ik_utils.joint_names: 00223 #rospy.loginfo("found %s"%joint_name) 00224 additional_joint_angles.append(start_angles[ind]) 00225 additional_joint_names.append(joint_name) 00226 IK_robot_state = None 00227 if additional_joint_angles: 00228 IK_robot_state = RobotState() 00229 IK_robot_state.joint_state.name = additional_joint_names 00230 IK_robot_state.joint_state.position = additional_joint_angles 00231 00232 #check that the desired link is in the list of possible IK links (only r/l_wrist_roll_link for now) 00233 link_name = req.motion_plan_request.start_state.multi_dof_joint_state.child_frame_ids[0] 00234 if link_name != self.ik_utils.link_name: 00235 rospy.logerr("link_name not allowed: %s"%link_name) 00236 return 0 00237 00238 #the start pose for that link 00239 start_pose = req.motion_plan_request.start_state.multi_dof_joint_state.poses[0] 00240 00241 #the frame that start pose is in 00242 frame_id = req.motion_plan_request.start_state.multi_dof_joint_state.frame_ids[0] 00243 00244 #turn it into a PoseStamped 00245 start_pose_stamped = self.add_header(PoseStamped(), frame_id) 00246 start_pose_stamped.pose = start_pose 00247 00248 #the desired goal position 00249 goal_pos = req.motion_plan_request.goal_constraints.position_constraints[0].position 00250 00251 #the frame that goal position is in 00252 goal_pos_frame = req.motion_plan_request.goal_constraints.position_constraints[0].header.frame_id 00253 00254 #convert the position to base_link frame 00255 goal_ps = self.add_header(PointStamped(), goal_pos_frame) 00256 goal_ps.point = goal_pos 00257 goal_pos_list = self.ik_utils.point_stamped_to_list(goal_ps, 'base_link') 00258 00259 #the desired goal orientation 00260 goal_quat = req.motion_plan_request.goal_constraints.orientation_constraints[0].orientation 00261 00262 #the frame that goal orientation is in 00263 goal_quat_frame = req.motion_plan_request.goal_constraints.orientation_constraints[0].header.frame_id 00264 00265 #convert the quaternion to base_link frame 00266 goal_qs = self.add_header(QuaternionStamped(), goal_quat_frame) 00267 goal_qs.quaternion = goal_quat 00268 goal_quat_list = self.ik_utils.quaternion_stamped_to_list(goal_qs, 'base_link') 00269 00270 #assemble the goal pose into a PoseStamped 00271 goal_pose_stamped = self.add_header(PoseStamped(), 'base_link') 00272 goal_pose_stamped.pose = Pose(Point(*goal_pos_list), Quaternion(*goal_quat_list)) 00273 00274 #get the ordered collision operations, if there are any 00275 ordered_collision_operations = None #req.motion_plan_request.ordered_collision_operations 00276 00277 #get the link paddings, if there are any 00278 link_padding = None #req.motion_plan_request.link_padding 00279 00280 #RUN! Check the Cartesian path for consistent, non-colliding IK solutions 00281 (trajectory, error_codes) = self.ik_utils.check_cartesian_path(start_pose_stamped, \ 00282 goal_pose_stamped, reordered_start_angles, self.pos_spacing, self.rot_spacing, \ 00283 self.consistent_angle, self.collision_aware, self.collision_check_resolution, \ 00284 self.steps_before_abort, self.num_steps, ordered_collision_operations, \ 00285 self.start_from_end, IK_robot_state, link_padding) 00286 00287 #find appropriate velocities and times for the valid part of the resulting joint path (invalid parts set to 0) 00288 #if we're searching from the end, keep the end; if we're searching from the start, keep the start 00289 start_ind = 0 00290 stop_ind = len(error_codes) 00291 if self.start_from_end: 00292 for ind in range(len(error_codes)-1, 0, -1): 00293 if error_codes[ind]: 00294 start_ind = ind+1 00295 break 00296 else: 00297 for ind in range(len(error_codes)): 00298 if error_codes[ind]: 00299 stop_ind = ind 00300 break 00301 (times, vels) = self.ik_utils.trajectory_times_and_vels(trajectory[start_ind:stop_ind], self.max_joint_vels, self.max_joint_accs) 00302 times = [0]*start_ind + times + [0]*(len(error_codes)-stop_ind) 00303 vels = [[0]*len(self.max_joint_vels)]*start_ind + vels + [[0]*len(self.max_joint_vels)]*(len(error_codes)-stop_ind) 00304 00305 rospy.logdebug("trajectory:") 00306 for ind in range(len(trajectory)): 00307 rospy.logdebug("error code "+ str(error_codes[ind]) + " pos : " + self.pplist(trajectory[ind])) 00308 rospy.logdebug("") 00309 for ind in range(len(trajectory)): 00310 rospy.logdebug("time: " + "%5.3f "%times[ind] + "vels: " + self.pplist(vels[ind])) 00311 00312 #the response 00313 res = GetMotionPlanResponse() 00314 00315 #the arm joint names in the normal order, as spit out by IKQuery 00316 res.trajectory.joint_trajectory.joint_names = self.ik_utils.joint_names[:] 00317 00318 #a list of lists of joint angles, velocities, and times for a trajectory that gets you from start to goal 00319 #(all 0s if there was no IK solution for a point on the path) 00320 res.trajectory.joint_trajectory.points = [] 00321 for i in range(len(trajectory)): 00322 joint_trajectory_point = JointTrajectoryPoint() 00323 joint_trajectory_point.positions = trajectory[i] 00324 joint_trajectory_point.velocities = vels[i] 00325 joint_trajectory_point.time_from_start = rospy.Duration(times[i]) 00326 res.trajectory.joint_trajectory.points.append(joint_trajectory_point) 00327 00328 #a list of ArmNavigationErrorCodes messages, one for each trajectory point, with values as follows: 00329 #ArmNavigationErrorCodes.SUCCESS (1): no problem 00330 #ArmNavigationErrorCodes.COLLISION_CONSTRAINTS_VIOLATED (-23): no non-colliding IK solution (colliding solution provided) 00331 #ArmNavigationErrorCodes.PATH_CONSTRAINTS_VIOLATED (-22): inconsistency in path between this point and the next point 00332 #ArmNavigationErrorCodes.JOINT_LIMITS_VIOLATED (-21): out of reach (no colliding solution) 00333 #ArmNavigationErrorCodes.PLANNING_FAILED (-1): aborted before getting to this point 00334 error_code_dict = {0:ArmNavigationErrorCodes.SUCCESS, 1:ArmNavigationErrorCodes.COLLISION_CONSTRAINTS_VIOLATED, \ 00335 2:ArmNavigationErrorCodes.PATH_CONSTRAINTS_VIOLATED, 3:ArmNavigationErrorCodes.JOINT_LIMITS_VIOLATED, \ 00336 4:ArmNavigationErrorCodes.PLANNING_FAILED} 00337 00338 trajectory_error_codes = [ArmNavigationErrorCodes(val=error_code_dict[error_code]) for error_code in error_codes] 00339 res.trajectory_error_codes = trajectory_error_codes 00340 res.error_code.val = ArmNavigationErrorCodes.SUCCESS 00341 # rospy.loginfo("trajectory:") 00342 # for ind in range(len(trajectory)): 00343 # rospy.loginfo("error code "+ str(error_codes[ind]) + " pos : " + self.pplist(trajectory[ind])) 00344 # rospy.loginfo("") 00345 # for ind in range(len(trajectory)): 00346 # rospy.loginfo("time: " + "%5.3f "%times[ind] + "vels: " + self.pplist(vels[ind])) 00347 00348 return res 00349 00350 00351 if __name__ == "__main__": 00352 00353 which_arm = None 00354 if len(rospy.myargv()) >= 2: 00355 which_arm = rospy.myargv()[1] 00356 interpolated_ik_service = InterpolatedIKService(which_arm) 00357 rospy.loginfo("Ready to serve interpolated IK motion plan requests.") 00358 00359 rospy.spin() 00360