00001 ''' Interface for controlling arm '''
00002
00003
00004
00005
00006
00007
00008 import rospy
00009
00010
00011 import threading
00012 from numpy import array, sign, pi, dot
00013 from numpy.linalg import norm
00014
00015
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
00033 from fetch_arm_control.msg import GripperState
00034
00035
00036
00037
00038
00039
00040 DURATION_MIN_THRESHOLD = 0.5
00041
00042
00043
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
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
00104
00105
00106
00107
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
00114
00115
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
00127
00128
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
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
00187
00188
00189
00190
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
00350
00351
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
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
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
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
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
00580
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