00001 '''Main interaction event handler. Receives speech and GUI commands and
00002 sends events out to the system.'''
00003
00004
00005
00006
00007
00008
00009 import rospy
00010
00011
00012 import threading
00013
00014
00015 from visualization_msgs.msg import MarkerArray
00016 from interactive_markers.interactive_marker_server import \
00017 InteractiveMarkerServer
00018 from tf import TransformListener
00019
00020
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
00033
00034
00035 BASE_LINK = 'base_link'
00036 TOPIC_IM_SERVER = '/fetch_pbd/programmed_actions'
00037
00038
00039
00040
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
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
00073 self._viz_publisher = rospy.Publisher(
00074 '/fetch_pbd/visualization_marker_array',
00075 MarkerArray,
00076 queue_size=10)
00077
00078
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
00085 self._is_recording_motion = False
00086
00087
00088 self._responses = {
00089
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
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
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
00116 GuiInputRequest.STOP_EXECUTION: self._stop_execution,
00117 GuiInputRequest.EXECUTE_ACTION: self._execute_action,
00118 GuiInputRequest.EXECUTE_PRIMITIVE: self._execute_primitive,
00119 }
00120
00121
00122
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
00131 self._robot.relax_arm()
00132
00133
00134
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
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
00165 if self._session.n_actions() > 0:
00166 self._session.publish_primitive_tf()
00167
00168
00169 if self._is_recording_motion:
00170 self._session.update_arm_trajectory()
00171
00172
00173
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
00179
00180
00181
00182
00183
00184
00185
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
00209 self._robot.play_sound(RobotSound.EXECUTION_ENDED)
00210 self._robot.nod_head()
00211 elif status == ExecutionStatus.PREEMPTED:
00212
00213
00214 self._robot.shake_head()
00215 else:
00216
00217 self._robot.play_sound(RobotSound.ERROR)
00218 self._robot.shake_head()
00219
00220
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
00239
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
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
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
00473 if self._robot.get_gripper_state() != GripperState.OPEN:
00474
00475 self._robot.set_gripper_state(GripperState.OPEN)
00476 if self._session.n_actions() > 0:
00477
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
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
00497 self._head_busy = True
00498 if self._robot.get_gripper_state() != GripperState.CLOSED:
00499 self._robot.set_gripper_state(GripperState.CLOSED)
00500
00501 if self._session.n_actions() > 0:
00502
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
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
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
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