pr2_omni_teleop.py
Go to the documentation of this file.
00001 #!/usr/bin/env python  
00002 import roslib
00003 roslib.load_manifest('pr2_omni_teleop')
00004 
00005 import rospy
00006 import tf
00007 import math
00008 import actionlib
00009 import tf.transformations as tr
00010 import numpy as np
00011 import hrl_lib.tf_utils as tfu
00012 import coefficients as coeff
00013 #import threading
00014 from geometry_msgs.msg import PoseStamped
00015 from geometry_msgs.msg import Wrench
00016 from phantom_omni.msg import OmniFeedback
00017 from geometry_msgs.msg import Twist
00018 from phantom_omni.msg import PhantomButtonEvent
00019 from teleop_controllers.msg import JTTeleopControllerState
00020 from actionlib_msgs.msg import *
00021 from pr2_controllers_msgs.msg import *
00022 from pr2_msgs.msg import AccelerometerState
00023 
00024 
00025 class ForceFeedbackFilter:
00026 
00027     def __init__(self, wrench_topic, dest_frame, wrench_frame, force_feedback_topic, tflistener, kp_name, kd_name):
00028         self.wrench_frame = wrench_frame
00029         self.dest_frame = dest_frame
00030         self.tflistener = tflistener
00031         self.omni_fb = rospy.Publisher(force_feedback_topic, OmniFeedback)
00032         rospy.Subscriber(wrench_topic, JTTeleopControllerState, self.wrench_callback)
00033         self.enable = False
00034         self.IIR_num = coeff.num
00035         self.IIR_den = coeff.den
00036         self.input_his = np.matrix(np.zeros((3,coeff.num.size)))
00037         self.output_his = np.matrix(np.zeros((3,coeff.den.size)))
00038         # self.prev_time = rospy.Time.now().nsecs*1e-9
00039         # self.prev_dt = 0.0
00040         self.omni_max_limit = np.array([7., 7., 7.])
00041         self.omni_min_limit = np.array([-7., -7., -7.])
00042         self.kp = rospy.get_param(kp_name)
00043         self.kd = rospy.get_param(kd_name)
00044         self.force_scaling = -0.04
00045         self.force_old = np.zeros(3)
00046 
00047     def set_force_scaling(self, scalar):
00048         self.force_scaling = -1.*scalar
00049 
00050     def set_enable(self, v):
00051         self.enable = v
00052 
00053     def wrench_callback(self, state):
00054         #calculating force estimate from position error (does not compensate for motion error due to dynamics)
00055         x_err = np.matrix([state.x_err.linear.x, state.x_err.linear.y, state.x_err.linear.z]).T
00056         x_dot = np.matrix([state.xd.linear.x, state.xd.linear.y, state.xd.linear.z]).T
00057         feedback = -1.0*self.kp*x_err-self.kd*x_dot
00058 
00059         #currently the calculated force feedback is published but not to omni, we use the velocity limited state from the controller
00060         wr_ee = [feedback[0,0], feedback[1,0], feedback[2,0]]
00061        
00062         #5th order IIR Butterworth filter designed in matlab to smooth force estimate
00063         self.input_his = np.matrix(np.hstack((np.array(wr_ee).reshape((3,1)), np.array(self.input_his[:,0:-1]))))
00064         wr_ee_filt = self.input_his*self.IIR_num - self.output_his[:,:-1]*self.IIR_den[1:]
00065         self.output_his = np.matrix(np.hstack((np.array(wr_ee_filt).reshape((3,1)), np.array(self.output_his[:,0:-1]))))
00066 
00067         #find and use the rotation matrix from wrench to torso                                                                   
00068         df_R_ee = tfu.rotate(self.dest_frame, 'torso_lift_link', self.tflistener) * tfu.rotate('torso_lift_link', self.wrench_frame, self.tflistener)
00069 #        wr_df = self.force_scaling*np.array(tr.translation_from_matrix(df_R_ee * tfu.translation_matrix([wr_ee_filt[0,0], wr_ee_filt[1,0], wr_ee_filt[2,0]])))
00070         wr_df = self.force_scaling*np.array(tr.translation_from_matrix(df_R_ee * tfu.translation_matrix([feedback[0,0], feedback[1,0], feedback[2,0]])))
00071 
00072         #limiting the max and min force feedback sent to omni                                                                    
00073         wr_df = np.where(wr_df>self.omni_max_limit, self.omni_max_limit, wr_df)
00074         wr_df = np.where(wr_df<self.omni_min_limit, self.omni_min_limit, wr_df)
00075 
00076         wr = OmniFeedback()
00077         wr.force.x = wr_df[0]
00078         wr.force.y = wr_df[1]
00079         wr.force.z = wr_df[2]
00080         if self.enable == True:
00081             self.omni_fb.publish(wr)
00082 
00083 # #this could be used for trying to damp the force feedback, didn't work very well
00084 # #         self.force_old[0] = wr.force.x
00085 # #         self.force_old[1] = wr.force.y
00086 # #         self.force_old[2] = wr.force.z
00087 # #         dt = rospy.Time.now().nsecs*1e-9-self.prev_time
00088 # #         self.prev_time = rospy.Time.now().nsecs*1e-9
00089 # #         print "time step: ", dt
00090 
00091 
00092 def limit_range(numb, lower, upper):
00093     if lower > upper:
00094         raise RuntimeError('lower bound > upper bound, wa?')
00095     return max(min(numb, upper), lower)
00096 
00097 class GripperOmniHandler:
00098     def __init__(self, topic, controller):
00099         rospy.Subscriber(topic, PhantomButtonEvent, self.button_event)
00100         self.gripper_server = actionlib.SimpleActionClient(controller + '/gripper_action', Pr2GripperCommandAction)
00101         self.gripper_server.wait_for_server()
00102         self.gripper_pose = None
00103         self.gripper_open = False
00104         self.gripper_close = False
00105         self.MAX_EFFORT = 40
00106         self.enabled = True
00107 
00108     def set_enabled(self, v):
00109         self.enabled = v
00110 
00111     def button_event(self, msg):
00112         if not (msg.grey_button == 1 and msg.white_button == 1):
00113             #grey button opens
00114             if msg.grey_button == 1:
00115                 if self.enabled:
00116                     self.gripper_server.send_goal(Pr2GripperCommandGoal(
00117                                          Pr2GripperCommand(position = 0.0, max_effort = self.MAX_EFFORT)),
00118                                          done_cb = self.gripper_done_cb,
00119                                          feedback_cb=self.gripper_fb_cb)
00120                     self.gripper_close = True
00121             elif msg.grey_button == 0 and self.gripper_pose != None and self.gripper_close:
00122                 self.gripper_server.send_goal(Pr2GripperCommandGoal(
00123                                      Pr2GripperCommand(position = self.gripper_pose-.01, max_effort = self.MAX_EFFORT))
00124                                      )
00125                 self.gripper_close = False
00126                 self.gripper_pose = None
00127                 
00128             #white button closes
00129             if msg.white_button == 1:
00130                 if self.enabled:
00131                     self.gripper_server.send_goal(Pr2GripperCommandGoal(
00132                                          Pr2GripperCommand(position = 0.09, max_effort = self.MAX_EFFORT)),
00133                                          done_cb = self.gripper_done_cb,
00134                                          feedback_cb=self.gripper_fb_cb)
00135                     self.l_gripper_open = True
00136             elif msg.white_button == 0 and self.gripper_pose != None and self.gripper_open:
00137                 self.gripper_server.send_goal(Pr2GripperCommandGoal(
00138                                      Pr2GripperCommand(position = self.gripper_pose+.01, max_effort = self.MAX_EFFORT)))
00139                 self.l_gripper_open = False
00140                 self.gripper_pose = None
00141 
00142     def gripper_done_cb(self, state, msg):
00143         self.gripper_pose = msg.position
00144 
00145     def gripper_fb_cb(self, msg):
00146         self.gripper_pose = msg.position
00147         self.effort = msg.effort
00148 
00149 class AccelerometerFeedback:
00150     #self.accel_fb = AccelerometerFeedback(self.OMNI_LEFT + '_sensable', '/accelerometer/l_gripper_motor', 'force_feedback', self.tflistener)
00151     def __init__(self, dest_frame, accel_topic, force_topic, tflistener):
00152         rospy.Subscriber(accel_topic, AccelerometerState, self.cb)
00153         self.viz_pub = rospy.Publisher('omni1_force', PoseStamped)
00154         self.tflistener = tflistener
00155         self.dest_frame = dest_frame
00156         self.force_pub = rospy.Publisher(force_topic, OmniFeedback)
00157         self.gain = .5
00158 
00159     def cb(self, msg):
00160         raw_read = [msg.samples[0].x, msg.samples[0].y, msg.samples[0].z]
00161         g = tr.translation_from_matrix(
00162                 tfu.rotate(msg.header.frame_id, 'base_footprint', self.tflistener) * \
00163                 tr.translation_matrix([0, 0, 9.665])) #empirical gravity
00164         grav_adjusted = np.array(raw_read) - np.array(g)
00165         #print grav_adjusted, g
00166 
00167         d_R_m = tfu.rotate(self.dest_frame, 'torso_lift_link', self.tflistener) * \
00168                 tfu.rotate('torso_lift_link', msg.header.frame_id, self.tflistener)
00169 
00170         t_sensable = self.gain * np.array(tr.translation_from_matrix(d_R_m * np.matrix(tr.translation_matrix(grav_adjusted))))
00171         w = OmniFeedback()
00172         w.force.x = t_sensable[0]
00173         w.force.y = t_sensable[1]
00174         w.force.z = t_sensable[2]
00175         self.force_pub.publish(w)
00176 
00177         ps = PoseStamped()
00178         ps.header.frame_id = self.dest_frame
00179         ps.header.stamp = rospy.get_rostime()
00180         ps.pose.position.x = t_sensable[0]
00181         ps.pose.position.y = t_sensable[1]
00182         ps.pose.position.z = t_sensable[2]
00183         ps.pose.orientation.w = 1
00184         self.viz_pub.publish(ps)
00185 
00186 class ControlPR2Arm:
00187 
00188     def __init__(self, omni_name, #='omni1', 
00189             pr2_control_topic, # = 'l_cart/command_pose',
00190             gripper_control_topic,  #'l_gripper_controller'
00191             gripper_tip_frame, #'l_gripper_tool_frame'
00192             center_in_torso_frame, #=[1,.3,-1], 
00193             scaling_in_base_frame, #=[3.5, 3., 5.],
00194             tfbroadcast, #=None,
00195             tflistener): #=None):
00196 
00197         #threading.Thread.__init__(self)
00198         self.enabled = False
00199         self.zero_out_forces = True
00200 
00201         self.X_BOUNDS = [.3, 1.5] #Bound translation of PR2 arms in the X direction (in torso link frame)
00202         self.kPos = 15.
00203         self.kVel = 0.5
00204         self.kPos_close = 70.
00205         self.omni_name = omni_name
00206         self.center_in_torso_frame = center_in_torso_frame
00207         self.scaling_in_base_frame = scaling_in_base_frame
00208         self.tflistener = tflistener
00209         self.tfbroadcast = tfbroadcast
00210         self.gripper_tip_frame = gripper_tip_frame
00211         self.prev_dt = 0.0
00212         self.tip_tt = np.zeros((3,1))
00213         self.tip_tq = np.zeros((4,1))
00214         rate = rospy.Rate(100.0)
00215 
00216         rospy.loginfo('Attempting to link ' + omni_name + ' to the PR2\'s torso frame.')
00217         success = False
00218         while not success and (not rospy.is_shutdown()):
00219             self.send_transform_to_link_omni_and_pr2_frame()
00220             rate.sleep()
00221             try:
00222                 self.scale_omni_l0 = np.abs(self.l0_rotate_base(self.scaling_in_base_frame))
00223                 success = True
00224             except tf.LookupException, e:
00225                 pass
00226             except tf.ConnectivityException, e:
00227                 pass
00228         rospy.loginfo('Finished linking frame for %s' % omni_name)
00229 
00230         self.omni_fb = rospy.Publisher(self.omni_name + '_force_feedback', OmniFeedback)
00231         self.pr2_pub = rospy.Publisher(pr2_control_topic, PoseStamped)
00232         self.scale_omni_l0 = np.abs(self.l0_rotate_base(self.scaling_in_base_frame))
00233 
00234         rospy.Subscriber(self.omni_name + '_pose', PoseStamped, self.omni_pose_cb)
00235         self.gripper_handler = GripperOmniHandler(self.omni_name + '_button', gripper_control_topic)
00236 
00237     ##
00238     # Link this omni to pr2 frame
00239     def send_transform_to_link_omni_and_pr2_frame(self):
00240         self.tfbroadcast.sendTransform(self.center_in_torso_frame,
00241                          tuple(tf.transformations.quaternion_from_euler(0, 0, 0)),
00242                          rospy.Time.now(),
00243                          self.omni_name,
00244                          "/torso_lift_link")
00245     ##
00246     # Rotate a vector from omni_link0 to base_footprint frame
00247     def l0_rotate_base(self, vec_base):
00248         m = tfu.translation_matrix(vec_base)
00249         vec_l0 = tr.translation_from_matrix((tfu.rotate('/base_footprint', '/torso_lift_link', self.tflistener) * \
00250                                              tfu.rotate('/torso_lift_link', self.omni_name + '_link0', self.tflistener)).T * m)
00251         return np.array(vec_l0)
00252 
00253     ##
00254     # Transform from omni base link to torso lift link taking into account scaling
00255     def torso_T_omni(self, omni_tip, msg_frame):
00256         #Transform into link0 so we can scale
00257         #print 'torso_t_omni', self.omni_name + '_link0', msg_frame
00258         z_T_6 = tfu.transform(self.omni_name + '_link0', msg_frame, self.tflistener)
00259         tip_0 = z_T_6 * omni_tip
00260         tip_0t = (np.array(tr.translation_from_matrix(tip_0)) * np.array(self.scale_omni_l0)).tolist()
00261         tip_0q = tr.quaternion_from_matrix(tip_0)
00262 
00263         #Transform into torso frame so we can bound arm workspace
00264         tll_T_0 = tfu.transform('/torso_lift_link', self.omni_name + '_link0', self.tflistener)
00265         tip_torso_mat = tll_T_0 * tfu.tf_as_matrix([tip_0t, tip_0q])
00266         tip_tt, tip_tq = tfu.matrix_as_tf(tip_torso_mat)
00267         tip_tt[0] = limit_range(tip_tt[0], self.X_BOUNDS[0], self.X_BOUNDS[1])
00268         self.tip_tt = tip_tt
00269         self.tip_tq = tip_tq
00270 
00271     ##
00272     # Transfrom from torso lift link to link omni base link, taking into account scaling
00273     def omni_T_torso(self, torso_mat):
00274         l0_mat = tfu.transform(self.omni_name + '_link0', '/torso_lift_link', self.tflistener) * torso_mat
00275         l0_t = (np.array(tr.translation_from_matrix(l0_mat)) / np.array(self.scale_omni_l0)).tolist()
00276         l0_q = tr.quaternion_from_matrix(l0_mat)
00277         omni_pt_mat = tfu.transform(self.omni_name, self.omni_name + '_link0', self.tflistener) * tfu.tf_as_matrix((l0_t, l0_q))
00278         return tfu.matrix_as_tf(omni_pt_mat)
00279 
00280 
00281     ##
00282     # Set whether control should be enabled
00283     def set_control(self, s):
00284         self.enabled = s
00285         if self.enabled:
00286             self.gripper_handler.set_enabled(True)
00287             self.zero_out_forces = True
00288         else:
00289             self.gripper_handler.set_enabled(False)
00290 
00291     ##
00292     # Callback for pose of omni
00293     def omni_pose_cb(self, msg):
00294         if self.enabled:
00295             #Get the omni's tip pose in the PR2's torso frame
00296             tip_omni, msg_frame = tfu.posestamped_as_matrix(msg)
00297             self.torso_T_omni(tip_omni, msg_frame)
00298             tip_tt = self.tip_tt
00299             tip_tq = self.tip_tq
00300             #Publish new arm pose
00301             ps = PoseStamped()
00302             ps.header.frame_id = '/torso_lift_link'
00303             ps.header.stamp = rospy.get_rostime()
00304             ps.pose.position.x = tip_tt[0]
00305             ps.pose.position.y = tip_tt[1]
00306             ps.pose.position.z = tip_tt[2]
00307             ps.pose.orientation.x = tip_tq[0]
00308             ps.pose.orientation.y = tip_tq[1]
00309             ps.pose.orientation.z = tip_tq[2]
00310             ps.pose.orientation.w = tip_tq[3]
00311             self.pr2_pub.publish(ps)
00312             if self.zero_out_forces:
00313                 wr = OmniFeedback()
00314                 wr.force.x = 0 
00315                 wr.force.y = 0 
00316                 wr.force.z = 0 
00317                 self.omni_fb.publish(wr)
00318                 self.zero_out_forces = False
00319         else:
00320             #this is a zero order hold publishing the last received values until the control loop is active again
00321             tip_tt = self.tip_tt
00322             tip_tq = self.tip_tq
00323             ps = PoseStamped()
00324             ps.header.frame_id = '/torso_lift_link'
00325             ps.header.stamp = rospy.get_rostime()
00326             ps.pose.position.x = tip_tt[0]
00327             ps.pose.position.y = tip_tt[1]
00328             ps.pose.position.z = tip_tt[2]
00329             ps.pose.orientation.x = tip_tq[0]
00330             ps.pose.orientation.y = tip_tq[1]
00331             ps.pose.orientation.z = tip_tq[2]
00332             ps.pose.orientation.w = tip_tq[3]
00333             self.pr2_pub.publish(ps)
00334 
00335             #this is to make the omni force well move if the arm has moved but the commanded
00336             #position of the arm has not changed
00337             tip_omni, msg_frame = tfu.posestamped_as_matrix(msg)
00338             m_o1 = tfu.transform(self.omni_name, msg_frame, self.tflistener) * tip_omni
00339             ee_point = np.matrix(tr.translation_from_matrix(m_o1)).T
00340             tip_torso = tfu.transform('/torso_lift_link', self.gripper_tip_frame, self.tflistener) \
00341                                   * tfu.tf_as_matrix(([0.,0.,0.], tr.quaternion_from_euler(0,0,0)))
00342             center_t, center_q = self.omni_T_torso(tip_torso)
00343             center_col_vec = np.matrix(center_t).T
00344 
00345             #Send force control info
00346             wr = OmniFeedback()
00347             # offsets (0, -.268, -.15) introduced by Hai in phantom driver
00348             # should be cleaned up at some point so that it is consistent with position returned by phantom -marc
00349             lock_pos = tr.translation_matrix(np.matrix([0,-.268,-.150]))*tfu.transform(self.omni_name+'_sensable', self.omni_name, self.tflistener)*np.row_stack((center_col_vec, np.matrix([1.])))
00350             wr.position.x = (lock_pos[0,0])*1000.0  #multiply by 1000 mm/m to get units phantom expects
00351             wr.position.y = (lock_pos[1,0])*1000.0 
00352             wr.position.z = (lock_pos[2,0])*1000.0 
00353             self.omni_fb.publish(wr)
00354 
00355 
00356 class OmniPR2Teleop:
00357     def __init__(self, arm, ff):
00358         rospy.init_node('omni_frames')
00359         self.enabled = False
00360         self.tfbroadcast = tf.TransformBroadcaster()
00361         self.tflistener = tf.TransformListener()
00362         self.controller_list = []
00363         self.ff_list = []
00364         if arm == "l" or arm == "b":
00365             self.left_controller = ControlPR2Arm(
00366                                         omni_name ='omni2', 
00367                                         pr2_control_topic = 'l_cart/command_pose',
00368                                         gripper_control_topic = 'l_gripper_controller',
00369                                         gripper_tip_frame = 'l_gripper_tool_frame',
00370                                         center_in_torso_frame = [1.2, .3, -1], 
00371                                         scaling_in_base_frame = [3.5, 3., 5.],
00372                                         tfbroadcast=self.tfbroadcast,
00373                                         tflistener=self.tflistener)
00374             self.controller_list.append(self.left_controller)
00375             rospy.Subscriber('omni2_button', PhantomButtonEvent, self.omni_safety_lock_cb)
00376             if ff == True:
00377                 self.left_feedback = ForceFeedbackFilter(wrench_topic = '/l_cart/state',
00378                                              dest_frame = '/omni2_sensable',
00379                                              wrench_frame = '/l_gripper_tool_frame', 
00380                                              force_feedback_topic = 'omni2_force_feedback',
00381                                              tflistener = self.tflistener,
00382                                              kp_name = '/l_cart/cart_gains/trans/p',
00383                                              kd_name = '/l_cart/cart_gains/trans/d')
00384                 self.ff_list.append(self.left_feedback)
00385         if arm == "r" or arm == "b":
00386             self.right_controller = ControlPR2Arm(
00387                                        omni_name ='omni1', 
00388                                        pr2_control_topic = 'r_cart/command_pose',
00389                                        gripper_control_topic = 'r_gripper_controller',
00390                                        gripper_tip_frame = 'r_gripper_tool_frame',
00391                                        center_in_torso_frame = [1.2, -.3, -1], 
00392                                        scaling_in_base_frame = [3.5, 3., 5.],
00393                                        tfbroadcast=self.tfbroadcast,
00394                                        tflistener=self.tflistener)
00395             rospy.Subscriber('omni1_button', PhantomButtonEvent, self.omni_safety_lock_cb)
00396             self.controller_list.append(self.right_controller)
00397             if ff == True:
00398                 self.right_feedback = ForceFeedbackFilter(wrench_topic = '/r_cart/state',
00399                       dest_frame = '/omni1_sensable',
00400                       wrench_frame = '/r_gripper_tool_frame', 
00401                       force_feedback_topic = 'omni1_force_feedback',
00402                       tflistener = self.tflistener,
00403                       kp_name = '/r_cart/cart_gains/trans/p',
00404                       kd_name = '/r_cart/cart_gains/trans/d')
00405                 self.ff_list.append(self.right_feedback)
00406         
00407         self.set_state(False)
00408 
00409     def omni_safety_lock_cb(self, msg):
00410         if msg.grey_button == 1 and msg.white_button == 1:
00411             self.set_state(not self.enabled)
00412 
00413     def set_state(self, s):
00414         self.enabled = s
00415         if self.enabled:
00416             rospy.loginfo('control ENABLED.')
00417             for cont in self.controller_list:
00418                 cont.set_control(True)
00419             for f in self.ff_list:
00420                 f.set_enable(True)
00421         else:
00422             rospy.loginfo('control disabled.  Follow potential well to pose of arm.')
00423             for cont in self.controller_list:
00424                 cont.set_control(False)
00425             for f in self.ff_list:
00426                 f.set_enable(False)
00427 
00428     def run(self):
00429         rate = rospy.Rate(100.0)
00430         rospy.loginfo('running...')
00431         while not rospy.is_shutdown():
00432             for cont in self.controller_list:
00433                 cont.send_transform_to_link_omni_and_pr2_frame()
00434 
00435 
00436 if __name__ == '__main__':
00437     import optparse
00438     p = optparse.OptionParser()
00439     
00440     p.add_option('--arms', action='store', type='string', dest='arm', default='r', 
00441                  help='this allows initialization of right, left or both arms with an input of r, l or b respectively')
00442     p.add_option('--ff', action='store', type='int', dest='ff', default='0',
00443                  help='enter 1 to activate force feedback, 0 otherwise')
00444 
00445     opt, args = p.parse_args()
00446 
00447     o = OmniPR2Teleop(opt.arm, opt.ff)
00448     o.run()
00449 
00450 
00451 


pr2_omni_teleop
Author(s): Hai Nguyen, Marc Killpack Advisor: Prof. Charlie Kemp, Lab: Healthcare Robotics Lab at Georgia Tech
autogenerated on Wed Nov 27 2013 11:51:39