onsdag den 24. november 2010

Lego lab lesson 10: Behaviour based architecture

Lego Lab lesson 10: Behaviour based architecture


Date: 18 - November - 2010
Duration: 3 hours
Participants: Kim Bjerge, María Soler, José Antonio Esparza

Goals of the lab session

Get experience working with Behaviour-based architecture by implementing simple actions grouped as behaviours.
Investigate the class organization in the lejOS related to the behaviour concept.
Explore how the behaviour structure could be extended.

Bumper car

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


When the detecting wall behaviour is acting, the robot is not reacting as fast as in the case it is not.

The turning back action implemented in the behaviour is blocking. That implementation can be seen in the following code snippet:


public void action()
{
Motor.A.rotate(-180, true);// start Motor.A rotating backward
Motor.C.rotate(-360); // rotate C farther to make the turn
}

Even though the exit button is being pressed, the robot will have to finish the full turn before actually executing the exit instruction.

  1. Both DriveForward and DetectWall have a method takeControl that are called in the Arbitrator. Investigate the source code for the Arbitrator and figure out if takeControl of DriveForward is called when the triggering condition of DetectWall is true.


In the source code of the arbitrator function, we have found a relevant piece of code that influences its behaviour. This part of the code is shown below:

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

The maximum priority variable is set with the number of behaviours registered in the system. The take control operation will be called no matter if there is a behaviour with higher priority currently waiting. This implies that, take control in the drive forward behaviour is called even when the trigger action in the waiting list of behaviours.
  1. Implement a third behavior, Exit. This behavior should react to the ESCAPE button and call System.Exit(0) if ESCAPE is pressed. Exit should be the highest priority behavior. Try to press ESCAPE both when DriveForward is active and when DetectWall is active. Is the Exit behavior activated immediately ? What if the parameter to Sound.pause(20) is changed to 2000 ? Explain.


The exit behaviour is implementing the Behaviour interface as follows:

class ExitBehavior implements Behavior
{

public boolean takeControl()
{
return Button.ENTER.isPressed();
}

public void suppress()
{
}

public void action()
{
System.exit(0);
}
}

As it can be seen, the suppress method has not been implemented. The reason is that, since this thread is the highest priority thread, it is never going to be interrupted.


Once the escape button has been pressed, the robot is exiting the program much faster than in the previous case. In the case the passed parameter to the method Sound.pause is set to 2000, the powering off time is increased considerably. This is because the Sound.pause method is blocking, and this blocking time has been multiplied by 100 once the modification has been introduced.

  1. To avoid the pause in the takeControl method of DetectWall a local thread in DetectWall could be implemented that sample the ultrasonic sensor every 20 msec and stores the result in a variable distance accessible to takeControl. Try that. For some behaviors the triggering condition depends on sensors sampled with a constant sample interval. E.g. a behavior that remembers sensor readings e.g. to sum a running average. Therefore, it might be a good idea to have a local thread to do the continuous sampling.


The below code snippet shows how the DetectWall method is separated into a thread on each own. This change did make the response from the exit behaviour much faster and steady.

class DetectWall implements Behavior
{
private TouchSensor touch;
private UltrasonicSensor sonar;
private boolean _ultrasonicDetected = false;
private Detect detect;
public DetectWall()
{
touch = new TouchSensor(SensorPort.S4);
sonar = new UltrasonicSensor(SensorPort.S1);
detect = new Detect();
detect.setDaemon(true);
detect.start();
}
private void pingWaitDetect()
{
sonar.ping();
Sound.pause(20);
LCD.drawInt(sonar.getDistance(),10,2);
_ultrasonicDetected = (sonar.getDistance() <>
}

public boolean takeControl()
{
return touch.isPressed() || _ultrasonicDetected;
}
....
private class Detect extends Thread
{
public void run()
{
while(true)
{
pingWaitDetect();
}
}
}
}
  1. Try to implement the behavior DetectWall so the actions taken also involve to move backwards for 1 sec before turning.


See code snippet below - the result is a system that is blocked while avoiding wall.

class DetectWall implements Behavior
{
....
public void action()
{
Motor.A.backward(); // Move backward 1 sec. before turning
Motor.C.backward();
Sound.pause(1000);
Motor.A.rotate(-180, true);// start Motor.A rotating backward
Motor.C.rotate(-360); // rotate C farther to make the turn
}
  1. Try to implement the behavior DetectWall so it can be interrupted and started again e.g. if the touch sensor is pressed again while turning.


We have chosen to move the motor control from the action of the DetectWall behavior to the separate thread that also samples the ultrasonic sensor. In this way the action will not be blocking and it is possible to extend the separate thread to control the moving backward and turning in a state machine that can be interrupted.

class DetectWall implements Behavior
{
....
public void action()
{
synchronized (detect)
{
detect.startAction();
}
while (!detect.isCompleted())
{
Thread.yield(); //don't exit before completing stateMachine
}
}

The DetectWall action starts the action and waits until it is completed. The detect class implements the new thread that starts the moving backward and turning. This behavior incorporate the checking of the touch sensor that will stop the action and reset the state machine.

private class Detect extends Thread
{
private int _counter;
private int _state = 0;
private void startAction()
{
_state = 1;
}
private boolean isCompleted()
{
return (_state == 0);
}

private void pingWaitDetect()
{
sonar.ping();
Sound.pause(20);
LCD.drawInt(sonar.getDistance(),10,2);
_ultrasonicDetected = (sonar.getDistance() <>
}
public void stateMachine()
{
synchronized (this)
{
if (touch.isPressed())
{
Motor.A.stop(); // Move backward 1 sec. before turning
Motor.C.stop();
_state = 0;
}
switch (_state)
{
case 0: // Idle
break;
case 1: // Start moving backward
Motor.A.backward(); // Move backward 1 sec. before turning
Motor.C.backward();
_counter = 0;
_state = 2;
break;
case 2: // Moving backward 1 sec
if (_counter++ == 25) // 1 sec.
_state = 3;
break;
case 3: // Rotating
Motor.A.rotate(-180, true);// start Motor.A rotating backward
Motor.C.rotate(-360); // rotate C farther to make the turn
_state = 0;
break;
}
}
}

public void run()
{
while(true)
{
pingWaitDetect();
// Called every 20 ms
stateMachine();
}
}

}

The robot in action can be seen in the following video: http://www.youtube.com/watch?v=8n5_UJtGNxc

Source code for this exercise can be found here:
http://code.google.com/p/josemariakim/source/browse/#svn/trunk/lab10_1/src

Motivation functions


Referring to Thiemo Krink’s motivation function in [2] how would it be possible to change the priority of the behaviors dynamically? When we take a closer look into the implementation of the Arbitrator in the Lejos API we see that a separate thread (Monitor) is started that continuously call takeControl and calls suppress. The priority is statical but could be change so takeControl instead returns a integer value defined by the individuals behaviors. This integer value could then be used to define the priority of the next behavior to be activated. The behavior with the highest value will then be activated each time through the loop in the Arbitrator. If takeControl returns zero nothing should happen.

Below is listed the code snippet of the Monitor thread that must be changed to select the dynamic prioritized behavior.

private class Monitor extends Thread
{
.....
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();

Conclusions

In this lab exercise we have worked with the concept of behaviour-based architecture. We started by trying to understand how this approach is implemented in the lejOS operative system. We realized that the behaviour interface has changed since the firsts versions of the lejOS, and we were missing some documentation and comments in the source code to understand its behaviour. Finally, we tried to explain how the behaviour interface together with the arbitrator could be modified to support dynamical behaviour priorities, based on Krinks motivation function [2]. Krinks ideas constitute an interesting link between computer science and biology very attractive for the robotic field experimentation.

References


[1], The leJOS Tutorial, Behavior Programming

[2], Thiemo Krink(in prep.). Motivation Networks - A Biological Model for Autonomous Agent Control.

Ingen kommentarer:

Send en kommentar