Denne blog bruges af Kim Munk Petersen (20063667), Andreas Koefoed-Hansen (20062154) og Tim Rasmussen (20061947) som dagbog i forbindelse med kurset Embedded Systems/Legolab på Århus Universitet.

torsdag den 26. november 2009

Øvelsesgang 10

Dato:
19-11-2009

Antal timer brugt:
3 timer

Deltagende personer:
Kim Munk Petersen, Andreas Koefoed-Hansen og Tim Rasmussen

Denne uges opgaver:
Ugens opgave er at eksperimentere med subsumption, ligesom ved øvelse 8, men denne gang bruge leJOS's subsumption framework bestående af interface'et lejos.subsumption.Behavior og klassen lejos.subsumption.Arbitrator. Derudover skal vi overveje hvorledes framework'et kan ændres så de forskellige opførslers prioritet ikke er statistk, men ændres dynamisk, svarende til Thiemo Krinks "motivation functions" [1]. Opgaverne tager udgangspunkt i BumperCar-eksemplet fra leJOS NXJ distributionen.





Fysiske overvejelser
Vi har til denne uges opgaver gjort brug af robotten fra sidste uges opgaver, som var bygget ud fra beskrivelsen i Brian Bagnall's kapitel 12. Ultralydssensoren er placeret som i sidste uge, men foran robotten er der nu placeret en slags kofanger, som registrerer om robotten kører ind i ting. Kofangeren af lavet ud fra idéen om en kofanger på den bil, men med den modifikation at vi har placeret to fjedre på hver side for at koncentrere stødene mere mod sensoren.

Opgave 1: BumperCar

Beskrivelse af BumperCar:
BumperCar indeholder 2 opførsler, DriveForward og DetectWall, der henholdsvis har til mål at få robotten til at køre fremad og undvige objekter. Oprførslen DriveForward tilkendegiver at den altid ønsker at blive udført ved at dens takeControl-metode altid returnerer true. Når den udføres, dvs. dens action-metode kaldes, sætter den begge motorer til at køre fremad og stopper først når den bliver suppresed. DetectWall's takeControl metode returnerer true hvis og kun hvis touch-sensoren er trykket ind eller der er et objekt mindre end 25cm foran robotten. Dens formål er at undgå kollision ved at få robotten til at bakke og dreje.


Initielle tests og observationer:
Vi lagde BumperCar over på NXT'en og startede programmet. Som forventet begynte robotten at undvige når dens touch-sensor blev trykket ind eller der var objekter mindre end 25cm forude, og ellers kørte den ligeud. Vi prøvede at holde touch-sensoren inde hvilket resulterede i at gentog opførslen DetectWall indtil vi slap sensoren hvorefter den atter begyndte at køre fremad efter at have fuldendt den igangværende DetectWall-opførsel. Grunden til at den vælger at udføre DetectWall i stedet for DriveForward er at DetectWall har højere prioritet idet den er indsat på en senere plads i behavior-arrayet der gives til arbitratoren. Idet der kun er en tråd til at udføre opførsler, starter en ny opførsel først efter den igangværende action-metode returnerer.

Arbitrator:
Ved at studere kildekoden for arbitratoren "Arbitrator.java", ses det at den leder efter opførsler der gerne vil udføres, dvs. deres takeControl-metode returnerer true. Idet den gennemløber behavior-arrayet bagfra, kan den stoppe søgningen så snart den har fundet en sådan opførsel da denne nødvendigvis må have højst prioritet. Koden-afsnittet ses herunder:

_highestPriority = NONE;
for (int i = maxPriority; i <= 0; i--) // maxPriority = _behavior.length - 1
{
  if (_behavior[i].takeControl())
  {
    _highestPriority = i;
    break;
  }
}

Betydningen af ovenstående kode er at DriveForward's takeControl-metode ikke kaldes når betingelsen for DetectWall er opfyldt, eftersom den break'er efter DetectWall's takeControl-metode returnerer true. Dette giver god mening i og med at den har højere prioritet.


Exit-opførsel:
Vi implementerede en opførsel "Exit" som har til formål at lukke programmet så snart escape-knappen på NXT-brick'en trykkes ned. For at programmet skal lukkes ned hurtigst muligt efter der er trykket på ESC, giver vi Exit-opførslen højest prioritet. Der er flere måder at implementere denne opførsel på:

Hvis man implementerer dens takeControl-metode således at den returnerer true hvis ESC-knappen er trykket ned vha. isPressed(), skal man holde knappen nede indtil Arbitratoren kommer til Exit-opførelsens takeControl-metode. Gør man ikke det kan man risikere at trykket "går tabt", hvilket også er beskrevet i leJOS' subsumption tutorial [2]. 
En mere robust løsning, som vi derfor valgte, er at oprette en ButtonListener på Escape-knappen der i princippet er en anden tråd der "lytter" efter tastetryk. Så snart dens buttonPressed-metode kaldes, er ESC-knappen blevet trykket ned og vi sætter en boolean hasPressed til true. I takeControl-metoden returneres denne boolean.

Det virkede umiddelbart til at Exit-opførslen blev udført øjeblikkeligt når vi trykkede escape når DriveForward var aktiv. Hvis den igangværende opførsel var DetectWall, lukkede programmet først efter opførslen var afsluttet, dvs. robotten havde bakket og drejet. Ved at forøge Sound.pause(20) til Sound.pause(2000) i DetectWall's takeControl-metode, opstod der en forsinkelse inden Exit-opførslen blev udført, ligegyldigt hvilken opførsel der var aktiv når vi trykkede escape. Forklaringen er at arbitrator'en forsinkes hvilket er uheldigt idet den netop skal være responsiv så robotten hurtigt kan skifte opførsel.

Ved nogle sensorer, bl.a. ultralydssensoren, er det umuligt at sample øjeblikkeligt. I stedet for at lave et delay i takeControl-metoden, kan man sample med en bestemt frekvens og så bruge den sidst målte værdi til at beslutte resultatet af takeControl. Denne løsning implementerede vi ved at oprette en ny tråd der sampler fra ultralydssensoren ca. hvert 20. millisekund. Herved gøres aribitratorens responstid meget kortere.

Ét sekunds bak før der drejes:
En opgave gik ud på at få robotten til at bakke et sekund inden den begyndte at dreje. Det implementerede vi først ved at sætte begge motorer til at køre baglæns, kalde Thread.sleep(1000) og så dreje robotten. det betyder at selv om der er en der forsøger at suppresse den, så skal den gøre sin handling færdig før der skiftes opførsel.

Man kan let forestille sig at man ønsker at robotten skal blive ved med at bakke indtil touch-sensoren ikke længere trykkes ind. Dette medfører at DetectWall-opførslen skal kunne interrupt'es. Vi havde store overvejelser omkring hvordan vi skulle implementere denne feature. Vi startede med at skifte sleep(1000) ud med en while-løkke og kald til System.currentTimeMillis(), således at while-løkken kørte indtil der var gået et sekund eller opførslen var blevet supressed af en med højere prioritet. Herved udføres tryk på escape øjeblikkeligt idet DetectWall-opførslen supresses og afsluttes prompte. Væsentligst er det dog at vi inde i while-løkken løbende kan tage stilling til hvorvidt opførslen skal genstartes.

I while-løkken kan man lave et rekursivt kald til action-metoden selv, hvis DetectWall-betingelsen stadig var opfyldt, dvs. takeControl() returnerer true. En ulempe ved denne løsning er at man let får call-stack overflow hvis betingelsen er opfyldt lang tid af gangen.

En anden løsning er at kalde return i while-løkken hvis betingelsen stadig er opfyldt, og derved interrupt'e opførslen og enten gentage den eller lade en anden komme til. Ulempen ved denne løsning er at opførslen ikke stopper motorerne inden der returneres, hvilket ikke er smart hvis der findes en opførsel med højere prioritet der ikke bruger motorerne (og derfor ikke kalder stop()). Hvis vi kaldte stop() inden return, ville der ske det at backward() og stop() bliver kaldt skiftevis med høj frekvens hvis DetectWall-betingelsen er opfyldt, hvilket nok medfører at robotten ikke bakker som den burde. Dette testede vi dog ikke.

Vores løsning er at opførslen genstarter sig selv så længe betingelsen er opfyldt, men vi har også et yderloop som kører indtil isDone er true. På denne måde sikrer vi at motorerne bliver stoppet selv om betingelsen længe er opfyldt. Koden ses herunder:

boolean isDone = false;
outer:while(!isDone) {
  _suppressed = false;
  Motor.A.backward();
  Motor.C.backward();
  long time = System.currentTimeMillis() + 1000;
  while(!_suppressed && System.currentTimeMillis() < time) {
    if (takeControl())
      continue outer;
  }
  Motor.A.stop();
  time = System.currentTimeMillis() + 800;
  while(!_suppressed && System.currentTimeMillis() < time) {
    if (takeControl())
      continue outer;
  }
  Motor.C.stop();
  isDone = true;
}


Referencer:

torsdag den 19. november 2009

Øvelsesgang 9

Dato:
19-11-2009

Antal timer brugt:
6 timer

Deltagende personer:
Kim Munk Petersen, Andreas Koefoed-Hansen og Tim Rasmussen

Denne uges opgaver:

Denne uges opgave går ud på at se hvordan man kan få robotten til at køre til forskellige positioner, enten via en TachoPilot eller via CompassPilot. Den første opgave gik ud på at observere hvordan man kunne bruge TachoPilot sammen med SimpleNavigator til at finde frem til forskellige koordinater og til slut vende tilbage til udgangspunktet. Robotten er derfor nødt til at vide hvor den er hele tiden. Anden opgave går ud på at få robotten til finde frem til nogle koordinater som i opgave 1, men her skal robotten også kunne undgå evt. forhindringer på vejen. Den sidste opgave går ud på at anvende et kompas til at finde frem til de forskellige koordinater og se om robotten kører mere præcis vha. et kompas frem for ved brugen af en TachoPilot.

Vi har løst opgaverne i følgende rækkefølge:
  1. Navigation
  2. Kompas navigation
  3. Navigation, hvor objekter undgås
Fysiske overvejelser

Robotten anvendt til denne uges opgaver er bygget ud fra beskrivelsen i Brian Bagnall's kapitel 12. Placeringen af kompasset har været til nøje overvejelser, eftersom den skal væres placeret minimum 10 - 15 cm fra NXT'en og motorrene. Vi har derfor først placeret kompasset i et tårn over selve NXT'en, men da dette tårn blev ustabilt, valgte vi at placere kompasset med den nødvendige afstand foran robotten.




Vi har målt følgende afstande:
  • Afstand mellem hjulene er 17.8 cm
  • Diameteren på hjulene er 56 mm
  • Afstand fra kompas til NXT midt = 11.5 cm
  • Afstand fra kompas til motor er 13 cm
Navigation

Vi brugte Brian Bagnalls kode og ændrede argumenterne der blev givet til TachoPilot constructoren til at passe med vores robot. Vi udførte først små tests hvor vi fik robotten til at køre 20 cm og prøvede at justere argumenterne efter dette. Da vi havde justeret disse, udførte vi tests på hele ruten og fik efter 3 forsøg en ret præsis gennemkørsel af ruten.

Vi brugte Lego klodser til at markere den tilbagelagte rute. Ruten blev mere kørt og mere korrekt i takt med at vi ændrede afstanden mellem hjulene. Den målte værdi var 17.8, men vi fik først en rigtig rute da vi ændrede til 18.4. Grunden til dette, kan skyldes at afstanden øges når der kommer belastning på hjulene, men dette giver ikke 6 mm, de sidste mm kan findes i unøjagtigheden i motor hastigheder.

Med en hjul diameter på 56mm kørte robotten meget præcist. Dvs. at når vi bad den om at køre 200mm så kørte den også næsten de 200mm. I vores tilfælde bad vi den køre 200cm, og den kørte godt 197cm. Da vi ændrede hjulstørrelsen til 55mm kørte den godt 199.7cm.

Kørelse 1: ca. 50 fra udgangspunkt (afstand mellem hjul 17.5)
Kørelse 2: ca. 30 fra udgangspunkt (afstand mellem hjul 17.8, her fandt vi ud af at vores måling var forkert)
Kørelse 3: ca. 7 fra udgangspunkt (afstand mellem hjul 18.4, her prøvede vi os frem med afstanden for at få den til at køre tilbage til udgangspunktet)





Navigation while avoiding objects

I forsøg på at løse denne opgave brugte vi to opførsler: en til at styre robotten hen til bestemte koordinater og en til at undvige objekter på robottens vej. Som tidligere i lignende opgaver, har den undvigende opførsel højest prioritet. Vores program bygger på Ole Capranis arkitektur til behavior based systems hvor hver opførsel er en tråd der nedarver fra en klasse Behavior og implementerer sin funktionalitet i run-metoden. Behavior-klassen stiller nogle metoder der styrer robottens motorer til rådighed og sørger for at det kun er ikke-suppressed opførsler der har adgang hertil. Vi tilføjede nogle metoder til Behavior-klassen, bl.a. goTo(x,y), travel(distance) og rotate(angle) der så kalder videre på en underliggende SimpleNavigator der styrer robotten. Alle definerede opførsler deler således en navigator hvilket gør at robotten i teorien bør opdatere sin position og retning, uanset hvilken opførsel der styrer den. Opførslen AvoidFront træder i kraft når robotten er 20cm eller mindre fra et objekt foran den. Den får herefter robotten til at rotere 90 grader og køre 20cm ligeud for at komme uden om objektet. Den anden opførsel WaypointDrive er aktiv når AvoidFront ikke er aktiv. Den har et array af koordinater og holder hele tiden styr på hvilket koordinat-sæt den er på vej hen til. Hver gang opførslen aktiveres kalder den goTo til dette koordinat-sæt, og når den når derhen skifter den til næste koordinat-sæt og fortsætter således indtil den når enden af koordinat-array'et. 


Da vi testede robotten prøvede vi i første omgang at lade den køre banen fra før uden at lade noget komme i vejen for den, og den kørte til alle punkterne som i første opgave. Derefter testede vi robottens AvoidFront opførsel og konstaterede at den blev aktiveret på de rigtige tidspunkter, dvs. når robotten nærmede sig et objekt, og den forsøgte at dreje omkring objektet som den burde. De to opførsler opfyldte derfor hver især deres mål. Når vi kombinerede de to opførsler gik det dog galt. Robotten mistede sin stedfornemmelse så snart AvoidFront-opførslen blev aktiveret. Vi fik den til at udskrive på LCD-skærmen hvor den var på vej hen, og det viste sig at den kørte til det korrekte koordinat-sæt, men at dens retning og position altså var forkert. Det lykkedes os ikke at løse problemet, men antager at problemet skyldes SimpleNavigator, som antageligt også gav problemer i forrige opgave.

Vi har hørt at andre grupper har samme problem, og heller ikke har været i stand til at løse det. I begyndelsen troede vi at problemet skyldtes at det kun var metoden goTo der opdaterede robottens interne koordinatsystem, men efter at have lavet en test med metoderne goTo, travel og rotate i SimpleNavigator, viste denne antagelse sig at være forkert. Robotten er tilsyneladende i stand til at opdatere sin position ligegyldigt hvilken af SimpleNavigators metoder man kalder.

Får vi senere brug for at have flere samtidige opførsler der skal navigere robotten rundt, vil vi løbende skrive robottens koordinater til en fil som vi så kan plotte, evt. så vi kan skelne koordinater skrevet af den ene henholdsvis den anden opførsel. Herved burde vi kunne se hvor og hvornår problemet opstår.

Compas Navigation


Til at løse denne opgave, monteres et kompas et sted på robotten. Første gange vi kørte vores program, kørte robotten rundt som om den havde været på Roskilde festival i en uge uden søvn og alt for store mængder alkohol. For at få den til at køre mere præcist vha. kompasset, valgt vi at kalibrere kompasset. Dette ændrede dog ikke meget på dens måde at køre på. Da vi placerede den på samme udgangspunkt som da vi gjorde brug af TachoPilot, kørte den meget forvildet rundt. For at finde ud af hvad grunden var til denne meget unøjagtige kørsel, lavede vi et simpelt program der blot skulle starte med at rotere robotten mod nord, hvorefter den skulle dreje 90 grader og vente 1 sekund inden den igen skulle dreje 90 grader. Heller ikke dette kunne den finde ud af. I stedet for at dreje samme vej rundt hele tiden, begyndte den pludselig at dreje den anden vej som om den var kommet forbi den ønskede position. Vi forsøgte derfor at teste om de værdier vi kunne hente ud fra CompassSensoren vha. getDegrees() var korrekte. Det viste sig at de var så korrekte som man kunne forvente.

Vi prøvede derfor forskellige ting, der kunne hjælpe os med at få robotten til at opfører sig efter hensigterne. Disse ting var følgende:
  • Ændrer placeringen af kompasset, så i stedet for at være placeret i toppen af et meget ustabilt tårn, blev den nu placeret foran robotten. Dette gjorde at den sad mere fast på robotten og derfor ikke havde de sammen udsving.

  • Ændre hastigheden af mortorerne, så vi var sikre på at grunden til den mærkelige opførelse ikke skyldtes at vi bevægede os forbi den ønskede position. Grunden til denne test, var at vi ikke vidste om SimpleNavigator på nogen måde håndtere fejltolerencer. Men selv ved meget lave motorhastigheder kørte robotten som tidligere.
På trods af de ændringer og målinger vi har lavet, er det ikke lykkes os at få robotten til at køre på en fornuftig måde når der bruges et kompas. Det er dog flere ændringer vi kunne have udført i håb om at disse kunne hjælpe med at få robotten til at opføre sig nogenlunde fornuftigt. Disse ændringer er:
  • Vi kunne undgå at bruge SimpleNavigato og i stedet blot kalde rotate metode direkte på CompassPilot. Dette vil fjerne evt. fejl der kan opstå ved at bruge SimpleNavigator på en CompassPilot.

  • Vi kunne også vælge at læse indputtet direkte fra CompassSensor og ud fra disse værdier selv styre motorerne. Herved vil vi fjerne 2 evt. fejlkilder.
Grunden til at vi kunne forestille os at disse ændringer vil have en god effekt skyldes at det virker som om SimpleNavigator ikke er så glad for at arbejde sammen med CompassPilot. Da vi gjorde brug af en TachoPilot, havde vi ingen problemer, så vi bygger ideen om at SimpleNavigatoren og CompassPiloten ikke arbejder godt sammen på baggrund af dette.

torsdag den 12. november 2009

SoundCar - Øvelsesgang 8

Dato:
06-11-2009

Antal timer brugt:
3 timer

Deltagende personer:
Kim Munk Petersen, Andreas Koefoed-Hansen og Tim Rasmussen

Denne uges opgaver:
Denne uges opgaver går ud på at se hvordan flere forskellige observerbare opførelser kan implementeres på en enkelt NXT. Til denne uges opgave skal der bruges en ultralydssensor. Vi har valgt at udfører opgaverne i den rækkefølge de er præsenteret i opgave beskrivelsen.

Dette betyder at opgaverne er løst i følgende rækkefølge:
  1. Observere robotten og se hvordan robotten opfører sig
  2. Find udløsningsbetingelserne og se hvordan robotten opfører sig med en eller to tråde aktive.
  3. Se på implementeringen af suppresser mekanismen Behavior.java, og sammenlign metoden med Fred Martin metode.
  4. Tilføjelse af "kør mod lyset" tråd.
Fysiske overvejelser
Eftersom vi allerede under sidste øvelsesgang havde monteret en ultralydsensor på toppen af robotten, har der ikke været nogen fysiske overvejelser under denne øvelse.





1. LEGO car that exhibits several behaviors
Programmet har 3 forskellige tråde der arbejder concurrent, men med forskellige prioriteter. Dette betyder at når robotten afspiller en lyd, så tager tråden PlaySound tråden kontrol over motorerne og stopper dem.
Når der ikke afspiller lyd kører robotten vha. RandomDrive tilfældigt rundt indtil den komme for tæt på et objekt. Hvis robotten kommer til en forhindring vil tråden Avoid sørge for at få robotten væk fra ved at overtage motorerne og undvige. Undvigelsesmanøvren fortsætter indtil PlaySound afspiller en lyd og tager rettigheden, eller den er langt nok væk fra objektet igen. Avoid bliver aktiveret hvis afstanden til objektet bliver mindre end 20.
På LCD panelet skriver robotten vha. definitionerne s = stop, f = forward, b = backward hvilken vej robotten kører samt hvor lang afstand der er til et objekt. Ud over disse informationer, skriver robotten også ud for drive, avoid og play hvor mange tråde der der suppresser den givne tråd.

2. Behaviors as concurrent threads
Ved at undlade at starte nogle tråde kunne vi tydeligt se den opførsel de enkelte tråde tilføjede til robotten.

Med kun RandomDrive tråden aktiv, vil robotten køre tilfældigt rundt og eftersom Avoid tråden ikke er aktiv vil robotten fortsætte med dette indtil den rammer ind i noget eller falder ud over kanten på bordet.
Når de to første tråde er aktive (RandomDrive + AvoidFront), vil robotten forsøge at undgå objekter tæt på, men stadig køre tilfældigt rundt når der ikke er forhindringer.
Når robotten opdager en forhindring suppresser den RandomDrive, som herved mister rettigheden til motorerne og AvoidFront tråden overtager alle rettigheder over motorerne.

3. Class Behavior
Et objekt er givet et andet objekt som det derefter kan suppresse. Dvs. at når tråden i PlaySound er aktiv vil den suppresse alle andre tråde, eftersom når PlaySound kalder suppress(), så vil suppress() metoden sørger for at suppressCount bliver talt op på det andet objekt. Det er lavet sådan at objekterne ikke kan udfører nogen handling hvis deres suppressCount er sat til 1 eller derover. Når f.eks. PlaySound er færdig med at udføre sin handling, så frigives motoren igen ved at tælle suppressCount ned igen. Fred Martin bruger et mere centraliseret princip, men ideen bag er stadig den samme. Han giver de forskellige tråede prioriteter, således at den tråd med den største prioritet får lov til at få den adgang den ønsker. Princippet gør brug af en metode kalder prioritize(), som kører concurrent med alle andre tråde. Den skanner igennem listen af aktive processer og finder den proces der har den højeste prioritet. Når den har fundet denne tråd, kopierer den trådens handlinger til motoren og udfører dem.

4. Add a behavior "Drive towards light"
Målet med forrige uges øvelser var at lave en robot der kørte i retning af lys. For at opnå mere erfaring med Rodney Brooks subsumption-arkitektur, tilføjede vi denne opførsel til samlingen af de tre eksisterende opførsler. Vi mener at det at følge lys bør have højere prioritet end RandomDrive og lavere prioritet end AvoidFront, hvorfor vi valgte at placere opførslen mellem disse. For at den nye opførsel, DriveTowardsLight ikke altid supress'er den underliggende opførsel, RandomDrive, har vi besluttet at den kun skal udløses når lyssensorerne ser et tilpas kraftigt lys, hvilet er implementeret vha. en justerbar tærskelværdi i vores kode. Vi har implementeret den nye opførsel ved at lave en klasse DriveTowardsLight der extender klassen Behavior.java givet i opgaven, og så copy paste'et kontrol-loop'et fra sidste uge ind i klassens run-metode, der er vist herunder:
public void run() {
while
(true) {
int leftValue, rightValue;
do {
leftValue = sensorLeft.readRawValue();
rightValue = sensorRight.readRawValue();
}
while(leftValue > lightThreshold && rightValue > lightThreshold);

suppress();
drawString("f");
forward(normalize(leftValue),normalize(rightValue));
delay(10);
release();
drawString(" ");
}
}
En anden interessant kodestump er det sted i klassen SoundCar.java, der også er givet i opgaven, hvor de forskellige opførsler instantieres og deres "prioritet", altså subsumption-ordenen, bestemmes:
rd = new RandomDrive("Drive",1, null);
dtw = new DriveTowardsLight("Light", 2, rd);
af = new AvoidFront ("Avoid",3,dtw);
ps = new PlaySounds ("Play ",4,af);

torsdag den 5. november 2009

Braitenberg/Tom Dean - Øvelsesgang 7

Dato:
05-11-2009

Antal timer brugt:
5 timer

Deltagende personer:
Kim Munk Petersen, Andreas Koefoed-Hansen og Tim Rasmussen

Denne uges opgaver:
Denne uges opgaver går ud på at benytte principperne i Tom Dean's note til at implementere Braitenberg's forskellige køretøjer. Vi har valgt at udfører opgaverne i den rækkefølge de er præsenteret i opgave beskrivelsen. Dette betyder at opgaverne er løs i følgende rækkefølge:
  1. Implementering af robot der kører mod lyset. Gjort brug af Tom Deans note.
  2. Implementering af en robot der gør brug af to tråde til at kontrollere hver forbindelse.
  3. Ændre implementering til at ændre værdien af MIN_LIGHT og MAX_LIGHT, så de følger værdierne målt levertiden n.
Formålet med denne uges øvelser er at forstå hvordan robottens handlinger kan afhænge af det miljø den færdes i.

Vigtige kodestumper vil være inkluderet i rapporten, mens der linkes til hele koden.

Fysiske overvejelser:
Vi har placeret 2 lyssensor på toppen af robotten til at indsamle oplysninger om lysforholdene. Sensorerne er placeret med ca. 10 cm mellemrum for at sikre en forskel i de målte værdier. Sensorerne kunne være placeret sammen, men herved vil forskellen på de målte værdier være mindre. Dette ville måske være en løsning hvis det er en lommelygte der f.eks. lyser på robotten. En lommelygtes spredning afhænger af hvor langt den er fra objektet, men lysstyrken vil oftest være stærkest i midten. Hvis sensorerne er placeret langt fra hinanden , vil lygtens lyskejle ikke ramme begge sensorer, hvorved robotten vil, hvis den følsomhed er lav, kører i hak. Da vi satser på at få robotten til at køre mod et vindue eller en dør, hvor der er normalt lysindfald, vælger vi at placere sensorerne med godt 10 cm. mellemrum. Et billed af robotten kan ses på nedenstående billede:





Implementation program til NXT-robotten med formålet at søge mod lys ved brug af Tom Deans note:

Vi benyttede de kodeeksempler der var i Tom Deans note til at lave metoderne til at udregne average og normalized values af inputtet fra lyssensorne, og derved bestemme hvor hurtigt motorerne skal køre.
Vi har en variabel kaldet beta der kan sættes til en værdi mellem 0 og 1. Denne variabel benyttes i forbindelse med udregning af average og bestemmer hvor meget der skal tages hensyn til de forgående læste værdier. Hvis beta er sat til 1 bliver der ikke taget hensyn til de værdier der er læst førhen, og hvis den er sat en værdi tæt på 0, får den læste værdi ikke meget indflydelse på hvordan avarage værdien bliver.

average = (int) ((beta * normalized) + ((1-beta)*average));

Vi testede dette ved printe både den nyeste læste værdi ud til skærmen, samt average værdien. Når beta var sat til 1 var værdierne ens, og når beta var lavere ændrede værdien sig langsommere til til den værdi der blev aflæst hvis sensoren tidligere har været i omgivelser med andre lysforhold.

Da vi prøvede at sætte motorerne til at køre efter den average værdi der blev udregnet var resultatet ret uventet. Når vi lyste med en lommelygte på sensoren kørte robotten imod lyset, men det var en ret ujævn motorgang.

Lyssensorens raw-values er ikke i et interval mellem 0-100 så vi var nødt til at normalisere værdierne så de kunne sendes videre til motorerne. Dette blev gjort at metoden normalize, der benytter værdierne MAX_LIGHT og MIN_LIGHT til at omregne den rå lysværdi.
Outputtet blev beregnet som følgende:
int output = (100 - ((light - MAX_LIGHT) * 100) / (MIN_LIGHT - MAX_LIGHT));

Vi lavede nogle test for at bestemme hvad MAX_LIGHT og MIN_LIGHT skulle sættes til og kom frem til at værdierne henholdsvis skulle være 300 og 600 for at robotten kunne arbejde i de omgivelser hvor vi var.

Ved normal loftsbelysning kørte robotten ikke, men hvis den blev vendt imod et vindue eller op mod loftslamperne begyndte den at køre imod lyset.

Tråde til kontrol af forbindelser
Implementeringen af tråde på NXT'en er lidt sværere end normalt, eftersom den ikke understøtter Runnable interfacet. Måden vi i stedet implementere det på er ved at lave en indre klasse kaldet WorkerThread der ekstender Thread. Konstruktøren i denne klasse tager som parameter en SensorPort og en MotorPort som derved parer de to enheder sammen. Hver objekt af WorkerThread der bliver oprettet, indeholder en række metoder til at returnere forskellige værdi tilknyttet objektet, samt en metode til at beregne den normaliserede værdi. Normalize metoden virker som beskrevet overfor.

I main metoden oprettes først LightSensor objekter, hvorefter FloorLight sættes til false.

LightSensor l1 = new LightSensor(SensorPort.S1);
LightSensor l2 = new LightSensor(SensorPort.S2);
l1.setFloodlight(false);
l2.setFloodlight(false);
herefter oprettes to objekter af WorkerThread, som derefter startes.

WorkerThread leftSensorThread = new WorkerThread(SensorPort.S1, MotorPort.C, null);
WorkerThread rightSensorThread = new WorkerThread(SensorPort.S2, MotorPort.B, null);
leftSensorThread.start();
rightSensorThread.start();
på denne måde oprettes to objekter der kører i hver sin tråd og samtidig forbinder en sensor med en motor.

Adaptivt lysinterval:
I teorien bør NXT-lyssensorerne kunne foretage målinger der resulterer i rå værdier i intervallet [0,1023]. Ved hjælp af eksperimenter har vi konstateret at dette interval er noget mindre i praksis. I vores kode bruger vi to variabler, MAX_LIGHT og MIN_LIGHT, til at angive grænsen for henholdsvis minimalt og maksimalt lys. Idet lyssensorerne fungerer således at der returneres en lav rå værdi ved kraftigt lys og en høj rå værdi ved svagt lys, har variablen MAX_LIGHT en lavere værdi end MIN_LIGHT hvilket ved første øjekast kan virke forvirrende.

Ved vores eksperimenter var den højest målte rå værdi ca. 800 (svagt lys) og den lavest målte ca. 100 (kraftigt lys). Som Tom Dean beskriver kan vi ikke vide hvilke lysforhold robotten kommer til at befinde sig i, hvilket betyder at vi med fordel kan lade MAX_LIGHT og MIN_LIGHT tilpasse sig forholdene. Dette gør vi ved at indskrænke det initielle lysinterval ift. vores målinger, og så lade det udvides automatisk hvis der er behov for det. Den initielle værdi for MAX_LIGHT er 300 og den initielle værdi for MIN_LIGHT er 600, altså begge 200 enheder fra værdierne målt i eksperimentet. Ifølge opgavebeskrivelsen skal kun de sidste N samples have indflydelse på tærsklerne, og vi har derfor implementeret en cirkulær liste indeholdende de sidste N målinger.

MAX_LIGHT og MIN_LIGHT justeres så efter henholdsvis største og mindste sample i listen, medmindre disse ligger inden for det initielle interval [300,600]. Grunden til at vi ikke tillader intervallet at blive indskrænket yderligere er at robotten vil blive for følsom over for ændringer i miljøet samt støj, og evt. risikere at få division med 0 hvor vi normaliserer lys-værdien i tilfælde af at MAX_LIGHT og MIN_LIGHT sættes til samme værdi. Som N bruger vi 5000 hvilket giver at den ældste sample i den cirkulære liste er ca. 50 sekunder gammel idet vi har indført et delay på 10ms i kontrol-loop'et.

Det selvjusterende lysinterval gør at robotten til dels "vænner" sig til dens omgivelser. Udsættes robotten for meget kraftigt lys således at dens MAX_LIGHT bliver meget lav, nedsættes dens lysfølsomhed hvilket kan ses ved at output'et fra normaliseringsfunktionen bliver lavere i takt med at MAX_LIGHT falder. Omvendt bliver den mere lysfølsom når den befinder sig i mørke områder hvilket giver en høj MIN_LIGHT, hvilket også ses ud fra normaliseringsfunktionen.

Faste læsere