Bang–bang
Although our previous controller was quite easy to implement and use, there is no way for it to know whether it reached the target or not. It pretty much just turns the motors on for a while and hopes for the best.
Bang-bang aims to fix this problem by using feedback from our robot. Feedback could be values from its encoders (to measure how far it has gone), gyro (to measure where it’s heading), or really anything else that we wish to control. The important thing here is that the data is real-time. The robot continuously tells the controller what is happening, so the controller can act accordingly.
Bang-bang implements the very first idea that comes to mind when we have real-time data available. The controller will return 1 if we haven’t passed the goal yet and 0 if we have.
Implementation
The only thing the Bang-bang controller needs is the feedback function returning information about the state of whatever we’re trying to control.
class Bangbang:
"""A class implementing a bang-bang controller."""
def __init__(self, get_feedback_value):
"""Create the bang-bang controller object from the feedback function."""
self.get_feedback_value = get_feedback_value
def set_goal(self, goal):
"""Sets the goal of the bang-bang controller."""
self.goal = goal
def get_value(self):
"""Returns +1 or -1 (depending on the value of the goal) when the robot
should be driving and 0 when it reaches the destination."""
if self.goal > 0:
if self.get_feedback_value() < self.goal:
return 1 # goal not reached and is positive -> drive forward
else:
if self.get_feedback_value() > self.goal:
return -1 # goal not reached and is negative -> drive backward
# if it shouldn't be driving neither forward nor backward
return 0
Examples
To make the robot drive a distance using this controller, we need a new Encoder
class to measure how far the robot has driven. The objects of this class return the average of the distance driven by the left wheel and by the right wheel.
# initialize objects that control robot components
left_motor = Motor(1)
right_motor = Motor(2)
encoder = Encoder()
# create a controller object and set its goal
controller = Bangbang(encoder)
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)
Notice that pretty much nothing changed between this and the dead reckoning example. This is the main advantage of all of the controllers having the same functions – we can use controller objects almost interchangeably, allowing us to easily try out and compare the accuracies of each of the controllers, without messing with the rest of our code.
Closing remarks
This is already markedly better than our previous dead reckoning approach, but it is still relatively inaccurate: the robot’s inertia will make the robot drive a little extra distance when the controller tells it that it shouldn’t be driving anymore, which means it will likely overshoot.
We could try to fix this by saying that it should start driving backward once it passed the goal, but the only thing you’d get is a robot that drives back and forth across the goal (which may be amusing, but not very helpful).
In the upcoming articles, we will try to improve our approach and create controllers that don’t just return 1 for driving and 0 for not driving, but also values in-between (when the robot should be driving slower and when faster).