The code that I sent was, as you found, not quite right but good enough to get students thinking about how threads work. The GoForward class that works without pausing is:

class GoForward implements Behavior {
Thread runThread; public boolean takeControl() {
       return true;
   }
public void suppress() {
       runThread.interrupt();
       MyMotors.stop();
   }
public void action() {
       runThread = Thread.currentThread();
       MyMotors.goForward(1000);
   }
}

Note that you have to be careful if you use multiple movement commands in your action method. For example, goForward is defined as:

 public static void goForward(int tim) {
   Motor.A.forward();
   Motor.C.forward();
   try {
     Thread.sleep(tim);
   } catch (Exception e) {
   }
   Motor.A.coast();
   Motor.C.coast();
 }

The problem is that if you put 2 or more of these commands in action(), the suppress() is only going to take out one of them. So, my RIS Roverbot big block methods are not useful in a behavior-based program. What I would do instead is overload goForward, etc., with a parameter-less method that does not contain the sleep. Then put them all together inside of one try-catch block (alternatively, you could have goForward throw an exception but that seems messier to me). For example, the action() method would be:

 public void action() {
   try {
     MyMotors.goForward();
     Thread.sleep(1000);
     MyMotors.spinLeft();
     Thread.sleep(1000);
  } catch (Exception e) {
  }
 }

Note that I probably should use an InterruptedException instead of the generic one but I'm lazy about typing.

Again, good luck,

wjr

Gary Stoneman wrote:
Thank you William for the prompt reply, your code was very much appreciated.

The solution you provide is agonisingly close. After limited testing (its quite late here now, so I will do some more testing in the morning) it seems that if you have many operations in the action task in the GoForward class then the program only responds to the touch sensor being activated once it has finished all of the code in the action task. I need the code to immediately branch to the action task in the HitWall no matter how far in we are in the 'main' behaviour. Is this possible?

The odd thing is, that when the touch sensor is activated the robot seems to pause as if it recognises this, but it then just continues executing the code in the GoForward behaviour.

Again this is after limited testing so it may well be my fault, I will attach the two modified classes so you can see the kind of effect I'm trying to see. (And see if its just my bad programming causing the problem :p ).

Thanks again for your help
Gary Stoneman

William J Rust wrote:
Attached are two classes, BehaviorMazeRunner and Arbitrator. BehaviorMazeRunner is just what the name suggests, a maze runner program that uses behaviors. Note that I don't have a sensor listener because the arbitrator queries the sensors at about the same rate as the listener thread does so using a listener doesn't buy you much. I also noticed as I reviewed the program that I don't explicitly initialize the sensors. That is a theoretical problem but I know that this program works so it isn't a practical one. What you are describing seems like a problem that a K-State professor had with Arbitrator (and which I just replicated while testing to make sure that BehaviorMazeRunner more or less works). When I tried BMR with the standard 2.1.0 Arbitrator, it didn't loop. With the code I've attached, it does.

Good luck and happy trails,

wjr

Gary Stoneman wrote:
Hi,

I'm trying to program my robot to do some simple task. Basically all the robot needs to do is run a set of ten instructions (consisting of: move forward for 2 seconds, move backward, turn right, turn left) in a loop for all eternity. However whilst doing this it needs a sensor listener to determine when one touch sensor is activated. When this happens another separate set of ten instructions should be run. After these new instructions have been completed we return to executing the first set of ten instructions.

So basically the robot will roam around using a random set of ten instructions forever, until it hits something, then it runs a different set of instructions once, then returns back to the first block of instructions.

I have currently tried using behaviors, but i cannot get the 'main' behavior to loop. If we take the simple bumper car example (in the lejos tutorial) I effectively want to loop the DriveForward routine indefinitely, but in such a way that we are continually looping through instructions, so it may go forward, turn left, froward, turn right and it just keeps looping. And of course in a way where it still responds to the touch sensor being activated.

Can anybody offer some ideas on how to solve this problem (hopefully I've explained myself well :-) ) ? Any help will be truly appreciated.

Many Thanks
Gary Stoneman


-------------------------------------------------------
This SF.Net email is sponsored by xPML, a groundbreaking scripting language that extends applications into web and mobile media. Attend the live webcast and join the prime developer group breaking into this new coding territory! http://sel.as-us.falkag.net/sel?cmd=lnk&kid=110944&bid=241720&dat=121642
_______________________________________________
Lejos-discussion mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/lejos-discussion


------------------------------------------------------------------------

package MyRobot;

import josx.platform.rcx.*;
import josx.robotics.*;

public class BehaviorMazeRunner {
/** * This maze runner works by using a technique called behaviors. A * behavior is implemented as a Java class. This file contains 3 separate * classes instead of the usual one class per file. The first class is * BehaviorMazeRunner and, like the other maze runners, it simply contains * a main method so it is a program. The other two classes implement the * Behavior interface, which simply means that these classes implement the * methods as defined by Behavior, i.e. takeControl, suppress and action. * takeControl is a method called to determine if an instance of a Behavior * wants to run. For example, if a sensor is pressed indicating that the * robot has run into a wall then the Behavior that deals with running into * a wall wants to be called; its takeControl method would return true. The * action method is called after an instance of a Behavior has returned * true. That is, after the robot has run into a wall, the action routine * is called to respond to that event. suppress is called when a higher * priority Behavior wants to take control. Priorities are assigned to * Behaviors by the order they are listed when the behavior array is * created, the leftmost Behavior having the lowest priority. Finally, the * lowest priority Behavior, in this case move, should always want to take * control since it will be interrupted when higher priority Behaviors
      * want to take control.
      */

    public static void main(String[] args) {
        Behavior turnRight = new HitWall(HitWall.TURN_RIGHT);
        Behavior turnLeft = new HitWall(HitWall.TURN_LEFT);
        Behavior move = new GoForward();
        Behavior[] behaviorArray = {move, turnLeft, turnRight};
        Arbitrator arbitrator = new Arbitrator(behaviorArray);
        arbitrator.start();
    }
}

/**
* HitWall is a Behavior for a robot when it encounters a wall. The constructor * takes an argument to set up for a wall on the right, TURN_LEFT, or a wall on
 * the left, TURN_RIGHT.
 */

class HitWall implements Behavior {
public static int TURN_RIGHT = 1;
    public static int TURN_LEFT = 2;
private int direction = TURN_LEFT; HitWall(int direction) {
        this.direction = direction;
    }
/** * A method required by the Behavior interface, the instance of the * Behavior will take control iff the appropriate sensor is pressed.
     */
public boolean takeControl() {
        return (direction == TURN_LEFT) ?
            Sensor.S3.readBooleanValue() :
            Sensor.S1.readBooleanValue();
    }
/** * A method required by the Behavior interface, the instance of the * Behavior will stop the motors if is preempted by a higher priority
     * Behavior.
     */
public void suppress() {
        MyMotors.stop();
    }
/** * A method required by the Behavior interface, the instance of the * Behavior will execute the action if the Behavior has taken control.
     */
         public void action() {
        MyMotors.goBackward(500);
        if (direction == TURN_LEFT) {
            MyMotors.turnLeft(500);
        } else {
            MyMotors.turnRight(500);
        }
    }
}

class GoForward implements Behavior {
/** * A method required by the Behavior interface, the instance of the * Behavior always returns true since this is the default Behavior. That is, * at least one Behavior must always be willing to run. That Behavior must * be the lowest priority Behavior because anything lower than the default
     * Behavior will never be asked if it wants to take control.
     */
public boolean takeControl() {
        return true;
    }
/** * A method required by the Behavior interface, the instance of the * Behavior will stop the motors if is preempted by a higher priority * Behavior. Since this is the default Behavior, any other Behavior taking
     * control was cause this method to be called.
     */
public void suppress() {
        MyMotors.stop();
    }
/** * A method required by the Behavior interface, the instance of the * Behavior will execute the action if the Behavior has taken control.
     */
         public void action() {
        MyMotors.goForward(1000);
    }
}
------------------------------------------------------------------------

// package josx.robotics;
package MyRobot;

import josx.robotics.*;
import josx.platform.rcx.*;
/**
* Arbitrator controls which behavior should currently be active in
* a behavior control system. Make sure to call start() after the
* Arbitrator is instantiated.
* @see Behavior
* @version 0.21  28-April-2003
*/
/**
 *  Modified by Masaaki Mizuno ([EMAIL PROTECTED]) and
 *  Letchumanan Muthaiah ([EMAIL PROTECTED])
 *  15-May-2003
 *
 *  This implementation is based on the time triggered design.
 *  In leJOS, each sensor can be registered with a listener object.
 *  A native thread (implemented inside the virtual machine)
* checks all the sensors (hardware) once every 3msec. When it has detected * any change in the hardware state, it wakes up the listener thread, and the
 *  listener thread in turn invokes associated methods in the registered
 *  listener objects.
 *  Therefore, no sensor events arrive faster than once every 3msec.
 *  The Arbitrator object uses this fact to implement the time-triggered
* design. The primary thread (PT) (arbitrator thread) wakes up every 3msec
 *  to work through the takeControl() methods of the behavior objects.
 */

public class Arbitrator {

   private Behavior [] behavior;
   private final int NONE = 99;
   private int totalBehaviors;
   private int currentBehavior;
   private BehaviorAction actionThread;

   // arbitratorLock is used by the arbitrator thread to sleep
   private Object arbitratorLock;
   /**
   * Allocates an Arbitrator object and initializes it with an array of
* Behavior objects. The highest index in the Behavior array will have the * highest order behavior level, and hence will suppress all lower level * behaviors if it becomes active. The Behaviors in an Arbitrator can not
   * be changed once the arbitrator is initialized.<BR>
* <B>NOTE:</B> Once the Arbitrator is initialized, the method start() must be
   * called to begin the arbitration.
   * @param behavior An array of Behavior objects.
   */
   public Arbitrator(Behavior [] behaviors) {
      this.behavior = behaviors;
      currentBehavior = NONE;
      actionThread = new BehaviorAction();
      arbitratorLock = new Object();
      totalBehaviors = behavior.length - 1;
      actionThread.start();
   }

   /**
   * This method starts the arbitration of Behaviors.
   * Modifying the start() method is not recomended. <BR>
* Note: Arbitrator does not run in a seperate thread, and hence the start()
   * method will never return.
   */
  public void start() {
    while(true) {
// Check through all behavior.takeControl() starting at highest level
    // behavior
      for(int i = totalBehaviors; i >= 0; --i) {
    if (behavior[i].takeControl()) {
if ((i > currentBehavior) || (actionThread.getCurrent() == NONE)) {
         if (i > currentBehavior) behavior[currentBehavior].suppress();
             currentBehavior = i;
actionThread.execute(i); // because execute is a synchronized method,
            // it is executed after the action thread
            // has returned from action() and set current to NONE
             break;
        }
      }
      }
      synchronized(arbitratorLock) {
        try {
              arbitratorLock.wait(3); // sleep 3 msec
        }catch(InterruptedException e){}
      }
    }
  }

   /**
   * This class handles the action() methods of the Behaviors.
   * We call this thread the action thread
   */
   private class BehaviorAction extends Thread {
      private int current = NONE;
      private int i;

      public void run() {
    while(true) {
      synchronized(this) {
         if (current != NONE) {
   LCD.showNumber(current); LCD.refresh(); // debugging statement
           behavior[current].action();
           current = NONE;
         }
      }
      Thread.yield(); // since Action thraead is not time triggered,
                          //  it is necessary to yield.
    }
      }

     public synchronized void execute(int index) {
// current is shared by both the arbitrator thead and the action thread
       current = index;
     }
        public int getCurrent() {
// even though this accesses shared variable "current", // this cannot be a synchornized method since while action() is executed
    // the action thread holds "this" lock.
    return current;
    }
  }
}
------------------------------------------------------------------------

No virus found in this incoming message.
Checked by AVG Free Edition.
Version: 7.1.385 / Virus Database: 268.4.0/305 - Release Date: 08/04/2006
------------------------------------------------------------------------

package MyRobot;

import josx.platform.rcx.*;
import josx.robotics.*;

public class BehaviorMazeRunner {
/**
     * This maze runner works by using a technique called behaviors. A
     * behavior is implemented as a Java class. This file contains 3 separate
     * classes instead of the usual one class per file. The first class is
     * BehaviorMazeRunner and, like the other maze runners, it simply contains
     * a main method so it is a program. The other two classes implement the
     * Behavior interface, which simply means that these classes implement the
     * methods as defined by Behavior, i.e. takeControl, suppress and action.
     * takeControl is a method called to determine if an instance of a Behavior
     * wants to run. For example, if a sensor is pressed indicating that the
     * robot has run into a wall then the Behavior that deals with running into
     * a wall wants to be called; its takeControl method would return true. The
     * action method is called after an instance of a Behavior has returned
     * true. That is, after the robot has run into a wall, the action routine
     * is called to respond to that event. suppress is called when a higher
     * priority Behavior wants to take control. Priorities are assigned to
     * Behaviors by the order they are listed when the behavior array is
     * created, the leftmost Behavior having the lowest priority. Finally, the
     * lowest priority Behavior, in this case move, should always want to take
     * control since it will be interrupted when higher priority Behaviors
     * want to take control.
     */
public static void main(String[] args) {
        //Behavior turnRight = new HitWall(HitWall.TURN_RIGHT);
        Behavior hit = new HitWall();
        Behavior move = new GoForward();
        Behavior[] behaviorArray = {move, hit};
        Arbitrator arbitrator = new Arbitrator(behaviorArray);
        arbitrator.start();
    }
}

/**
 * HitWall is a Behavior for a robot when it encounters a wall. The constructor
 * takes an argument to set up for a wall on the right, TURN_LEFT, or a wall on
 * the left, TURN_RIGHT.
 */

class HitWall implements Behavior {
//public static int TURN_RIGHT = 1;
    //public static int TURN_LEFT = 2;
//private int direction = TURN_LEFT; HitWall() {
        //this.direction = direction;
    }
/**
     * A method required by the Behavior interface, the instance of the
     * Behavior will take control iff the appropriate sensor is pressed.
     */
public boolean takeControl() {
        return Sensor.S1.readBooleanValue();
    }
/**
     * A method required by the Behavior interface, the instance of the
     * Behavior will stop the motors if is preempted by a higher priority
     * Behavior.
     */
public void suppress() {
        Motor.A.stop();
        Motor.C.stop();
    }
/**
     * A method required by the Behavior interface, the instance of the
     * Behavior will execute the action if the Behavior has taken control.
     */
public void action() {
        //Here is were we respond to the sensor
        turnRight();
        moveForward();
        turnRight();
    }
public void moveForward() {
        Motor.A.forward();
        Motor.C.forward();
        try{Thread.sleep(1800);}catch(Exception e) {}
        Motor.A.stop();
        Motor.C.stop();
    }
public void moveBackward() {
        Motor.A.backward();
        Motor.C.backward();
        try{Thread.sleep(1800);}catch(Exception e) {}
        Motor.A.stop();
        Motor.C.stop();
    }
public void turnRight() {
        Motor.A.forward();
        Motor.C.backward();
        try{Thread.sleep(1183);}catch(Exception e) {}
        Motor.A.stop();
        Motor.C.stop();
    }
public void turnLeft() {
        Motor.A.backward();
        Motor.C.forward();
        try{Thread.sleep(1183);}catch(Exception e) {}
        Motor.A.stop();
        Motor.C.stop();
    }
}

class GoForward implements Behavior {
/**
     * A method required by the Behavior interface, the instance of the
     * Behavior always returns true since this is the default Behavior. That is,
     * at least one Behavior must always be willing to run. That Behavior must
     * be the lowest priority Behavior because anything lower than the default
     * Behavior will never be asked if it wants to take control.
     */
public boolean takeControl() {
        return true;
    }
/**
     * A method required by the Behavior interface, the instance of the
     * Behavior will stop the motors if is preempted by a higher priority
     * Behavior. Since this is the default Behavior, any other Behavior taking
     * control was cause this method to be called.
     */
public void suppress() {
        Motor.A.stop();
        Motor.C.stop();
    }
/**
     * A method required by the Behavior interface, the instance of the
     * Behavior will execute the action if the Behavior has taken control.
     */
public void action() {
        //This should always be run in a continuous loop <- which works
        //However we only respond to a sensor after we have executed all of 
this code
        moveForward();
        turnLeft();
        turnLeft();
        turnLeft();
        moveForward();
        //Down to here, then we respond
    }
public void moveForward() {
        Motor.A.forward();
        Motor.C.forward();
        try{Thread.sleep(1800);}catch(Exception e) {}
        Motor.A.stop();
        Motor.C.stop();
    }
public void moveBackward() {
        Motor.A.backward();
        Motor.C.backward();
        try{Thread.sleep(1800);}catch(Exception e) {}
        Motor.A.stop();
        Motor.C.stop();
    }
public void turnRight() {
        Motor.A.forward();
        Motor.C.backward();
        try{Thread.sleep(1183);}catch(Exception e) {}
        Motor.A.stop();
        Motor.C.stop();
    }
public void turnLeft() {
        Motor.A.backward();
        Motor.C.forward();
        try{Thread.sleep(1183);}catch(Exception e) {}
        Motor.A.stop();
        Motor.C.stop();
    }
}

------------------------------------------------------------------------

// package josx.robotics;
package MyRobot;

import josx.robotics.*;
import josx.platform.rcx.*;
/**
* Arbitrator controls which behavior should currently be active in
* a behavior control system. Make sure to call start() after the
* Arbitrator is instantiated.
* @see Behavior
* @version 0.21  28-April-2003
*/
/**
 *  Modified by Masaaki Mizuno ([EMAIL PROTECTED]) and
 *  Letchumanan Muthaiah ([EMAIL PROTECTED])
 *  15-May-2003
 *
 *  This implementation is based on the time triggered design.
 *  In leJOS, each sensor can be registered with a listener object.
 *  A native thread (implemented inside the virtual machine)
 *  checks all the sensors (hardware) once every 3msec. When it has detected
 *  any change in the hardware state, it wakes up the listener thread, and the
 *  listener thread in turn invokes associated methods in the registered
 *  listener objects.
 *  Therefore, no sensor events arrive faster than once every 3msec.
 *  The Arbitrator object uses this fact to implement the time-triggered
 *  design.  The primary thread (PT) (arbitrator thread) wakes up every 3msec
 *  to work through the takeControl() methods of the behavior objects.
 */

public class Arbitrator {

   private Behavior [] behavior;
   private final int NONE = 99;
   private int totalBehaviors;
   private int currentBehavior;
   private BehaviorAction actionThread;

   // arbitratorLock is used by the arbitrator thread to sleep
   private Object arbitratorLock;
   /**
   * Allocates an Arbitrator object and initializes it with an array of
   * Behavior objects. The highest index in the Behavior array will have the
   * highest order behavior level, and hence will suppress all lower level
   * behaviors if it becomes active. The Behaviors in an Arbitrator can not
   * be changed once the arbitrator is initialized.<BR>
   * <B>NOTE:</B> Once the Arbitrator is initialized, the method start() must be
   * called to begin the arbitration.
   * @param behavior An array of Behavior objects.
   */
   public Arbitrator(Behavior [] behaviors) {
      this.behavior = behaviors;
      currentBehavior = NONE;
      actionThread = new BehaviorAction();
      arbitratorLock = new Object();
      totalBehaviors = behavior.length - 1;
      actionThread.start();
   }

   /**
   * This method starts the arbitration of Behaviors.
   * Modifying the start() method is not recomended. <BR>
   * Note: Arbitrator does not run in a seperate thread, and hence the start()
   * method will never return.
   */
  public void start() {
    while(true) {
    // Check through all behavior.takeControl() starting at highest level
    // behavior
      for(int i = totalBehaviors; i >= 0; --i) {
        if (behavior[i].takeControl()) {
          if ((i > currentBehavior) || (actionThread.getCurrent() == NONE)) {
             if (i > currentBehavior) behavior[currentBehavior].suppress();
                     currentBehavior = i;
actionThread.execute(i); // because execute is a synchronized method,
                        // it is executed after the action thread
                        // has returned from action() and set current to NONE
                     break;
            }
          }
      }
      synchronized(arbitratorLock) {
            try {
                        arbitratorLock.wait(3); // sleep 3 msec
            }catch(InterruptedException e){}
      }
    }
  }

   /**
   * This class handles the action() methods of the Behaviors.
   * We call this thread the action thread
   */
   private class BehaviorAction extends Thread {
      private int current = NONE;
      private int i;

      public void run() {
        while(true) {
          synchronized(this) {
             if (current != NONE) {
   LCD.showNumber(current); LCD.refresh(); // debugging statement
               behavior[current].action();
               current = NONE;
             }
          }
          Thread.yield(); // since Action thraead is not time triggered,
                          //  it is necessary to yield.
        }
      }

     public synchronized void execute(int index) {
     // current is shared by both the arbitrator thead and the action thread
       current = index;
     }
public int getCurrent() { // even though this accesses shared variable "current", // this cannot be a synchornized method since while action() is executed
    // the action thread holds "this" lock.
        return current;
    }
  }
}



-------------------------------------------------------
This SF.Net email is sponsored by xPML, a groundbreaking scripting language
that extends applications into web and mobile media. Attend the live webcast
and join the prime developer group breaking into this new coding territory!
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=110944&bid=241720&dat=121642
_______________________________________________
Lejos-discussion mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/lejos-discussion

Reply via email to