00001
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
00016 from sensor_msgs.msg import LaserScan
00017
00018 class MoveBaseStraightAction(object):
00019
00020 FREQ = 10
00021
00022 def __init__(self, name):
00023
00024
00025 self.MAX_SPEED = rospy.get_param('~max_speed', 0.2)
00026 self.MIN_SPEED = rospy.get_param('~min_speed', 0.05)
00027
00028
00029
00030 self.ANGULAR_SPEED_GAIN = rospy.get_param('~angular_speed', 0.6)
00031
00032
00033 self.GOAL_THRESHOLD = rospy.get_param('~goal_threshold', 0.02)
00034
00035
00036
00037 self.GOAL_THRESHOLD_ACCEPTABLE = rospy.get_param('~goal_threshold_acceptable', 0.2)
00038
00039 self.YAW_GOAL_TOLERANCE = rospy.get_param('~yaw_goal_tolerance', 0.2)
00040
00041
00042 self.RANGE_MINIMUM = rospy.get_param('~range_minimum', 0.5)
00043
00044
00045
00046 self.SLOWDOWN_RANGE = rospy.get_param('~slowdown_range', 0.5)
00047
00048
00049
00050
00051 self.REQUIRED_APERTURE = rospy.get_param('~required_aperture', np.pi * 0.75)
00052
00053
00054 self.HOLONOMIC = rospy.get_param('~holonomic', False)
00055
00056
00057 self.GOAL_TOPIC_NAME = rospy.get_param('~goal_topic_name', None)
00058
00059
00060 self.action_name = name
00061 self.tf_listener = tf.TransformListener()
00062 self.cmd_vel_pub = rospy.Publisher('base_controller/command', Twist, queue_size=1)
00063
00064 self.speed_multiplier = 1.0
00065
00066
00067 self.scan = None
00068 self.laser_base_offset = None
00069 rospy.Subscriber("/base_scan", LaserScan, self.laser_cb)
00070
00071 self.footprint_frame = rospy.get_param('~footprint_frame', '/base_footprint')
00072
00073
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 blocked(self, target_angle, dist):
00095
00096 blocked = False
00097 block_reason = None
00098
00099 self.speed_multiplier = min(1.0, (dist - self.GOAL_THRESHOLD) / self.SLOWDOWN_RANGE)
00100 laser_angle = self.scan.angle_min
00101 for laser_distance in self.scan.ranges:
00102 (base_distance, base_angle) = self.laser_to_base(laser_distance, laser_angle)
00103 angle_diff = abs(base_angle - target_angle)
00104 will_get_closer = angle_diff < (np.pi / 2.0)
00105 is_too_close = (base_distance < self.RANGE_MINIMUM) and (laser_distance > self.scan.range_min)
00106 if will_get_closer:
00107 self.speed_multiplier = min(self.speed_multiplier, (base_distance - self.RANGE_MINIMUM) / self.SLOWDOWN_RANGE)
00108 if is_too_close and will_get_closer:
00109 blocked = True
00110 block_reason = (base_distance, base_angle, angle_diff)
00111 break
00112 laser_angle += self.scan.angle_increment
00113 if blocked:
00114 rospy.logwarn('%s: Blocked! Reason: Distance %s at angle %s (%s angle difference to goal direction)' % (self.action_name, block_reason[0], block_reason[1], block_reason[2]))
00115 return blocked
00116
00117
00118 def translate_towards_goal_holonomically(self, x_diff, y_diff, dist):
00119 drive_speed = max(self.MAX_SPEED * self.speed_multiplier, self.MIN_SPEED)
00120 cmd = Twist()
00121 cmd.linear.x = (x_diff / dist) * drive_speed
00122 cmd.linear.y = (y_diff / dist) * drive_speed
00123 self.cmd_vel_pub.publish(cmd)
00124
00125
00126 def translate_towards_goal(self, target_angle):
00127
00128 cmd = Twist()
00129 cmd.linear.x = max(self.MAX_SPEED * self.speed_multiplier, self.MIN_SPEED)
00130 cmd.angular.z = target_angle * self.ANGULAR_SPEED_GAIN
00131 self.cmd_vel_pub.publish(cmd)
00132
00133 def rotate_in_place(self, direction):
00134
00135 cmd = Twist()
00136 if (direction > 0):
00137 cmd.angular.z = 0.2
00138 else:
00139 cmd.angular.z = -0.2
00140 self.cmd_vel_pub.publish(cmd)
00141
00142 def laser_cb(self, scan):
00143 if not self.laser_base_offset:
00144
00145 laser_frame = scan.header.frame_id
00146 while not rospy.is_shutdown():
00147 try:
00148 self.tf_listener.waitForTransform(self.footprint_frame, laser_frame, rospy.Time(), rospy.Duration(1.0))
00149 self.laser_base_offset = self.tf_listener.lookupTransform(self.footprint_frame, laser_frame, rospy.Time())[0][0]
00150 break
00151 except (tf.Exception) as e:
00152 rospy.logwarn("MoveBaseBlind tf exception! Message: %s" % e.message)
00153 rospy.sleep(0.1)
00154 continue
00155
00156 self.scan = scan
00157
00158
00159 def manual_cb(self, target_pose):
00160 goal = MoveBaseGoal(target_pose=target_pose)
00161 self.client.send_goal(goal)
00162
00163 def execute_cb(self, goal):
00164 if not self.scan or not self.laser_base_offset:
00165 rospy.logwarn('No messages received yet on topic %s, aborting goal!' % rospy.resolve_name('base_scan'))
00166 self.action_server.set_aborted()
00167 return
00168
00169
00170 target_pose = goal.target_pose
00171
00172
00173 rospy.loginfo('%s: Executing, moving to position: (%f, %f)' % (self.action_name, target_pose.pose.position.x, target_pose.pose.position.y))
00174
00175 rate = rospy.Rate(hz = MoveBaseStraightAction.FREQ)
00176 while not rospy.is_shutdown():
00177 rate.sleep()
00178
00179 while not rospy.is_shutdown() and not self.action_server.is_preempt_requested():
00180 try:
00181
00182
00183 target_pose.header.stamp = rospy.Time.now()
00184
00185 self.tf_listener.waitForTransform('/base_footprint',
00186 target_pose.header.frame_id,
00187 target_pose.header.stamp, rospy.Duration(1.0))
00188 target_pose_transformed = self.tf_listener.transformPose('/base_footprint', target_pose)
00189 break
00190 except (tf.Exception) as e:
00191 rospy.logwarn("MoveBaseBlind tf exception! Message: %s" % e.message)
00192 rospy.sleep(0.1)
00193 continue
00194
00195
00196 if self.action_server.is_preempt_requested():
00197 rospy.loginfo('%s: Preempted' % self.action_name)
00198 self.action_server.set_preempted()
00199 break
00200
00201 x_diff = target_pose_transformed.pose.position.x
00202 y_diff = target_pose_transformed.pose.position.y
00203 dist = np.sqrt(x_diff ** 2 + y_diff ** 2)
00204
00205 euler = tf.transformations.euler_from_quaternion([target_pose_transformed.pose.orientation.x,
00206 target_pose_transformed.pose.orientation.y, target_pose_transformed.pose.orientation.z,
00207 target_pose_transformed.pose.orientation.w])
00208
00209 target_angle = np.arctan2(target_pose_transformed.pose.position.y,
00210 target_pose_transformed.pose.position.x)
00211
00212
00213
00214 if self.blocked(target_angle, dist):
00215
00216 self.cmd_vel_pub.publish(Twist())
00217 if(dist <= self.GOAL_THRESHOLD_ACCEPTABLE):
00218 rospy.loginfo('%s: Succeeded (blocked, but closer to goal than acceptable goal threshold)' % self.action_name)
00219 self.action_server.set_succeeded()
00220 else:
00221 rospy.logwarn('%s: Aborted' % self.action_name)
00222 self.action_server.set_aborted()
00223 break
00224
00225
00226 if ((target_angle - self.REQUIRED_APERTURE/2) < self.scan.angle_min or
00227 (target_angle + self.REQUIRED_APERTURE/2) > self.scan.angle_max):
00228
00229 pass
00230
00231
00232
00233 if (self.HOLONOMIC and dist > self.GOAL_THRESHOLD):
00234 self.translate_towards_goal_holonomically(x_diff, y_diff, dist)
00235
00236
00237 elif (dist > self.GOAL_THRESHOLD):
00238 self.translate_towards_goal(target_angle)
00239
00240
00241 elif (abs(euler[2]) > self.YAW_GOAL_TOLERANCE):
00242 self.rotate_in_place(euler[2])
00243
00244
00245 else:
00246
00247 self.cmd_vel_pub.publish(Twist())
00248 rospy.loginfo('%s: Succeeded' % self.action_name)
00249 self.action_server.set_succeeded()
00250 break
00251
00252 if __name__ == '__main__':
00253 rospy.init_node('move_base_straight')
00254 MoveBaseStraightAction(rospy.get_name())
00255 rospy.spin()