Truth and belief

position() is the lab's overhead camera. odometry() is what the robot has, and it drifts.

U1.3The robot as a systemUniversity25 min

Do this lesson in the simulator

position() and heading() tell you exactly where the robot is. No robot can do that.

In this simulator they are the equivalent of an overhead camera in a motion capture lab: a measurement made by the building, not by the robot, accurate to a millimetre, and completely unavailable once the robot leaves the room. They are here so that you can mark your own work. Using them as the input to a controller is cheating, and worse, it teaches nothing.

What the BugBot actually carries is an odometry board: an optical flow sensor looking down at the mat, and an IMU.

What the robot has What it gives you
flow() (vx, vy): how fast the mat is sliding past, in the robot's own frame
imu() (heading, turn_rate, ax, ay): a fused heading, a gyro rate, and what the body feels
odometry() (x, y, heading): those two added up since the run started

Watch belief come apart from truth

from bugbot import *
connect()

for leg in range(4):
    forward(70, distance=40)
    turn_right(40, angle=90)
    ox, oy, oh = odometry()
    tx, ty = position()
    print("leg", leg, "believes", (ox, oy, round(oh)), "is at", (tx, ty, round(heading())))

Run this in the simulator

The robot starts out right and ends up wrong, and it is wrong in a particular way: the error grows, and it grows faster after each turn.

Why dead reckoning drifts

Odometry integrates. Each step it takes a measured velocity and a measured turn rate and adds them to a running total. Adding up a measurement adds up its errors too.

  • Noise adds up as a random walk: the error grows with the square root of time. Annoying, survivable.
  • Bias adds up linearly: a gyro reading 0.4 degrees per second too high is 24 degrees out after a minute, every time, in the same direction. Far worse than noise.
  • Scale error in the flow sensor makes every centimetre slightly long or slightly short.
  • And a heading error rotates everything after it. That is why the turns hurt: a small heading error at the corner turns into a large position error down the next straight.

That last point is the one to keep. In dead reckoning, heading error is the expensive one.

Turning the noise off

set_noise(level) scales all of it: 0 is a perfect robot, 1 is this one as built, 3 is a bad day. It is a teaching tool, not something a real robot has.

from bugbot import *
connect()

set_noise(0)               # a robot that cannot be built
for leg in range(4):
    forward(70, distance=40)
    turn_right(40, angle=90)
print("perfect sensors:", odometry(), "truth", position(), round(heading()))

Run this in the simulator

With the noise off, the dead reckoning still is not perfect, because the world is still noisy even when the sensors are not. The drive slips, the robot is shoved by its own vibration, and the flow sensor honestly reports motion that your model of "driving in a straight line" did not include.

Task: how far out is it?

Drive at least 120 cm, with at least one turn in it. At the end, print how far odometry() is from position(), as error: 4.3.

from bugbot import *
connect()

forward(70, distance=50)

Challenges

  1. Do the same run with set_noise(0), set_noise(1) and set_noise(3). Tabulate the error.
  2. Drive 200 cm in a straight line, then 200 cm as four 50 cm legs with three turns. Which ends further out, and why?
  3. Print the heading error separately from the position error. Which one would you rather fix?