This is an introduction to the most basic method of robotics programming: direct, or reactive control. This module provides an overview of such "stateless" controllers. When completed, the reader should be ready to explore more sophisticated controllers with state.
In this module we will create robot controllers that have a close coupling between perception and action. This means that there is limited state information and computation. This is called direct or reactive control. The basic form of a direct control robot program is shown below. It must include a step method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# A simple brain from pyrobot.brain import Brain # Define the robot's brain class class SimpleBrain(Brain): # Only method you have to define is the step method def step(self): self.robot.translate(0.3) # go forward # Create a brain for the robot def INIT(engine): return SimpleBrain('SimpleBrain', engine)
Running the program
Before we explain the details of the above program, you may want to run it. First, download the program to your home directory, and save it as SimpleBrainProgram.py.
To quickly start up Pyro on this program, you can do the following:
pyrobot -s StageSimulator -w tutorial.cfg -r Player6665 -b /home/'''username'''/SimpleBrainProgram.py &
Where username is replaced with your username.
Understanding the SimpleBrain program
Recall that before you load the brain program into Pyro, you have already loaded the robot driver (Player6665.py). This instantiates an object of the specific robot class which is accessible to your program using the identifier robot. Every robot instance has to have a brain object that defines the behavior of the robot. This is done by extending a predefined class, called Brain. In the program, this is accomplished by the lines of code reproduced below and numbered for reference:
1 from pyrobot.brain import Brain 2 3 # Define the robot's brain class 4 5 class SimpleBrain(Brain): 6 # Only method you have to define is the step method 7 8 def step(self): 9 self.robot.translate(0.3) # go forward
Anything following the # symbol is a python comment.
In order to extend the class Brain you have to import the class first as shown in line 1, and then extend it as shown in line 5. For direct control, all one has to do is define the step method starting in line 8. In each cycle of a robot run, the step method is executed and this is where you will define commands for your robot. For our example, we just wanted to have the robot move forward. This is done by the command:
Once instantiated, the Brain object (in this case, the SimpleBrain object) can get the robot it controls by using the robot method. It is an accessor defined in the Brain class. Thus, calling the translate method on that robot object specifies the robot to move forward at speed 0.3.
The next few lines of code (shown below) create a specific instance of SimpleBrain and specify the robot which is controlled by it.
# Create a brain for the robot def INIT(engine): return SimpleBrain('SimpleBrain', engine)
The INIT method creates a brain instance to which the engine it controls is specified. The command:
is a standard Python constructor call to create an instance of the class, SimpleBrain. The constructor is inherited from the Brain class since SimpleBrain itself doesn't define one. The constructor takes two arguments (besides self), one specifying the name of the brain (in our case SimpleBrain), and the other specifying the engine (the variable engine).
An engine is an abstraction that contains all of the pieces that go together to run a robot, including the simulator (if using one), world file, configuration information, robot, etc.
The INIT method should return the newly created brain object (hence the return statement).
When you load this brain program into Pyro and press the Run or Step buttons in the Pyro window, the step method is executed on the robot, once for Step and continuously for Run.
Exercise: Making sure that your robot has the proper brain
To ensure that you understand the program, let us find out if the robot we specify does have the brain program you created. Modify the INIT method in MySimpleBrain.py as shown below:
# Create a brain for the robot def INIT(engine): brain = SimpleBrain('SimpleBrain', engine) print engine.robot.name + " robot now has " + brain.name + " brain." return brain
The INIT method creates a SimpleBrain instance specifying a name and the robot instance. Next, we simply print out the name of the robot as well as the name of the brain that was assigned to it. When run, the print statement will therefore confirm that the robot we specified has the brain we just instantiated.
Edit and save the modified program. Reload the program into Pyro and run it. Notice the output of the print statement that appears in the console window. It should say:
Player6665 robot now has SimpleBrain brain.
Exercises: Trying other motion commands
Modify the step method of your program to do the following tasks. Be sure to save and press Reload Brain button between modifications.
Use move instead of translate and try different values.
Write a program for the robot to transcribes a circle.
Write a program that makes the robot do a little dance.
You may want to use the sleep command from the Python time module. Import it using from time import * and then to sleep for x seconds, use the command sleep(x).
In addition to the movement commands based on a desired translate and rotate, you can also command the robot by sending power values to each wheel. For example:
will send 0.5 power to the left wheel and 0.25 to the right. Like translate and rotate, these power levels go from -1 to +1. See if you can fill out the equivalency chart below:
|robot.move(t, r)||robot.motors(l, r)|
Using the Setup Method in a brain
Often, you may need to perform some initializations before a brain's step method assumes control. In Pyro brain programs, you can make use of a method called, setup to do this. The structure of the brain program that uses a setup method is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# A simple brain with a setup method from pyrobot.brain import Brain # Define the robot's brain class class SimpleBrain2(Brain): # The setup method can be used to do initializations def setup(self): self.moveAmount = 0.3 # The step method is the main behavior loop def step(self): self.robot.translate(self.moveAmount) # go forward # Create a brain for the robot def INIT(engine): return SimpleBrain2('SimpleBrain2', engine)
The program above initializes an instance variable in setup which is then referenced in the step method. The setup method is executed once (as a post constructor operation) each time a new instance of the brain (sub)class is created. Thus, when the INIT method above creates an instance of SimpleBrain, the value of the moveAmount field is set to 0.3.
You can access attributes of a brain from the Pyro command line, like the variable moveAmount, by typing: brain.moveAmount
Example: Transcribing a Figure Eight
Let us write a behavior for a robot to try to transcribe a figure eight on the floor. The idea is to go around in a circle once and then, after completing a circle, to switch the direction of rotation and go around in the circle once again, thus completing a figure eight. The program to do this behavior is shown below. Try loading this program into a robot/simulator of your choice and see what happens.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
from pyrobot.brain import Brain from time import * class fig8(Brain): # Try and transcribe a figure-8 # initialize def setup(self): self.rot = 0.3 self.delay = 10.0 # change direction and go about in a circle def step(self): robot = self.robot self.rot = self.rot*-1 robot.move(0.3, self.rot) sleep(self.delay) def INIT(engine): return fig8('fig8', engine)
Most likely, the robot goes in a circular zig-zag, S-pattern. This is because, we have to wait for the robot to transcribe a complete circle before changing directions. You can fix this by adjusting the sleep command's argument, self.delay, or by adjusting the rate of rotation. Go ahead and try different values until you get the robot to transcribe a figure eight.
In addition to motor commands (translate, rotate, move), you can also access the values reported by a robot's sensors. Each robot comes equipped with a unique sensor suite. For example, the Pioneer robots may have sonar sensors, a front bumper, a gripper, and a camera. The Khepera robots have infrared sensors, light sensors, and a camera. The Pioneer's sonar sensors and the Khepera's infrared sensors are both active range finders which can be used to avoid obstacles. Although these two robots have different kinds of sensors, Pyro unifies the way you will use them in your program. However, it will be up to your program to interpret the sensor data. For now we will demonstrate how to use the range sensors. Go to Pyro Sensors to review the sensor topologies and range sensing commands introduced earlier.
Reading Pioneer sonar sensors
Recall that each Pioneer robot comes equipped with a set of 16 sonar sensors. Let's write a simple program that prints out the minimum and maximum values of the front range sensors for a robot while it is moving forward.
1 2 3 4 5 6 7 8 9 10 11 12 13
from pyrobot.brain import Brain class Sensor(Brain): # print the min and max value of the front sensors while moving def step(self): self.move(0.5,0) self.front = [s.distance() for s in self.robot.range["front"]] print "min front: ", min(self.front), "max front: ", max(self.front) def INIT(engine): return Sensor('Sensor', engine)
Download this program, load it, and then run it one step at a time using the Step button in the Pyro window. You will see the front range values printed in your console window. By default the values returned are in proportion to the robot's physical diameter. You can move the robot around within the simulated world by left clicking the mouse at a new location and you can adjust its heading by right clicking the mouse. Position the robot so it faces a wall a fair distance away and then step it toward the wall and notice the values of the sensors change. This will give you a good idea of the kind of values reported depending on the distance to objects and will be helpful in writing more complex behaviors.
Simple obstacle avoidance
Let us now combine sensing with movement to design a robot behavior that senses its environments and moves about without bumping into objects. The simplest algorithm to do this can be described as shown below:
if approaching an obstacle to the left side, turn right if approaching an obstacle to the right side, turn left else go forward
The program shown below, implements the above behavior for a Pioneer robot using its three front-left sensors (numbered 1, 2, and 3) and three front-right sensors (numbered 4, 5, and 6). Recall from the example above that sensor values in robot units decrease as the robot approaches an obstacle. We have written the program below to be sensitive to obstacles 1 robot unit away. Download this program and try running it on a real or simulated Pioneer robot that is situated in a corridor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
from pyrobot.brain import Brain class Avoid(Brain): def wander(self, minSide): # if approaching an obstacle to the left side, turn right if min([s.distance() for s in self.robot.range["front-left"]]) < minSide: self.move(0,-0.3) # if approaching an obstacle to the right side, turn left elif min([s.distance() for s in self.robot.range["front-right"]]) < minSide: self.move(0,0.3) else: # go forward self.move(0.5, 0) def step(self): self.wander(1) def INIT(engine): return Avoid('Avoid', engine)
Exercise 3: Improving obstacle avoidance
Modify the obstacle avoidance program so that it responds appropriately when the front sonar sensors detect an obstacle. Be sure to save and press the Reload Brain button between modifications.
Below is a program that by default goes forward until it finds a wall and then tries to follow the wall on the left. Download this program and try running it on a robot.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
from pyrobot.brain import Brain class WallFollow(Brain): # follows walls on its left, ignores sonar sensors on its right def wallFollow(self, dist): frontLeft = self.robot.sonar.distance() backLeft = self.robot.sonar.distance() front = min([s.distance() for s in self.robot.sonar[2:6]]) if front < dist: print "wall in front" self.move(0,-0.5) elif (frontLeft < dist or backLeft < dist): print "following:", if frontLeft < backLeft: print "turn slight away" self.move(0.3,-0.1) else: print "turn slight toward" self.move(0.3,0.1) else: print "find wall" self.move(0.3,0) def step(self): self.wallFollow(1) def INIT(engine): return WallFollow('WallFollow', engine)
Exercise 4: Improving wall following
Notice that when the robot is following a wall and reaches a corner, the robot is able to turn the corner and continue following the wall. However, if the robot is following a wall and it ends up in open space, the robot loses the wall. Try to modify the wall following program so that the robot maintains contact with the wall and continues following it even when it makes a sharp turn. Be sure to save and press the Reload Brain button between modifications.
Also try to improve the program by enabling it to either follow walls on the left or on the right.