move_base_straight.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 
00003 '''
00004 Created on 01.08.2013
00005 
00006 @author: Thorsten Gedicke
00007 '''
00008 
00009 import roslib; roslib.load_manifest('move_base_straight')
00010 import rospy
00011 import numpy as np
00012 import tf
00013 import actionlib
00014 from geometry_msgs.msg import PoseStamped, Twist
00015 from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal, MoveBaseResult
00016 from sensor_msgs.msg import LaserScan
00017 
00018 class MoveBaseStraightAction(object):
00019 
00020     FREQ = 10
00021 
00022     def __init__(self, name):
00023         # Get parameters -------------------------------------------------------
00024         # Max and min speeds
00025         self.MAX_SPEED = rospy.get_param('~max_speed', 0.2) # [m/s]
00026         self.MIN_SPEED = rospy.get_param('~min_speed', 0.05) # [m/s]
00027 
00028         # Tolerance for goal approach (we stop when distance is below this)
00029         self.GOAL_THRESHOLD = rospy.get_param('~goal_threshold', 0.02) # [m]
00030 
00031         # Tolerance for movement success (action returns aborted if
00032         # distance to goal is still above this after we stopped)
00033         self.GOAL_THRESHOLD_ACCEPTABLE = rospy.get_param('~goal_threshold_acceptable', 0.2) # [m]
00034 
00035         # Minimal safety radius around base footprint
00036         self.RANGE_MINIMUM = rospy.get_param('~range_minimum', 0.5) # [m]
00037 
00038         # We start to slow down if distance to goal or obstacle distance to
00039         # RANGE_MINIMUM is below SLOWDOWN_RANGE.
00040         self.SLOWDOWN_RANGE = rospy.get_param('~slowdown_range', 0.5) # [m]
00041 
00042         # Scanned aperture around the goal for a movement to be cosidered safe.
00043         # At this time, there is no effect for this parameter since nothing
00044         # special is done for unsafe movements.
00045         self.REQUIRED_APERTURE = rospy.get_param('~required_aperture', np.pi * 0.75) # [rad]
00046 
00047         # We can also listen for PoseStamped targets on some topic.
00048         self.GOAL_TOPIC_NAME = rospy.get_param('~goal_topic_name', None)
00049         # ----------------------------------------------------------------------
00050 
00051         self.action_name = name
00052         self.tf_listener = tf.TransformListener()
00053         self.cmd_vel_pub = rospy.Publisher('base_controller/command', Twist)
00054 
00055         # Subscribe to base_scan
00056         self.scan = None
00057         rospy.Subscriber("/base_scan", LaserScan, self.laser_cb)
00058 
00059         footprint_frame = rospy.get_param('~footprint_frame', '/base_footprint')
00060         laser_frame = rospy.get_param('~laser_frame', '/base_laser_link')
00061 
00062         # Get base laser to base footprint frame offset
00063         while not rospy.is_shutdown():
00064             try:
00065                 self.tf_listener.waitForTransform(footprint_frame, laser_frame, rospy.Time(), rospy.Duration(1.0))
00066                 self.LASER_BASE_OFFSET = self.tf_listener.lookupTransform(footprint_frame, laser_frame, rospy.Time())[0][0]
00067                 break
00068             except (tf.LookupException, tf.ConnectivityException) as e:
00069                 rospy.logwarn("MoveBaseBlind tf exception! Message: %s" % e.message)
00070                 rospy.sleep(0.1)
00071                 continue
00072 
00073         # Set up action server
00074         self.action_server = actionlib.SimpleActionServer(self.action_name, MoveBaseAction, execute_cb=self.execute_cb, auto_start=False)
00075         self.action_server.start()
00076         rospy.loginfo('%s: Action server up and running...' % (self.action_name))
00077 
00078         if self.GOAL_TOPIC_NAME:
00079             self.client = actionlib.SimpleActionClient('move_base_straight', MoveBaseAction)
00080             self.client.wait_for_server()
00081             rospy.Subscriber(self.GOAL_TOPIC_NAME, PoseStamped, self.manual_cb)
00082             rospy.loginfo('%s: Manual control topic is: %s' % (self.action_name, self.GOAL_TOPIC_NAME))
00083 
00084     def laser_to_base(self, distance_laser, angle_laser):
00085         """
00086         Uses the laws of sines and cosines to transform range/angle pairs in the
00087         base laser frame to range/angle pairs relative to the base footprint frame.
00088         """
00089         gamma = np.pi - angle_laser
00090         distance_base = np.sqrt(self.LASER_BASE_OFFSET**2 + distance_laser**2 - 2 * self.LASER_BASE_OFFSET * distance_laser * np.cos(gamma))
00091         angle_base = np.arcsin(distance_laser * np.sin(gamma) / distance_base)
00092         return (distance_base, angle_base)
00093 
00094     def laser_cb(self, scan):
00095         self.scan = scan
00096 
00097     def manual_cb(self, target_pose):
00098         goal = MoveBaseGoal(target_pose=target_pose)
00099         self.client.send_goal(goal)
00100 
00101     def execute_cb(self, goal):
00102         if not self.scan:
00103             rospy.logwarn('No messages received yet on topic %s, aborting goal!' % rospy.resolve_name('base_scan'))
00104             self.action_server.set_aborted()
00105             return
00106 
00107         # helper variables
00108         success = False
00109         target_pose = goal.target_pose
00110 
00111         # publish info to the console for the user
00112         rospy.loginfo('%s: Executing, moving to position: (%f, %f)' % (self.action_name, target_pose.pose.position.x, target_pose.pose.position.y))
00113 
00114         rate = rospy.Rate(hz = MoveBaseStraightAction.FREQ)
00115         while not rospy.is_shutdown():
00116             rate.sleep()
00117             # get transform relative to /base_footprint (--> target_pose_transformed)
00118             while not rospy.is_shutdown() and not self.action_server.is_preempt_requested():
00119                 try:
00120                     # set the time stamp to now so we actually check for the
00121                     # same transform in waitForTransform and transformPose.
00122                     target_pose.header.stamp = rospy.Time.now()
00123 
00124                     self.tf_listener.waitForTransform('/base_footprint',
00125                             target_pose.header.frame_id,
00126                             target_pose.header.stamp, rospy.Duration(1.0))
00127                     target_pose_transformed = self.tf_listener.transformPose('/base_footprint', target_pose)
00128                     break
00129                 except (tf.LookupException, tf.ConnectivityException) as e:
00130                     rospy.logwarn("MoveBaseBlind tf exception! Message: %s" % e.message)
00131                     rospy.sleep(0.1)
00132                     continue
00133 
00134             # check that preempt has not been requested by the client
00135             if self.action_server.is_preempt_requested():
00136                 rospy.loginfo('%s: Preempted' % self.action_name)
00137                 self.action_server.set_preempted()
00138                 break
00139 
00140             x_diff = target_pose_transformed.pose.position.x
00141             y_diff = target_pose_transformed.pose.position.y
00142             dist = np.sqrt(x_diff ** 2 + y_diff ** 2)
00143 
00144             # Goal reached?
00145             if (dist < self.GOAL_THRESHOLD):
00146                 # send command to stop
00147                 self.cmd_vel_pub.publish(Twist())
00148                 rospy.loginfo('%s: Succeeded' % self.action_name)
00149                 self.action_server.set_succeeded()
00150                 break
00151 
00152             # Something in the way?
00153             target_angle = np.arctan2(target_pose_transformed.pose.position.y,
00154                                       target_pose_transformed.pose.position.x)
00155             # target_angle is 0 in the center of the laser scan
00156             blocked = False
00157             block_reason = None
00158             speed_multiplier = min(1.0, (dist - self.GOAL_THRESHOLD) / self.SLOWDOWN_RANGE) # used to reduce speed for goal/obstacle approach
00159             laser_angle = self.scan.angle_min
00160             for laser_distance in self.scan.ranges:
00161                 (base_distance, base_angle) = self.laser_to_base(laser_distance, laser_angle)
00162                 angle_diff = abs(base_angle - target_angle)
00163                 will_get_closer = angle_diff < (np.pi / 2.0)
00164                 is_too_close = (base_distance < self.RANGE_MINIMUM) and (laser_distance > self.scan.range_min)
00165                 if will_get_closer:
00166                     speed_multiplier = min(speed_multiplier, (base_distance - self.RANGE_MINIMUM) / self.SLOWDOWN_RANGE)
00167                 if is_too_close and will_get_closer:
00168                     blocked = True
00169                     block_reason = (base_distance, base_angle, angle_diff)
00170                     break;
00171                 laser_angle += self.scan.angle_increment
00172             if blocked:
00173                 # send command to stop
00174                 self.cmd_vel_pub.publish(Twist())
00175                 rospy.logwarn('%s: Blocked! Distance to goal: %s m. Critical object distance of %s m at angle %s (%s angle difference to goal direction)' % (self.action_name, dist, block_reason[0], block_reason[1], block_reason[2]))
00176                 if(dist <= self.GOAL_THRESHOLD_ACCEPTABLE):
00177                     rospy.logwarn('%s: Succeeded' % self.action_name)
00178                     self.action_server.set_succeeded()
00179                 else:
00180                     rospy.logwarn('%s: Aborted' % self.action_name)
00181                     self.action_server.set_aborted()
00182                 break
00183 
00184             # Can we see enough?
00185             if ((target_angle - self.REQUIRED_APERTURE/2) < self.scan.angle_min or
00186                 (target_angle + self.REQUIRED_APERTURE/2) > self.scan.angle_max):
00187                 # Driving blind (maybe activate warning signals here)
00188                 pass
00189 
00190             # Drive towards goal!
00191             drive_speed = max(self.MAX_SPEED * speed_multiplier, self.MIN_SPEED)
00192             cmd = Twist()
00193             cmd.linear.x = (x_diff / dist) * drive_speed
00194             cmd.linear.y = (y_diff / dist) * drive_speed
00195             self.cmd_vel_pub.publish(cmd)
00196 
00197 
00198 if __name__ == '__main__':
00199   rospy.init_node('move_base_straight')
00200   MoveBaseStraightAction(rospy.get_name())
00201   rospy.spin()


move_base_straight
Author(s): Thorsten Gedicke
autogenerated on Mon Oct 6 2014 12:20:45