interaction.py
Go to the documentation of this file.
00001 '''Main interaction event handler. Receives speech and GUI commands and
00002 sends events out to the system.'''
00003 
00004 # ######################################################################
00005 # Imports
00006 # ######################################################################
00007 
00008 # Core ROS imports come first.
00009 import rospy
00010 
00011 # System builtins
00012 import threading
00013 
00014 # ROS builtins
00015 from visualization_msgs.msg import MarkerArray
00016 from interactive_markers.interactive_marker_server import \
00017      InteractiveMarkerServer
00018 from tf import TransformListener
00019 
00020 # Local
00021 from fetch_arm_control.msg import GripperState
00022 from fetch_pbd_interaction.session import Session
00023 from fetch_pbd_interaction.msg import ExecutionStatus, Landmark
00024 from fetch_pbd_interaction.srv import Ping, PingResponse, GetObjectList, \
00025                                       GuiInput, GuiInputRequest, \
00026                                       GuiInputResponse
00027 from fetch_pbd_interaction.msg import RobotSound, WorldState
00028 from fetch_pbd_interaction.robot import Robot
00029 from std_srvs.srv import Empty
00030 
00031 # ######################################################################
00032 # Module level constants
00033 # ######################################################################
00034 
00035 BASE_LINK = 'base_link'
00036 TOPIC_IM_SERVER = '/fetch_pbd/programmed_actions'
00037 
00038 
00039 # ######################################################################
00040 # Classes
00041 # ######################################################################
00042 
00043 
00044 class Interaction:
00045     '''Interaction is the multiplexer of commands received into system
00046     actions.
00047 
00048     Interaction receives GUI input (gui_input) and sends
00049     these off into the system to be
00050     processed. Interaction holds a small amount of state to support
00051     recording trajectories.
00052     '''
00053 
00054     def __init__(self, grasp_suggestion_service, 
00055                 grasp_feedback_topic, external_ee_link,
00056                 to_file, from_file, play_sound, social_gaze):
00057 
00058         # Create main components.
00059         self._tf_listener = TransformListener()
00060         self._robot = Robot(self._tf_listener, play_sound, social_gaze)
00061         self._im_server = InteractiveMarkerServer(TOPIC_IM_SERVER)
00062         self._session = Session(
00063                         self._robot, self._tf_listener,
00064                         self._im_server,
00065                         to_file=to_file, 
00066                         from_file=from_file,
00067                         grasp_suggestion_service_name=grasp_suggestion_service,
00068                         grasp_feedback_topic=grasp_feedback_topic,
00069                         external_ee_link=external_ee_link)
00070         self._head_busy = False
00071 
00072         # ROS publishers, subscribers, services
00073         self._viz_publisher = rospy.Publisher(
00074                                 '/fetch_pbd/visualization_marker_array',
00075                                 MarkerArray,
00076                                 queue_size=10)
00077 
00078         # rospy.Subscriber('gui_input', GuiInput, self._gui_input_cb)
00079         rospy.Service('/fetch_pbd/gui_input', GuiInput, self._gui_input_cb)
00080 
00081         rospy.Subscriber('/fetch_pbd/world_updates', WorldState, 
00082                             self._world_update_cb)
00083 
00084         # Initialize trajectory recording state.
00085         self._is_recording_motion = False
00086 
00087         # Command/callback pairs for input
00088         self._responses = {
00089             # Action Creation/Navigation
00090             GuiInputRequest.CREATE_ACTION: self._create_action,
00091             GuiInputRequest.SWITCH_TO_ACTION: self._switch_to_action,
00092             GuiInputRequest.NEXT_ACTION: self._next_action,
00093             GuiInputRequest.PREV_ACTION: self._previous_action,
00094             GuiInputRequest.UPDATE_ACTION_NAME: self._update_action_name,
00095             GuiInputRequest.DELETE_ACTION: self._delete_action,
00096             GuiInputRequest.COPY_ACTION: self._copy_action,
00097             # Primitive Creation Navigation
00098             GuiInputRequest.SWITCH_PRIMITIVE_ORDER: self._switch_primitive_order,
00099             GuiInputRequest.DELETE_PRIMITIVE: self._delete_primitive,
00100             GuiInputRequest.COPY_PRIMITIVE: self._copy_primitive,
00101             GuiInputRequest.SELECT_PRIMITIVE: self._select_primitive,
00102             GuiInputRequest.DELETE_ALL_PRIMITIVES: self._delete_all_primitives,
00103             GuiInputRequest.DELETE_LAST_PRIMITIVE: self._delete_last_primitive,
00104             GuiInputRequest.HIDE_PRIMITIVE_MARKER: self._hide_primitive_marker,
00105             GuiInputRequest.SHOW_PRIMITIVE_MARKER: self._show_primitive_marker,
00106             # Programming
00107             GuiInputRequest.OPEN_HAND: self._open_hand,
00108             GuiInputRequest.CLOSE_HAND: self._close_hand,
00109             GuiInputRequest.RECORD_OBJECTS: self._record_objects,
00110             GuiInputRequest.SAVE_TARGET: self._save_target,
00111             GuiInputRequest.START_RECORDING_TRAJECTORY: \
00112                                 self._start_recording_trajectory,
00113             GuiInputRequest.STOP_RECORDING_TRAJECTORY: self._stop_recording_trajectory,
00114             GuiInputRequest.POSE_EDITED: self._primitive_pose_edited,
00115             # Execution
00116             GuiInputRequest.STOP_EXECUTION: self._stop_execution,
00117             GuiInputRequest.EXECUTE_ACTION: self._execute_action,
00118             GuiInputRequest.EXECUTE_PRIMITIVE: self._execute_primitive,
00119         }
00120 
00121         # The PbD backend is ready.
00122         # This basically exists for tests that aren't actually written yet
00123         rospy.loginfo('Interaction initialized.')
00124         self._ping_srv = rospy.Service('/fetch_pbd/interaction_ping', Ping,
00125                                        self._interaction_ping)
00126 
00127         rospy.Subscriber('/fetch_pbd/add_grasp', Landmark,
00128                       self._add_grasp)
00129 
00130         # Make sure gravity compensation controllers are on before we start
00131         self._robot.relax_arm()
00132 
00133     # ##################################################################
00134     # Instance methods: Public (API)
00135     # ##################################################################
00136 
00137     def update(self):
00138         '''General update for the main loop.
00139 
00140         This is called continuously from the node.
00141         This pauses for 100ms at the end of every
00142         run before returning.
00143         '''
00144 
00145         arm_moving = self._robot.is_arm_moving()
00146 
00147         self._head_busy = self._head_busy or self._session.head_busy()
00148 
00149         if not arm_moving and not self._head_busy:
00150             # rospy.loginfo("Arm moving")
00151             self._robot.look_forward()
00152         else:
00153             if self._session.n_actions() > 0:
00154                 current_action = self._session.get_current_action()
00155                 if not current_action is None:
00156                     action_status = current_action.get_status()
00157 
00158                     if (action_status != ExecutionStatus.EXECUTING and
00159                             not self._head_busy):
00160                         self._robot.look_at_ee()
00161             elif not self._head_busy:
00162                 self._robot.look_at_ee()
00163 
00164         # Update the current action if there is one.
00165         if self._session.n_actions() > 0:
00166             self._session.publish_primitive_tf()
00167 
00168             # Record trajectory primitive.
00169             if self._is_recording_motion:
00170                 self._session.update_arm_trajectory()
00171 
00172             # If the objects in the world have changed, update the
00173             # action with them.
00174             current_action = self._session.get_current_action()
00175             if not current_action is None:
00176                 action_status = current_action.get_status()
00177                 current_action.update_viz()
00178                 # if action_status != ExecutionStatus.NOT_EXECUTING:
00179                 #     # self._arm_reset_publisher.publish(String(''))
00180                 #     if action_status != ExecutionStatus.EXECUTING:
00181                 #         self._end_execution()
00182 
00183 
00184     # ##################################################################
00185     # Internal ("private" methods)
00186     # ##################################################################
00187 
00188 
00189     def _world_update_cb(self, msg):
00190         ''' Respond to changes in world
00191         Right now these changes are mostly initiated by
00192         this class anyway but in case something else changes
00193         we want to keep track
00194 
00195         Args:
00196             msg (WorldState)
00197         '''
00198         current_action = self._session.get_current_action()
00199         if not current_action is None:
00200             current_action.update_objects()
00201 
00202     def _end_execution(self):
00203         '''Says a response and performs a gaze action for when an action
00204         execution ends.'''
00205         status = self._session.get_current_action().get_status()
00206         rospy.loginfo("Execution ended. Status: " + str(status))
00207         if status == ExecutionStatus.SUCCEEDED:
00208             # Execution completed successfully.
00209             self._robot.play_sound(RobotSound.EXECUTION_ENDED)
00210             self._robot.nod_head()
00211         elif status == ExecutionStatus.PREEMPTED:
00212             # Execution stopped early (preempted).
00213             # self._robot.play_sound(RobotSound.ERROR)
00214             self._robot.shake_head()
00215         else:
00216             # Couldn't solve for joint positions (IK).
00217             self._robot.play_sound(RobotSound.ERROR)
00218             self._robot.shake_head()
00219 
00220         # self._robot.relax_arm()
00221         self._session.get_current_action().end_execution()
00222 
00223 
00224     def _interaction_ping(self, req):
00225         '''This is the service that is provided so that external nodes
00226         know the interaction (i.e. PbD) is ready. This basically exists
00227         for tests that haven't been written yet for Fetch.
00228 
00229         Args:
00230             req (PingRequest): unused
00231 
00232         Returns:
00233             PingResponse: empty
00234         '''
00235         return PingResponse()
00236 
00237 
00238     # The following methods receive commands from speech / GUI and
00239     # process them. These are the multiplexers.
00240 
00241     def _gui_input_cb(self, req):
00242         '''Callback for when input is received from GUI
00243 
00244         Args:
00245             req (GuiInputRequest): The input received from GUI
00246         '''
00247         # We extract the command string as we use it a lot.
00248         cmd = req.command
00249         if cmd in self._responses.keys():
00250             rospy.loginfo('\033[32m' + 'Calling response for command ' + cmd
00251                           + '\033[0m')
00252             response = self._responses[cmd]
00253 
00254             if not self._session.n_actions() > 0:
00255                 threading.Thread(group=None,
00256                     target=response,
00257                     args=(req,),
00258                     name='gui_response_thread').start()
00259 
00260             elif (self._session.get_current_action() is None and
00261                     (cmd == GuiInputRequest.CREATE_ACTION or
00262                     cmd == GuiInputRequest.COPY_ACTION or
00263                     cmd == GuiInputRequest.DELETE_ACTION or
00264                     cmd == GuiInputRequest.SWITCH_TO_ACTION)):
00265                 threading.Thread(group=None,
00266                     target=response,
00267                     args=(req,),
00268                     name='gui_response_thread').start()
00269 
00270             elif self._session.get_current_action() is None:
00271                 rospy.logwarn("No current action")
00272 
00273             elif ((self._session.get_current_action().get_status() !=
00274                     ExecutionStatus.EXECUTING) or
00275                     cmd == GuiInputRequest.STOP_EXECUTION):
00276                 threading.Thread(group=None,
00277                     target=response,
00278                     args=(req,),
00279                     name='gui_response_thread').start()
00280             else:
00281                 rospy.logwarn(
00282                     'Ignoring speech command during execution: ' + cmd)
00283         else:
00284             rospy.logwarn('This command (' + cmd + ') is unknown.')
00285 
00286         return GuiInputResponse()
00287 
00288     def _create_action(self, gui_input):
00289         '''Creates a new empty action.
00290 
00291         Args:
00292             gui_input (GuiInputRequest) : unused
00293         '''
00294         self._session.new_action()
00295         self._robot.play_sound(RobotSound.CREATED_ACTION)
00296         self._robot.nod_head()
00297 
00298     def _switch_to_action(self, gui_input):
00299         '''Switches to an action that is already loaded in the session.
00300 
00301         The action is accessed by the index in the session's action list.
00302         The index is 0-based, so the first action is action 0.
00303 
00304         Args:
00305             gui_input (GuiInputRequest) : contains the index into the session's action
00306                                    list to switch to.
00307         '''
00308         # Command: switch to a specified action.
00309         success = self._session.switch_to_action_by_index(int(gui_input.param))
00310         if not success:
00311             self._robot.play_sound(RobotSound.ERROR)
00312             self._robot.shake_head()
00313         else:
00314             self._robot.play_sound(RobotSound.SUCCESS)
00315             self._robot.nod_head()
00316 
00317     def _next_action(self, gui_input):
00318         '''Switches to next action.
00319 
00320         Args:
00321             gui_input (GuiInputRequest) : unused
00322         '''
00323         if self._session.n_actions() > 0:
00324             if self._session.next_action():
00325                 self._robot.play_sound(RobotSound.SUCCESS)
00326                 self._robot.nod_head()
00327             else:
00328                 self._robot.play_sound(RobotSound.ERROR)
00329                 self._robot.shake_head()
00330         else:
00331             self._robot.play_sound(RobotSound.ERROR)
00332             self._robot.shake_head()
00333 
00334     def _previous_action(self, gui_input):
00335         '''Switches to previous action.
00336 
00337         Args:
00338             gui_input (GuiInputRequest) : unused
00339         '''
00340         if self._session.n_actions() > 0:
00341             if self._session.previous_action():
00342                 self._robot.play_sound(RobotSound.SUCCESS)
00343                 self._robot.nod_head()
00344             else:
00345                 self._robot.play_sound(RobotSound.ERROR)
00346                 self._robot.shake_head()
00347         else:
00348             self._robot.play_sound(RobotSound.ERROR)
00349             self._robot.shake_head()
00350 
00351     def _update_action_name(self, gui_input):
00352         '''Update name of action.
00353 
00354         Args:
00355             gui_input (GuiInputRequest) : contains new name of action
00356         '''
00357         self._session.update_action_name(gui_input.param)
00358 
00359     def _delete_action(self, gui_input):
00360         '''Deletes action with certain index
00361 
00362         Args:
00363             gui_input (GuiInputRequest) : contains index of action to delete
00364         '''
00365         self._session.delete_action(int(gui_input.param))
00366 
00367     def _copy_action(self, gui_input):
00368         '''Copies action with certain index
00369 
00370         Args:
00371             gui_input (GuiInputRequest) : contains index of action to copy
00372         '''
00373         self._session.copy_action(int(gui_input.param))
00374         self._robot.play_sound(RobotSound.CREATED_ACTION)
00375         self._robot.nod_head()
00376 
00377     def _switch_primitive_order(self, gui_input):
00378         '''Changes the order of primitives
00379 
00380         Args:
00381             gui_input (GuiInputRequest) : contains the previous and current indices
00382                                    of the moved primitive
00383         '''
00384         old_index = gui_input.list_params[0]
00385         new_index = gui_input.list_params[1]
00386         self._session.switch_primitive_order(old_index, new_index)
00387 
00388     def _delete_primitive(self, gui_input):
00389         '''Deletes primitive with certain index from current action
00390 
00391         Args:
00392             gui_input (GuiInputRequest) : contains index of primitive to delete
00393         '''
00394         self._session.delete_primitive(int(gui_input.param))
00395 
00396     def _copy_primitive(self, gui_input):
00397         '''Copies primitive with certain index from current action
00398 
00399         Args:
00400             gui_input (GuiInputRequest) : contains index of primitive to copy
00401         '''
00402         self._session.copy_primitive(int(gui_input.param))
00403 
00404     def _select_primitive(self, gui_input):
00405         '''Selects a primitive in the current action.
00406 
00407         Args:
00408             gui_input (GuiInputRequest) : contains index of primitive to select
00409         '''
00410         primitive_number = int(gui_input.param)
00411         self._session.select_action_primitive(primitive_number)
00412 
00413     def _delete_all_primitives(self, gui_input):
00414         '''Deletes all primitives in the current action.
00415 
00416         Args:
00417             gui_input (GuiInputRequest) : unused
00418         '''
00419         if self._session.n_actions() > 0:
00420             if self._session.n_primitives() > 0:
00421                 self._session.clear_current_action()
00422                 self._robot.play_sound(RobotSound.ALL_POSES_DELETED)
00423                 self._robot.nod_head()
00424             else:
00425                 self._robot.play_sound(RobotSound.ERROR)
00426                 self._robot.shake_head()
00427         else:
00428             self._robot.play_sound(RobotSound.ERROR)
00429             self._robot.shake_head()
00430 
00431     def _delete_last_primitive(self, gui_input):
00432         '''Deletes last primitive of the current action.
00433 
00434         Args:
00435             gui_input (GuiInputRequest) : unused
00436         '''
00437         if self._session.n_actions() > 0:
00438             if self._session.n_primitives() > 0:
00439                 self._session.delete_last_primitive()
00440                 self._robot.play_sound(RobotSound.OTHER)
00441                 self._robot.nod_head()
00442             else:
00443                 self._robot.play_sound(RobotSound.ERROR)
00444                 self._robot.shake_head()
00445         else:
00446             self._robot.play_sound(RobotSound.ERROR)
00447             self._robot.shake_head()
00448 
00449     def _hide_primitive_marker(self, gui_input):
00450         '''Hide marker with certain index
00451 
00452         Args:
00453             gui_input (GuiInputRequest) : contains index of marker to hide
00454         '''
00455         self._session.hide_primitive_marker(int(gui_input.param))
00456 
00457     def _show_primitive_marker(self, gui_input):
00458         '''Show marker with certain index
00459 
00460         Args:
00461             gui_input (GuiInputRequest) : contains index of marker to show
00462         '''
00463         self._session.show_primitive_marker(int(gui_input.param))
00464 
00465     def _open_hand(self, gui_input):
00466         '''Opens gripper
00467 
00468         Args:
00469             gui_input (GuiInputRequest) : unused
00470         '''
00471         self._head_busy = True
00472         # First, open the hand if it's closed.
00473         if self._robot.get_gripper_state() != GripperState.OPEN:
00474             # Hand was closed, now open.
00475             self._robot.set_gripper_state(GripperState.OPEN)
00476             if self._session.n_actions() > 0:
00477                 # If we're currently programming, save that as a primitive
00478                 self._session.add_arm_target_to_action()
00479                 self._robot.play_sound(RobotSound.POSE_SAVED)
00480 
00481             self._robot.play_sound(RobotSound.OTHER)
00482             self._robot.look_at_ee(follow=False)
00483 
00484         else:
00485             # Hand was already open; complain.
00486             self._robot.play_sound(RobotSound.ERROR)
00487             self._robot.look_at_ee(follow=False)
00488         self._head_busy = False
00489 
00490     def _close_hand(self, gui_input):
00491         '''Closes gripper
00492 
00493         Args:
00494             gui_input (GuiInputRequest) : unused
00495         '''
00496         # First, close the hand if it's open.
00497         self._head_busy = True
00498         if self._robot.get_gripper_state() != GripperState.CLOSED:
00499             self._robot.set_gripper_state(GripperState.CLOSED)
00500             # Hand was open, now closed.
00501             if self._session.n_actions() > 0:
00502                 # If we're currently programming, save that as a primitive.
00503                 self._session.add_arm_target_to_action()
00504                 self._robot.play_sound(RobotSound.POSE_SAVED)
00505 
00506             self._robot.play_sound(RobotSound.OTHER)
00507             self._robot.look_at_ee(follow=False)
00508         else:
00509             # Hand was already closed; complain.
00510             self._robot.play_sound(RobotSound.ERROR)
00511             self._robot.look_at_ee(follow=False)
00512         self._head_busy = False
00513 
00514     def _record_objects(self, gui_input):
00515         '''Makes the robot look for a table and objects.
00516 
00517         Args:
00518             gui_input (GuiInputRequest) : unused
00519         '''
00520         self._head_busy = True
00521         if self._session.record_objects():
00522             self._robot.play_sound(RobotSound.SUCCESS)
00523             self._robot.nod_head()
00524         else:
00525             self._robot.play_sound(RobotSound.ERROR)
00526             self._robot.shake_head()
00527         self._head_busy = False
00528 
00529     def _save_target(self, gui_input):
00530         '''Saves current arm state as an action primitive (ArmTarget).
00531 
00532         Args:
00533             gui_input (GuiInputRequest) : unused
00534         '''
00535         self._head_busy = True
00536         if self._session.n_actions() > 0:
00537             self._session.add_arm_target_to_action()
00538             self._robot.play_sound(RobotSound.POSE_SAVED)
00539             self._robot.nod_head()
00540         else:
00541             self._robot.play_sound(RobotSound.ERROR)
00542             self._robot.shake_head()
00543         self._head_busy = False
00544 
00545     def _add_grasp(self, msg):
00546         '''Callback to add a grasp for the specified object to 
00547         the current action
00548 
00549         Args:
00550             msg (Landmark)
00551         '''
00552         rospy.loginfo("Attempting to add grasp")
00553         self._head_busy = True
00554         if self._session.n_actions() > 0:
00555             self._session.add_grasp_to_action(msg)
00556             self._robot.play_sound(RobotSound.POSE_SAVED)
00557             self._robot.nod_head()
00558         else:
00559             self._robot.play_sound(RobotSound.ERROR)
00560             self._robot.shake_head()
00561         self._head_busy = False
00562 
00563     def _start_recording_trajectory(self, gui_input):
00564         '''Starts recording continuous motion.
00565 
00566         Args:
00567             gui_input (GuiInputRequest) : unused
00568         '''
00569         self._head_busy = True
00570         if self._session.n_actions() > 0:
00571             if not self._is_recording_motion:
00572                 self._is_recording_motion = True
00573                 self._session.start_recording_arm_trajectory()
00574                 self._robot.play_sound(RobotSound.START_TRAJECTORY)
00575                 self._robot.nod_head()
00576             else:
00577                 self._robot.play_sound(RobotSound.ERROR)
00578                 self._robot.shake_head()
00579         else:
00580             self._robot.play_sound(RobotSound.ERROR)
00581             self._robot.shake_head()
00582         self._head_busy = False
00583 
00584     def _stop_recording_trajectory(self, gui_input):
00585         '''Stops recording continuous motion.
00586 
00587         Args:
00588             gui_input (GuiInputRequest) : unused
00589         '''
00590         self._head_busy = True
00591         if self._is_recording_motion:
00592             self._is_recording_motion = False
00593 
00594             self._session.stop_recording_arm_trajectory()
00595             self._robot.play_sound(RobotSound.POSE_SAVED)
00596             self._robot.nod_head()
00597 
00598         else:
00599             self._robot.play_sound(RobotSound.ERROR)
00600             self._robot.shake_head()
00601         self._head_busy = False
00602 
00603     def _primitive_pose_edited(self, gui_input):
00604         '''Updates the primitive pose with input from the interface
00605 
00606         Args:
00607             gui_input (GuiInputRequest) : contains pose information
00608         '''
00609         primitive_number = int(gui_input.param)
00610         self._session.update_primitive_pose(primitive_number,
00611                                             gui_input.position,
00612                                             gui_input.orientation)
00613 
00614     def _execute_action(self, gui_input):
00615         '''Starts the execution of the current action.
00616 
00617         Args:
00618             gui_input (GuiInputRequest) : unused
00619         '''
00620         # We must have a current action.
00621         self._head_busy = True
00622         self._robot.play_sound(RobotSound.STARTING_EXECUTION)
00623         if self._session.execute_current_action():
00624             self._robot.play_sound(RobotSound.SUCCESS)
00625             rospy.loginfo("Action succeeded")
00626             self._robot.nod_head()
00627         else:
00628             self._robot.play_sound(RobotSound.ERROR)
00629             rospy.logwarn("Action failed")
00630             self._robot.shake_head()
00631         self._head_busy = False
00632 
00633     def _stop_execution(self, gui_input):
00634         '''Stops ongoing execution after current primitive is finished
00635 
00636         Args:
00637             gui_input (GuiInputRequest) : unused
00638         '''
00639         self._head_busy = True
00640         status = self._session.get_current_action().get_status()
00641         if status == ExecutionStatus.EXECUTING:
00642             self._session.get_current_action().stop_execution()
00643             self._robot.play_sound(RobotSound.OTHER)
00644             # self._robot.nod_head()
00645         else:
00646             self._robot.play_sound(RobotSound.ERROR)
00647             self._robot.shake_head()
00648         self._head_busy = False
00649 
00650     def _execute_primitive(self, gui_input):
00651         '''Execute primitive with certain index
00652 
00653         Args:
00654             gui_input (GuiInputRequest) : contains index of primitive to execute
00655         '''
00656         self._head_busy = True
00657         self._session.execute_primitive(int(gui_input.param))
00658         self._head_busy = False


fetch_pbd_interaction
Author(s): Sarah Elliott
autogenerated on Thu Jun 6 2019 18:27:21