MasterarbeitLaTeX/tex/4_Umsetzung.tex
2023-07-03 01:59:01 +02:00

1024 lines
72 KiB
TeX

\chapter{Umsetzung}
Bei der Umsetzung des geplanten Systemaufbaus waren Anpassungen nötig, die den geplanten Systemaufbau, sowie dessen Komplexität, negativ beeinflussen.
Die Kommunikation zwischen dem Actormodell und dem Behavior Tree musste in mehrere Komponenten aufgeteilt werden, um Konflikte innerhalb der Simulationssoftware zu vermeiden.
Zudem ist die Bewegungsplanung mit MoveIt2 deutlich komplexer als vorerst angenommen.
Diese Komplexität entsteht aus der Interaktion mehrerer Komponenten, die das Gesamtsystem zur Ausführung benötigt.
Alle Einzelsysteme mussten hierfür konfiguriert werden, um die Kommunikation dieser zu ermöglichen.
Mit den genannten Änderungen ergibt sich die in Abbildung \ref{umsetzung_overview} gezeigte Übersicht des Systems.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-Umsetzung-Übersicht}
\centering
\caption{Visualisierung des überarbeiteten Konzepts}
\label{umsetzung_overview}
\end{figure}
\section{Docker-Compose}
Um Docker für die Verwaltung einer ROS-Installation verwenden zu können, müssen einige Anpassungen vorgenommen werden.
Da viele Anwendungen, unter anderem auch die Simulationsumgebung, eine Desktopumgebung benötigen, musste der Zugriff auf eine solche Umgebung berücksichtigt werden.
Diese Probleme können nicht durch Docker allein gelöst werden, da die Virtualisierungsumgebung eine Trennung der Systeme vorsieht.
Die Isolation des Containers von der Benutzeroberfläche des Systems kann durch eine Kombination aus zwei Komponenten aufgehoben werden.
Um diese Modifikationen trotzdem reproduzierbar zu machen, wurde ein Shellscript geschrieben, dass zum Starten des Containers verwendet wird.
Dieses Skript erstellt zuerst die benötigten Verzeichnisse für den Container, falls diese noch nicht existieren.
Danach werden die SSH-Keys des Hosts in den Container kopiert, um eine SSH-Verbindung zu ermöglichen.
Dieser Umweg über SSH ist nötig, da die benötigten Umgebungsvariablen für ROS sonst nicht in allen Fällen gesetzt werden können.
Außerdem werden die benötigten Zugriffe auf den lokalen X-Server durch den Container mittels Hostname erlaubt.
Diese Änderung gestattet es dem Container, Fenster auf dem Desktop anzuzeigen, solange die benötigten SysFS-Dateien hereingereicht werden.
Dies geschieht durch Einträge in der compose.yml-Datei, die diese als ``bind mount'' in den Container hereinreicht.
Um Zugriff auf die Grafikbeschleunigung des Systems zu erhalten, muss deren Repräsentation im SysFS unter \code{/dev/dri} hineingereicht werden.
Der Zugriff auf die Desktopumgebung, der im vorherigen Schritt entsperrt wurde, wird durch das mounten von \code{/tmp/.X11-unix} erreicht.
Dabei handelt es sich um den Unix-Socket des X11 Displayservers.
Zum Starten des Containers wird das Script \code{start.sh} im Verzeichnis der Containerinstallation ausgeführt.
Das Script führt die obengenannten Schritte aus und startet den Container.
Eine Verbindung zum Container ist möglich, nachdem die Meldung \code{ros_1 | Ready to connect.} in der Konsole erscheint.
Dafür wird ein SSH-Client eingesetzt, der eine Verbindung zu der lokalen Netzadresse des Systems mit dem Benutzer \code{ros} aufbaut.
Der Port des SSH-Servers wird dabei durch die Deklaration im docker-compose.yaml an das Hostsystem durchgereicht.
Hierbei ist zu beachten, dass der SSH-Server im Container vom Host aus über Port 2222 erreichbar ist.
Nach der Verbindung wird automatisch die ROS2-Umgebung eingerichtet.
Diese kann ohne weitere Befehle nach dem Verbindungsaufbau genutzt werden.
Um die erstellten Pakete zu kompilieren, wurde das Skript \code{build.sh} (Abbildung \ref{buildscript}) im \code{workspace}-Verzeichnis erstellt.
\begin{figure}
\begin{minted}[breaklines,frame=single]{bash}
#!/bin/bash
pushd "$(dirname "$0")" || exit
colcon build --event-handlers console_cohesion+ --cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja
popd || exit
\end{minted}
\caption{build.sh Shellskript}
\label{buildscript}
\end{figure}
Dieses Skript nutzt \code{colcon}, um alle Pakete in \code{~/workspace}-Verzeichnis zu erstellen.
Dabei wird auch eine \code{compile_commands.json}-Datei im build-Unterordner erstellt, die von Entwicklungsumgebungen zur Syntaxvervollständigung genutzt werden.
Um eine Nutzung in allen Entwicklungsumgebungen zu erlauben, wurde diese zusätzlich in das Hauptverzeichnis des Workspaces gelinkt.
Dies ist notwendig, da einige Entwicklungsumgebungen nur dort nach dieser Datei suchen.
Da der Kompiliervorgang parallel abläuft, erscheinen Informationen zu allen Paketen gleichzeitig, was die Zuordnung von Fehlern erschwert.
Um trotzdem alle wichtigen Informationen zu erhalten, kommt der Event-Handler \code{console_cohesion} zum Einsatz, der die Ausgaben neu formatiert.
Durch diesen werden die Ausgaben gruppiert, und erst nach einen vollständigen Kompiliervorgang eines Pakets nacheinander ausgegeben.
Dies ermöglicht es, aufgetretene Fehler einfacher auf ein bestimmtes Paket zurückführen zu können, ohne das gesamte Log zu durchsuchen.
Hierbei ist es hilfreich, dass die verbleibenden Kompiliervorgänge abgebrochen werden, falls der Kompiliervorgang eines Pakets fehlschlägt.
Dadurch befindet sich der Fehlers immer im letzten Paket der Ausgabe, da alle anderen Prozesse abgebrochen und nicht ausgegeben werden.
\section{Entwicklungsumgebung}
Ein Texteditor ist für das Schreiben von ROS-Packages ausreichend und bietet bei der Arbeit mit Containern sogar einen großen Vorteil.
Das Editieren von Dateien ist mit einem Texteditor auch von außerhalb des Containers möglich.
Jedoch besitzt ein Texteditor nur wenige Funktionen einer vollständigen Entwicklungsumgebung, die den Prozess der Softwareentwicklung beschleunigen.
Um diese Funktionen bieten zu können, analysieren Entwicklungsumgebungen den geschriebenen Code.
Dies geschieht meist auf eine von zwei unterschiedlichen Weisen.
Die Entwicklungsumgebung kann eine interne Repräsentation des geschriebenen Codes generieren.
Diese Repräsentation kann genutzt werden, um die implementierten Funktionen der Entwicklungsumgebung bereitzustellen.
Um dies zu erreichen, muss für jede unterstützte Sprache Code geschrieben werden, der in die Entwicklungsumgebung integriert wird.
Als Alternative existiert das Language Server Protocol, kurz LSP\cite{lsp}, dass eine Schnittstelle in Form eines JSON-RPC Protokolls zur Verfügung stellt, um Infomationen über den Code an die Entwicklungsumgebung zu übergeben.
Dies erlaubt einer Entwicklungsumgebung, nur noch den benötigten Server der Sprache zu starten, um Informationen über den Code in einem standardisierten Protokoll zu erhalten.
Der große Vorteil des LSP ist, dass eine Entwicklungsumgebung alle Funktionen der Programmiersprache vollständig unterstützt, solange das Protokoll vollständig implementiert wurde und ein entsprechender Server für die Sprache existiert.
Bestimmte Funktionen, die beim Design des LSP nicht bedacht wurden, können einfacher in einer interner Implementation umgesetzt werden.
Deswegen besitzen Entwicklungsumgebungen mit interner Coderepräsentation häufig mehr Funktionen, spezialisieren sich jedoch auf bestimmte Sprachen.
In diesem Projekt wurden mehrere Entwicklungsumgebungen mit den jeweils unterschiedlichen Verfahren eingesetzt.
Um diese mit dem Docker-Container verwenden zu können, müssen diese Umgebungen die Entwicklung auf entfernten Systemen unterstützten, da der Container vom ausführenden System getrennt ist.
Um dies zu ermöglichen, wird ein Teil der Entwicklungsumgebung im Container ausgeführt, um mit dem inneren Kontext der Maschine arbeiten zu können.
Dazu wird beim Start einer Verbindung zu einem entfernten System dieser Server auf das Zielsystem übertragen und gestartet.
Die anfängliche Entwicklung wurde mit PyCharm und CLion durchgeführt, da diese durch ihre interne Codeanalyse die ROS-Umgebung ohne Konfiguration nutzen konnten.
Jedoch sind diese Umgebungen sehr ressourcenintensiv, was die gleichzeitige Ausführung erheblich verlangsamt.
Daher wurde später eine Entwicklungsumgebung mit LSP-Unterstützung, in diesem Fall Lapce, verwendet.
Der dafür notwenige LSP-Server wird durch eine Veränderung des Buildscripts automatisch im Container installiert.
Unter Verwendung dieser neuen Server kann Lapce die Codevervollständigung und Codeanalyse wie PyCharm und CLion durchführen.
Gleichzeitig wird der Ressourcenverbrauch gesenkt, da nur ein einziger Editor benötigt wird.
\begin{figure}[ht]
\includegraphics[width=\textwidth]{img/MA-Umsetzung-Lapce}
\centering
\caption{Entwicklungsumgebung Lapce}
\label{lapce}
\end{figure}
\newpage
\section{Verwendete Datentypen}\label{datatypes}
In diesem Projekt werden viele unterschiedliche Datentypen sowohl für den Datenaustauch zwischen Nodes, als auch in der internen Implementation der Nodes verwendet.
Diese Datentypen sind größtenteils aus der Programmiersprache C++ bekannt, jedoch werden auch weitere Typen aus eigenem Code oder eingebundenen Bibliotheken verwendet.
Um die Verständlichkeit der Dokumentation zu erleichtern, sind die in der Implementation verwendeten Datentypen hier zusammengefasst und beschrieben.
\begin{description}
\item[Pose]
ist einer der häufigsten Datentypen im Umgang mit Gazebo.
Der Pose-Datentyp enthält die Information über die Lage und Rotation eines Objekts im Raum.
Die Daten werden als relative Werte im Bezug auf das übergeornete Objekt gespeichert.
Objekte wie der Mensch liegen in der Hierarchie der Simulation direkt unter der Welt, deren Pose sich direkt im Nullpunkt und ohne Rotation befindet.
Durch diesen Umstand sind die Koordinaten des Menschen absolut, da sie nicht durch die Welt verschoben und rotiert werden.
\item[Area]
ist eine Datenstruktur mit einem Vektor an Positionen, die eine Zone im zweidimensionalen Raum definieren.
Jede Position ist eine einfache Datenstruktur aus 2 Gleitkommazahlen, die den X- und Y-Koordinaten der Position entsprechen.
Der Verwendungszweck dieser Struktur ist die einfache Definition von Zonen, die für Positionsgenerierungen und Positionsabfragen genutzt werden können.
\item[ActorPluginState]
definiert 4 Zustände, die das ActorPlugin annehmen kann. Diese 4 Werte sind SETUP, IDLE, MOVEMENT und ANIMATION.
\item[FeedbackMessage]
beschreibt die erste der beiden MessageQueue-Nachrichten, die vom ActorPlugin an den ActorServer gesendet wird.
In dieser Struktur befindet sich der aktuelle Plugin-Zustand als \code{state} Parameter vom Typ ActorPluginState
und außerdem ein \code{progress} Parameter in Form einer Gleitkommazahl, die den Fortschritt der aktuellen Aktion angibt.
Um bei Bewegungen die aktuelle Position des Menschen zu erhalten, ist zusätzlich die aktuelle Pose des Modells im Parameter \code{current} enthalten.
\item[ActionMessage]
ist die zweite Nachricht, die über die zweite MessageQueue vom ActorServer an das ActorPlugin gesendet wird.
Wie in der FeedbackMessage ist ein \code{state} Parameter vom selben Typ enthalten, jedoch dient dieser hier als Vorgabe für den nächsten State.
Ein \code{animationName} Parameter wird als char-Array mit einer maximalen Länge von 255 Zeichen übergeben.
Dieser bestimmt später die Animation, die je nach ActorPluginState während einer Bewegung oder Animation ausgeführt wird.
Der \code{animationSpeed} Parameter beschreibt entweder die Abspielgeschwindigkeit der Animation, oder die zurückgelegte Distanz pro Animationsdurchlauf.
Außerdem wird im Falle einer Bewegung der Parameter \code{target} vom Typ Pose verwendet, um die Endposition und Rotation des Actors zu bestimmen.
\end{description}
\section{Simulationswelt}
Die Definition aller Simulationselemente erfolgt im Paket \code{ign_world}.
In diesem Paket sind sowohl die Geometrien der Welt, aber auch die benötigten Dateien zum Starten der Simulation enthalten.
Diese Dateien enthalten zum Beispiel die Definition des virtuellen Raumes, in dem die Simulation der Mensch-Roboter-Interaktion abläuft.
Für diesen Raum wurde ein Raumplan erstellt, der alle benötigten Bereiche für die Szenarien besitzt (Abbildung \ref{room-plan}).
Zuerst wird ein Stellplatz für den Roboter benötigt.
Dieser sollte an einer Ecke des Raumes positioniert werden, was zu höheren Verfahrgeschwindigkeiten führt.
Das ist durch die dynamische Verfahrgeschwindigkeit bedingt, die bei geringerer Distanz zum Menschen abnimmt.
Des weiteren werden eine Arbeits- und Lagerstätte für den Menschen benötigt, die in den Szenarien genutzt werden sollen.
Im Koexistenzszenario soll der Mensch nur an diesen Stellen seine Arbeit verrichten, sich jedoch seltener dem Roboter nähern, um dessen Fortschritt zu begutachten.
Die Lagerstätte soll im Kooperationsszenario neben der Arbeit des Menschen auch für die fehlerhaften Teile verwendet werden.
Diese Teile werden durch den Roboter aussortiert und durch den Menschen in das Lager verbracht.
Eine Nutzung der Arbeits- und Lagerstätte im Kollaborationsszenario ist nicht geplant, da der Mensch den Roboter überwachen und dessen Fehler korrigieren soll.
Der so geplante Raum wurde in Blender modelliert und als Datei im .stl-Format exportiert, um sie in die Welt einbinden zu können.
Das Resultat des Exports ist in Abbildung \ref{room-finished} dargestellt.
Für das Kooperationsszenario wurde ein Förderband erstellt, das nur in diesem Szenario dem Raum hinzugefügt wird.
Der so erstellte Raum wird in einer .sdf-Datei als Modell referenziert, um diesen später in die Simulation einbinden zu können.
Das Förderband erhält ein eigenes Modell in einer weiteren Datei, die zur Laufzeit in die Simulation geladen wird.
Für beide wird, wie später auch für den Roboter selbst, das Paket \code{ros_gz_sim} verwendet.
Dieses veranlasst mit dem \code{create}-Programm das Erstellen der übergebenen Datenstruktur in Gazebo.
\begin{figure}
\begin{minipage}{.45\textwidth}
\includegraphics[width=\textwidth]{img/MA-Umsetzung-Welt-Plan.drawio}
\centering
\caption{Geplanter Raum}
\label{room-plan}
\end{minipage}
\hspace{.09\textwidth}
\begin{minipage}{.45\textwidth}
\includegraphics[width=\textwidth]{img/MA-Umsetzung-Welt-Blender}
\centering
\caption{Umsetzung in Blender}
\label{room-finished}
\end{minipage}
\end{figure}
\section{Mensch}
Der Mensch soll in der Simulation verschiedene Aufgaben ausführen, die durch Behavior Trees definiert sind.
Um diese Aufgaben visuell darzustellen, werden verschiedene Animationen für ein Modell des Menschen benötigt.
Ein Modell eines Menschen ist bereits in Gazebo integriert, welches als sogenannter ``Actor'' durch vorher definierte Pfade bewegt werden kann.
Die für das Modell existierenden Animationen müssen um die gewünschten Animationen erweitert werden.
Um diese Animationen eines Modells in Bewegungen und Verhalten umzuwandeln, muss das Verhalten während der Simulation gesteuert werden.
Dies erfolgt über das ActorPlugin, welches in Gazebo ausgeführt wird.
Mit diesem Plugin kann der Mensch in der Simulation gesteuert werden.
Da eine Anbindung an ROS in Gazebo nicht möglich ist, kommuniziert das ActorPlugin mit dem ActorServer über MessageQueues.
Dieser ActorServer übersetzt Nachrichten und Anfragen zwischen ROS und der MessageQueue des ActorPlugins.
\subsection{Modellierung}
Um neue Animationen für den Menschen in der Simulation erstellen zu können, muss ein Modell für diesen erstellt werden.
Dafür wurde eine der bereits modellierten Laufanimationen von Gazebo in Blender geöffnet und bearbeitet.
Eine Kopie des Menschenmodells aus der Laufanimation dient als Grundlage für das neue Modell.
Dieses Modell ist durch die interne Geometrie, zum Beispiel zwischen Körper und Pullover der Person, nur schlecht für Animationen geeignet.
Die interne Geometrie bildet ``Falten'' im Modell, die während der Animation hervortreten können, was zu unerwarteten Artefakten führen kann.
Solche Artefakte entstehen durch unterschiedliche Verschiebung der Strukturen, die aus dem inneren des ursprünglichen Modells hervortreten, wenn es bewegt wird.
Beim kopierten Modell kam es häufig zum Hervortreten der Haut unter dem Rollkragen und der Hose.
Diese Artefakte wurden durch die Vereinfachung des Modells an den entsprechenden Stellen behoben.
An diesem Punkt könnte die Animation des Modells mit dem importierten Skelett beginnen, jedoch fehlen diesem viele Knochen, wie zum Beispiel für Finger und Zehen, aber auch Rotationsknochen für Arme und Beine.
Diese fehlenden Knochen werden für einige der gewünschten Animationen benötigt und müssten hinzugefügt werden.
Da das importierte Skelett noch andere Fehler aufwies, wurde es verworfen.
Um ein neues, passendes Skelett zu erstellen, wurde mit dem ``Rigify''\cite{rigify}-Plugin ein standartisiertes Menschenskelett generiert.
Dieses neue Skelett kann an das bereits vorhandene Modell angepasst werden.
Um eine bessere Übersicht zu ermöglichen, sollten zuerst alle nicht benötigten Skeletteile, wie zum Beispiel für Gesichtsanimationen, entfernt werden.
Danach müssen die Knochen durch Verschiebung und Skalierung an die richtigen Positionen im Modell gebracht werden.
Dabei muss auf die Ausrichtung der Knochen zueinander geachtet werden.
Das Kreuzprodukt der Vektoren beider Knochensegmente bestimmt die Richtung der Beugeachse, die sich im Verbindungspunkt beider Knochen befindet.
Ist diese nicht richtig ausgerichtet, zum Beispiel wenn beide Knochen auf einer Gerade liegen, bewegen sich Gelenke bei der Verwendung von inverser Kinematik zur Positionsvorgabe falsch.
Der Grund dafür ist das Kreuzprodukt, dass im oben genannten Fall ein Nullvektor ist, wodurch keine Beugeachse bestimmt werden kann.
Deshalb muss bei der Platzierung darauf geachtet werden, dass der Startpunkt A des ersten und der Endpunkt C des zweiten Knochens auf einer Gerade liegen.
Der Verbindungspunkt B der beiden Knochen wird vorerst auf dieser Gerade platziert.
Dieser muss senkrecht zu dieser Gerade und der gewünschten Biegeachse verschoben werden, wie in Abbildung \ref{bend} gezeigt.
\begin{figure}
\begin{minipage}{.45\textwidth}
\includegraphics[width=\textwidth]{img/MA-Umsetzung-Person-Bones}
\centering
\caption{Knochen des Modells}
\label{person-bones}
\end{minipage}
\hspace{.09\textwidth}
\begin{minipage}{.45\textwidth}
\includegraphics[width=\textwidth]{img/MA-Umsetzung-Person-Armature}
\centering
\caption{Armaturen des Modells}
\label{person-armature}
\end{minipage}
\end{figure}
\begin{figure}
\includegraphics[width=.5\textwidth]{img/MA-bend-axis}
\centering
\caption{Visualisierung der generierten Beugeachse}
\label{bend}
\end{figure}
Das neu erstellte Skelett ist in Abbildung \ref{person-bones} visualisiert.
Um eine bessere Verformung bei der Bewegung von Knochen zu erreichen, wird das so genannte ``weight painting'' eingesetzt.
Hierfür werden für jeden Knochen entweder automatisch oder manuell Teile des Meshes mit Gewichten versehen.
Je höher die Wichtung, desto stärker ist die Verformung an dieser Stelle, wenn der Knochen bewegt oder skaliert wird.
Die so erstellten Knochen werden auch als deformierende Knochen bezeichnet, da deren Bewegung das Modell beeinflusst.
Da das Animieren aller Knochen einzeln sehr zeitaufwändig ist, werden diese in Gruppen zusammengefasst.
Hierfür werden in Blender sogenannte Constraints eingesetzt, die Knochen automatisch durch eingestellte Bedingungen positionieren können.
Durch das Verwenden von Rigify und einem standardisierten Skelett ist die automatische Generierung der Constraints möglich.
Hierfür können die in dem Skelett enthaltenen Informationen vom Plugin genutzt werden, um alle häufig genutzten Constraints automatisch zu generieren.
In diesem Schritt werden auch neue Knochen eingefügt, die das Skelett durch Constraints beeinflussen.
Das neue Animationsmodell, visualisiert in Abbildung \ref{person-armature}, kann jetzt für Animationen genutzt werden.
Hierfür stehen mehrere Knochengruppen zur Verfügung, die typische Animationsmethoden abdecken.
In der Farbe Rot sind im Modell (siehe Abbildung \ref{person-armature}) Knochen markiert, die für inverse Kinematik genutzt werden, in diesem Fall Arme und Beine.
Die Knochen geben die gewünschte Ausrichtung und die Zielposition des untersten Knochens vor.
Aus diesen Angaben wird die wirkliche Position der Knochen berechnet, die durch die Constraints automatisch auf die entsprechenden Knochen übertragen wird.
Orange gefärbte Knochen werden für generelle Einstellungen von Fingern, Handfläche und Zehen genutzt.
Die Handfläche kann so gekrümmt werden, wodurch sich alle Finger mit der Krümmung mitbewegen.
Ein Abknicken aller Zehen kann durch die Rotation des Hilfsknochens an den Zehen erreicht werden.
Die Finger können auch einzeln rotiert und eingeknickt werden.
Hierbei wird der Grad des Einknickens nicht durch eine Rotation des Knochens ausgedrückt, da diese bereits durch die Rotation des Fingers selbst benutzt wird.
Anstatt der Rotation wird die Skalierung des Knochens eingesetzt, wobei ein kleinerer Knochen eine stärkere Knickung aller Fingerglieder bewirkt.
Die gelben Knochen beeinflussen die generelle Pose des Modells.
Der Quader in der Hüfte gibt die gewünschte Höhe des Modells vor, die auch für die inverse Kinematik benutzt wird.
Die anderen Knochen beeinflussen die Rotation des Beckens, der Wirbelsäule, der Schultern und des Kopfes.
Das hier erstellte, verbesserte Rigify-Skelett kann durch den Einsatz der neuen Constraints einfacher animiert werden.
\subsection{Erstellen von Animationen}
Animationen werden in Blender über sogenannte Keyframes erstellt.
Ein Keyframe stellt dabei einen spezifischen Zeitpunkt in der Animation dar.
Jedem Keyframe kann eine beliebige Anzahl von Knochenpositionen zugeordnet werden.
Eine Animation entsteht, sobald mehrere Keyframes auf der Zeitachse existieren.
Alle zugeordneten Positionen werden zwischen beiden Keyframes durch auswählbare Kurven interpoliert.
In Abbildung \ref{actorinterpolate} wird dieser Prozess am Beispiel der ``low_to_standing''-Animation dargestellt.
Für die neuen Animationen werden die erstellten Steuerknochen verwendet, welche die später in Gazebo sichtbaren Knochen über Constraints beeinflussen.
\begin{figure}
\centering
\begin{minipage}{.25\textwidth}
\includegraphics[width=\textwidth]{img/MA-Animation-Human-Start}
\end{minipage}
\begin{minipage}{.25\textwidth}
\includegraphics[width=\textwidth]{img/MA-Animation-Human-Interpolated}
\end{minipage}
\begin{minipage}{.25\textwidth}
\includegraphics[width=\textwidth]{img/MA-Animation-Human-End}
\end{minipage}
\caption{Erste und letzte Keyframes einer Animation mit interpoliertem Zwischenbild}
\label{actorinterpolate}
\end{figure}
\subsection{Export der Modellanimationen}
Die Verwendung des hier erstellten, verbesserten und animierten Rigs ist in vielen Grafikengines, darunter auch Gazebo, noch nicht möglich.
Es bedarf einer zusätzlichen Anpassung des Skeletts, bei der die neu erstellte Knochenstruktur aufgelöst wird, da Gazebo keine Constraints unterstützt.
Um aus einem existierenden, vollständig verbundenen Skelett ein Skelett ohne verbundene Knochen zu extrahieren, exisitiert ein weiteres Plugin mit dem Namen ``Game_Rig_Tools''\cite{gamerig}.
Dieses separiert die neuen Steuerknochen wieder vom ursprünglichen Modell, damit in diesem nur noch die deformierenden Knochen enthalten sind.
Alle erstellten Animationen der Steuerknochen müssen in direkte Bewegungen der deformierenden Knochen des Modells umgewandelt werden.
Um dies zu erreichen wird der in Abbildung \ref{export-prepare} visualisierte Arbeitsablauf verwendet.
Im ersten Schritt wird das zu exportierende Skelett ausgewählt, um diesem später die neue Animation zuweisen zu können.
Dann muss im zweiten Schritt die gewünschte Animation der Liste der bekannten Animationen hinzugefügt werden.
Danach muss die durch die Constraints abgebildete Animation auf das ausgewählte Skelett übertragen werden, was mit dem ``Bake Action Bakery''-Knopf ausgelöst wird.
Dabei wird die Position aller deformierenden Knochen zu jedem Zeitpunkt der Animation bestimmt, und in einer neuen Animation mit modifiziertem Namen abgespeichert.
Diese neu erstellte Animation kann im vierten Schritt dem ausgewählten Skelett zugewiesen werden.
Nach dieser Veränderung kann die Animation im Collada-Format exportiert werden.
Dazu muss das visuelle Modell der Selektion hinzugefügt werden, da Gazebo dieses für jede Animation benötigt.
Jetzt kann der Export über die in Blender integrierte Exportoption ausgelöst werden.
Hierfür müssen die in Abbildung \ref{export-settings} gezeigten Exporteinstellungen verwendet werden, damit Gazebo die exportierte Animation nutzen kann.
Alle in der Blender-Version 3.5 nicht standardmäßigen Einstellungen wurden dabei durch Punkte hervorgehoben.
Zuerst müssen im Reiter ``Main'' die globalen Exporteinstellungen angepasst werden.
Der markierte Punkt 2. bewirkt, dass nur die vorher ausgewählten Modellteile exportiert werden.
Dies verkleinert das Modell, da alle Steuerknochen von Gazebo nicht verwendet werden können, was die Ladezeit der Simulation verbessert.
Die Punkte 3. und 4. bewirken, dass das exportierte Modell die in Gazebo verwendete Vorwärtsachse erhält.
In Blender ist Y als Vorwärtsachse üblich, jedoch verwendet Gazebo die X-Achse für diesen Zweck.
Wird diese Modifikation nicht vorgenommen, sind die Modelle um 90 Grad verdreht.
Zuletzt muss im mit 5 markierten Animations-Reiter noch eine weitere Einstellung vorgenommen werden.
Die mit 6 markierte Einstellung bewirkt, dass alle Bewegungen der exportierten Knochen mit gespeichert werden, auch wenn diese sich nicht bewegen.
Da sich einige Knochen in der Animation in einer konstanten Pose befinden, exportiert Blender diese nicht.
Dies führt zu Fehlern, falls die Knochen von der Standartposition abweichen.
Ein solcher Fehler äußert sich in Gazebo durch verdrehte Knochen während der Animation.
\begin{figure}
\begin{subfigure}[b]{\textwidth}
\includegraphics[width=.5\textwidth]{img/MA-Umsetzung-Animation-Prepare}%
\hfill
\includegraphics[width=.5\textwidth]{img/MA-Umsetzung-Animation-Prepare2}
\end{subfigure}
\caption{Vorbereitung zum Export mit Game_Rig_Tools}
\label{export-prepare}
\end{figure}
\begin{figure}
\begin{subfigure}[b]{\textwidth}
\includegraphics[height=.50\linewidth]{img/MA-Umsetzung-Animation-Save}%
\hfill
\includegraphics[height=.50\linewidth]{img/MA-Umsetzung-Animation-Save2}
\end{subfigure}
\caption{Benötigte Exporteinstellungen in Blender}
\label{export-settings}
\end{figure}
\subsection{Programmierung}
Die von Gazebo verwendete Plugininfrastruktur ist ideal, um schnell neue Funktionen in Gazebo zu entwickeln.
Um dies zu ermöglichen werden beim Start der Simulation zusätzliche Programme in diese eingebunden.
\subsubsection{Message Queue}
Nach der ersten Implementation des ActorPlugins kam es zu Kollisionen mit \code{ros_control}, die beide \code{rclcpp} zur Kommunikation benutzen.
Diese Kollisionen führt zu einem Crash, wenn beide Plugins in der Simulation geladen werden.
Der Crash geschieht, da beide Plugins rclcpp, eine Bibliothek zur Kommunikation mit ROS-Topics, verwenden.
In dieser Bibliothek wird eine globale Instanz angelegt, die den Zustand des Kommunikationsprotokolls abbildet.
Da jedoch von beiden Plugins auf diesen Zustand zugegriffen wird, kommt es zur Problemen, da kein Synchronisationsmechanismus existiert.
Die dadurch entstehenden gleichzeitigen Zugriffe auf die selben Ressourcen führen zur Terminierung des Programms.
Nachdem die Fehlerursache ermittelt war, stellte sich heraus, daß der Konflikt allein durch Modifikation des ActorPlugins nicht aufzulösen ist.
Eine Anpassung beider Plugins auf die gemeinsame Nutzung der Bibliothek ist möglich, erfordert aber potentiell weitreichende Neuimplementationen, die zeitlich im Rahmen dieser Arbeit nicht umsetzbar waren.
Die Nutzung eines separaten Nachrichtendienstes, der keinen globalen Kontext benötigt, ist die sicherste und schnellste Lösung des Problems.
Eine solche Implementation erfordert jedoch zusätzliche Logik, um die beiden Dienste ineinander übersetzen zu können.
Die Auswahl eines Dienstes wurde dabei aus einer Reihe an unterschiedlichen Möglichkeiten getroffen.
Webdienste, wie zum Beispiel REST-API's und Websockets werden von vielen Sprachen nativ untersützt, was diese universell einsetzbar macht.
Eine REST-API hat den Vorteil, dass sie durch fast jede Programmiersprache genutzt werden kann, die Hyper Text Transfer Protocol, auch HTTP genannt, unterstützt.
da diese zur Kommunikation genutzt werden
Das HTTP besitzt jedoch keinen einheitlichen Feedbackmechanismus.
Dieser müsste entweder durch kontinuierliche Abfragen des aktuellen Status, auch ``polling'' genannt, oder eine lang anhaltende Request realisiert werden.
Eine Verwendung von kontinuierlichen Abfragen ist zwar mit fast jeder Programmiersprache möglich, jedoch ist die Reaktionszeit des Systems an die Antwortzeit des Servers gebunden.
Der Einsatz einer lang anhaltenden Request umgeht dieses Problem, jedoch müssen der eingesetzte Webserver und der Client diese Funktion unterstützen.
Das neuere Websocket-Protokoll\cite{websocket} bieten die Möglichkeit, bidirektional Daten zu übertragen.
Dadurch können Aktionsanfragen und Feedback auf dem gleichen Kanal übertragen werden, was das Protokoll übersichtlicher macht.
Beide Technologien basieren auf einem Webserver, der auf einem bestimmten Port des Systems ausgeführt werden muss, was Kollisionen mit anderen Services ermöglicht.
Die Portnummer kann zwar geändert werden, ist jedoch nicht einfach mit einer Komponente assoziierbar, was sie zu einer ``Magischen Zahl'' macht.
Dies sorgt für schlechte Lesbarkeit in einem wichtigen Teil des Kontrollflusses.
Außerdem besitzen beide Technologien durch TCP oder UDP und HTTP relativ großen Protokolloverhead, der bei den hohen Updateraten der Gazebo-Simulation zu Problemen führen könnte.
Eine andere Möglichkeit ist die Nutzung von ``shared memory'', einem geteilten Speicherbereich zwischen beiden Programmen.\cite{shmem}
Dieser kann zur bidirektionalen Kommunikation genutzt werden, da beide Programme auf den gleichen Speicherbereich zugreifen können.
Alle Zugriffe auf den Bereich sind extrem schnell, da sie den Arbeitsspeicher des Systems nie verlassen, was diese Technik ideal zur Datenübertragung zwischen Prozessen macht.
Durch das Erlauben gleichzeitiger Zugriffe kann es vorkommen, dass beide Programme gleichzeitig versuchen, Änderungen am Speicher vorzunehmen.
Diese gleichzeitige Modifikation, auch ``race condition'' genannt, kann zu Fehlern führen, wenn die Zugriffe auf den Bereich nicht kontrolliert werden.
Die letzte betrachtete Methode ist die Verwendung einer Message Queue.
Hier wird im Betriebssystem ein Speicherbereich mit bestimmter Größe für den Datenaustauch reserviert.
Dieser Bereich besitzt ein Identifikationsmerkmal, dass es Anwendungen erlaubt, Zugriff auf diesen zu erlangen.
Ein Programm kann in diesem Bereich Daten ablegen, die durch andere Programme gelesen und geschrieben werden können.
Die Koordinierung der Zugriffe erfolgt dabei durch das Betriebssystem, was gleichzeitige Zugriffe, wie bei shared memory, ausschließt.
Durch diesen Umstand kommt es zu einer Reduktion des Nachrichtendurchsatzes.
Die Angaben zu den Durchsätzen der verschiedenen Datenübertragungsmechanismen wurden mit \code{ipc-bench}\cite{ipcBench} auf dem Entwicklungssystem ermittelt.
Als Nachrichtendienst wurde die MessageQueue ausgewählt, da die integrierten Funktionen die Entwicklung erleichtern und die Geschwindigkeit ausreichend für das Einsatzszenario ist.
Jedoch existieren unter Linux 2 unabhängige Implementationen von MessageQueues mit unterschiedlichen Funktionen.
Die erste Implementation ist die System V MessageQueue\cite{mqSystemV}, und verwendet zur Identifikation einfache Ganzzahlen.
Die neuere Implementation der MessageQueue ist die POSIX-MessageQueue.\cite{mqPosix}
Sie bietet einige weitere Funktionen, wie zum Beispiel asynchrone Benachrichtigungen bei neuen Nachrichten, Quality of Service und nutzt bis zu 256 Zeichen lange Zeichenketten zur Identifikation.
Die ausgewählte Implementation ist die neuere POSIX-Implementation einer Message Queue, da diese basierend auf den Erfahrungen mit der System V Implementation entwickelt wurde.
\subsubsection{ROS-Nachrichten}
Die verwendeten Nachrichten für den ActionServer, als auch für die Message Queue sind in den entsprechenden Paketen \code{ros_actor_action_server_msgs} und \code{ros_actor_message_queue_msgs} organisiert.
Sie sind absichtlich nicht in den nutzenden Paketen untergebracht, da sie durch ein externes Programm in den benötigten Code umgewandelt werden.
Dieser Schritt muss vor dem Kompiliervorgang der nutzenden Pakete geschehen.
Dazu werden diese Nachrichtenpakete als Dependency der nutzenden Pakete angegeben, was eine automatische Anpassung der Kompilationsreihenfolge bewirkt.
Eine Action des \code{ros_action}-Pakets besteht immer aus 3 einzelnen Nachrichten, deren Inhalt frei definiert werden kann.
\begin{itemize}
\item{Zum Start der Action wird die erste Nachricht vom Client an den Server gesendet.
In dieser Startnachricht befinden sich alle Informationen, die zur Ausführung der Action benötigt werden.
Nach dieser Nachricht kann der Server später entscheiden, ob die Aktion ausgeführt werden soll und sie gegebenenfalls abbrechen.}
\item{Nach dem Beginn der Action werden vom Server eine oder mehrere Feedbacknachrichten an den Client gesendet, die den Fortschritt der Action beschreiben.
Dabei ist es die Aufgabe des Programmierers, diese an signifikanten Punkten des Ablaufs zu senden.}
\item{Am Ende der Action wird die Endnachricht an den Client gesendet, die weitere Rückgabewerte liefern kann.
Die Endnachricht enthält standardmäßig eine Erfolgsangabe, weshalb diese nicht angegeben werden muss.}
\end{itemize}
Ein ActionServer innerhalb eines Programmes definiert 3 Funktionen, welche die Handhabung einer Aktion definieren.
\begin{itemize}
\item{
Die erste Funktion übergibt den Wert der Startnachricht, die mit einer Antwort quittiert werden muss.
Hierbei sind die Antworten ACCEPT_AND_DEFER, ACCEPT_AND_EXECUTE und REJECT möglich.
Die erste mögliche Antwort ACCEPT_AND_EXECUTE signalisiert die sofortige Ausführung des gewünschten Befehls.
Als Alternative existiert die Antwort ACCEPT_AND_DEFER, die für eine verspätete Ausführung der gewünschten Aktion steht.
Die REJECT-Antwort weist die Ausführung der Aktion vorzeitig ab.
}
\item{
Die zweite Funktion übergibt Abbruchanfragen an den Server, falls die Aktion durch den Client abgebrochen werden soll.
Auch diese Anfrage kann entweder mit ACCEPT akzeptiert werden, oder mit REJECT zurückgewiesen werden.
}
\item{
Um Feedback während der Ausführung der Aktion geben zu können, exisitiert die dritte Funktion.
Diese erhält als Parameter ein Objekt, mit dem Feedback- und Endnachrichten an den Client gesendet werden können.
}
\end{itemize}
Die gesamte Kommunikation einer Anfrage verläuft wie in Abbildung \ref{plugin_sequence} dargestellt.
Zuerst wird durch den Client eine Zielvorgabe an den Server gesendet.
Sollte der Server diese abweisen, wird die Kommunikation beendet.
Dies geschieht, falls bereits eine Aktion ausgeführt wird.
Falls keine Aktion ausgeführt wird, beginnt die Kommunikation mit dem ActorPlugin.
Das Plugin erhält die Zielinformationen und seinen neuen Status aus der Zielvorgabe des Clients über die MessageQueue.
Der Server wartet den Zustandswechsel des Plugins ab und bestätigt dem Client die Ausführung der Zielanfrage.
Ab diesem Zeitpunkt ist eine Terminierung der Anfrage durch den Client möglich.
Dies geschieht durch eine Abbruchanfrage, welche sofort bearbeitet wird.
Für den Abbruch wird der Status des Plugins über die MessageQueue auf IDLE gesetzt, was alle laufenden Aktionen sofort beendet.
Der Zustandswechsel des Plugins wird abgewartet, um bei erreichen des IDLE-Zustands die Abbruchanfrage zu bestätigen.
Solange die Aktion läuft, werden periodisch Feedbacknachrichten über die MessageQueue an den Server gesendet.
Der Server bringt diese in ein neues Format und leitet sie an den Client weiter.
Wenn das Ziel erreicht wird, wechselt das Plugin eigenständig in den IDLE-Zustand, was vom Server durch eine Feedbacknachricht erkannt wird.
Dieser Zustandswechsel löst die Meldung einer vollständigen Ausführung des gewünschten Befehls an den Client aus.
\begin{figure}
\includegraphics[width=\textwidth]{uml/out/plugin_connectivity.eps}
\caption{Kommunikation des ActorPlugins}
\label{plugin_sequence}
\end{figure}
\subsubsection{ActorPlugin}
Das ActorPlugin steuert den Menschen in der Simulationsumgebung anhand von empfangenen Nachrichten aus der eingehenden Message Queue.
Der Code des Plugins ist dabei im Paket \code{ign_actor_plugin} organisiert, dass im Gazebo-Modell der Welt referenziert werden muss, um das Plugin zu laden.
Das Plugin wird durch den Startvorgang und später von den empfangenen Nachrichten in folgende Zustände versetzt werden:
\begin{description}
\item[Setup]
wird ausschließlich zu Simulationsbeginn verwendet, um alle benötigten Referenzen aus der Simualtionumgebung im Plugin zu hinerlegen, so dass diese in den anderen Zuständen genutzt werden können.
\item[Idle]
definiert den Zustand, der nach erfolgreicher Ausführung eines Befehls angenommen wird.
\item[Movement]
bedeutet die Ausführung einer Bewegung in potentiell mehreren Schritten.
\item[Animation]
entspricht der Ausführung einer Animation an der aktuellen Position des Actors.
Diese kann durch einen Skalierungsfaktor beschleunigt oder verlangsamt werden.
\end{description}
In diesen Zuständen muss das ActorPlugin die Simulationsumgebung beeinflussen, in der die simulierte Person bewegt und animiert wird.
Dies erfordert den Zugriff auf simulationsinterne Daten, die durch das Plugin gelesen und verändert werden sollen.
Der Zugriff auf diese Daten wird durch ein EntityComponentManager-Objekt ermöglicht.
Durch den EntityComponentManager kann auf das so genannte ``Entity Component System'' zugegriffen werden, in dem alle Simulationsobjekte organisiert sind.
Ein Entity Component System besteht aus einer oder mehr Entities, die Objekte innerhalb der Simulation abbilden.
Objekte können beliebig viele Components besitzen, bei denen es sich um zusätzliche Eigenschaften und Erweiterungen der Funktionen des betroffenen Objekts handelt.
Die wichtigsten Komponenten für die Funktion des Plugins sind dabei:
\begin{description}
\item[components::Actor]
Dieses Objekt beschreibt die simulierte Person mit allen weiteren Daten, wie zum Beispiel deren Animationen.
\item[components::AnimationName]
In dieser Komponente wird der aktuell verwendete Animationsname abgelegt, der von Gazebo zur Auswahl der aktuell laufenden Animation verwendet wird.
\item[components::AnimationTime]
enthält den aktuellen Zeitpunkt innerhalb der Animation, um die richtigen Positionsdaten aus dieser auswählen zu können. Dabei wird zwischen einzelnen Positionen linear interpoliert, falls der Zeitpunkt zwischen diesen liegt.
\item[components::Pose]
entspricht der bereits beschriebenen Pose aus Abschnitt \ref{datatypes}, jedoch verwendet Gazebo dafür diesen eigenen Datentyp.
\item[components::TrajectoryPose]
beschreibt eine Pose während einer Bewegung. Ist diese nicht gesetzt, werden keine Animationen abgespielt.
\end{description}
Durch das Verändern dieser Komponenten kann die gewünschte Funktionalität des Plugins erreicht werden.
Diese Veränderungen müssen zu jedem Simulationschritt erneut vorgenommen werden, um einen flüssigen Ablauf zu gewährleisten.
Um dies zu erreichen, können im Plugin bestimmte Interfaces mit entsprechenden Funktionen implementiert werden.\cite{ignPlugin}
Diese Funktionen werden später in der Simulation aufgerufen.
Folgende Funktionen werden von Gazebo unterstützt:
\begin{description}
\item[Configure] wird aufgerufen, sobald das Plugin geladen wird.
Alle benötigten Informationen über dessen Umgebung werden als Parameter übergeben.
Als ersten Parameter wird die Entity übergeben, an welcher das Plugin als Component hinzugefügt wurde.
Außerdem wird eine Referenz auf die Konfiguration des Plugins in der geladenen Welt übergeben.
Für den Zugriff auf die Simulationsumgebung werden zuletzt noch eine aktuelle Instanz des EntityComponentManagers und EventManagers von Gazebo übergeben.
\item[PreUpdate] wird verwendet, um den nächsten Update-Schritt vorzubereiten.
Diese Funktion erlaubt die Modifikation von Entities und Components und wird vor der eigentlichen Physiksimulation aufgerufen.
\item[Update] wird genutzt, um die in der Simulation abgelaufene Zeit zu simulieren.
Auch hier ist die Modifikation von Entities und Components möglich.
In dieser Funktion wird beispielsweise die Physiksimulation der Objekte durchgeführt.
\item[PostUpdate] bietet die Möglichkeit, die Ergebnisse der Simulation vor dem nächsten Simulationsschritt auszulesen.
Dies wird zum Beispiel für die simulierten Sensoren in Gazebo genutzt, die hier ihren Status prüfen.
In dieser Funktion kann nur lesend auf Entities und Components zugegriffen werden.
\end{description}
Jede dieser Funktionen ist in einem Interface mit dem Präfix \code{ISystem} definiert, die vom Plugin genutzt werden können.
Für Gazebo muss das Plugin noch registriert werden, was mit dem \code{IGNITION_ADD_PLUGIN}-Makro geschieht.
Mit diesen Vorbereitungsschritten wird das Plugin von Gazebo verwendbar gemacht.
Das ActorPlugin benötigt für seine Funktion das übergeornete Simulationsobjekt, dass durch das Plugin beeinflusst werden soll.
Dies erfordert die Implementation des \code{ISystemConfigure}-Interfaces für das ActorPlugin.
Außerdem soll die simulierte Person bewegt werden. Da dieser Vorgang während der Simulationszeit ablaufen soll, muss hierfür das \code{ISystemUpdate}-Interface genutzt werden.
Als erstes wird durch das Laden des Plugins die Configure-Funktion aufgerufen.
Da das Plugin erst durch Gazebo geladen werden muss, kann davon ausgegangen werden, dass alle benötigten Message Queues bereits durch den ActorServer erstellt wurden.
Trotz dieses Umstandes wartet das Plugin auf deren Erstellung, um bei Konfigurationsfehlern auffälliges Verhalten zu zeigen.
Im Log wird diese Aktion vermerkt, um das Debugging zu erleichtern.
Nach dem erfolgreichen Aufbau der Verbindung wird ein Thread gestartet, der die eingehenden Nachrichten verarbeitet.
Dabei wird zuerst ein Sperre gesetzt, um parallele Zugriffe durch die Update-Funktion zu verhindern.
Alle in der Anfrage gespeicherten Daten werden übernommen und der gewünschte State gesetzt.
Nach dem Entfernen der Sperre wird im nächsten Simulationschritt die gewünschte Aktion durchgeführt.
Das Setzen aller obrigen Zustände ist dabei möglich, jedoch werden nur Idle, Animation und Movement genutzt.
Der Idle-Zustand kann zum Beenden eines anderen Zustands eingesetzt werden, falls dieser abgebrochen werden soll.
Die Zustände Animation und Movement werden gesendet, falls neue Ziele gesetzt werden sollen.
Da beide Zustände Animationen verwenden, wird bei deren erster Ausführung in der Update-Funktion die gewünschte Animation gesetzt.
Um dies zu ermöglichen, werden alle in der Actor-Komponente gespeicherten Animationen durchsucht.
Sollte die Animation nicht gefunden werden, wird versucht, diese anhand ihres Namens zu laden.
Wurde eine Animation gefunden, wird deren Länge gespeichert, um diese später für das Feedback nutzen zu können.
Ab diesem Zeitpunkt unterscheiden sich die Implementation von Animation und Movement.
Im Zustand der Animation wird die Komponente AnimationTime bei jedem Update aktualisiert.
Um die Ausführungsgeschwindigkeit einer Animation anpassen zu können, wird ein Skalierungsfaktor genutzt.
Dieser optionale Faktor kann mit der abgelaufenen Zeit multipliziert werden.
Dies verursacht eine Beschleunigung oder Verlangsamung der Ausführung.
Nach jedem Update-Schritt wird eine Feedback-Nachricht gesendet, die den aktuellen Fortschritt der Animation enthält.
Wurde die Animation vollständig durchlaufen, wird der Zustand des ActorPlugins auf Idle gesetzt.
\begin{figure}
\includegraphics[width=\textwidth]{uml/out/plugin_states.eps}
\caption{Zustandsübergänge im ActorPlugin}
\label{plugin_states}
\end{figure}
Alle Zustandsübergänge sind in Abbildung \ref{plugin_states} zusammengefasst.
Soll über den Movement-Zustand eine Bewegung abgespielt werden, müssen noch weitere Parameter der Bewegung berechnet werden.
Dies geschieht nach dem Setzen der Animation während des ersten Update-Aufrufs.
Zuerst wird ein Vektor zur Zielposition berechnet, der später zur Bewegung genutzt wird.
Sollte dieser über einer gewissen Mindestlänge liegen, wird eine Rotation des Menschen in Bewegungsrichtung ausgeführt.
Nach dieser Rotation wird die Bewegung an die Zielposition umgesetzt.
Dabei wird die Bewegungsanimation abgespielt, die durch einen Faktor an die zurückgelegte Distanz angepasst wird.
Dies vermeidet, dass die Beine der Person über den Boden gleiten.
Wurde eine Endrotation angegeben, die nicht dem Nullvektor entspricht, wird nach der Bewegung noch eine Rotation in diese Richtung ausgeführt.
Dabei werden auch die Beine wieder in die Ausgangslage bewegt.
Wenn sowohl die Zielrotation ausgeführt wurde und die Beine wieder in Ausgangslage sind, wird der Zustand auf Idle gesetzt.
Das ActorPlugin besitzt kein Konzept eines ROS-ActionServers und verlässt sich auf den ActorServer, der die Feedbacknachrichten in das richtige Format bringt.
Um Zustandsübergänge erkennen zu können, werden diese als Feedback über die zweite Message Queue an den ActorServer gesendet.
Weitere Feedbacknachrichten werden in den Zuständen Movement und Animation zu jedem Simulationsschritt gesendet.
In diesen zusätzlichen Nachrichten werden die aktuelle Position des Menschen und der Fortschritt der laufenden Aktion aktualisiert.
Der Fortschritt einer Aktion wird aus der Animationszeit oder der berechneten Bewegungszeit und der bereits vergangenen Simulationszeit berechnet.
\subsubsection{ActorServer}
Der ActorServer ist die Brücke zwischen ROS und dem ActorPlugin.
Er ist als das Programm \code{ros_actor_action_server} im gleichnamigen Paket enthalten.
Dieser weitere Dienst bindet das ActorPlugin an ROS an.
Nach dem Start des ActorServers werden zwei ROS-ActionServer gestartet.
Diese können jeweils zum Abspielen von Animationen oder zum Starten von Bewegungen des simulierten Menschen genutzt werden.
Wenn ein Client eine dieser Aktionen startet, überträgt er die Zieldaten an den entsprechenden ActionServer.
Beide ActionServer prüfen nach dem Empfang eines neuen Ziels als erstes, ob bereits eine andere Aktion ausgeführt wird.
Wird bereits eine andere Aktion ausgeführt, kommt es zur Ablehnung der aktuellen Anfrage.
Im anderen Fall wird die Anfrage akzeptiert und in das MessageQueue-Format übersetzt und an das ActorPlugin gesandt.
Um das Starten mehrerer gleichzeitiger Aktionen zu unterbinden, muss der Empfang einer neuen Anfrage bestätigt werden, bevor weitere Befehle über den ROS-ActionServer entgegen genommen werden können.
Hierzu wird ein Mutex verwendet, der die Auswertung neuer Nachrichten verhindert, so lange der aktuelle Befehl noch nicht durch das Plugin bestätigt wurde.
Parallel werden alle eingehenden Feedback-Nachrichten der Message Queue des ActorPlugins in Feedback für die aktuell laufende Action umgewandelt.
Im Falle des Bewegungs-ActionServers werden mehrere Parameter benötigt.
Zuerst werden Animationsname und -diztanz benötigt, um die richtige Animation auszuwählen und die Bewegung mit der Animation zu synchronisieren.
Als Feedbacknachricht erhält der Client die aktuelle Pose des Actors im Simulationsraum.
Soll eine Animation über den Action Server abgespielt werden, wird neben dem Animationsnamen noch die Animationsgeschwindigkeit als Parameter benötigt.
Die Feedbacknachricht enthält den Fortschritt der Animation als Gleitkommazahl von null bis eins.
\section{Roboter}
Der Roboter besteht aus mehreren interagierenden Systemen, die in ihrer Gesamtheit das vollständige Robotermodell in der Simulation verwendbar machen.
Zuerst muss ein Modell des Roboters erstellt werden, dass in Gazebo geladen werden kann.
Dieses Modell muss dann für die Bewegungsplanung mit MoveIt erweitert werden.
Hierbei werden Controller von ros_control mit dem Modell verbunden, um den aktuellen Zustand der Achsen zu überwachen und diese steuern zu können.
Um diese Vielfalt an Daten standardisiert für andere Nodes in ROS weitergeben zu können, wird eine MoveGroup in ROS gestartet, welche die Planung von Bewegungen durch andere Programme zulässt.
\subsection{Modellierung}
Für den Kuka LBR iisy ist kein Simulationsmodell für Gazebo und ROS verfügbar.
Daher wurde das für die Simulation benötigte Modell aus einer .stl Datei des Herstellers generiert.
Diese Datei enthält eine geometrische Beschreibung des Roboters, zu sehen in Abbildung \ref{robot_raw}, welche nicht direkt im Simulator nutzbar ist.
Aus dieser Datei wurden mit FreeCAD\cite{freecad} alle Glieder des Roboters als separate Collada-Dateien exportiert.
Dabei muss darauf geachtet werden, dass die exportierten Daten eine ausreichende Meshgröße haben.
Diese kann vor dem Export in FreeCAD eingestellt werden.
Durch diese Einstellung bleiben feine Strukturen erhalten, die später in Blender wieder reduziert werden können.
Ein solches Vorgehen erlaubt es Blender, bessere Entscheidungen über die Reduktion von Strukturen zu treffen.
Das Resultat ist ein exakteres Endmodell des Roboters.
\begin{figure}
\includegraphics[width=\textwidth/2]{img/MA-Roboter-Rohdaten}
\centering
\caption{Rohdaten aus .stl-Datei}
\label{robot_raw}
\end{figure}
Diese Dateien können dann in Blender bearbeitet werden, um sie für die Simulation tauglich zu machen.
Hierfür wurde die hohe Auflösung der Modelle reduziert, was sich positiv auf die Dateigröße, die Startzeit der Simulation und die Rendergeschwindigkeit auswirkt.
Außerdem wurden die Glieder so ausgerichtet, dass sich der Verbindungspunkt zum vorherigen Glied im Nullpunkt des Koordinatensystems befindet.
Das vollständige visuelle Modell ist in Abbildung \ref{robot_visual} zu sehen.
Um die Simulation weiter zu beschleunigen, wurden die Kollisionsboxen des Arms noch weiter vereinfacht, was die Kollisionsüberprüfung deutlich beschleunigt.
Dabei werden stark simplifizierte Formen verwendet, die das hochqualitative visuelle Modell mit einfachen Formen umfassen.
Das resultierende Modell, in Abbildung \ref{robot_collision} dargestellt, wird später zur Kollisionserkennung verwendet.
Diese Herangehensweise ist nötig, da die Kollisionserkennung auf der CPU durchgeführt und durch komplexe Formen stark verlangsamt wird.
Um aus den generierten Gliedermodellen ein komplettes Robotermodell erstellen zu können, wurde eine .urdf-Datei erstellt.
In dieser werden die verwendeten Gelenktypen zwischen den einzelnen Gliedern, aber auch deren Masse, maximale Geschwindigkeit, maximale Motorkraft, Reibung und Dämpfung hinterlegt.
Diese Daten können später zur Simulation der Motoren genutzt werden, die den Arm bewegen.
Die Gelenkpositionen sind dabei relative Angaben, die sich auf das Glied beziehen, an dem ein weiteres Glied über das Gelenk verbunden werden soll.
Alle kontrollierbaren Gelenke benötigen auch eine Gelenkachse, die je nach Gelenktyp die mögliche Beweglichkeit bestimmt.
Alle hier erstellten Dateien wurden im Paket \code{iisy_config} zusammengefasst, um diese einfacher wiederauffinden zu können.
\begin{figure}
\begin{minipage}{.49\textwidth}
\includegraphics[width=\textwidth]{img/MA-Roboter-Visuell}
\centering
\caption{Visuelles Modell}
\label{robot_visual}
\end{minipage}
\begin{minipage}{.49\textwidth}
\includegraphics[width=\textwidth]{img/MA-Roboter-Kollision}
\centering
\caption{Kollisionsmodell}
\label{robot_collision}
\end{minipage}
\end{figure}
\subsection{MoveIt 2 Konfiguration}
Das im Abschnitt Modellierung erstellte Paket mit dem Roboter-Modell kann mit dem neu implementierten MoveIt Configurator um die benötigten Kontrollstrukturen erweitert werden.
Dazu wurde der Setupassistent von MoveIt2 verwendet, der das Modell für MoveIt anpasst.
Dabei wird das Modell mit weiteren Parametern versehen, die durch MoveIt genutzt werden.
Die Erstellung des erweiterten Modells mit dem Assistenten funktionierte komplett fehlerfrei, jedoch ließen sich die generierten Dateien nicht direkt nutzen.
Um die generierten Dateien nutzen zu können, mussten diese weiter angepasst werden.
Dies beinhaltete die korrekte Integration der Roboterdefinitionen im Paket, aber auch zahlreiche Pfadreferenzen auf verwendete Dateien, die nicht korrekt generiert wurden.
Diese können standardmäßig nicht in Gazebo, RViz und MoveGroup gleichzeitig geladen werden, da diese Programme unterschiedliche Pfadangaben verlangen, was die Nutzung verhindert.
Eine Möglichkeit zur Lösung des Problems ist die Verwendung von \code{file://}-URIs, die von allen dieser Programme gelesen werden können.
Der Moveit Setup Assistent kann diese URIs nicht lesen, was zu Fehlern bei einer Rekonfiguration führen kann.
Falls eine Neukonfiguration des Roboters mit dem Assistenten durchgeführt werden soll, muss diese Umwandlung temporär rückgängig gemacht werden.
Das so erstellte Modell kann über den Aufruf von \code{ros2 launch iisy_config demo.launch.py} in RViz getestet werden.
Hierfür erscheint das Robotermodell mit mehreren Markern und Planungsoptionen, die genutzt werden können, um beliebige Bewegungen zu planen und auszuführen.
\subsection{Integration mit Gazebo}
Das so erweiterte Modell kann zur Laufzeit in Gazebo geladen werden.
Dafür wird das Paket \code{ros_gz_sim} verwendet, dass das \code{create}-Programm beinhaltet.
Mit diesem Werkzeug wird ein Modell unter einem bestimmten Namen anhand einer Datei oder eines übergebenen Strings in Gazebo importiert.
Das Modell kann dabei über Argumente im Raum verschoben und rotiert werden, falls diese Funktionalität benötigt wird.
In diesem Fall wird das Modell als String geladen, der durch \code{xacro} erstellt wurde.
Dies ist nötig, um Informationen aus anderen Dateien in das generierte XML übernehmen zu können.
Im Modell sind auch die verwendeten Gazebo-Plugins deklariert, die für die Integration mit \code{ros_control} verantwortlich sind.
Das Gazebo-Plugin lädt dabei die verwendeten Controller und versorgt diese mit Informationen über den Roboter.
\section{Behavior Trees}
Alle Behavior Trees wurden im Paket \code{btree} organisiert, das die Bäume im XML-Format und die Implementation der Nodes und umliegenden Infrastruktur in C++ enthält.
Für die Umsetzung des Szenarios wurden neue Nodes für den BehaviorTree erstellt.
Diese lassen sich nach Nutzung in verschiedene Gruppen einordnen.
\subsubsection{Allgemein nutzbare Nodes}
\begin{description}
\item[GenerateXYPose]
generiert eine Pose in einem durch den \code{area} Parameter angegebenen Bereich.
Um dies zu ermöglichen, wird zuerst die Fläche aller Dreiecke berechnet, die den Bereich definieren.
Diese werden durch den Gesamtinhalt geteilt, um eine Wichtung der Dreiecke zum Gesamtinhalt zu erreichen.
Darauf wird eine Zufallszahl zwischen 0 und 1 gebildet.
Von dieser werden die Wichtungen der Dreiecke abgezogen, bis diese Zufallszahl im nächsten abzuziehenden Dreieck liegt.
Als letzter Schritt wird in diesem Dreieck eine zufällige Position ermittelt, die über den Ausgabeparameter \code{pose} ausgegeben wird.
\item[InAreaTest]
prüft, ob eine Pose, vorgegeben durch den \code{pose} Parameter, in einer durch den \code{area} Parameter definierten Zone liegt.
Hierfür wird überprüft, ob die X und Y-Werte der Pose in einem der Dreiecke liegen, welche die Area definieren.
Der Rückgabewert ist das Ergebnis dieser Überprüfung, wobei SUCCESS bedeutet, dass sich die Pose in der Area befindet.
\item[OffsetPose]
wird genuzt, um eine Pose im Raum zu bewegen und/oder deren Orientierung zu verändern.
Falls der \code{offset} Parameter gesetzt ist, wird dieser mit dem \code{input} Parameter summiert.
Außerdem wird die Orientierung der Pose auf den \code{orientation} Parameter gesetzt, falls dieser vorhanden ist, was den ursprünglichen Wert überschreibt.
\item[InterruptableSequence]
stellt eine Sequence dar, die auch nach ihrem Abbruch ihre Position behält.
Dies ist notwendig, wenn ein bestimmtes Verhalten unterbrechbar ist, aber zu einem späteren Zeitpunkt fortgesetzt werden soll.
Hierzu wird der Iterator der unterlegenden Sequenz nur erhöht, wenn die untergeordnete Node SUCCESS zurückmeldet.
Außerdem wird der Iterator nicht zurückgesetzt, wenn die Ausführung dieser modifizierten Sequenz abgebrochen wird.
\item[WeightedRandom]
ist eine Steuerungsnode, die mehrere untergeordnete Nodes besitzt.
Dabei werden diese nicht, wie bei anderen Steuerungsnodes üblich, sequentiell ausgeführt.
Anhand einer vorgegebenen Wichtung im \code{weights} Parameter wird eine der untergeordneten Nodes zufällig ausgewählt.
Diese Node wird als einzige Node ausgeführt, bis diese den SUCCESS-Status zurück gibt.
Nach dem dieser Status erreicht wurde, wird bei dem nächsten Durchlauf eine neue Node ausgewählt.
Der Rückgabewert ist der Rückgabewert der ausgewählten untergeordneten Node.
\item[IsCalled]
fragt den aktuellen Called-Status des Actors ab, der in einigen Szenarien vom Roboter verwendet wird, um den simulierten Menschen zu rufen.
Der Rückgabewert der Node ist dabei SUCCESS, falls der Mensch gerufen wird, oder FAILURE, wenn kein RUF durchgeführt wird.
\item[SetCalledTo]
setzt den aktuellen Called-Status auf den Wert des übergebenen \code{state} Parameters.
Da diese Aktion nicht fehlschlagen kann, liefert diese Node immer SUCCESS als Rückgabewert.
\item[RandomFailure]
generiert eine Zufallszahl von 0 bis 1, die mit dem \code{failure_chance} Parameter verglichen wird.
Der Rückgabewert ist das Ergebnis des Vergleichs, FAILURE, wenn die Zufallszahl kleiner als der \code{failure_chance} Parameter ist, oder im anderen Falle SUCCESS.
\end{description}
\subsubsection{Menschenspezifische Nodes}
\begin{description}
\item[ActorAnimation]
wird verwendet, um dem simulierten Menschen eine auszuführende Animation zu senden.
Die Node benötigt zur Ausführung einen Animationsname, der im \code{animation_name} Parameter angegeben wird.
Ein optionaler \code{animation_speed} Parameter gibt die Ausführungsgeschwindigkeit vor.
Der Rückgabewert ist SUCCESS, wenn die komplette Ausführung gelang, oder FAILURE, falls diese abgebrochen oder abgelehnt wurde.
\item[ActorMovement]
funktioniert wie eine ActorAnimation, sendet jedoch eine Bewegungsanfrage.
Auch für diese wird ein Animationsname benötigt, der im \code{animation_name} Parameter definiert wird.
Jedoch wird für die Synchronisation zur Bewegung ein Disztanzwert benötigt, der in einem Animationsdurchlauf zurückgelegt wird.
Dieser wird im \code{animation_distance} Parameter übergeben.
Eine Zielpose wird im \code{target} Parameter gesetzt.
Eine Besonderheit dieses Paramerters ist die Verwendung des Null-Quaternions als Richtungsangabe, was die Endrotation auf die Laufrichtung setzt.
\end{description}
\subsubsection{Roboterspezifische Nodes}
\begin{description}
\item[RobotMove] gibt dem Roboter eine neue Zielposition über den \code{target} Parameter vor.
Bei dieser Node handelt es sich um eine asynchrone Node, die nur bei der erfolgreichen Ausführung der Bewegung mit dem SUCCESS-Status beendet wird.
\item[SetRobotVelocity] setzt eine neue maximale Geschwindigkeit des Roboters, vorgegeben durch den \code{velocity} Parameter.
Der Rückgabewert ist immer SUCCESS.
\end{description}
Diese beiden roboterspezifischen Nodes beeinflussen im Hintergrund das gleiche System, da Bewegungen bei Geschwindigkeitsänderungen neu geplant und ausgeführt werden müssen.
Dazu wird das letzte Ziel bis zu dessen Erreichen vorgehalten, um die Ausführung neu zu starten, falls eine Geschwindigkeitsänderung durchgeführt werden muss.
Um die RobotMove-Node nicht zu unterbrechen, wird diese nur über einen Callback über den Erfolg oder Misserfolg der gesamten Bewegung und nicht über den Erfolg einzelner Teilbewegungen informiert.
\subsection{Subtrees}
Um die Wiederverwendung von bestimmten Abläufen zu erleichtern, wurden diese in Subtrees ausgelagert.
In diesem Fall wurden die Aktionen ``Arbeiten an der Werkbank'' und ``Ablegen eines Objekts im Lagerregal'' des Menschen ausgelagert, da diese in mehreren Fällen verwendet werden.
Diese Abstraktion erhöht die Lesbarkeit der Bäume und vereinfacht die Verwendung gleicher Aktionen in mehreren Bäumen.
Für das Arbeiten an der Werkbank ist eine Bewegung des Menschen hin zu dieser erforderlich.
Außerdem muss sich der Mensch zur Werkbank ausrichten, um die späteren Animationen korrekt auszuführen.
Dazu wird eine Bewegung zu einem Punkt vor der Werkbank mit vorgegebener Zielrichtung genutzt.
Nach dieser Bewegung soll ein Arbeitsprozess simuliert werden.
Da dieser Prozess weit vom Roboter entfernt ist, ist eine genaue Animation nicht erforderlich.
In diesem Fall wird die Arbeit durch ausstrecken und zurücknehmen der Hand in Richtung der Werkbank simuliert.
Dies wird durch zwei Animations-Nodes mit entsprechenden Parametern gesteuert.
Die Ausführung der Nodes dieser Gesamtaktion soll sequenziell erfolgen, weshalb die Nodes unterhalb einer Sequence-Node gruppiert werden.
Der vollständige resultierende Baum ist in Abbildung \ref{subtree_work} visualisiert.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-subtree-work}
\centering
\caption{BehaviorTree für das Arbeiten an der Werkbank}
\label{subtree_work}
\end{figure}
Das Ablegen von Objekten in einem Schrank ist ein komplizierterer Prozess, da der Schrank in mehreren Ebenen befüllt werden kann.
Außerdem sind mehrere Fächer verfügbar, die genutzt werden können.
Wie bereits bei der Werkbank wird zuerst eine Bewegung hin zum Schrank ausgeführt.
Diese hat eines der drei im Schrank vorhandenen Schrankteile zum Ziel.
Die Auswahl des Ziels erfolgt zufällig durch eine WeightedRandom-Node, die eine der drei Bewegungs-Nodes ausführt.
Danach erfolgt eine weitere zufällige Auswahl die entscheidet, ob das obere oder das untere Fach des betreffenden Schranks genutzt werden soll.
Im Falle eines oberen Faches werden nur zwei Animationen benötigt.
Die Hand wird zu einem oberen Fach ausgestreckt und zurückgezogen.
Für den Fall eines unteren Fachs werden vier Animationen genutzt.
Zuerst kniet die Person auf den Boden nieder, um das untere Fach erreichen zu können.
Danach wird ein Objekt durch Inspizieren und Ablegen in das Fach gelegt.
Jetzt kann die Person wieder aufstehen, was die Person in die gleiche Position wie am Anfang der Aktion bringt.
Für die beiden Fälle werden auch Sequenz-Nodes genutzt, da die Aktionen nacheinander ausgeführt werden sollen.
Die beiden zufälligen Auswahlverfahren werden durch eine Sequenz verbunden.
Der resultierende Ablauf wurde als Baum in Abbildung \ref{subtree_deposit} visualisiert.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-subtree-deposit}
\centering
\caption{BehaviorTree für das Ablegen von Objekten im Schrank}
\label{subtree_deposit}
\end{figure}
\subsection{Verhalten des Roboters}
Das grundlegende Verhalten des Roboters ist in jedem Anwendungsfall gleich.
Dies hängt mit der automatischen Geschwindigkeitsanpassung zusammen.
Der Roboter soll bei annäherndem Mensch zuerst seine Geschwindigkeit reduzieren, wenn dieser eine Warnzone betritt.
Falls sich der Mensch weiter bis in die Sicherheitszone begibt, ist ein temporärer Stopp des Roboters vorgesehen.
Dieser Stopp soll nach dem Entfernen des Menschen aus der Sicherheitszone wieder aufgehoben werden, um die Arbeit fortzusetzen.
Um diese Funktionen zu erlauben, ist eine Reaktion auf Statusänderungen von vorherigen Nodes nötig.
Dies wird durch eine übergeordnete ReactiveSequence erreicht.
Eine solche Node verhält sich ähnlich einer normalen Sequence, jedoch prüft sie zu jedem Ausführungsschritt noch einmal alle vorherigen Nodes.
Dies geschieht auch, wenn eine andauernde Aktion sich noch im Status RUNNING befindet.
Wenn eine vorherige Node einen Fehler zurückgibt, wird die aktuell laufende spätere Node abgebrochen.
Als erste Node in der ReactiveSequence wird getestet, ob sich der Mensch in der Sicherheitszone aufhält.
Wenn dies nicht der Fall ist, kann die Ausführung weiter fortgesetzt werden.
Dies wird durch eine über dem Test angeordnete Inverter-Node erreicht.
Nach diesem Schritt kann die Geschwindigkeitsanpassung vorgenommen werden, falls diese nötig ist.
Dies wird durch eine If-Then-Else Node erreicht.
Als Bedingung wird ein Test auf den Aufenthalt des Menschen in der Warnzone verwendet.
Wenn der Mensch sich in dieser befindet, wird die Geschwingigkeit auf 10\% gesetzt.
Sonst wird die Geschwindigkeit wieder auf 100\% erhöht.
Nach dieser Sequenz kann der spezifische Teil für die gewünschte Applikation hinzugefügt werden.
In Abbildung \ref{tree_base_robot} wurde dies durch eine Wolke repräsentiert.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-tree-base-robot}
\centering
\caption{Grundlegender BehaviorTree des Roboters}
\label{tree_base_robot}
\end{figure}
\subsubsection{Koexistenz}
Im ersten Szenario, der Koexistenz zwischen Mensch und Roboter, soll eine Arbeitsaufgabe durch den Roboter allein simuliert werden.
Hierfür soll der Roboter Objekte von der rechten auf die linke Tischseite transportieren.
Alle Nodes werden einer ReactiveSequence untergeordnet, um bei Unterbrechungen durch den Mensch den Fortschritt der Aktion behalten zu können.
Mit einer GenerateXYPose-Node wird zuerst ein Ziel auf der linken Tischseite generiert.
Dieses Ziel befindet sich auf dem Boden, und muss auf die Tischebene angehoben werden.
Das Anheben wird durch eine OffsetPose-Node realisiert.
Mit einer RobotMove-Node wird die Bewegung zum generierten Zielpunkt ausgeführt.
Dies geschieht auf gleichem Weg für die rechte Tischseite.
Der vollständige Baum ist in Abbildung \ref{tree_robot_coex} zu sehen.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-tree-robot-coex}
\centering
\caption{Erweiterter BehaviorTree des Roboters bei Koexistenz }
\label{tree_robot_coex}
\end{figure}
\subsubsection{Kooperation}
Im Kooperationsszenario wird der Ablauf des ersten Szenarios modifiziert.
Nach der Bewegung zu einem Objekt auf der linken Tischseite wird eine zufällige Entscheidung getroffen.
Mit einer Chance von 90\% ist das Objekt den Anforderungen gerecht, und kann durch den Roboter auf ein Förderband gelegt werden.
Im anderen Fall soll das Objekt auf den rechten Tisch gelegt werden.
Von dort aus soll der Mensch dieses Objekt abholen, um den Ausschuss im Regal zu verstauen.
Es wird dafür die gleiche Operationsabfolge aus dem ersten Szenario verwendet.
Am Ende der übernommenen Sequenz wird eine SetCalledTo-Node ergänzt, die den Menschen ruft.
Eine Visualisierung des modifizierten Baums wird in Abbildung \ref{tree_robot_coop} gezeigt.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-tree-robot-coop}
\centering
\caption{Erweiterter BehaviorTree des Roboters bei Kooperation}
\label{tree_robot_coop}
\end{figure}
\subsubsection{Kollaboration}
Für das dritte Szenario soll der Roboter mit dem Menschen kollaborieren.
Um dieses Szenario umzusetzen, wird wiederum das erste Szenario modifiziert.
Hier soll der Roboter eine Palette ausladen, wobei es durch die Beschaffenheit der Objekte zu Fehlern kommen kann.
Dies wird durch das Hinzufügen mehrerer RandomFailure-Nodes erreicht, welche die Sequenz abbrechen können.
Da diese Probleme durch den Menschen behoben werden sollen, muss ein Mechanismus geschaffen werden, um diesen zu rufen.
Hierfür wird eine Fallback-Node über der InterruptableSequence angeordnet.
Diese Fallback-Node führt im Fall eines Fehlers in der InterruptableSequence die nächste Node aus.
Bei dieser handelt es sich um eine SetCalledTo-Node, welche den Menschen ruft.
Der Aufbau des Baumes wird in Abbildung \ref{tree_base_colab} dargestellt.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-tree-robot-colab}
\centering
\caption{Erweiterter BehaviorTree des Roboters bei Kollaboration}
\label{tree_base_colab}
\end{figure}
\subsection{Verhalten des Menschen}
\subsubsection{Koexistenz}
Im ersten Szenario soll der Mensch nur neben dem Roboter arbeiten.
Um das Verhalten des Roboters trotzdem zu prüfen, soll der Mensch selten die Sicherheitszone betreten, um den Roboter zu überwachen.
Dies wird durch eine WeightedRandom-Node erreicht, die mit 95 prozentiger Wahrscheinlichkeit den normalen Arbeitsvorgang ausführt.
Im anderen Zweig wird mit einer Wahrscheinlichkeit von 5\% eine Bewegung in die Sicherheitszone ausgelöst.
Der normale Arbeitsvorgang ist ein linearer Ablauf und wird von einer Sequence-Node repräsentiert.
In dieser werden nacheinander die beiden Subtrees ``WorkOnBench'' und ``DepositToShelf'' ausgeführt.
Das Bewegen in die Sicherheitszone wird auch durch eine Sequence-Node erreicht.
Diese enthält eine GenerateXYPose-Node, um ein Ziel in der Sicherheitszone auszuwählen und eine ActorMovement-Node, um die Bewegung auszulösen.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-tree-actor-coex}
\centering
\caption{BehaviorTree des Menschen bei Koexistenz }
\label{tree_actor_coex}
\end{figure}
\subsubsection{Kooperation}
Für das Kooperationsszenario wird diese Abfolge durch eine weitere, übergeordnete Sequence-Node ergänzt.
Diese wurde ausgewählt, da ein Ruf durch den Roboter in diesem Szenario nicht zeitkritisch ist.
Eine solche Implementation erlaubt das vollständige Ausführen der vorgehenden Aufgabe.
In dieser hinzugefügten Sequence-Node wird die nötige Funktionalität für das Abholen der aussortierten Objekte implementiert.
Dies erfolgt nach einer AmICalled-Node, welche die Sequence vorzeitig beendet, falls der Mensch nicht gerufen wurde.
Das vorzeitige Beenden führt zu einer erneuten Auswahl zwischen Arbeit und Inspektion.
Sollte der Mensch jedoch durch den Roboter gerufen werden, wird die Sequence weiter ausgeführt.
In diesem Fall soll der Mensch das Objekt vom rechten Tisch abholen und in das Regal legen.
Dies erfolgt durch eine ActorMovement-Node, welche den Mensch zum rechten Tisch bewegt.
Nach dieser werden zwei ActorAnimation-Nodes ausgeführt, welche die Animationen ``standing_extend_arm'' und ``standing_retract_arm'' durchführen.
Dies simuliert den Griff auf den Tisch, um das Objekt aufzuheben.
Um das Objekt im Schrank zu verstauen, wird der bereits definierte Subtree ``DepositToShelf'' genutzt.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-tree-actor-coop}
\centering
\caption{BehaviorTree des Menschen bei Kooperation }
\label{tree_actor_coop}
\end{figure}
\subsubsection{Kollaboration}
Für das letzte Szenario soll der Mensch dem Roboter beim Entladen einer Palette assistieren.
Falls der Mensch nicht vom Roboter gerufen wird, soll dieser im Raum herumwandern.
Für die Festlegung einer Baumstruktur ist es wichtig, ob der Mensch sofort, oder erst nach dem Vollenden seiner Arbeit dem Roboter zu Hilfe kommt.
In diesem Fall soll er seine Arbeit unterbrechen, um auch diese Möglichkeit zu demonstrieren.
Ein solches Verhalten lässt sich mit Hilfe einer Fallback- mit untergeordneter ReactiveSequence-Node modellieren.
In der ReactiveSequence wird zuerst überprüft, ob der Mensch gerufen wurde.
Da dieser Zustand die ReactiveSequence abbrechen soll, wird die verantwortliche AmICalled-Node mit einer Inverter-Node versehen.
In der ReactiveSequence kann eine weitere Sequence nach der Inverter-Node angeornet werden.
Diese Sequence-Node enthält den Teil des Baumes, der ausgeführt werden soll, solange der Mensch nicht gerufen wird.
In diesem Fall wird mit einer WeightedRandom-Node eine von drei GenerateXYPose-Nodes ausgewählt.
Diese geben jeweils Ziele in der sicheren Zone, Warnzone und Sicherheitszone zurück.
Nach der WeightedRandom-Node wird eine ActorMovement-Node genutzt, welche den Mensch zu dem ausgewählen Ziel bewegt.
Neben der ReactiveSequence unter der Fallback-Node wird eine weitere Sequence angeordnet.
Hier wird die gewünschte Aktionsfolge bei Ruf durch den Roboter definiert.
In diesem Fall soll das Aufheben eines heruntergefallenen Objekts durchgeführt werden.
Dazu wird zuerst ein Ziel in der Warnzone unter Zuhilfenahme einer GenerateXYPose-Node generiert.
Nach einer Bewegung zu diesem Ziel durch eine ActorMovement-Node wird ein Objekt aufgehoben.
Dies geschieht durch vier ActorAnimation-Nodes, welche jeweils die Animationen ``standing_to_low'', ``low_inspect'', ``low_grab'' und ``low_to_standing'' ausführen.
Um das Objekt wieder abzulegen, wird eine Bewegung zum rechten Tisch des Roboters durch eine ActorMovement-Node ausgeführt.
Auch hier wird zum Ablegen eine weitere Animationsfolge aus zwei ActorAnimation-Nodes genutzt.
Dabei wird zuerst ``standing_extend_arm'' abgespielt, gefolgt von ``standing_retract_arm''.
Als letzte Aktion wird der Ruf nach dem Menschen durch eine SetCalledTo-Node auf false zurückgesetzt.
\begin{figure}
\includegraphics[width=\textwidth]{img/MA-tree-actor-colab}
\centering
\caption{BehaviorTree des Menschen bei Kollaboration }
\label{tree_actor_colab}
\end{figure}