joint_trajectory_file_playback.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Copyright (c) 2013-2015, Rethink Robotics
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions are met:
00008 #
00009 # 1. Redistributions of source code must retain the above copyright notice,
00010 #    this list of conditions and the following disclaimer.
00011 # 2. Redistributions in binary form must reproduce the above copyright
00012 #    notice, this list of conditions and the following disclaimer in the
00013 #    documentation and/or other materials provided with the distribution.
00014 # 3. Neither the name of the Rethink Robotics nor the names of its
00015 #    contributors may be used to endorse or promote products derived from
00016 #    this software without specific prior written permission.
00017 #
00018 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00019 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00020 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00021 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00022 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00023 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00024 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00025 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00026 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00027 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00028 # POSSIBILITY OF SUCH DAMAGE.
00029 """
00030 Baxter RSDK Joint Trajectory Example: file playback
00031 """
00032 
00033 import argparse
00034 import operator
00035 import sys
00036 import threading
00037 
00038 from bisect import bisect
00039 from copy import copy
00040 from os import path
00041 
00042 import rospy
00043 
00044 import actionlib
00045 
00046 from control_msgs.msg import (
00047     FollowJointTrajectoryAction,
00048     FollowJointTrajectoryGoal,
00049 )
00050 from trajectory_msgs.msg import (
00051     JointTrajectoryPoint,
00052 )
00053 
00054 import baxter_interface
00055 
00056 from baxter_interface import CHECK_VERSION
00057 
00058 
00059 class Trajectory(object):
00060     def __init__(self):
00061         #create our action server clients
00062         self._left_client = actionlib.SimpleActionClient(
00063             'robot/limb/left/follow_joint_trajectory',
00064             FollowJointTrajectoryAction,
00065         )
00066         self._right_client = actionlib.SimpleActionClient(
00067             'robot/limb/right/follow_joint_trajectory',
00068             FollowJointTrajectoryAction,
00069         )
00070 
00071         #verify joint trajectory action servers are available
00072         l_server_up = self._left_client.wait_for_server(rospy.Duration(10.0))
00073         r_server_up = self._right_client.wait_for_server(rospy.Duration(10.0))
00074         if not l_server_up or not r_server_up:
00075             msg = ("Action server not available."
00076                    " Verify action server availability.")
00077             rospy.logerr(msg)
00078             rospy.signal_shutdown(msg)
00079             sys.exit(1)
00080         #create our goal request
00081         self._l_goal = FollowJointTrajectoryGoal()
00082         self._r_goal = FollowJointTrajectoryGoal()
00083 
00084         #limb interface - current angles needed for start move
00085         self._l_arm = baxter_interface.Limb('left')
00086         self._r_arm = baxter_interface.Limb('right')
00087 
00088         #gripper interface - for gripper command playback
00089         self._l_gripper = baxter_interface.Gripper('left', CHECK_VERSION)
00090         self._r_gripper = baxter_interface.Gripper('right', CHECK_VERSION)
00091 
00092         #flag to signify the arm trajectories have begun executing
00093         self._arm_trajectory_started = False
00094         #reentrant lock to prevent same-thread lockout
00095         self._lock = threading.RLock()
00096 
00097         # Verify Grippers Have No Errors and are Calibrated
00098         if self._l_gripper.error():
00099             self._l_gripper.reset()
00100         if self._r_gripper.error():
00101             self._r_gripper.reset()
00102         if (not self._l_gripper.calibrated() and
00103             self._l_gripper.type() != 'custom'):
00104             self._l_gripper.calibrate()
00105         if (not self._r_gripper.calibrated() and
00106             self._r_gripper.type() != 'custom'):
00107             self._r_gripper.calibrate()
00108 
00109         #gripper goal trajectories
00110         self._l_grip = FollowJointTrajectoryGoal()
00111         self._r_grip = FollowJointTrajectoryGoal()
00112 
00113         # Timing offset to prevent gripper playback before trajectory has started
00114         self._slow_move_offset = 0.0
00115         self._trajectory_start_offset = rospy.Duration(0.0)
00116         self._trajectory_actual_offset = rospy.Duration(0.0)
00117 
00118         #param namespace
00119         self._param_ns = '/rsdk_joint_trajectory_action_server/'
00120 
00121         #gripper control rate
00122         self._gripper_rate = 20.0  # Hz
00123 
00124     def _execute_gripper_commands(self):
00125         start_time = rospy.get_time() - self._trajectory_actual_offset.to_sec()
00126         r_cmd = self._r_grip.trajectory.points
00127         l_cmd = self._l_grip.trajectory.points
00128         pnt_times = [pnt.time_from_start.to_sec() for pnt in r_cmd]
00129         end_time = pnt_times[-1]
00130         rate = rospy.Rate(self._gripper_rate)
00131         now_from_start = rospy.get_time() - start_time
00132         while(now_from_start < end_time + (1.0 / self._gripper_rate) and
00133               not rospy.is_shutdown()):
00134             idx = bisect(pnt_times, now_from_start) - 1
00135             if self._r_gripper.type() != 'custom':
00136                 self._r_gripper.command_position(r_cmd[idx].positions[0])
00137             if self._l_gripper.type() != 'custom':
00138                 self._l_gripper.command_position(l_cmd[idx].positions[0])
00139             rate.sleep()
00140             now_from_start = rospy.get_time() - start_time
00141 
00142     def _clean_line(self, line, joint_names):
00143         """
00144         Cleans a single line of recorded joint positions
00145 
00146         @param line: the line described in a list to process
00147         @param joint_names: joint name keys
00148 
00149         @return command: returns dictionary {joint: value} of valid commands
00150         @return line: returns list of current line values stripped of commas
00151         """
00152         def try_float(x):
00153             try:
00154                 return float(x)
00155             except ValueError:
00156                 return None
00157         #convert the line of strings to a float or None
00158         line = [try_float(x) for x in line.rstrip().split(',')]
00159         #zip the values with the joint names
00160         combined = zip(joint_names[1:], line[1:])
00161         #take out any tuples that have a none value
00162         cleaned = [x for x in combined if x[1] is not None]
00163         #convert it to a dictionary with only valid commands
00164         command = dict(cleaned)
00165         return (command, line,)
00166 
00167     def _add_point(self, positions, side, time):
00168         """
00169         Appends trajectory with new point
00170 
00171         @param positions: joint positions
00172         @param side: limb to command point
00173         @param time: time from start for point in seconds
00174         """
00175         #creates a point in trajectory with time_from_start and positions
00176         point = JointTrajectoryPoint()
00177         point.positions = copy(positions)
00178         point.time_from_start = rospy.Duration(time)
00179         if side == 'left':
00180             self._l_goal.trajectory.points.append(point)
00181         elif side == 'right':
00182             self._r_goal.trajectory.points.append(point)
00183         elif side == 'left_gripper':
00184             self._l_grip.trajectory.points.append(point)
00185         elif side == 'right_gripper':
00186             self._r_grip.trajectory.points.append(point)
00187 
00188     def parse_file(self, filename):
00189         """
00190         Parses input file into FollowJointTrajectoryGoal format
00191 
00192         @param filename: input filename
00193         """
00194         #open recorded file
00195         with open(filename, 'r') as f:
00196             lines = f.readlines()
00197         #read joint names specified in file
00198         joint_names = lines[0].rstrip().split(',')
00199         #parse joint names for the left and right limbs
00200         for name in joint_names:
00201             if 'left' == name[:-3]:
00202                 self._l_goal.trajectory.joint_names.append(name)
00203             elif 'right' == name[:-3]:
00204                 self._r_goal.trajectory.joint_names.append(name)
00205 
00206         def find_start_offset(pos):
00207             #create empty lists
00208             cur = []
00209             cmd = []
00210             dflt_vel = []
00211             vel_param = self._param_ns + "%s_default_velocity"
00212             #for all joints find our current and first commanded position
00213             #reading default velocities from the parameter server if specified
00214             for name in joint_names:
00215                 if 'left' == name[:-3]:
00216                     cmd.append(pos[name])
00217                     cur.append(self._l_arm.joint_angle(name))
00218                     prm = rospy.get_param(vel_param % name, 0.25)
00219                     dflt_vel.append(prm)
00220                 elif 'right' == name[:-3]:
00221                     cmd.append(pos[name])
00222                     cur.append(self._r_arm.joint_angle(name))
00223                     prm = rospy.get_param(vel_param % name, 0.25)
00224                     dflt_vel.append(prm)
00225             diffs = map(operator.sub, cmd, cur)
00226             diffs = map(operator.abs, diffs)
00227             #determine the largest time offset necessary across all joints
00228             offset = max(map(operator.div, diffs, dflt_vel))
00229             return offset
00230 
00231         for idx, values in enumerate(lines[1:]):
00232             #clean each line of file
00233             cmd, values = self._clean_line(values, joint_names)
00234             #find allowable time offset for move to start position
00235             if idx == 0:
00236                 # Set the initial position to be the current pose.
00237                 # This ensures we move slowly to the starting point of the
00238                 # trajectory from the current pose - The user may have moved
00239                 # arm since recording
00240                 cur_cmd = [self._l_arm.joint_angle(jnt) for jnt in self._l_goal.trajectory.joint_names]
00241                 self._add_point(cur_cmd, 'left', 0.0)
00242                 cur_cmd = [self._r_arm.joint_angle(jnt) for jnt in self._r_goal.trajectory.joint_names]
00243                 self._add_point(cur_cmd, 'right', 0.0)
00244                 start_offset = find_start_offset(cmd)
00245                 # Gripper playback won't start until the starting movement's
00246                 # duration has passed, and the actual trajectory playback begins
00247                 self._slow_move_offset = start_offset
00248                 self._trajectory_start_offset = rospy.Duration(start_offset + values[0])
00249             #add a point for this set of commands with recorded time
00250             cur_cmd = [cmd[jnt] for jnt in self._l_goal.trajectory.joint_names]
00251             self._add_point(cur_cmd, 'left', values[0] + start_offset)
00252             cur_cmd = [cmd[jnt] for jnt in self._r_goal.trajectory.joint_names]
00253             self._add_point(cur_cmd, 'right', values[0] + start_offset)
00254             cur_cmd = [cmd['left_gripper']]
00255             self._add_point(cur_cmd, 'left_gripper', values[0] + start_offset)
00256             cur_cmd = [cmd['right_gripper']]
00257             self._add_point(cur_cmd, 'right_gripper', values[0] + start_offset)
00258 
00259     def _feedback(self, data):
00260         # Test to see if the actual playback time has exceeded
00261         # the move-to-start-pose timing offset
00262         if (not self._get_trajectory_flag() and
00263               data.actual.time_from_start >= self._trajectory_start_offset):
00264             self._set_trajectory_flag(value=True)
00265             self._trajectory_actual_offset = data.actual.time_from_start
00266 
00267     def _set_trajectory_flag(self, value=False):
00268         with self._lock:
00269             # Assign a value to the flag
00270             self._arm_trajectory_started = value
00271 
00272     def _get_trajectory_flag(self):
00273         temp_flag = False
00274         with self._lock:
00275             # Copy to external variable
00276             temp_flag = self._arm_trajectory_started
00277         return temp_flag
00278 
00279     def start(self):
00280         """
00281         Sends FollowJointTrajectoryAction request
00282         """
00283         self._left_client.send_goal(self._l_goal, feedback_cb=self._feedback)
00284         self._right_client.send_goal(self._r_goal, feedback_cb=self._feedback)
00285         # Syncronize playback by waiting for the trajectories to start
00286         while not rospy.is_shutdown() and not self._get_trajectory_flag():
00287             rospy.sleep(0.05)
00288         self._execute_gripper_commands()
00289 
00290     def stop(self):
00291         """
00292         Preempts trajectory execution by sending cancel goals
00293         """
00294         if (self._left_client.gh is not None and
00295             self._left_client.get_state() == actionlib.GoalStatus.ACTIVE):
00296             self._left_client.cancel_goal()
00297 
00298         if (self._right_client.gh is not None and
00299             self._right_client.get_state() == actionlib.GoalStatus.ACTIVE):
00300             self._right_client.cancel_goal()
00301 
00302         #delay to allow for terminating handshake
00303         rospy.sleep(0.1)
00304 
00305     def wait(self):
00306         """
00307         Waits for and verifies trajectory execution result
00308         """
00309         #create a timeout for our trajectory execution
00310         #total time trajectory expected for trajectory execution plus a buffer
00311         last_time = self._r_goal.trajectory.points[-1].time_from_start.to_sec()
00312         time_buffer = rospy.get_param(self._param_ns + 'goal_time', 0.0) + 1.5
00313         timeout = rospy.Duration(self._slow_move_offset +
00314                                  last_time +
00315                                  time_buffer)
00316 
00317         l_finish = self._left_client.wait_for_result(timeout)
00318         r_finish = self._right_client.wait_for_result(timeout)
00319         l_result = (self._left_client.get_result().error_code == 0)
00320         r_result = (self._right_client.get_result().error_code == 0)
00321 
00322         #verify result
00323         if all([l_finish, r_finish, l_result, r_result]):
00324             return True
00325         else:
00326             msg = ("Trajectory action failed or did not finish before "
00327                    "timeout/interrupt.")
00328             rospy.logwarn(msg)
00329             return False
00330 
00331 
00332 def main():
00333     """RSDK Joint Trajectory Example: File Playback
00334 
00335     Plays back joint positions honoring timestamps recorded
00336     via the joint_recorder example.
00337 
00338     Run the joint_recorder.py example first to create a recording
00339     file for use with this example. Then make sure to start the
00340     joint_trajectory_action_server before running this example.
00341 
00342     This example will use the joint trajectory action server
00343     with velocity control to follow the positions and times of
00344     the recorded motion, accurately replicating movement speed
00345     necessary to hit each trajectory point on time.
00346     """
00347     epilog = """
00348 Related examples:
00349   joint_recorder.py; joint_position_file_playback.py.
00350     """
00351     arg_fmt = argparse.RawDescriptionHelpFormatter
00352     parser = argparse.ArgumentParser(formatter_class=arg_fmt,
00353                                      description=main.__doc__,
00354                                      epilog=epilog)
00355     parser.add_argument(
00356         '-f', '--file', metavar='PATH', required=True,
00357         help='path to input file'
00358     )
00359     parser.add_argument(
00360         '-l', '--loops', type=int, default=1,
00361         help='number of playback loops. 0=infinite.'
00362     )
00363     # remove ROS args and filename (sys.arv[0]) for argparse
00364     args = parser.parse_args(rospy.myargv()[1:])
00365 
00366     print("Initializing node... ")
00367     rospy.init_node("rsdk_joint_trajectory_file_playback")
00368     print("Getting robot state... ")
00369     rs = baxter_interface.RobotEnable(CHECK_VERSION)
00370     print("Enabling robot... ")
00371     rs.enable()
00372     print("Running. Ctrl-c to quit")
00373 
00374     traj = Trajectory()
00375     traj.parse_file(path.expanduser(args.file))
00376     #for safe interrupt handling
00377     rospy.on_shutdown(traj.stop)
00378     result = True
00379     loop_cnt = 1
00380     loopstr = str(args.loops)
00381     if args.loops == 0:
00382         args.loops = float('inf')
00383         loopstr = "forever"
00384     while (result == True and loop_cnt <= args.loops
00385            and not rospy.is_shutdown()):
00386         print("Playback loop %d of %s" % (loop_cnt, loopstr,))
00387         traj.start()
00388         result = traj.wait()
00389         loop_cnt = loop_cnt + 1
00390     print("Exiting - File Playback Complete")
00391 
00392 if __name__ == "__main__":
00393     main()


baxter_examples
Author(s): Rethink Robotics Inc.
autogenerated on Sat Jun 8 2019 20:16:28