arm.py
Go to the documentation of this file.
00001 ''' Interface for controlling arm '''
00002 
00003 # ######################################################################
00004 # Imports
00005 # ######################################################################
00006 
00007 # Core ROS imports come first.
00008 import rospy
00009 
00010 # System builtins
00011 import threading
00012 from numpy import array, sign, pi, dot
00013 from numpy.linalg import norm
00014 
00015 # ROS builtins
00016 import moveit_commander
00017 import actionlib
00018 import tf
00019 from control_msgs.msg import FollowJointTrajectoryGoal, \
00020                              FollowJointTrajectoryAction, \
00021                              FollowJointTrajectoryResult
00022 from trajectory_msgs.msg import JointTrajectoryPoint, JointTrajectory
00023 
00024 from actionlib import SimpleActionClient
00025 from control_msgs.msg import GripperCommandAction, GripperCommandGoal
00026 from sensor_msgs.msg import JointState
00027 from geometry_msgs.msg import Quaternion, Point, Pose, PoseStamped
00028 from robot_controllers_msgs.msg import QueryControllerStatesAction, \
00029                                        QueryControllerStatesGoal, \
00030                                        ControllerState
00031 
00032 # Local
00033 from fetch_arm_control.msg import GripperState
00034 
00035 # ######################################################################
00036 # Module level constants
00037 # ######################################################################
00038 
00039 # The minimum time to allow for moving between poses.
00040 DURATION_MIN_THRESHOLD = 0.5  # seconds
00041 
00042 # ######################################################################
00043 # Classes
00044 # ######################################################################
00045 
00046 class Arm:
00047     ''' Interface for controlling mid-level operations of the arm.
00048     Interfaces with MoveIt and joint trajectory controllers to move the
00049     arm to locations using different planning strategies.
00050     '''
00051     def __init__(self, tf_listener):
00052         '''
00053         Args:
00054             tf_listener (TransformListener)
00055         '''
00056         self._tf_listener = tf_listener
00057 
00058         self._gripper_state = None
00059 
00060         self._l_gripper_joint_name = 'l_gripper_finger_joint'
00061         self._r_gripper_joint_name = 'r_gripper_finger_joint'
00062 
00063         self._ee_name = 'wrist_roll_link'
00064         self._joint_names = ['shoulder_pan_joint',
00065                             'shoulder_lift_joint',
00066                             'upperarm_roll_joint',
00067                             'elbow_flex_joint',
00068                             'forearm_roll_joint',
00069                             'wrist_flex_joint',
00070                             'wrist_roll_joint']
00071 
00072         # names/poses for joints we have received information about
00073         self._received_joint_names = []
00074         self._received_joint_poses = []
00075 
00076         self._last_ee_pose = None
00077         self._movement_buffer_size = 40
00078         self._last_unstable_time = rospy.Time.now()
00079         self._arm_movement = []
00080         self._is_executing = False
00081 
00082         self._lock = threading.Lock()
00083         rospy.Subscriber('/joint_states', JointState, self._joint_states_cb)
00084 
00085         controller_states = "/query_controller_states"
00086 
00087         self._controller_client = actionlib.SimpleActionClient(
00088                                         controller_states,
00089                                         QueryControllerStatesAction)
00090         self._controller_client.wait_for_server()
00091 
00092         self._gravity_comp_controllers = ["arm_controller/gravity_compensation"]
00093 
00094         self._non_gravity_comp_controllers = list()
00095         self._non_gravity_comp_controllers.append(
00096                     "arm_controller/follow_joint_trajectory")
00097         self._non_gravity_comp_controllers.append(
00098                     "arm_with_torso_controller/follow_joint_trajectory")
00099 
00100         traj_controller_name = ("/arm_controller/follow_joint_trajectory")
00101         self._traj_action_client = SimpleActionClient(
00102                         traj_controller_name, FollowJointTrajectoryAction)
00103         # self._traj_action_client.wait_for_server()
00104 
00105         # Initialise all the Moveit stuff
00106 
00107         # Wait for move_group to be ready
00108         rospy.wait_for_service('/compute_ik')
00109         self._move_group = moveit_commander.MoveGroupCommander("arm")
00110         self._move_group.set_planning_time(5.0)
00111         self._move_group.set_planner_id('RRTConnectkConfigDefault')
00112 
00113         # Define ground plane
00114         # This creates objects in the planning scene that mimic the ground
00115         # If these were not in place gripper could hit the ground
00116         self._planning_scene = moveit_commander.PlanningSceneInterface()
00117         self._planning_scene.remove_world_object("ground_plane")
00118 
00119         ground_pose = PoseStamped()
00120         ground_pose.header.frame_id = "base_link"
00121         ground_pose.pose.position.x = 0.0
00122         ground_pose.pose.position.y = 0.0
00123         ground_pose.pose.position.z = -0.012
00124         ground_pose.pose.orientation.w = 1.0
00125 
00126         # For some reason that defies logic, it's best to add and remove
00127         # planning scene objects in a loop.
00128         # It sometimes doesn't work the first time.
00129         for i in range(5):
00130             self._planning_scene.add_box("ground_plane", ground_pose,
00131                                         (1.5, 1.5, 0.02))
00132             rospy.sleep(0.2)
00133 
00134         gripper_controller_name = "gripper_controller/gripper_action"
00135         self._gripper_client = SimpleActionClient(gripper_controller_name,
00136                                                     GripperCommandAction)
00137         self._gripper_client.wait_for_server()
00138         self._update_gripper_state()
00139 
00140     # ##################################################################
00141     # Instance methods: Public (API)
00142     # ##################################################################
00143 
00144     def update(self):
00145         ''' Periodical update for one arm'''
00146         ee_pose = self.get_ee_state()
00147         if ee_pose != None and self._last_ee_pose != None:
00148             self._record_arm_movement(Arm._get_distance_bw_poses(ee_pose,
00149                                                         self._last_ee_pose))
00150         self._last_ee_pose = ee_pose
00151 
00152     def relax_arm(self):
00153         '''Turns on gravity compensation controller and turns
00154         off other controllers
00155         '''
00156 
00157         goal = QueryControllerStatesGoal()
00158 
00159         for controller in self._gravity_comp_controllers:
00160             state = ControllerState()
00161             state.name = controller
00162             state.state = state.RUNNING
00163             goal.updates.append(state)
00164 
00165         for controller in self._non_gravity_comp_controllers:
00166             state = ControllerState()
00167             state.name = controller
00168             state.state = state.STOPPED
00169             goal.updates.append(state)
00170 
00171         self._controller_client.send_goal(goal)
00172 
00173     def un_relax_arm(self):
00174         '''Turns on gravity compensation controller and turns
00175         off other controllers
00176         '''
00177 
00178         goal = QueryControllerStatesGoal()
00179 
00180         for controller in self._non_gravity_comp_controllers:
00181             state = ControllerState()
00182             state.name = controller
00183             state.state = state.RUNNING
00184             goal.updates.append(state)
00185 
00186         # for controller in self._gravity_comp_controllers:
00187         #     state = ControllerState()
00188         #     state.name = controller
00189         #     state.state = state.STOPPED
00190         #     goal.updates.append(state)
00191 
00192         self._controller_client.send_goal(goal)
00193 
00194     def get_ee_state(self, ref_frame='base_link'):
00195         ''' Returns end effector pose for the arm
00196 
00197         Args:
00198             ref_frame (st, optional): Reference frame that you
00199                                       want the pose return in
00200 
00201         Returns:
00202             PoseStamped | None
00203         '''
00204         try:
00205             time = self._tf_listener.getLatestCommonTime(ref_frame,
00206                                                          self._ee_name)
00207             (position, orientation) = self._tf_listener.lookupTransform(
00208                                                 ref_frame, self._ee_name, time)
00209             tf_pose = PoseStamped()
00210             tf_pose.header.frame_id = ref_frame
00211             tf_pose.pose.position = Point(position[0], position[1],
00212                                           position[2])
00213             tf_pose.pose.orientation = Quaternion(orientation[0],
00214                                             orientation[1],
00215                                             orientation[2], orientation[3])
00216             return tf_pose
00217         except (tf.LookupException, tf.ConnectivityException,
00218                 tf.ExtrapolationException) as e:
00219             rospy.logwarn('Something wrong with transform request: ' + str(e))
00220             return None
00221 
00222     def get_joint_state(self, joint_names=None):
00223         '''Returns position(s) for the requested or all arm joints
00224 
00225         Args:
00226             joint_names ([str], optional):
00227 
00228         Returns:
00229             [float]
00230         '''
00231         if joint_names is None:
00232             joint_names = self._joint_names
00233 
00234         if self._received_joint_names == []:
00235             rospy.logerr("No robot_state messages received!\n")
00236             return []
00237 
00238         positions = []
00239         self._lock.acquire()
00240         for joint_name in joint_names:
00241             if joint_name in self._received_joint_names:
00242                 index = self._received_joint_names.index(joint_name)
00243                 position = self._received_joint_poses[index]
00244                 positions.append(position)
00245             else:
00246                 rospy.logerr("Joint %s not found!", joint_name)
00247         self._lock.release()
00248         rospy.loginfo("Current joints: {}".format(positions))
00249         return positions
00250 
00251     def get_ik_for_ee(self, ee_pose, seed=None):
00252         ''' Finds the IK solution for given end effector pose
00253 
00254         Args:
00255             ee_pose (PoseStamped)
00256 
00257         Returns:
00258             [float]
00259         '''
00260         rospy.loginfo("Solve ik: {}".format(ee_pose))
00261         joints = self._solve_ik(ee_pose)
00262         rospy.loginfo("Joints: {}".format(joints))
00263 
00264         if joints and seed:
00265             rollover = array((array(joints) - array(seed)) / pi, int)
00266             joints -= ((rollover + (sign(rollover) + 1) / 2) / 2) * 2 * pi
00267 
00268         return joints
00269 
00270     def move_to_pose(self, ee_pose):
00271         ''' Move arm to specified pose
00272 
00273         Args:
00274             ee_pose (Pose)
00275 
00276         Returns:
00277             bool
00278         '''
00279         ee_pose_stamped = None
00280         rospy.loginfo("Executing pose: {}".format(ee_pose))
00281 
00282         if type(ee_pose) is Pose:
00283             ee_pose_stamped = PoseStamped()
00284             ee_pose_stamped.pose = ee_pose
00285             ee_pose_stamped.header.frame_id = "base_link"
00286 
00287         elif type(ee_pose) is PoseStamped:
00288             ee_pose_stamped = ee_pose
00289         else:
00290             rospy.loginfo("Not a pose or pose stamped")
00291             return False
00292 
00293         self._move_group.set_pose_target(ee_pose_stamped)
00294 
00295         self._move_group.set_planning_time(1.0)
00296 
00297         plan = self._move_group.plan()
00298 
00299         if not plan.joint_trajectory.points:
00300             return False
00301         else:
00302 
00303             self._is_executing = True
00304             go = self._move_group.go(wait=True)
00305             self._is_executing = False
00306             rospy.loginfo("Go: {}".format(go))
00307             return True
00308 
00309     def get_gripper_state(self):
00310         '''Returns current gripper state
00311 
00312         Returns:
00313             GripperState.OPEN/CLOSED
00314         '''
00315         return self._gripper_state
00316 
00317     def open_gripper(self, pos=0.15, eff=100.0, wait=True):
00318         '''Opens gripper
00319 
00320         Args:
00321             pos (float)
00322             eff (float)
00323             wait (bool)
00324         '''
00325         self._send_gripper_command(pos, eff, wait)
00326         self._gripper_state = GripperState.OPEN
00327 
00328     def close_gripper(self, pos=0.0, eff=100.0, wait=True):
00329         '''Closes gripper
00330 
00331         Args:
00332             pos (float)
00333             eff (float)
00334             wait (bool)
00335         '''
00336         self._send_gripper_command(pos, eff, wait)
00337         self._gripper_state = GripperState.CLOSED
00338 
00339     def move_to_joints_plan(self, joints, velocities=None):
00340         '''Moves the arm to the desired joint angles using moveit
00341 
00342         Args:
00343             joints ([float])
00344         '''
00345 
00346         joint_state = JointState()
00347         joint_state.position = joints
00348         joint_state.name = self._joint_names
00349         # if velocities is None:
00350         #     velocities = [0] * len(joints)
00351         # joint_state.velocity = velocities
00352 
00353         try:
00354             self._move_group.set_joint_value_target(joint_state)
00355         except Exception as e:
00356             rospy.logerr("Moveit Error: {}".format(e))
00357             return False
00358 
00359         self._move_group.set_planning_time(1.0)
00360 
00361         plan = self._move_group.plan()
00362 
00363         if not plan.joint_trajectory.points:
00364             return False
00365         else:
00366 
00367             self._is_executing = True
00368             go = self._move_group.go(wait=True)
00369             self._is_executing = False
00370             rospy.loginfo("Go: {}".format(go))
00371             return True
00372 
00373     def move_to_joints(self, joints, times_to_joints, velocities=None):
00374         '''Moves the arm to the desired joint angles directly without planning
00375 
00376         Args:
00377             joints ([float])
00378             time_to_joint ([float])
00379             velocities ([float])
00380         '''
00381         self.un_relax_arm()
00382 
00383         trajectory = JointTrajectory()
00384         trajectory.header.stamp = (rospy.Time.now() +
00385                                              rospy.Duration(0.1))
00386         trajectory.joint_names = self._joint_names
00387 
00388         for i in range(len(joints)):
00389             trajectory.points.append(JointTrajectoryPoint(
00390                             positions=joints[i],
00391                             time_from_start=rospy.Duration(times_to_joints[i])))
00392 
00393         self._is_executing = True
00394 
00395         follow_goal = FollowJointTrajectoryGoal(trajectory=trajectory)
00396         self._traj_action_client.send_goal(follow_goal)
00397         self._traj_action_client.wait_for_result()
00398         self._is_executing = False
00399         self.relax_arm()
00400 
00401         result = self._traj_action_client.get_result()
00402         if result.error_code == FollowJointTrajectoryResult.SUCCESSFUL:
00403             rospy.loginfo("arm joints move successful")
00404             return True
00405         else:
00406             rospy.loginfo("arm joints move not successful")
00407             return False
00408 
00409     def get_time_to_pose(self, target_pose):
00410         '''Returns the time to get to the arm pose held in target_pose.
00411 
00412         Args:
00413             target_pose (PoseStamped|None): A Pose holding the pose to
00414                 move to, or None if the arm should not move.
00415 
00416         Returns:
00417             float|None: How long (in seconds) to allow for moving
00418                 arm to the pose in target_pose, or None if the arm
00419                 will not move.
00420         '''
00421         # Check whether arm will move at all.
00422         if target_pose is None:
00423             rospy.loginfo('\t' + 'arm will not move.')
00424             return None
00425         else:
00426             time_to_pose = Arm._get_time_bw_poses(
00427                 self.get_ee_state(),
00428                 target_pose
00429             )
00430             rospy.loginfo(
00431                 '\tDuration until next frame for arm' +
00432                 'arm : ' + str(time_to_pose))
00433             return time_to_pose
00434 
00435     def is_executing(self):
00436         '''Whether or not there is an ongoing action execution on the arm
00437 
00438         Returns:
00439             bool
00440         '''
00441         return self._is_executing
00442 
00443     def reset_movement_history(self):
00444         ''' Clears the saved history of arm movements'''
00445         self._last_unstable_time = rospy.Time.now()
00446         self._arm_movement = []
00447 
00448     def get_movement(self):
00449         '''Returns cumulative movement in recent history
00450 
00451         Returns:
00452             [float]
00453         '''
00454         return sum(self._arm_movement)
00455 
00456     # ##################################################################
00457     # Static methods: Internal ("private")
00458     # ##################################################################
00459 
00460     @staticmethod
00461     def _get_time_bw_poses(pose0, pose1, velocity=0.2):
00462         '''Determines how much time should be allowed for moving between
00463         pose0 and pose1 at velocity.
00464 
00465         Args:
00466             pose0 (PoseStamped)
00467             pose1 (PoseStamped)
00468             velocity (float, optional): Defaults to 0.2.
00469 
00470         Returns:
00471             float: How long (in seconds) to allow for moving between
00472                 pose0 and pose1 and velocity.
00473         '''
00474         dist = Arm._get_distance_bw_poses(pose0, pose1)
00475         duration = dist / velocity
00476         return (
00477             DURATION_MIN_THRESHOLD if duration < DURATION_MIN_THRESHOLD
00478             else duration)
00479 
00480     @staticmethod
00481     def _get_distance_bw_poses(pose0, pose1):
00482         '''Returns the dissimilarity between two end-effector poses
00483 
00484         Args:
00485             pose0 (PoseStamped)
00486             pose1 (PoseStamped)
00487 
00488         Returns:
00489             float
00490         '''
00491         w_pos = 1.0
00492         w_rot = 0.2
00493         pos0 = array((pose0.pose.position.x, pose0.pose.position.y,
00494                      pose0.pose.position.z))
00495         pos1 = array((pose1.pose.position.x, pose1.pose.position.y,
00496                      pose1.pose.position.z))
00497         rot0 = array((pose0.pose.orientation.x, pose0.pose.orientation.y,
00498                       pose0.pose.orientation.z, pose0.pose.orientation.w))
00499         rot1 = array((pose1.pose.orientation.x, pose1.pose.orientation.y,
00500                       pose1.pose.orientation.z, pose1.pose.orientation.w))
00501         pos_dist = w_pos * norm(pos0 - pos1)
00502         rot_dist = w_rot * (1 - dot(rot0, rot1))
00503 
00504         if pos_dist > rot_dist:
00505             dist = pos_dist
00506         else:
00507             dist = rot_dist
00508         return dist
00509 
00510     # ##################################################################
00511     # Instance methods: Internal ("private")
00512     # ##################################################################
00513 
00514     def _joint_states_cb(self, msg):
00515         '''Callback function that saves the joint positions when a
00516         joint_states message is received
00517 
00518         Args:
00519             msg (JointState): current joint positions for arm
00520         '''
00521         self._lock.acquire()
00522 
00523         for i, joint_name in enumerate(msg.name):
00524             if joint_name in self._received_joint_names:
00525                 idx = self._received_joint_names.index(joint_name)
00526                 self._received_joint_poses[idx] = msg.position[i]
00527             else:
00528                 self._received_joint_names.append(joint_name)
00529                 self._received_joint_poses.append(msg.position[i])
00530 
00531         self._lock.release()
00532 
00533     def _solve_ik(self, ee_pose):
00534         '''Gets the IK solution for end effector pose
00535 
00536         Args:
00537             ee_pose (PoseStamped)
00538 
00539         Returns:
00540             Pose
00541         '''
00542 
00543         self._move_group.set_pose_target(ee_pose)
00544 
00545         self._move_group.set_planning_time(1.0)
00546 
00547         plan = self._move_group.plan()
00548 
00549         if not plan.joint_trajectory.points:
00550             return None
00551         else:
00552             joint_names = plan.joint_trajectory.joint_names
00553             # rospy.loginfo("Plan: {}".format(plan))
00554             positions = plan.joint_trajectory.points[-1].positions
00555             return [positions[i] for i, x in enumerate(joint_names) \
00556                                             if x in self._joint_names]
00557 
00558     def _send_gripper_command(self, pos=0.115, eff=30.0, wait=True):
00559         '''Sets the position of the gripper
00560 
00561         Args:
00562             pose (float): Usually get this from echoing /joint_states
00563             eff (float)
00564             wait (bool)
00565         '''
00566         command = GripperCommandGoal()
00567         command.command.position = pos
00568         command.command.max_effort = eff
00569         self._gripper_client.send_goal(command)
00570         if wait:
00571             self._gripper_client.wait_for_result(rospy.Duration(5.0))
00572 
00573     def _update_gripper_state(self):
00574         '''Updates gripper state based on joint positions'''
00575 
00576         gripper_pos = self.get_joint_state([self._l_gripper_joint_name,
00577                                            self._r_gripper_joint_name])
00578 
00579         # Check if both fingers are further than 0.04m away from center.
00580         # This could be changed to a different threshold/check
00581         if gripper_pos != []:
00582             if gripper_pos[0] > 0.04 and gripper_pos[1] > 0.04:
00583                 self._gripper_state = GripperState.OPEN
00584             else:
00585                 self._gripper_state = GripperState.CLOSED
00586         else:
00587             rospy.logwarn('Could not update the gripper state.')
00588 
00589     def _record_arm_movement(self, movement):
00590         '''Records the sensed arm movement
00591 
00592         Args:
00593             movement (float): dissimilarity between current pose and
00594                                 previous pose
00595         '''
00596         self._arm_movement = [movement] + self._arm_movement
00597         if len(self._arm_movement) > self._movement_buffer_size:
00598             self._arm_movement = \
00599                 self._arm_movement[0:self._movement_buffer_size]
00600 
00601 


fetch_arm_control
Author(s): Sarah Elliott
autogenerated on Thu Jun 6 2019 18:27:17