Autonomous Racing  1
f1tenth Project Group of Technical University Dortmund, Germany
wallfollowing.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import rospy
4 from std_msgs.msg import ColorRGBA
5 from sensor_msgs.msg import LaserScan
6 from drive_msgs.msg import drive_param
7 
8 from rviz_geometry import show_circle_in_rviz, show_line_in_rviz
9 
10 from circle import Circle, Point
11 
12 import math
13 
14 import numpy as np
15 
16 from dynamic_reconfigure.server import Server
17 from wallfollowing2.cfg import wallfollowing2Config
18 
19 TOPIC_DRIVE_PARAMETERS = "/input/drive_param/autonomous"
20 TOPIC_LASER_SCAN = "/scan"
21 
22 last_speed = 0
23 
24 
25 class Parameters():
26  def __init__(self, default_values):
27  self.names = default_values.keys()
28  for name in self.names:
29  setattr(self, name, default_values[name])
30 
31  def __str__(self):
32  return '\n'.join(name + ": " + str(getattr(self, name))
33  for name in self.names)
34 
35 
36 class PIDController():
37  def __init__(self, p, i, d, anti_windup=0.2):
38  self.p = p
39  self.i = i
40  self.d = d
41  self.anti_windup = anti_windup
42 
43  self.integral = 0
44  self.previous_error = 0
45 
46  def update_and_get_correction(self, error, delta_time):
47  self.integral += error * delta_time
48  if self.integral > self.anti_windup:
49  self.integral = self.anti_windup
50  elif self.integral < -self.anti_windup:
51  self.integral = -self.anti_windup
52 
53  derivative = (error - self.previous_error) / delta_time
54  self.previous_error = error
55  return self.p * error + self.i * self.integral + self.d * derivative
56 
57 
58 def map(in_lower, in_upper, out_lower, out_upper, value):
59  result = out_lower + (out_upper - out_lower) * \
60  (value - in_lower) / (in_upper - in_lower)
61  return min(out_upper, max(out_lower, result))
62 
63 
64 def drive(angle, velocity):
65  message = drive_param()
66  message.angle = angle
67  message.velocity = velocity
68  drive_parameters_publisher.publish(message)
69 
70 
71 def get_scan_as_cartesian(laser_scan):
72  ranges = np.array(laser_scan.ranges)
73 
74  angles = np.linspace(
75  laser_scan.angle_min,
76  laser_scan.angle_max,
77  ranges.shape[0])
78 
79  laser_range = laser_scan.angle_max - laser_scan.angle_min
80  usable_range = math.radians(parameters.usable_laser_range)
81  if usable_range < laser_range:
82  skip_left = int((-laser_scan.angle_min - usable_range / 2) / laser_range * ranges.shape[0]) # nopep8
83  skip_right = int((laser_scan.angle_max - usable_range / 2) / laser_range * ranges.shape[0]) # nopep8
84  angles = angles[skip_left:-1 - skip_right]
85  ranges = ranges[skip_left:-1 - skip_right]
86 
87  inf_mask = np.isinf(ranges)
88  if inf_mask.any():
89  ranges = ranges[~inf_mask]
90  angles = angles[~inf_mask]
91 
92  points = np.zeros((ranges.shape[0], 2))
93  points[:, 0] = -np.sin(angles) * ranges
94  points[:, 1] = np.cos(angles) * ranges
95 
96  return points
97 
98 
99 def find_left_right_border(points, margin_relative=0.1):
100  margin = int(points.shape[0] * margin_relative)
101 
102  relative = points[margin + 1:-margin, :] - points[margin:-margin - 1, :]
103  distances = np.linalg.norm(relative, axis=1)
104 
105  return margin + np.argmax(distances) + 1
106 
107 
108 def follow_walls(left_circle, right_circle, barrier, delta_time):
109  global last_speed
110 
111  prediction_distance = parameters.corner_cutting + \
112  parameters.straight_smoothing * last_speed
113 
114  predicted_car_position = Point(0, prediction_distance)
115  left_point = left_circle.get_closest_point(predicted_car_position)
116  right_point = right_circle.get_closest_point(predicted_car_position)
117 
118  target_position = Point(
119  (left_point.x + right_point.x) / 2,
120  (left_point.y + right_point.y) / 2)
121  error = (target_position.x - predicted_car_position.x) / \
122  prediction_distance
123  if math.isnan(error) or math.isinf(error):
124  error = 0
125 
126  steering_angle = pid.update_and_get_correction(
127  error, delta_time)
128 
129  radius = min(left_circle.radius, right_circle.radius)
130  speed_limit_radius = map(parameters.radius_lower, parameters.radius_upper, 0, 1, radius) # nopep8
131  speed_limit_error = max(0, 1 + parameters.steering_slow_down_dead_zone - abs(error) * parameters.steering_slow_down) # nopep8
132  speed_limit_acceleration = last_speed + parameters.max_acceleration * delta_time
133  speed_limit_barrier = map(parameters.barrier_lower_limit, parameters.barrier_upper_limit, 0, 1, barrier) ** parameters.barrier_exponent # nopep8
134 
135  relative_speed = min(
136  speed_limit_error,
137  speed_limit_radius,
138  speed_limit_acceleration,
139  speed_limit_barrier
140  )
141  last_speed = relative_speed
142  speed = map(0, 1, parameters.min_throttle, parameters.max_throttle, relative_speed) # nopep8
143  steering_angle = steering_angle * map(parameters.high_speed_steering_limit_dead_zone, 1, 1, parameters.high_speed_steering_limit, relative_speed) # nopep8
144  drive(steering_angle, speed)
145 
146  show_line_in_rviz(2, [left_point, right_point],
147  color=ColorRGBA(1, 1, 1, 0.3), line_width=0.005)
148  show_line_in_rviz(3, [Point(0, 0), predicted_car_position],
149  color=ColorRGBA(1, 1, 1, 0.3), line_width=0.005)
150  show_line_in_rviz(4, [predicted_car_position,
151  target_position], color=ColorRGBA(1, 0.4, 0, 1))
152 
154  5, [Point(-2, barrier), Point(2, barrier)], color=ColorRGBA(1, 1, 0, 1))
155 
156 
157 def handle_scan(laser_scan, delta_time):
158  if parameters is None:
159  return
160 
161  points = get_scan_as_cartesian(laser_scan)
162 
163  if points.shape[0] == 0:
164  rospy.logwarn("Skipping current laser scan message since it contains no finite values.") # nopep8
165  return
166 
167  split = find_left_right_border(points)
168 
169  right_wall = points[:split:4, :]
170  left_wall = points[split::4, :]
171 
172  left_circle = Circle.fit(left_wall)
173  right_circle = Circle.fit(right_wall)
174 
175  barrier_start = int(points.shape[0] * (0.5 - parameters.barrier_size_realtive)) # nopep8
176  barrier_end = int(points.shape[0] * (0.5 + parameters.barrier_size_realtive)) # nopep8
177  barrier = np.max(points[barrier_start: barrier_end, 1])
178 
179  follow_walls(left_circle, right_circle, barrier, delta_time)
180 
181  show_circle_in_rviz(left_circle, left_wall, 0)
182  show_circle_in_rviz(right_circle, right_wall, 1)
183 
184 
185 last_scan = None
186 
187 
188 def laser_callback(scan_message):
189  global last_scan
190 
191  scan_time = scan_message.header.stamp.to_sec()
192  if last_scan is not None and abs(scan_time - last_scan) > 0.0001 and scan_time > last_scan: # nopep8
193  delta_time = scan_time - last_scan
194  handle_scan(scan_message, delta_time)
195 
196  last_scan = scan_time
197 
198 
200  global parameters
201  parameters = Parameters(config)
202  pid.p = parameters.controller_p
203  pid.i = parameters.controller_i
204  pid.d = parameters.controller_d
205  return config
206 
207 
208 rospy.init_node('wallfollowing', anonymous=True)
209 parameters = None
210 pid = PIDController(1, 1, 1)
211 
212 rospy.Subscriber(TOPIC_LASER_SCAN, LaserScan, laser_callback)
213 drive_parameters_publisher = rospy.Publisher(
214  TOPIC_DRIVE_PARAMETERS, drive_param, queue_size=1)
215 
216 Server(wallfollowing2Config, dynamic_configuration_callback)
217 
218 while not rospy.is_shutdown():
219  rospy.spin()
def get_scan_as_cartesian(laser_scan)
def show_line_in_rviz(id, points, color, line_width=0.02)
def dynamic_configuration_callback(config, level)
def handle_scan(laser_scan, delta_time)
def find_left_right_border(points, margin_relative=0.1)
def __init__(self, default_values)
def map(in_lower, in_upper, out_lower, out_upper, value)
Point
Definition: circle.py:8
def show_circle_in_rviz(circle, wall, id)
def drive(angle, velocity)
def update_and_get_correction(self, error, delta_time)
def laser_callback(scan_message)
def follow_walls(left_circle, right_circle, barrier, delta_time)
def __init__(self, p, i, d, anti_windup=0.2)