Todays goals:

Are as described in the lesson 10 exercises notes:

  • Exercise 1 – BumperCar
  • Exercise 2 – Motivation Functions

Additional “Optional” Goals

  • No additional goals

The Plan

In this lesson we will mainly look into creating and changing various behavior in Max N00b. In Exercise 1 we will look into the Arbiter class and Behavior interface provided by the leJOS framework. In Exercise 2 we will refactor the code provided by our teacher and make it into a compositional design architecture.

Exercise 1 – BumperCar

Plan

First we will make Max run the BumperCar program found here. Max will be equipped with a bumper and an ultrasonic sensor. This will make us able to make the following experiments to investigate the functions of the Arbitrator:

  1. Press touch sensor and keep it pressed – then see what happens.
  2. Implementing a third behavior, Exit.
  3. Investigate the source code for the Arbitrator and figure out if takeControl of DriveForward is called when DetectWall is true.
  4. Implementation of a local thread in DetectWall to sample Ultrasonic Sensor data.
  5. Look at implementing a local thread for continuous sampling of sensor data.
  6. Implement behavior of DetectWall to include 1 second of driving backwards before turning.
  7. Implement interruption in DetectWall.

Experiment

1. Press the touch sensor and keep it pressed. What happens ? Explain.

To test this, we have built Max as an Express Bot that has two touch sensors. We found from running the code provided that it throws an exception. We looked into it and found that the code had not instantiated the touch sensor. So we decided to fix this by adding AvoidWall.java (for the Ultrasonic Sensor) into the project and added it as a Behavior in the Arbitrator class (BumperCar.java). Furthermore, we modified the DetectWall.java (which is our behaviour class for the touch sensors) by removing the UltraSonic sensor from the class and adding an additional Touch Sensor, as Max was built with two. This does actually create two behaviors.

This means we have divided the sensors into two different classes with the same behavior. However, we find this more flexible for future experiments as we can easily modify the behavior for each sensor type.

2. Implementing a third behavior, Exit. To test this, we will implement a third behavior in the code. We will create a new class, called Exit, which implements the Behavior interface. We will then hit Escape when he is using the DriveForward behavior, the AvoidWall behavior, and the DetectWall behavior.

The Exit behavior is supposed to call System.Exit(0), which terminates the program running on Max.

3. Investigate the source code for the Arbitrator and figure out if takeControl of DriveForward is called when DetectWall is true. In this experiment we will see if the takeControl of DriveForward is called when DetectWall is true. We will do this by investigate the source code for the leJOS Arbitrator.

4. Implementation of a local thread in DetectWall to sample Ultrasonic Sensor data. In this experiment, we will create a local thread which samples the readings from the Ultrasonic Sensor in a variable. Since the getDistance() method from the Ultrasonic Sensor has a delay built into it, the takeControl() method, the Arbitrator used in BumperCar will be delayed when it gets to that one. By creating a local variable for the sensor data and use that in the takeControl() method, we will avoid the delay.

5. Implement behavior of DetectWall to include 1 second of driving backwards before turning. In this experiment we will see if we can make Max drive backwards for 1 second when he is driving in the DetectWall behavior. For doing this, we will modify the DetectWall.java and then test it on Max by having him drive forwards until he encounters an obstacle.

6. Implement interruption in DetectWall. This experiment will be tested in the same way as the experiment before. However, this time, we will modify the code so Max can be interrupted in his DetectWall behavior when he starts turning. For doing this we will modify the DetectWall.java class.

Programming

1. Press the touch sensor and keep it pressed. What happens ? Explain.

The project code can be found here. As explained in the Experiment part, we have taken the Ultrasonic sensor and given it it’s own behavior class. Therefore, we have actually added a 3rd behavior already (But we will go more into detail about this in the next experiment). The interesting code in this part is looking at the three methods, takeControl(), suppress() and action():

  • action(): The code in this method represents the tasks the robot performs in the behavior.
  • takeControl(): A boolean that indicates if the behavior should take control of the robot.
  • suppression(): The code in this method causes the current behavior to stop.

For DetectWall.java:

public class DetectWall implements Behavior {
  private TouchSensor touch1, touch2;
  private boolean suppressed = false;

  public DetecWall(SensorPort port1, SensorPort port2 ){
    touch1 = new TouchSensor( port1 );
    touch2 = new TouchSensor( port2 );
  }

  public boolean takeControl() {
    return touch1.isPressed() || touch2.isPressed();
  }

  public void suppress() {
    suppressed = true;
  }

  public void action() {
    suppressed = false;

    Motor.A.rotate(-180, true);
    Motor.C.rotate(-360, true);

    while( Motor.C.isMoving() && !suppressed ){
      Thread.yield();
      Motor.A.stop();
      Motor.C.stop();
    }
  }
}

As can be seen, the takeControl() method checks if the touch sensors have been pushed. If either (or both), the DetectWall.java class will take control of the robot and do what is stated in action(), which makes the robot drive backwards (hence the values in Motor.A and Motor.B being negative) and rotate (Motor.C.rotate is set to (-360, true). The boolean true is added so we can interrupt (or suppress) the thread, making the Motor not having to finish turning -360 degrees.

2. Implementing a third behavior, Exit. We have added a new class, called Exit that implements the Behavior interface. With this, we will add the behavior to an Array of behaviors, as seen in the BumperCar.java class:

public class BumperCar {

  public static void main(String [] args) {
    Behavior b1 = new DriveForward();
    Behavior b2 = new AvoidWall(SensorPort.S2);
    Behavior b3 = new HitWall(SensorPort.S1, SensorPort.S3);
    Behavior b4 = new Exit(Button.ESCAPE);
    Behavior [] bArray = {b1, b2, b3, b4};

    Arbitrator arby = new Arbitrator(bArray);
    arby.start();
  }
}

With this setup, we will add the highest prioritized behavior in the end of the Array. The Arbitrator will then run through the list and know which behavior to prioritize highest. By doing this we can now Exit the program when running. See results section for further details.

3. Investigate the source code for the Arbitrator and figure out if takeControl of DriveForward is called when DetectWall is true. None in this experiment.

4. Implementation of a local thread in DetectWall to sample Ultrasonic Sensor data. To implement a local thread, we are using the standard Java framework for handling threads:

public AvoidWall(SensorPort port ){
  sonar = new UltrasonicSensor( port );
  Thread t = new Thread(new Runnable(){
    public void run(){
      while(true){
        distance = sonar.getDistance();
      }
    }
  });
   t.start();
 }

In this way, we create a thread in the constructor of our AvoidWall behavior class. This thread will keep running even though the behaviors change because we never tell it to stop. This means the variable distance will be used as the check in the takeControl() method.

5. Implement behavior of DetectWall to include 1 second of driving backwards before turning. To modify the behavior in DetectWall, we have changed the action() method in this way:

public void action() {
  suppressed = false;
  Motor.A.backward();
  Motor.C.backward();
  Delay.msDelay(1000);

  Motor.A.flt();
  Motor.C.flt();

  Motor.A.rotate(-180, true);
  Motor.C.rotate(-360, true);

  while( Motor.C.isMoving() && !suppressed ){
    Thread.yield();
    Motor.A.stop();
    Motor.C.stop();
  }
}

In this way, we make Max drive backwards and then delay the thread for 1000ms. This means the last commando (drive backwards) will be executed for 1 second. We then make the motors float (not stop) to make a more smooth transition between moving backwards and turning. We notice, though, that by using the Delay.msDelay(1000), it is not possible to interrupt it while driving backwards because we delay the main thread.

6. Implement interruption in DetectWall. To add the interrupt modification in the behavior, we changed the action() method in the following way:

public void action() {
  active = true;
  suppressed = false;
  Motor.A.backward();
  Motor.C.backward();
  Delay.msDelay(1000);
  Motor.A.flt();
  Motor.C.flt();

  Motor.A.rotate(-180, true);
  Motor.C.rotate(-720, true);

  while( Motor.C.isMoving() && !suppressed ){
    if (takeControl()){
      suppress();
    }
  }

  Thread.yield();
  Motor.A.stop();
  Motor.C.stop();
}

Thereby while Max is turning, suppress() is called, which stops and immediately restarts action(). This can be seen in the results section.

Results

1. Press the touch sensor and keep it pressed. What happens ? Explain.

A video of Max with the touch sensor pressed can be seen here. When we press the front bumper with touch sensors, Max’ takeControl() in the DetectWall.java class is called. And we can see that it interrupts the standard behaving provided from DriveForward.java.

2. Implementing a third behavior, Exit. Three videos of Max having the System.exit(0) behavior implemented interrupting the other behaviors can be found here:

  • DriveForward
  • DetectWall
  • AvoidWall.

An interesting thing we found, however, is how the Arbitrator works. All the various behaviours are in an Array that the Arbitrator will go through. When we push the ESCAPE-button, the program will not terminate at first. However, if we hold the ESCAPE-button down, the program will terminate correctly. This is caused by the fact that when the Arbitrator goes through the list of behaviors, and you push the ESCAPE-button, the Arbitrator might be in the middle of one of the other behaviors (for instance DetectWall), and you release the button too fast for the Arbitrator to switch behavior. This means the Exit-behavior will never be called correctly. However, if you hold the ESCAPE-button down for a while, the Arbitrator will have enough time to register the takeControl() method in the Exit-behavior.

3. Investigate the source code for the Arbitrator and figure out if takeControl of DriveForward is called when DetectWall is true.

As is stated in the Arbitrator source code:

//FIND HIGHEST PRIORITY BEHAVIOR THAT WANTS CONTROL
synchronized (this) {
  _highestPriority = NONE;

  for (int i = maxPriority; i >= 0; i--){
    if (_behavior[i].takeControl()){
    _highestPriority = i;
    break;
  }
}

int active = _active;// local copy: avoid out of bounds error in 134
if (active != NONE && _highestPriority > active {
  _behavior[active].suppress();
}
}// end synchronize block - main thread can run now
  Thread.yield();
}

As seen in the code snippet above, the Arbitrator from leJOS finds the behaviour with the highest priority and set it as the active one. This is a continuous process, meaning that if a new behavior with a higher priority than the active one wants control, the previous behavior is suppressed and the new takes control of the robot. All of this means that since the DriveForward behavior is lower prioritized than the DetectWall behavior, DriveForward can not get control while the DetectWall behaviour wants control and the takeControl() method is not called.

4. Implementation of a local thread in DetectWall to sample Ultrasonic Sensor data. After implementing a local thread that handles the Ultrasonic sensor the time needed to find the highest priority behavior is reduced, since there is no longer any delay from the Ultrsonic sensor’s getDistance method. However as shown in this video, the difference is not noticable. The reason for this may be that the delay, which has been reduced, is so small, that it cannot be seen by the naked eye.

5. Implement behavior of DetectWall to include 1 second of driving backwards before turning. We have made Max drive backwards in 1 second as seen in this video. We have made it so he cannot be interrupted when he is driving backwards for 1 second.

6. Implement interruption in DetectWall. We have implemented an interrupt in the DetectWall class that can interrupt while Max turns. As seen in the video, the interrupt is first possible after Max has moved backwards.

Exercise 2 – Motivation Functions

Plan

We will modify the Arbitrator class, BumperCar class and Behavior interface, provided from the exercise notes, to follow a compositional design architecture. This means that behaviours will be extracted to individual classes and all classes and interfaces arranged in appropriate packages.

Finally, the sensors used will be provided to the individual behaviours as parameters on creation instead of the behaviours themselves initializing them. This will allow for multiple behaviours to utilize the same sensor without causing errors as a result of having the same sensor initialized into several instances. Based on the leJOS architecture, you have one sensor per port – and if you have two instances of the same sensor, you can ask the sensor to do two different things simultaneously which can cause errors. If we only keep one instance of the same sensor, the different behavior will be queued up.

Experiment

After making the changes to the programming, we also made a change to the DriveForward behavior so that it uses the same ultrasonic sensor as the DetectWall behaviour by setting the driving speed from 800 dpm (degrees per minute) to 400 dpm when in 75cm of an obstacle.

As seen in this video Max drives towards the obstacle and when in 75cm of it, it decelerates and finally takes evasive action.

Programming

As seen in the following code segment, this is how the main method of the BumperCar now looks and is the only thing it now contains, with all of the behaviours in their own separate classes.

public static void main(String[] args){
  Motor.A.setSpeed(800);
  Motor.C.setSpeed(800);
  UltrasonicSensor ultraSensor = new UltrasonicSensor(SensorPort.S2);
  TouchSensor touchSensor = new TouchSensor(SensorPort.S1);

  Behavior b1 = new DriveForward(ultraSensor);
  Behavior b2 = new DetectWall(touchSensor, ultraSensor);
  Behavior b3 = new Exit();
  Behavior[] behaviorList = {b1, b2, b3};

  Arbitrator arbitrator = new Arbitrator(behaviorList);

  LCD.drawString("Bumper Car",0,1);
  Button.ENTER.waitForPress();
  arbitrator.start();
 }

All the code can be found here.

Results

The changes made means that the code now allows for:

  • Use of the same sensor between behaviours
  • More easily interchangeable behaviours
  • More readable code because of classes being divided into packages and separating behaviours from the main class.

Further improvements could be made by introducing a drive controller instead of calling the motors directly, and also changing the behavior list from a defined list of e.g. three behaviours to a more dynamic list that allows for the addition to and removal of behaviour at run time.

General

Problems

The biggest problem we have experienced in this exercise, is as described in exercise 1, when behaviours cause a delay in the code, making the behaviours unresponsive for a short time. This is, however, not the only reason for unresponsiveness, and the reasons are limitless, but to sum it up, everything that causes the system to wait for a new command to perform will impact the responsiveness, meaning that the code that finds the next command should be threaded; the code that handles the execution of commands should be threaded; the sensor data that is needed for the behaviours should be threaded, and so on.

Conclusion

Behaviours are very interesting to use and allows for a rich interaction with the robot, but they do have their issues (as described in the problems section just before).