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:
- [1], Thiemo Krink(in prep.). Motivation Networks - A Biological Model for Autonomous Agent Control
- [2], The leJOS Tutorial, Behavior Programming