$search
00001 #! /usr/bin/env python 00002 # Copyright (c) 2008, Willow Garage, Inc. 00003 # All rights reserved. 00004 # 00005 # Redistribution and use in source and binary forms, with or without 00006 # modification, are permitted provided that the following conditions are met: 00007 # 00008 # * Redistributions of source code must retain the above copyright 00009 # notice, this list of conditions and the following disclaimer. 00010 # * Redistributions in binary form must reproduce the above copyright 00011 # notice, this list of conditions and the following disclaimer in the 00012 # documentation and/or other materials provided with the distribution. 00013 # * Neither the name of the Willow Garage, Inc. nor the names of its 00014 # contributors may be used to endorse or promote products derived from 00015 # this software without specific prior written permission. 00016 # 00017 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00018 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00019 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00020 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 00021 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00022 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00023 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00024 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00025 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00026 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00027 # POSSIBILITY OF SUCH DAMAGE. 00028 00029 # Author: Jon Scholz 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 import threading 00038 00039 from tf import transformations 00040 00041 00042 from std_msgs.msg import * 00043 from std_msgs.msg import Bool 00044 from geometry_msgs.msg import * 00045 #from pr2_controllers_msgs.msg import * 00046 from math import pi, pow, atan, sin, cos, sqrt 00047 00048 class ArticulateCartException(Exception): 00049 pass 00050 00051 class articulateCartServer(object): 00052 """ A server for manipulating a cart being grasped by the grippers 00053 of the pr2. Uses manipulation_transforms to compute gripper poses satisfying 00054 incoming goal requests. Goals for the cart pose are specified in the 00055 base_footprint frame. manipulation_transforms is used to map this goal to 00056 gripper poses in the base_footprint frame, which are then written 00057 to the goal topics for teleop_controllers (make sure to specify base_footprint 00058 as the frame_id rather than torso_lift_link). 00059 Accepts goals as PoseStamped messages, and issues PoseStamped messages 00060 to the arms. 00061 """ 00062 00063 # create messages that are used to publish feedback/result 00064 _r_arm_SSE = 0 00065 _l_arm_SSE = 0 00066 _r_arm_err_twist = Twist() 00067 _l_arm_err_twist = Twist() 00068 errthresh = 0.02 00069 00070 ## 00071 # @param tf_object_name: if non-empty, attempt to lookup object initial pose on tf with given frame name, and re-initialize manipulation_transforms 00072 # 00073 def __init__(self, tf_object_name): 00074 self._tf_object_name = tf_object_name 00075 00076 # A tf listener for reading gripper poses and stuff 00077 self._tl = tf.TransformListener() 00078 00079 # Indicator fake twist controller loop 00080 self._fake_twist_loop_started = False # Toggled by first incoming twist message 00081 00082 self._last_warn_infeasible = 0 00083 00084 ## Mutex for pose-related members {_cart_pose_tup, _cart_pose_msg, _pose_resp, _twist_req, _cart_pose_euler} 00085 self._pose_mutex = threading.RLock() 00086 00087 self._last_twist_req = rospy.Time(0) 00088 00089 self._r_xfm = None 00090 self._l_xfm = None 00091 00092 ## Load cart workspace constraints from parameter server 00093 self._cart_x_min = rospy.get_param("cart_pushing/x_min") 00094 self._cart_x_max = rospy.get_param("cart_pushing/x_max") 00095 self._cart_y_min = rospy.get_param("cart_pushing/y_min") 00096 self._cart_y_max = rospy.get_param("cart_pushing/y_max") 00097 self._cart_t_min = rospy.get_param("cart_pushing/t_min") 00098 self._cart_t_max = rospy.get_param("cart_pushing/t_max") 00099 00100 ## Hard-code a set of arm postures 00101 self._postures = { 00102 'off': [], 00103 'mantis': [0, 1, 0, -1, 3.14, -1, 3.14], 00104 'elbowupr': [-0.79,0,-1.6, 9999, 9999, 9999, 9999], 00105 'elbowupl': [0.79,0,1.6 , 9999, 9999, 9999, 9999], 00106 'old_elbowupr': [-0.79,0,-1.6, -0.79,3.14, -0.79,5.49], 00107 'old_elbowupl': [0.79,0,1.6, -0.79,3.14, -0.79,5.49], 00108 'elbowdownr': [-0.028262077316910873, 1.2946342642324222, -0.25785640577652386, 00109 -1.5498884526859626, -31.278913849571776, -1.0527644894829107, -1.8127318367654268], 00110 'elbowdownl': [-0.0088195719039858515, 1.2834828245284853, 0.20338442004843196, 00111 -1.5565279256852611, -0.096340012666916802, -1.0235018652439782, 1.7990893054129216], 00112 'elbowmidr': [-0.28990347455256005, 1.089312348420358, -0.94309188122730947, -1.3356881736879038, 00113 0.42316056966725524, -1.3491191190220519, -5.3263966597959937], 00114 'elbowmidl': [0.52521589892944154, 1.0820586985900948, 1.0342420867971822, -1.3718169587386895, 00115 -6.6154390746853364, -1.3118240320657639, -10.572623351665454] 00116 } 00117 00118 00119 ## Attach to all topics and services 00120 self._initialize_ros_coms() 00121 00122 self._active = False 00123 self._active_sub = rospy.Subscriber('~articulate_cart', Bool, self._active_cb) 00124 00125 self._active_pub = rospy.Publisher('~articulation_active', Bool, latch=True) 00126 self._active_pub.publish(self._active) 00127 00128 ## Initialize object and gripper poses according to constructor params 00129 self._initialize_poses() 00130 00131 00132 00133 ## Spin thread for posture updater 00134 thread.start_new_thread(self._posture_loop, ("elbowdownr", "elbowdownl")) # elbowmidr, elbowmidl 00135 00136 ## All done: 00137 print "Articulate Cart Server Ready!" 00138 00139 def _active_cb(self, msg): 00140 rospy.loginfo('Cart articulation status set to %s' % (('ENABLED' if msg.data else 'DISABLED'))) 00141 self._active = msg.data 00142 self._active_pub.publish(self._active) 00143 00144 if self._active: 00145 self._initialize_poses() 00146 else: 00147 # Reset postures 00148 self._r_posture_pub.publish(Float64MultiArray()) 00149 self._l_posture_pub.publish(Float64MultiArray()) 00150 00151 def _initialize_ros_coms(self): 00152 ## subscriber for goal topic 00153 self._goal_sub = rospy.Subscriber('~command_twist', TwistStamped, self._fake_twist_goal_cb) 00154 00155 00156 ## publisher for arm cartesian pose/twist controller 00157 self._r_arm_pose_pub = rospy.Publisher('/r_cart/command_pose', PoseStamped) 00158 self._l_arm_pose_pub = rospy.Publisher('/l_cart/command_pose', PoseStamped) 00159 self._r_arm_twist_pub = rospy.Publisher('/r_cart/command_twist', TwistStamped) 00160 self._l_arm_twist_pub = rospy.Publisher('/l_cart/command_twist', TwistStamped) 00161 00162 ## Publishers for teleop_controllers' posture topics 00163 self._r_posture_pub = rospy.Publisher("/r_cart/command_posture", Float64MultiArray) 00164 self._l_posture_pub = rospy.Publisher("/l_cart/command_posture", Float64MultiArray) 00165 00166 ## Publisher for when when an invalid pose is received 00167 self.invalid_pose_pub = rospy.Publisher("~invalid_pose", Empty) 00168 00169 ## subscriber for arm state errors 00170 self._r_arm_sub = rospy.Subscriber('/r_cart/state/x_err', Twist, self._r_arm_err_cb) 00171 self._l_arm_sub = rospy.Subscriber('/l_cart/state/x_err', Twist, self._l_arm_err_cb) 00172 self._r_arm_err_lock = thread.allocate_lock() 00173 self._l_arm_err_lock = thread.allocate_lock() 00174 00175 00176 def _initialize_poses(self): 00177 r_pose_tup = self.get_tf_transform('base_footprint', 'r_gripper_tool_frame') 00178 l_pose_tup = self.get_tf_transform('base_footprint', 'l_gripper_tool_frame') 00179 cart_pose_tup = self.get_tf_transform('base_footprint', self._tf_object_name) 00180 00181 if not r_pose_tup or not l_pose_tup or not cart_pose_tup: 00182 rospy.logerr('Unable to find initial cart and gripper poses!') 00183 self._r_xfm = None 00184 self._l_xfm = None 00185 return False 00186 00187 # Convert to matrices 00188 r_pose_mtx = transformations.compose_matrix(translate = r_pose_tup[0], angles = transformations.euler_from_quaternion(r_pose_tup[1])) 00189 l_pose_mtx = transformations.compose_matrix(translate = l_pose_tup[0], angles = transformations.euler_from_quaternion(l_pose_tup[1])) 00190 cart_pose_mtx = transformations.compose_matrix(translate = cart_pose_tup[0], angles = transformations.euler_from_quaternion(cart_pose_tup[1])) 00191 00192 self._r_xfm = numpy.dot(r_pose_mtx, numpy.linalg.inv(cart_pose_mtx)) 00193 self._l_xfm = numpy.dot(l_pose_mtx, numpy.linalg.inv(cart_pose_mtx)) 00194 00195 rospy.loginfo('Cart, gripper poses initialized') 00196 00197 return True 00198 00199 def _fake_twist_goal_cb(self, msg): 00200 """An alternative callback for twist messages that spawns an internal controller to simulate 00201 the object twist action using pose commands""" 00202 rospy.logdebug('Read goal: [%f,%f,%f][%f,%f,%f]', 00203 msg.twist.linear.x, 00204 msg.twist.linear.y, 00205 msg.twist.linear.z, 00206 msg.twist.angular.x, 00207 msg.twist.angular.y, 00208 msg.twist.angular.z) 00209 00210 # Save twist request for fake controller 00211 with self._pose_mutex: 00212 self._twist_req = msg.twist 00213 self._last_twist_req = msg.header.stamp 00214 00215 # if this is the first incoming message, spin the controller thread 00216 if self._fake_twist_loop_started == False: 00217 self._fake_twist_loop_started = True; 00218 thread.start_new_thread(self._fake_twist_controller, ()) # at default frequency 00219 00220 def _r_arm_err_cb(self, twist): 00221 self._r_arm_err_lock.acquire() 00222 self._r_arm_err_twist=twist; 00223 self._r_arm_SSE = self.get_twist_SSE(twist) 00224 self._r_arm_err_lock.release() 00225 00226 def _l_arm_err_cb(self, twist): 00227 self._l_arm_err_lock.acquire() 00228 self._l_arm_err_twist=twist 00229 self._l_arm_SSE = self.get_twist_SSE(twist) 00230 self._l_arm_err_lock.release() 00231 00232 def _posture_loop(self, r_posture, l_posture): 00233 """ Send updates to the posture topic every ROS second """ 00234 while not rospy.is_shutdown(): 00235 if self._active: 00236 self._r_posture_pub.publish(Float64MultiArray(data = self._postures[r_posture])) 00237 self._l_posture_pub.publish(Float64MultiArray(data = self._postures[l_posture])) 00238 rospy.sleep(1.0) 00239 00240 def _fake_twist_controller(self, r = 20, recovery_horizon = 5): 00241 """ Simulates the twist applied to the object, and updates the gripper poses accordingly. 00242 """ 00243 00244 counter = 0 00245 00246 rate = rospy.Rate(r) 00247 t = 1.0/r 00248 00249 while not rospy.is_shutdown(): 00250 with self._pose_mutex: 00251 # Don't command cart if our last update is stale 00252 if (rospy.get_time() - self._last_twist_req.to_sec() > 1.0): 00253 rate.sleep() 00254 continue 00255 00256 # if counter % 200 == 0: 00257 # rospy.loginfo('Cart rel pose is %s', self._cart_pose_euler) 00258 counter += 1 00259 00260 if self._r_xfm is None and self._l_xfm is None and not self._initialize_poses(): 00261 continue 00262 00263 # Load object orientation from tf 00264 cart_pose_tup = self.get_tf_transform('base_footprint', self._tf_object_name) 00265 00266 if not cart_pose_tup: 00267 rospy.logerr('Unable to find cart pose in base footprint frame!') 00268 continue 00269 00270 cart_pose_euler = self.pose_tup_to_euler_list(cart_pose_tup[0], cart_pose_tup[1]) 00271 00272 if self._check_cart_pose(cart_pose_euler): 00273 # Default case: current cart pose is valid; let's make sure it stays that way 00274 new_pose = forward_simulate_twist(cart_pose_euler, self._twist_req, t) 00275 if self._check_cart_pose(new_pose): 00276 self.set_new_pose(new_pose) 00277 else: 00278 self.invalid_pose_pub.publish() 00279 if rospy.get_time() - self._last_warn_infeasible > 1.0: 00280 rospy.logwarn('Applying %s for %s secs from %s would lead to infeasible pose %s; ignoring.', 00281 self._twist_req, t, cart_pose_euler, new_pose) 00282 self._last_warn_infeasible = rospy.get_time() 00283 00284 else: 00285 # 'Recovery' case: current pose is not valid; we check if the twist is bringing 00286 # us back into validity 00287 succeeded = False 00288 for i in xrange(recovery_horizon): 00289 new_pose = forward_simulate_twist(cart_pose_euler, self._twist_req, i*t) 00290 if self._check_cart_pose(new_pose): 00291 # Twist is bringing us back to validity, so we accept it 00292 rospy.loginfo('Current pose %s invalid, but %s leads to valid pose %s after %s seconds', 00293 cart_pose_euler, self._twist_req, new_pose, i*t) 00294 self.set_new_pose(new_pose) 00295 succeeded = True 00296 break 00297 if not succeeded: 00298 self.invalid_pose_pub.publish() 00299 rospy.logwarn('Currently in collision at %s, and twist %s not helping', 00300 cart_pose_euler, self._twist_req) 00301 rate.sleep() 00302 00303 # Sends commands to arm 00304 def set_new_pose (self, p): 00305 cart_pos_mtx = transformations.compose_matrix(translate=p[0], angles=p[1]) 00306 r_desired = _matrix_to_pose(numpy.dot(self._r_xfm, cart_pos_mtx)) 00307 l_desired = _matrix_to_pose(numpy.dot(self._l_xfm, cart_pos_mtx)) 00308 00309 self._pose_command_action(r_desired, l_desired) 00310 00311 def _pose_command_action(self, r_desired, l_desired): 00312 """Send the target effector poses to the arm control topics 00313 Assumes that self._pose_resp is up-to-date""" 00314 if self._active: 00315 r_ps = PoseStamped(pose=r_desired) 00316 r_ps.header.stamp = rospy.get_rostime() 00317 r_ps.header.frame_id = 'base_footprint' 00318 00319 l_ps = PoseStamped(pose=l_desired) 00320 l_ps.header.stamp = rospy.get_rostime() 00321 l_ps.header.frame_id = 'base_footprint' 00322 00323 self._r_arm_pose_pub.publish(r_ps) 00324 self._l_arm_pose_pub.publish(l_ps) 00325 00326 #print self._pose_resp.effector_poses 00327 # Uncomment to wait for error to drop before returning 00328 # while self._l_arm_SSE + self._r_arm_SSE > self.errthresh and not rospy.is_shutdown(): 00329 # print self._l_arm_SSE + self._r_arm_SSE 00330 00331 def get_transform_param(self, name): 00332 """Returns the named transform as a pose tuple 00333 00334 Arguments: 00335 - `name`: string matching a pose on the param server 00336 """ 00337 param = rospy.get_param(name) 00338 rospy.loginfo('Parameter %s is %s', name, param) 00339 return (tuple(param["position"]), tuple(param["orientation"])) 00340 00341 00342 def set_transform_param(self, name, pose_tup): 00343 """Sets the provided transform param tuple on the paramater server in "cart format" 00344 00345 Arguments: 00346 - `pose_tup`: a tuple containing ((trans),(rot)) 00347 - `name`: a string to set 00348 """ 00349 rospy.set_param(name, {"position":pose_tup[0], "orientation":pose_tup[1]}) 00350 00351 def pose_tup_to_matrix(self, tup): 00352 """Returns a numpy 4x4 transform array from a pose provided in a tuple ((vector), (quaternion))""" 00353 mtr = tf.transformations.quaternion_matrix(tup[1]) 00354 mtr[0][3] = tup[0][0] 00355 mtr[1][3] = tup[0][1] 00356 mtr[2][3] = tup[0][2] 00357 return mtr 00358 00359 def pose_tup_to_msg(self, tup): 00360 """Returns Pose message assembled from tf query tuple """ 00361 msg = Pose() 00362 msg.position.x = tup[0][0] 00363 msg.position.y = tup[0][1] 00364 msg.position.z = tup[0][2] 00365 msg.orientation.x = tup[1][1] 00366 msg.orientation.y = tup[1][2] 00367 msg.orientation.z = tup[1][2] 00368 msg.orientation.w = tup[1][3] 00369 return msg 00370 00371 def get_rel_pose(self, p1, p2): 00372 """Compute relative pose of p2 in frame of p1 (4x4 numpy arrays)""" 00373 return numpy.dot(numpy.linalg.inv(p1), p2) 00374 00375 def pose_tup_to_euler_list(self, position, orientation): 00376 """Returns a list representation of Pose [[vector],[euler angles]]""" 00377 return [position, 00378 list(tf.transformations.euler_from_quaternion([orientation[0], 00379 orientation[1], 00380 orientation[2], 00381 orientation[3]]))] 00382 00383 def pose_msg_to_euler_list(self, Pose): 00384 """Returns a list representation of Pose [[vector],[euler angles]]""" 00385 return [[Pose.position.x, 00386 Pose.position.y, 00387 Pose.position.z], 00388 list(tf.transformations.euler_from_quaternion([Pose.orientation.x, 00389 Pose.orientation.y, 00390 Pose.orientation.z, 00391 Pose.orientation.w]))] 00392 def euler_list_to_pose_msg(self, euler_pose): 00393 """ convert to pose represented as a list [[vector],[euler angles]] to a Pose msg""" 00394 quat = tf.transformations.quaternion_from_euler(*euler_pose[1]) 00395 return Pose(position=Point(*euler_pose[0]), 00396 orientation=Quaternion(*quat)) 00397 00398 def twist_msg_to_tup(self, msg): 00399 """Returns a twist tuple ((linear), (angular)) from parameters in a Twist message """ 00400 return ((msg.linear.x, msg.linear.y, msg.linear.z), 00401 (msg.angular.x, msg.angular.y, msg.angular.z)) 00402 00403 def twist_tup_to_msg(self, tup): 00404 """ Returns a Twist message from parameters in a tuple ((linear), (angular))""" 00405 msg=Twist() 00406 msg.linear.x = tup[0][0] 00407 msg.linear.y = tup[0][1] 00408 msg.linear.z = tup[0][2] 00409 msg.angular.x = tup[1][0] 00410 msg.angular.y = tup[1][1] 00411 msg.angular.z = tup[1][2] 00412 return msg 00413 00414 def interpolate_pose_tup(self, p1, p2, blend=0.5): 00415 """Returns weighted interpolation of two pose tuples ((vector), (quaternion))""" 00416 linear = blend*numpy.array(list(p1[0])) + (1-blend)*numpy.array(list(p2[0])) 00417 angular = tf.transformations.quaternion_slerp(p1[1], p2[1], blend) 00418 return (linear.tolist(), angular.tolist()) 00419 00420 def interpolate_twist_tup(self, t1, t2, blend=0.5): 00421 """Returns a weighted average of two twist tuples ((linear), (angular))""" 00422 linear = blend*numpy.array(list(t1[0])) + (1-blend)*numpy.array(list(t2[0])) 00423 angular = blend*numpy.array(list(t1[1])) + (1-blend)*numpy.array(list(t2[1])) 00424 return (linear.tolist(), angular.tolist()) 00425 00426 def get_tf_transform(self, ref_frame, target_frame): 00427 """An exception-handling method for querying a transform from tf""" 00428 try: 00429 self._tl.waitForTransform(ref_frame, target_frame, rospy.Time(0), rospy.Duration(0.2)) 00430 return self._tl.lookupTransform(ref_frame, target_frame, rospy.Time(0)) 00431 except (tf.Exception, tf.LookupException, tf.ConnectivityException) as ex: 00432 print ex 00433 return None 00434 00435 def get_l_drift_twist(self): 00436 """ Uses pose call to solver to compute error at left gripper, and returns as twist 00437 """ 00438 #TODO: debug this 00439 00440 # Compute target pose for left arm 00441 #r_pose_tup = self.get_tf_transform('base_footprint', 'r_gripper_tool_frame') 00442 #r_pose_mtr = self.pose_tup_to_matrix(r_pose_tup) 00443 #l_pose_goal = 00444 00445 # Compute relative transform to actual pose 00446 ########################################################## 00447 # Compute actual relative pose of l_gripper in r_gripper frame 00448 #r_pose_tup = self.get_tf_transform('base_footprint', 'r_gripper_tool_frame') 00449 #l_pose_tup = self.get_tf_transform('base_footprint', 'l_gripper_tool_frame') 00450 #r_pose_mtr = self.pose_tup_to_matrix(r_pose_tup) 00451 #l_pose_mtr = self.pose_tup_to_matrix(l_pose_tup) 00452 #l_relpose_actual = self.get_rel_pose(r_pose_mtr, l_pose_mtr) 00453 00454 # Compute transform from expected to actual 00455 #l_pose_err = self.get_rel_pose(self._l_relpose, l_relpose_actual) 00456 00457 # Convert to euler for interpretation as a twist tuple 00458 #return (tuple(tf.transformations.translation_from_matrix(l_pose_err)), 00459 # tf.transformations.euler_from_matrix(l_pose_err)) 00460 return ((0,0,0),(0,0,0)) 00461 00462 def get_twist_SSE(self, twist): 00463 return sum([pow(twist.linear.x,2), 00464 pow(twist.linear.y,2), 00465 pow(twist.linear.z,2), 00466 pow(twist.angular.x,2), 00467 pow(twist.angular.y,2), 00468 pow(twist.angular.z,2)]) 00469 00470 def _check_cart_pose(self, pose): 00471 """ Returns true if pose is reachable given workspace constraints of cart""" 00472 if pose[0][0] < self._cart_x_min or pose[0][0] > self._cart_x_max or \ 00473 pose[0][1] < self._cart_y_min or pose[0][1] > self._cart_y_max or \ 00474 pose[1][2] < self._cart_t_min or pose[1][2] > self._cart_t_max: 00475 return False 00476 00477 return True 00478 00479 00480 ############################################################ 00481 # Non-member utility functions 00482 ############################################################ 00483 00484 def forward_simulate_twist (pose, twist, t): 00485 """Forward simulate twist from pose for t seconds and return the resulting pose""" 00486 return [[pose[0][0] + twist.linear.x*t, 00487 pose[0][1] + twist.linear.y*t, 00488 pose[0][2] + twist.linear.z*t], 00489 [pose[1][0] + twist.angular.x*t, 00490 pose[1][1] + twist.angular.y*t, 00491 pose[1][2] + twist.angular.z*t]] 00492 00493 def _matrix_to_pose(mtx): 00494 trans = transformations.decompose_matrix(mtx)[3] 00495 00496 ps = Pose() 00497 ps.position.x = trans[0] 00498 ps.position.y = trans[1] 00499 ps.position.z = trans[2] 00500 00501 qt = transformations.quaternion_from_matrix(mtx) 00502 ps.orientation.x = qt[0] 00503 ps.orientation.y = qt[1] 00504 ps.orientation.z = qt[2] 00505 ps.orientation.w = qt[3] 00506 00507 return ps 00508 00509 ############################################################ 00510 # Node 00511 ############################################################ 00512 00513 if __name__ == '__main__': 00514 from optparse import OptionParser 00515 parser = OptionParser(usage="") 00516 parser.add_option("-o", "--object", dest="object", default="cart", 00517 help="attempt to load an initial object transform with name OBJECT from tf [default=\"%default\"]") 00518 (options, args) = parser.parse_args() 00519 00520 rospy.init_node(PKG+"_server") 00521 00522 rospy.sleep(0.5) # init 00523 server = articulateCartServer(options.object) 00524 00525 rospy.spin() 00526 00527 00528