Dies ist die Projektwebsite zum Anfängerpraktikum Lego Boules vom Team A im Sommersemester 2017 an der Universität Heidelberg.
Wir sind Tom (Angewandte Informatik) und Jonas (Mathematik) und studieren seit 4 Semestern im Bachelor 100%.
Unser Robotikpratikum wurde von Monika Harant betreut und von Katja Mombaur supervised.
Zu Beginn des Praktikums haben wir uns die folgende Aufgabe definiert:
Unser Ziel ist es, einen Roboter aus Lego zu bauen und programmieren. Dieser soll am Ende des Praktikums in der Lage sein, eine Metallkugel über eine vorgegebene Distanz über eine Schaumstofffläche rollen zu lassen. Insbesondere liegt der Fokus dabei auf der Genauigkeit.
Der Roboter soll also eine vereinfachte Form des Spieles Boules simulieren können. Dazu gab es am Ende des Projektes einen Wettbewerb mit dem anderen Lego Boules Team.
Ebenfalls zu Beginn des Projektes haben wir uns Meilensteine definiert, welche einen Leitfaden bei der Umsetzung des Praktikums bildeten:
Unseren ersten Meilenstein konnten wir schnell abschließen.
Wir haben lange gebraucht, um den Brick mit dem Laptop zu verbinden, aber dann lies sich, dank Pythons einfacher Syntax, schnell damit arbeiten.
Wir haben anschließend ein kleines Hello World
-Programm geschrieben.
Zu diesem Zeitpunkt haben wir uns auch dazu entschieden, git als Versionskontrollprogramm zu benutzen.
Die restlichen Meilensteine konnten wir dann relativ gut erreichen. Dies lag auch an intensiven Arbeitsphasen vor den Meilensteinterminen.
Wie der Name des Praktikums schon verrät, besteht unser Roboter hauptsächlich aus Legosteinen. Diese sind auf der einen Seite besonders gut geeignet, weil sie einfach kombinierbar sind und problemlos wiederverwendet werden können. Jedoch hat Lego auch Nachteile, wie zum Beispiel:
Als Kugel wurden ca. 90 Gramm schwere und 2,8 cm breite Metallkugelen verwendet. Das Spielfeld bildete eine Schaumstoffmatte, welche ein paar Zentimeter dick war. Im Verlaufe des Praktikums sind zu unseren Materialien noch Pappe, Holz und Heißkleber hinzugekommen.
Unsere erste Idee war, eine Rampe mit verstellbarem Winkel zu bauen, von der wir die Kugel runterrollen lassen. Dabei sollte ein glatter Übergang von der Rampe zum Schaumstoff gegeben sein, sodass die Kugel beim Aufkommen nicht umherspringt. Konzeptuell sollte es so aussehen:
Bevor wir angefangen haben, diese Idee in einen ersten Prototypen umzusetzen, wollten wir uns erst einmal mit den Grundlagen der Physik beschäftigen und analysieren, wie sich denn die Kugel auf einer Rampe verhalten würde. Hier haben wir versucht, das Modell zu vereinfachen, indem die Rampe komplett gerade ist. Es zeigte sich jedoch später, dass diese Verienfachung starke Auswirkungen auf die tatsächliche Weite der Kugel hat.
Die Idee war relativ intuitiv und simpel: Je nachdem wie man den Winkel \( \alpha \) der Rampe zum Boden einstellt, verändert sich die Startposition der Kugel und somit die Höhe \( h \) bei fester Strecke \( s \). Dann lässt sich eine Funktion herleiten, welche für eine gegebene Distanz \( z \) den entsprechenden Winkel \( \alpha \) liefert. So kamen wir auf folgende Formel \[ z = \frac{5}{7}s \cdot \frac{\sin \alpha - c_s \cdot \cos \alpha}{c_z} , \] wobei \(c_s \) und \(c_z\) noch zu bestimmende Reibungskoeffizienten sind. Diese hat leider den Nachteil, dass sie schwer nach \( \alpha \) umstellbar ist. Womit wir unseren zweiten Meilenstein beendet haben, mit einem wenig hilfreichen Ergebnis.
Leider hat sich herausgestellt, dass dieses theoretische Modell in der Praxis nicht wirklich geeignet war, um Weiten für gegebenes \( \alpha \) vorherzusagen. Deshalb haben wir uns in der Zukunft dazu entschieden, eine empirisch begründete Funktion zu benutzen.
Zunächst haben wir versucht, eine Metallschiene als Rampe zu verwenden, jedoch gestaltete sich die Montage auf den Legosteinen als kompliziert. Deshalb haben wir die Rampe kurzerhand komplett aus Lego gebaut. Dies klappt relativ gut, da die Kugel auf den Kanten der Unterseiten entlang rollen kann. Einzig beim Übergang von einem zum nächsten Legostein gibt es leichte Unebenheiten, die sich aber nicht auf die Kugelbewegung auswirken.
Um die Rampe nun zu neigen, haben wir sie an einem Punkt unten auf einer Lego Platte befestigt, sodass sie sich um diesem Punkt drehen konnte. Hinten war die Rampe mit einer Stütze mit einem Motor auf dem Boden verbunden. Dadurch, dass der Motor sich vor- oder zurückbewegt hat, änderte sich der Winkel der Rampe.
Erste händische Versuche haben ergeben, dass die Kugel nur eine sehr geringe Reichweite hat. Außerdem lagen minimale und maximale Distanz sehr nah beieinander, wie man im obigen Bild sieht. Jedoch stellten wir fest, dass die Reichweite gar nicht so sehr von der Höhe, sondern von der Startposition auf der Rampe abhängt, was uns auf die Idee für die finale Version brachte. Insgesamt wirkten auch die Ergebnisse dieses Meilensteins "Hardware planen & bauen" eher enttäuschend. Deshalb sind wir dazu übergegangen, andere Ansätze auszuprobieren.
Im nächsten Meilenstein "erster Protyp" haben wir kleine Prototypen erstellt und getestet, welche im folgenden jedoch zur besseren Erkennbarkeit nur schematisch dargestellt sind.
Idee: Die sechs Reifen werden von Motoren über Zahnräder gedreht, so gedreht, dass in der Mitte eine „Saugbewegung“ nach vorne entsteht. Die Kugel liegt dort drin und wird somit nach vorne katapultiert.
Probleme: Zum einen ist die Kugel viel zu glatt, als dass es genügend Reibung geben könnte, um die Kugel zu bewegen. Zum anderen ist die Kugel für die entsprechenden Lego-Reifen zu klein gewesen, sodass sie in dem Loch zwischen vier Reifen hängen geblieben ist. Vermutlich wären die Motoren auch nicht schnell und kräftig genug, um die Kugel auf Geschwindigkeit zu bringen, wobei man dies durch geeignete Übersetzung vielleicht lösen könnte.
Idee: Um die notwendige Beschleunigung für die Kugel zu erhalten, wird in diesem Ansatz die Kugel sehr weit von Motorrotationsachse entfernt positioniert. Wenn sich der Motor nun etwas dreht, legt die Kugel eine große Strecke zurück. Sie muss nur noch zum richtigen Zeitpunkt aus der Halterung geschupst werden.
Probleme: Das Gewicht der Kugel ist viel zu hoch, als dass ein solch langer Arm die Kugel und noch einen weiteren Motor aus Auslöser tragen könnte. Dafür müsste man mit einem Gegengewicht arbeiten, was die Geschwindigkeit wieder bremst. Außerdem vermuten wir, dass die Wurfdistanz nicht sehr hoch ist.
Idee: Die Rotationsenergie eines Motors wird in eine Längsbewegung umgewandelt, welche wiederum in elastische Energie umgewandelt wird, indem eine Platte mit einem Gummiband nach hinten gezogen wird. Beim Loslassen der Platte wird die Kugel angestoßen und nach vorne katapultiert. Je nachdem wie stark die Bänder gespannt werden desto weiter fliegt die Kugel.
Probleme: Das Gewicht der Kugel ist sehr hoch und somit wären sehr viele Bänder nötig. Und auch dann würde man mit einer Impulsübertragung vermutlich nicht sehr weit gekommen.
Idee: Die Kugel wird zwischen zwei Reifen eingespannt, welche über Achsen an rotierenden Motoren befestigt sind. Somit wird die Kugel auf Geschwindigkeit gebracht. Zu einem geeigneten Zeitpunkt wird die Kugel dann losgelassen aus der Klemme und rollt nach vorne wegen der Haftreibung
Probleme: Zum einen sind die Motoren sind nicht schnell genug, wobei dies durch Übersetzung lösbar wäre. Zum anderen ist die Kugel zu glatt und hat somit keine Haftung auf dem Untergrund und bewegt sich nicht vorwärts, sondern dreht durch.
Aus den geschilderten Problemen, haben wir uns dazu entschieden, noch einmal den Rampen-Ansatz auf Realisierbarkeit zu überprüfen.
Die erste Veränderung im Vergleich zu dem früheren Prototyp war die Fixierung der Rampe, d.h. der Winkel zwischen Rampe und Boden war fest. Stattdessen wollten wir nun die Startposition der Kugel auf der Rampe verschieben und dadurch verschiedene Weiten erzielen. Dadurch wurde die Gesamtkonstruktion sehr viel stabiler.
Dafür haben wir einen Käfig gebaut, der auf der Rampe beweglich ist. Diesen haben wir mit einer Kette an einem Zahnrad befestigt, durch Drehen dieses Zahnrades wurde dann der Käfig hoch- bzw runterbewegt. Um das Zahnrad automatisch zu bewegen, haben wir einen Motor angebracht, den Lift.
Der Käfig hat einen kleinen Freiraum, in dem die Kugel untergebracht wird. Dieser Käfig hat vorne ein Tor, dieses Tor kann durch einen weiteren Motor, den Trigger, geöffnet und geschlossen werden.
Durch den abrupten Übergang von der Rampe zu der Matte gab es einige Probleme. Einerseits wurde die Weite stark eingeschränkt, da die Kugel viel umhergesprungen ist; andererseits war die Weite aus dem selben Grund auch wenig konsistent.
Diesem Problem konnten wir dadurch entgegen wirken, dass wir einen Übergang aus Pappe mit Heiskleber an der Rampe befestigt haben. Dies führte dazu, dass wir sehr viel konsistenere und längere Weiten erzielen konnten. Durch diese Modifikation, wurde dann der Rampen-Ansatz gerettet und wir konnten die restliche Zeit dieses Meilenstein dafür verwenden die Rampe in einen wirklichen Roboter zu verwandeln.
Damit unser Roboter sich auch tatsächlich bewegt, fehlt nun noch unsere Software. Da diese auf dem Brick mit ev3dev laufen soll, hatten wir die Auswahl zwischen verschiedenen Programmiersprachen. Wir haben uns für Python entschieden, da es gut dokumentiert war und Entwicklung in Python leicht ist.
Insgesamt besteht unsere Software aus zwei Programmen:
controller.py
stellt das Grundgerüst unserer Software und wird automatisch beim Ausführen von controller.py
instanziert und aufgerufen.
Dafür besitzt der Controller eine Interaction Instanz, um der Userin Text anzuzeigen und um Eingaben zu bitten.
Diese Eingabe wird dann mithilfe von model_dicts.py
dazu benutzt, um die Kugel entsprechend weit zu rollen.make_model_dicts.py
wird model_dicts.py
automatisch erstellt, um gegebene Weiten umzurechnen. Für das Rollen einer Kugel wird zuerst die Weite abgefragt. Diese wird dann umgerechnet zu Umdrehungen des Lift-Motors. Der Lift bewegt sich so viele Grad. Anschließend wird der Trigger geöffnet und die Kugel somit losgelassen. Dann wird der Trigger geschlossen und der Lift begibt sich in die Ausgangsposition zurück. Abschließend wird die Userin gefragt, ob sie noch einmal spielen möchte.
Wir haben uns gedacht, dass nicht jeder immer ihren Laptop zum Boules spielen dabei hat, deshalb ist es praktisch, den Roboter über den Brick bedienen zu können. Dafür haben wir eine Interaction-Klasse geschrieben.
Diese verfügt über drei Methoden:
print
: Gibt alle angebene Parameter als einzelne Zeilen so groß wie möglich auf dem Bildschirm aus.get_number
: Lässt die Nutzerin eine Zahl zwischen start
und end
eingeben.
Indem eine neue Klasse angelegt wird, die als Zustand die aktuell angegebene Zahl anzeigt und Methoden bereitstellt, um auf Tastendrücke zu reagieren.
Dabei wird immer die aktuelle Stelle (die entsprechende Ziffer ist schwarz unterlegt) inkrementiert bzw. dekrementiert.get_bool
: zeigt eine Zeile an, gefolgt von "ja" oder "nein" und lässt die Nutzerin dazwischen entscheiden.Da der Lift-Motor sich nur über Gradzahlen ansteuern lässt, mussten wir einen Weg finden diese in eine geschätze Entfernung umzurechnen.
Dafür haben wir zuerst 20 Datenpunkte gleichmäßig zwischen minimaler und maximaler Position gemessen (Schritt 1, siehe Messreihen). Da wir an diesen Punkten relativ sicher sind, wie weit die Kugel rollt, haben wir uns dafür entschieden Spline-Interpolation zu benutzen, um Schätzungen für die restliche Position des Triggers zu generieren (Schritt 3 (im Vergleich zu anderen Schritt 2)). Somit haben wir also eine Funktion, die für eine gegebene Höhe eine ungefähre Weite schätzt (Schritt 4). Diese müssen wir also noch invertieren. Dies geschieht indem wir die Funktion an 190 Punkten auswerten (Schritt 5) und dann für jeden Centimer die Höhe heraussuchen mit dem kleinsten Abstand zu dieser Weite (Schritt 6 und 7). So erhalten wir eine Funktion die eine gegebene Weite in eine Höhe umrechnet (Schritt 8).
Diese Funktion speichern wir in einem Dictionary und dieses Dictionary wird in der model_dicts.py
Datei gespeichert
Der vollständige Quellcode befindet sich auf Github. Hier die wichtigsten 4 Dateien:
In dem Meilenstein "Messen, Lernen, Bauen" haben wir uns dann viel damit beschäftigt, wie weit die Kugel bei verschiedenen Startpositionen rollt. Dafür haben wir mit unserem fertigen Roboter an verschiedenen Tagen häufig gemessen. Dabei haben sich Daten ergeben, die klare Unterschiede an verschiedenen Tagen zeigen. Die Unterschiede sind vermutlich auf die unten gelisteten Probleme zurükzuführen.
Vorgegeben | Spline | Polynom | ||
---|---|---|---|---|
linear | kubisch | linear | kubisch | |
41 | 0 | 1 | 3.75 | 0 |
75 | 0 | -1.5 | -4 | -0.5 |
79 | -0.5 | -0.25 | -5.5 | -0.5 |
82 | 1 | 1 | -5 | 0 |
84 | 0 | 0 | 0 | -0.5 |
104 | -4 | -4 | -4.5 | -4 |
107 | -1.5 | -2 | -2.5 | -1.5 |
108 | -2 | -1.5 | -1.5 | 3.5 |
112 | -3 | -3 | 2 | 1 |
126 | 0 | 1 | 0.5 | 3.5 |
Für unsere Datenpunkte hatten wir verschiedene Möglichkeiten, eine Funktion zu fitten, welche zu einer Höhe eine Weite berechnet. Dafür haben wir uns Spline-Interpolation und Polynom-Approximation angeschaut. Diese konnten dann einfach mit dem ModelMaker in verschiedene Modelle überführt werden, mit denen eine Höhe vorhergesagt wird, mithilfe derer man auf eine gegebene Weite kommen soll.
Wir wollen nun folgende Funktions-Typen vergleichen:
Dafür sieht man in der nebenstehenden Tabelle verschiedene, zufällige Zielweiten und daneben den Abstand zur tatsächlich gerollten Weite mit der vorgeschlagenen Höhe, des jeweils vorgegebenen Modells.
Wir sind insgesamt mit den Ergebnissen zufrieden. Alle Modelle hatten relativ kleine Abweichungen für die getesteten Werte. Wobei allerdings die Splines klar besser sind als die Polynome. Zwischen linearen und kubischen Splines ist der Unterschied sehr gering. Da kubische Splines als Modellierungsfunktionen natürlicher sind, haben wir uns für die kubischen Splines entschieden. Allerdings ist das Laden der anderen Modelle mittels Komandozeilenargumenten problemlos möglich.
Im Laufe des Praktikums sind wir auf verschiedene Probleme gestoßen.
Das wohl größte Problem war, dass der Übergang zwischen Schaumstoffmatte und Rampe zu unglatt war. Sobald die Kugel von der Rampe auf die Schaumstoffmatte kam, ist sie erst einmal tief versunken und dann unregelmäßig umhergesprungen, wobei viel Energie nicht in die Reichweite umgesetzt wurde. Damit konnten wir nicht präzise Boules spielen.
Unsere Lösung für dieses Problem war eine Pappe, die wir mit Heißkleber unten an der Rampe befestigt haben. Da die Pappe sich im Gegensatz zu den Legosteinen biegen lässt, nimmt sie eine ziemlich optimale Form an, sodass der Übergang einigermaßen glatt ist. Um die Auswirkungen der Pappe zu sehen, haben wir ein vorher-nachher Vergleich gemacht:
Jedoch ließ sich das "Springen" nicht ganz vermeiden. Das liegt daran, dass der Übergang von der Legorampe zur Pappe nicht ganz optimal ist. Wir haben zwar versucht, die Pappe so einzukerben und mit Holz zu beschweren, sodass die Kugel möglichst ideal auf die Pappe kommt, aber das scheint immer noch nicht perfekt zu sein.
Das sieht man auch an den Werten der Messreihen. Wir vermuten, dass die Abweichungen mit der sich verändernden Pappe zusammenhängen. Denn je häufiger die Kugel auf die Pappe kommt, desto mehr verformt sich diese. Solch kleine Änderungen scheinen sich messbar auf die Distanz auszuwirken. Eine Lösungsidee hierfür wäre ein unverformbares, aber trotzdem vom Übergang her glattes Bauteil, welches man vielleicht mit einem 3D Drucker erstellen könnte.
bool
abgefragt haben und direkt danach ein int
. Nach langer Recherche, woran das liegen könnte, haben wir einfach ein sleep
eingebaut, was den Fehler behoben hat.Zur Inbetriebnahme müssen folgende Dinge gemacht werden
boules.sh
starten (ein ausführbares Shell-Skript, dass python3 controller.py
enthält). Alternativ zum Laden verschiedener Modelle sind folgende Aufrufe möglich:
python3 controller.py spline_lin
python3 controller.py spline_cubic
python3 controller.py pol_lin
python3 controller.py pol_cubic