arm_control_marker.py
Go to the documentation of this file.
00001 '''Represents a single 3D marker (in RViz) for controlling the Fetch arm.'''
00002 
00003 # ######################################################################
00004 # Imports
00005 # ######################################################################
00006 
00007 # Core ROS imports come first.
00008 import rospy
00009 
00010 # System builtins
00011 from numpy import array, linalg
00012 import threading
00013 
00014 # ROS builtins
00015 from geometry_msgs.msg import Quaternion, Point, Pose, PoseStamped
00016 from std_msgs.msg import Header, ColorRGBA
00017 import tf
00018 from interactive_markers.interactive_marker_server import (
00019     InteractiveMarkerServer)
00020 from interactive_markers.menu_handler import MenuHandler
00021 from visualization_msgs.msg import (
00022     Marker, InteractiveMarker, InteractiveMarkerControl,
00023     InteractiveMarkerFeedback)
00024 
00025 # Local
00026 from fetch_arm_control.msg import GripperState
00027 
00028 
00029 # ######################################################################
00030 # Constants
00031 # ######################################################################
00032 
00033 DEFAULT_OFFSET = 0.085
00034 COLOR_MESH_REACHABLE = ColorRGBA(1.0, 0.5, 0.0, 0.6)
00035 COLOR_MESH_UNREACHABLE = ColorRGBA(0.5, 0.5, 0.5, 0.6)
00036 STR_MESH_GRIPPER_FOLDER = 'package://fetch_description/meshes/'
00037 STR_GRIPPER_PALM_FILE = STR_MESH_GRIPPER_FOLDER + 'gripper_link.STL'
00038 STR_L_GRIPPER_FINGER_FILE = STR_MESH_GRIPPER_FOLDER + \
00039                                 'l_gripper_finger_link.STL'
00040 STR_R_GRIPPER_FINGER_FILE = STR_MESH_GRIPPER_FOLDER + \
00041                                 'r_gripper_finger_link.STL'
00042 INT_MARKER_SCALE = 0.2
00043 GRIPPER_MARKER_SCALE = 1.05
00044 REF_FRAME = 'base_link'
00045 
00046 # ######################################################################
00047 # Class
00048 # ######################################################################
00049 
00050 class ArmControlMarker:
00051     '''Marker for controlling arm of robot.'''
00052 
00053     _im_server = None
00054 
00055     def __init__(self, arm):
00056         '''
00057         Args:
00058             arm (Arm): Arm object for the robot's arm.
00059         '''
00060         if ArmControlMarker._im_server is None:
00061             im_server = InteractiveMarkerServer('interactive_arm_control')
00062             ArmControlMarker._im_server = im_server
00063 
00064         self._arm = arm
00065         self._name = 'arm'
00066         self._is_control_visible = False
00067         self._menu_handler = None
00068         self._prev_is_reachable = None
00069         self._pose = self._arm.get_ee_state()
00070         self._lock = threading.Lock()
00071         self._current_pose = None
00072         self._menu_control = None
00073 
00074         self.update()
00075 
00076 
00077     # ##################################################################
00078     # Instance methods: Public (API)
00079     # ##################################################################
00080 
00081     def update(self):
00082         '''Updates marker/arm loop'''
00083 
00084         self._menu_handler = MenuHandler()
00085 
00086         # Inset main menu entries.
00087         self._menu_handler.insert(
00088             'Move gripper here', callback=self._move_to_cb)
00089         self._menu_handler.insert(
00090             'Move marker to current gripper pose',
00091             callback=self._move_pose_to_cb)
00092 
00093         if self._is_hand_open():
00094             self._menu_handler.insert(
00095                 'Close gripper',
00096                 callback=self._close_gripper_cb)
00097         else:
00098             self._menu_handler.insert(
00099                 'Open gripper',
00100                 callback=self._open_gripper_cb)
00101 
00102         frame_id = REF_FRAME
00103         pose = self.get_pose()
00104 
00105         # if self._marker_moved() or self._menu_control is None:
00106         rospy.loginfo("Marker moved")
00107 
00108         menu_control = InteractiveMarkerControl()
00109         menu_control.interaction_mode = InteractiveMarkerControl.BUTTON
00110         menu_control.always_visible = True
00111 
00112 
00113         menu_control = self._make_gripper_marker(
00114             menu_control, self._is_hand_open())
00115         self._menu_control = menu_control
00116 
00117         # Make and add interactive marker.
00118         int_marker = InteractiveMarker()
00119         int_marker.name = self._get_name()
00120         int_marker.header.frame_id = frame_id
00121         int_marker.pose = pose.pose
00122         int_marker.scale = INT_MARKER_SCALE
00123         self._add_6dof_marker(int_marker, True)
00124         int_marker.controls.append(self._menu_control)
00125         ArmControlMarker._im_server.insert(
00126             int_marker, self._marker_feedback_cb)
00127 
00128         self._menu_handler.apply(ArmControlMarker._im_server,
00129             self._get_name())
00130         ArmControlMarker._im_server.applyChanges()
00131 
00132     def reset(self):
00133         '''Sets marker to current arm pose'''
00134         self.set_new_pose(self._arm.get_ee_state(), is_offset=True)
00135 
00136     def destroy(self):
00137         '''Removes marker from the world.'''
00138         ArmControlMarker._im_server.erase(self._get_name())
00139         ArmControlMarker._im_server.applyChanges()
00140 
00141     def set_new_pose(self, new_pose, is_offset=False):
00142         '''Changes the pose of the marker to new_pose.
00143 
00144         Args:
00145             new_pose (PoseStamped)
00146         '''
00147         self._lock.acquire()
00148         if is_offset:
00149             self._pose = new_pose
00150         else:
00151             self._pose = ArmControlMarker._offset_pose(new_pose, -1)
00152         self._lock.release()
00153 
00154     def get_pose(self):
00155         '''Returns the pose of the marker
00156 
00157         Returns:
00158             Pose
00159         '''
00160         self._lock.acquire()
00161         pose = ArmControlMarker._copy_pose(self._pose)
00162         self._lock.release()
00163         return ArmControlMarker._offset_pose(pose)
00164 
00165     # ##################################################################
00166     # Static methods: Internal ("private")
00167     # ##################################################################
00168 
00169     @staticmethod
00170     def _get_pose_from_transform(transform):
00171         '''Returns pose for transformation matrix.
00172 
00173         Args:
00174             transform (Matrix3x3)
00175 
00176         Returns:
00177             PoseStamped
00178         '''
00179         pos = transform[:3, 3].copy()
00180         rot = tf.transformations.quaternion_from_matrix(transform)
00181         return PoseStamped(Header(frame_id='base_link'),
00182                 Pose(Point(pos[0], pos[1], pos[2]),
00183                      Quaternion(rot[0], rot[1], rot[2], rot[3])
00184         ))
00185 
00186     @staticmethod
00187     def _get_matrix_from_pose(pose):
00188         '''Returns the transformation matrix for given pose.
00189 
00190         Args:
00191             pose (PoseStamped)
00192 
00193         Returns:
00194             Matrix3x3
00195         '''
00196         pp, po = pose.pose.position, pose.pose.orientation
00197         rotation = [po.x, po.y, po.z, po.w]
00198         transformation = tf.transformations.quaternion_matrix(rotation)
00199         position = [pp.x, pp.y, pp.z]
00200         transformation[:3, 3] = position
00201         return transformation
00202 
00203     @staticmethod
00204     def _offset_pose(pose, constant=1):
00205         '''Offsets the world pose for visualization.
00206 
00207         Args:
00208             pose (PoseStamped): The pose to offset.
00209             constant (int, optional): How much to scale the set offset
00210                 by (scales DEFAULT_OFFSET). Defaults to 1.
00211 
00212         Returns:
00213             PoseStamped: The offset pose.
00214         '''
00215         transform = ArmControlMarker._get_matrix_from_pose(pose)
00216         offset_array = [constant * DEFAULT_OFFSET, 0, 0]
00217         offset_transform = tf.transformations.translation_matrix(offset_array)
00218         hand_transform = tf.transformations.concatenate_matrices(
00219             transform, offset_transform)
00220         return ArmControlMarker._get_pose_from_transform(hand_transform)
00221 
00222 
00223 
00224     @staticmethod
00225     def _copy_pose(pose):
00226         '''Copies pose msg
00227 
00228         Args:
00229             pose (PoseStamped)
00230         Returns:
00231             PoseStamped
00232         '''
00233         copy = PoseStamped(
00234             Header(frame_id=pose.header.frame_id),
00235             Pose(Point(pose.pose.position.x, pose.pose.position.y,
00236                 pose.pose.position.z),
00237             Quaternion(pose.pose.orientation.x,
00238                 pose.pose.orientation.y,
00239                 pose.pose.orientation.z,
00240                 pose.pose.orientation.w))
00241         )
00242         return copy
00243 
00244     @staticmethod
00245     def _pose2array(p):
00246         '''Converts pose to array
00247 
00248         Args:
00249             p (PoseStamped)
00250         Returns:
00251             numpy.array
00252         '''
00253         return array((p.pose.position.x, p.pose.position.y,
00254                      p.pose.position.z,
00255                      p.pose.orientation.x, p.pose.orientation.y,
00256                      p.pose.orientation.z, p.pose.orientation.w))
00257 
00258     @staticmethod
00259     def _is_the_same(pose1, pose2, tol=0.001):
00260         '''Checks if poses are the same within tolerance
00261 
00262         Args:
00263             pose1 (Pose)
00264             pose2 (Pose)
00265             tol (float, optional)
00266         Returns:
00267             bool
00268         '''
00269         if pose1 is None or pose2 is None:
00270             return True
00271 
00272         diff = pose1 - pose2
00273         dist = linalg.norm(diff)
00274         return dist < tol
00275 
00276     @staticmethod
00277     def _make_mesh_marker(color):
00278         '''Creates and returns a mesh marker.
00279 
00280         Args:
00281             color (ColorRGBA)
00282         Returns:
00283             Marker
00284         '''
00285         mesh = Marker()
00286         mesh.mesh_use_embedded_materials = False
00287         mesh.type = Marker.MESH_RESOURCE
00288         mesh.scale.x = 1.0
00289         mesh.scale.y = 1.0
00290         mesh.scale.z = 1.0
00291         mesh.color = color
00292         return mesh
00293 
00294     # ##################################################################
00295     # Instance methods: Internal ("private")
00296     # ##################################################################
00297 
00298     def _marker_moved(self):
00299         '''Returns whether marker has moved
00300 
00301         Returns:
00302             bool
00303         '''
00304         moved = True
00305         current_pose = self.get_pose()
00306         if not self._current_pose is None:
00307             if ArmControlMarker._is_the_same(
00308                     ArmControlMarker._pose2array(current_pose),
00309                     ArmControlMarker._pose2array(self._current_pose)):
00310                 moved = False
00311         self._current_pose = current_pose
00312         return moved
00313 
00314     def _marker_feedback_cb(self, feedback):
00315         '''Callback for when an event occurs on the marker.
00316 
00317         Args:
00318             feedback (InteractiveMarkerFeedback)
00319         '''
00320         if feedback.event_type == InteractiveMarkerFeedback.BUTTON_CLICK:
00321             # Set the visibility of the 6DOF controller.
00322             # This happens a ton, and doesn't need to be logged like
00323             # normal events (e.g. clicking on most marker controls
00324             # fires here).
00325             rospy.logdebug('Changing visibility of the pose controls.')
00326             self._is_control_visible = not self._is_control_visible
00327         elif feedback.event_type == InteractiveMarkerFeedback.MOUSE_UP:
00328             self.set_new_pose(PoseStamped(Header(frame_id='base_link'),
00329                                                  feedback.pose))
00330             self.update()
00331         else:
00332             # This happens a ton, and doesn't need to be logged like
00333             # normal events (e.g. clicking on most marker controls
00334             # fires here).
00335             rospy.logdebug('Unknown event: ' + str(feedback.event_type))
00336 
00337     def _open_gripper_cb(self, feedback):
00338         '''Callback for opening gripper.
00339 
00340         Args:
00341             feedback (InteractiveMarkerFeedback): Unused
00342         '''
00343         self._arm.open_gripper()
00344         for i in range(30):
00345             if self._arm.get_gripper_state() == GripperState.OPEN:
00346                 break
00347             rospy.sleep(0.1)
00348         self.update()
00349 
00350     def _close_gripper_cb(self, feedback):
00351         '''Callback for closing gripper.
00352 
00353         Args:
00354             feedback (InteractiveMarkerFeedback): Unused
00355         '''
00356         self._arm.close_gripper()
00357         for i in range(30):
00358             if self._arm.get_gripper_state() != GripperState.OPEN:
00359                 break
00360             rospy.sleep(0.1)
00361         self.update()
00362 
00363     def _move_to_cb(self, feedback):
00364         '''Callback for when moving to a pose is requested.
00365 
00366         Args:
00367             feedback (InteractiveMarkerFeedback): Unused
00368         '''
00369 
00370         if not ArmControlMarker._is_the_same(
00371             ArmControlMarker._pose2array(
00372                 self._pose),
00373                 ArmControlMarker._pose2array(self._arm.get_ee_state()),
00374                 0.01):
00375             rospy.loginfo("Move to cb from marker for real")
00376 
00377             self._lock.acquire()
00378             pose = ArmControlMarker._copy_pose(self._pose)
00379             self._lock.release()
00380 
00381             target_pose = pose
00382 
00383             if target_pose is not None:
00384                 # time_to_pose = self._arm.get_time_to_pose(self.get_pose())
00385 
00386                 thread = threading.Thread(
00387                     group=None,
00388                     target=self._arm.move_to_pose,
00389                     args=(target_pose,),
00390                     name='move_to_arm_state_thread'
00391                 )
00392                 thread.start()
00393 
00394                 # Log
00395                 # side_str = self._arm.side()
00396                 rospy.loginfo('Started thread to move arm.')
00397             else:
00398                 rospy.loginfo('Will not move arm; unreachable.')
00399         else:
00400             rospy.loginfo("move too small?")
00401 
00402     def _move_pose_to_cb(self, feedback):
00403         '''Callback for moving gripper marker to current pose.
00404 
00405         Args:
00406             feedback (InteractiveMarkerFeedback): Unused
00407         '''
00408         self.reset()
00409         self.update()
00410 
00411     def _is_reachable(self):
00412         '''Checks and returns whether there is an IK solution for this
00413         marker pose
00414 
00415         Returns:
00416             bool: Whether this action step is reachable.
00417         '''
00418 
00419         self._lock.acquire()
00420         pose = ArmControlMarker._copy_pose(self._pose)
00421         self._lock.release()
00422 
00423         target_joints = self._arm.get_ik_for_ee(
00424                 pose, self._arm.get_joint_state())
00425 
00426 
00427         is_reachable = (target_joints is not None)
00428 
00429         # A bit more complicated logging to avoid spamming the logs
00430         # while still giving useful info. It now logs when reachability
00431         # is first calculated, or changes.
00432         report = False
00433 
00434         # See if we've set reachability for the first time.
00435         if self._prev_is_reachable is None:
00436             report = True
00437             reachable_str = (
00438                 'is reachable' if is_reachable else 'is not reachable')
00439         # See if we've got a different reachability than we had before.
00440         elif self._prev_is_reachable != is_reachable:
00441             report = True
00442             reachable_str = (
00443                 'is now reachable' if is_reachable else
00444                 'is no longer reachable')
00445 
00446         # Log if it's changed.
00447         if report:
00448             rospy.loginfo(reachable_str)
00449 
00450         # Cache and return.
00451         self._prev_is_reachable = is_reachable
00452 
00453         print
00454         return is_reachable
00455 
00456     def _get_name(self):
00457         '''Generates the unique name for the marker.
00458 
00459         Returns:
00460             str: A human-readable unique name for the marker.
00461         '''
00462         return self._name
00463 
00464     def _is_hand_open(self):
00465         '''Returns whether the gripper is open
00466 
00467         Returns:
00468             bool
00469         '''
00470         return self._arm.get_gripper_state() == GripperState.OPEN
00471 
00472     def _add_6dof_marker(self, int_marker, is_fixed):
00473         '''Adds a 6 DoF control marker to the interactive marker.
00474 
00475         Args:
00476             int_marker (InteractiveMarker)
00477             is_fixed (bool): Looks like whether position is fixed (?).
00478                 Currently only passed as True.
00479         '''
00480         # Each entry in options is (name, orientation, is_move)
00481         options = [
00482             ('rotate_x', Quaternion(1, 0, 0, 1), False),
00483             ('move_x', Quaternion(1, 0, 0, 1), True),
00484             ('rotate_z', Quaternion(0, 1, 0, 1), False),
00485             ('move_z', Quaternion(0, 1, 0, 1), True),
00486             ('rotate_y', Quaternion(0, 0, 1, 1), False),
00487             ('move_y', Quaternion(0, 0, 1, 1), True),
00488         ]
00489         for opt in options:
00490             name, orient, is_move = opt
00491             control = self._make_6dof_control(
00492                 name, orient, is_move, is_fixed)
00493             int_marker.controls.append(control)
00494 
00495     def _make_6dof_control(self, name, orientation, is_move, is_fixed):
00496         '''Creates and returns one component of the 6dof controller.
00497 
00498         Args:
00499             name (str): Name for the control
00500             orientation (Quaternion): How the control should be
00501                 oriented.
00502             is_move (bool): Looks like whether the marker moves the
00503                 object (?). Currently passed as True for moving markers,
00504                 False for rotating ones.
00505             is_fixed (bool): Looks like whether position is fixed (?).
00506                 Currently always passed as True.
00507 
00508         Returns:
00509             InteractiveMarkerControl
00510         '''
00511         control = InteractiveMarkerControl()
00512         control.name = name
00513         control.orientation = orientation
00514         control.always_visible = False
00515         if self._is_control_visible:
00516             if is_move:
00517                 control.interaction_mode = InteractiveMarkerControl.MOVE_AXIS
00518             else:
00519                 control.interaction_mode = InteractiveMarkerControl.ROTATE_AXIS
00520         else:
00521             control.interaction_mode = InteractiveMarkerControl.NONE
00522         if is_fixed:
00523             control.orientation_mode = InteractiveMarkerControl.FIXED
00524         return control
00525 
00526     def _get_mesh_marker_color(self):
00527         '''Gets the color for the mesh marker (thing that looks like a
00528         gripper) for this step.
00529 
00530         A simple implementation of this will return one color for
00531         reachable poses, another for unreachable ones. Future
00532         implementations may provide further visual cues.
00533 
00534         Returns:
00535             ColorRGBA: The color for the gripper mesh for this step.
00536         '''
00537         if self._is_reachable():
00538             return COLOR_MESH_REACHABLE
00539         else:
00540             return COLOR_MESH_UNREACHABLE
00541 
00542     def _make_gripper_marker(self, control, is_hand_open=False):
00543         '''Makes a gripper marker, adds it to control, returns control.
00544 
00545         Args:
00546             control (InteractiveMarkerControl): IM Control we're using.
00547             is_hand_open (bool, optional): Whether the gripper is open.
00548                 Defaults to False (closed).
00549 
00550         Returns:
00551             InteractiveMarkerControl: The passed control.
00552         '''
00553         mesh_color = self._get_mesh_marker_color()
00554 
00555         # Create mesh 1 (palm).
00556         mesh1 = ArmControlMarker._make_mesh_marker(mesh_color)
00557         mesh1.mesh_resource = STR_GRIPPER_PALM_FILE
00558         mesh1.pose.position.x = DEFAULT_OFFSET
00559         mesh1.pose.orientation.w = 1
00560 
00561         # TODO (sarah): make all of these numbers into constants
00562         if is_hand_open:
00563             mesh2 = ArmControlMarker._make_mesh_marker(mesh_color)
00564             mesh2.mesh_resource = STR_L_GRIPPER_FINGER_FILE
00565             mesh2.pose.position.x = 0.08
00566             mesh2.pose.position.y = -0.165
00567             mesh2.pose.orientation.w = 1
00568 
00569             mesh3 = ArmControlMarker._make_mesh_marker(mesh_color)
00570             mesh3.mesh_resource = STR_R_GRIPPER_FINGER_FILE
00571             mesh3.pose.position.x = 0.08
00572             mesh3.pose.position.y = 0.165
00573             mesh3.pose.orientation.w = 1
00574         else:
00575             mesh2 = ArmControlMarker._make_mesh_marker(mesh_color)
00576             mesh2.mesh_resource = STR_L_GRIPPER_FINGER_FILE
00577             mesh2.pose.position.x = 0.08
00578             mesh2.pose.position.y = -0.116
00579             mesh2.pose.orientation.w = 1
00580 
00581             mesh3 = ArmControlMarker._make_mesh_marker(mesh_color)
00582             mesh3.mesh_resource = STR_R_GRIPPER_FINGER_FILE
00583             mesh3.pose.position.x = 0.08
00584             mesh3.pose.position.y = 0.116
00585             mesh3.pose.orientation.w = 1
00586 
00587         # Append all meshes we made.
00588         control.markers.append(mesh1)
00589         control.markers.append(mesh2)
00590         control.markers.append(mesh3)
00591 
00592         # Return back the control.
00593         # TODO(mbforbes): Why do we do this?
00594         return control


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