- Understanding the Problem of Navigation
- Navigation Theory
- Using the Navigator API
- Creating a Navigator Robot
- TimingNavigator Accuracy
- Summary
Creating a Navigator Robot
Let's build a robot so we can try out some simple navigation. Tippy seems like it could be good for navigation, because it rotates within its own footprint, but in fact Tippy is too fast! Fast robots like Tippy are actually not good for timing navigation because the wheels skid when starting and stopping. Also, it takes Tippy a few milliseconds to build up enough force to start moving, which can throw off our timing measurements. What we need is a robot that doesn't skid and starts moving immediately when it is instructed to move. Slow rotation is vital, so for this chapter we will build a robot that uses tank treads to achieve differential steering.
Building the Trilobot
This section shows you how to build a robot using tank treads. The robot moves relatively slowly, and makes slow turns, making it ideal for use with the TimingNavigator class. The robot is named Trilobot because it resembles an extinct animal known as a trilobite (Figure 77). Both trilobot and trilobite have wraparound antennae, they are low to the ground, they have a hard outer shell, and both have arthropod-level intelligence.
Figure 77 Trilobite vs. Trilobot.
Step 1 Join two 16-unit beams together with two 8-unit axles, spacing them with a bush. Repeat the same for the other side.
Step 2 Clamp three 2 × 8 plates to the underside of the chassis, joining the two halves together. The front plate should hang over the edge by one unit.
Step 3 Attach two touch sensors and two 1 × 2 beams with axle connectors to the front plate.
Step 4 Clamp a 2 × 8 plate over the assemblage. Insert two 3-unit axles into the green axle connectors.
Step 5 Attach two axle connectors to the front of the robot and insert two axle pins into the top. These will be used to support the front bumper.
Step 6 Attach two black lift arms to the axle pins. At this point they should swing freely.
Step 7 Attach two dark gray 3/4 pins in the hole closest to the axle pins. These will be used to secure a bar to keep the bumpers from moving forward too much. Next, insert two axle pins to the underside of the bumper.
Step 8 Attach two 8-unit gears to the axle pins on the underside of the bumper. Clip on the 5-unit lift arm, which will act as a restraining bar for the bumper. This will allow the bumpers some movement back and forth without swinging all the way out.
Step 9 Attach two bushes to each of the two front axles. On the rear axles, attach a 24-tooth gear and a bush.
Step 10 Attach four friction pins to the outer beams. These will be used to secure the RCX brick to the chassis.
Step 11 Clamp the RCX brick to the top of the chassis. Make sure it fits snugly to the top of the beams. Insert four 3/4 pins into the holes of the RCX brick.
Step 12 Attach four securing beams to the sides of the robot to lock the RCX down.
Step 13 Place a rubber tread over two sprocket wheels and insert the wheels onto a pair of axles. Repeat for the other side.
Step 14 Secure the front sprocket wheels with Technic bushes. The rear sprocket wheels should be secured with 16-tooth gears, which will cause the wheel to turn with the motorized axle.
Step 15 Attach two 1 × 8 plates to the rear of the chassis. This prepares a surface to clamp down two motors, as the bottom of the motor is not flat.
Step 16 Attach a 16-tooth gear to each motor, then attach them to the rear of the chassis, back to back.
Step 17 Attach two 2 × 4 plates across the gap between the two motors (difficult to see in this picture). On top of these plates, attach two more 2 × 4 plates perpendicular to the first pair. This provides added strength to the unit.
Step 18 Attach two 2 × 6 plates to the top of the robot. This secures the motors in place when force is applied to the gears.
Step 19 Attach two short wire bricks to the front sensors, keeping the wires to the outer sides of the robot. Attach two more short wire bricks to the rear motors, with the wires facing outward. When you attach the wires to Ports A and C, make sure the wires face inward.
Programming Trilobot
Now that we have the Trilobot built, it is time to give it some intelligent navigation abilities. TimingNavigator requires us to figure out how long it takes to travel one meter, and how long it takes to rotate 360 degrees. The first part is easy; all we need to do is make a simple program to get it moving forward and then time it:
import josx.platform.rcx.*; class TravelTest { public static void main(String [] args) { Motor.A.setPower(7); // Change to equalize motors Motor.C.setPower(7); // Change to equalize motors Motor.A.forward(); Motor.C.forward(); } }
This code has two lines for setting the power to each motor. Generally the motors do not produce the same torque, which can cause your robot to veer off in one direction. When I first tested Trilobot, it veered off significantly to the left, indicating that Motor C is turning faster than Motor A. By playing around with the aPower argument in the setPower() method, I came up with the following revised code:
import josx.platform.rcx.*; class TravelTest { public static void main(String [] args) { Motor.A.setPower(7); Motor.C.setPower(4); // Decreases power Motor.A.forward(); Motor.C.forward(); } }
It is very important to balance the two motors! If they are not balanced the robot will be thrown off course. Now we need to create a small track for Trilobot by measuring out a meter (100 cm) on the floor. If you want, you can measure out 100 inches if you are more comfortable using the imperial system of measurement, but just make sure you consistently use inches with all measurements. If you use inches in the constructor, the TimingNavigator class will use inches for the x and y coordinates.
For better accuracy, instead of measuring 100 units, you can measure out 400 units, and then divide the final result by four to get the average. Keep in mind the surface the robot travels on affects speed. My test on a carpet produced different times than when it was done on a hard surface. The total four meter trip took 21.9 seconds on carpet, so we will use the value of 5.475 seconds per meter. Next, we need to measure the time it takes to rotate 360 degrees. For this, I'm going to make it rotate four times and average them out. The code is the same as the previous example, only one of the motors will move backward:
import josx.platform.rcx.*; class RotateTest { public static void main(String [] args) { Motor.A.setPower(7); Motor.C.setPower(4); // Decreases power Motor.A.forward(); Motor.C.backward(); } }
Four complete rotations took 16.0 seconds, so one rotation is about 4.0 seconds. I also tried rotating counterclockwise to see if it produced a different result, but it was quite similar at 16.27 seconds for four rotations, or 4.0675 seconds per rotation. I'll use a final average of 4.03 seconds. Now that we have our two required values we can create a TimingNavigator object and try some simple functions.
Our first test will make Trilobot spin 360 degrees (so we can see how accurate we were with the calibrations), then trace out the shape of a square. The path will look similar to Figure 78. To trace the square, we will make it go to the following coordinates: (100,0) (100,100) (0,100) (0,0):
Figure 78 Test path for Trilobot.
1. import josx.platform.rcx.*; 2. import josx.robotics.*; 3. 4. class Trilobot { 5. 6. public static void main(String [] args) { 7. Motor.A.setPower(7); 8. Motor.C.setPower(5); 9. 10. TimingNavigator n = new TimingNavigator(Motor.C, Motor.A, 5.475f, 4.03f); 11. n.rotate(360); 12. n.gotoPoint(100,0); 13. n.gotoPoint(100,100); 14. n.gotoPoint(0,100); 15. n.gotoPoint(0,0); 16. } 17. }
WARNING
The third and fourth parameters in this constructor will likely be different for you than the values here (5.475 and 4.03). These values are dependent on the motor strengths and battery charge of your robot. If your robot uses lithium batteries the wheels will probably turn faster, so the parameters in the constructor will likely be smaller than for a robot that uses rechargeable batteries.
In this code we have retained the method calls to setPower() to equalize the motors. If all goes well the robot should complete a full square and then stop. If you notice that Trilobot rotates more than 360 degrees, you might want to consider lowering the rotate argument in the TimingNavigator constructor; likewise, if it doesn't rotate enough it should be increased. Once this is straightened out as close as possible (it will never be perfect) we can move on to some more serious programming using behavior control.
In this example, and the rest of the book, we use behavior control as the architecture for robotics programming. Behavior programming gets a little more challenging with high-level classes such as Navigator, but not much more. Trilobot will have four behaviors it will use to navigate. The lowest level behavior will randomly go from one point to another within a square 150 × 150 cm (Figure 79). At any time while the robot is moving to a point, if one of the bumpers hits an object it reacts, based on the bumper. If the left bumper detects an object, the robot backs up 20 cm and makes a buzz. If the right bumper detects an object, the robot backs up 20 cm and makes a beep. After more than 30 seconds elapse, the robot makes a sound then returns to the point of origin, stops, and pauses for five seconds before continuing. Each one of these reactions will be in a separate Behavior object (Table 71), and each behavior will share the same Navigator object for its own purposes to keep track of coordinates. In this example it will be interesting to place a reference object, such as a dime, at the point of origin to see how close it comes after 30 seconds.
Figure 79 Trilobot confines itself to a square.
Table 7-1 Behavior Descriptions for Trilobot
Condition |
Action |
Suppress |
Always |
Move to random points within a square region |
Stop traveling and update coordinates |
Left bumper collision |
Make noise and back up 20 cm |
Stop moving backward |
Right bumper collision |
Make noise and back up 20 cm |
Stop moving backward |
30 seconds |
Seek point of origin |
Stop moving to origin |
Let's begin with the lowest level behavior first. We use the TimingNavigator class to control all operations in all classes. The method gotoPoint() will serve our purpose of moving about randomly within a square 1.5 m × 1.5 m, as follows:
1. import josx.robotics.*; 2. public class Move implements Behavior { 3. private boolean active; // Indicates behavior is active 4. private Navigator nav; 5. 6. public Move(Navigator nav) { 7. this.nav = nav; 8. active = false; 9. } 10. 11. public boolean takeControl() { 12. return true; 13. } 14. 15. public void suppress() { 16. active = false; 17. nav.stop(); 18. } 19. 20. public void action() { 21. active = true; 22. while(active) { 23. float x = (int)(Math.random() * 150); 24. float y = (int)(Math.random() * 150); 25. nav.gotoPoint(x, y); 26. } 27. } 28. }
Notice this class uses an instance of TimingNavigator in the constructor. This object will be shared by many behaviors. The main action for the Move behavior takes place starting at Line 20. The while loop will repeat until active is false, which will occur as soon as suppress() is called. The suppress() method also stops the robot by calling Navigator.stop(), and the Navigator object automatically updates the internal positional coordinates when this occurs. Now let's create some classes to handle collisions with other objects.
The Trilobot robot has two separate touch sensors, one for the left bumper and one for the right bumper. The code actually reacts the same for the left and right bumpers, so only one bumper is necessary for this program, but I included a split bumper so that Trilobot could be reprogrammed with more interesting behavior based on the side the collision occurred on. For the purposes of this program, we want Trilobot to stop and back up when a collision occurs. The suppress() code will stop Trilobot backing up, and the takeControl() method will only take control when the bumper hits an object. We use a SensorListener, as described in the previous chapter, so collisions are never missed. The left bumper behavior is as follows:
1. import josx.robotics.*; 2. import josx.platform.rcx.*; 3. 4. public class LeftBump implements Behavior, SensorListener { 5. private Navigator nav; 6. private boolean hasCollided; 7. 8. public LeftBump(Navigator nav) { 9. this.nav = nav; 10. hasCollided = false; 11. Sensor.S1.addSensorListener(this); 12. } 13. 14. public boolean takeControl() { 15. if(hasCollided) { 16. hasCollided = false; // reset value 17. return true; 18. } else 19. return false; 20. } 21. 22. public void suppress() { 23. nav.stop(); 24. } 25. 26. public void stateChanged(Sensor bumper, int oldValue, int newValue) { 27. if(bumper.readBooleanValue() == true) 28. hasCollided = true; 29. } 30. 31. public void action() { 32. // Back up: 33. Sound.buzz(); 34. nav.travel(-20); 35. } 36. }
This code is quite straightforward. When the touch sensor is hit, Line 28 changes the hasCollided value to true. Then, when takeControl() is called the method returns true. Line 16 resets the hasCollided flag. Likewise, the right bumper behavior is almost identical:
1. import josx.robotics.*; 2. import josx.platform.rcx.*; 3. 4. public class RightBump implements Behavior, SensorListener { 5. private Navigator nav; 6. private boolean hasCollided; 7. 8. public RightBump(Navigator nav) { 9. this.nav = nav; 10. hasCollided = false; 11. Sensor.S3.addSensorListener(this); 12. } 13. 14. public boolean takeControl() { 15. if(hasCollided) { 16. hasCollided = false; // reset value 17. return true; 18. } else 19. return false; 20. } 21. 22. public void suppress() { 23. nav.stop(); 24. } 25. 26. public void stateChanged(Sensor bumper, int oldValue, int newValue) { 27. if(bumper.readBooleanValue() == true) 28. hasCollided = true; 29. } 30. 31. public void action() { 32. // Back up: 33. Sound.beep(); 34. nav.travel(-20); 35. } 36. }
Our fourth behavior is the most complex of all. It requires the robot to go back to the point of origin when 30 seconds have elapsed. To do this, we will use the josx.util.Timer class and the josx.util.TimerListener interface. TimerListener will be notified by the timer every 30 seconds via the timedOut() method. We'll make our behavior implement TimerListener, and the timedOut() method will simply change a Boolean value to indicate the time is up, as follows:
1. public void timedOut() { 2. timeUp = true; 3. } The takeControl() method will then return true only if the time is up: 1. public boolean takeControl() { 2. return timeUp; 3. }
The action() method is very easy to implement. It simply calls the method gotoPoint(0,0) to make Trilobot return to the point of origin. The completed module for this behavior is as follows:
1. import josx.robotics.*; 2. import josx.platform.rcx.*; 3. import josx.util.*; 4. 5. public class GoHome implements TimerListener, Behavior { 6. 7. Navigator nav; 8. boolean timeUp; 9. 10. public GoHome(Navigator nav) { 11. this.nav = nav; 12. timeUp = false; 13. Timer t = new Timer(30000, this); 14. t.start(); 15. } 16. 17. public void timedOut() { 18. timeUp = true; 19. } 20. 21. public boolean takeControl() { 22. return timeUp; 23. } 24. 25. public void suppress() { 26. nav.stop(); 27. } 28. 29. public void action() { 30. Sound.beepSequence(); 31. nav.gotoPoint(0,0); 32. Sound.twoBeeps(); 33. try {Thread.sleep(5000);}catch(InterruptedException e) {} 34. Sound.beep(); 35. timeUp = false; // reset time up 36. } 37. }
As you can see, the methods are all very simple and straightforward. Now we merely need to create a main class to start the process:
1. import josx.platform.rcx.*; 2. import josx.robotics.*; 3. 4. class TrilobotMain { 5. public static void main(String [] args) { 6. Motor.A.setPower(7); 7. Motor.C.setPower(5); 8. TimingNavigator nav = new TimingNavigator(Motor.C, Motor.A, 5.475f, 4.03f); 9. 10. Behavior b1 = new Move(nav); 11. Behavior b2 = new LeftBump(nav); 12. Behavior b3 = new RightBump(nav); 13. Behavior b4 = new GoHome(nav); 14. 15. Behavior [] bArray = {b1, b2, b3, b4}; 16. Arbitrator arby = new Arbitrator(bArray); 17. arby.start(); 18. } 19. }
WARNING
Lines 6, 7, and 8 should be tailored to your specific robot. These variables are dependent on relative motor strengths, battery level, and other factors.
That's it! Notice all four Behavior objects share the same Navigator object (nav). Little by little we have built up many simple steps to create some relatively complex behavior. Upload the code to Trilobot and see how well it performs. Hopefully, after 30 seconds, Trilobot will come close to the point of origin. Now let's examine the accuracy of the results we can achieve with TimingNavigator.