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 PKG="articulate_cart"
00032 import roslib; roslib.load_manifest(PKG)
00033 import rospy
00034 import tf
00035 import thread
00036 import numpy
00037 
00038 from manipulation_transforms.srv import *
00039 
00040 from std_msgs.msg import *
00041 from geometry_msgs.msg import *
00042 
00043 from math import pi, pow, atan, sin, cos, sqrt
00044 
00045 class ArticulateCartException(Exception):
00046   pass
00047 
00048 class articulateCartServer:
00049   """ A server for manipulating a cart being grasped by the grippers
00050   of the pr2.  Uses manipulation_transforms to compute gripper poses satisfying
00051   incoming goal requests.  Goals for the cart pose are specified in the
00052   base_footprint frame.  manipulation_transforms is used to map this goal to
00053   gripper poses in the base_footprint frame, which are then written
00054   to the goal topics for teleop_controllers (make sure to specify base_footprint
00055   as the frame_id rather than torso_lift_link).
00056   Accepts goals as PoseStamped messages, and issues PoseStamped messages
00057   to the arms.
00058   """
00059 
00060   
00061   _r_arm_SSE = 0
00062   _l_arm_SSE = 0
00063   _r_arm_err_twist = Twist()
00064   _l_arm_err_twist = Twist()
00065   errthresh = 0.02
00066 
00067   
00068   
00069   
00070   
00071   
00072   def __init__(self, use_tf_grippers, use_gripper_midpoint, tf_object_name):
00073     
00074     self._tl = tf.TransformListener()
00075 
00076     
00077     self._initialize_ros_coms()
00078 
00079     
00080     self.goal_received = False
00081     self.goal_sub = rospy.Subscriber('cart_pushing/ready_pose_achieved', rospy.Header, self.ready_pose_cb)
00082     rospy.loginfo('Articulate cart initialization waiting for ready pose before proceeding')
00083     ready_pose_counter = 0
00084     while not rospy.is_shutdown() and not self.goal_received:
00085        rospy.sleep(0.1)
00086        ready_pose_counter += 1
00087        if ready_pose_counter > 100:
00088          raise ArticulateCartException('Timed out when waiting for confirmation of ready pose achieved')
00089     rospy.loginfo('Articulate cart received ready pose')
00090     
00091     
00092     self._initialize_poses(use_tf_grippers, use_gripper_midpoint, tf_object_name)
00093 
00094     
00095     self._pose_mutex = thread.allocate_lock()
00096         
00097     
00098     self._fake_twist_loop_started = False 
00099 
00100     
00101     thread.start_new_thread(self._posture_loop, ("elbowmidr", "elbowmidl")) 
00102     
00103     
00104     print "Articulate Cart Server Ready!"
00105 
00106   def ready_pose_cb(self, msg):
00107     self.goal_received = True
00108 
00109   def _initialize_ros_coms(self):
00110     
00111     self._goal_sub = rospy.Subscriber('~command_twist', TwistStamped, self._fake_twist_goal_cb)
00112 
00113     
00114     
00115     self._r_arm_pose_pub = rospy.Publisher('/r_cart/command_pose', PoseStamped)
00116     self._l_arm_pose_pub = rospy.Publisher('/l_cart/command_pose', PoseStamped)
00117     self._r_arm_twist_pub = rospy.Publisher('/r_cart/command_twist', TwistStamped)
00118     self._l_arm_twist_pub = rospy.Publisher('/l_cart/command_twist', TwistStamped)
00119     
00120     
00121     self._r_posture_pub = rospy.Publisher("/r_cart/command_posture", Float64MultiArray)
00122     self._l_posture_pub = rospy.Publisher("/l_cart/command_posture", Float64MultiArray)
00123 
00124     
00125     self.invalid_pose_pub = rospy.Publisher("~invalid_pose", Empty)
00126     
00127     
00128     self._r_arm_sub = rospy.Subscriber('/r_cart/state/x_err', Twist, self._r_arm_err_cb)
00129     self._l_arm_sub = rospy.Subscriber('/l_cart/state/x_err', Twist, self._l_arm_err_cb)
00130     self._r_arm_err_lock = thread.allocate_lock()
00131     self._l_arm_err_lock = thread.allocate_lock()
00132     
00133     
00134     rospy.loginfo("Waiting for solver services")
00135     rospy.wait_for_service("manipulation_transforms_server/MapObjectPoseToEffectors", 10)
00136     rospy.wait_for_service("manipulation_transforms_server/LoadInitialTransforms", 10)
00137     self._solver_reloadTransforms_srv = rospy.ServiceProxy("manipulation_transforms_server/LoadInitialTransforms",
00138                                                            LoadInitialTransforms)
00139     self._cartPoseToGrippers_srv = rospy.ServiceProxy("manipulation_transforms_server/MapObjectPoseToEffectors",
00140                                                         MapObjectPoseToEffectors)
00141     rospy.loginfo("Solver services connected")
00142 
00143     
00144       
00145   def _initialize_poses(self, use_tf_grippers, use_gripper_midpoint, tf_object_name):
00146     
00147     call_reload=False
00148     if use_tf_grippers:
00149         
00150         self._r_pose_tup = self.get_tf_transform('base_footprint', 'r_gripper_tool_frame')
00151         self._l_pose_tup = self.get_tf_transform('base_footprint', 'l_gripper_tool_frame')
00152         if self._r_pose_tup is not None and self._l_pose_tup is not None:
00153             use_defaults = False
00154         else:
00155             rospy.logerr("TF lookup of gripper poses failed.  Using default grasp transforms from the parameter server")
00156         
00157         call_reload=True
00158     else:
00159       
00160       self._r_pose_tup = self.get_transform_param("cart_pushing/r_gripper_grasp")
00161       self._l_pose_tup = self.get_transform_param("cart_pushing/l_gripper_grasp")
00162     
00163     
00164     if use_gripper_midpoint:
00165         
00166         self._cart_pose_tup = self.interpolate_pose_tup(self._r_pose_tup, self._l_pose_tup, 0.5)
00167         
00168         call_reload=True
00169     elif tf_object_name:
00170         
00171         self._cart_pose_tup = self.get_tf_transform('base_footprint', tf_object_name)
00172         call_reload=True
00173     else:
00174         
00175         self._cart_pose_tup = self.get_transform_param("cart_pushing/cart_init_pose")
00176         
00177     
00178     self._cart_pose_msg = PoseStamped()
00179     self._cart_pose_msg.pose.position = geometry_msgs.msg.Point(*self._cart_pose_tup[0])
00180     self._cart_pose_msg.pose.orientation = geometry_msgs.msg.Quaternion(*self._cart_pose_tup[1])
00181     self._cart_pose_msg.header.frame_id = "base_footprint"
00182     self._cart_pose_msg.header.stamp = rospy.Time.now()
00183 
00184     
00185     self._cart_pose_euler = self.pose_msg_to_euler_list(self._cart_pose_msg.pose)
00186 
00187     
00188     self._r_pose_mtr = self.pose_tup_to_matrix(self._r_pose_tup)
00189     self._l_pose_mtr = self.pose_tup_to_matrix(self._l_pose_tup)
00190     self._l_relpose = self.get_rel_pose(self._r_pose_mtr, self._l_pose_mtr)
00191 
00192     
00193     self._cart_x_min = rospy.get_param("cart_pushing/x_min")
00194     self._cart_x_max = rospy.get_param("cart_pushing/x_max")
00195     self._cart_y_min = rospy.get_param("cart_pushing/y_min")
00196     self._cart_y_max = rospy.get_param("cart_pushing/y_max")
00197     self._cart_t_min = rospy.get_param("cart_pushing/t_min")
00198     self._cart_t_max = rospy.get_param("cart_pushing/t_max")
00199 
00200     
00201     if call_reload:
00202         rospy.loginfo("Reloading solver grasp transforms with current gripper poses")
00203         self.set_transform_param("cart_pushing/cart_init_pose", self._cart_pose_tup)
00204         self.set_transform_param("cart_pushing/r_gripper_grasp", self._r_pose_tup)
00205         self.set_transform_param("cart_pushing/l_gripper_grasp", self._l_pose_tup)
00206         resp = self._solver_reloadTransforms_srv("")
00207         if not resp.success:
00208           rospy.logerror("Failed to reload solver transforms!")
00209         else:
00210           rospy.loginfo("Reloaded solver transforms using current gripper poses")
00211 
00212     
00213     self._postures = {
00214       'off': [],
00215       'mantis': [0, 1, 0,  -1, 3.14, -1, 3.14],
00216       'elbowupr': [-0.79,0,-1.6,  9999, 9999, 9999, 9999],
00217       'elbowupl': [0.79,0,1.6 , 9999, 9999, 9999, 9999],
00218       'old_elbowupr': [-0.79,0,-1.6, -0.79,3.14, -0.79,5.49],
00219       'old_elbowupl': [0.79,0,1.6, -0.79,3.14, -0.79,5.49],
00220       'elbowdownr': [-0.028262077316910873, 1.2946342642324222, -0.25785640577652386, 
00221                       -1.5498884526859626, -31.278913849571776, -1.0527644894829107, -1.8127318367654268],
00222       'elbowdownl': [-0.0088195719039858515, 1.2834828245284853, 0.20338442004843196, 
00223                       -1.5565279256852611, -0.096340012666916802, -1.0235018652439782, 1.7990893054129216],
00224       'elbowmidr': [-0.28990347455256005, 1.089312348420358, -0.94309188122730947, -1.3356881736879038, 
00225                      0.42316056966725524, -1.3491191190220519, -5.3263966597959937],
00226       'elbowmidl': [0.52521589892944154, 1.0820586985900948, 1.0342420867971822, -1.3718169587386895, 
00227                     -6.6154390746853364, -1.3118240320657639, -10.572623351665454]
00228       }
00229              
00230 
00231   def _fake_twist_goal_cb(self, msg):
00232     """An alternative callback for twist messages that spawns an internal controller to simulate
00233     the object twist action using pose commands"""
00234     rospy.logdebug('Read goal: [%f,%f,%f][%f,%f,%f]', 
00235                   msg.twist.linear.x,
00236                   msg.twist.linear.y,
00237                   msg.twist.linear.z,
00238                   msg.twist.angular.x,
00239                   msg.twist.angular.y,
00240                   msg.twist.angular.z)
00241     
00242     
00243     with self._pose_mutex:
00244       self._twist_req = msg.twist
00245 
00246     
00247     if self._fake_twist_loop_started == False:
00248         self._fake_twist_loop_started = True;
00249         thread.start_new_thread(self._fake_twist_controller, ()) 
00250       
00251   def _r_arm_err_cb(self, twist):
00252     self._r_arm_err_lock.acquire()
00253     self._r_arm_err_twist=twist;
00254     self._r_arm_SSE = self.get_twist_SSE(twist)
00255     self._r_arm_err_lock.release()
00256 
00257   def _l_arm_err_cb(self, twist):
00258     self._l_arm_err_lock.acquire()
00259     self._l_arm_err_twist=twist
00260     self._l_arm_SSE = self.get_twist_SSE(twist)
00261     self._l_arm_err_lock.release()
00262 
00263   def _posture_loop(self, r_posture, l_posture):
00264     """ Send updates to the posture topic every ROS second """
00265     while not rospy.is_shutdown():
00266       self._r_posture_pub.publish(Float64MultiArray(data = self._postures[r_posture]))
00267       self._l_posture_pub.publish(Float64MultiArray(data = self._postures[l_posture]))
00268       rospy.sleep(1.0)
00269 
00270   def _fake_twist_controller(self, r = 20, recovery_horizon = 5):
00271       """ Simulates the twist applied to the object, and updates the gripper poses accordingly.
00272       """
00273 
00274       counter = 0
00275 
00276       
00277       with self._pose_mutex:
00278           self._cart_pose_euler = self.pose_msg_to_euler_list(self._cart_pose_msg.pose)
00279       rate = rospy.Rate(r)
00280       t = 1.0/r
00281       
00282       while not rospy.is_shutdown():
00283           with self._pose_mutex:
00284             
00285             
00286             counter += 1
00287 
00288             if self._check_cart_pose(self._cart_pose_euler):
00289               
00290               new_pose = forward_simulate_twist(self._cart_pose_euler, self._twist_req, t)
00291               if self._check_cart_pose(new_pose):
00292                 self.set_new_pose(new_pose)
00293               else:
00294                 self.invalid_pose_pub.publish()
00295                 rospy.logwarn('Applying %s for %s secs from %s would lead to infeasible pose %s; ignoring.',
00296                               self._twist_req, t, self._cart_pose_euler, new_pose)
00297 
00298             else:
00299               
00300               
00301               succeeded = False
00302               for i in xrange(recovery_horizon):
00303                 new_pose = forward_simulate_twist(self._cart_pose_euler, self._twist_req, i*t)
00304                 if self._check_cart_pose(new_pose):
00305                   
00306                   rospy.loginfo('Current pose %s invalid, but %s leads to valid pose %s after %s seconds',
00307                                 self._cart_pose_euler, self._twist_req, i*t)
00308                   self.set_new_pose(new_pose)
00309                   succeeded = True
00310                   break
00311               if not succeeded:
00312                 self.invalid_pose_pub.publish()
00313                 rospy.logwarn('Currently in collision at %s, and twist %s not helping',
00314                               self._cart_pose_euler, self._twist_req)
00315           rate.sleep()
00316 
00317   def set_new_pose (self, p):
00318       self._cart_pose_euler = p
00319       self._cart_pose_msg.pose = self.euler_list_to_pose_msg(p)
00320       self._pose_resp = self._cartPoseToGrippers_srv(self._cart_pose_msg)
00321       self._pose_command_action() 
00322       
00323   def _pose_command_action(self):
00324     """Send the target effector poses to the arm control topics
00325     Assumes that self._pose_resp is up-to-date"""
00326     if len(self._pose_resp.effector_poses) != 2:
00327       rospy.logerr("Expected 2 gripper poses, but got %d" % len(self._pose_resp.effector_poses))
00328     else:
00329       self._r_arm_pose_pub.publish(self._pose_resp.effector_poses[0])
00330       self._l_arm_pose_pub.publish(self._pose_resp.effector_poses[1])
00331     
00332     
00333     
00334     
00335     
00336 
00337   def get_transform_param(self, name):
00338       """Returns the named transform as a pose tuple
00339       
00340       Arguments:
00341       - `name`: string matching a pose on the param server
00342       """
00343       param = rospy.get_param(name)
00344       rospy.loginfo('Parameter %s is %s', name, param)
00345       return (tuple(param["position"]), tuple(param["orientation"]))  
00346   
00347 
00348   def set_transform_param(self, name, pose_tup):
00349       """Sets the provided transform param tuple on the paramater server in "cart format"
00350       
00351       Arguments:
00352       - `pose_tup`: a tuple containing ((trans),(rot))
00353       - `name`: a string to set
00354       """
00355       rospy.set_param(name, {"position":pose_tup[0], "orientation":pose_tup[1]})
00356   
00357   def pose_tup_to_matrix(self, tup):
00358       """Returns a numpy 4x4 transform array from a pose provided in a tuple ((vector), (quaternion))"""
00359       mtr = tf.transformations.quaternion_matrix(tup[1])
00360       mtr[0][3] = tup[0][0]
00361       mtr[1][3] = tup[0][1]
00362       mtr[2][3] = tup[0][2]
00363       return mtr
00364 
00365   def pose_tup_to_msg(self, tup):
00366     """Returns Pose message assembled from tf query tuple """
00367     msg = Pose()
00368     msg.position.x = tup[0][0]
00369     msg.position.y = tup[0][1]
00370     msg.position.z = tup[0][2]
00371     msg.orientation.x = tup[1][1]
00372     msg.orientation.y = tup[1][2]
00373     msg.orientation.z = tup[1][2]
00374     msg.orientation.w = tup[1][3]
00375     return msg
00376   
00377   def get_rel_pose(self, p1, p2):
00378       """Compute relative pose of p2 in frame of p1 (4x4 numpy arrays)"""      
00379       return numpy.dot(numpy.linalg.inv(p1), p2)
00380 
00381   def pose_msg_to_euler_list(self, Pose):
00382       """Returns a list representation of Pose [[vector],[euler angles]]"""
00383       return [[Pose.position.x,
00384                Pose.position.y,
00385                Pose.position.z],
00386                list(tf.transformations.euler_from_quaternion([Pose.orientation.x,
00387                                                               Pose.orientation.y,
00388                                                               Pose.orientation.z,
00389                                                               Pose.orientation.w]))]
00390   def euler_list_to_pose_msg(self, euler_pose):
00391       """ convert to pose represented as a list [[vector],[euler angles]] to a Pose msg"""
00392       quat = tf.transformations.quaternion_from_euler(*euler_pose[1])
00393       return Pose(position=Point(*euler_pose[0]),
00394                   orientation=Quaternion(*quat))
00395       
00396   def twist_msg_to_tup(self, msg):
00397       """Returns a twist tuple ((linear), (angular)) from parameters in a Twist message """
00398       return ((msg.linear.x, msg.linear.y, msg.linear.z),
00399               (msg.angular.x, msg.angular.y, msg.angular.z))
00400 
00401   def twist_tup_to_msg(self, tup):
00402       """ Returns a Twist message from parameters in a tuple ((linear), (angular))"""
00403       msg=Twist()
00404       msg.linear.x = tup[0][0]
00405       msg.linear.y = tup[0][1]
00406       msg.linear.z = tup[0][2]
00407       msg.angular.x = tup[1][0]
00408       msg.angular.y = tup[1][1]
00409       msg.angular.z = tup[1][2]  
00410       return msg
00411         
00412   def interpolate_pose_tup(self, p1, p2, blend=0.5):
00413       """Returns weighted interpolation of two pose tuples ((vector), (quaternion))"""
00414       linear = blend*numpy.array(list(p1[0])) + (1-blend)*numpy.array(list(p2[0]))
00415       angular = tf.transformations.quaternion_slerp(p1[1], p2[1], blend)
00416       return (linear.tolist(), angular.tolist())
00417   
00418   def interpolate_twist_tup(self, t1, t2, blend=0.5):
00419       """Returns a weighted average of two twist tuples ((linear), (angular))"""
00420       linear = blend*numpy.array(list(t1[0])) + (1-blend)*numpy.array(list(t2[0]))
00421       angular = blend*numpy.array(list(t1[1])) + (1-blend)*numpy.array(list(t2[1]))
00422       return (linear.tolist(), angular.tolist())
00423 
00424   def get_tf_transform(self, ref_frame, target_frame):
00425       """An exception-handling method for querying a transform from tf"""
00426       try:
00427           self._tl.waitForTransform(ref_frame, target_frame, rospy.Time(0), rospy.Duration(4.0))
00428           return self._tl.lookupTransform(ref_frame, target_frame, rospy.Time(0)) 
00429       except (tf.Exception, tf.LookupException, tf.ConnectivityException) as ex:
00430           print ex
00431           return None
00432   
00433   def get_l_drift_twist(self):
00434       """ Uses pose call to solver to compute error at left gripper, and returns as twist
00435       """
00436       
00437       
00438       
00439       
00440       
00441       
00442       
00443       
00444       
00445       
00446       
00447       
00448       
00449       
00450       
00451       
00452       
00453       
00454       
00455       
00456       
00457       
00458       return ((0,0,0),(0,0,0))
00459       
00460   def get_twist_SSE(self, twist):
00461     return sum([pow(twist.linear.x,2),
00462                pow(twist.linear.y,2),
00463                pow(twist.linear.z,2),
00464                pow(twist.angular.x,2),
00465                pow(twist.angular.y,2),
00466                pow(twist.angular.z,2)])
00467   
00468   def _check_cart_pose(self, pose):
00469     """ Returns true if pose is reachable given workspace constraints of cart"""
00470     if pose[0][0] < self._cart_x_min or pose[0][0] > self._cart_x_max or \
00471        pose[0][1] < self._cart_y_min or pose[0][1] > self._cart_y_max or \
00472        pose[1][2] < self._cart_t_min or pose[1][2] > self._cart_t_max:
00473        return False
00474     
00475     return True
00476 
00477 
00478 
00479 
00480 
00481 
00482 def forward_simulate_twist (pose, twist, t):
00483     """Forward simulate twist from pose for t seconds and return the resulting pose"""
00484     return [[pose[0][0] + twist.linear.x*t,
00485              pose[0][1] + twist.linear.y*t,
00486              pose[0][2] + twist.linear.z*t],
00487             [pose[1][0] + twist.angular.x*t,
00488              pose[1][1] + twist.angular.y*t,
00489              pose[1][2] + twist.angular.z*t]]
00490 
00491 
00492 
00493 
00494 
00495   
00496 if __name__ == '__main__':
00497   from optparse import OptionParser
00498   parser = OptionParser(usage="")
00499   parser.add_option("-g", action="store_true", dest="use_tf_grippers", default=False,
00500                     help="use current gripper poses for initial grasp transforms (otherwise leaves defaults from param server)")
00501   parser.add_option("-m", "--midpoint", action="store_true", dest="midpoint", default=False,
00502                     help="set object initial pose to midpoint between grippers [default=%default]")
00503   parser.add_option("-o", "--object", dest="object", default="",
00504                     help="attempt to load an initial object transform with name OBJECT from tf [default=\"%default\"]")
00505   (options, args) = parser.parse_args()
00506   
00507   rospy.init_node(PKG+"_server")
00508   server = articulateCartServer(options.use_tf_grippers, options.midpoint, options.object)
00509   
00510   rospy.spin()
00511 
00512 
00513