move_base_straight.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 
3 '''
4 Created on 01.08.2013
5 
6 @author: Thorsten Gedicke
7 '''
8 
9 import roslib; roslib.load_manifest('move_base_straight')
10 import rospy
11 import numpy as np
12 import tf
13 import actionlib
14 from geometry_msgs.msg import PoseStamped, Twist
15 from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
16 from sensor_msgs.msg import LaserScan
17 
18 class MoveBaseStraightAction(object):
19 
20  FREQ = 10
21 
22  def __init__(self, name):
23  # Get parameters -------------------------------------------------------
24  # Max and min speeds
25  self.MAX_SPEED = rospy.get_param('~max_speed', 0.2) # [m/s]
26  self.MIN_SPEED = rospy.get_param('~min_speed', 0.05) # [m/s]
27 
28  # multiplier for turning ratio; e.g., 0.6 = rotate at 60% of the speed
29  # required to fully turn towards the goal in 1 second
30  self.ANGULAR_SPEED_GAIN = rospy.get_param('~angular_speed', 0.6)
31 
32  # Tolerance for goal approach (we stop when distance is below this)
33  self.GOAL_THRESHOLD = rospy.get_param('~goal_threshold', 0.02) # [m]
34 
35  # Tolerance for movement success (action returns aborted if
36  # distance to goal is still above this after we stopped)
37  self.GOAL_THRESHOLD_ACCEPTABLE = rospy.get_param('~goal_threshold_acceptable', 0.2) # [m]
38 
39  self.YAW_GOAL_TOLERANCE = rospy.get_param('~yaw_goal_tolerance', 0.2) # [rad]
40 
41  # Minimal safety radius around base footprint
42  self.RANGE_MINIMUM = rospy.get_param('~range_minimum', 0.5) # [m]
43 
44  # We start to slow down if distance to goal or obstacle distance to
45  # RANGE_MINIMUM is below SLOWDOWN_RANGE.
46  self.SLOWDOWN_RANGE = rospy.get_param('~slowdown_range', 0.5) # [m]
47 
48  # Scanned aperture around the goal for a movement to be cosidered safe.
49  # At this time, there is no effect for this parameter since nothing
50  # special is done for unsafe movements.
51  self.REQUIRED_APERTURE = rospy.get_param('~required_aperture', np.pi * 0.75) # [rad]
52 
53  # Is the robot holonomic?
54  self.HOLONOMIC = rospy.get_param('~holonomic', False)
55 
56  # We can also listen for PoseStamped targets on some topic.
57  self.GOAL_TOPIC_NAME = rospy.get_param('~goal_topic_name', None)
58  # ----------------------------------------------------------------------
59 
60  self.action_name = name
62  self.cmd_vel_pub = rospy.Publisher('base_controller/command', Twist, queue_size=1)
63 
64  self.speed_multiplier = 1.0
65 
66  # Subscribe to base_scan
67  self.scan = None
68  self.laser_base_offset = None # will be initialized from first scan message
69  rospy.Subscriber("/base_scan", LaserScan, self.laser_cb)
70 
71  self.footprint_frame = rospy.get_param('~footprint_frame', '/base_footprint')
72 
73  # Set up action server
74  self.action_server = actionlib.SimpleActionServer(self.action_name, MoveBaseAction, execute_cb=self.execute_cb, auto_start=False)
75  self.action_server.start()
76  rospy.loginfo('%s: Action server up and running...' % (self.action_name))
77 
78  if self.GOAL_TOPIC_NAME:
79  self.client = actionlib.SimpleActionClient('move_base_straight', MoveBaseAction)
80  self.client.wait_for_server()
81  rospy.Subscriber(self.GOAL_TOPIC_NAME, PoseStamped, self.manual_cb)
82  rospy.loginfo('%s: Manual control topic is: %s' % (self.action_name, self.GOAL_TOPIC_NAME))
83 
84  def laser_to_base(self, distance_laser, angle_laser):
85  """
86  Uses the laws of sines and cosines to transform range/angle pairs in the
87  base laser frame to range/angle pairs relative to the base footprint frame.
88  """
89  gamma = np.pi - angle_laser
90  distance_base = np.sqrt(self.laser_base_offset**2 + distance_laser**2 - 2 * self.laser_base_offset * distance_laser * np.cos(gamma))
91  angle_base = np.arcsin(distance_laser * np.sin(gamma) / distance_base)
92  return (distance_base, angle_base)
93 
94  def blocked(self, target_angle, dist):
95  # Something in the way?
96  blocked = False
97  block_reason = None
98  # used to reduce speed for goal/obstacle approach
99  self.speed_multiplier = min(1.0, (dist - self.GOAL_THRESHOLD) / self.SLOWDOWN_RANGE)
100  laser_angle = self.scan.angle_min
101  for laser_distance in self.scan.ranges:
102  (base_distance, base_angle) = self.laser_to_base(laser_distance, laser_angle)
103  angle_diff = abs(base_angle - target_angle)
104  will_get_closer = angle_diff < (np.pi / 2.0)
105  is_too_close = (base_distance < self.RANGE_MINIMUM) and (laser_distance > self.scan.range_min)
106  if will_get_closer:
107  self.speed_multiplier = min(self.speed_multiplier, (base_distance - self.RANGE_MINIMUM) / self.SLOWDOWN_RANGE)
108  if is_too_close and will_get_closer:
109  blocked = True
110  block_reason = (base_distance, base_angle, angle_diff)
111  break
112  laser_angle += self.scan.angle_increment
113  if blocked:
114  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]))
115  return blocked
116 
117 
118  def translate_towards_goal_holonomically(self, x_diff, y_diff, dist):
119  drive_speed = max(self.MAX_SPEED * self.speed_multiplier, self.MIN_SPEED)
120  cmd = Twist()
121  cmd.linear.x = (x_diff / dist) * drive_speed
122  cmd.linear.y = (y_diff / dist) * drive_speed
123  self.cmd_vel_pub.publish(cmd)
124 
125 
126  def translate_towards_goal(self, target_angle):
127  # Drive towards goal
128  cmd = Twist()
129  cmd.linear.x = max(self.MAX_SPEED * self.speed_multiplier, self.MIN_SPEED)
130  cmd.angular.z = target_angle * self.ANGULAR_SPEED_GAIN
131  self.cmd_vel_pub.publish(cmd)
132 
133  def rotate_in_place(self, direction):
134  # Rotate towards goal orientation
135  cmd = Twist()
136  if (direction > 0):
137  cmd.angular.z = 0.2
138  else:
139  cmd.angular.z = -0.2
140  self.cmd_vel_pub.publish(cmd)
141 
142  def laser_cb(self, scan):
143  if not self.laser_base_offset:
144  # Get laser to base_footprint frame offset
145  laser_frame = scan.header.frame_id
146  while not rospy.is_shutdown():
147  try:
148  self.tf_listener.waitForTransform(self.footprint_frame, laser_frame, rospy.Time(), rospy.Duration(1.0))
149  self.laser_base_offset = self.tf_listener.lookupTransform(self.footprint_frame, laser_frame, rospy.Time())[0][0]
150  break
151  except (tf.Exception) as e:
152  rospy.logwarn("MoveBaseBlind tf exception! Message: %s" % e.message)
153  rospy.sleep(0.1)
154  continue
155 
156  self.scan = scan
157 
158 
159  def manual_cb(self, target_pose):
160  goal = MoveBaseGoal(target_pose=target_pose)
161  self.client.send_goal(goal)
162 
163  def execute_cb(self, goal):
164  if not self.scan or not self.laser_base_offset:
165  rospy.logwarn('No messages received yet on topic %s, aborting goal!' % rospy.resolve_name('base_scan'))
166  self.action_server.set_aborted()
167  return
168 
169  # helper variables
170  target_pose = goal.target_pose
171 
172  # publish info to the console
173  rospy.loginfo('%s: Executing, moving to position: (%f, %f)' % (self.action_name, target_pose.pose.position.x, target_pose.pose.position.y))
174 
175  rate = rospy.Rate(hz = MoveBaseStraightAction.FREQ)
176  while not rospy.is_shutdown():
177  rate.sleep()
178  # get transform relative to /base_footprint (--> target_pose_transformed)
179  while not rospy.is_shutdown() and not self.action_server.is_preempt_requested():
180  try:
181  # set the time stamp to now so we actually check for the
182  # same transform in waitForTransform and transformPose.
183  target_pose.header.stamp = rospy.Time.now()
184 
185  self.tf_listener.waitForTransform('/base_footprint',
186  target_pose.header.frame_id,
187  target_pose.header.stamp, rospy.Duration(1.0))
188  target_pose_transformed = self.tf_listener.transformPose('/base_footprint', target_pose)
189  break
190  except (tf.Exception) as e:
191  rospy.logwarn("MoveBaseBlind tf exception! Message: %s" % e.message)
192  rospy.sleep(0.1)
193  continue
194 
195  # check that preempt has not been requested by the client
196  if self.action_server.is_preempt_requested():
197  rospy.loginfo('%s: Preempted' % self.action_name)
198  self.action_server.set_preempted()
199  break
200 
201  x_diff = target_pose_transformed.pose.position.x
202  y_diff = target_pose_transformed.pose.position.y
203  dist = np.sqrt(x_diff ** 2 + y_diff ** 2)
204 
205  euler = tf.transformations.euler_from_quaternion([target_pose_transformed.pose.orientation.x,
206  target_pose_transformed.pose.orientation.y, target_pose_transformed.pose.orientation.z,
207  target_pose_transformed.pose.orientation.w])
208 
209  target_angle = np.arctan2(target_pose_transformed.pose.position.y,
210  target_pose_transformed.pose.position.x)
211  # target_angle is 0 in the center of the laser scan
212 
213  # Check if path is blocked. If so, abort.
214  if self.blocked(target_angle, dist):
215  # send command to stop
216  self.cmd_vel_pub.publish(Twist())
217  if(dist <= self.GOAL_THRESHOLD_ACCEPTABLE):
218  rospy.loginfo('%s: Succeeded (blocked, but closer to goal than acceptable goal threshold)' % self.action_name)
219  self.action_server.set_succeeded()
220  else:
221  rospy.logwarn('%s: Aborted' % self.action_name)
222  self.action_server.set_aborted()
223  break
224 
225  # Can we see enough?
226  if ((target_angle - self.REQUIRED_APERTURE/2) < self.scan.angle_min or
227  (target_angle + self.REQUIRED_APERTURE/2) > self.scan.angle_max):
228  # Driving blind (maybe activate warning signals here)
229  pass
230 
231  # Goal not yet reached?
232  # Translate holonomically
233  if (self.HOLONOMIC and dist > self.GOAL_THRESHOLD):
234  self.translate_towards_goal_holonomically(x_diff, y_diff, dist)
235 
236  # Translate non-holonomically
237  elif (dist > self.GOAL_THRESHOLD):
238  self.translate_towards_goal(target_angle)
239 
240  # If arrived, but yaw error too large, stop and rotate
241  elif (abs(euler[2]) > self.YAW_GOAL_TOLERANCE):
242  self.rotate_in_place(euler[2])
243 
244  # Arrived
245  else:
246  # send command to stop
247  self.cmd_vel_pub.publish(Twist())
248  rospy.loginfo('%s: Succeeded' % self.action_name)
249  self.action_server.set_succeeded()
250  break
251 
252 if __name__ == '__main__':
253  rospy.init_node('move_base_straight')
254  MoveBaseStraightAction(rospy.get_name())
255  rospy.spin()
def translate_towards_goal_holonomically(self, x_diff, y_diff, dist)
def blocked(self, target_angle, dist)
def laser_to_base(self, distance_laser, angle_laser)


move_base_straight
Author(s): Thorsten Gedicke
autogenerated on Mon Jun 10 2019 15:49:14