Dead reckoning

One of the simplest ways of controlling the robot autonomously is using dead reckoning.

It uses one of the first equations you learned in physics: \(time = distance / speed\). We use it to calculate, how long it takes something to travel a certain distance based on its average speed.

Let’s look at an example: say our robot drives an average of \(s = 2.5 \frac{m}{s}\). We want it to drive a distance of \(d = 10m\). To calculate, how long it will take the robot, all you have to do is divide distance by speed: \(t = d/s = 10/2.5 = 4s\).

This is exactly what dead reckoning does – it calculates the time it will take the robot to drive the distance to the goal. When asked, returns 1 if that amount of time hasn’t elapsed yet and 0 if it has.

Implementation

There are two things that the controller needs: the average speed of the robot and a way to measure how much time had passed.

class DeadReckoning:
    """A class implementing a dead reckoning controller."""

    def __init__(self, speed, get_current_time):
        """Takes the average speed and a function that returns current time."""
        self.get_current_time = get_current_time
        self.speed = speed

    def set_goal(self, goal):
        """Sets the goal of the controller (and also starts the controller)."""
        self.goal = goal
        self.start_time = self.get_current_time()

    def get_value(self):
        """Return the current value the controller is returning."""
        # at what time should we reach the destination (d=d_0 + s/v)
        arrival_time = self.start_time + (self.goal / self.speed)

        # return +-1 if we should have reached the destination and 0 if not
        if self.get_current_time() < arrival_time:
            return 1 if self.goal > 0 else -1
        else:
            return 0

As we see, the parameters the __init__ function is expecting to get are:

  • speed – the average speed of the robot.
  • get_current_time – a function that returns the current time to measure, whether the calculated time had elapsed.

Example

# initialize objects that control robot components
left_motor = Motor(1)
right_motor = Motor(2)

# create a controller object and set its goal
controller = DeadReckoning(2.5, get_current_time)
controller.set_goal(10)

while True:
    # get the controller value
    controller_value = controller.get_value()

    # drive the robot using tank drive controlled by the controller value
    tank_drive(controller_value, controller_value, left_motor, right_motor)

This is an implementation of the problem proposed in the Introduction: make a robot drive 10 meters.

Notice how we used our previously implemented tank_drive function to set both motors to drive forward. We could have written left_motor(controller_value) and right_motor(controller_value), but this is a cleaner way of writing it.

Closing remarks

Although this is quite a simple controller to implement, you might realize that it is neither accurate nor practical. If the robot hits a bump on the road or slips on a banana peel, there is nothing it can do to correct the error, since it doesn’t know where it is.

Another important thing to keep in mind when using this controller is that if you want to change how fast the robot is driving/turning, you will need to re-calculate the average speed of the robot, which is very tedious.

We’ll be focusing on improving accuracy in the upcoming articles by incorporating real-time data from the robot into our controllers.