Dieses Repository enthält die Dokumentation in Form einer Sphinx Doku.
## Install `sphinx-book-theme`
## [Repository den Quellcode zur Praxisergänzende 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.
- Das Verzeichnis 'chall4/tb3-ros2-template' befindet sich das Projekt zum Lösen des Labyrinth. Auch hier enthält die Datei 'tb3-ros2-template/tb3.py' den Quellcode.
Create a Python virtual environment to not influence your standard Python development environment:
```sh
virtualenv venv
. ./venv/bin/activate
pip install sphinx-book-theme myst-parser
```
Sphinx uses reStructuredtext as default. Using `myst_parser` we can also use Markdown.
## Cloning the template
First fork this project using the fork button on the top right. Then clone your project.
# Creating html
```sh
cd sphinx-book-template
SPHINXBUILD=~/venv/bin/sphinx-build make html
```
You can view the output using your browser, e.g.:
```sh
xdg-open _build/html
```
# Deployment on World Wide Web (WWW)
## Manually
You can copy the folder `html` on a web server.
If you are using a server which serves the `$HOME/public_html` to the web, then make sure that the web server has access to this folder. In the following example we are using a server called `joan.th-deg.de`:
```sh
ssh joan.th-deg.de
mkdir public_html # This folder will be shared
```
We have to give other users (your group & others) the permission to public_html by giving listing access to your home folder. But other users could now could read the files by guessing their filename, so we have to remove the read permission and execute/enter-directory permission for other users:
```sh
chmod go-rx --recursive ~ # Remove rx from other users from all files/folders
chmod go+x ~ # Add (listing the files is forbidden, because `r` is missing)
# and with the exception of public_html
chmod go+rx public_html
```
If you already have other files and folders in `public_html`, then make them readable by other users:
```sh
# Give others the permission to read all the files
find ~/public_html -type f -execchmod go+r {} +
# Give others the permission to enter and list the directories
find ~/public_html -type d -execchmod go+rx {} +
```
## Automatic
This template includes a continuous integration and deployment (CI/CD) configuration (`.gitlab-ci.yml`) for Gitlab which copies the html files to a server whenever you push a new commit. To use it, you need to have an SSH access to a web server.
Three steps:
1. Setting up the values for the variables in `.gitlab-ci.yml`
1. Modifying `.gitlab-ci.yml` according to your username and the deployment folder on the web server
1. Activating a runner in `Settings - CI/CD - Runners`. A shared runner may be available.
1. Check the status of the runner on the main page of your repository. If successful, you should see the result on the web address, e.g., https://joan.th-deg.de/~gaydos/sphinx-book-template
The explanation for the first steps follows:
### Setting up the values for the variables in `.gitlab-ci.yml`
First store your SSH private key (`$SSH_PRIVATE_KEY`) and the SSH public key of the SSH server (`$SSH_KNOWN_HOSTS`) in the CI/CD variables. You find a tutorial here: [Add a CI/CD variable to a project](https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-project). You do not have to activate `Protected` nor `Masked`.
#### What do I store in `SSH_PRIVATE_KEY`?
You find your private key in `$HOME/.ssh`. You can create new keys using `ssh-keygen`. The public keys have the extension `*.pub`. The extensionless files contain your private keys, e.g., `$HOME/.ssh/id_rsa`:
This is the key that you accept when you first connect to an SSH server. You can find the public keys of the SSH servers that you have connected to under `$HOME/.ssh/known_hosts`. Alternatively use `ssh-keyscan SERVER_ADDRESS`.
For example a SSH public key of `joan.th-deg.de` is:
Finally we have to give an SSH key access to our account. To give access append the public key to `$HOME/.ssh/authorized_keys` on the web server. For example:
-[Sphinx-book theme – Get Started](https://sphinx-book-theme.readthedocs.io).
<arel="license"href="http://creativecommons.org/licenses/by/4.0/"><imgalt="Creative Commons License"style="border-width:0"src="https://i.creativecommons.org/l/by/4.0/88x31.png"/></a><br/>This work is licensed under a <arel="license"href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.
Das Ziel der Challenge "Collision avoidance" war es, dass der Robotor selbstständig Ziele erkennen, davor Stoppen und ihnen Ausweichen kann. Dies sollte nicht nur in der Simunlation, sondern auch in der echten Welt funktionieren.
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
## 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 potentielle Subsriber. Ein solcher wurde hierfür Entwickelt.
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 muss 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 sollen, wohingegen der zweite die Drehbewegung bestimmt. Um für das Stoppen zu Sorgen, müsse daher beide Werte mit 0.0 befüllt werden.
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.
Als nächstes musste das Ausweichen vor dem Hindernis umgesetzt werden. Dafür muss sich der TurtleBot vor diesem wegdrehen. Dafür muss zunächt 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.
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.
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.
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öffentlich 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.
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
### UML
Der Ablauf für jeden empfangenen Sensorwert kann als folgendes Ablaufdiagramm dargestellt werden.
Der Ablauf für jeden empfangenen Sensorwert kann als folgendes Ablaufdiagramm dargestellt werden.


## Probleme
## Probleme
Ein Problem, das während der Umsetzung auftratt 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 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 Obrigen 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öschen, 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.
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.
In diesem Projekt wurden die Komponenten "Nodes" und "Topics" verwendet. Durch vernetzung von diesen, kann der sogenannte ROS2 Graph erstellt werden.
In diesem Projekt wurden die Komponenten "Nodes" und "Topics" verwendet. Durch die Verbindung von diesen kann der sogenannte ROS2 Graph erstellt werden.
## Nodes
## Nodes
Mithilfe von Nodes kann die Software in mehrere Module aufgeteilt werden. Dabei soll sich jede Node um genau eine Aufgabe kümmern. Dabei kann es sich um das Auslesen von Sensorwerten, das Steuern von Motoren oder auch das Navigieren des Roboters anhand der zuvor erwähnten Sensorwerten handeln.
Mithilfe von Nodes kann die Software in mehrere Module aufgeteilt werden. Hierbei soll sich jede Node um genau eine Aufgabe kümmern. Dabei kann es sich um das Auslesen von Sensorwerten, das Steuern von Motoren oder auch das Navigieren des Roboters anhand der zuvor erwähnten Sensorwerten handeln.
In Python kann mithilfe von Vererbung ein solcher Node sehr einfach erstellt werden:
In Python kann mithilfe von Vererbung ein solcher Node sehr einfach erstellt werden:
```python
```python
importrclpy
importrclpy
...
@@ -25,13 +25,13 @@ class MyNode(Node):
...
@@ -25,13 +25,13 @@ class MyNode(Node):
if__name__=='__main__':
if__name__=='__main__':
main()
main()
```
```
Nachdem eine Klasse erstellt wurde, die von der bereits exisitierenden Klasse "Node" erbt, kann auf mehrere Utilmethoden zugegriffen werden. Um die Node in der ROS Umgebung zu registieren muss sie nur initialisiert und anschließend mithilfe der "spin()" Methode bekannt gemacht werden.
Nachdem eine Klasse erstellt wurde, die von der bereits existierenden Klasse "Node" erbt, kann auf mehrere Utilmethoden zugegriffen werden. Um die Node in der ROS Umgebung zu registrieren, muss sie nur initialisiert und anschließend mithilfe der "spin()" Methode bekannt gemacht werden.
## Topics
## Topics
Topics sind für die Kommunikation und den Datenaustausch zwischen einzelnen oder mehreren Nodes zuständig. Dies funktioniert duch ein Publish–subscribe pattern.
Topics sind für die Kommunikation und den Datenaustausch zwischen einzelnen oder mehreren Nodes zuständig. Dies funktioniert durch ein Publish–subscribe pattern.
Ein sogenannter Publisher kann Daten in ein Topic "veröffentlichen".
Ein sogenannter Publisher kann Daten in ein Topic "veröffentlichen".
Subscriber können wiederum ein Topic "abonnieren". Dadurch werden sie über alle neuen Daten, die in das Topic geschrieben (veröffentlicht) werden benachrichtigt. Es ist hier auch möglich, dass ein Subscriber mehrere Topics abonniert und ein Publisher die Daten in mehrere Topics schreibt.
Subscriber können wiederum ein Topic "abonnieren". Dadurch werden sie über alle neuen Daten, die in das Topic geschrieben (veröffentlicht) werden, benachrichtigt. Es ist hier auch möglich, dass ein Subscriber mehrere Topics abonniert und ein Publisher die Daten in mehrere Topics schreibt.
### Daten empfangen
### Daten empfangen
```python
```python
...
@@ -53,7 +53,7 @@ class MyNode(Node):
...
@@ -53,7 +53,7 @@ class MyNode(Node):
print(msg)
print(msg)
```
```
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. Hiezu 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
### Daten senden
```python
```python
...
@@ -74,5 +74,5 @@ class MyNode(Node):
...
@@ -74,5 +74,5 @@ class MyNode(Node):
self.pub.publish(msg)
self.pub.publish(msg)
```
```
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.
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.
Um nun Daten zu versenden zu können, muss die "publish()" Methode mit dem zu versendenden Datensatz als Parameter aufgerufen werden.
Bei dieser Aufgabe soll der Turtlebot so programmiert werden, dass er selbstä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
## Umsetzung
Bevor dieses Problem angegangen werden konnte, mussten im Vorfeld zuerste einige kleinere Aufgaben umgesetzt werden. Diese waren:
Bevor dieses Problem angegangen werden konnte, mussten im Vorfeld zuerst einige kleinere Aufgaben umgesetzt werden. Diese waren:
### Kollisionserkennung
### 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.
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°
### 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 potentielle 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 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.
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:
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`
`0.43 * 1.83rad/sek * 2 sek = 1.57 rad`
`1.57 rad = 90°`
`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.
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
### Erkennung des Ziels
Auch für das Erkennen der Roten Wand, die das Ziel wiederspiegelt 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
### Implementierung
Um dieses 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ärts fahren. Dies wird solange gemacht, bis eine von zwei Bedingungen auftritt.
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.
- 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.
- Es wird eine Wand vor dem Roboter gefunden. Darauf hin wird überprüft, ob der Weg in die linke oder rechte Seite frei ist, indem überprüft wird, ob der entsprechende Abstand, der durch den Laser Distance Sensor ermittelt wird, größer als der Wert eins ist. Falls einer dieser Pfade möglich ist, dreht sich der Bot um 90° in diese Richtung und beginnt erneut gerade aus zu fahren. Falls allerdings weder der Weg links noch der rechts frei ist, sich der Roboter also in einer Sackgasse befindet, wird zufällig bestimmt in welche Richtung er sich drehen soll. Ist diese Rotation abgeschlossen, soll er ebenfalls wieder nach vorne fahren. Dies führt nun dazu, dass sofort wieder eine Wand erkannt wird und die Oben beschriebenen Schritte durchgeführt werden. In diesem Fall wird nun aber sicher ein Weg frei sein, sodass sich der Bot nun drehen kann und so eine 180° Drehung durchführen kann.
- Es wird eine Wand vor dem Roboter gefunden. Darauf hin wird überprüft, ob der Weg in die linke oder rechte Seite frei ist, indem überprüft wird, ob der entsprechende Abstand, der durch den Laser Distance Sensor ermittelt wird, größer als der Wert eins ist. Falls einer dieser Pfade möglich ist, dreht sich der Bot um 90° in diese Richtung und beginnt erneut gerade aus zu fahren. Falls allerdings weder der Weg links noch der rechts frei ist, sich der Roboter also in einer Sackgasse befindet, wird zufällig bestimmt, in welche Richtung er sich drehen soll. Ist diese Rotation abgeschlossen, soll er ebenfalls wieder nach vorne fahren. Dies führt nun dazu, dass sofort wieder eine Wand erkannt wird und die oben beschriebenen Schritte durchgeführt werden. In diesem Fall wird nun aber sicher ein Weg frei sein, sodass sich der Bot nun drehen kann und so eine 180° Drehung durchführen kann.
Um nun das Ziel zu finden, wird jedes mal, wenn eine Wand erkannt wird, überprüft, ob die Wand vor, rechts oder links des Roboters die Farbe Rot, die das Ziel markiert, hat. Wird das direkt vor dem Bot erkannt, wurde das Ziel gefunden und der TurtleBot kann stehen bleiben. Falls das Ziel auf der rechten bzw. linken Seite erkannt wird, wird eine Rotation in die entsprechende Richtung eingeleitet. Ist diese Drehung abgeschlossen, wird erneut getestet, ob die Wand, die sich nun vor dem Bot befindet, das Ziel ist. Diese Bedingung wird nun natürlich erfüllt, sodass der Roboter auch in diesem Fall stehen bleiben kann.
### Beispiel
In folgendem Beispiel ist der Weg, der durch den TurtleBot zurückgelegt wurde, in Rot markiert.

Um nun das Ziel zu finden, wird jedes mal, wenn eine Wand erkannt wird, überprüft ob die Wand vor, rechts oder links des Roboters die Farbe Rot, die das Ziel markiert, hat. Wird das direkt vor dem Bot erkannt, wurde das Ziel gefunden und der TurtleBot kann stehen bleiben. Falls das Ziel auf der rechten, bzw. linken Seite erkannt wird, wird eine Rotation in die entsprechende Richtung eingeleitet. Ist diese Drehung abgeschlossen, wird erneut getestet, ob die Wand, die sich nun vor dem Bot befindet, das Ziel ist. Diese Bedingung wird nun natürlich erfüllt, sodass der Roboter auch in diesem Fall stehen bleiben kann.
### UML
### UML
Der Ablauf pro Sensorwert kann als folgendes, etwas abstrahiertes Ablaufdiagramm dargestellt werden.
Der Ablauf pro Sensorwert kann als folgendes, etwas abstrahiertes Ablaufdiagramm dargestellt werden.


## Probleme
## Probleme
Das größte Problem, das während der Implementierung auftratt, 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 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.
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.
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.