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