Projekt: System- und Rechnerarchitektur
Vorbemerkungen
Raum, Hardware und Labor
Ziel des Labors
Zu den Programmieraufgaben
- Im Labor wird mit Ubuntu gearbeitet.
- Für die Bearbeitung steht euch ein kleines Framework "Khepera-Lib" zur Verfügung.
- Sollte es Fragen zu einzelnen Modulen im Framework geben, schaut bitte erstmal in "KheperaDokus" bevor ihr eine Email schreibt.
- Für jede Aufgabe gibt es einen festen Bearbeitungszeitraum von ein oder zwei Wochen (je nach Aufgabe).
- Es ist zwingend erforderlich, dass ihr zuhause arbeitet. Der Zeitraum in den Terminen ist NICHT ausreichend, um die Aufgaben zu erfüllen. Die Aufgaben und Materialien sind so konzipiert, dass das meiste auch zuhause bearbeitet werden kann. Die Termine im Labor sind vorwiegend zum Demonstrieren eures Fortschritts, zum Stellen von Fragen und zum Testen auf dem Bot bzw. entsprechend zum korrigieren gedacht.
- Es wird empfohlen keine der vorhanden Dateien zu verändern (außer natürlich student.cpp, student.h). Es ist aber auch nicht verboten!
- Natürlich dürft ihr gern eigene Dateien einbinden.
- Nutzt niemals
while (true){. . . }
! Und auch keine äquivalenten Schleifen, damit sich euer Programm über STRG+C ordentlich beenden lässt. - Zur Darstellung der Map, die der Bot detektiert wird Code bereitgestellt. Dieser ist NICHT zu ändern.
- Der Mapserver muss verwendet werden.
- Mapserver: Farben: Rot = Wand; weiß = unbekannt, schwarz = erkundet, grün = Landmark, blau = code-Position
- Die Pfadangaben sind immer relativ zu eurem Homeverzeichnis
- Jede gelöste Aufgabe ist als einzelner commit in eurem gitrepository zu speichern
- In einigen Aufgaben gibt es Vergleiche euer Werte mit Sollwerten. Habt ihr alles richtig gemacht, so sollten diese Werte immer stimmen, ABER ihr sollten trotzdem immer prüfen, ob Abweichungen nicht einfach nur Rundungsfehler sind
- Ihr könnt eure Lösungen auch immer schon zu einem früheren Termin vorstellen, falls ihr früher fertig seid. Entsprechend könnt ihr auch schon früher mit der nächsten Aufgabe anfangen ABER bearbeitet die Aufgaben, sofern nicht anders erwähnt, in der gegebenen Reihenfolge, da diese zusammenhängen
Bestehen des Labors
Das Labor ist bestanden, wenn alle Aufgaben im festgelegten Zeitraum erfüllt sind. Dazu gehören:
- Vollständiges Erkunden der Map. d.h. alle Wände und freien Positionen die der Bot erreichen bzw. mit seinen Sensoren prüfen kann, müssen als Wände bzw. freie Positionen (WALL, FREE) markiert und auf der Karte angezeigt werden.
- die Wände dürfen maximal 1 Pixel bzw. 1 cm dick sein
- die Wände müssen in Abhängigkeit der Sensorwerte gesetzt werden
- die drei Code-Markierungen müssen erkannt worden sein
- die Positionen der erkannten Codemarkierungen müssen nach dem vollständigen Erkunden in der von uns vorgegebenen Reihenfolge auf dem kürzesten Pfad abgefahren und erneut gescannt und erkannt werden.
- eine komplette Fahrt darf maximal 15 min dauern.
- während der Fahrt dürfen keine Wände berührt/verschoben werden
- Fehler während der Fahrt müssen korrigiert werden. Insbesondere ist eine Fehlerkorrektur mit dem "track" zu implementieren
- die Abgabe muss mit
gridmap.sh
angezeigt werden - Die Abnahme wird mit bewusst schlecht kalibriertem Roboter durchgeführt. Ziel des Labors ist es Fehler zu korrigieren.
- Während der Abnahme wird mit mindestens Geschwindigkeit X gefahren.
Git-Repository
Zur Verwaltung eurer Daten nutzt ihr bitte euer git-Repo. Um Zugang dazu zu erhalten, meldet euch bitte auf https://scm.sra.uni-hannover.de:8181/scm/ mit euren Zugangsdaten an. Hier findet ihr unter anderem ein Repo mit dem Namen KheperaStudent-progrp(eure Gruppennummer). Klickt darauf und in der unteren Leiste werden euch Informationen dazu angezeigt. Unter anderem steht dort, mit welchem Befehl in der Konsole/Terminal ihr das Git-Repo clonen könnt. Das könnt ihr auf euren privaten Rechnern auch gern machen, aber auf euren Accounts im Labor ist das bereits geschehen.
Informationen zu den Robotern
Geschwindigkeit
Die Roboter haben eine Minimalgeschwindigkeit von 4000 (ca. 4,4 cm pro Sekunde). Darunter werden die Bewegungen ungenau oder die Räder drehen sich vielleicht gar nicht. Die Maximalgeschwindigkeit liegt bei ca. 30000 (ca. 33,3 cm pro Sekunde).
Bewegungsrichtung
Die Motoren können mit den Befehlen der Klasse 'machine' angesteuert werden. Positive Werte sorgen sinnvollerweise für eine Vorwärtsbewegung. Rückwärts analog. So ist eine Drehung um die eigene Achse also möglich, wenn die Motoren mit betragsmäßig identischen Werten mit unterschiedlichen Vorzeichen angesteuert werden. An dieser Stelle wird davon abgeraten dies bei hoher Geschwindigkeit zu tun, da die Fliehkräfte doch ein wenig zu stark werden. Wir sind uns ziemlich sicher, dass die Geräte nicht dafür konstruiert wurden sich auf dem Rücken zu drehen.
Sensoren
Die Roboter sind mit 11 Sensoren ausgestattet. 9 sind horizontal angebracht und 2 befinden sich auf der Unterseite. Wie gesagt sollen die Bots nicht auf den Tischen fahren, womit die unteren Sensoren für eure Aufgaben nicht zu beachten sind. Ein wichtiger Punkt, den man immer berücksichtigten muss ist, dass die Sensoren nicht absolut identisch sind. Sensor 1 kann auf derselben Distanz andere Werte haben als Sensor 2.
Aufgabe 0 – Erste Schritte - Zeitraum: erster Termin
Startet den QT-Creator (grünes Symbol, Leiste links, Aufschrift "QT"). Drückt nun strg+shift+o um ein Projekt zu laden. Ein Fenster öffnet sich. Geht in das Verzeichnis KheperaStudent und öffnet die Datei KheperaStudent.creator. Das Projekt wird geöffnet. Wählt hier nun das Verzeichnis "RobLab". Macht euch mit student.cpp und student.h vertraut. Das sind die wesentlichen Dateien, die verändert werden müssen. Es gibt eine Funktion namens „student_run(...)“, die von der main.cpp ausgeführt wird. In ihr läuft eine Endlosschleife, die die Funktion hinter dem Funktionspointer stp.state.hook immer wieder ausführt. Durch Manipulation dieses Pointers wird die Funktionsweise einer Statemachine abgebildet. Das wird einmal in student_init() und in jedem "state" gemacht (Siehe letzte Zeile in student_init()).
Ändert in der Datei KheperaStudent/Khepera-Lib/constants/constants.h die Konstante USEHOST auf die IP des Rechners, an dem ihr gerade sitzt. Die Nummer eures Rechners im Labor steht direkt unter dem Notebook-Bildschirm.
Kompiliert nun das Basisprogramm und starten es. Öffnet hierzu ein Terminal (strg+alt(links)+t) und wechseln in denselben Ordner ( cd KheperaStudent/RobLab ). Nun gebt ./compileAll.sh ein. Es wird alles kompiliert.
Nützliche Befehle:
(X = Botnummer)
-
Ordner wechseln:
cd PFAD
-
Compilieren:
compileAll.sh
-
Hochladen:
k3put +X DATEI [weitere Dateien]
, zum Beispiel, falls die Dateimain
auf den Roboter #108 (IP-Adresse 192.168.?.108) geladen werden soll: -
Runterladen:
k3get +X DATEI [weitere Dateien]
(erstellt einen extra Ordner für alle heruntergeladenen Dateien Dateien) -
Sich auf dem Roboter einloggen:
k3go +X
(es ist kein Passwort auf den Robotern eingerichtet! Bei Abfrage einfach enter drücken)
Öffnet ein Terminal und navigiert zu "KheperaStudent/Khepera-Lib/mapserver". Führt nun ./gridmap.sh aus. Hier könnt ihr euch die Karte anzeigen lassen. Startet nun in einem anderen Terminal ./KheperaStudent/Roblab/main. Ihr seht den Verlauf einer kleinen Testfahrt. Die Darstellung zeigt hier nur den Pfad, den der Bot gefahren ist.
Auf dem Terminal könnt ihr verfolgen, in welchem Zustand sich der Roboter gerade befindet. Achtet insbesondere darauf, in welchem Zustand sich der Bot als erstes befindet und wie er in den nächsten Zustand wechselt. Falls euch das zu schnell geht erhöht den Wert der Konstanten SIMSLEEPTIME in student.h. Hierbei handelt es sich um Millisekunden pro Schritt in der Statemachine.
Aufgabe 1 – Erkunden einer Wand - Zeitraum: 2 Wochen
In Roblab/simdata
findet ihr Simulationsdaten. Diese beinhalten
jeweils alle Positions-, Sensor- und Orientierungsdaten einer
Testfahrt des Bots auf einer bestimmten Karte. In student.h müsst ihr
nun den Pfad in "USEDATAPATH" zu simdata/simA1_1.txt
ändern. Jetzt
noch kompilieren und schon könnt ihr die Simulation mit ./main
auf
eurem Rechner (nicht Bot) starten. Mit ./gridmap.sh
könnt ihr die Map
beobachten.
Die Darstellung zeigt hier nur den Pfad, den der Bot gefahren ist. Jede Position hat 3 mögliche Status: UNKNOWN, FREE, WALL. Der Status kann mit
setPointStatus(map, x, y, status);
gesetzt werden. Aktuell wird in der Methode student_run() immer die aktuelle Position gesetzt.
setPointStatus(stp.map, real2grid(stp.ot.result.x), real2grid(stp.ot.result.y), FREE);
Erklärung:
stp.map
: eure Standardmapstp.ot.result.x
: x-Wert der Roboterkoordinatestp.ot.result.y
: y-Wert der Roboterkoordinatereal2grid
: Umwandlung von Roboter- in MapkoordinatenFREE
: Status -> frei/erreichbar bzw. kein Hindernis
Mit
int sensorVal = khepera3.infrared_proximity.sensor[IR_LEFT];
float dist = sensorDist(sensorVal);
erhaltet ihr eine grobe Distanz zur Wand. Achtung! Der Distanzwert ist nur während der Wandverfolgung als Distanz nutzbar, da man sonst nicht garantieren kann, dass der Sensor senkrecht auf eine Fläche gerichtet ist. WICHTIG: Es wird zwischen Bot- und Mapkoordinaten unterschieden. Wenn der Bot x = 0.08 hat, so hat die Map x = 8 (siehe real2grid und grid2real). Außerdem sind im Roboterkoordinatensystem positive und negative y-Richtung vertauscht und somit ist die positive Drehrichtung auch invertiert (also rechts herum positiv)
Erklärung:
khepera3.infrared_proximity.sensor
: Array auf aktuelle SensorwerteIR_LEFT
: Konstante der ID des linken SensorssensorVal
: Wert des linken SensorssensorDist(...)
: berechne Distanz zum Hindernis in Abhängigkeit vonsensorVal
1.1 Wände einzeichnen
Eure Aufgabe ist nun, die Methode getWallPos(...) (siehe student.cpp) zu implementieren, die die korrekte Wandposition in Abhängigkeit der Botposition bestimmt und an den Adressen x, y abspeichert. Die korrekte Wandposition ist links vom Bot in der Distanz vom Mittelpunkt des Bots zur Wand. Für diese Aufgabe braucht ihr:
- Botposition ( stp.ot.result.x , stp.ot.result.y )
- Ausrichtung des Bots ( stp.ot.result.theta )
- Den Wert des Sensors IR_LEFT ( khepera3.infrared_proximity.sensor[IR_LEFT] )
- Die ungefähre Distanz zur Wand ( nutzt dafür die Methode: sensorDist('Wert von Sensor IR_LEFT)) )
Um eure Methode zu testen kompiliert wieder simA1_1.txt und startet das Programm. Wird der Roboterpfad nun von einer rot dargestellten Wand umschlossen (abgesehen von dem kleinen Teil, in dem er geradeaus zur Wand fährt) und entspricht der Istwert der Distanz zwischen Bot und Wand immer dem Sollwert, so ist diese Aufgabe bestanden. Wenn 2-3 kleine Lücken auftauchen (eine Lücke = ein fehlender Pixel) ist das erstmal nicht schlimm.
1.2 Landmarken und Pfadkorrektur
Setzt nun zunächst in student.h die Konstante A1_2 auf 1, A1_1 auf 0 und USEDATAPATH auf "simdata/simA1_2.txt".
Während der Bot eine Wand verfolgt wird ein track angelegt. Hier wird jede Position, Ausrichtung und der linke Sensorwert gespeichert. Damit euer Bot sich zurechtfindet braucht er Orientierungspunkte, sogenannte Landmarks. In diesem Projekt sind das Eckpunkte. Ist eine Wand vollständig abgefahren, so werden aus einem track die Eckpunkte gesammelt. Diese werden zur Korrektur der Map genutzt.
Eure Aufgabe ist nun mithilfe dieser Daten einen Track zu korrigieren. Verwendet hierfür simA1_2.txt. Das ist im übrigen auch die größte Map, die vorkommen kann. Wenn ihr den Verlauf auf der Map gut beobachtet, werdet ihr erkennen, dass die "rechten Winkel" nicht wirklich rechtwinklig sind. Das liegt daran, dass die Odometrie des Roboters denkt, dass er sich in den Kurven um mehr als 90° bzw. PI/2 drehen musste. Dieser Fehler sorgt dafür, dass der Bot seinen Startpunkt verpasst.
Ihr müsst nun dafür sorgen, dass dieser Fehler korrigiert wird. Dazu muss der Bot
- erkennen, dass der Startpunkt verfehlt, oder allgemein, dass ein Fehler gemacht wurde
- das Fehlerausmaß ermitteln
- Korrektur ermitteln
- Korrektur auf track anwenden
Wie ihr vielleicht an den Ausgaben im Terminal gesehen habt wird bereits erkannt, ob der Bot seinen Startpunkt verfehlt. Die Genauigkeit hier liegt bei 1cm. Um das zu prüfen werden zwei Radien genutzt. Der innere Radius (1cm) muss erreicht werden, um den Pfad als "Fehlerfrei" anzusehen. Der äußere Radius dient der Überprüfung, ob der Bot in der Nähe des Startpunktes liegt. Die Fehlerbedingung lautet wie folgt:
- Ist der Bot in den äußeren Radius gelangt, aber nicht in den inneren UND entfernt sich der Bot wieder vom Startpunkt, so hat er seinen Startpunkt verpasst.
Ein weiterer wichtiger Punkt, den ihr für die Eckpunkte im Hinterkopf behalten solltet ist, dass auch geprüft wird, ob der Bot in eine ähnlich Richtung gut, wie er sie am Startpunkt hatte. Das ist wichtig, da der Roboter unter Umständen auch unerwünscht in den äußeren Radius gelangen kann (z.B. wenn der Startpunkt auf der anderen Seite einer sehr engen Passage ist). Jetzt aber zur Fehlerkorrektur.
Hier kann man die Arbeit gut wie folgt aufteilen.
Teil 1
Die Similarity Transform wird in den Videos zum Kurs SLAM von Prof. Claus Brenner sehr gut erklärt (auf Englisch). Implementiert hierzu bitte die dafür vorgesehen Klasse und ihre Methoden in KheperaStudent/Khepera-Lib/simTrans/simTrans.h. Die Methoden sind similarityTransform(...) und useTransformSingle(...). Der Parameter perc ist erstmal nicht wichtig und bleibt auf 1. Die entsprechende Videoreihe geht von SLAM B 01 bis SLAM B 03 bzw.: zu Youtube
Kurze Zusammenfassung der einzelnen Schritte von Similarity Transform:
- Erkennen von Landmarks (Hier Eckpunkte)
- Heraussuchen der zugehörigen bereits vorher gefundenen Landmarks
- Berechnen der Vektorschwerpunkte von alten und neuen Landmarks
- Reduzierte Koordinaten aus linker, rechter Liste berechnen
- cs, ss, rr, ll berechnen
- lambda, c, s berechnen
- tx, ty berechnen
Teil 2
Damit der Roboter eine Korrektur durchführen kann, braucht er mehr Informationen. Er muss also weiterfahren, als nur bis zum Startpunkt. Genauer gesagt muss er an zwei Eckpunkten vorbeifahren, um diese zu scannen. In simA1_2.txt (was ihr gerade nutzt) fährt er bereits viel weiter als nötig. Eure Aufgabe ist nun wie folgt:
- Macht euch eine Kopie vom alten track
- der Track ist als Stack (
KheperaStudent/Khepera-Lib/stack/stack.h
) gespeichert Stack<track_t> oldtrack = track
ist völlig ausreichend. Der Zuweisungsoperator=
Operator wurde hier umgeschrieben und macht eine deep copy.
- der Track ist als Stack (
- Ermittelt die Daten der Eckpunkte aus dem bisherigen track
- Nutzt dafür einfach
collectCorners(...)
und macht euch mit der Klasse Stack vertraut, da ihr einen braucht, um die Daten zu speichern.
- Nutzt dafür einfach
- Berechnet eine Schätzung, wie weit er fahren muss, bis er an zwei Eckpunkten vorbeigefahren ist
- Berechnet die Distanz vom eigentlichen Startpunkt zum ersten Eckpunkt
- Berechnet die Distanz vom ersten Eckpunkt zum zweiten
- Bildet die Summe
- Erhöht den Wert um einen Faktor >= 1.2 (Damit er an der zweiten Ecke zum scannen vorbeifährt)
- Sorgt dafür, dass er nur genau soweit fährt.
- löscht den track, damit er wieder von vorne Daten sammeln kann
- sorgt dafür, dass er wieder weiterfährt
- prüft immer die bisher gefahrene Strecke (nicht die euklidische Distanz, sondern die Länge des bisherigen Pfades)
- bedenkt, dass ihr hier am Anfang seine tatsächliche aktuelle Position und NICHT den ursprünglichen Startpunkt der Wandverfolgung nutzt
- ermittelt die Daten der Eckpunkte, welche er gerade besucht hat
- Nutzt wieder
collectCorners(...)
, nur diesmal mit dem neuen track. Speichert die Daten in einem weiteren Stack.
- Nutzt wieder
Teil 1 und 2 zusammenführen
- Benutzt die Stacks
leftlist
undrightlist
für eure linke und rechte Liste, da diese gleich zum testen genutzt werden. Die Similarity Transform wird nun auf den alten track (oldtrack) angewendet. WICHTIG: da Fehler Stück für Stück auftreten und nicht der ganz Pfad gleich "falsch" ist, müsst ihr die SimTrans anteilig anwenden. Der erste Punkt des oldtrack ist am weitesten weg und muss somit zu 100% korrigiert werden. Der letzte Punkt muss gar nicht korrigiert werden, also 0%. Erweitert hierzu eure Korrekturfunktion um einen Parameter, der anteilige Korrekturen erlaubt. Kleiner Tipp: der jeweilige Prozentanteil ist auf alpha, tx und ty anzuwenden.
Jetzt müsst ihr nur noch die Variable buildwall auf false setzen, kompilieren und euer Programm starten. Lief alles richtig, so ist eure Map nun korrigiert. Wichtig hierbei ist, dass der Pfad für eure Wand partiell korrigiert wurde und die gesamte restliche Map zu 100%.
In der Ausgabe des Terminals könnt ihr sehen, ob eure Berechnungen korrekt sind. Weichen die Ist- und Sollwerte zu stark voneinander ab habt ihr vielleicht einfach leftlist und rightlist vertauscht. Vergesst nicht, dass an dieser Stelle nicht die Botposition, sondern die ganze Map korrigiert wird. Es kann hier gut sein, dass die map nach der Korrektur über den mapserver nicht vollständig angezeigt wird, weil hier zu viele Daten verarbeitet werden. Mit der Funktion debugPrintMap(stp.map) könnt ihr euch die map aber im Terminal anzeigen lassen. Für diese Aufgabe wird sie bereits eingesetzt.
Aufgabe 2 Markierung, Wegfindung und Erkundung - Zeitraum: 2 Wochen
WICHTIG: Die Teilaufgaben dieser Aufgabe können völlig unabhängig und in beliebiger Reihenfolge bearbeitet werden (d.h. ihr könnt sie untereinander aufteilen, wenn ihr wollt). Beachtet aber, dass jede ein gutes Stück komplexer ist als ist vorherige.
Deaktiviert nun erstmal wieder A1_2.
Bisher wurde immer nur eine Wand abgefahren und die einzigen markanten Punkte waren Eckpunkte. Jetzt sollen alle weiteren Wände gefunden werden.
2.1 Markierung
Diesen Aufgabenteil könnt ihr nur im Labor vollständig testen ABER ihr könnt und solltet ihn trotzdem zuhause vorbereiten.
Setzt nun zunächst in student.h die Konstante A2_2 auf 1 und A1_2 auf 0.
Auf einigen der Styroporwände findet ihr Codes. Diese müssen erkannt und ihre Position für später gespeichert werden. Das geht aber ganz schnell, da der Erkennungsalgorithmus schon mit getcode() bereit steht. getcode() liefert euch dabei die Nummer des Codes (1,2,3) als Integer, oder -1 falls kein Code erkannt wurde (was also meistens passiert). Möchte man später den Punkt anfahren, so errechnet man eine angemessen von der Wand entfernte Position, aber dazu später mehr.
Erweitert state_follow_leftWall() so, dass die einzelnen Codepositionen im globalen Array codepositions (bereits vorhanden) gespeichert werden, wenn der entsprechende Code gefunden wurde. codepositions ist mit Nullen initialisiert. Mehr ist hier nicht zu tun. Die Codepositionen werden bei Fehlern der Wandverfolgung bereits mit korrigiert.
Testet euer Ergebnis im Labor. Hierzu braucht ihr folgendes: Öffnet in Roblab compileAll.sh und ändert die Variable COMPGCC auf false. Jetzt wird nicht mehr für euren Rechner sondern für die Roboter-Architektur kompiliert. Falls nicht schon erledigt ändert auch noch die Variable botnr auf eure Botnummer (siehe Botrücken).
Schaltet nun euren Bot ein und wartet bis die WLAN-Karte konstant leuchtet. Setzt nun den start-state auf state_forward(1) und kompiliert euren Code mit ./compileAll.sh. Der Code wird automatisch euren Bot übertragen. Mit
k3go +BOTNR
wechselt ihr auf euren Bot. Wechselt nun in euren Gruppenorder.
cd progrp6
für Gruppe 6. Euer Bot darf NICHT auf dem Tisch gestartet werden. Baut euch ein Rechteck aus Styroporwänden (ca. 1/4 der Karte) und setzt noch ein einzelnes Wandelement in die Mitte. Jetzt könnt ihr den Bot mit:
./main
starten.
- Vergesst nicht einen commit für eure Aufgaben zu erstellen!
2.2 Wegfindung
Für die Bearbeitung dieser Aufgabe braucht ihr folgende Variablen:
stp.ot.result.x
bzw.stp.ot.result.y
für die aktuelle Position des Roboters (ACHTUNG: mit real2grid in Mapkoordinaten umwandeln)- stp.ot.result.theta für die Ausrichtung (wo gucke ich hin?). WICHTIG: Die positive Drehrichtung des Roboters ist RECHTS herum.
Um den kürzesten Pfad von einem Punkt zum anderen zu kriegen könnt ihr getPath() nutzen. getPath() implementiert den Floodfill-Algorithmus, also eine einfache Breitensuche. Je nachdem, ob ihr einen Status oder eine Koordinate eingebt liefert euch getPath() entweder den kürzesten Pfad vom Startpunkt zum dichtesten Punkt, der diesen Status hat (z.B. UNKNOWN) oder zu der jeweiligen Zielkoordinate. getPath() verwendet dabei die von euch übergebene map, d.h. ihr könnt auch eine manipulierte Version der Originalmap übergeben. Der ausgegebene Pfad ist ebenfalls in Bot-Koordinaten. WICHTIG!! getPath nimmt den kürzesten Pfad mit einer minimalen Breite von 1cm. Das könnt ihr im Terminal beobachten, wenn ihr den Code mit state_pathfindingTest(1) startet. Ihr kriegt eine Karte für den Startzustand und eine nach der Pfadsuche. Der Startpunkt befindet sich in dem kleinen Kasten (g), der Zielpunkt ist mit b markiert und der Pfad wird in der zweiten Karte mit dem Buchstaben "g" angezeigt (Das ist bereits ein optimierter Pfad, der nur die absolut nötigen Pfadpunkte zeigt). D.h. wenn ihr eure normale Map übergebt kriegt ihr einen Pfad, der auch direkt an der Wand (1 cm Distanz) verlaufen kann (siehe Terminal). Das ist natürlich kritisch, da der Bot nicht nur ein kleiner 1cm²-Block ist, sondern einen gewissen Durchmesser (ca. 14cm) hat. Weiterhin wollt ihr mindestens so weit von der Wand entfernt sein, wie bei der Wandverfolgung, wenn nicht sogar 1-2cm weiter. Eure Aufgabe ist nun eine Möglichkeit zu entwickeln dafür zu sorgen, dass der kürzeste Pfad nicht mehr so dicht an der Wand liegt.
Dieses Problem könnt ihr lösen wie ihr möchtet, aber im Wesentlichen habt ihr folgende Möglichkeiten: 1. Ihr modifiziert getPath() so, dass der Pfad den Abstand einhält. 2. Ihr gebt getPath() eine modifizierte Version der Map mit "fetten" Wänden.
Letzteres ist wesentlich leichter zu realisieren und wird deshalb von uns empfohlen. Achtet dabei aber auch darauf, die Wände nicht so dick zu machen, dass Wege versperrt werden. Weiterhin müsst ihr dafür sorgen, dass Bot und Ziel nicht von den dicken Wänden eingeschlossen werden. Kleiner Tipp: Erst neue map erstellen, dann Bot und Ziel in dieser Map befreien. Egal wofür ihr euch entscheidet, sorgt dafür, dass die Distanz zur Wand leicht zu ändern ist, sprich: durch Änderung einer Variablen bzw. Parameter einer Funktion.
Testet eure Lösung mit dem State state_pathfindingTest(1) und einer Wanddistanz von 3cm (diese Distanz reicht erstmal fürs Testen).
- Vergesst nicht einen commit für eure Aufgaben zu erstellen!
2.3 Erkundung
WICHTIG: Auch diese Aufgabe kann stark zuhause vorbereitet werden. Lest euch die Aufgabe also bitte erstmal vollständig durch, bevor ihr denkt, ihr müsst für alles im Labor sein.
Ihr könnt jetzt eine Wand abfahren, die Codes scannen und speichern und einen Pfad woanders hin finden. ABER bis jetzt habt ihr den Bot eigentlich nie gesteuert. Beobachtet jetzt das Verhalten und schaut euch im Code an, wie das realisiert wird. Stoppt den Bot, wenn ihr genug gesehen habt. Öffnet nun in KheperaStudent/Roblab das Script getData.sh und ändert dort die Bot-ID auf die von eurem Bot. Führt getData.sh nun einmal aus. Falls noch nicht vorhanden habt ihr jetzt ein Verzeichnis das mit "k3" beginnt und mit eurer Botnummer endet. Dort wird mit getData.sh immer die aktuelle collectedData.txt Datei heruntergeladen (sofern euer Bot an ist). Diese Datei könnt ihr genauso benutzen wie die bisherigen Simulationsdateien, sprich ihr ändert den Pfad in USEDATAPATH in student.h. Ihr könnt jetzt also jede Testfahrt die ihr macht speichern und exakt am Rechner wiederholen so oft ihr wollt, aber vor allem wesentlich schneller. So könnt ihr im Labor gesammelte Testfahrten auch zuhause nutzen und insbesondere auch wesentlich leichter Debuggen. Nutzt diese Möglichkeit bitte, wann immer möglich. Das spart euch viele Arbeitsstunden. Zum Vergleich: Die Erkundung der gesamten Map kann mit dem Bot ca. 10 Minuten dauern. Auf dem Rechner kann man diese 10 Minuten in einer Sekunde durchführen.
Euer Bot soll jetzt, nachdem er eine Wand erkundet hat, zur nächsten unbekannten Position fahren. Holt euch also einen Pfad zum nächsten unbekannten Punkt mit getPath(). Nun müsst ihr die einzelnen Punkte des Pfades abfahren. Euer Bot muss also eigentlich nur zwei weitere Dinge lernen.
- Dreh dich zum nächsten Punkt.
- Fahre geradeaus, bis du beim nächsten Punkt bist.
Schreibt dies bitte in den State state_drive2NextUnknown. Zur Wiederholung: Im Roboterkoordinatensystem sind positive und negative y-Richtung vertauscht und somit ist die positive Drehrichtung auch invertiert (also rechts herum positiv).
WICHTIG: Der Bot setzt zur Zeit nur seine eigene Position frei. Damit die Karte zu vervollständigen dauert ewig (bzw. ist unmöglich, da er nicht nah genug an die Wand herankommt). Sorgt dafür, dass ein größerer Bereich freigesetzt wird. Ihr dürft aber keine Wände überschreiben. Prüft also, welchen Zustand die zu setzende Position gerade hat.
Wenn keine unbekannten Positionen mehr erreichbar sind (kein Pfad), dann soll der Bot stehenbleiben (machine_stop()).
Habt ihr alles richtig gemacht, so könnt ihr mit gridmap.sh beobachten, wie der Bot Stück für Stück die Karte ausfüllt. Auf einer größeren Karte wird es zu Fehlern kommen, aber um die kümmert ihr euch in der nächsten Aufgabe.
Aufgabe 3 - Zeitraum: bis zum letzten wöchentlichen Termin
Diese Aufgabe ist die Hauptaufgabe, in der alles Vorherige benötigt wird. Lest euch die Aufgabe bitte erstmal vollständig durch bevor ihr anfangt. Das folgende Verhalten soll implementiert werden:
Der Bot wird irgendwo in ein Labyrinth eingesetzt, welches die Hälfte des gesamten Feldes einnimmt. Von hier aus soll er zunächst die gesamte Karte erkunden. Das heißt, er soll in der map alle erreichbaren Positionen und alle erkennbaren Wände dokumentieren. Dabei sind im Labyrinth drei Codemarkierungen mit der Kamera zu finden. Sobald der Bot die Karte vollständig dokumentiert hat soll er die einzelnen Codes besuchen. Hat der Bot die letzte Codeposition erreicht ist die Gesamtaufgabe erfüllt. Der gesamte Ablauf darf dabei maximal 15 Minuten dauern und der Bot darf keine Wände berühren. Während eurer Vorführung müsst ihr angeben können, in welchem Zustand der Bot gerade ist, und erklären können, was der Bot gerade tut.
Das meiste kommt euch ziemlich bekannt vor. Was kommt also noch dazu? Bisher habt ihr immer nur Pfade zum nächsten unbekannten Punkt gesucht. Jetzt müsst ihr am Ende zusätzlich noch die Codepositionen anfahren. Implementiert dafür den State state_drive2nextcode(), für den ihr das meiste aus state_drive2NextUnknown kopieren könnt. Für den Pfad nutzt ihr dabei weiterhin die Methode getpath(), ABER diesmal setzt ihr natürlich Zielkoordinaten. Der Wert für den Zustand muss jetzt auf "(status_t)-1" gesetzt werden.
Jetzt kommen wir zum Kernpunkt dieser Aufgabe. Macht hier erst weiter, wenn der Teil davor erledigt ist. Beachtet hier bitte die Unterscheidung zwischen state_drive2NextUnknown und state_drive2nextcode: Am Ende von 2.3 haben wir von möglichen Fehlern gesprochen, die der Bot während der Fahrt macht. Bei der großen Karte die ihr am Ende abfahren müsst lassen sich Fehler kaum vermeiden, aber korrigieren (so wie in Aufgabe 1.2). Der Bot darf nicht gegen Wände fahren. Bei einer fehlerfreien Fahrt wäre das nach Aufgabenteil 2.2 Pathfinding kein Problem mehr. Allerdings fahren die Bots nicht fehlerfrei und so kann es dazu kommen, dass der Bot zwischendurch eine Wand "mitnimmt" oder ankratzt. Um das zu vermeiden müsst ihr in state_drive2NextUnknown() Wände unterscheiden können. Ihr unterscheidet hier zwei Fälle.
- Die Wand ist bekannt (es handelt sich also um bereits gesammelte Wandkoordinaten)
- Die Wand ist neu (ihr müsst die Wand also tatsächlich neu abfahren)
Der zweite Fall ist also der Normalfall. d.h. es kam zu keinem Fehler. Kommt es zum ersten Fall, so müsst ihr diese Wand solange nochmal abfahren, bis ihr mindestens wieder 2 Eckpunkte gefunden habt. Anschließend müsst ihr nur die Position des Roboters korrigieren. Dafür nutzt ihr wieder eure Methoden similarityTransform zur Berechnung der Transformation, und useTransformSingle(...) für die eigentliche Korrektur. Im Gegensatz zu state_follow_leftWall(...) wird hier aber die Botposition korrigiert, und NICHT die Map.
In state_drive2nextcode ist das wesentlich einfacher. Ihr habt die gesamte Karte bereits erkundet. Also gibt es auch keine unbekannten Wände mehr, und jede Wand der ihr zu nahe kommt ist eine bereits bekannte Wand. Die Unterscheidung fällt also weg. Die Korrektur aber natürlich nicht. Implementiert sie noch für diesen State und testet alles ordentlich durch.
WICHTIG: Euch wird der große Bearbeitungszeitraum für diese Aufgabe aufgefallen sein. Seht das bitte nicht als Einladung jetzt lockerzulassen. Nachdem ihr alles implementiert habt werdet ihr sehr wahrscheinlich viel Zeit mit Tests und Korrekturen verbringen. Ein wesentlicher Teil dieser Aufgabe ist nämlich das Anpassen des Fahrverhaltens eures Bots und seiner Parameter. Das bedeutet mehr Arbeit im Labor.
Für das allgemeine Verhalten bei der Wandverfolgung ist die Methode pureFollowLeftWall()
zuständig. Mit der if-Bedingung könnt ihr festlegen, wie dicht der Bot in einer Ecke an die Wand heranfährt, bevor er sich dreht. Im else-Teil könnt ihr die Geschwindigkeit mit "speed" manipulieren. In mymachinedrive(...)
könnt ihr durch Änderung von "diff" beeinflussen, wie stark er nach links/rechts korrigiert, während er die Wand verfolgt.
In state_forward
, state_drive2NextUnknown
und state_drive2nextcode
geht es im Wesentlich darum, anzupassen mit welchen Sensoren man Hindernisse detektiert, und wie früh man reagiert.
Euer Bot kann das alles? Herzlichen Glückwunsch! Damit könnt ihr bei erfolgreicher Abgabe ein paar Leistungspunkte gewinnen. Ihr solltet allerdings trotzdem noch einen commit erstellen.