Dieses Repository enthält die Dokumentation in Form einer Sphinx Doku.
## [Repository für den Quellcode zur Praxisergänzende Vertiefung 2](https://mygit.th-deg.de/fl09703/ros_plv)
## [Repository für den Quellcode zur Praxisergänzenden Vertiefung 2](https://mygit.th-deg.de/fl09703/ros_plv)
Dieses Repository enthält den Quellcode der Projekte.
- Im Verzeichnis 'tb3-ros2_1_2' befindet sich das Projekt 'Collision Avoidance'. Die Datei 'tb3-ros2_1_2/tb3.py' enthält dabei den eigentliche Quellcode.
- Im Verzeichnis 'tb3-ros2_1_2' befindet sich das Projekt 'Collision Avoidance'. Die Datei 'tb3-ros2_1_2/tb3.py' enthält dabei den eigentlichen Quellcode.
- Das Verzeichnis 'chall4/tb3-ros2-template' befindet sich das Projekt zum Lösen des Labyrinths. Auch hier enthält die Datei 'tb3-ros2-template/tb3.py' den Quellcode.
Das Ziel der Challenge "Collision avoidance" war es, dass der Roboter selbstständig Ziele erkennen, davor Stoppen und ihnen Ausweichen kann. Dies sollte nicht nur in der Simulation, sondern auch in der echten Welt funktionieren.
## Umsetzung
Der erste Schritt zur Lösung dieses Problems war die Erkennung von Hindernissen. Hierfür wurde der Laser Distance Sensor der TurtleBots benutzt. Dieser misst im 360 Grad Umkreis den Abstand des Bots zu anderen Gegenständen und versendet sie durch das 'scan'-Topic an potenzielle Subscriber. Ein solcher wurde hierfür entwickelt.
Zunächst wird für jeden Satz an Messwerten, der durch den Sensor versendet wird, überprüft, ob der Abstand zu dem Objekt direkt vor dem Roboter kleiner als 0.3 ist. Falls diese Bedingung erfüllt ist, befindet sich vor dem Bot ein Hindernis und er muss gestoppt werden. Um dies zu erledigen wird einfach die Nachricht "(0.0,0.0)" in das Topic "cmd_vel" gesendet werden. Der erste Wert in diesem Paar steht dabei, für die Geschwindigkeit mit der sich der Roboter bewegen soll, wohingegen der zweite die Drehbewegung bestimmt. Um für das Stoppen zu sorgen, müsse daher beide Werte mit 0.0 befüllt werden.
Der erste Schritt zur Lösung dieses Problems war die Erkennung von Hindernissen. Hierfür wurde der Laser Distance Sensor des TurtleBots benutzt. Dieser misst im 360 Grad Umkreis den Abstand des Bots zu anderen Gegenständen und versendet sie durch das 'scan'-Topic an potenzielle Subscriber. Ein solcher wurde hierfür entwickelt.
Zunächst wird für jeden Satz an Messwerten, der durch den Sensor versendet wird, überprüft, ob der Abstand zu dem Objekt direkt vor dem Roboter kleiner als 0.3 ist. Falls diese Bedingung erfüllt ist, befindet sich vor dem Bot ein Hindernis und er muss gestoppt werden. Um dies zu erledigen, wird einfach die Nachricht "(0.0,0.0)" in das Topic "cmd_vel" gesendet. Der erste Wert in diesem Paar steht dabei, für die Geschwindigkeit mit der sich der Roboter bewegen soll, wohingegen der zweite die Drehbewegung bestimmt. Um für das Stoppen zu sorgen, müsse daher beide Werte mit 0.0 befüllt werden.
Als Nächstes musste das Ausweichen vor dem Hindernis umgesetzt werden. Dafür muss sich der TurtleBot vor diesem wegdrehen. Dafür muss zunächst erneut eine Nachricht in das "cmd_vel"-Topic veröffentlich werden. Dafür muss der zweite Wert des Wertepaares auf die gewünschte Drehgeschwindigkeit gesetzt werden. In der konkreten Implementierung wird hier 90% der Maximalwertes (1.82 * 90% = 1.63) verwendet. Um genau zu sein, wird dieser Befehl mit dem oben beschriebenen Stopbefehlt kombiniert. Das bedeutet, es wird zunächst 0.0 als Wert für die Vorwärtsbewegung und 1.63 als Drehgeschwindigkeit gesetzt.
Nachdem sich der Roboter weit genug gedreht hat, wird die Drehgeschwindigkeit wieder auf 0.0 und die Bewegungsgeschwindigkeit auf den Maximalwert gesetzt, sodass sich der Bot in einer geraden Linie nach vorne fährt.
Als Nächstes musste das Ausweichen vor dem Hindernis umgesetzt werden. Dafür muss sich der TurtleBot vor diesem wegdrehen. Dafür muss zunächst erneut eine Nachricht in das "cmd_vel"-Topic veröffentlich werden. Dafür muss der zweite Wert des Wertepaares auf die gewünschte Drehgeschwindigkeit gesetzt werden. In der konkreten Implementierung wird hier 90% des Maximalwertes (1.82 * 90% = 1.63) verwendet. Um genau zu sein, wird dieser Befehl mit dem oben beschriebenen Stopbefehlt kombiniert. Das bedeutet, es wird zunächst 0.0 als Wert für die Vorwärtsbewegung und 1.63 als Drehgeschwindigkeit gesetzt.
Nachdem sich der Roboter weit genug gedreht hat, wird die Drehgeschwindigkeit wieder auf 0.0 und die Bewegungsgeschwindigkeit auf den Maximalwert gesetzt, sodass der Bot in einer geraden Linie nach vorne fährt.
Das Bestimmen, ob der Bot weit genug rotiert ist, wird anhand der Dauer der Bewegung realisiert. Hierzu wird zunächst, sobald die Drehung initialisiert wird, der Zeitstempel gespeichert. Danach wird für jeden Sensorwert, der durch den Laser Distance Sensor veröffentlicht wird, überprüft, ob seit diesem Zeitstempel eine gewisse Dauer vergangen ist. Hierfür wird bei dieser Aufgabe der Wert 0.3 Sekunden verwendet. Falls dies der Fall ist, wird die Bewegung gestoppt. Außerdem wird der Zeitstempel wieder auf den Python-Wert None gesetzt. Dadurch kann festgestellt werden, ob sich der Roboter aktuell in einer Drehbewegung befindet.
### UML
Der Ablauf für jeden empfangenen Sensorwert kann als folgendes Ablaufdiagramm dargestellt werden.
...
...
@@ -14,5 +14,5 @@ Der Ablauf für jeden empfangenen Sensorwert kann als folgendes Ablaufdiagramm d
## Probleme
Ein Problem, das während der Umsetzung auftrat, war, dass der Sensor des Roboters in der Simulation, die zum Testen verwendet wurde und in der Realität unterschiedliche Werte lieferte. Der konkrete Unterschied war, dass es in der echten Welt öfters aufgetreten ist, dass der Sensor in eine Richtung keine Gegenstände findet, anhand er den Abstand bestimmen kann. Deshalb wurde in diese Richtung der Abstand 0.0 gesetzt. Da dies kleiner als 0.3 ist, wurde in diesem Fall fälschlicherweise ein Hindernis erkannt. Um dieses Problem zu lösen, wurde die Bedingung zum Erkennen eines Objektes von `Abstand < 0.3` zu `Abstand < 0.3 und Abstand != 0` ergänzt.
Ein Problem, das während der Umsetzung auftrat, war, dass der Sensor des Roboters in der Simulation, die zum Testen verwendet wurde und in der Realität unterschiedliche Werte lieferte. Der konkrete Unterschied war, dass es in der echten Welt öfters aufgetreten ist, dass der Sensor in eine Richtung keine Gegenstände findet, anhand der er den Abstand bestimmen kann. Deshalb wurde in diese Richtung der Abstand 0.0 gesetzt. Da dies kleiner als 0.3 ist, wurde in diesem Fall fälschlicherweise ein Hindernis erkannt. Um dieses Problem zu lösen, wurde die Bedingung zum Erkennen eines Objektes von `Abstand < 0.3` zu `Abstand < 0.3 und Abstand != 0` ergänzt.
Ein weiteres Problem war, dass mit dem obigen Ansatz nur der Abstand direkt nach vorne geprüft wurde. Dies funktionierte zwar, wenn sich ein Hindernis direkt vor dem TurtleBot befand, sobald es sich allerdings etwas zur Seite befand, wurde es nicht mehr erkannt. Um das zu lösen, musste der Abstand in einem Bereich vor dem Roboter überprüft werden. Hierzu wurden die Abstandswerte des Sensors in einem 40-Grad Bereich nach links und rechts getestet. Dadurch wurden nun auch seitliche Objekte erkannt und Kollisionen vermieden.
Mithilfe der "create_subscription()" Methode können Daten aus Topics empfangen werden. Hierzu muss als Erstes der Datentyp der Nachrichten angegeben werden. In dem Obrigen Beispiels ist das "String" aus dem Package "std_msgs.msg". Danach muss der Name des Topics angegeben werden("Topic"). Der nächste Parameter bestimmt nun, wie die Daten verarbeitet werden. Dies funktioniert mithilfe eines Callbacks. Hierzu wird eine Methode als Parameter übergeben, die dann für jeden empfangenen Datensatz aufgerufen wird. Der letzte Parameter gibt außerdem noch die Queue Size an.
Mithilfe der "create_subscription()" Methode können Daten aus Topics empfangen werden. Hierzu muss als Erstes der Datentyp der Nachrichten angegeben werden. In dem Obrigen Beispiels ist das "String" aus dem Package "std_msgs.msg". Danach muss der Name des Topics angegeben werden("topic"). Der nächste Parameter bestimmt nun, wie die Daten verarbeitet werden. Dies funktioniert mithilfe eines Callbacks. Hierzu wird eine Methode als Parameter übergeben, die dann für jeden empfangenen Datensatz aufgerufen wird. Der letzte Parameter gibt außerdem noch die Queue Size an.
### Daten senden
```python
...
...
@@ -75,4 +75,4 @@ class MyNode(Node):
```
Der Verbindungsaufbau, um Daten zu versenden, verläuft fast identisch zum Erstellen des Subscribers. Die einzigen Unterschiede sind, dass einerseits die "create_publisher()" Methode verwendet werden muss. Andererseits ist keine Callback-Methode nötig.
Um nun Daten zu versenden zu können, muss die "publish()" Methode mit dem zu versendenden Datensatz als Parameter aufgerufen werden.
\ No newline at end of file
Um nun Daten zu versenden, muss die "publish()" Methode mit dem zu versendenden Datensatz als Parameter aufgerufen werden.
Bei dieser Aufgabe soll der Turtlebot so programmiert werden, dass er selbstständig durch ein zufällig erstelltes Labyrinth fährt und vor einem Ziel, dass durch eine Rote Wand markiert ist, stehen bleibt.
Bei dieser Aufgabe soll der Turtlebot so programmiert werden, dass er selbstständig durch ein zufällig erstelltes Labyrinth fährt und vor einem Ziel, dass durch eine rote Wand markiert ist, stehen bleibt.
## Umsetzung
Bevor dieses Problem angegangen werden konnte, mussten im Vorfeld zuerst einige kleinere Aufgaben umgesetzt werden. Diese waren:
### Kollisionserkennung
Zur Erkennung von Wänden wird erneut auf den Laser Distance Sensor zurückgegriffen, der die von ihm ermittelten Daten an das Topic 'scan' sendet. Sobald bei einem von diesem veröffentlichten Datensatz der Wert direkt vor dem Bot kleiner als 0.3 ist, wird dies als Kollision erkannt.
### Drehung um 90°
Um im Irrgarten navigieren zu können, musste eine Möglichkeit geschafft werden, um den Bot um 90° zu drehen. Sobald die Drehung initialisiert wird, wird einerseits ein Befehl in das Topic 'cmd_vel' gesendet, um eine potenzielle Vorwärtsbewegung zu stoppen und die Drehbewegung auf 43% des Maximalwertes zu setzen. Andererseits wird hier auch der Zeitstempel des Beginns der Rotation gespeichert.
Um im Irrgarten navigieren zu können, musste eine Möglichkeit geschaffen werden, um den Bot um 90° zu drehen. Sobald die Drehung initialisiert wird, wird einerseits ein Befehl in das Topic 'cmd_vel' gesendet, um eine potenzielle Vorwärtsbewegung zu stoppen und die Drehbewegung auf 43% des Maximalwertes zu setzen. Andererseits wird hier auch der Zeitstempel des Beginns der Rotation gespeichert.
Im Anschluss wird nun für jeden neuen Datensatz, der in das 'scan'-Topic veröffentlicht wird, überprüft, ob seit dem Beginn der Drehung zwei Sekunden vergangen sind. Ist dies der Fall, wird die Rotation durch das Senden der Daten (0.0, 0.0) in das Topic-'cmd_vel' beendet. Der Hintergrund zu dieser Logik ist folgende Rechnung:
`0.43 * 1.83rad/sek * 2 sek = 1.57 rad`
`1.57 rad = 90°`
Dadurch kann sichergestellt werden, dass sich der Bot immer parallel zu den Wänden des Labyrinths bewegt, solange diese ebenfalls nur aus 90° Kurven besteht.
### Erkennung des Ziels
Auch für das Erkennen der Roten Wand, die das Ziel widerspiegelt werden die Werte des Laser Distance Sensors verwendet. In diesem Fall allerdings nicht die gemessenen Abstände, sondern die Intensitäten, die in dem Feld 'intensities' gesetzt werden. Diese Werte ändern sich je nach Farbe des erkannten Objektes. So hat z.B. die Farbe Rot in der Gazebo Simulation den Wert 2.0.
Auch für das Erkennen der Roten Wand, die das Ziel widerspiegelt, werden die Werte des Laser Distance Sensors verwendet. In diesem Fall allerdings nicht die gemessenen Abstände, sondern die Intensitäten, die in dem Feld 'intensities' gesetzt werden. Diese Werte ändern sich je nach Farbe des erkannten Objektes. So hat z.B. die Farbe Rot in der Gazebo Simulation den Wert 2.0.
### Implementierung
Um diese Aufgabe zu lösen, musste ein Weg gefunden werden, durch das Labyrinth zu navigieren. Hierzu wird der Rechte-Hand-Algorithmus verwendet. Dieser beschreibt, dass man den Ausgang eines Irrgartens finden kann, in dem man den Weg immer so folgt, dass die rechte Hand die Mauer berührt und immer Kontakt zu dieser hält. In Fall des Turtlebots wurde dies folgendermaßen umgesetzt: Zu Beginn wird überprüft, ob sich rechts von dem Bot eine Wand befindet, indem getestet wird, ob der Laser Distance Sensor für diese Seite einen Wert liefert, der kleiner als 0.9 ist. Ist dies nicht der Fall, dreht sich der Roboter um 90° nach rechts. Dieser Schritt wird solange wiederholt, bis eine Wand auf der rechten Seite gefunden wird. Sobald dies der Fall ist, soll der TurtleBot einfach in einer geraden Linie vorwärtsfahren. Dies wird solange gemacht, bis eine von zwei Bedingungen auftritt.
- Es wird eine Abzweigung nach rechts gefunden. Dies wird bestimmt, in dem für jeden im "scan"-topic veröffentlichten Datensatz überprüft wird, ob der Abstand auf der rechten Seite größer als eins ist. Wurde eine Abzweigung erkannt, wird der aktuelle Zeitstempel gespeichert. Darauf hin wird nun für jede neue veröffentlichte Nachricht überprüft, ob seit diesem Zeitraum mindestens 0.75 Sekunden vergangen sind. Dadurch wird sichergestellt, dass sich der Bot etwa in der Mitte der Abzweigung befindet. Sobald diese Zeitspanne vergangen ist, dreht sich der Roboter um 90° nach rechts und beginnt erneut die Vorwärtsbewegung.
...
...
@@ -31,5 +31,5 @@ Der Ablauf pro Sensorwert kann als folgendes, etwas abstrahiertes Ablaufdiagramm

## Probleme
Das größte Problem, das während der Implementierung auftrat, entstand bei der exakten Drehung des Roboters um 90°. Da dies anhand einer festen Drehgeschwindigkeit in Kombination mit der vergangenen Zeit durchgeführt wird, mussten diese beiden Werte sehr genau abgestimmt werden, damit es keine Abweichungen vom gewünschten Weg gibt. Allerdings führte die hohe Auslastung des Simulationsrechners immer wieder zu Verzögerungen im Ablauf, sodass die oben berechneten Werte nicht anwendbar waren. Auch durch den Wechsel auf eine weniger stark ausgelastete Simulation konnte keine anhaltende Lösung erreicht werden. Um die Implementierung auf die Gegebenheiten innerhalb der Simulation anzupassen, musste der oben berechnete Werte von `0.43 * 1.83rad/sek * 2 sek`musste per Try and Error auf `0.47 * 1.83rad/sek * 2 sek` verändert werden. Da sich allerdings die Performance der Simulation auch von Durchgang zu Durchgang veränderte, gab es auch bei den Rotationen immer wieder kleinere Abweichungen.
Das größte Problem, das während der Implementierung auftrat, entstand bei der exakten Drehung des Roboters um 90°. Da dies anhand einer festen Drehgeschwindigkeit in Kombination mit der vergangenen Zeit durchgeführt wird, mussten diese beiden Werte sehr genau abgestimmt werden, damit es keine Abweichungen vom gewünschten Weg gibt. Allerdings führte die hohe Auslastung des Simulationsrechners immer wieder zu Verzögerungen im Ablauf, sodass die oben berechneten Werte nicht anwendbar waren. Auch durch den Wechsel auf eine weniger stark ausgelastete Simulation konnte keine anhaltende Lösung erreicht werden. Um die Implementierung auf die Gegebenheiten innerhalb der Simulation anzupassen, musste der oben berechnete Wert von `0.43 * 1.83rad/sek * 2 sek` per Try and Error auf `0.47 * 1.83rad/sek * 2 sek` verändert werden. Da sich allerdings die Performance der Simulation auch von Durchgang zu Durchgang veränderte, gab es auch bei den Rotationen immer wieder kleinere Abweichungen.
Es wurde außerdem alternativ versucht, die Drehung nicht mittels Zeit, sondern anhand des Zustandes der Räder, der in ein eigenes Topic gesendet wird, zu steuern. Allerdings lieferte auch dies nicht den gewünschten Erfolg.