Peek Under the Hood
In order to get a decent understanding of what is going on, it is important that we look under the hood to see what really happens. With leJOS, this can be accomplished by using the emulation software that is bundled with the distribution. Here we will write a very simple program that sets the power of motor A to maximum (7) and then starts the motor in the forward direction. The code is as follows:
import josx.platform.rcx.*;
public class SimpleTest {
public static void main (String[] aArg) {
Motor.A.setPower(7);
Motor.A.forward();
}
}
The source is compiled as follows to the byte code lejosc SimpleTest. Then, by running the emulation binary on the byte code, the following output is produced. Running the emulation software this way will not produce the linked binaries, but just display the instruction set.
C:\Tech\Article\Lego\Dev\Legos\src>emu-lejos.exe SimpleTest
& ROM call 3: 0x1A4E (8192, 4, 7)
& ROM call 3: 0x1A4E (8192, 1, 7)

Figure 3.
As can be seen from the above image, the method calls setPower() and forward() to motor A are translated into native calls. The rom.h header file, which can be found under LEJOS_HOME/rcx_impl, gives us clues as to how these instructions are translated into native calls. For instance, the instruction 0x1A4E (8192, 4, 7) is a call into the following function:
static inline void control_motor (short code, short mode, short power)
{
__rcall3 (0x1a4e, code, mode, power);
}
The three parameters are code, mode, and power. From the comments of the rom.h source code, the following are the possible values for code, mode, and power.
- code=2000: Control motor 0
- code=2001: Control motor 1
- code=2002: Control motor 2
- modes: 1=forward, 2=backward, 3=stop, 4=float
- power: valid are 0..7, values are taken modulo 8
Going back to the source code, the first line of code, Motor.A.setPower(7), translates into & ROM call 3: 0x1A4E (8192, 4, 7), which is a function call into control_motor, with the code as 2000 (8192 as hex is 2000), mode as 4 (flt), and the power level as 7. The second method call, Motor.A.forward(), is translated into & ROM call 3: 0x1A4E (8192, 1, 7). This is delegated to the native system as a function call into control_motor, with the code as 2000 (8192 as hex is 2000), mode as 1 (forward), and power level as 7. As it can be observed, no query is made to find out what the power level is between the first call and the second call. This is because the power level of 7 is cached as an instance variable in the Motor class, and hence, when the second call is made, the cached value is used to send the right power level to the underlying native subsystem.
Test Drive
Finally, it is now time to write some code and see how everything fans out. To start with, we will write a very simple program that will enable our robot to move forward until it encounters an obstruction. When stopped by the obstruction, the robot will back up, turn to the right and go forward again. This process continues until the robot is stopped by the user pressing the green Run button on the RCX brick. Essentially, the core purpose of this robot is to wander aimlessly. But in spite of such a non-productive purpose, the means of getting to the end will help us understand how to effectively make use of the leJOS API.
We will use the behavior model provided by the leJOS architecture to implement the above requirement. The concept of behavior is actually simple and extensible. Conventional robot programming is always thought as a flow control process. For example, the following diagram specifies what we are trying to achieve, using traditional flowcharts.

Figure 4.
The Behavior API encapsulates behavior in an object and exposes callback methods that enable an external agent to control the behavior of the robot. This also helps developers to add, modify, or remove behaviors easily without visiting the other areas of the code. The Behavior interface has three important methods and they are:
boolean takeControl(): This method returns a Boolean value. A value of true indicates that this behavior should become active. For example, if we are trying to model a touch sensor, this would return true if the sensor is touched; otherwise it should return false.
void action(): When the method takeControl() returns true, it means the behavior should be made active. This method will be called when the behavior becomes active. This is where the core logic of the expected behavior goes.
void suppress(): This purpose of this method is to cancel whatever the action() intends to do. This method can also be used to update any data before the behavior completes.
The above three methods define the core control areas for behavior. Any class that implements the Behavior interface is required to provide concrete implementation for these methods. Once various behaviors for a robot are defined and implemented, the next step is to have an arbitrator work with the behavior objects. It's the responsibility of an arbitrator to call the appropriate method in a defined sequence.
The arbitrator is a concrete class that regulates how and when a behavior becomes active. The arbitrator defines a constructor that takes in an array of behavior objects. The priority level of the behavior objects is determined by the array index. For example, the behavior object that is at the end of the array has higher priority over the behavior object that is at the beginning of the array. So when two behavior objects return takeControl() to be true at the same time, which action() to call is determined based on which behavior object has an higher array index. For the arbitrator to start arbitrating, the start() methods should be called.
In our example we will define three different behavior objects. The primary purpose of the MoveForward object is to define the behavior of moving forward. The three methods are implemented as follows.
public class MoveForward implements Behavior {
public boolean takeControl() {
return true;
}
public void suppress() {
Motor.A.stop();
Motor.C.stop();
}
public void action() {
Motor.A.forward();
Motor.C.forward();
}
}
The method takeControl() returns true all the time because that is what we want to do--keep moving forward. The action() lets the motors A and C rotate in the forward direction. suppress() stops both the motors.
Our next behavior is to steer away from obstacle. While the robot is in motion, if an obstacle is encountered, then the touch sensor is activated. When this happens, the robot backs up and then turns to the right. The three behavioral methods for this class are as follows:
public class SteerObstacle implements Behavior {
public boolean takeControl() {
return Sensor.S2.readBooleanValue();
}
public void suppress() {
Motor.A.stop();
Motor.C.stop();
}
public void action() {
Motor.A.backward();
Motor.C.backward();
try {
Thread.sleep(1000);
} catch (Exception e) {}
Motor.A.stop();
try {
Thread.sleep(1000);
} catch (Exception e) {}
Motor.C.stop();
}
}
In this case, the takeControl() method return true or false depending on whether the touch sensor is touched or not. The action() method starts the motors A and C in the backward direction for 1000 milliseconds and then stops the A motor. The C motor is continued backwards for another 1000 milliseconds. With one motor stationary and the other motor rotating, we get a rotational movement of the robot towards the right.
The last behavior object is to stop the motors when the green Run button on the RCX is pressed. The three behavioral methods are as follows.
public class Stop implements Behavior, ButtonListener {
private boolean buttonPressed = false;
public Stop(){
Button.RUN.addButtonListener(this);
}
public boolean takeControl() {
return buttonPressed;
}
public void suppress() {
Motor.A.stop();
Motor.C.stop();
}
public void action() {
Motor.A.stop();
Motor.C.stop();
}
public void buttonPressed(Button b){
if ( buttonPressed )
buttonPressed = false;
else
buttonPressed = true;
}
public void buttonReleased(Button b){}
}
In this case, we have implemented a ButtonListener to find out if the green Run button of the RCX was pressed. In the button is pressed, then we return a true and if the button is pressed again, we return a false. A listener to the button could be added as Button.RUN.addButtonListener(this); and two callback methods, buttonPressed() and buttonReleased(), need to be implemented. The action() and suppress() methods in this behavior simply stop both the A and C motors.
Finally, we have implemented three behaviors. To arbitrate the behavior, we implement the following arbitration code.
public class Wanderer {
public static void main(String[] args) {
Wanderer wanderer = new Wanderer();
wanderer.wander();
}
private void wander(){
Behavior b1 = new MoveForward();
Behavior b2 = new Stop();
Behavior b3 = new SteerObstacle();
Behavior [] bArray = {b1, b2, b3};
Arbitrator arby = new Arbitrator(bArray);
arby.start();
}
}
As it can be seen, we simply create three behavior objects and one arbitrator object. Of course, the arbitrator knows what behaviors to arbitrate based on the array of behavior objects passed into its constructor. Finally, the arbitrator is started by the start() method.
Now that the source code for the application is written, we will have to compile it, link it to create a downloadable binary, and upload the binary into RCX for execution. This is accomplished as follows.
Compile the source using the provided lejosc batch file.
>lejosc com\article\lejos\sample1\MoveForward.java
>lejosc com\article\lejos\sample1\SteerObstacle.java
>lejosc com\article\lejos\sample1\Stop.java
>lejosc com\article\lejos\sample1\Wanderer.java
Link the source to create the binary file, called Wanderer.bin. The name can be anything, but for sake of clarity, it's recommended that you use the filename "<class name&rt; .bin."
>lejoslink -o Wanderer.bin com.article.lejos.sample1.Wanderer
Finally, upload the binary using the provided batch file.
>lejosdl Wanderer.bin
|
Figure 5. Click image for full-size screen shot.
|
In the above image, notice the message "Tower not responding." This happened because the RCX was not turned on. However, sometimes a developer might encounter issues during the transfer process. When this happens, turning the RCX off and then back on seems to fix the problem.
When the code is downloaded into the RCX, pressing the Run button causes the program to be executed. When this happens, the robot starts moving in the forward direction until it encounters an obstacle. When it does, the touch sensor is activated, which causes the robot to back up for one second and then turn to right. After turning to the right, the robot continues forward until it encounters another obstacle. Pressing the green Run button will now cause the robot to stop. Pressing the Run button will result in robot starting to wandering again.
Conclusion
In this article, we just scratched the tip of the iceberg. The leJOS API provides a rich set of functionality to explore other areas like communication, navigation, vision, and speech. leJOS and its peer TinyVM let Java enthusiasts program their robots in their favorite language, with the only limitation being the extent of the imagination.
Resources