A small campus shuttle drives at a constant forward speed v (m/s) and steers by choosing its turning rate \omega (rad/s, positive counter-clockwise). It visits the points of waypoints in order, and a PID controller on the heading error chooses \omega.
The pose is (x, y, theta) (metres; theta in radians counter-clockwise from +x). gains = (kp, ki, kd). The controller keeps a running sum I (starting at 0) and the previous error (none at first). The simulation runs in steps of dt seconds. At each position, starting with pose, in this order:
- Arrive. While the current waypoint is within
0.25m of the shuttle (distance<= 0.25), move on to the next waypoint (several may be passed at once). - Stop if every waypoint has been reached, or if
stepssteps have already been taken. - Error. e = the direction from the shuttle to the current waypoint (
atan2) minustheta, wrapped to (-\pi, \pi] (add or subtract 2\pi; exactly -\pi becomes \pi). - Control.
I = I + e * dt;D = (e - previous error) / dt, or0if there is no previous error yet; remembereas the previous error. \omega = k_p e + k_i I + k_d D, then limited to [-1, 1] rad/s. Nothing is reset when the waypoint changes. - Move (one Euler step, position with the old heading):
x += v * cos(theta) * dt,y += v * sin(theta) * dt, thentheta = theta + omega * dt, wrapped to (-\pi, \pi].
Write follow_waypoints(pose, waypoints, gains, v, dt, steps) that returns (x, y, theta, reached, taken): the final pose, the number of waypoints reached and the number of steps taken. Collect the positions and press Run with plot(xs, ys) to see the route.
Examples
Input: pose = (0, 0, 0), waypoints = [(1.02, 0)], gains = (1, 0, 0), v = 0.5, dt = 0.1, steps = 100
Output: (0.8000000000000002, 0.0, 0.0, 1, 16)
Explanation: 0.05 m per step straight ahead; at x = 0.75 the waypoint is still 0.27 m
away, at x = 0.8 it is 0.22 m away and counts as reached.
Input: pose = (0, 0, 0), waypoints = [(2, 0), (2, 2)], gains = (2, 0, 0.1), v = 0.5, dt = 0.1, steps = 200
Output: (2.064671372621046, 1.7649311952276134, 1.8276718581382891, 2, 77)
Input: pose = (0, 0, math.pi), waypoints = [(3, 0)], gains = (1.5, 0, 0), v = 0.4, dt = 0.1, steps = 200
Output: (2.798224854254143, -0.06115595880402403, 0.2942360286507172, 1, 105)
Explanation: the waypoint is straight behind: the first error is 0 - pi = -pi, which wraps
to +pi, so the shuttle turns counter-clockwise (through -y) to come round.
Constraints
- answers are compared with a tolerance of
1e-6
Goals
- Steer a unicycle robot toward waypoints with a PID controller on the wrapped heading error
- Simulate the loop with a fixed order of updates, a turn-rate limit and a waypoint acceptance radius