Universität Erlangen-Nürnberg Lehrstuhl für Informatik 8 Künstliche Intelligenz Studienarbeit: Graphenbasierte Robotersteuerung, Konzeption und Implementierung einer autonomen Erkennung von Hindernissen und Abweichungen. Sami Ben Younes Abidi Fachbereich: Informatik Betreuer: Prüfer: Dr.-Ing. Bernd Ludwig Prof. Dr. Herbert Stoyan 1 Inhaltsverzeichnis: 1. Einleitung und Aufgabenstellung ........................................................................................ 3 1.1. Motivation und Einordnung der Arbeit ......................................................................... 3 1.2. Zielsetzung .................................................................................................................... 3 1.3. Weiterer Aufbau der Arbeit ........................................................................................... 5 2. Einführung in den Roboter Robertino und Stand der Technik ....................................... 6 2.1. Beschreibung von Robertino ......................................................................................... 6 2.2. Zugriff auf Robertino .................................................................................................... 7 2.3. Stand der Technik .......................................................................................................... 8 3. Plandarstellung .................................................................................................................... 13 3.1 Einführung in die Graphentheorie ................................................................................ 13 3.2 Modellierung eines Plans ............................................................................................. 14 3.2.1 Positionen und Abstände ........................................................................................... 14 3.2.2 Winkeln ..................................................................................................................... 17 4. Wegberechnung .................................................................................................................. 20 4.1 Der Dijkstra-Algorithmus ............................................................................................ 20 4.2 Einsatz von Dijkstra bei der Wegberechnung .............................................................. 22 5. Nachrichtenübertragung..................................................................................................... 24 5.1 Abbildung Kommandos/Nachrichten ........................................................................... 24 5.2 Implementierung der Abbildung in Java ...................................................................... 25 5.3 Ablaufsbeispiel ............................................................................................................. 28 6. Fehlererkennung und Korrekturverfahren ....................................................................... 30 6.1 Erkennung von Hindernissen ....................................................................................... 30 6.2 Korrekturverfahren bei Hindernissen .......................................................................... 31 6.3 Erkennung von Abweichungen ................................................................................... 32 6.4 Korrekturverfahren bei Abweichungen ........................................................................ 33 6.4.1 Lineare Regression .................................................................................................... 33 6.4.2 Korrekturverfahren .................................................................................................... 35 7. Architektur und Zusammenspiel der in Java implementierten Komponenten ........... 37 7.1 Einführung in UML (Unified Modelling Language) ................................................... 37 7.1.1 UML-Klassendiagramm ............................................................................................ 37 7.1.2 UML-Sequenzdiagramme ......................................................................................... 38 7.1.3 UML-Aktivitätsdiagramme ....................................................................................... 38 7.2 Überblick auf die Klassenarchitektur ........................................................................... 39 7.3 Die Klasse Plan ............................................................................................................ 41 7.4 Die Klasse Show_the_way ........................................................................................... 42 7.5 Die Klassen Client, DriveDClient und MouseDClient ................................................ 43 7.6 Die Klassen ClientInputThread und MouseClientInputThread.................................... 44 7.8 Die Klasse Controller ................................................................................................... 44 7.9 Die Klasse ObtacleDetected ......................................................................................... 45 7.10 Die Klassen Deviation und Regression ...................................................................... 45 7.11 Zusammenspiel der Komponenten ............................................................................. 46 8. Tests und Ergebnisse ......................................................................................................... 51 9. Zusammenfassung / Ausblick ........................................................................................... 52 10. Literatur............................................................................................................................... 54 2 1. Einleitung und Aufgabenstellung 1.1. Motivation und Einordnung der Arbeit Um unser Leben zu erleichtern werden heutzutage verschiedene Roboter fast überall eingesetzt. Diese Roboter können für uns mehrere Aufgaben von den ganz einfachen Aufgaben (z.B. Waschen mit einer Waschmaschine) bis zu den ganz komplizierten (z.B. Exploration der Marsoberfläche) erledigen. Für die ganz komplizierten Aufgaben wurde eine neue Generation von Robotern entwickelt. Diese Roboter verfügen sogar über künstliche Intelligenz, so dass sie unabhängig vom Menschen die Umwelt beobachten können und Entscheidungen selbstständig treffen. Nach [Go00] bedingt die Konstruktion von Robotern als autonome intelligente Systeme ein enges interdisziplinäres Zusammenwirken der Gebiete mechanisches und elektrisches Design der aktorischen Komponenten mit den sensorisch-kognitiv orientierten Bereichen: Signalverarbeitung, Mustererkennung, Wissenrepräsentation und Inferenz, Bilderkennug, Planung, Navigation, Lernen, Dialogführung MenschMaschine, Architektur. Aus Sicht der künstlichen Intelligenz klassifiziert [Go00] die Roboter in drei Kategorien. Bei den stationären Robotern verfügt der Roboter über einen Arm und einen Greifer. Es geht hier neben der Planung kollisionsfreier Bewegungen des Armes um die Erkennung und Verfolgung von Objekten sowie die exakte Bestimmung ihrer Lage für den Greifvorgang. Bei den mobilen Robotern verfügen der Roboter meistens über Räder und mehrere Sensoren. Mit den Rädern lässt sich der Roboter fahren. Mit den Sensoren exploriert er seine Umgebung. Die Hauptaufgabe besteht hier weiterhin in der Navigation, die in unterschiedlich stark strukturierten, dynamischen und ggf. auch unbekannten Umgebungen zuverlässig arbeitet. Die humanoiden Roboter stellen die „Krone der Robotik“ dar. Die Fähigkeiten dieser Roboter sollten weitgehend dem Menschen nachgebildet werden. Diese Roboter sehen strukturell wie die Menschen aus und können sogar laufen und Treppen steigen. 1.2. Zielsetzung Haben Sie einmal in einem großen Flughafen den Weg zum Terminal nicht gefunden? Es ist absolut möglich, dass man in einem Flughafen wie Frankfurt den richtigen Weg zu einem bestimmten Terminal nicht findet, obwohl viele Flughafenmitarbeiter am Ort 3 sind und gerne helfen. Stellen Sie sich vor, dass am Flughafen viele kleine Autos auf Sie warten. Diese Autos (Roboter) verfügen über ein Sprachsystem zur Begrüßung und können Sie bis zum gewünschten Gate begleiten. So sind Sie fast sicher, dass Sie den Flieger nicht verpassen werden, was natürlich sehr schön wäre. Nennen wir diese Anwendung Flughafenbegleiter. Der Lehrstuhl für Künstliche Intelligenz an der Universität Erlangen-Nürnberg verfügt über einen kleinen Roboter namens Robertino. Mit diesem Roboter wird versucht eine ähnliche Anwendung wie den Flughafenbegleiter zu implementieren. Man kann diese Anwendung wie folgt beschreiben: Der Roboter steht vor der Tür des Aufzugs des Lehrstuhls und wartet. Wenn die Tür aufgeht, muss Robertino erkennen, ob der Aufzug leer oder nicht ist. Wenn eine Person im Aufzug erkannt ist, begrüßt Robertino den Besucher und fragt ihn, wo er hin möchte. Er erkennt das gewünschte Ziel und fordert den Besucher auf, ihn zu folgen. Es klingt alles so einfach, ist aber sehr kompliziert zu realisieren. Es könnte auch Hindernisse auf dem Weg zum Zielraum geben. Deswegen muss Robertino so intelligent sein, dass er die Hindernisse selbstständig erkennt und Umwege berechnen kann. Robertino kann auch aus irgendeinem Grund aus seinem Weg abweichen. Das muss er auch automatisch erkennen und den Fehler rechtzeitig korrigieren. Im Rahmen dieser Studienarbeit werden folgende Teilaufgaben untersucht und in Java implementiert. 1. Plandarstellung und Wegberechnung 2. Fehlererkennung und notwendige Korrektur Bei der Plandarstellung wird der Lehrstuhlplan in einer geeigneten Form modelliert. Der Roboter soll in der Lage sein, diese Darstellungsform zu verstehen, damit er sich auf dem Gang des Lehrstuhls orientieren und positionieren kann. Die Teilaufgabe Wegberechnung hat als Ziel, dem Roboter zu ermöglichen sich zwischen verschiedenen Positionen auf dem Plan zu bewegen. Es wird spezifiziert, wie Robertino den kürzesten Weg zu einem Ziel berechen kann. Die Teilaufgaben Fehlererkennung und notwendige Korrektur untersuchen die automatische Erkennung von Hindernissen oder möglichen Abweichungen von Robertino auf dem Weg zu seinem Ziel. Bei Fehlersituationen muss eine automatische Korrektur ohne Eingriff des Menschen erfolgen. 4 1.3. Weiterer Aufbau der Arbeit Begonnen wird diese Studienarbeit mit dem Kapitel 2, in dem eine Beschreibung des Roboters Robertino und der Zugriffsmöglichkeiten vorgestellt wird. In Kapitel 3 wird zunächst die Graphentheorie eingeführt. Anschließend wird diese Theorie in die Modellierung der Lehrstuhlkarte eingesetzt. Der Kapitel 4 widmet sich dem Einsatz eines bekannten Algorithmus bei der Wegberechnung des Roboters. In Kapitel 5 wird die Nachrichtenübertragung zwischen dem Roboter und den Steuerkomponenten vorgestellt. Die verschiedenen Fehlersituationen und Korrekturverfahren werden in Kapitel 6 behandelt. Die Beschreibung der in Java implementierten Klassen sowie des Zusammenspiels dieser Klassen folgt in Kapitel 7. Kapitel 8 beschreibt Testergebnisse der im Rahmen dieser Studienarbeit entwickelten Lösung. Abschluss dieser Arbeit bilden eine Zusammenfassung und ein Ausblick auf weitere Erweiterungsmöglichkeiten der entwickelten Komponenten. 5 2. Einführung in den Roboter Robertino und Stand der Technik 2.1. Beschreibung von Robertino Abbildung 2-1: Robertino [Li02] Der Roboter Robertino (Abbildung 2-1) besitzt 3 Motoren in einem Abstand von 120 Grad. Er verfügt auch über 3 Räder, die ihm Bewegungen in die X-Richtung und die Y-Richtung sowie Rotation um die eigene Achse ermöglichen. Um die Abstände messen zu können verfügt Robertino über 6 Infrarotsensoren. Mit seiner Kamera kann Robertino auch Bilder aufnehmen, deren Auflösung 640x480 ist. Unter dem Roboter wurde auch eine optische Maus eingebaut, deren Nutzen später erwähnt wird. Das „Gehirn“ von Robertino ist ein industrieller Rechner mit den folgenden Charakteristiken: 500MHz Intel Mobile-Pentium III 128MB RAM, 20GB Hard disk (IDE) Graphics adapter 2x USB, 2x RS232, 1x Parallel port 3x Firewire, 2x CAN-Bus controller (active), 2x 32bit PC-Card slots 10/100 MBit LAN, IEEE802.11a wireless LAN Betriebsystem: Debian GNU/LINUX 6 2.2. Zugriff auf Robertino Der Zugriff auf die Hardware von Robertino erfolgt über so genannte Dämonen, die entweder in C oder in Java geschrieben sind. Die in Rahmen dieser Studienarbeit verwendeten Dämonen sind: 1. Drived: ist ein TCP/IP Server auf Port 30000 und empfängt vom Client Nachrichten in der Form 26##omega(100),vx(100),vy(100)#. Die Zahl 26 entspricht in diesem Beispiel der Länge der Nachricht. Mit diesen ASCII-String-Nachrichten kann man die Geschwindigkeit des Roboters auf der x-Achse und die y-Achse sowie seine Rotationsgeschwindigkeit einstellen. omega(100) setzt die Winkelgeschwindigkeit auf 100 mdeg/s. vx(100) setzt die Geschwindigkeit auf der x-Achse auf 100 mm/s. vy(100) setzt die Geschwindigkeit auf der y-Achse auf 100 mm/s. Dieser Server sendet dem Client als Antwort seine Sensorwerte. Die Antwortnachricht ist von der Form 99##ADC00(60),ADC01(5),...,va0(0),va1(9),va2(5)#. Der Sensor ADC00 liefert in diesem Fall den Wert 60 mm zurück. Die Abbildung 2-2 zeigt die x-Achse und die y-Achse des Roboters. Die Abbildung 2-3 stellt die verschiedenen Sensoren dar. Beide Abbildungen wurden aus [Li02] übernommen. Abbildung 2-2: x-Achse, y-Achse Abbildung 2-3: Sensoren 7 2. Moused: ist ein TCP/IP Server auf Port 30002; er berichtet dem Client über die Bewegungen der unter dem Roboter eingebauten optischen Maus. Als Mauswerte bekommt der Client die Koordinaten (x,y) des Roboters im Bezug auf seine Startposition. Dazu besitzt Robertino auch den Server: 3. Visiond: ist ein TCP/IP Server auf Port 30001; er liefert Kameraaufnahmen (VGAScans) in JPEG, RAW Format. Ein Client öffnet zum Beispiel eine TCP/IP -Verbindung zum Drived-Server und sendet dem Roboter auf dieser Verbindung die Geschwindigkeitseinstellung. Ein zweiter Client öffnet eine andere TCP/IP -Verbindung zum Moused-Server. Der Roboter reagiert mit der notwendigen Bewegung in die gewollte Richtung und sendet dem Drived-Client die Sensormesswerte und dem Moused-Client die Mauswerte zurück. 2.3. Stand der Technik Umgebungsmodellierung: Bei der Umgebungsmodellierung erstellt man ein Modell auf der Basis der Informationen, die der Roboter über seine Sensoren von seiner Umgebung erhält. Wir wollen kurz unterschiedliche Arten von Umgebungsmodellen diskutieren. [Go00] unterscheidet hierbei zwischen: a) topologischen, b) geometrischen, c) rasterbasierten Modellen. topologische Karten: bei einer topologischen Karte der Umgebung werden annotierte Graphen verwendet. Die Kanten dieser Graphen stellen für den Roboter relevante Orte der Umgebung dar. Eine Kante verbindet dann zwei Knoten, wenn sie unmittelbar voneinander erreichbar sind. Der Vorteil dieser Repräsentation im Vergleich zu den anderen Modellierungsmöglichkeiten liegt in der Kompaktheit. Diese Karten sind auch sehr gut für die Wegplanung geeignet, weil das Problem in diesem Fall auf die Erreichbarkeit in Graphen reduzieren lässt. Es gibt schon Systeme, die toplogische Modelle für die 8 Navigation nutzen [Hi96]. Diese Art von Modellierung wird im Rahmen dieser Studienarbeit implementiert. geometrische Modellierung: 2D- und 3D-Modelle stellen die detaillierteste Modellierungsform dar. Diese Modelle erfordern geeignete Verfahren zur Detektion und Lokalisierung der einzelnen Objekte. Deswegen liegen ihrer Nachteile in ihrer schwierigen Lern- und Aktualisierbarkeit. Rasterbasierte Modelle: Rasterbasierte oder Occupancy probability grid maps ist eine sehr populäre Repräsentationsform für autonome mobile Roboter. Dieses Modell besteht aus einer Diskretisierung der Umgebung des Roboters in üblicherweise kleine Quadrate. Jede Zelle in dieser Gridkarte enthält die Wahrscheinlichkeit, dass die entsprechende Stelle der Umgebung durch ein Hindernis belegt ist. Lokalisierung Ein weiteres grundlegendes Problem der mobilen Robotik ist das Problem der Lokalisierung oder der Positionsbestimmung. Wenn der Roboter über ein Modell der Umgebung verfügt, lässt sich das Lokalisierungsproblem als die Aufgabe beschreiben, die wahrscheinlichste Position des Roboters gegeben alle Sensorinformationen und alle durchgeführten Navigationsoperationen zu berechnen. Die Markow-Lokalisierung ist ein sehr allgemeiner Ansatz zur Schätzung der Position eines mobilen Roboters. Man versucht bei diesem Ansatz eine Wahrscheinlichkeitsverteilung P(L = l) über dem Zustandsraum des Roboters in seiner Umgebung zu schätzen. Der Zustandsraum des Roboters enthält in diesem Fall alle möglichen Positionen und Orientierungen des Vehikels. Formal besteht das Problem der Positionsschätzung daraus, eine Wahrscheinlichkeitsverteilung P(Lt) über eine Zufallvariable Lt zu schätzen. Die Werte l der Variablen Lt sind die 3-Tupel der Form l = (x,y,ß), wobei x und y die Position und ß die Orientierung des Roboters angeben. Für jeden möglichen Zustand des Roboters beschreibt die Verteilung P(L t) die Wahrscheinlichkeit, dass sich der Roboter genau in diesem Zustand befindet. Diese Wahrscheinlichkeitsverteilung P(Lt) wird aktualisieret, wenn der Roboter Informationen 9 über seine Umgebung Sensorinformationen. aufnimmt. Sei d der gesamte Datenstrom von Die Markow-Lokalisierung schätzt eine Posterior-Verteilung über LT bedingt über allen erhaltenen Informationen: P(LT = l | d) = P(LT = l | d0,d1,..,dT) [Go00] bietet eine ausführliche Beschreibung der Markow-Lokalisierung an. Pfadplanung Pfadplanung gehört zu den klassischen Problemen der mobilen Robotik. Das Ziel der Pfadplanung ist die Berechnung eines möglichst kurzen Weges von einer aktuellen Position des Roboters zu einer gegebenen Zielposition. Es existieren mehrere Verfahren für die die Berechnung solcher Trajektoren. Eine häufige Voraussetzung aber ist, dass die Umgebung des Roboters vollständig bekannt ist. Eine zweite Voraussetzung ist, dass die Umgebung während der Fahrt des Roboters unverändert bleibt. Bekannte Techniken dieser Art sind zum Beispiel VornoiDiagramme oder Sichtbarkeitsgraphen. Der Unterschied zwischen diesen Verfahren ist, dass Sichtbarkeitsgraphen Weglänge minimieren, während Vornoi-Diagramme versuchen, den Abstand zu Hindernissen zu maximieren. Das Buch [La91] ist dem Pfadplanungsproblem komplett gewidmet. Wenn sich die Umgebung aber dynamisch verändert, wird die Potentialfeldmethode eingesetzt. [Kh86] bietet eine detaillierte Beschreibung dieser Methode. Es gibt auch eine andere Methode, die speziell für Occupancy grid maps geeignet ist. Diese Methode heißt Value Iteration und wird in [Go00] ausführlich beschrieben. Wenn sich die Umgebung des Roboters aber verändert und Hindernisse plötzlich auf dem Pfad stehen werden typische Techniken wie reaktive Kollisionsvermeidungen eingesetzt. Im Rahmen dieser Studienarbeit werden solche Techniken auch eingesetzt. Systeme Die oben beschriebenen Techniken werden bei verschiedenen Systemen für die Navigation autonomer mobiler Roboter eingesetzt. Beispiele hierfür sind die Roboter Xavier, Kurt, Rhino und Minerva. Xavier wird zur Navigation in einem Bürogebäude eingesetzt. Kurt kann Kanalsysteme autonom inspizieren. Die Systeme Rhino und Minerva wurden in den Jahren 1997 und 1998 im Deutschen Museum Bonn sowie in National Museum of American History in Washington DC über mehrere Tage als interaktive Museumsführer eingesetzt. Beide 10 Roboter waren sehr zuverlässig und führten tausende von Besuchern durch die Ausstellung und erklärten Exponate über ihre Displays. Rhino hat 47 Stunden operiert und eine Distanz von 18,6 km zurückgelegt. Das gewünschte Exponat wurde nur in 6 von 2400 Fällen nicht erreicht. In Nagoya, Japan wurde 1997 die erste Weltmeisterschaft fußballspielender Roboter ausgetragen. Deutsche Teams waren dabei und waren sehr erfolgreich. Xavier [Li03] Rhino [Li04] Kurt [Li05] Minerva [Li06] Ein Team der Universität Stanford hat zusammen mit dem Volkswagen Electronics Research Laboratory (ERL) und seinem auf den Namen "Stanley" getauften VW Touareg die DARPA Grand Challenge 2005 in der Mojave-Wüste gewonnen. Ziel des Wettbewerbs war es, mit autonomen Fahrzeugen einen vorgegeben Parcours von 130 Meilen möglichst schnell zu absolvieren. Stanley [Li07] Der von den Entwicklungsingenieuren „Stanley“ getaufte Prototyp, ist ausgerüstet mit Sensoren und von vier Laser-Detektoren. Ergänzt werden die Systeme durch StereoSichtgeräte, hoch entwickelte 24-GHz-Radaranlagen und ein besonders exakt analysierendes, satellitengestütztes GPS-Navigationssystem, das die genaue Position des Fahrzeugs auf den Millimeter genau digital abbildet. Verarbeitet werden die Informationen von sieben zusammengeschalteten Pentium M-Motherboards mit einer Rechenleistung von 1,6 GHz pro Prozessor. Die gesammelten Daten werden von diesen Rechnern ständig kontrolliert und die die notwendigen Fahranweisungen 11 werden daraufhin ermittelt. Im Rahmen dieser Studienarbeit wird ein ähnlicher Ansatz implementiert. 12 3. Plandarstellung Beim Autofahren, brauchen wir meistens eine Karte. Diese Karte wird entweder auf Papier gedruckt, ist elektronisch verfügbar (z.B. GPS) oder wird einfach in unseren Gehirnzellen gespeichert. Ähnlich braucht unser Roboter eine Karte, um sich zu orientieren. Aus einem Plan wird eine Karte erzeugt. Wie könnte man aber diese Karte modellieren, so dass Robertino ihre Inhalte leicht verstehen kann. Ich habe mich für Graphen entschieden um Pläne (bzw. Karten) darzustellen. Diese Entscheidung hat aber ihre Gründe. Graphendarstellung ist eine ausgereifte Technik, die sehr effiziente Algorithmen bietet. Diese Algorithmen sind von enormer Bedeutung für dieses Projekt, weil sie die Arbeit sehr vereinfachen. Deswegen gibt es zunächst eine kleine Einführung in die Graphentheorie. 3.1 Einführung in die Graphentheorie Das Thema Graphentheorie wird im Buch [Kla00] gründlich behandelt. Hier gibt es aber nur die wichtigsten Begriffe und Definitionen aus diesem Buch. Ein Graph G = (V, E) ist ein Tupel mit nicht leerer, endlicher Knotenmenge V = {v1,…, vn} und Kantenmenge E = {e1,…, em}. Jede Kante hat zwei Endknoten und wird mit e = [i, j] bezeichnet. Ein Digraph G = (V, E) ist wie ein Graph aber jede Kante e Є E hat eine Richtung. In diesem Fall bezeichnet man die kanten e mit (i, j) wobei i der Anfangspunkt t(e) und j die Spitze h(e) von e ist. t(e) und h(e) sind die Endpunkte von e. Eine Adjazenzmatrix A(G) ist eine n x n-Matrix mit den Werten: aij = 1 falls [i, j] Є E aij = 0 sonst (oder (i, j) Є E für Digraphen) Ein Weg P = (i1,…ip, ip+1,…, ie) ist eine Folge von Knoten mit: [ip, ip+1] Є E für Graphen (ip, ip+1) Є E oder (ip+1, ip) Є E für Digraphen Ein zusammenhängender (Di-)Graph besitzt die folgende Eigenschaft: Je zwei Knoten in G sind durch einen Weg verbunden. 13 Beispiel Die Abbildung 3-1 zeigt links einen Graphen und rechts einen Digraphen. Darunter sind die zugehörigen Adjazenzmatrizen dargestellt. Beide Beispiele sind zusammenhängende (Di-)Graphen. Abbildung 3-1: Graph und Digraph 3.2 Modellierung eines Plans 3.2.1 Positionen und Abstände Graphen bestehen aus Knoten und Kanten. Die Kanten tragen Kosten. Was modellieren also diese Knoten, Kanten und Kosten? Die Knoten sind ausgewählte Positionen des Roboters. Die Kanten verbinden diese Positionen und ihre Kosten entsprechen den tatsächlichen Abständen zwischen den Positionen. Der Roboter bewegt sich zwischen diesen Positionen und fährt entlang der dazwischen liegenden Kanten. 14 Abbildung 3-2: Floor des KI-Lehrstuhls und Graph 15 Anhand des Beispiels (Abbildung 3-1) wird diese Modellierung deutlich gemacht. Die von 1 bis 15 durchnumerierten Knoten sind für den Roboter ausgewählte Positionen. Der entstehende Graph ist eine sehr einfache Möglichkeit den Plan zu modellieren. Natürlich gibt es mehrere bessere Möglichkeiten mit größerer Anzahl von Knoten und Kanten um diesen Plan zu modellieren. Zum Beispiel könnte man den einfachen Graph um mehrere Knoten und Kanten erweitern. Je mehr Knoten und Kanten es gibt, desto besser ist unsere Darstellung. Wenn es mehr Kanten und Knoten gibt, gibt es natürlich mehrere unterschiedliche Wege mit dem gleichen Startknoten und Zielknoten. Wenn ein ursprünglicher Weg aus irgendeinem Grund nicht befahrbar wäre, hätte Robertino alternative Möglichkeiten. Bleiben wir jetzt bei der einfachen Modellierung. Es gibt immer eine Strecke, die vom Fahrstuhl bis zu einer bestimmten Tür führt. Eine Strecke ist eine Folge von Knoten und Kanten. Der Roboter braucht zusätzlich die Winkel zwischen den benachbarten Kanten auf seinem Weg zu einem bestimmten Ziel. Zusätzlich zu den Abständen zwischen den Knoten muss man die notwendigen Winkel berechen und speichern. Die Abbildung 3-3 ist eine Abstandsmatrix. Sie wird verwendet, um die Abstände zwischen den Knoten zu speichern. Knoten 1 1 2 2 3 4 690 3560 5 6 690 1980 7 8 690 690 9 11 12 13 14 690 690 1340 15 590 590 3 690 4 3560 5 690 6 1980 7 690 8 690 1770 9 10 10 690 1770 690 11 4210 4210 12 690 13 690 14 1340 15 690 690 Abbildung 3-3: Abstandsmatrix In der Zelle [1,2] gibt es zum Beispiel die Zahl 590. Das bedeutet, dass der Abstand zwischen den Knoten (1) und (2) 590 mm beträgt. Diese Matrix ist symmetrisch, weil 16 der verwendete Graph kein gerichteter Graph ist. Der Roboter kann sich also in zwei Richtungen auf der gleichen Kante bewegen. Wenn eine Zelle der Abstandmatrix leer ist, dann existiert keine Kante zwischen den betroffenen Knoten. Matrizen sind in Java sehr einfach darzustellen. Zuerst erzeugt man die leere Matrize: int[][] distance_between_nodes = new int[14][14]; Dann füllt man sie mit den notwendigen Abständen. Um den Abstand zwischen den Knoten (1) und (2) zu speichern braucht man folgende Zuweisungen: distance_between_nodes[1][2] = 590; distance_between_nodes[2][1] = 590; Dazu muss man alle Zellen in der Matrize, die leer geblieben sind, mit „unendlich“ füllen. Eine leere Zelle wie (1,5) z.B. bedeutet, dass es keine Kante zwischen den Knoten (1) und (5) existiert. 3.2.2 Winkeln Jetzt muss man eine geeignete Darstellungsform für die Winkel finden. Betrachten wir die zwei Kanten [2,4] und [4,5]. Der Winkel zwischen diesen Kanten beträgt 90 Grad. Der Roboter fährt entlang der Kante [2,4]. Wenn er den Knoten (4) erreicht dreht gegen den Uhrzeigersinn um 90 Grad dann fährt er weiter entlang der Kante [4,5]. Abbildung 3-4: Graphenerweiterung Um die Strecke (3-4-5) zu fahren, führt der Roboter eine Folge von Befehlen aus. 17 Diese Folge könnte so aussehen: GO [3560] ; TURN [-90] ; GO[690] Er benötigt immer den Winkel zwischen der Kante, auf der er sich zur Zeit befindet, und der nächsten Kante auf seinem Weg zum Ziel. Man kann eine 3-dimensionale Struktur (Abbildung 3-5) verwenden: Zelle (1,2,3) mit Inhalt [-90] 3 -90 2 Knoten y 3 1 2 1 1 2 Knoten z 3 Knoten x Abbildung 3-5: Würfel der Winkeln Die drei Dimensionen des Würfels sind Knoten [x], Knoten [y] und Knoten [z]. Der Inhalt der Zelle (x, y, z) ist die Antwort auf die Frage: Um wie viel Grad w muss sich Robertino drehen, wenn er die Fahrt auf der Teilstrecke [x-y] beendet hat und auf die Teilstrecke [y-z] weiter fahren muss? (Abbildung 3-6) z Dist(y,z) Dist(x,y) x W y Drehung um w Grad um auf der Kante [y-z] weiter zu fahren aktuelle Bewegungsrichtung von Robertino [ x-y ] Abbildung 3-6: Drehung 18 x Im Beispielswürfel bedeutet der Inhalt -90° der Zelle (1, 2, 3), dass Robertino genau eine Drehung von -90° machen muss, um von der Teilstrecke [1-2] auf die Teilstrecke [2-3] zu kommen. Um diesen Winkelwürfel in Java umzusetzen, kann man ein 3-dimensionales Feld verwenden. Man erzeugt zuerst den leeren Würfel: int[][][] angle_between_nodes = new int[14][14][14]; Dann füllt man dieses Feld mit den notwendigen Winkeln. Um den Winkel zwischen den Kanten [1-2] und [2-3] zu speichern braucht man folgende Zuweisung: angle_between_nodes[1][2][3] = -90; Jetzt verfügt Robertino über alle notwendigen Abstände und Winkel. Er bekommt vom Besucher eine Zieleingabe. Robertino muss in seinen Plan „schauen“ und den richtigen Weg zum Ziel finden. Dieser Weg muss so kurz wie möglich sein. Weil der Plan als Graph modelliert wird, kann man hier einen Algorithmus für kürzeste Wege verwenden. 19 4. Wegberechnung 4.1 Der Dijkstra-Algorithmus Dieser Abschnitt beruht auf dem Kapitel 6 von [Kla00]. Wir betrachten einen Digraphen G=(V,E) mit den Kosten c(e)=c(ij) für alle e = (ij) Є E Sei s ein beliebiger aber fest gewählter Knoten s Є V. Man bezeichnet das folgende Problem als kürzeste Diweg Problem: Finde für alle i Є V \ {s} einen Diweg Psi von s nach i mit minimalen Kosten c(Psi) wobei c(Psi) = ∑ c(e) und e Є Psi. Man nennt c(Psi) auch die Entfernung von s nach i in G bzgl. der Kosten c(e), e Є Psi. Mit dem Dijkstra-Algorithmus kann man das kürzeste Weg Problem lösen. Dijkstra-Algorithmus (Input) G = (V,E) Digraph mit Kosten c(e) für alle e Є E (1) Setze d(i) := ∞ (für alle i= 1,…,n) d(s) := 0 pred(s) := 0 p := 1 Setze Xp := {s} für alle i Є V \ Xp T(i) := c(si) falls (s,i) Є E T(i) := ∞ sonst für alle i Є V \ Xp pred(i) = s (2) Finde ip+1 mit T(ip+1) = min i Є V\ Xp T(i) und setzte d(ip+1) := T(ip+1) falls T(ip+1) = ∞ (STOP) Es existieren keine Diwege von s nach i für alle i Є V \ Xp (3) Setze Xp+1 := Xp U { ip+1} Setze p := p+1 falls p = n (STOP) alle kürzeste Diwege Psi gefunden (4) für alle i Є V \ Xp falls T(i) > d(ip) + c(ip,i) setze T(i) := d(ip) + c(ip,i) pred(i) := ip Gehe zu Schritt (2) 20 Im Algorithmus stellt d(i) die Kosten des kürzesten Diwegs vom Startknoten s zum Knoten i dar. T(i) sind aber vorläufige Werte für die Kosten des Diwegs von s nach i. Die Werte T(i) werden in jeder Iteration aktualisiert. In jeder Iteration p aktualisiert man für jedes i Є V \ Xp den Wert von T(i). Die Bezeichnung pred(i) liefert den Vorgänger von Knoten i in Psi . Da die Kardinalität |Xp| von Xp in jeder Iteration um 1 größer wird, gibt es maximal n-1 Iterationen. Wir verwenden jetzt den in Abbildung 4 dargestellten Graph, um die kürzesten Diwege von s = v1 nach vi, i = 2,…,6 mit dem Dijkstra-Algorithmus zu bestimmen. Die Kantenbewertungen sind die Kosten c(e). Abbildung 4-1: Graph Abbildung 4-2: Schritte des Algorithmus 21 Man kann den Dijkstra-Algorithmus durch eine Tabelle übersichtlicher darstellen. Die Tabelle in Abbildung 4-2 ist eine solche Tabelle, wobei der im Schritt (2) des Algorithmus gefundene Knoten ip+1 jeweils eingerahmt. Wenn man die Markierungen pred(i) zurückverfolgt erhält man die kürzesten Diwege Psi. 4.2 Einsatz von Dijkstra bei der Wegberechnung Abbildung 4-3: Beispiel für Wegberechnung Betrachten wir jetzt den Plan und die Graphdarstellung in Abbildung 4-3. Stellen wir uns vor, dass Robertino vor der Eingangstür auf dem Startknoten (0) steht. Wir wollen, dass Robertino uns den Weg zum Knoten (4) zeigt. Man sieht, dass es zum Knoten (4) zwei mögliche Wege gibt, entweder durch den Raum 1 oder durch den Floor. Robertino führt zuerst den Dijkstra-Algorithmus mit dem Knoten (0) als Startknoten aus. Der Algorithmus greift auf die Abstandsmatrix zu und liefert als Ergebnis eine Tabelle, die alle die nötigen Informationen zur Ermittlung der kürzesten Wege zu allen anderen Knoten enthält. Wie diese Tabelle in unserem Beispiel aussieht, zeigt die Abbildung 4-4. Zielknoten Vorgänger 1 0 2 1 3 2 4 3 5 6 6 7 7 8 8 1 Abbildung 4-4: Dijkstra-Ergebnis Robertino braucht aber nur den Weg zum Knoten (4). Er sucht in der Tabelle den Eintrag 4 und erhält durch Zurückverfolgung der Vorgänger vom Knoten (4) den kürzesten Weg zu seinem Ziel: 22 [Ziel = 4] [Vorg(4) = 3] [Vorg(3) = 2] [Vorg(2) = 1] [Vorg(1) = 0] Robertino weiß jetzt, dass er die Kanten [0,1], [1,2], [2,3], und dann am Ende [3,4] fahren soll, um die Position 4 zu erreichen. Er greift auf die Abstandsmatrix zu und ermittelt die benötigten Kantenkosten (die Abstände zwischen den Knoten). Er findet danach die benötigten Winkel heraus und beginnt mit seiner Fahrt. Nachdem Robertino den Knoten (4) erreicht hat, kann er auch zu einem anderen Knoten weiterfahren. Er kann zum Beispiel zum Knoten (0) zurückfahren. Er muss nur den Dijkstra-Algorithmus mit dem aktuellen Knoten als Startknoten ausführen. 23 5. Nachrichtenübertragung 5.1 Abbildung Kommandos/Nachrichten Leider versteht Robertino Kommandos wie GO [3560]; TURN [-90]; GO[690] gar nicht. Deswegen müssen wir diese Befehle in Nachrichten umwandeln, die Robertino verstehen kann. Man braucht eine Abbildung, die solche Kommandos in eine Reihe von Nachrichten derart 24##omega(0),vx(100),vy(0) umwandelt. ?##omega(?),vx(?),vy(?)# ?##omega(?),vx(?),vy(?)# ?##omega(?),vx(?),vy(?)# ………………………???? GO [200]; TURN [90]; ………. Notebook: Client Robertino: Server Abbildung 5-1: Abbildung Kommandos / Nachrichten Was passiert, wenn Robertino nur eine Nachricht vom Typ L##omega(a),vx(b),vy(c) an seinem Serversocket empfängt? Diese Nachricht macht keine Aussagen über die Distanz, die Robertino fahren muss. Sie zeigt nur, in welche Richtung und wie schnell Robertino sich bewegen muss. Wie kann Robertino also unter Verwendung dieser Nachrichten eine bestimmte Distanz d fahren? Beim Empfang einer solchen Nachricht bewegt sich Robertino tatsächlich um eine kleine Distanz n und wenn er keine weiteren Nachrichten empfängt, dann stoppt er. Je größer die Geschwindigkeit ist, desto größer ist diese Distanz n. 24 Natürlich bewegt sich Robertino auf einem glatten Boden schneller als auf dem Teppichboden des Lehrstuhls. Diese Distanz n hängt also auch vom Widersand des Bodens ab, auf dem Robertino fährt. Ein Paar Messungen auf dem Teppichboden des Lehrstuhls haben folgendes ergeben: Geschwindigkeit 10 (mm/s) Gefahrene 5 Distanz n (mm) 20 30 40 50 70 100 150 160 170 180 200 10 15 20 31 43 62 93 101 108 116 124 Abbildung 5-2: Messwerte Die Distanz n wächst also linear proportional zur Geschwindigkeit. Jetzt kann man berechnen, wie oft die Nachricht geschickt werden muss um eine bestimmte Distanz d fahren zu können. Sei: d zu fahrende Distanz auf der x-Achse, X die verwendete Geschwindigkeit. Gesucht: Anzahl m der notwendigen Nachrichten vom Typ: L##omega(0),vx(X),vy(0) Zuerst sucht man n die gefahrene Distanz pro Nachricht unter Berücksichtigung der verwendeten Geschwindigkeit. n = 62 mm für X=100 mm/s und m = d/n Man braucht also eine Schleife, die die Nachricht an Robertino m-mal schickt, damit er die Distanz d korrekt fährt. 5.2 Implementierung der Abbildung in Java 1 500 2 Kürzester Weg zum Knoten 2: GO[500] Abbildung 5-3: Beispiel Betrachten wir hier das einfache Beispiel aus der Abbildung 5-3, um die Implementierung leichter verstehen zu können. 25 Robertino muss also 500 mm vom Knoten 1 zum Knoten 2 fahren. Wir legen die Geschwindigkeit auf 50 mm/s fest. Die Klasse Show_the_way verfügt über eine Methode goDistance die den Befehl GO[500] auf eine Folge von Nachrichten abbildet. public void goDistance (int sp , int distance ) { setOmega(0); setSpeed(sp); int dist_l = loopToDistance(); int loops = numberOfLoops(distance, dist_l); send(loops); } public int loopToDistance () { int distance = (speed*62)/100 ; return distance; } Als Parameter bekommt diese Methode die Geschwindigkeit sp (hier 50) und die zu fahrende Distanz distance (hier 500). Zuerst wird mit dem Methodenaufruf setOmega(0) die Winkelgeschwindigkeit omega auf null gesetzt. Mit setSpeed(sp) wird die Geschwindigkeit vx auf 50 mm/s gesetzt. Die Methode loopToDistance() berechnet die gefahrene Distanz pro gesendeter Nachricht. Die Variable dist_l enthält also die berechnete Distanz. Für die Geschwindigkeit 50 mm/s ist die gefahrene Distanz 31 mm. Der Methodenaufruf numberOfLoops(distance,dist_l) berechnet, wie oft muss die Nachricht geschickt werden. Die Variable loops enthält die Anzahl der Nachrichten. In unserem Beispiel muss die Nachricht 500/31 = 16,12-mal geschickt werden. Unser Ergebnis loops muss aber eine natürliche Zahl sein. Die Nachricht kann nicht 16,12mal geschickt werden. Die Nachricht nur 16-mal zu schicken, ist auch keine Lösung. Für loops = 16 würde Robertino nur 16x31= 496 mm fahren. Er muss aber 500 mm fahren und keine 496 mm. Diese 4 mm Unterschied sind nicht vernachlässigbar. Mit dem Auftreten weiterer Präzisionsfehler auf weiteren Teilstrecken könnte Robertino sogar ein Paar Zentimeter vor dem ursprünglichen Ziel anhalten. 26 Frage: Wie kann man Robertino präziser steuern? Je langsamer Robertino fährt, desto präziser kann er fahren. Bei einer hohen Geschwindigkeit ist die Teilstrecke, die der Roboter als Reaktion auf eine empfangene Nachricht fährt, ziemlich lang (200mm/s 124mm). Bei kleineren Geschwindigkeiten sind die Teilstrecken kürzer (10mm/s 5mm) und der Roboter lässt sich deswegen präziser fahren. Die gesamte Strecke mit einer Geschwindigkeit von 10mm/s zu fahren wäre aber zu langsam für unsere Anwendung. Man muss hier einen Kompromiss zwischen Präzision und Schnelligkeit finden. Wir lassen Robertino am Anfang schnell fahren, am Ende seiner Fahrt muss er aber langsamer fahren. In unserem Beispiel schicken wir z.B. am Anfang 15-mal die Nachricht mit Vx=50. Robertino fährt also 15*31= 465 mm. Noch zu fahren sind 500-465 = 35mm. Jetzt kann man die Geschwindigkeit 10mm/s verwenden. Eine Nachricht mit vx = 10 bewegt Robertino um 5mm. 35/5 = 7-mal muss man die Nachricht schicken um die letzten 35mm fahren zu können. Mit diesem Ansatz fährt Robertino genau 500 mm und keine 496 mm. Die Klasse Show_the_way verfügt auch über die Methode public void send (int loops) { for (int i=0 ; i<loops ; i++){ String message = "omega(" + getOmega() + "),vx(" + getSpeed() + "),vy(" + 0 + ")#"; message = message.length() + "##" + message; char[] messageChars = message.toCharArray(); try { if (i==0) { System.out.println(message);} out.writeBytes(message); out.flush(); Thread.sleep(600); } catch (Exception e) { e.printStackTrace(); break; } } } Diese Methode erzeugt die benötigte Nachricht: String message = "omega(" + getOmega() + "),vx(" + getSpeed() + "),vy(" + 0 + ")#"; message = message.length() + "##" + message; char[] messageChars = message.toCharArray(); 27 und schreibt sie dann auf dem Clientsocket: out.writeBytes(message); out.flush(); Natürlich muss man eine bestimmte Zeit warten, bis Robertino eine Nachricht bearbeitet hat. Für die Bearbeitung einer Nachricht benötigt Robertino z.B. für eine Geschwindigkeit von 50mm/s 31/50 s = 0,61s = 600 ms. Das heißt man darf ihn nicht mit Nachrichten überfluten, sondern muss diese bestimmte Latenz respektieren. Weil er beim Empfang einer Nachricht eine kurze Strecke fährt und stoppt, muss die nächste Nachricht am besten genau dann eintreffen, wenn Robertino stoppt. Analog zur Methode goDistance gibt es die Methode turn, die den Befehl [Turn 90] auf eine Folge von Nachrichten abbildet. 5.3 Ablaufsbeispiel Abbildung 5-4: Ablaufsbeispiel 28 In der Abbildung 5-4 steht Robertino am Anfang auf Position 1. Er muss die Teilstrecke bis Position 2 fahren. Nach der Durchführung der Berechnungen muss die Nachricht genau 3-mal geschickt werden, damit Robertino die gesamte Strecke fährt. Robertino reagiert auf die erste Nachricht und fährt einen Teil der Teilstrecke. Sobald er anhält, empfängt er die zweite Nachricht und fährt den zweiten Teil der Teilstrecke weiter. Die kleinen Pausen, die Robertino zwischen den Teilfahrten macht, kann der Benutzer nicht bemerken, weil ihre Dauer in Millisekundenbereich liegt. 29 6. Fehlererkennung und Korrekturverfahren In diesem Kapitel werden zwei Probleme behandelt. Zuerst: wie kann Robertino erkennen, dass es ein Hindernis auf dem Weg gibt? Zweitens: wie kann er eine mögliche Wegabweichung entdecken? Es werden auch Korrekturansätze für beide Problemsituationen vorgeschlagen. 6.1 Erkennung von Hindernissen Wie in Kapitel 2 erwähnt wurde, verfügt Robertino über mehrere Sensoren, die es ihm ermöglichen Distanzen zu den möglichen Hindernissen zu messen. Abbildung 6-1: Robertino entdeckt ein Hindernis Das Erkennungsverfahren hier ist ganz einfach. Die Abbildung oben zeigt ein Hindernis auf dem Weg von Robertino von Knoten 1 nach 4. Robertino hat zwei vordere Sensoren, mit denen er ständig die Distanzen zu den möglichen Hindernissen auf der Strecke berechnen kann. Stellen uns wir vor, dass es nur einen Sensor vorne gibt und nennen wir diesen Sensor S. Sei a der vom Sensor S zu einem bestimmten Zeitpunkt t gelieferte Wert. a ist demzufolge der Abstand zum Hindernis auf der Strecke (1,4). Während Robertino von 1 nach 4 fährt muss er parallel und nach einem konstanten Zeitabstand die Werte a abfragen. Sobald a kleiner als ein kritischer Abstand k (a < k) wird, ist ein Hindernis auf dem Weg entdeckt. Die Frage lautet jetzt: 30 wie kommt Robertino von Knoten 1 nach 4, ohne dass irgendjemand das Hindernis entfernt? 6.2 Korrekturverfahren bei Hindernissen Um das Korrekturverfahren besser zu verstehen, betrachten wir die Abbildung 6-2. Auf der Teilstrecke [3,4] befindet sich ein Hindernis und Robertino muss aber den Knoten 4 erreichten. Abbildung 6-2: Korrektur bei Hindernissen Robertino kann Knoten 4 erreichten, indem er einen geeigneten Umweg findet. Die folgenden Schritte soll er dazu durchgehen: 1. Er muss den Abstand x zum letzten erreichten Knoten (im Beispiel Knoten 1) berechnen. Das ist möglich wenn man eine Variable verwendet, die auf 0 gesetzt wird wenn der Roboter einen Knoten erreicht und immer aktualisiert wird, wenn Robertino die nächste Teilstrecke fährt. 2. Robertino muss den Abstand x bis zum Knoten 1 zurückfahren. 3. In Knoten 1 wird das Programm neu gestartet: Die Kosten der Kante mit dem Hindernis werden auf unendlich gesetzt. Der Dijkstra-Algorithmus wird erneut 31 ausgeführt, nun aber mit dem Knoten 1 als Startknoten. Der kürzeste Weg von Knoten 1 nach 4 ist jetzt der Pfad (1,2,3,4). 6.3 Erkennung von Abweichungen Wegen Mängel in der Mechanik, kann Robertino aus seinem Weg abweichen. Deswegen werden die Mauswerte des Moused-Dämons ständig kontrolliert, um eine mögliche Abweichung frühzeitig zu entdecken. Die Abbildung 6-3 zeigt uns ein Beispiel. Abbildung 6-3: Abweichung Die Punkte (x,y) sind die gelieferten Mauswerte. Robertino sollte die Teilstrecke [2-3] fahren, ist aber in die Richtung R gerutscht. Wie kann man also die Mauswerte (x,y) benutzen, um diese Abweichung zu entdecken. Die schmalen grauen Rechtecke in Abbildung 6-3 zeigen den Toleranzbereich der (x,y)-Werte. Die x-Werte sollen in dem Intervall [-d,d] liegen. Wenn sie diesen Bereicht verlassen, wird eine mögliche Abweichung entdeckt. Die y-Werte sollen bei der erwarteten gefahrenen Distanz liegen. Sie sollen deswegen in dem Intervall [ye-f,ye+f] liegen. ye ist die erwartete gefahrene Distanz und f ist eine Fehlertoleranzkonstante. Wenn sie diesen Bereicht verlassen, wird auch eine mögliche Abweichung entdeckt. 32 Man soll aber nicht sofort mit der Korrektur anfangen, wenn die Mauswerte zum ersten Mal den Toleranzbereich verlassen. Es wird erstmals abgewartet, bis eine Menge von fehlerhaften (x,y)-Werten vorliegt. Mit einem einzigen Wert hat man keine Chance, den Abweichungswinkel abzuschätzen. Wir speichern deswegen diese Menge fehlerhafter Messwerte in einer Tabelle und versuchen daraus den Abweichungswinkel abzuschätzen. Zuerst sollen wir aber die Anzahl der Elemente dieser Menge festlegen. Die Größe der Fehlertabelle entspricht dann der Elementenanzahl dieser Menge. Nach und nach wird die Tabelle mit den fehlerhaften Werten ausgefüllt. Erst wenn die Tabelle voll mit Messwerten ist, kann man davon ausgehen, dass eine Abweichung vorliegt. Das Hauptprogramm wird dann unterbrochen und das Korrekturverfahren wird gestartet. 6.4 Korrekturverfahren bei Abweichungen Diese Abweichungen und Positionen können nur mit einem statistischen Fehler gemessen werden. Das Verfahren der linearen Regression berechnet für die (x,y)-Messwerte eine Regressionsgerade, die dieses Rauschen ausgleicht und uns eine gute Abschätzung des Abweichungswinkels zur Verfügung stellt. 6.4.1 Lineare Regression Die Lineare Regression löst die folgende Standardaufgabe: Gegeben: Paare von Messdaten (xi,yi), i=1,…,n geometrisch eine Punktwolke in der Ebene. xi und yi können dabei durchaus mehrfach auftreten. Zu gegebenem xi können mehrere Messwerte yi1,…,yip vorliegen. Aufgabe: ein lineares Modell y = ß0 + ß1x an die Messdaten anzupassen, also eine beste gerade durch die Punktwolke zu legen. Wie bildet man hier das Modell des Regressionsansatzes? Das gesuchte Modell für den Zusammenhang zwischen x und y ist das lineare Modell y = ß0 + ß1x mit unbekannten Koeffizienten ß0 und ß1. Die vorliegenden Messdaten liegen jedoch nicht exakt auf der entsprechenden Gerade, sondern zeigen Abweichungen εi, i = 1,..,n (Abbildung 6-4): yi = ß0 + ß1xi + εi 33 Um aus den vorliegenden Daten Schätzwerte ß’0, ß’1 für ß0, ß1 zu gewinnen, minimieren wir die Summe der Fehlerquadrate: sodass also ß’0, ß’1 Lösung des Minimierungsproblems : ist. Wir wollen hier aber nicht detailliert vorführen, wie man die Schätzwerte ß’0, ß’1 erhält. Der ausführliche Berechnungsvorgang gibt es in der Literatur [Li00]. Das Ergebnis der Regression ist demzufolge die geschätzte Regressionsgerade y = ß’0 + ß’1x . Die durch das Modell prognostizierten Werte sind dann y’i = ß’0 + ß’1xi. Abbildung 6-4: Abweichung εi [Li00] 34 6.4.2 Korrekturverfahren Abbildung 6-5 : Korrekturverfahren bei Abweichungen Die Abbildung 6-5 stellt eine mögliche Abweichungssituation dar. Auf der Teilstrecke [1,2] weicht Robertino ab und bewegt sich in die Richtung R. Nach der Erkennung einer Abweichung befindet sich der Roboter auf der dem Punkt (x,y). Das Korrekturverfahren besteht in diesem Fall aus den folgenden Schritten: 1. Die Regressionsgerade R aus der in Kapitel 6.3 beschriebenen Fehlertabelle bestimmen. 2. Ermittlung des Winkels α: (x,y) aus der Fehlertabelle ermitteln (letzter Eintrag) b = z - y ( z = Länge der Teilstrecke [1,2] ) δ = cotan(b/x) φ = cotan(k) ( k = Koeffizient der Regressionsgerade ) α = 180 - δ - φ 35 3. Ermittlung des Abstands d d2 = b2 + x2 4. Ermittlung des Winkels β β = 90 - δ 5. Zum Knoten 2 fahren : Turn[α] & Go[d] & Turn[β] 6. In Knoten 2 Dijkstra-Algorithmus mit dem Startknoten 2 ausführen. 7. Zum Zielknoten weiterfahren. Die vollständige Implementierung dieses Verfahrens ist im Anhang B zu finden. 36 7. Architektur und Zusammenspiel der in Java implementierten Komponenten 7.1 Einführung in UML (Unified Modelling Language) UML ist eine Sprache und Notation zur Spezifikation, Konstruktion, Visualisierung und Dokumentation von Modellen für Softwaresysteme. Diese Sprache bietet zahlreiche interessante Modellierungsdiagramme an. Um die im Rahmen dieser Studienarbeit entwickelte Software zu beschreiben, habe ich mich für Klassendiagramme, Aktivitätsdiagramme und Sequenzdiagramme entschieden. 7.1.1 UML-Klassendiagramm Die Abbildung 7-1 zeigt ein UML-Klassendiagramm. Ein Klasse im Klassendiagramm besteht aus drei Blöcken. Der Block klasse enthält den Klassennamen (z.B. Klassenbezeichner einer Java-Klasse). Unter Attribute werden die Attribute der Klasse aufgelistet. Der untere Block Operationen enthält eine Liste der Klassenmethoden. Syntax für Attribute: Sichtbarkeit Attributname Syntax für Operationen: Sichtbarkeit Operationsname (Parameterliste): Rückgabewert Sichtbarkeit: + (public) - (private) # (protected) Die Pfeile in der Abbildung 7-1 modellieren mögliche Relationen zwischen den Klassen. Eine Klasse kann z.B. aus einer anderen klasse abgeleitet werden oder ein Interface implementieren. Abbildung 7-1: UML-Klassendiagramm 37 7.1.2 UML-Sequenzdiagramme Man verwendet Klassendiagramme, um statische Informationen darzustellen. Jedoch interagieren die Objekte in einem funktionierenden System miteinander. Diese Interaktionen ziehen sich über einen Zeitraum hin. Das UML-Sequenzdiagramm zeigt die zeitliche Dynamik der Interaktion. Abbildung 7-2: UML-Sequenzdiagramm Die Abbildung 7-2 ist ein UML-Sequenzdiagramm. Die Objekte werden in einem Sequenzdiagramm oben von links nach rechts angeordnet. Von jedem Objekt geht eine gestrichelte Linie nach unten, die man als Lebenslinie des Objektes bezeichnet. An der Lebenslinie befindet sich ein schmales Rechteck, die so genannte Aktivierung. Die Aktivierung repräsentiert die Ausführung einer Operation durch das Objekt. Ein Pfeil verbindet eine Lebenslinie mit einer anderen und modelliert eine Nachricht, die ein Objekt einem anderen sendet. Die Zeitdauer beginnt oben und schreitet nach unten fort. Eine kompakte und einfache Beschreibung der Sequenzdiagramme gibt es in [Js00]. 7.1.3 UML-Aktivitätsdiagramme Die UML-Aktivitätsdiagramme eignen sich zur Modellierung von Abläufen aller Art, unabhängig vom Anwendungsbereich. Sie werden beispielsweise zur Modellierung von Programmabläufen, technischen Prozessabläufen oder Arbeitsabläufen verwendet. Ein Rechteck mit gerundeten Ecken stellt eine Aktivität dar. Sobald die Verarbeitung innerhalb einer Aktivität beendet ist, geht sie auf die nächste Aktivität über. Der 38 Übergang von einer Aktivität zur nächsten wird durch einen Pfeil repräsentiert. Das Aktivitätsdiagramm besitzt einen Anfangspunkt in Form eines schwarz gefüllten Kreises und einen Endpunkt in Form eines Ochsenauges. Ein Entscheidungsknoten wird durch eine Raute dargestellt und stellt eine Verzweigung dar. Eine Verzweigung hat einen Eingang und zwei oder mehrere Ausgänge. Jeder Ausgang wird mit einer Bedingung versehen. Trifft eine Bedingung zu, wird der entsprechende Ausgang ausgewählt und an diesem weitergearbeitet. Zur Darstellung von Nebenläufigen Pfaden werden Parallelisierungsknoten eingesetzt. Ein Parallelisierungsknoten ist ein Balken mit einem Eingang und zwei oder mehreren Ausgängen dargestellt und dient zum Aufspalten eines Ablaufs in zwei oder mehrere parallele Abläufe. (Abbildung 7-3) Um die parallelen Abläufe zusammenzuführen werden Synchronisationsknoten eingesetzt. Synchronisationsknoten sind Balken mit mehreren Eingängen und einem Ausgang. An diesem Knoten findet eine Synchronisation statt, d.h., es wird gewartet, bis alle eingehenden Abläufe eintreffen, dann geht der Ablauf an diesem Punkt weiter. Abbildung 7-3 7.2 Überblick auf die Klassenarchitektur Die Abbildung 7-4 zeigt ein UML-Klassendiagramm. Dieses Diagramm bietet eine Darstellung der verschiedenen Klassen an, die im Rahmen dieser Studienarbeit implementiert worden sind. Die wichtigsten Klassen werden in den nächsten Abschnitten genauer untersucht. 39 Abbildung 7.4: Klassendiagramm (Teil 1) 40 Abbildung 7.4: Klassendiagramm (Teil 2) 7.3 Die Klasse Plan Diese Klasse modelliert einen beliebigen Plan als Graph mit Knoten und Kanten. In einem 2-dimentionalen Feld int[ ][ ] costMatrix werden die Abstände zwischen den verschiedenen Knoten gespeichert. Die Größe dieses Felds ist gleich der Anzahl der Knoten im Graph. Ein Eintrag in diesem Feld z.B. costMatrix [1][2] = 5 entspricht dem 41 Abstand zwischen den Knoten 1 und 2. Wenn es zwischen zwei bestimmten Knoten keine Kante existiert, z.B. zwischen den Knoten 1 und 4, wird der Abstand zwischen diesen Knoten auf unendlich gesetzt (z.B. costMatrix [1][4] = INFINIT und INFINIT sehr groß). Zur Darstellung der Winkel zwischen den Kanten verfügt die Klasse Plan über ein 3- dimentionales Feld int [ ][ ][ ] angles. Ein Eintrag in diesem Feld z.B. angles [1][2][3] = 90 entspricht dem Winkel zwischen den Kanten [1,2] und [2,3]. Die wichtigste Methode der Klasse Plan ist die Methode int[ ] dij (int from). Diese Methode führt den Dijkstra-Algorithmus aus und liefert ein Feld zurück, der jedem Knoten einen Vorgänger zuweist. Nennen wir dieses Feld Priorfeld. Dieses enthält implizit die notwendigen Informationen, um die kürzesten Pfade vom Knoten from zu allen anderen Knoten zu ermitteln. Die Methode int [ ] getNodes (int from, int to) extrahiert aus dem Feld Priorfeld die Folge der Knoten auf dem kürzestem weg vom Knoten „from“ zum Knoten „to“ und speichert diese Folge in einem Feld. Nennen wir dieses Feld path. Die Methode void getDistances (int[ ] path) zerlegt das Feld path in Teilstrecken (Kanten), greift auf costMatrix zu, um die Abstände (die Kosten der Kanten) zu ermitteln. Diese Abstände werden nach der Erscheinungsreihenfolge der zugehörigen Kanten auf path in einem neuen Feld gespeichert. Nennen wir dieses Feld dist. Die Methode int [ ] getAngles (int [ ] path) verwendet die Knoten im Feld path und die Einträge im 3-D-Feld angles um die Folge der notwendigen Winkel auf dem Weg zum Ziel zu ermitteln. Diese Folge wird auch in einem Feld gespeichert. Nennen wir dieses Feld angle. Aus den zwei oben genanten Feldern dist und angle erzeugt die Methode String makePath (int [ ] dist, int [ ] angle) eine Zeichenkette, die die notwendigen Roboterbewegungen zum Zielknoten zusammenfasst. Ein Beispiel für diese Zeichenketten ist GO[50] & TURN[90] & GO[100]. 7.4 Die Klasse Show_the_way Diese Klasse ist die wichtigste Klasse der Anwendung. Sie enthält die main-Methode und verfügt über weitere Methoden zur Roboterteuerung. Diese Klasse verfügt auch über Klassenvariablen, die den Roboterzustand beschreiben. Die Methode void setSpeed (int sp) setzt die Geschwindigkeit des Roboters auf sp mm/s und die Methode void setOmega (int omg) setzt die Winkelgeschwindigkeit auf omg mdeg/s. 42 Wenn der Roboter sich in einer Position (x) auf der Teilstrecke zwischen zwei verschiedenen Endknoten (a) und (b) befindet, kann man mit den Methoden int getLastNode() bzw. int getNextNode() ermitteln, welcher Knoten zuletzt besucht wurde (a) bzw. welcher Knoten demnächst erreicht (b) wird. Die Methode int getdrivedDistance() liefert in diesem Fall die Länge der Gefahrenen Strecke vom zuletzt besuchten Knoten (a) zur aktuellen Position des Roboter auf der Teilstrecke (x). Die wichtigste Methode ist aber die Methode void drive (int start, int goal, Plan plan). Anhang A zeigt die komplette Implementierung dieser Methode. Diese Methode sorgt dafür, dass der Roboter von einem beliebigen Startknoten start den Zielknoten goal erreicht. Das erfolgt natürlich unter Verwendung von Informationen der Instanz plan der Klasse Plan. Die Realisierung der einzelnen Teilaufgaben der drive-Methode erfolgt mit den Methoden void goDistance (int sp , int distance), void turn (int omega, int angle), void send (int loops). Diese drei genanten Methoden und weitere Hilfsmethoden wurden schon in einem früheren Kapitel genauer untersucht (siehe 5.2). Bei Fehlersituationen werden die Methoden void setObstacle (boolean value) bzw. void setDeviation (boolean value) verwendet um den Roboter in den Zustand „Hindernis erkannt = true“ bzw. „Abweichung = true“ zu versetzen. 7.5 Die Klassen Client, DriveDClient und MouseDClient Die Klasse Client ist ein einfacher TCP-Client mit einem TCP-Socket und zwei Ströme DataInputStream und DataOutputStream. Die Methode boolean connect (String hostname, int port , int timeout ) baut eine TCPVerbindung zum Host hostname zur Portnummer port auf. Die Methode void disconnect() baut diese Verbindung nach der Nachrichtenübertragung ab. Aus der Klasse Client werden die zwei Klassen DriveDClient und MouseDClient abgeleitet. Beide Klassen besitzen einen Input-Thread, der für die eintreffenden Nachrichten zuständig ist. Dieser Thread wird mit dem Aufruf der Methode void receive() gestartet. Der Input-Thread der Klasse DriveDClient z.B. empfängt die Nachrichten mit den Sensorwerten, die der DriveDServer sendet. Dieser Input-Thread ist vom Typ ClientInputThread. Diese Klasse wird im kommenden Abschnitt untersucht. 43 7.6 Die Klassen ClientInputThread und MouseClientInputThread Die Klasse ClientInputThread ist ein Thread zum Empfang der Nachrichten des DriveDServers und die Extraktion der relevanten Werte. Wenn die Methode void run() gestartet wird liest sie die Nachricht aus dem DataInputStream, speichert die Nachricht in einer Variable received und ruft die Methode boolean extract (String received). Die extract-Methode kann die für diese Studienarbeit relevanten Daten aus einer Nachricht der Form 99##ADC00(9),ADC01(5),...,va2(5)# extrahieren. Relevant sind in diesem Fall die Sensorwerte ADC12 und ADC50 auf der vorderen Seite des Roboters. Nach der Extraktion kann man mit den Methoden int get_adc12() und int get_adc50() beide Sensorwerte abfragen. Die Klasse MouseClientInputThread ist der Klasse ClientInputThread sehr ähnlich. Anstatt Sensorwerte zu empfangen, empfängt sie Mauswerte aus dem MouseDServer. 7.8 Die Klasse Controller Diese Klasse wird aus der Klasse Thread abgeleitet. Sie versucht eine fehlerfreie Fahrt des Roboters zu gewährleisten. Ihre Methode void run() enthält eine endlose Schleife. Jede von diesen Schleifen besteht aus zwei verschieden Phasen. Der ControllerThread schläft während der ersten Phase und ist nicht aktiv. Diese Schlafphase ist periodisch und dauert jedes Mal controll_frequency Millisekunden. Der ControllerThread ist danach wieder aktiv und die Kontrollphase beginnt. Die Kontrollphase besteht aus drei Methodenaufrufen. Mit der Methode void collectValues() werden alle Kontrolldaten zum gleichen Zeitpunkt abgefragt. Diese Kontrolldaten sind die Sensorwerte ADC12 und ADC50 aus dem ClientInputThread und die Mauswerte X und Y aus dem MouseClientInputThread. Die Methode boolean sensorValues_OK() überprüft, ob einer der Sensorwerte unter dem kritischen Abstand critical_distance liegt. Wenn das der Fall ist, wird ein Hindernis entdeckt. Dann überprüft die Methode boolean mouseValues_OK(), ob die Mauswerte in einem Toleranzbereich liegen oder nicht. Wenn mehrere aufeinander folgende Mauswerte (X,Y) den Toleranzbereich nicht respektieren, werden sie in dem Feld int mouseCriticalValues [ ][ ] gespeichert. Dieses Feld hat eine begrenzte Größe. Sobald dieses Feld voll mit fehlerhaften (X,Y) Einträge ist, hat man eine potenzielle Abweichung entdeckt. 44 7.9 Die Klasse ObtacleDetected Diese Klasse implementiert das Interface ErrorClass. Die Methode void correct (Show_the_way way, Plan plan, int goal) dieser Klasse wird verwendet, wenn ein Hindernis entdeckt wird. Sie implementiert das in Kapitel 6.2 beschriebene Korrekturverfahren. Die Methode void informTheVisitor() informiert den Benutzer, dass ein Hindernis entdeckt wurde und, dass ein Umweg gesucht wird. 7.10 Die Klassen Deviation und Regression Diese Klasse implementiert auch das Interface ErrorClass. Die Methode void correct (Show_the_way way, Plan plan, int goal) wird hier aber genutzt, wenn eine Abweichung entdeckt wird. Sie implementiert das in Kapitel 6.4.2 vorgestellte Korrekturverfahren. Im Anhang B findet man die Implementierung dieser Methode. Die Methode void informTheVisitor() informiert den Benutzer, dass eine Abweichung entdeckt wurde und, dass der Weg Korrigiert wird. Bei der Korrektur wird aber die Regressionsgerade gesucht. Die statische Methode double[ ] linear_equation(double rawData[ ][ ], int norder) der Klasse Regression berechnet aus den in rawData gespeicherten Messwerten die geschätzte Regressionsgerade. 45 7.11 Zusammenspiel der Komponenten Abbildung 7-5: Zusammenspiel der Komponenten 46 Wir haben die verschiedenen Systemkomponenten getrennt beobachtet und ihre besonderen Funktionalitäten spezifiziert. Doch wie kooperieren diese Komponenten miteinander, um die globale Funktionalität zu realisieren. In diesem Kapitel wird anhand zahlreicher Abbildungen das Zusammenspiel der Klassen deutlich gemacht. Die Abbildung 7-5 ist ein einfaches Schema zur Darstellung des gesamten Systems. Die Steuerung des Roboters erfolgt auf einem externen Rechner (Notebook). Die Realisierung der Aufgaben findet aber auf der Seite des Roboters statt. Die verschiedenen Clients und Servern kommunizieren über ein W-LAN. Die Abbildung 7-6 ist ein UML-Sequenzdiagramm. Wir können mit diesem Sequenzdiagramm ein Ablaufbeispiel genauer untersuchen, um das Zusammenspiel der im Klassendiagramm dargestellten Java-Klassen besser zu verstehen. Um den Überblick zu behalten, wurde das Sequenzdiagramm in fünf Teilen zerlegt. Jeder Teil ist mit einer Buschstabe versehen. Wir erklären hier kurz was bei jedem Teil passiert. Teil A: Der Benutzer startet das Programm und gibt sein Wunschziel an. Show_the_way baut TCP-Verbindungen zu den verschiedenen Servern auf und startet Controller. Teil B: Show_the_way holt sich die nötigen Abstände und Winkel aus Plan und ruft seine drive-Methode auf. Teil C: Show_the_way schickt DriveDServer Nachrichten zu und DriveDServer antwortet mit Sensorwerten. MouseDServer fängt auch an Mauswerte zu senden. Robertino beginnt seine Fahrt. Teil D: Controller sammelt aus den Clients DriveDClient und MouseDClient Mauswerte und Sensorwerte. Teil E: Controller entdeckt ein Hindernis und informiert Show_the_way. Show_the_way ruft die correct-Methode von ObsatcleDetected auf. ObstacleDetected führt die Korrektur durch und ruft die drive-Methode von Show_the_way rekursiv auf. Schließlich übernimmt Show_the_way erneut die Robotersteuerung. 47 Abbildung 7-6 : Sequenzdiagramm (Teil 1) 48 Abbildung 7-6 : Sequenzdiagramm (Teil 2) 49 Am Ende fassen wir die verschiedenen Aktivitäten in einem Aktivitätsdiagramm zusammen (Abbildung 7-7). Abbildung 7-7: Aktivitätsdiagramm 50 8. Tests und Ergebnisse Test 1: Robertino soll von einem Startknoten zu einem Zielknoten fahren. Er soll den Dijkstra-Algorithmus ausführen und den kürzesten weg zum Ziel auswählen. Dieser Test wurde nicht simuliert sondern tatsächlich mit Robertino durchgeführt. Betroffene Klassen: Client ClientInputThread DriveDClient Plan Show_the_way Ergebnisse: Robertino wählt immer den richtigen kürzesten Weg zum Zielknoten. Mit einigen Präzisionsfehlern erreicht er sein Ziel. Probleme: Die Räder des Roboters lassen sich nicht präziser steuern. Der Roboter hält meistens vor seinem Ziel an. Hardwaremängel: Die Akkus des Roboters sind leider nicht mehr funktionsfähig. Test 2: Robertino soll ein Hindernis entdecken und seinen Weg korrigieren. Dieser Test wird nur mit einem Dummyserver simuliert, der zufällige Sensorwerte liefert. Betroffene Klassen: Client ClientInputThread DriveDClient Plan Show_the_way Controller ObstacleDetected DrivedDummyServer Ergebnisse: Robertino reagiert auf virtuelle Hindernisse, berechnet korrekte Umwege und gibt die Bearbeitungsschritte am Bildschirm aus. Probleme: Es wäre angemessener für diese Anwendung, dass es einen Sensor genau an der vorderen Seite des Roboters gibt. Die Position der vorderen Sensoren links und rechts ist nicht optimal. Test 3: Robertino soll Abweichungen erkennen. Das Verfahren wurde komplett implementiert. An einem MouseDServer arbeitet aber eine Forschungsgruppe. Betroffene Klassen: Client ClientInputThread DriveDlient Plan Show_the_way Controller Deviation Ergebnisse: Keine Ergebnisse. Der Test wurde nicht durchgeführt. Bei der Implementierung ginge ich davon aus, dass der MouseDServer während der Fahrt (x,y)-Mousewerte liefert. Probleme: Hardwaremängel. Der Roboter, der zur Verfügung stand, besitzt keine eingebaute Maus. 51 9. Zusammenfassung / Ausblick Zum Abschluss können wir die neuen Fähigkeiten von Robertino zusammenfassen. Robertino kann jetzt selbstständig den Weg zu einem bestimmten Raum fahren. Er muss zuerst den kürzesten Weg berechnen. Die Modellierung eines Plans erfolgt mit Graphen. Um den kürzesten Weg zu berechnen, wird der Dijkstra-Algorihtmus eingesetzt. Robertino ist auch fähig, Hindernisse zu entdecken. Wenn er ein Hindernis entdeckt, berechnet er automatisch einen gültigen Umweg und setzt seine Fahrt auf dem neuen Weg fort. Seine Sensorwerte werden zu diesem Zweck ständig kontrolliert und auf die Präsenz möglicher Hindernisse überprüft. Die Umwegberechnung erfolgt wieder mit dem Dijkstra-Algorithmus. Eine weitere Fähigkeit von Robertino ist die Erkennung von Abweichungen. Wenn Robertino aus irgendeinem Grund rutscht und die Fahrt in die falsche Richtung fortgesetzt wird, dann kann er diese Fehlersituation selbstständig erkennen. Die Werte einer unten am Roboter eingebauten Maus werden in diesem Fall kontrolliert und auf Unzulässigkeit geprüft. Anhand der Mausmesswerte, ermöglicht uns das Verfahren der Linearen Regression eine Abschätzung des Abweichungswinkels zu gewinnen. Diese erreichten Fähigkeiten sind Grundfähigkeiten, die man natürlich erweitern könnte. Er wäre zum Beispiel schön die spontan zu Sprechen, um mit Robertino zu kommunizieren. „Ich will zu Herrn Müller“, „Zeig mir den Raum 295“ sind zwei mögliche spontane Befehle, die Robertino verstehen und ausführen sollte. Es wäre auch schön, wenn Robertino sprechen würde und den Benutzer begrüßen oder über mögliche Probleme informiert. Hier könnte man Werkzeuge aus dem Gebiet Musterkennung, wie Sprachsynthese und Spracherkennung einsetzen. Robertino muss in der Lage sein solche spontane Befehle in Texte umzuwandeln. Danach muss er aus diesem Text das vom Benutzer verlangte Ziel extrahieren. Dann folgt die Abbildung des Ziels auf einen Knoten des in Robertino gespeicherten Graphs. Robertino kann sogar seine Kamera verwenden um sich besser zu orientieren oder um Objekte des Umfeldes zu klassifizieren. Nach der Analyse der Kamerabilder wird zum Beispiel entschieden, ob es eine Person im Aufzug gibt. Bei der Erkennung von Türen, Hindernissen... könnte die Kamera könnte auch helfen. Ein Teilgebiet der Mustererkennung widmet sich der Bilderkennung und bietet verschiedene Bildklassifikatoren an. Wenn die Einsatzfläche von Robertino ziemlich groß wird (z.B. Flughafen) können wir sogar mehrere Instanzen von Robertino verwenden, die nur Teilflächen als Einsatzgebiet haben. Diese Agenten können von einem zentralen Koordinator 52 gesteuert werden, um eine globale Aufgabe zu erfüllen. Auf seinem Weg zu einem bestimmten Ziel kann ein Benutzer von mehreren Robotern begleitet werden. Jeder Roboter begleitet aber nur entlang der Teilstrecke die zu seinem Einsatzgebiet gehört. Dann übernimmt ein anderer Roboter die Begleitung. Die Intelligenz dieser Roboter hat unser Leben enorm erleichtert. Leider wird diese Intelligenz aber häufig missbraucht und in Kriegen eingesetzt. Wir lassen uns aber von diesem Missbrauch nicht aufhalten. Die Forschung geht weiter und die Roboter werden noch intelligenter. 53 10. Literatur [Gk04] Handbuch der Java-Programmierung, Guido Krüger (4-Auflage AddisonWesley, 2004) [Jk02] Computernetze, James F. Kurose , Keith W.Ross (Addison-Wesley,2002) [Go00] Handbuch der Künstlichen Intelligenz , G. Görz, G.-R. Rollinger, J. Schneeberger (3-Auflage Oldenbourg, 2000) [Kla00] Lineare und Netzwerk-Optimierung, Horst. W. Hamacher , Kathrin Klamroth (Vieweg,2000) [Js00] Jetzt lerne ich UML, Joseph Schmuller (Markt + Technik, 2000) [Bo06] Analyse und Design mit UML 2.1, Bernd Oestereich (8-Auflage Oldenburg, 2006) [Hi96] Landmark-Based Autonomous Navigation in Sewerage Pipes, J. Herzberg und F. Kirchner, 1996 [La91] Robot Motion Planning, J. Latombe, 1991 [Kh86] Real-time obstacle avoidance for robot manipulator an mobile Robots. The interanational Journal of Robotic research, 1986. [Li00] http://www.mathe-online.at/nml/materialien/innsbruck/regression/ Regression.pdf [Li01] http://www.angelfire.com/tx4/cus/regress/java.html [Li02] www.openrobertino.org [Li03] http://www.cs.cmu.edu/afs/cs.cmu.edu/Web/People/Xavier/ [Li04] http://www.deutsches-museumbonn.de/highlights/100jahremuseum/default.html [Li05] http://idw-online.de/pages/de/image13460 [Li06] http://www.cs.cmu.edu/~minerva/ [Li07] http://www.golem.de/0510/40897.html 54 Anhang A public void drive( int start, int goal, Plan plan ) { setObstacle(false); setDeviation(false); int[] path = plan.getNodes(start,goal); plan.getNodes(start,goal); System.out.println("\n" + "nodes to visit.."); for (int i = 0 ; i < path.length ; i++) { System.out.print(path[i] + "-"); } System.out.print("OK!"); int[] dist = plan.getDistances(path); int[] ang = plan.getAngles(path); String way = plan.makePath(dist,ang); System.out.println("\n" + "route calculated..\n" + way + "\n"); try { for (int i = 0 ; i < ang.length ; i++) { setLastNode(path[i]); setNextNode(path[i+1]); goDistance(50,dist[i]); turn(5000,ang[i]); System.out.println("Die Strecke " + path[i] + " ------> " + path[i+1] + " + Drehung erfolgreich! "); } goDistance(50,dist[dist.length-1]); System.out.println("Die Strecke " + path[path.length-2] + " ------> " + path[path.length-1] + " erfolgreich gefahren! "); setNextNode(path[path.length-1]); } catch (Exception e) { if (obstacle == true) { ObstacleDetected obstacle = new ObstacleDetected(controller); obstacle.informTheVisitor(); try { obstacle.correct(this, plan, goal); } catch (Exception e1) { e1.printStackTrace(); } } if (deviation == true) { Deviation deviation = new Deviation(controller); deviation.informTheVisitor(); try { deviation.correct(this, plan, goal); } catch (Exception e1) { e1.printStackTrace(); } } } } 55 Anhang B public void correct(Show_the_way way, Plan plan, int goal) throws Exception { double[][] criticalValues = controller.getMouseCriticalValues(); double[] coef = Regression.linear_equation(criticalValues,1); int from = way.getLastNode(); int to = way.getNextNode(); double k = coef[1]; double fi = Math.atan(k); double fromto = plan.getDist(from,to); double y = criticalValues[4][0]; double x = criticalValues[4][1]; int sign; if (x < 0) sign = -1; else sign = 1; double b = fromto - y; double delta = Math.atan(b/x); double alfa = 180 - (delta + fi); int alfa_r = (int) Math.round(alfa); double d = Math.sqrt(b*b + x*x); int d_r = (int) Math.round(d); double beta = 90 - delta; int beta_r = (int) Math.round(beta); way.turn(5000,sign*alfa_r); way.goDistance(50,d_r); way.turn(5000,sign*beta_r); way.drive(to,goal,plan); } Anlage CD-ROM mit Quellcode. 56