In this lab session we will experiment with the LeJOS behavior architecture.
Duration of activity: 7 hours
Group members participating: Ole Rasmussen, Kasper Føns
Goal
To complete the exercises for Lego Lab 10.
Plan
Since our goal is to complete the exercises for this weeks lab the plan will be to complete each exercise in sequence:
- BumperCar
- Testing what happens when the touch sensor is pressed
- Implement an exit behavior
- Investigate arbitrator
- Move sampling of the ultrasonic sensor to another thread
- Modifying DetectWall behavior to first move backwards
- Modifying DetectWall to allow activation while turning
- Implementing dynamic prioritization - Motivation functions
1. BumperCar
1.1 Touch sensor pressed, what happens?
We have installed the sample program "BumperCar" from LeJOS. We have a ultrasonic sensor in front and a touch sensor dangling that we can manually touch to simulate the robot bumping into something (Figure 1).
![]() |
| Figure 1 - Our robot design. Notice that only the front ultrasonic sensor is used and the touch sensor is manually pressed. |
There are three types of reactions when pressing the touch sensor:
- Pressing and releasing touch sensor very fast
The robot will turn right and then go forward again. Looking into the source code of the program, this happens because the DetectWall behavior want's to take control, but when the DriveForward behavior exits the action method, DetectWall no longer want's to take control. Since the DriveForward behavior contains a bug, where it only stops the left motor when being interrupted the robot turns left until the left motor is again told to move forward. This is fixed by stopping both right and left motors in the DriveForward behavior when interrupted. - Pressing and holding the touch sensor for a while
Here the robot will move backwards for a little while and then turn right. This happens because the DetectWall behavior has higher priority than the DriveForward behavior. The DriveForward behavior gets suppressed and the DetectWall behavior gets control. It is the one that backs off and turns. - Pressing and keep holding it down
Here the robot will keep driving backwards and turning. This happens because DetectWall always wants control and it has higher priority.
1.2 Implementing an exit behavior
Here we should implement a behavior which wants to have control when the ESCAPE button is pressed, and when its action is called, will call System.exit(). Notice that this kind of behavior is a bit strange, and really should be implemented using a listener on a Button outside of the Arbitrator class.
At the lecture we were shown that the DetectWall behavior does not respect calls to the suppress method. Therefore if the DetectWall behavior is executing its action method, then when pressing the ESCAPE button nothing will happen, since DetectWall ignores all calls to suppress. We need to implement correct suppression handling to fix this issue.
Our implementation of the exit behavior can be seen in Figure 2.
![]() |
| Figure 2 - Exit behavior implementation. Wants to take control if ESCAPE is pressed, exits the system if action is called. |
We tested the implementation and it behaved as we expected. When the DriveForward is active and we quickly touch the ESCAPE button, DriveForward is stopped, but then the ExitBehavior no longer wants to take control, just as in section 1.1. This was as expected. We also expected that the DetectWall behavior would not respect the suppress method call, which we also saw in our testing.
We modified DetectWall behavior to respect the suppress method call, Figure 3 shows our final implementation which worked beautifully (i.e., it was suppressed immediately when the ESCAPE button was pressed).
![]() |
| Figure 3 - Implementation of DetectWall which respects call to the suppress method. |
1.3 Investigate Arbitrator
We are asked to investigate if the takeControl method of DriveForward is called when DetectWall's takeControl method returns true. We will do this by examining the Arbitrator implementation.
Inside the Arbitrator there are two threads running. One is the main thread which calls the Arbitrator.start() method. This thread is responsible for calling the action() method of a behavior. The other thread is a thread inside the Arbitrator called Monitor. The monitor thread samples the takeControl method on the registered behaviors and stores what behavior should become active next. See Figure 4 for the code.
The monitor calls the takeControl() method on each behavior starting from the highest prioritized one. As soon as a behavior returns true, the monitor stores this in a field and stops checking lower prioritized behaviors. This means that if DetectWall returns true in its takeControl() implementation, the takeControl method of the DriveForward behavior will not be called. That answers this question.
Inside the Arbitrator there are two threads running. One is the main thread which calls the Arbitrator.start() method. This thread is responsible for calling the action() method of a behavior. The other thread is a thread inside the Arbitrator called Monitor. The monitor thread samples the takeControl method on the registered behaviors and stores what behavior should become active next. See Figure 4 for the code.
![]() |
| Figure 4 - The Monitor thread implementation inside the Arbitrator class. When a higher prioritized behavior wants to take control, lower ones are not asked. |
The monitor calls the takeControl() method on each behavior starting from the highest prioritized one. As soon as a behavior returns true, the monitor stores this in a field and stops checking lower prioritized behaviors. This means that if DetectWall returns true in its takeControl() implementation, the takeControl method of the DriveForward behavior will not be called. That answers this question.
1.4 Move ultrasonic sampling to separate thread
The takeControl method is obligated to return its results as fast as possible. Since the DetectWall behavior samples the ultrasonic sensor, it must wait approximately 25msec for a ultrasonic burst to be sent and received. This is too long and in this section we will move this sampling to a new thread, the UltrasonicSampler thread. Figure 5 shows the complete DetectWall implementation with our UltrasonicSampler thread inside.
![]() |
| Figure 5 - DetectWall with UltrasonicSampler to avoid waiting too much in the takeControl method. |
Notice that we have set the lastDistance to Integer.MAX_VALUE as a first value before the first sample is registered. Also notice that the field is declared volatile, which indicates that the value will be used by different threads. The UltrasonicSampler is set to be a daemon thread, so that the system exists correctly if the main thread stops. It works as expected, though the change can not be observed by looking at the robot running.
1.5 DetectWall to reverse a second before turning
Here we are asked to change DetectWalls action to first reverse for one second and then turn. This should be a straightforward implementation, but we need to consider that we can be suppressed doing both actions.
Our implementation can be seen in Figure 6.
![]() |
| Figure 6 - DetectWall action. Implements reverse for one second while still handling suppression calls. |
It worked as expected. We could suppress it and it went backwards for approximately one second. Notice that we removed BumperCar.leftMotor.rotate(-180, true) since this would only make it go reverse for another 180 tacho counts.
1.6 Modify DetectWall to allow second activation while turning
This time we are asked to allow the DetectWall behavior to activate while it is turning. We will implement this by suppressing ourselves, if the touch sensor is pressed, while we are active and turning. When we suppress ourselves we exit from the action method, after which the DetectWall behavior should once again be chosen in the Arbitrator. Our implementation can be seen in Figure 7.
It worked as expected.
![]() |
| Figure 7 - DetectWall with reactivation |
2. Implementing dynamic prioritization - Motivation functions
We have implemented the dynamic prioritization arbitrator as suggested by Thiemo Krink [1]. The algorithm is quite simple: first find the behavior with the maximum priority, and then only activate it if the priority is higher than the priority of the behavior previously selected for activation or if there's is no such previous behavior. The code for the arbitrator can be seen in Figure 8.
![]() |
| Figure 8 - The dynamic prioritization arbitrator |
For the behaviors the only interesting change is in the DetectWall class (DriveForward has a constant priority). The basic idea of the takeControl method is to return a high(er) priority when the behavior is currently turning than otherwise. Assume DriveForward has a priority of x. Assume also that the base priority of DetectWall is x < y. When the bumper sensor is pressed the new priority z of DetectWall should be such that z > x while setting a variable 'turning' = true. Then, when the bumper sensor is pressed again and if 'turning' is true then the final priority of DetectWall becomes q where q > z. The result is that the arbitrator will ask DetectWall to supress itself to allow the higher-priority instance of DetectWall to run. The code for the DetectWall can be seen in figure 9.
![]() |
| Figure 9 - Dynamically prioritized DetectWall behavior |
References
[1], Thiemo Krink(in prep.). Motivation Networks - A Biological Model for Autonomous Agent Control.









