Wow thanks for the insight. Its all very interesting to see how this all
works. I currently have a solutions that seems to work adequately (there
may well be some small lapses where things don't happen as they should,
but it seems to function correctly). The solution was to add flags as
you say, but just use them internally in the hitWall class. Using a
thread for the hitWall we simply introduce a alreadyExecuted flag
(called hit in the code) this is true if we are already executing the
routine, if this is the case then we simply suppress the current
behaviour and reset the flag. This of course means that the line of
execution returns to the goForward class, which will most likely force
the robot to strike an obstacle again and trigger another hitWall routine.
The only thing to improve on now would be instead of returning to the
goForward after we suppress a hitWall (where suppress is triggered by
another touch) we should instead rerun the hitWall immediately. Whilst I
endeavour to correct this the current solution will work for likely 99%
of the scenarios the robot will encounter.
I'll post the code again in case you want to have a look at it, its
pretty straight forward (and quite messy as I have left lots of
commented out code in). I'll have a look at your suggestions and see if
I can come out with a working program.
Thanks
Gary
William J Rust wrote:
What you are pointing out is the fundamental flaw of the behavior
paradigm: a behavior cannot interrupt itself. The rationale for this
is pretty clear: the takeControl may not be cleared until well into
the action method and takeControl is called every few milliseconds.
You'd wind up blowing the stack every time takeControl wasn't cleared
immediately.
Therefore, what you are looking for is a kludge to work around this
problem. Essentially, the action method has become an interrupt
service routine, ISR, and ISRs must return very quickly while action
methods don't. So, you need to decouple the two. It's pretty
straightforward to do. Simply add a sensor listener and a cancel
behavior that's the highest priority.
The sensor listener should set a flag to indicate that the sensor has
been pressed. Recall that stateChanged gets called twice for each
push-release cycle so make sure you discard the release calls. If you
want your action method to be called for each press then you should
use a counter instead of a flag. The cancel behavior takes control
whenever the listener flag is set. By taking control, it suppresses
the HitWall behavior (note that the HitWall suppress needs to be
updated to keep track of the runThread just like the goForward
behavior. That wasn't necessary before because HitWall was the
highest priority behavior but it isn't anymore). The action method of
the cancel behavior is to simply to clear, or decrement, the listener
flag and set a second flag to run the HitWall behavior. The first
thing that the HitWall action method should do is clear that flag.
Note that these flags will have to be passed in some sort of object
to the constructors of the various behaviors in order for this to work.
That's all there is to it. Again, good luck.
wjr
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 hit1 = new HitWall();
Behavior move = new GoForward();
Behavior[] behaviorArray = {move, hit, hit1};
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, SensorListener {
Thread runaThread;
boolean hit = false;
//public static int TURN_RIGHT = 1;
//public static int TURN_LEFT = 2;
//private int direction = TURN_LEFT;
HitWall() {
//this.direction = direction;
hit = false;
Sensor.S1.addSensorListener(this);
}
/**
* A method required by the Behavior interface, the instance of the
* Behavior will take control iff the appropriate sensor is pressed.
*/
public boolean takeControl() {
/* if( (hit == true) ) {
hit = false;
Sound.twoBeeps();
suppress();
//runaThread.interrupt();
return false;
}else { */
//hit = true;
//Sound.beep();
return Sensor.S1.readBooleanValue();
//}
}
public void stateChanged(Sensor bumper, int oldValue, int newValue) {
if(bumper.readBooleanValue() == true) {
if (hit == true) {
suppress();
//action();
}
}
}
/**
* 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() {
runaThread.interrupt();
hit = false;
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() {
runaThread = Thread.currentThread();
try {
hit = true;
//Here is were we respond to the sensor
turnRight();
Thread.sleep(1183);
moveForward();
Thread.sleep(1800);
turnRight();
Thread.sleep(1183);
hit = false;
} catch (Exception e) {
}
//runaThread = null;
}
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(1160);}catch(Exception e) {}
//Motor.A.stop();
//Motor.C.stop();
}
}
class GoForward implements Behavior {
Thread runThread;
/**
* 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() {
runThread.interrupt();
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
runThread = Thread.currentThread();
try {
moveForward();
Thread.sleep(1800);
moveForward();
Thread.sleep(1800);
moveBackward();
Thread.sleep(1800);
} catch(Exception w) {
//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;
}
}
}