When writing robot control programs, so far, you have used a very basic technique in designing control programs:
def main(): # do forever or for some time or until a certain condition is satisfied # sense and transform sensors values # reason or decide what to do next # do it
As you have seen, such a control program works well for many simple tasks. However, you may have already run into many situations where, once the task gets a little complex, it becomes difficult to structure a program in terms of a single stream of control as shown above. For example, the corral exiting behavior from the last chapter requires you to combine two simple behaviors: solve a maze (avoid obstacles) and then seek light to get out of the corral. As you have seen before, it was fairly easy to program each of the individual behaviors: obstacle avoidance; light following. But, when you combine these behaviors to accomplish the corral exiting behavior two things happen: you are forced to amalgamate the two control strategies into a single one and it may become difficult to decide which way to combine them; additionally, the resulting program is not very pretty and hard to read. In reality, hardly any robot program are written that way. In this chapter, we will look at some different ways of structuring robot programs that makes designing behaviors easy, and yet, the resulting structure of the overall program is also clean and straightforward. You can design some very sophisticated behaviors using these ideas.
People in the robotics community call the style of programming shown above as reactive control or direct control. Also referred to as sensor fusion, the resulting programs are purely sensor driven and hence apper to be too bottom-up. That is, the values of the sensors drive the logic of control as opposed to the goals of the robot tasks themselves. In behavior-based control you get away from sensors and focus the design of your robot programs based on the number and kinds of behaviors your robot has to carry out. For example, in the corral exiting exercise, the robot essentially has to carry out three kinds of behaviors: cruise (in the absence of any obstacles and/or light), avoid obstacles (if present), and seek light (if present). In a behavior-based style of writing programs, you will define each of these behaviors as an individual decision unit. Thus, each is quite simple and straightforward to write. Next, the control program has to fuse the behaviors recommended by each invidual behavior unit. Consider the picture shown below:
In the diagram above, we have shown the three basic behaviors that we are trying to combine: Cruise, Avoid, SeekLight. Each of these behaviors outputs a triple: Yes/No, Translate Speed, Rotate Speed. A yes implies that the behavior module has a recommendation (No implies that it doesn't). That is, it allows the possibility of a behavior having no recommendation. For example, in the corral exiting situation, in the absence of a light source being sensed by the robot, the SeekLight module will not have any recommendation. It then becomes the task of the arbitrator (or the decision module) to decide which ofthe available recommendations to use to drive the robot. Notice that, in the end, to control the robot, all one has to do is decide how much to translate and rotate. many different arbitration schemes can be incorporated. We will use a simple but effective one: Assign a priority to each behavior module. Then the arbitrator always chooses the highest priority recommendation. This style of control architecture is also called subsumption architecture. In the figure above, we have actually drawn the modules in the order of their priority: the higher the module is in the figure, the higher its priority. The lowest behavior, cruise does not require any sensors and is always present: it wants the robot to always go forward.
Arranging a control based on combining simple behaviors has several advantages: you can design each individual behavior very easily; you can also test each individual behavior but only adding that behavior and seeing how well it performs; you can incrementally add any number of behaviors on top of each other. In the scheme above, the control regime implies that the robot will always cruise forward. But, if there is an obstacle present, it will override the cruise behavior and hence try to avoid the obstacle. However, if there is a light source detected, it will supercede all behaviors and engage in light seeking behavior. ideally, you can imagine that all the behaviors will be running simultaneously (asynchronously). In that situation, the arbitrator will always have one or more recommendations to adopt based on priority.
Let us develop the program that implements behavior-based control. First, we define each behavior:
cruiseSpeed = 0.8 turnSpeed = 0.8 lightThresh = 80 def cruise(): # is always ON, just move forward return True, cruiseSpeed, 0 def avoid(): # see if there are any obstacles L, R = getIR() L = 1 - L R = 1 - R if L: return True, 0, -turnSpeed elif R: return True, 0, turnSpeed else: return False, 0, 0 def seekLight(): L, C, R = getLight() if L < lightThresh: return True, cruiseSpeed/2.0, turnSpeed elif R < lightThresh: return True, cruiseSpeed/2.0, -turnSpeed else: return False, 0, 0
In the above, you can see that each individual behavior is simple and is easy to read (and write). There are several ways to incorporate these into a behavior-based program. Here is one:
# the list of behaviors, oredered in their priority (left is highest) behaviors = [seekLight, avoid, cruise] def main(): while True: T, R = arbitrate() move(T, R) main()
The main program just calls the arbitrate function that returns the chose translate and rotate commands which are then applied to the robot. Arbitrate itself is simple enough:
def arbitrate(): for behavior in behaviors: output, T, R = behavior() if output: return T, R
That is, it querries each behavior in order of priority to see if has a recommendation. if it does, that is the set of motor commands returned.