torsdag den 29. marts 2012

Lab 7

Date: 29.03.2012
Duration: 3 hours
Group members: Stefan, Thomas and Jeppe

Goal

LEGO car that exhibits several behaviors
Build the 9797 Lego car as described on page 28 to 30 in the NXT booklet.
Observe the car and try to identify
1) the different behaviors observed and
2) the circumstances that triggers the different behaviors.

Behaviors as concurrent threads
3) Look into the three classes RandomDrive, AvoidFront and PlaySound and try to identify how the triggering condition is implemented and
4) how the actions for each behavior is implemented.

5) Try to watch the car with only RandomDriver and
6) then with only the two first threads active to observe the behaviors more clearly.

Class Behavior
7) What is the purpose of making these threads daemon threads as done in the constructor of the Behavior class? - To make sure that all threads are killed when the main thread is killed.

8) How is the "suppressCount" integer in the Behavior.java class used to implement a suppression mechanism to obtain controlled access to the motors.
9) Compare this with the arbiter of Fred Martin.

Add a behavior "Drive toward light"
10) Try to implement the "Drive toward light" of Lesson 6 as a behavior thread and include it in the subsumption mechanism of SundCar.java. This include to
11) figur out under what conditions the behavior should be triggered and the priority of the behavior in the subsumption architecture.

Plan

Build the robot as described on page 28 - 30 in the NXT booklet.
Then start solving the tasks outlined in section Goal. We divided the tasks between:
Jeppe solves 3-7, Thomas solves 8 - 9, Stefan solves 10 - 11 and 1 - 2.

Results

1) The robot seems to execute its commands a bit sequential, however the hierarchy does work.
First it moves a bit random, then it plays a sound. Avoid front obviously has a higher priority than the move random.

2) Circumstances that triggers different behaviors:
Sound is triggered every 10 second, avoid front is triggered when an obstacle is encountered.
The random movement is active when the two above is not.

3-4) Implementation of the triggering mechanism.
The tree classes RandomDrive, PlaySound and AvoidFront all extends the Behavior class.
The first thing that happens in each of the three classes run method is that they call suppress. This method will suppress all the other behaviors lower in the hierarchy than the current. If one of the threads is already suppressed, it will still start executing its commands. The thread is not blocked. This could easily be a problem. We will elaborate further on this problem in (8).

5) With only RandomDrive, the robot moves slowly around and does not avoid any objects
6) Without RandomDrive the robot does not move unless an object is placed in front of it. It will still play the sounds every 10 second.



7. What is the purpose of making these threads daemon threads as done in the constructor of the Behavior class?

A daemon thread is automatically terminated when all non daemon threads have finished their executions. The purpose of making the threads in the behavior class daemon is that when someone presses the Escape button on the NXT, the “Main”-thread finishes execution and all behavior threads automatically closes down. If this was not the case the robot would continue running its program – in effect becoming stuck running the program.

8. How is the "suppressCount" integer in the Behavior.java class used to implement a suppression mechanism to obtain controlled access to the motors?

When a higher priority behavior executes it’s suppress method all lower priority behaviors have their suppressCount integer increased by 1 recursivly. Whenever a behavior tries to send a command to the motors using methods forward, backward and stop it is only actually executed whenever the behavior’s suppressCount is 0. This way it is ensured that only the highest priority behavior wanting to access the motors actually accesses the motors. Whenever a behavior calls its release method all lower priority behaviors have their suppressCount decreased by 1 recursivly. This may allow a lower priority behavior to execute its motor control commands.    
The pattern has a problem in that it allows a subset of a behavior’s sequence of motor commands to be executed instead of all of them. For instance in the SoundCar example the robot may be very close to a wall when the play sounds behavior is activated. While the robot is break dancing the avoid wall behavior keeps executing its instructions and again and again and may resume control of the motors after it has executed the following instructions:

backward(70,70);
drawString("b");
delay(1000);

And just before it executes:
forward(0,80);
drawString("f");
delay(800);

Resulting in the robot maybe driving into the obstacle!


11) figur out under what conditions the behavior should be triggered and the priority of the behavior in the subsumption architecture.
In the original LightSensorCar, the loop would run every 250 ms, to get a new reading from the light sensor, and then set the power accordingly.


This was changed, so the loop would run, when the sensors detected a light source above a certain threshold. This would make the car try to drive towards a light source, if one that is powerful enough is detected.


Then the priority of the light method would be set to just above the random walk. This way the robot would play sounds, and avoid walls, and turn randomly, as long as no source of bright light was detected. When a source of bright light was detected however, the car would begin to turn towards this, while still trying to avoid bumping into walls, and still playing sounds.

9. Compare this with the arbiter of Fred Martin:

The arbiter pattern is a centralized way of archiving the same as the subsumption pattern. In the arbiter pattern a class is created that runs through all threads / objects wanting to use the motors and executes the class’ instructions with the highest priority. The subsumption pattern archives the same in a decentralized way using recursive calls. 

Conclusion

We tried working with a behavior hierarchy and observed the robot. The suppression hierarchy does work, but yields a potential issue if the robot suppresses a lower hierarchy action in the middle of its command sequence. 
This started a good discussion in the group.
Consider that I am holding a cup of hot coffee in my hand. Then my primary task is to keep the coffee in the cup. If I then want to see what time it is on my wrist watch, then I might end up poring out the coffee when I turn my arm.
So a hierarchy of suppression always comes with some cost on the lower ranking processes.
I.e. they might be suppressed in the middle of a sequence of commands.

References


Lab 7, http://legolab.cs.au.dk/DigitalControl.dir/NXT/Lesson7.dir/Lesson.html

torsdag den 8. marts 2012

Lab 6


Date: 8. march 2012 & 13.march 2012
Duration of activity: 6 hours
Group members participating: Thomas, Stefan and Jeppe

Goal
  • Build three variations of the Braitenberg robots.1) One that is attracted to sound.2) One that is afraid of silence (it goes backward when it is quiet).
    3) A robot that is afraid of light (turns away when it senses light).
Plan

Vehicle 1 Assignment
Start with vehicle 1. As sensor use a soundsensor to implement a vehicle that moves faster forward the louder the environment sound is.
Use two motors to drive the vehicle and apply the same power to the two motors. The main issue is how to map sensor values to motor power. If raw sensor values are used, the mapping should be from the range 0, 1023 to the motor power range, i.e. 0, 100.
Also try to map to motor powers between -100,100 and interpret negative motor powers as moving the motors backwards. How would you describe the resulting behaviours?

Vi startede med at lave en robot, i følge arbejdstegningerne 9797 side 8-22. På robotten placerede vi desuden en lyd-sensor, som pegede frem foran robotten. Vores robot fik uploadet følgende program til sig:

public static void main(String [] args)throws Exception {
  //exitlistener
  Button.ESCAPE.addButtonListener(new ButtonListener() {
    public void buttonPressed(Button b) {
      System.exit(0);
    }
    public void buttonReleased(Button b) {}
  });
  int first_reading = SensorPort.S1.readRawValue();
  int min = first_reading;
  int max = first_reading;
  while (true) {
    int crr_sound = SensorPort.S1.readRawValue();
    crr_sound = 1023 - crr_sound;
    if (crr_sound < min)
      min = crr_sound;
    if (crr_sound > max)
      max = crr_sound;
    int power = (int) ((crr_sound / 1023F) * 100);
    LCD.clear();
    LCD.drawString("Crr:" + crr_sound,0,0);
    LCD.drawString("Max:" + max,0,1);
    LCD.drawString("Min:" + min,0,2);
    LCD.drawString("Power: " + power, 0, 3);
    LCD.refresh();
    MotorPort.A.controlMotor(power,1);
    MotorPort.B.controlMotor(power,1);
    Thread.sleep(250);
  }
}

Programmet tager raw value fra lydsensoren og mapper dissetil power værdier mellem 0 og 100. Vi observerede, at alle værdier i rawintervallet mellem 0 og 1023, så ud til at blive brugt – ved at kigge på max og min på displayet af robotten i løbet af vores forsøg. Raw værdierne var dog høje når der var næsten ingen lyd og små når der var meget støj. Dette var lige netop modsat af, hvordan vi ønskede at give motorerne power. Så derfor mappede vi raw value til power ved at tage 1023 (max raw værdien) og trække vores rawmåling fra denne. Dette gav høje værdier når støjen var stor og små når der var stille, som ønsket. For at få de mappede raw values i intervallet 0 til 100 dividerede vi disse med 1023 og gangede med 100.

Robotten bevægede sig ikke med mindre, der var ”meget” støj i nærheden af den. Vi skulle klappe højt eller snakke med høj stemme 5cm fra mikrofonen for at den bevægede sig. Vi kunne observere, at hvis vi ikke snakkede foran robotten, men bare lod baggrundsstøjen blive målt, at power værdien lå mellem 2-3. Hvis vi snakkede sammen 30cm fra robottens mikrofon lå power værdien i intervallet 20-29. Dette er som tidligere nævnt til forelæsning ikke nok til at overvinde den statiske friktion, der er mellem hjulene og underlaget.

Power mellem -100 og 100:
Med samme robot som i forrige sektion ændrede vi robottens program således at robotten bakkede, når der var stille og kørte fremad, når den opfangede lyder under 511 raw value. Dette gav anledning til følgende kode:

public static void main(String [] args)throws Exception {
  //exit listener
  Button.ESCAPE.addButtonListener(new ButtonListener() {
    public void buttonPressed(Button b) {
      System.exit(0);
    }
    public void buttonReleased(Button b) {}
  });
  SoundSensor s_sensor = newSoundSensor(SensorPort.S1);
  int first_reading = SensorPort.S1.readRawValue();
  int min = first_reading;
  int max = first_reading;
  while (true) {
    int crr_sound = SensorPort.S1.readRawValue();
    //split in middle of interval,high sounds gives positive crr_sound, low sounds
      gives negative crr_sound
    crr_sound = 511 - crr_sound;
    if (crr_sound < min)
      min = crr_sound;
    if (crr_sound > max)
      max = crr_sound;
    int power = (int) ((crr_sound / 511F) * 100);
    if (power > 0) {
      MotorPort.A.controlMotor(power,1);
      MotorPort.B.controlMotor(power,1);
    } else {
      power = power*(-1);
      MotorPort.A.controlMotor(power,2);
      MotorPort.B.controlMotor(power,2);
    }
    LCD.clear();
    LCD.drawString("Crr:" + crr_sound,0,0);
    LCD.drawString("Max:" + max,0,1);
    LCD.drawString("Min:" + min,0,2);
    LCD.drawString("Power: " + power, 0, 3);
    LCD.refresh();
    Thread.sleep(250);
}

Ved udførsel af programmet, bakkede robotten hele tiden, når den ikke målte andet end baggrundsstøj. Når vi snakkede højt 5cm foran mikrofonen kørte robotten frem (60-70 power) i kort tid og bakkede derefter hurtigt igen (80-90 power).


Vehicle 2a & 2b Assignment


In the two other vehicles use e.g. light sensors. Again make sure to define the mappings between sensor range and motor power. Try with both exibitory and inhibitory connections and describe the resulting behaviours.

Først tilsluttede vi en lys-sensor, for at finde et interval. For at finde den lavest mulige værdi holdt holdt vi en finger helt hen til sensoren, for at blokere så meget lys som muligt for den. For at finde den højest mulige værdi, lyste vi med en optisk mus ind i sensoren. Dette gav os et interval på 222-895.

To lyssensorer blev så påmonteret fronten af bilen med 10 cm mellemrum, således at begge pegede ligefrem.

Bilen blev kodet således, at hver motor får en power med en værdi på 50, hvortil der bliver lagt følgende udregning:
(Læsning - 222)/(895-222) * 50 + 50.

I model 2a blev power for venstre motor udregnet for læsningen på venstre sensor og tilsvarende for højre motor og højre sensor, mens det omvendte var tilfældet for model 2b.

Beskrivelse af bilernes opførsel med exibitory:
Disse forsøg blev foretaget i køkkennet i lego-lab, hvor vi med lukket dør og slukket lys, kunne undgå unødige lyskilder. Hefter blev der på midten af gulvet sat en lyskilde.

Bil 2a, der skulle kører væk fra lys, blev startet og sat til at køre frem mod en lyskilden. Fra start kørte bilen umiddelbart ligeud med meget lav hastighed, men efterhånden som den kom nærmere lyskilden, begyndte den at dreje til siden mens den generelle hastighed steg, hvorved den kørte forbi lyskilden.

Det så ud til, at lys-sensoren er meget "nærsynet", og den skulle inden for få centimeter af lyskilden, før der kommer nogle tydeligt observerbare udsving i power til motorerne.

Bil 2b, der skulle køre mod lys, blev fra start sat på en måden så den ved at køre direkte frem, ville køre lige forbi lyskilden. Her kunne vi igen se, at når den kom inden for få centimeter af lyskilden, begyndte den at styre hen mod denne. Dog med så relativt svagt udsving, at den stadig kørte forbi lyskilden.

Beskrivelse af bilerne opførsel med inhibitory:
Her var udregningen for power: 100 - (Læsning - 222)/(895-222) * 50.

Bil 2a og 2b, her kørte bilen med fuld hastighed så længe den ikke befandt sig i nærheden af lyskilden. Så snart den kom inden for kort afstand af denne begyndte den igen kraftigt at svinge væk fra/svinge ind mod lys-kilden.

Observationer på bilerne med inhibitory forbindelser, var betydeligt mere interessante, idet de ikke sneglede sig afsted, så længe der ikke var en lyskilde.

Kode:

public class LightCar2Inhib {
  //Vehicle 2b inhibitory
  public static void main(String [] args) throws Exception {
    //exit listener
    Button.ESCAPE.addButtonListener(new ButtonListener() {
    public void buttonPressed(Button b) { System.exit(0);}
    public voidbuttonReleased(Button b) { }
  });
  LightSensor l_sensor = newLightSensor(SensorPort.S1);
  LightSensor r_sensor = newLightSensor(SensorPort.S2);
  int first_reading = SensorPort.S1.readRawValue();
  //Max: 895
  //Min: 222
  while (true) {
    float crr_left = (float)SensorPort.S2.readRawValue();
    float crr_right = (float) SensorPort.S1.readRawValue();
    crr_left = 1023 - crr_left;
    crr_right = 1023 - crr_right;
    int powerleft = 100 - (int)((crr_left -222F)/(895-222) * 50);
    int powerright = 100 - (int) ((crr_right - 222F)/(895-222) * 50);
    MotorPort.A.controlMotor(powerleft,1);
    MotorPort.B.controlMotor(powerright,1);
    Thread.sleep(250);
  }
}


public class LightCarInhib {
  //Vehicle 2a inhibitory
  public static void main(String [] args) throws Exception {
    //exit listener
    Button.ESCAPE.addButtonListener(new ButtonListener() {
    public void buttonPressed(Button b) { System.exit(0);}
    public void buttonReleased(Button b) { }
  });
  LightSensor l_sensor = newLightSensor(SensorPort.S1);
  LightSensor r_sensor = newLightSensor(SensorPort.S2);
  int first_reading = SensorPort.S1.readRawValue();
  //Max: 895
  //Min: 222
  while (true) {
    float crr_left = (float)SensorPort.S2.readRawValue();
    float crr_right = (float) SensorPort.S1.readRawValue();
    crr_left = 1023 - crr_left;
    crr_right = 1023 - crr_right;
    int powerleft = 100 - (int)((crr_left -222F)/(895-222) * 50);
    int powerright = 100 - (int) ((crr_right - 222F)/(895-222) * 50);
    MotorPort.A.controlMotor(powerright,1);
    MotorPort.B.controlMotor(powerleft,1);
    Thread.sleep(250);
  }
}

public class LightSensorCar{
  //Vehicle 2a exibitory
  public static void main(String [] args) throws Exception {
    //exit listener
    Button.ESCAPE.addButtonListener(new ButtonListener() {
      public void buttonPressed(Button b) { System.exit(0);}
      public voidbuttonReleased(Button b) { }
    });
    LightSensor l_sensor = newLightSensor(SensorPort.S1);
    LightSensor r_sensor = newLightSensor(SensorPort.S2);
    int first_reading = SensorPort.S1.readRawValue();
    //Max: 895
    //Min: 222
    while (true) {
      float crr_left = (float)SensorPort.S2.readRawValue();
      float crr_right = (float) SensorPort.S1.readRawValue();
      crr_left = 1023 - crr_left;
      crr_right = 1023 - crr_right;
      int powerleft = (int) ((crr_left - 222F)/(895-222) * 50) + 50;
      int powerright = (int) ((crr_right - 222F)/(895-222) * 50) + 50;
      MotorPort.A.controlMotor(powerright,1);
      MotorPort.B.controlMotor(powerleft,1);
      Thread.sleep(250);
    }
}


public class LightSensorCar2 {
  //Vehicle 2b exibitory
  public static void main(String [] args) throws Exception {
    //exit listener
    Button.ESCAPE.addButtonListener(new ButtonListener() {
      public void buttonPressed(Button b) { System.exit(0);}
      public voidbuttonReleased(Button b) { }
    });
    LightSensor l_sensor = newLightSensor(SensorPort.S1);
    LightSensor r_sensor = newLightSensor(SensorPort.S2);
    int first_reading = SensorPort.S1.readRawValue();
    //Max: 895
    //Min: 222
    while (true) {
      float crr_left = (float)SensorPort.S2.readRawValue();
      float crr_right = (float) SensorPort.S1.readRawValue();
      crr_left = 1023 - crr_left;
      crr_right = 1023 - crr_right;
      int powerleft = (int) ((crr_left - 222F)/(895-222) * 50) + 50;
      int powerright = (int) ((crr_right - 222F)/(895-222) * 50) + 50;
      MotorPort.A.controlMotor(powerleft,1);
      MotorPort.B.controlMotor(powerright,1);
      Thread.sleep(250);
    }
}

Min Max Light Values
I opsamler nu de sidste N sensor målinger for at have en indikation af max og min værdierne i det miljø som robotten arbejder i.
Vi har en liste af størrelse N, som løbende bliver opdateret.
Koden er vist herunder:

public class LightCar2Inhib {
  // Vehicle 2b inhibitory
  static float maxvalue = 895;
  static int noReadings = 80;
  static float minvalue = 222;
  static int pointer = 0;
  static int[] readings = new int[noReadings];
  static int readingssize = 0;

  public static void updateMaxMin() {
    if (readingssize < 1) return;
    maxvalue = 0;
    minvalue = 2000;
    for(int i = 0; i< readingssize;i++){
      if (maxvalue < readings[i])
        maxvalue = readings[i];
      if (minvalue > readings[i])
        minvalue = readings[i];
    }
  }

  public static void putNewReading(int valueright, int valueleft) {
    int removedValue = readings[pointer % noReadings];
    readings[pointer % noReadings] = valueright;
    pointer++;
    int removedValue2 = readings[pointer % noReadings];
    readings[pointer % noReadings] = valueleft;
    pointer++;
    if (removedValue == maxvalue
     || removedValue == minvalue
     || removedValue2 == maxvalue
     || removedValue2 == minvalue) { updateMaxMin(); }
  }

  public static void main(String[] args) throws Exception {
    // exit listener
    Button.ESCAPE.addButtonListener(new ButtonListener() {
      public void buttonPressed(Button b) {
        System.exit(0);
      }
      public void buttonReleased(Button b) {}
    });
    LightSensor l_sensor = new LightSensor(SensorPort.S1);
    LightSensor r_sensor = new LightSensor(SensorPort.S2);
    int first_reading = SensorPort.S1.readRawValue();
    while (true) {
      float crr_left = (float) SensorPort.S2.readRawValue();
      float crr_right = (float) SensorPort.S1.readRawValue();
      crr_left = 1023 - crr_left;
      crr_right = 1023 - crr_right;
      putNewReading((int)crr_left, (int)crr_right);
      int powerleft = 100-(int)((crr_left – minvalue)/(maxvalue-minvalue)*50);
      int powerright = 100-(int)((crr_right-minvalue)/(maxvalue-minvalue)*50);
      MotorPort.A.controlMotor(powerleft, 1);
      MotorPort.B.controlMotor(powerright, 1);
      Thread.sleep(250);
     }     
}


Party Finder

Party Finder robotten er baseret på samme program som 2a og 2b robotterne.
Da max. og min. værdierne bliver målt ud fra kørsel af robotten er det
ikke væsentligt om det er en lyd sensor eller lys sensor som sidder på
robotten, så koden er den samme ud over vi initialisere SoundSensor-objekter i stedet for LightSensor.

Robotten havde svært ved at skelne lyden mellem de to sensorer, eller sagt på en anden måde de to lyd-sensorer opfangede samme lydstyrke på næsten alle tidspunkter. Det bevirkede, at den ikke drejede kraftig henimod/væk fra sit mål.