UNIVERSITA T OSNABRU CK Fachbereich Mathematik/Informatik Diplomstudiengang Mathematik, Vertiefungsfach Informatik Diplomarbeit Objekte, Threads und Events fu r Roboter { Ein Toolkit zur hardware-unabh angigen Robotersteuerung in Java { Betreuer: Verfasser: Prof. Dr. AXEL T. SCHREINER STEPHAN JATZOLD Vorgelegt im Februar 2002 iii Danksagungen Ich danke Herrn Prof. Dr. Axel-Tobias Schreiner fur seine Unterstutzung und die Betreung dieser Diplomarbeit. Seine Bereitschaft neue Inhalte und Ideen in die Lehre an der Universitat zu bringen ist bemerkenswert. Fur die nanzielle Unterstutzung bei zwei Forschungsaufenthalten in den USA, am Rochester Institute of Technology, im Zusammenhang mit dieser Arbeit, bin ich der Universitatsgesellschaft Osnabruck und dem Fachbereich Mathematik/Informatik der Universitat Osnabruck zu Dank verpichtet. Ganz besonderer Dank gilt meinen Eltern, die mir fur meine Ausbildung jede mogliche Freiheit lassen und dabei groe Geduld beweisen. Ich hoe ich kann ihnen vermitteln, da es sich lohnt. Fur die Durchsicht des Textes und inhaltliche Diskussionen danke ich Bernd Kuhl, Eva Ebenhoh und Helga Jatzold. Eva Ebenhoh danke ich fur ihre Geduld und ihre Unterstutzung, sie versteht mich wie sonst niemand. Hilfsmittel Fur die Erstellung dieser Arbeit wurden die folgenden Programme verwendet: nedit zum editieren der Texte LATEX in Verbindung mit einer Menge Erweiterungen zum Setzen dieser Arbeit xg zur Erstellung der Graken linux und viele der dort u blicherweise installierten Tools wie make, convert usw. jikes als hauptsachlich verwendeter Java-Compiler verschiedene Java Development Kits zur Java-Entwicklung verschiedene Programme und Gerate zur Erstellung und Bearbeitung der Fotos Erklarung Hiermit erklare ich, diese Diplomarbeit selbstandig verfasst und keine anderen als die angegebenen Quellen und Hilfsmittel verwendet zu haben. Stephan Jatzold iv Inhaltsverzeichnis 1 Einleitung 1.1 1.2 1.3 1.4 Vorgeschichte . . Begrisklarungen Zielgruppe . . . . Inhaltsubersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 Grasche Programmiersysteme . . . . . 3.1.1 LLWin . . . . . . . . . . . . . . . 3.1.2 RCX-Code . . . . . . . . . . . . 3.1.3 Robolab . . . . . . . . . . . . . . 3.2 Textbasierte Programmiersysteme . . . 3.2.1 Fernsteuerungen . . . . . . . . . 3.2.2 Sprachen fur die Lego-Firmware 3.2.3 Systeme mit eigener Firmware . 3.2.4 leJOS . . . . . . . . . . . . . . . 3.3 Vorlauges Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Ariadne { a Li(ght)Se(arching) Trusty 2.1 2.2 2.3 2.4 Trusty . . . . . . . . . . . . . . LiSe . . . . . . . . . . . . . . . Ariadne . . . . . . . . . . . . . Hervorzuhebende Eigenschaften . . . . . . . . . . . . . . . . 3 Programmiersysteme fur Roboter 4 Die Idee des Abstract Robot Toolkit 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 Tutebot . . . . . . . . . . . . . . . . . . Fischertechnik und Lego Mindstorms . . Java . . . . . . . . . . . . . . . . . . . . Design-Pattern und das AWT . . . . . . Das Abstract Robot Toolkit . . . . . . . Die Sensor-Implementierungen in ART Model, View, Controller . . . . . . . . . Subsumption-Architektur . . . . . . . . v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 3 4 5 7 7 8 9 9 13 13 14 19 20 22 22 24 25 27 32 35 36 39 40 41 43 50 56 57 INHALTSVERZEICHNIS vi 5 Ariadne & ART - Ein Tutorial 5.1 5.2 5.3 5.4 5.5 "Hello, Robot!" . . . . . . . . . . . . . . . . . . . . . . . . . . . DriveTrain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Und wie benutzt man das nun? . . . . . . . . . . . . . . . . . . Trusty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kongurierte Sensoren . . . . . . . . . . . . . . . . . . . . . . . 5.5.1 SubsumptionTrustyMSDemoRobot . . . . . . . . . . . . . 5.5.2 Der Lego-Rotationssensor . . . . . . . . . . . . . . . . . 5.5.3 Rotationsmessung mit den Fischertechnik-Impulsradern 5.6 LiSe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.7 Ariadne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Implementierung eines RobotInterface 6.1 6.2 6.3 6.4 6.5 Ein virtuelles RobotInterface . . . . . . . . . . . Die Peers . . . . . . . . . . . . . . . . . . . . . . . Die Ports . . . . . . . . . . . . . . . . . . . . . . . Die Methoden von RobotInterface . . . . . . . . Die Einbindung in ART: RobotInterfaceFactory 7 Zusammenfassung 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10 Ergebnis . . . . . Geschwindigkeit . Flexibilitat . . . Architektur . . . Sicherheit . . . . Andere Hardware Subsumption . . JavaBeans . . . . XML . . . . . . . Persistenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 59 62 73 77 86 86 89 91 94 101 107 107 110 114 117 120 125 125 125 126 127 129 130 130 131 132 133 A Installation des Abstract Robot Toolkit 135 B Troubleshooting 139 Abbildungsverzeichnis 141 Literaturverzeichnis 143 A.1 Installation der Beispiele . . . . . . . . . . . . . . . . . . . . . . . 136 A.2 Klassendokumentation . . . . . . . . . . . . . . . . . . . . . . . . 137 B.1 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 B.2 Geschwindigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 B.3 Automatische Erkennung . . . . . . . . . . . . . . . . . . . . . . 140 1 Einleitung Moderne Programmiersprachen, wie z.B. Java, stellen inzwischen fur viele Bereiche ein recht weit entwickeltes API (Application Program Interface) zur Verfugung. Fur die Programmierung von Robotern scheint es aber bisher zumindest kein frei verfugbares API zu geben, das einen objektorientierten, von der konkreten Hardware abstrahierenden Zugang zu Robotern bereitstellt. Fur die Ansteuerung von Robotern aus Java heraus ist man in vielen Fallen auf die Beschaftigung mit wenig intuitiven Byte-Stromen angewiesen. Die vorliegende Arbeit will diese Lucke fullen. Es wird eine Schnittstelle entwickelt, die eine objektorientierte und event-basierte Sicht auf die Komponenten eines Roboters ermoglicht. Unter Verwendung dieser Schnittstelle kann man Algorithmen zur Robotersteuerung entwickeln, die weitgehend wiederverwendbar sind { nicht nur fur den gleichen Roboter, sondern fur verschiedene Roboter, von moglicherweise sogar unterschiedlicher Hardware-Architektur. Wo immer es moglich ist, werden die gleichen Klassen fur unterschiedliche Roboter-Hardware verwendet, womit eine weitgehende Unabhangigkeit von der verwendeten Plattform erreicht wird. Implementiert wurde diese Schnittstelle exemplarisch fur Roboter-Hardware von Lego und Fischertechnik; ein Beispiel fur eine Implementierung fur weitere Plattformen wird gegeben. 1.1 Vorgeschichte Lego Mindstorms kam Ende 1998 in den USA auf den Markt und ein Jahr spater auch in Deutschland. Seitdem hat sich eine groe Fan-Gemeinde entwickelt und im Internet organisiert. Die von Lego zur Verfugung gestellten Moglichkeiten zur Programmierung des Brick, wie der groe gelbe Legostein mit Batterien und einem Computer darin haug genannt wird1, waren damals die grasche Programmierumgebung RCX-Code und das ActiveX-Control Spirit.ocx. 1 In diesem Dokument soll mit Brick die Hardware des Computer-Bausteins im Lego Mindstorms Robotics-Invention-System (RIS) bezeichnet werden. Der Begri RCX (so wird der Brick auch manchmal genannt) soll hier hingegen die Original-Firmware, das von Lego ausgelieferte Betriebssystem des Brick bezeichnen. Es existieren verschiedene RCX-Versionen fur den Brick und weitere Lego-Produkte wie den Cybermaster und den Scout. 1 2 1. EINLEITUNG Beide Systeme laufen auch heute nur unter Windows und vor allem RCXCode ist auerdem fur Menschen ohne Programmiererfahrung konzipiert worden, aber: Der Brick kann sein Betriebssystem uber Infrarot laden, es ist nicht auf dem Chip eingebrannt. Vielleicht war es aber auch schlicht der Drang, wissen zu wollen, wie "Es" funktioniert, auf jeden Fall knackte Kekoa Proudfoot (Proudfoot, 1998) noch im selben Jahr den Brick (im wahrsten Sinne des Wortes) und Dave Baum entwickelte NQC (Baum, 2001), eine alternative Programmiersprache zur Programmierung von RCX, dem Betriebssystem des Brick. Uber das Internet wurden und werden die Erkenntnisse und die Arbeiten einer immer groer werdenden Gruppe von Enthusiasten zuganglich gemacht, so da gute eineinhalb Jahre spater, im Fruhjahr 2000, der Brick in vielen Sprachen, darunter C, C++, Forth und Java, programmiert werden kann. Einige dieser Systeme setzen dafur nicht auf der Standard-Firmware auf, sondern beinhalten gleich ein eigenes Betriebssystem fur den Brick, welches die Moglichkeiten, z.B. zur Ansteuerung des integrierten LCD betrachtlich erweitert. Inzwischen hat auch Lego das Betriebssystem erweitert und die textbasierte Programmiersprache Mindscript entwickelt, sowie die Bytecodes des RCX unter dem Namen Lego-Assembler veroentlicht. Doch Konstruktionsbaukasten mit Computern zu verbinden ist eine Idee, die fur Fischertechnik bereits 1984 in ein Produkt umgesetzt wurde. Das UniversalInterface kann an die parallele Schnittstelle eines IBM-PC, Commodore C64 oder Atari ST angeschlossen werden. Das Interface hat keinen eigenen Mikrocontroller, sondern vermittelt im Wesentlichen nur zwischen den elektrischen Signalen des angeschlossenen Computers und den an das Interface angeschlossenen Motoren und Sensoren. Erst das 1997 herausgebrachte Intelligent-Interface kann auf der Basis der Windows-Software LLWin entwickelte Programme auch autonom ausfuhren. LLWin ist ein grasches Programmiersystem, das an das industriell verwendete iCon-L der Firma Pro-Sign Process Design GmbH angelehnt ist. Bereits von Anfang an lag dem Universal-Interface eine gedruckte Dokumentation bei, welche die Signale an der parallelen Schnittstelle beschreibt, die zur Ansteuerung des Interface notig sind. Auerdem verfugt das Interface uber mehr Ein- und Ausgange als der Brick. Trotzdem ist die Fan-Gemeinde und die frei verfugbare Software fur die Fischertechnik-Interfaces wesentlich weniger weit entwickelt, als das bei Lego Mindstorms der Fall ist. Fur das IntelligentInterface, welches eine serielle Schnittstelle hat, ist von Fischertechnik ein Kommunikationsprotokoll veroentlicht worden, mit welchem die gleichen Moglichkeiten wie mit dem Universal-Interface eronet werden. Die Fahigkeit, Programme auf das Interface zu laden und dort dann lokal, vom PC getrennt ausfuhren zu lassen, bleibt leider der Fischertechnik-Software LLWin vorbehalten. Es scheint keine Quelle zu geben, die diesen Vorgang dokumentiert und eine Analyse der Daten die von LLWin an das Interface gesendet werden ist nicht so einfach, da die Datenmenge bereits bei den einfachsten Programmen recht gro ist. 1.2. BEGRIFFSKLARUNGEN 3 1.2 Begrisklarungen Die meisten der betrachteten Programmiersysteme fur Roboter bieten Programmierern nur sehr eingeschrankte Moglichkeiten zur Anwendung ublicher Techniken und Paradigmen in der Softwareentwicklung. Im Folgenden wird auf ein paar Aspekte eingegangen, die in dieser Arbeit immer wieder zur Bewertung der einzelnen Programmiersysteme herangezogen werden. Wiederverwendung von Code: Damit ist der Umstand gemeint, da ein Programm, oder Teile davon, in einem neuen Programm erneut verwendet werden. Eine Form von Wiederverwendung ist das Kopieren (von Teilen) eines Quelltextes, entweder durch "Cut&Paste" oder durch bloes Abschreiben aus Beispielprogrammen. Diese Vorgehensweise kann zum Lernen von neuen Techniken durchaus als geeignet angesehen werden. Zum Aufbau einer Bibliothek von haug benutzten Bausteinen gibt es aber besseres. Solche Sofwarebausteine konnen heute u blicherweise Funktionen oder Objekte sein. Sind diese Bausteine fertig, getestet und genugend abstrakt, bzw. modular gehalten (sowie naturlich deren Funktionsweise hinreichend bekannt), mu nur noch der Code geschrieben werden, der diese miteinander verbindet, und sie konnen somit eine erhebliche Arbeitserleichterung darstellen. Threads: Unter Threads versteht man den aus logischer Sicht gleichzeitigen Ablauf verschiedener Programmteile nebeneinander. Ein Scheduler verteilt diese Ablaufe auf die verfugbaren Prozessoren. Preemptive Scheduling liegt vor, wenn der Scheduler zu beliebiger Zeit einen Thread zugunsten eines anderen vorubergehend stilllegen kann. Interrupts vs. Polling: Wenn Software auf Zustands anderungen in der Hardware reagieren soll, stehen im Wesentlichen zwei Techniken zur Verfugung: Interrupts und Polling. Polling bedeutet, da der Zustand von der Seite der Software aus standig aktiv u berpruft wird. Umgekehrt wird bei Interrupts die Software erst dann aktiv, wenn eine Anderung eingetreten ist. Die Anderung wird in diesem Fall von der Hardware uber den Interrupt signalisiert. Wenn man Polling verwenden mu, weil die Hardware keine (geeigneten) Interrupts zur Verfugung stellt, ist es mit Threads moglich, die benotigten Interrupts in der Software zu simulieren. So programmiert man oberhalb dieser Simulation, als waren Interrupts in der Hardware vorhanden. Zudem ist es mit mehreren Threads moglich, die Notwendigkeit des Polling vollends zu kapseln, also in einem Modul zu verstecken. Ein Modul besitzt mit Threads die Moglichkeit, vollig unabhangig von anderen Modulen aktiv zu werden. Ohne Threads ware es notwendig, da der einzige vorhandene Programmzahler haug genug in dem, dann als passiv zu bezeichnenden, Polling-Modul "vorbeikommt". Events: Events sind im Zusammenhang mit Threads und Modularisierung wichtig. Ein Thread gibt anderen Threads (in anderen Modulen) u ber Events Nachrichten. Ein Thread kann auf einen Event warten, bzw. ein Modul wird von einem Thread ausgefuhrt, wenn ein bestimmter Event eingetreten ist. Dabei sollte ein auf einen Event wartender Thread keine, bzw. wenig Rechenzeit in Anspruch nehmen mussen., also kein sogenanntes busy-wait durchfuhren. Im Optimalfall wird ein Event z.B. fur die Anderung eines Sensorwertes von der Hardware durch einen Interrupt ausgelost, der dann die Ausfuhrung eines 4 1. EINLEITUNG Threads veranlat. Subsumption: Subsumption ist ein von Rodney A. Brooks (Brooks, 1985) erdachtes Konzept zur Steuerung von Robotern. Es basiert auf der Idee, bzw. der Beobachtung aus der Biologie, da komplexe Verhaltensweisen haug das Ergebnis von dem Zusammenspiel vieler einzelner, einfacher Verhaltensweisen (Reexe) sind. Es eignet sich besonders gut fur die Wiederverwendung, da die einzelenen Verhaltensweisen unterschiedlich kombiniert werden konnen. Der "Original-Trusty" von Knudsen ist z.B. mit Subsumption programmiert worden (Knudsen, 1999, S. 179). Ob jektorientierung: Diese Technik zur Modularisierung mittels Klassen und zur Abstraktion mittels Interfaces2 ist sehr nutzlich, da sie in weiten Teilen an die menschliche Vorstellung von Dingen angelehnt ist. Das Entwickeln einer vernunftigen Modularisierung, deren Funktionsweise auch verstandlich ist, wird damit erleichtert. Java: Java (Gosling et al., 2000) ist eine Sprache in der diese Techniken relativ leicht und elegant einsetzbar sind. Auerdem bietet Java die Moglichkeit der plattformubergreifenden Programmierung (Lindholm und Yellin, 1999), mit dem (leider nicht ganz perfekt eingelosten) Versprechen "Write Once, Run Anywhere". So soll der Programmierer eigentlich nicht einmal darauf achten mussen, auf welcher Plattform die Software am Ende laufen soll. Salopp gesagt, konnte man die Motivation fur diese Arbeit auch folgendermaen zusammenfassen: "Das, was Java fur normale Computer ist, will ich fur Roboter auch!" 1.3 Zielgruppe Diese Arbeit ist weitgehend unter Verwendung zweier Produktreihen entstanden, die als Spielzeug vermarktet werden: Lego Mindstorms und Fischertechnik Computing. Sowohl Lego als auch Fischertechnik wird auerdem im Forschungsund Bildungsbereich eingesetzt und auf der Basis von Fischertechnik werden Modelle zur Simulation von industriellen Fertigungsanlagen entwickelt. In diesem, eigentlich sehr breiten, Anwendungsbereich bewegt sich auch diese Arbeit. Die Ideen, die hinter dem in dieser Arbeit entwickelten Programmiersystem stecken, sind von der konkret verwendeten Hardware zwar unabhangig, wurden aber bislang nur fur Hardware aus den Lego Mindstorms und Fischertechnik Computing Produktreihen realisiert. Daraus ergibt sich aber bereits ein Impuls fur den Forschungsbereich, denn eine Portierung, bzw. Erweiterung auf ande re, neue Roboter-Hardware ware sozusagen eine Uberpr ufung des entwickelten Modells an der Realitat. Wer sich mit Robotern als Hobby beschaftigt, will dabei (auch) Spa haben. Nun kann auch Java-Programmieren Spa machen, warum also nicht beides miteinander verbinden? Macht Java programmieren keinen Spa, bzw. mu man es erst lernen, so kann man sich vielleicht mit einem Roboter als Anwendung motivieren. 2 Der Begri Interface wird hier im gleichen Sinne wie in Java verwendet. Ein Interface beschreibt die Schnittstelle und bis zu einem gewissen Grad auch das Ergebnis der Funktionalitat von Objekten. 1.4. INHALTSUBERSICHT 5 Fur Lehrende und Lernende ist Motivation sehr hilfreich. Durch die Verbindung von Mechanik und Informatik im Roboterbau kann man das eine als Vehikel benutzen, um die damit verbundene Motivation auf das andere zu ubertragen. In diesem Fall, wie auch beim Hobby, kommt es weniger auf perfekte Prazision und hohe Geschwindigkeit des Roboters an, als darauf, da man relativ leicht und schnell zu greifbaren Ergebnissen kommt. Spielzeug ist dafur eigentlich die ideale Plattform (Nievergelt, 1999), zumal die notwendige Hardware im Vergleich mit professionelleren Systemen deutlich billiger zu bekommen ist. Bei der Entwicklung von Simulationsmodellen in der Industrie ist zwar der Spielraum fur den Hardware-Aufwand groer, aber dafur die Entwicklungszeit oft von entscheidender Bedeutung. Je mehr dabei auf bereits vorhandene Module zuruckgegrien werden kann desto besser. Kann die gleiche Software sowohl die Modelle als auch die "groen" Anlagen steuern, hat man nicht nur Zeit gespart, sondern bekommt auch die unschatzbare Moglichkeit, die Software vorher, am Modell, ohne groere Gefahr, zu testen. Eine Implementierung fur diesen speziellen Verwendungszweck ist zwar im Rahmen dieser Arbeit nicht geschehen, wurde aber weitgehend oengehalten. Fur die Programmierung der reinen Simulationsmodelle, z.B. aus Fischertechnik, nutzen die gleichen Dinge wie auch schon bei Hobby & Lehre: Es macht Spa und produziert schnell Ergebnisse. 1.4 Inhaltsubersicht Das folgende Kapitel beschreibt den "Beispielroboter" Ariadne. Ariadne kann auf ein Licht zufahren und dabei Hindernissen ausweichen. Der Roboter ist dafur aus zwei unabhangigen Modulen aufgebaut, von denen eines ein Licht nden (LiSe) und das andere fahren und Hindernissen ausweichen kann (Trusty). In Kapitel 3 werden bereits bestehende Programmiersysteme untersucht. Durch die Programmierung des in Kapitel 2 beschriebenen Robotermodells mit unterschiedlichen Systemen werden deren Besonderheiten und Unterschiede ver deutlicht. Am Ende dieses Uberblicks werden die dadurch erlangten Erkenntnisse zusammengefasst und erste Schlussfolgerungen daraus gezogen, welche vor allem einen Ausgangspunkt fur das darauf folgende Kapitel bilden sollen. Kapitel 4 ist die Beschreibung des im Rahmen dieser Arbeit entwickelten Programmiersystems, das Abstract Robot Toolkit (ART). Es wird zuerst der Grundgedanke erlautert, welcher hinter dem System steckt, und welche Ziele mit dem System erreicht werden sollen. Zudem werden einige wichtige Konzepte wie MVC und Subsumption in diesem Zusammenhang naher betrachtet. In Kapitel 5 wird eine praktische Einfuhrung in das System gegeben, bei der gezeigt wird, wie man damit Roboter programmieren kann. Hier wird, neben einigen zusatzlichen Erlauterungen der Fahigkeiten von ART, der rote Faden aus Kapitel 3 wieder aufgenommen und das von dort bereits bekannte Robotermodell programmiert. In Kapitel 6 wird erklart, wie das System fur andere Roboter-Hardware er- 6 1. EINLEITUNG weitert werden kann. Dafur wird als Beispiel ein "Treiber" erlautert, welcher Textfelder des AWT als Ein- und Ausgange fur Sensoren und Aktuatoren verwendet. Das letzte Kapitel baut auf die Schlussfolgerungen aus Abschnitt 3.3 auf. Es wird ein Vergleich zwischen den Systemen aus Kapitel 3 und dem eigenen System, das in den Kapiteln 4 bis 6 beschrieben wurde, durchgefuhrt. Die wesentlichen Vor- und Nachteile, sowie die Unterschiede, die in Kapitel 5 zutage getreten sind, werden noch einmal zusammengefasst. Es wird bewertet, wie vollstandig die gesetzten Ziele erreicht wurden und wo oenbar prinzipielle Schwierigkeiten stecken. Auerdem werden Ideen fur eine Weiterentwicklung aufgezeigt. 2 Ariadne { a Li(ght)Se(arching) Trusty Als Anwendungsbeispiel fur die verschiedenen Programmiersysteme soll ein Robotermodell dienen, welches auf ein Licht zufahren und dabei Hindernissen ausweichen kann. Dieses Modell soll (auf der Harware-Seite), mit moglichst vergleichbarer Funktionalitat, einerseits aus Fischertechnik- und andererseits aus Lego-Bauteilen realisiert werden. Zudem soll es aus zwei weitgehend unabhangigen Modulen bestehen: Eines ist fur das Chassis, das fahren und Hindernissen ausweichen kann (Trusty). Das andere Modul ermittelt die relative Richtung, in der sich eine Lichtquelle bendet (LiSe). Die Verbindung der beiden Module kann dann einen Roboter ergeben, der auf ein Licht zufahren und dabei Hindernissen ausweichen kann (Ariadne). Jeder lichtsuchende Trusty-Roboter besteht also aus zwei Robotern mit geringerem Funktionsumfang, die von Ariadne miteinander verbunden werden.1 2.1 Trusty Robotermodelle, die herumfahren und dabei im Weg stehenden Dingen wie Colaaschen, Stuhlen oder auch Menschen ausweichen, sind beliebt. Der Name Trusty stammt aus Knudsen (1999) und hat sich im Rahmen der Vorlesung zur Roboterprogrammierung an der Universitat Osnabruck (Schreiner, 2000b) als Name fur solche ausweichenden Roboter eingeburgert. Die hier verwendete Variante besitzt einen Antrieb, der aus zwei Motoren besteht, die jeweils ein Rad antreiben. Ein drittes Rad ist leicht drehbar um die senkrechte Achse gelagert, um einerseits einen sicheren Stand des Roboters zu ermoglichen, aber andererseits der durch die Rader mit den Motoren vorgegebenen Bewegung weitgehend zu folgen. Dieser Antrieb ist typisch fur einfache 1 www.webster.com wei u ber Ariadne folgendes zu berichten: Etymology: Latin, from Greek Ariadne : a daughter of Minos who helps Theseus escape from the labyrinth Nun gut, hier ist es Trusty, der die Hilfe bekommt und da unsere Ariadne von LiSe unterst utzt wird, braucht sie auch keinen Faden . . . 7 8 2. ARIADNE { A LI(GHT)SE(ARCHING) TRUSTY Abbildung 2.1: Das Trusty-Chassis als Schema. Zwei voneinader unabh angige Antriebsr ader, sowie vorne zwei Bumper-Sensoren. mobile Roboter.2 Vorne sind zwei bewegliche Querstangen angebracht, die jeweils einen Touchsensor betatigen, wenn der Roboter gegen ein Hindernis fahrt. Der Roboter ist also nur in der Lage, genugend fest stehende Hindernisse, die sich zudem auch noch in der richtigen Hohe benden, zu erkennen. Von Tischkanten zum Beispiel wurde er einfach herunterfallen. 2.2 LiSe Oben auf dem Roboter benden sich zwei um eine senkrechte Achse drehbar montierte Lichtsensoren. Mit zwei Sensoren, die V-formig montiert sind, ist es relativ einfach herauszunden, ob links oder rechts mehr Licht ist. Die Drehachse wird von einem Motor, stark untersetzt (1:243 bei Lego und 1:6 { bzw. 1:1448,7 wenn man das am Motor montierte Getriebe mit einbezieht { bei Fischertechnik), angetrieben. Auerdem kann die Position der Drehachse uber einen Rotationssensor gemessen werden (Lego 48, Fischertechnik 48 Abstufungen pro Umdrehung der Achse mit dem Lichtsensor; da diese Werte gleich sind, ist nur ein Zufall). Da die Lichtsensoren an einem Kabel hangen, konnen sie auerdem nicht um ganze 360Æ gedreht werden. Es gibt also eine Art Endabschalter, d.h. einen Touchsensor, der bei Erreichen des einen Randes des Bewegungsbereiches betatigt wird. Das Erreichen des anderen Randes kann dann uber den Rotationssensor festgestellt werden. Damit ist es auerdem moglich, die zum Unterbau relative Ausrichtung der Drehachse festzustellen, ohne da sich die Achse beim Start in einer denierten Position benden mu. Mit diesem Aufbau ist es moglich, die Lichtsensoren auf eine Lichtquelle auszurichten und diese Ausrichtung bei einer relativen Bewegung der Lichtquelle daran anzupassen. Die Lichtquelle kann innerhalb des Bewegungsbereiches verfolgt werden und die Position der Ausrichtung ist uber den Rotationssensor feststellbar. Der Name LiSe steht fur "Light-Search". 2 Das beschriebene Rad wird im englischen auch als idler wheel bezeichnet. 9 2.3. ARIADNE S Abbildung 2.2: LiSe als Schema. Zwei Lichtsensoren, V-f ormig zueinander positioniert, ein Antrieb mit Rotationssensor (S) und Endabschalter. 2.3 Ariadne Beide Teile, das Trusty-Chassis und der drehbare Turm mit den Lichtsensoren von LiSe, sind fur sich genommen schon als eigenstandige Roboter anzusehen. Trusty kann herumfahren und Hindernissen ausweichen und LiSe kann eine sich bewegende Lichtquelle verfolgen. Fugt man diese beiden Module zusammen hat man einen Roboter der eine Lichtquelle verfolgen, darauf zufahren, und dabei Hindernissen ausweichen kann: Ariadne. Dieses Zusammenfugen geschieht auf der Ebene der Lego- bzw. Fischertechnik-Bausteine, indem der Turm auf dem Chassis in naturlicher Weise montiert wird. Dieses Zusammenfugen der voneinander unabhangigen Module zu einem komplexeren Ganzen soll so weit wie moglich auch auf der Steuerungsebene in der Software nachvollzogen werden. Die Steuerung fur die jeweiligen Module soll daher unabhangig voneinander programmiert werden. Trotzdem wird versucht, die dabei entstandene Software mit moglichst wenig Aufwand fur Ariadne wiederverzuverwenden. Wurde man die Lichtsensoren z.B. durch eine Kamera ersetzen, so konnten naturlich auch Objekte wie beispielsweise ein Fuball verfolgt werden { schon konnte der Roboter beim Robocup mitspielen! 2.4 Hervorzuhebende Eigenschaften Das Robotermodell Ariadne ist aus verschiedenen Bedurfnissen heraus entstanden. Obwohl man an die Eingange des Brick und des Intelligent Interface im Grunde beliebige Strom- bzw. Widerstandsquellen anschlieen kann, so sind doch die Schalter (Tastsensoren) am haugsten anzutreen. Fur analoge Sensoren ist ein Lichtsensor das beste Beispiel, da er in den Grundbaukasten "Robotics Invention System" (1.0 - 2.0) und "Mobile Robots" bereits vorhanden ist. Ein Rotationssensor gehort bei Lego zwar nicht zum "Standard", ist aber auch ein sehr wichtiger typische Sensorik: 10 2. ARIADNE { A LI(GHT)SE(ARCHING) TRUSTY Abbildung 2.3: Die Lichtsensoren bei Lego sind die beiden blauen Steine mit dem rot leuchtenden Punkt. Bei Fischertechnik sind die Lichtsensoren die kleinen gelben Steine, die wie Lampen aussehen. Das selbstgebaute Getriebe f ur die Ubersetzung nimmt viel Platz ein. Sensor, da nur mit ihm eine prazise Steuerung der Motoren moglich ist. Die Rotationssensoren von Lego und Fischertechnik unterscheiden sich von den anderen Sensoren zudem dadurch, da sie eine kontinuierliche Abfrage ihres Zustands benotigen, um einen sinnvollen Wert liefern zu konnen. Da ein wesentlicher der betrachteten Aspekte die Modularisierung und Wiederverwendung von Software ist, mu der Beispielroboter selbst aus Modulen bestehen, die unabhangig voneinander betrachtet werden konnen. modularer Aufbau: Wenigstens eines der Modelle sollte eine Art "Standardmodell" sein. Ein Beispiel, das in vielen Buchern zu dem Thema verwendet wird, ist ein Roboter wie Trusty. Damit ist es auch einem Leser ohne Erfahrung in der Konstruktion mit Lego und/oder Fischertechnik moglich, zumindest Trusty nachzubauen und eigene Programmierversuche durchzufuhren. Selbst die Anleitungen zu den Grundbaukasten enthalten Beispiele fur einen solchen Roboter (er heit dort nur nicht Trusty). Standardmodell: Die Anschlussmoglichkeiten der verwendeten Hardware sollten weitgehend ausgereizt werden, um zu sehen, wie sich die Systeme bei aufwandigeren Konstruktionen verhalten. Der Brick bietet im Grunde nur drei Anschlusse fur Sensoren, fur das komplette AriadneBeispiel werden aber sechs Sensoren verwendet. Da aber vor allem die Tastsensoren des Brick nicht unbedingt eine spezielle Konstruktion benotigen, kann man diese durchaus parallel zu einem anderen Sensor ver- viele Sensoren und Motoren: 2.4. HERVORZUHEBENDE EIGENSCHAFTEN 11 wenden (Ferrari und Ferrari, 2002). Es ist dann aber notwendig, da die verwendete Software einem weitgehende Freiheit darin lasst, wie man den Eingang konguriert und seinen Wert ausliest. Die Interface-Hardware von Lego und Fischertechnik ist sehr verschieden. So wird ein ganzlich anderes serielles Protokoll verwendet, die elektrischen Eigenschaften der Sensoren unterscheiden sich weitgehend und die Ansteuerung der Motoren ist auch nicht auf die gleiche Weise umgesetzt. Zudem sind am Intelligent Interface sogar noch ein paar Anschlusse frei. Trotzdem konnen mit beiden Systemen Roboter gebaut werden, die sich im Wesentlichen gleichen. Damit konnen, anhand eines gemeinsamen Steuerungsalgorithmus fur beide Varianten, die Abstraktionsfahigkeiten des Abstract Robot Toolkit (ART) auf die Probe gestellt werden. Lego und Fischertechnik Das Abstract Robot Toolkit ist aber keineswegs nur fur dieses Robotermodell entwickelt worden. Es soll vielmehr jeder Roboter mit ART gesteuert werden konnen, zumindest vom Prinzip her. Die Verwendung von Lego und Fischertechnik hat rein praktische Grunde. Die Hardware ist billig und der Zusammenbau eines Roboters erfordert keine tiefgehenden ingenieurwisenschaftlichen Kenntnisse. Der Beispielroboter Ariadne ist nur das Testobjekt mit dem das Prinzip erklart und u berpruft wird. 12 2. ARIADNE { A LI(GHT)SE(ARCHING) TRUSTY 3 Programmiersysteme fur Roboter In diesem Kapitel werden verschiedene Programmiersysteme fur LEGO Mindstorms und Fischertechnik Computing vorgestellt. Besonderer Wert wird dabei auf die Moglichkeiten zur Modularisierung und Wiederverwendung gelegt, sowie auf die Verwendung von Threads und Events. Im Abschnitt 3.1 werden grasche Programmierumgebungen vorgestellt. Der Abschnitt 3.2 auf Seite 22 beschaftigt sich mit den verschiedenen Programmiersystemen auf Textbasis, von denen einige gleich ein eigenes Betriebssystem mitbringen. Es gibt noch wesentlich mehr, frei verfugbare Systeme, als hier beschrieben werden. Als Einstieg im Zusammenhang mit dem Brick bietet sich das LugnetForum im Internet an (http://news.lugnet.com/robotics/rcx/). Von Fischertechnik gibt es auch eine Web-Seite von welcher aus weitere Programmiersysteme erreichbar sind, die sich aber meistens auf eine Art Treiber, fur die Ansteuerung aus bestimmten Programmiersprachen heraus, beschranken (http://www.fischertechnik.de/ft-Computing.html). Alle bekannten verfugbaren Systeme zu betrachten wurde im Rahmen dieser Arbeit zu weit gehen. Aus den Bereichen der graschen Programmiersysteme wird LLWin besonders ausfuhrlich behandelt, bei den textbasierten wurde leJOS der meiste Raum eingeraumt. Ansonsten werden einige der wichtigsten Systeme kurz vorgestellt und die im Zusammenhang mit dieser Arbeit wichtigen Eigenschaften erlautert. Fur einige der vorgestellten Systeme existieren Beispielprogramme fur LiSe, Trusty und Ariadne, deren Quellcode auf der CD zu dieser Arbeit enthalten ist, sowie aus dem Internet geladen werden kann (siehe Anhang, Abschnitt A.1 auf Seite 136). Hin und wieder werden, um Besonderheiten am realen Beispiel zu verdeutlichen, Ausschnitte aus den Programmen in der Beschreibung der jeweiligen Systeme gezeigt. 3.1 Grasche Programmiersysteme Fur die Computerinterfaces von Lego und Fischertechnik sind vor allem drei grasche Programmierumgebungen von Bedeutung, die vom Hersteller der je13 14 3. PROGRAMMIERSYSTEME FUR ROBOTER weiligen Hardware angeboten werden: LLWin (Lucky Logic f ur Windows) fur Fischertechnik RCX-Code f ur Lego Robolab f ur Lego RCX-Code ist speziell fur das Robotics Invention System entwickelt worden. Robolab und LLWin sind an die jeweilige Hardware angepasste Versionen einer Software, die auch in der Industrie eingesetzt wird. Robolab stammt von LabVIEW ab und der groe Bruder von LLWin ist iCon-L. LabVIEW wird von National Instruments verkauft und ist vor allem fur den Einsatz im Labor konzipiert. iCon-L wird von der Firma Pro-Sign Process Design GmbH als Programmiersystem fur Prozessautomation verkauft. Bei den graschen Systemen wird ein Schwerpunkt auf LLWin gelegt. LLWin ist einerseits machtiger als RCX-Code, allein schon deswegen, weil man Variablen verwenden kann, andererseits ist es uberschaubarer als Robolab, da weniger Programmierbausteine zur Verfugung stehen. 3.1.1 LLWin LLWin wird von Fischertechnik verkauft und lauft nur unter Windows. Ahnlich wie bei den anderen graschen Programmiersystemen werden Bausteine auf einem Arbeitsblatt plaziert und durch "Drahte", die den Kontrolluss symbolisieren, miteinander verbunden. Programme konnen in das Interface geladen werden. Das Interface fuhrt sie dann unabhangig von einem angeschlossenen PC aus. Werden die Programme hingegen auf dem PC betrieben, ist sogar eine Ablaufverfolgung in der graschen Programmansicht moglich. Es gibt 115 Speicherplatze fur Variablen1 und innerhalb von Zuweisungen und Vergleichen konnen, zumindest ab Version 3.0, einfache arithmetische Operationen wie + = und Klammern eingesetzt werden. Ohne diese Operationen war bisher etwa eine sinnvolle Kalibrierung von Sensoren kaum zu bewerkstelligen, da man beispielsweise ein arithmetisches Mittel nur auf dem Umweg uber die Operationen INC, DEC, einen Vergleich und eine Schleife (also mit entsprechend groem Aufwand) berechnen konnte. Die LLWin-Version von Trusty wurde mit Subsumption (siehe 1.2 auf Seite 4) programmiert, LiSe hingegen nicht. Bei Trusty ist Subsumption besonders hilfreich im Hinblick auf die spatere Erweiterung zu Ariadne, da dadurch in Trusty's Verhalten leichter eingegrien werden kann. LiSe konnte, von der Aufgabenstellung her, zwar auch sehr gut mit Subsumption programmiert werden (siehe z.B. Abschnitt 3.2.4 auf Seite 27 oder auch Abschnitt 5.6 auf Seite 99), eine solche Implementierung in LLWin wurde jedoch 3-4 Threads mehr benotigen. Die Steuerung von Ariadne ist aber bereits in ihrer jetzigen Form { ohne Subsumption bei LiSe { schon zu komplex, um 1 99 normale sowie 16 Z ahlvariablen, die auch von dem Baustein Alle sind 16 Bit signed. POSITION benutzt werden. 3.1. 15 GRAFISCHE PROGRAMMIERSYSTEME auf das Interface heruntergeladen zu werden. Die Ausfuhrung ist dort schlicht zu langsam. Bei Ausfuhrung in LLWin auf einem angeschlossenen PC (Pentium III, 650Mhz) ist die Geschwindigkeit zwar noch ausreichend, die Reaktionszeiten "fuhlen" sich aber bereits etwas langsam an. Aus diesem Grund wurde die Steuerung von LiSe in LLWin auf konventionellem Weg, mit einer groen Schleife, realisiert. Bei der Programmierung muten Kompromisse eingegangen werden, da die komplette Fassung von Ariadne anfangs zu langsam und zu ungenau lief. Zum Beispiel war die Positionsmessung mit so groen Fehlern behaftet, da sie unbrauchbar war. Zur Losung wird nun immer die letzte gewollte Drehrichtung gespeichert und zwischen einer Umkehrung der Drehrichtung eine kurze Pause eingelegt, in der der Motor steht. Damit wird die Positionsmessung zwar noch langsamer, aber wieder ausreichend genau. Ein Grund fur die geringe Ausfuhrungsgeschwindigkeit sind wahrscheinlich die vielen Threads. Sie sind jedoch fur eine elegante und ubersichtliche Programmierung unabdingbar. Zudem sollten ja auch die Moglichkeiten von LLWin ausgelotet werden. Es ist bestimmt moglich, mit LLWin eine Steuerung fur Ariadne zu entwickeln, die schneller lauft und den Roboter mindestens genausogut steuert, das ware dann wahrscheinlich aber kein geeignetes Beispiel mehr, weil man wohl auf den Versuch der Modularisierung und Wiederverwendbarkeit, elegante Beispiele fur Thread-Synchronisation sowie Subsumption verzichten mute. Threads In LLWin ist das eigentliche Erzeugen von verschiedenen Threas sehr einfach. Dazu braucht man nur mehrere Startsymbole auf das Arbeitsblatt zu setzen. Eine Moglichkeit zu deren Synchronisierung ist jedoch nicht vorgesehen. Es bleibt nur der Ausweg dieses uber Variablen selbst umzusetzen. Threads konnen sich nicht gegenseitig aufwecken oder unterbrechen, eine Synchronisierung verlangt daher die wiederholte Abfrage eines Zustands. Main VAR81=1 0 Kopierthread für Analogwerte. Kopierthread für die Analogwerte erst einmal durchlaufen lassen. VAR96 1025 VAR97 1025 VAR98 EX Obergrenzen für den Analogwert 1 Eigene Variablen initialisieren VAR5 VAR96 VAR6 VAR97 ... ... Obere Schranke der Lichtsensoren. VAR81 1 Signal an Main-Thread, daß die Initialisierung abgeschlossen ist. Abbildung 3.1: Der linke Thread braucht f ur die Initialisierung seiner Variablen die bereits initialisierten Variablen VAR96/97 aus dem rechten Thread. Der Linke wartet zu Beginn daher bis der Rechte einen bestimmten Punkt in seiner Ausf uhrung erreicht hat (in diesem Fall bis die Schleife einmal durchgelaufen ist). Im Beispielprogramm werden Threads auf zwei Arten synchronisiert. Im 16 3. PROGRAMMIERSYSTEME FUR ROBOTER einen Fall soll ein Thread warten, bzw. einen bestimmten Ausfuhrungsweg erst dann nehmen, wenn ihm ein anderer explizit dazu die Erlaubnis gibt. Das wird schlicht uber das Setzen und Abfragen von jeweils einer Variable pro solch einer Kommunikation geregelt. Im einfachsten Fall wird dies { im Algorithmus von LiSe { dazu verwendet, zwei Threads ihre Initialisierung in einer bestimmten Reihenfolge ausfuhren zu lassen (siehe Abbildung 3.1 auf der vorherigen Seite). Im Algorithmus von Trusty sollen die Threads, die fur die Implementierung der jeweiligen Verhaltensweisen zustandig sind, nur dann ihr Verhalten ausfuhren, wenn der Thread, der die Prioritaten der Verhaltensweisen regelt, ihnen das Signal dazu gibt. Der Thread fur die Prioritatenvergabe hingegen mu dann warten, bis die jeweils angestoene Verhaltensweise "fertig" ist, damit nicht die nachste beginnt, solange die vorherige noch lauft. An dieser Stelle wird der Baustein POSITION verwendet, um Threads moglichst ressourcenschonend zu synchronisieren (siehe Abschnitt 3.1.1 und Abbildung 3.3 auf der nachsten Seite). Etwas schwieriger wird es, wenn es nicht darum geht, zwei Threads immer an der gleichen Stelle aufeinander warten zu lassen, sondern wenn erreicht werden soll, da bestimmte, kritische Bereiche nicht von mehreren Threads gleichzeitig ausgefuhrt werden konnen. In Java gibt es dafur eine eigene Kontrollstruktur { synchronized. Ohne Java lost man solch ein Problem z.B. mit einer Semaphore. Ein Ein VAR82 VAR82 VAR82<0 VAR82 1 0.5 s Aus 0 Aus Abbildung 3.2: Implementierung einer Semaphore in LLWin. Das Unterprogramm AQUIRE zahlt VAR82 um eins herunter, wird aber nur verlassen, wenn VAR82 dabei nicht kleiner 0 wird. RELEASE z ahlt VAR82 wieder um eins hoch. Abbildung 3.2 zeigt die Implementierung einer Art Semaphore (Tanenbaum, 1990; Dijkstra, 1965) als Unterprogramm in LLWin. Die korrekte Funktionsweise dieser Implementierung ist davon abhangig, da die einzelnen Bausteine unteilbar abgewickelt werden. Davon kann man aber ausgehen, da es sich bei LLWin um einen Interpreter handelt. Mit dieser Implementierung ist ein geschutzter Bereich moglich. Fur mehrere muten auch mehrere solcher Unterprogramm-Paare implementiert werden. Mit dem Unterprogramm AQUIRE wird Zugri auf einen geschutzten Bereich verlangt, mit RELEASE wird er wieder freigegeben. Dabei mu man sich naturlich an die Spielregeln halten und darf z.B. nicht RELEASE aufrufen, wenn man noch gar kein AQUIRE aufgerufen hat. Direkte Veranderungen an der von den Unterprogrammen benutzten Variable sind in diesem Sinne (naturlich) genausowenig gestattet. Eine Ausnahme ist die Initialisierung der Zahlvariable von AQUIRE/RELEASE. Das Programm beginnt mit einem Zustand, in dem kein Aufruf von AQUIRE zu 3.1. 17 GRAFISCHE PROGRAMMIERSYSTEME Ende gehen wurde, d.h. ein Thread sollte mindestens einmal RELEASE ohne ein vorausgegangenes AQUIRE aufrufen. Es gibt also zu jedem Zeitpunkt nur hochstens genausoviele bereits durchlaufene AQUIRE-Aufrufe wie bereits durchlaufene RELEASE-Aufrufe. Events In LLWin gibt es etwas, das man mit Events vergleichen kann, in zwei Bausteinen: POSITION und FLANKE. Kommt ein Thread zu solch einem Baustein, verharrt er dort solange, bis die angegebene Anzahl von Flanken (POSITION), bzw. der angegebene Typ Flanke (fallend oder steigend bei FLANKE) an dem angegebenen Eingang registriert wurde. In Bezug auf die analogen Eingange mu man auf Events verzichten. Verhaltensweise Ausweichen Links Scheduler für Subsumption Linker Bumper Sensor VAR24=1 E1 1 0 VAR34 Ausweichen Mitte VAR34 0 VAR22 ... VAR22 0 VAR35 E6 VAR21=1 0 1 1 VAR35 0 Ins Licht Drehen VAR22 VAR31 E6 1 VAR31 0 VAR32 Immer Geradeaus Darf nicht Soll 0 1 0 Will nicht Will VAR32=1 1 0 1 ... VAR25=1 1 1 ... E6 0 0 Ausweichen ... Nachricht an Scheduler, daß fertig Abbildung 3.3: Die Variablen VAR3x werden von dem Scheduler (links) auf den Wert 1 gesetzt, als Signal an den jeweiligen Verhaltensweisen-Thread (rechts). Umgekehrt setzt ein Verhaltensweisen-Thread, nachdem er sein Verhalten ausgef uhrt hat, diese Variable wieder auf 0. Die gleiche Variable kann hier f ur zwei verschiedene Kommunikationen genutzt werden, da die Kommunikationen sich immer genau abwechseln. Da der Baustein POSITION zum Zahlen eine globale Variable benutzt, ist es moglich (aus einem anderen Thread heraus) den Wert auf die im Baustein angegebene Zahl zu setzen und damit die Ausfuhrung des Bausteins zu beenden. Es ist zwar nicht dokumentiert, aber der Baustein sollte in diesem Fall auch wirklich immer zu Ende gehen, da ein Test zeigt, da auch eine Uber-, bzw. Unterschreitung der angegebenen Zahl zum Abbruch fuhrt. Es ist also durch "Missbrauch" des Bausteines POSITION realisierbar, einen Thread { moglicherweise2 ohne Ressourcenverbrauch { auf ein durch einen anderen Thread aus2 Die konkrete Implementation von POSITION ist nicht dokumentiert. 18 3. PROGRAMMIERSYSTEME FUR ROBOTER gelostes Ereignis warten zu lassen (siehe Abbildung 3.3 auf der vorherigen Seite). Will man durch ein Ereignis, wie z.B. eine steigende Flanke, einen bestimmten Vorgang anstoen und wahrenddessen aber weiterhin den Eingang auf ein erneutes Eintreten des Ereignisses uberwachen, mu man sich darum selber kummern. Aus diesem Grund gibt es zur kontinuierlichen Bestimmung der Position des Towers im Algorithmus von LiSe zwei Threads, denn um die Position einer Achse mit einem der vorgesehenen Impulsrader zu u berwachen, reicht der Baustein POSITION alleine nicht aus. Positionsthread. Speichert Position von M4 ständig in VAR71. E8 VAR72 > VAR73 1 AQUIRE VAR74 VAR73 VAR73 VAR72 VAR72 32000 0 Flanken beobachten und mit minimalem Aufwand speichern um möglichst keine Flanke zu verpassen. Beginn eines exklusiven Zugriffsbereichs. VAR82 wird dafür ähnlich wie Semaphore verwendet. Die Berechnung ist von einem Aquire-Release Paar umgeben, um einen sicheren Reset der Variablen zur Laufzeit zu ermöglichen. (RST_ROT) E34=1 bedeutet M4 dreht linksherum. E 34 0 E44=1 heißt M4 rechtsdrehend. 1 E 44 1 0 VAR8=1 1 0 VAR71 VAR71+(VA... RELEASE In VAR8 steht die letzte Suchrichtung. Seit dem letzten Durchgang beobachtete Flanken je nach Motorzustand von der Positionsvariable (VAR71) subtrahieren (Rechtsdrehung) oder zu ihr addieren (Linksdrehung). VAR71 VAR71-(VA... Ende des Exklusiven Bereiches Abbildung 3.4: Ein Thread z ahlt die Flanken, der andere bestimmt die Richtung. W urde das der gleiche Thread machen, gingen Flanken leichter verloren. Leider ist es nicht ganz einfach die Drehrichtung eines Motors zu bestimmen, der vielleicht inzwischen schon wieder aus ist ... Einer der Threads bendet sich standig im Baustein POSITION, damit moglichst keine Flanke verloren geht. Ein weiterer beobachtet die Zahlvariable dieses POSITION-Bausteins und addiert oder subtrahiert die Veranderungen seit seinem letzten Schleifendurchlauf in einer weiteren Variablen, je nachdem in welche Richtung sich der Tower gerade dreht, bzw. wahrscheinlich das letzte Mal gedreht hat. Diese Variable enthalt dann die Position des Towers (siehe Abbildung 3.4). Modularisierung und Wiederverwendung Zur Strukturierung eines Programms hat man die Moglichkeit, Unterprogramme zu erstellen. Parameter oder lokale Variablen gibt es nicht, ein Unterpro- 3.1. GRAFISCHE PROGRAMMIERSYSTEME 19 gramm greift auf dieselben globalen Variablen zu wie der Rest des Programms. Wiederverwendung von (Teilen von) Programmen in anderen Programmen ist nur durch Cut&Paste einer Selektion von Symbolen auf dem Arbeitsblatt moglich. Dabei mu auerdem darauf geachtet werden, da eventuell mitselektierte Unterprogramme bereits vorher kopiert wurden und den gleichen Namen bekommen haben. Eine groe Schwierigkeit stellen in diesem Zusammenhang die Variablen dar. Da alle Variablen global und deren Anzahl beschrankt ist, mu man zwangslau g irgendwann mit Uberschneidungen bei den verwendeten Variablen rechnen. Dann mussen Variablen umbenannt werden, wodurch sehr leicht neue Fehler entstehen. Welche Variable im jeweiligen Projekt noch nicht benutzt wurde, ist nur durch eigene Kontrolle herauszunden, eine Suchfunktion oder Ahnliches gibt es nicht. In einem Projekt wie Ariadne werden die Variablen bereits knapp, vor allem, wenn man wenigstens etwas Struktur hineinbringen und Variablen innerhalb eines gemeinsamen Kontextes systematisch auswahlen mochte. Im Beispiel kann der Algorithmus von LiSe ohne Anpassungen (wenn man einmal von Variablennamen absieht) per Cut&Paste ubernommen werden. Bei Trusty ist das schon etwas schwieriger, da die Steuerung von Ariadne nicht gleichzeitig mit der Trusty-Steuerung auf die gleichen Ausgange zugreifen darf. Diese beiden Steuerungen mussen miteinander kooperieren. Durch Subsumption (siehe Abschnitt 1.2 auf Seite 4) ist die Integration von Trusty und Ariadne zwar einfach, aber nicht ohne Veranderung des bereits fur Trusty existierenden Schedulers (vgl. Abbildung 3.3 auf Seite 17) zu bewerkstelligen, da Ariadne in dessen Vorrangregelung integriert werden mu. 3.1.2 RCX-Code Die mit dem Robotics Invention System mitgelieferte Programmierumgebung, RCX-Code, basiert im Wesentlichen auf stark spezialisierten und eingeschrankten Struktogrammen nach Nassi-Shneiderman (Schreiner, 2000b). Das bedeutet unter anderem, da die einzelnen Bausteine des Programms direkt aneinandergesetzt und daher im Gegensatz zu LLWin und Robolab dafur keine Drahte gezogen werden mussen. Auch in RCX-Code gibt es einen Start-Baustein und u ber die sogenannten "Sensor-Watcher" konnen Threads beim Auftreten eines, durch den SensorWatcher denierten, Events gestartet werden. Der gesamte Sprachumfang des RCX-Code aus dem RIS 1.0 ist recht ausfuhrlich in Schreiner (2000b) dokumentiert. In den Versionen fur das RIS 1.5 und 2.0 ist RCX-Code nochmals deutlich erweitert worden. Die gesamte Oberache des RIS, in welche die Programmierumgebung RCXCode eingebettet ist, wird vor allem der erklarten Zielgruppe des Produkts gerecht. Sie ist ansprechend, fast wie ein Computerspiel, gestaltet und um sich in RCX-Code hineinzunden sind kaum Erfahrungen in der Programmierung notwendig. Die Bausteine sind weitgehend selbsterklarend und nicht schwer zu nden, da der Zugri auf den gesamten Vorrat praktisch direkt auf der Oberache, ohne irgendwelche Menuzugrie moglich ist. Wer bereits mit "richtigen" Programmierersprachen Erfahrungen gemacht 20 3. PROGRAMMIERSYSTEME FUR ROBOTER Abbildung 3.5: Ein Programm in RCX-Code, der Programmierumgebung des RIS. Es steuert einen Trusty-ahnlichen Roboter und demonstriert die Moglichkeit, da Threads uber die einzige Variable in RCX-Code, den "Counter", kommunizieren. hat, wird jedoch sehr bald feststellen, da er fur die gewohnte Losung von erstaunlich vielen Programmieraufgaben Variablen braucht. Leider sind diese in RCX-Code praktisch gar nicht vorhanden. Auf diese Weise sind auch die Moglichkeiten zum Datenaustausch zwischen den einzelnen Threads nur sehr eingeschrankt moglich. Andererseits ist es auch erstaunlich, was trotzdem alles mit dieser Umgebung programmierbar ist. Zudem liegt der Ursprung des RIS am Massachusettes Institute of Technology { dem gleichen Umfeld aus dem auch die LOGO-Turtle kommt (Papert, 1999). Die Idee, Programmieren durch Verwendung einer beinahe zustandsfreien Programmiersprache zu lernen und dabei auch noch Spa zu haben, wird daher im RIS nur auf eine neue, aufregende Art umgesetzt. In diesem Kontext ist es nicht weiter verwunderlich, da mit RCX-Code die Verwendung von mehreren Sensoren an dem gleichen Eingang nicht unterstutzt wird. Dadurch ist die Programmierung des Beispielroboters aber kaum moglich. Lediglich Trusty wurde keine Schwierigkeiten machen, ein Beispielprogramm fur einen Trusty-ahnlichen Roboter zeigt die Abblidung 3.5. 3.1.3 Robolab Robolab wird von LEGO Dacta als Steuerungs-Software fur den Brick verkauft. Es lauft sowohl unter Windows als auch unter MacOS. Robolab zeichnet sich vor allem durch seine Moglichkeiten zur Auswertung von Messdaten aus. Die schiere Fulle an Symbolen, die zum Aufbau eines Programms auf dem Arbeitsblatt plaziert werden konnen, ist { zumindest am Anfang { kaum zu u berblicken. 3.1. GRAFISCHE PROGRAMMIERSYSTEME 21 Es gibt daher funf verschiedene "Schwierigkeitsstufen" und nur in einer steht einem wirklich das komplette Arsenal an Operationen zur Verfugung. Abbildung 3.6: Ein einfaches Programm in Robolab, welches die Motoren A und C laufen lasst, bis der Taster am Eingang 1 gedruckt wird. Es gibt Variablen (sogenannte Container ) und alle wichtigen arithmetischen Operationen. Fur Motoren und die verschiedenen Sensortypen gibt es gleich mehrere Bausteine, um diese anzusteuern. Hervozuheben ist die Integration eines speziellen Sensoradapters uber den z.B. schlicht eine Spannung gemessen werden kann. Die Programmierung erfolgt, indem man Symbole, die fur bestimmte Daten, bzw. Operationen stehen, auf dem Arbeitsblatt plaziert und durch Drahte mit einander verbindet. Uber die Drahte wird in Robolab nicht nur der Programm-, sondern auch der Datenuss gesteuert. Wie man in Abblidung 3.6 sehen kann, wird z.B. der Baustein, der einen Motor einschaltet, uber die Verknupfung mit den Containern 'A', 'C' und '5' konguriert. Der eigentliche Kontrolluss bei der Ausfuhrung geht entlang der gestricheleten Linien von der grunen bis zur roten Ampel, im Beispiel also von links nach rechts. Leider ist Robolab nicht in der Lage, einen Sensoreingang des Brick, zu verschiedenen Zeitpunkten des Programmablaufs, fur die Verwendung mit mehreren Sensoren, unterschiedlich zu kongurieren. Mochte man also mehrere Sensoren ubereinander an einem Port ansteuern, geht das nur eingeschrankt und etwas umstandlich. Man mu sich zum einen auf einen Typ Sensorbaustein beschranken und auerdem sind Kombinationen { z.B. mit dem Rotationssensor { u berhaupt nicht moglich. Das macht die Entwicklung eines Steuerungsprogramms fur Ariadne unmoglich, da die Lego-Version verlangt, insgesamt sechs Sensoren an den drei vorhanden Anschlussen anzusteuern. So hangt z.B. der 22 3. PROGRAMMIERSYSTEME FUR ROBOTER End-Abschalter von LiSe mit dem Rotationssensor zusammen an einem Eingang. Aus diesem Grund wird hier auf Beispielprogramme verzichtet. Fur die Verwendung des Brick zur Erfassung und Auswertung von Messreihen ist Robolab aber oenbar sehr gut geeignet und dafur ist es wohl in erster Linie auch gedacht. In Bezug auf Threads und Wiederverwendung besitzt Robolab ahnliche Fahigkeiten wie LLWin. 3.2 Textbasierte Programmiersysteme Textbasierte Programmiersysteme haben gegenuber den graschen oft den Vorteil, da das Format, in welchem der Programm-Code gespeichert wird, einfacher Text ist. Das eronet weitreichende Moglichkeiten die Programmierumgebung durch Verwendung weiterer Tools zu verbessern, was im Falle von proprietaren Formaten schwieriger ist. Besonders naheliegend ist z.B. die Verwendung eines Praprozessors oder eines Tools zur Versionskontrolle. Zudem ist man als "richtiger" Programmierer einfach daran gewohnt, Programme in Form von Text aufzuschreiben. Vor allem die frei verfugbaren Systeme, die meistens aus purem Enthusiasmus entwickelt wurden, sorgen in diesem Bereich fur eine sehr groe Auswahl. Es existieren im Wesentlichen drei verschiedene Ansatze, welche hier in eigenen Abschnitten vorgestellt werden sollen. Als letztes wird leJOS genauer diskutiert. Es handelt sich dabei um ein Betriebssystem fur den Brick, welches erlaubt in Java geschriebenen Code auf dem Brick auszufuhren. 3.2.1 Fernsteuerungen In diesem Abschnitt werden einige Systeme vorgestellt, die es erlauben, Programme zu schreiben, die auf einem normalen PC ablaufen und die InterfaceHardware von dort aus fernsteuern. FishFace Fur Fischertechnik unter Windows gibt es FishFace von Ulrich Muller (http: //www.ftComputing.de/). Es bietet Zugri auf alle Ein- und Ausgange des parallelen Interface und des Intelligent Interface. Es enthalt auerdem einige Funktionen, die vor allem den Umgang mit Motoren und Impulsradern vereinfachen. Zugri auf Events bekommt man, indem bestimmte Methoden des FishFaceObjekts uberschrieben werden. Es gibt aber nur Methoden fur die digitalen Eingange und, da es nur ein Interface-Objekt gibt, ist auch nur ein Objekt als "Listener" verwendbar, namlich das FishFace-Objekt selbst. Die Methoden lauten im einzelnen: InputImpuls(InputNr as Integer, ImpulsNr as Integer) PositionChange(PositionListe as Variant) 3.2. TEXTBASIERTE PROGRAMMIERSYSTEME 23 InterfaceStatus(Quelle as Integer) Die ersten beiden dienen zur Uberwachung des Status der Methoden WaitForChange(InputNr, NrOfChanges) und MoveTo/MoveDelta (Bewegung eines Motors fur eine bestimmte angegeben Anzahl von Impulsen) und werden nur ausgelost, wenn gerade die entsprechende Methode abgearbeitet und dabei ein Statuswechsel an den digitalen Eingangen erkannt wird. Die letzte Methode wird bei jedem erkannten Statuswechsel der igitalen Eingange aufgerufen. Die Dokumentation von FishFace (Muller, 2000) macht keine explizite Aussage uber die Implementierung von FishFace im Zusammenhang mit Threads. Prinzipiell scheint das Interface-Objekt keinen eigenen Thread zu besitzen, der sich um das Interface kummert. Ruft man keine Methode auf, so "verstummen" die Motoren des Interfaces bald. Dies wurde nicht passieren, wenn z.B. standig die Inputs abgefragt wurden. Daraus folgt, da diese nur dann abgefragt werden, wenn man entweder selbst irgendwelche Methoden aufruft, das Diagnose-Panel aktiviert oder die Kontrolle durch Ausfuhrung der Methoden WaitForChange/Low oder MoveTo/MoveDelta an das Interface abgibt. Spirit.ocx Von Lego gibt es das Active-X Control Spirit.ocx (The LEGO Group, 1998) welches, unter Windows z.B. aus Visual Basic heraus, Zugang zur jeweiligen Roboterhardware bietet. Es ist ziemlich genau auf die Fahigkeiten der StandardFirmware von Lego abgestimmt, was aber auch nicht weiter verwundert. Da z.B. auch der Lego Cybermaster eine ahnliche Firmware enthalt, funktioniert Spirit.ocx fur diesen ebenfalls. Die Interface-Hardware wird durch ein Objekt reprasentiert, welches die gesamte Funktionalitat enthalt. Es gibt naturlich Funktionen um die Sensoren abzufragen oder die Motoren zu beeinussen. Das eigentlich besondere ist aber, da Spirit.ocx auch einen Modus kennt, in welchem die aufgerufenen Funktionen nicht sofort ausgefuhrt, sondern als Task auf den Brick ubertragen werden. Da fur diesen Modus auch Funktionen wie "if" und "while" zur Verfugung stehen, konnen auf diese Weise "richtige" Programme entwickelt werden. Dieser Code ist zwar nicht mehr schon anzusehen, aber einige Systeme beruhen auf dieser Fahigkeit und stellen den Code einfach eleganter dar. So soll z.B. das in Abschnitt 3.1.2 auf Seite 19 vorgestellte RCX-Code auf Spirit.ocx beruhen und "Gordon's Brick Programmer" (http://www.umbra.demon.co.uk/gbp.html) stellt ein solches Programm z.B. als einen attributierten Baum dar. Inzwischen stellt Lego einen neueren PC-basierten Treiber namens Ghost bereit. Dieser konnte jedoch fur die vorliegende Arbeit nicht mehr untersucht werden. Java-package "ft" Das Java-package "ft" (Schreiner, 2000b) funktioniert im Prinzip auf allen Plattformen, fur die eine Implementierung des Java Communications API (Sun Microsystems, 1998) existiert. Dies gilt nur fur die Ansteuerung des Intelligent Interface. Das parallele Interface wird nur unter Windows direkt unterstutzt. 24 3. PROGRAMMIERSYSTEME FUR ROBOTER Das "ft"-package geht schon recht weit in die Richtung die auch mit dieser Arbeit angestrebt wird. Es ist bereits eine Bibliothek von mehreren Klassen. Grasche Views und Controls, sowie Model-Objekte fur Motoren sowie die analogen und digitalen Eingange. Es bietet zudem ein Observer-Modell mit Events von Motoren und den digitalen und analogen Eingangen. Ein Steuerungsalgorithmus ist primar als Unterklasse von ft.Controller vorgesehen. Eine Abweichung von diesem Schema wurde sofort zu einem erhohten Arbeitsaufwand fuhren. Eigene Threads und deren Synchronisierung sind naturlich ganz normal wie in Java ublich verwendbar. Das "ft"-package liefert jedoch kaum Konzepte fur die Wiederverwendung des Steuerungsalgorithmus, zumindest nicht uber Modell-, bzw. Interface-Grenzen hinweg. Zudem wird die an die Ein- und Ausgange angeschlossene Peripherie immer als ein Motor oder ein digitaler, bzw. analoger, Sensor angesehen. Eine Unterscheidung zwischen z.B. einer Lampe und einem Motor wird nicht gemacht. Dafur ist es aber recht schnell, denn fur Windows ist sogar ein eigener spezialisierter JavaComm-Treiber fur die serielle Schnittstelle enthalten, der die recht langsame Implementierung von Sun umgeht. 3.2.2 Sprachen fu r die Lego-Firmware Auf den Brick konnen leicht Programme geladen werden, die dort lokal zur Ausfuhrung gebracht werden. Dies geschieht entweder unter Verwendung von Spirit.ocx, oder "per Hand", da das Format, welches die Standard-Firmware von Lego verwendet, bekannt ist. Darauf werden nun viele Sprachen, bzw. Programmierumgebungen aufgesetzt. Am bekanntesten ist wohl NQC (http:// www.enteract.com/~dbaum/nqc/), das eine C-ahnliche Syntax und einen ebensolchen Praprozessor verwendet und einem praktisch alle Moglichkeiten eronet, die in der Original-Firmware von Lego existieren. Lego hat inzwischen auch eine eigene textbasierte Sprache namens MindScript die auf dem von Lego nun in der Version 2.0 ausgelieferten Betriebssystem fur den Brick basiert (http://mindstorms.lego.com/sdk2/). MindScript soll laut Lego zudem als Zwischenformat fur grasche Systeme verwendet werden. Die neue Lego-Firmware bietet kongurierbare Events und ist im Zusammenhang mit Variablen wesentlich leistungsfahiger geworden. Es gibt nun auch lokale Variablen innerhalb eines Tasks. Diese neuen Features werden auch von NQC unterstutzt. Fur die Programmierung nach dem Subsumption-Konzept ist die neu eingebaute Moglichkeit, den Zugri einzelner Tasks auf bestimmte Ressourcen abhangig von deren Prioritat zu regeln, besonders interessant. Listing 1: acquire(acquire turnMotor) { monitor(EVENT MASK(nullPositionReachedEvent) | EVENT MASK(maxPositionReachedEvent)) { PlayTone(3000, 20); SetDirection(turnMotor, rightDirection); On(turnMotor); ➥ 3.2. TEXTBASIERTE PROGRAMMIERSYSTEME Listing 1: 25 (Fortsetzung) do { Wait(10); sees light right(result); } until(result == 0); } catch { Off(turnMotor); } } catch { } Listing 1 zeigt die Verwendung von Events und der Zugriskontrolle in NQC. Es stammt aus dem Beispiel-Programm fur LiSe. Mit acquire wird auf die im Argument angegebene Ressource Zugri verlangt. Wenn kein anderer Task mit hoherer Prioritat Zugri auf diese Ressource verlangt, wird der Block hinter acquire ausgefuhrt, andernfalls springt die Ausfuhrung zu dem korrespondierenden catch-Block. Der Sprung zum catch erfolgt auch aus dem acquireBlock heraus, falls spater ein Task mit hoherer Prioritat die Ressource fur sich beansprucht. Tasks konnen also in gewisser Weise dadurch unterbrochen werden. Das monitor-Statement funktioniert ahnlich, zur genaueren Erklarung wird auf das Manual zu NQC verwiesen (Baum, 2001). 3.2.3 Systeme mit eigener Firmware Fur den Brick gibt es mehrere alternative Betriebssysteme (Firmware), welche man verwenden kann, um die Standard-Firmware von Lego zu ersetzen. Der erste, der es geschat hat ein von LEGO unabhangiges, lauahiges System im Internet bereitzustellen, war wohl Kekoa Proudfoot (Proudfoot, 1998). Zu den Systemen, die mit einer solchen Ersatz-Firmware kommen, zahlen vor allem: legOS von Markus L. Noga pbForth von Ralph Hempel leJOS von Jose L. Solorzano In diesem Abschnitt werden legOS und pbForth vorgestellt. leJOS wird, wegen seiner Java-Fahigkeiten, besonders ausfuhrlich in Abschnitt 3.2.4 auf Seite 27 behandelt. legOS Das Betriebssystem, welches { in Bezug auf Geschwindigkeit und Speicherhaushalt { auf dem Brick am besten abschneidet, ist legOS (http://legos. sourceforge.net/). Es wurde ursprunglich von Markus L. Noga entwickelt (Noga, 1999). legOS verwendet eine Version des GNU C-Compilers (gcc) die Code fur den Hitachi H8 Mikroprozessor des Brick erzeugt. Fur die Verwendung spezieller Peripherie des Brick, wie den Ein- und Ausgangen, IR-Port, LCD und Sound enthalt es entsprechende, spezialisierte Bibliotheken. 26 3. PROGRAMMIERSYSTEME FUR ROBOTER Wo es ging wurde versucht POSIX-Konform zu bleiben. Neue Prozesse werden mit fork() erzeugt und Zeichenketten mit cputs() ausgegeben, um nur ein paar Beispiele zu nennen. Naturlich gibt es auer der Speichergroe des Brick keine Beschrankungen in Bezug auf die Anzahl oder den Typ von Variablen. Die direkte Verwendung der Hardware, ohne irgendwelche Umwege, macht legOS so schnell, da es heit, man konnte sogar Sprache damit samplen { wenn einem nicht bereits nach ein paar Sekunden der Speicher ausgehen wurde. Die Tools von legOS zur Kommunikation zwischen Brick und PC, die man braucht um beispielsweise ein ausfuhrbares Programm zu laden, gibt es leider nur fur Windows und Linux. Sowohl MacOS und andere Unix-Systeme als Linux werden nicht unterstutzt, da die Tools dort wegen einer anderen I/OSchnittstelle bisher nicht laufen (Baum et al., 2000, S. 152). Neben der Geschwindigkeit ragt als Besonderheit auch noch die NetzwerkSchicht LNP3 hervor. Sie unterstutzt eine paketbasierte Kommunikation zwischen einem PC mit IR-Tower und mehreren addressierbaren Bricks. Was gegen legOS spricht, sind die Grunde, die auch gegen eine Verwendung von C auf einem Betriebssystem wie Linux sprechen. Es ist einfach schwieriger in C fehlerfrei zu programmieren, als das z.B. mit Java der Fall ist. Wenn es also auf eine extrem hohe Geschwindigkeit nicht ankommt und z.B. der Interaktivitat oder dem Software-Design ein groerer Stellenwert eingeraumt wird, dann haben die anderen Systeme wie pbForth und leJOS die Nase vorn. pbForth Forth ist eine bereits recht alte Programmiersprache, die speziell dafur entwickelt wurde, um Computer mit wenig Ressourcen zu bedienen (Baum et al., 2000, S. 70). Forth gibt es daher fur eine Vielzahl von kleinen Computersystemen, doch auch fur z.B. Linux ist Forth (gForth) verfugbar. Das besondere an pbForth (http://www.hempeldesigngroup.com/lego/ pbForth/homePage.html) gegenuber allen anderen Betriebssystemen fur den Brick ist seine Interaktivitat. Wenn man den Brick mit pbForth programmiert, wird ein PC nur als Terminal fur die Konsole von pbForth verwendet. Alles, was man tippt, landet direkt im Brick und wird dort Stuck fur Stuck interpretiert. Das eronet vollig neue Moglichkeiten zum Testen, da man alles direkt "online" ausprobieren kann und sofort eine Ruckmeldung bekommt. In Forth verwendet man sogenannte words um Werte auf einem Stack zu manipulieren. Diese Art zu programmieren ist im Vergleich zu "normalen" Programmiersprachen recht unkonventionell und erfordert daher in vielen Fallen eine ganz andere Denkweise. Da Forth interaktiv ist kann man sehr bequem ausprobieren. Dadurch macht das Lernen mehr Spass. Man kann neue Funktionen in Form von weiteren words hinzufugen. Mehrere Threads sind nur mittels kooperativem Scheduling moglich und erfordern daher eine sehr sorgfaltige Planung. Da pbForth eine Stack-Maschine ist, musste man im Prinzip auch andere Sprachen fur pbForth u bersetzen konnen. 3 'NP' steht fur Networking Protocol, u ber die Bedeutung des 'L' gibt es hingegen je nach Quelle unterschiedliche Auassungen (Lego, legOS, layered, . . . ). 3.2. TEXTBASIERTE PROGRAMMIERSYSTEME 3.2.4 27 leJOS Auf der ROM interface library librcx von Kekoa Proudfoot (Proudfoot, 1998) beruht auch die Variante eines Betriebssystems fur den Brick die mit Java programmiert werden kann: leJOS (http://lejos.sourceforge.net/).4 Es gibt noch mindestens ein weiteres Projekt um Java direkt auf dem Brick ausfuhrbar zu machen, doch ist es noch in einem sehr unvollstandigen Stadium (RCXJVM, http://misc.traveller.com/rcxjvm/). Es sieht zudem so aus, als hatte sich dort schon seit langerer Zeit nichts mehr getan. Der Vorlaufer von leJOS heit tinyVM und wird nicht mehr, bzw. nur noch minimal weiterentwickelt. tinyVM enthalt weniger Features, hat dafur aber auch einen etwas kleineren footprint : 10KB, gegenuber 16KB fur leJOS. Da der Brick insgesamt nur u ber 32KB Speicher verfugt und das ROM (bzw. das Betriebssystem) auerdem eine gewisse Menge zusatzlichen Speicher braucht, bleiben fur ein Anwendungsprogramm unter leJOS nur etwa 10KB Speicher u brig. Fur das komplette hier vorgestellte Beispielprogramm fur Ariadne ist das bereits zu knapp, doch dazu am Ende dieses Abschnitts mehr. Inzwischen (Version 1.0.4.alpha) existiert in der Distribution von leJOS ein neues package (josx.robotics) welches Klassen zur Navigation eines Roboters und zur Programmierung nach dem Subsumption-Konzept (siehe 1.2 auf Seite 4) enthalt. Die Beispielprogramme fur Trusty, LiSe und Ariadne verwenden diese nicht, da bereits eigene (wenn auch einfachere) Klassen fur diese Funktionalitat vorhanden waren. Nichtsdestotrotz zeigt es, da die im Rahmen dieser Arbeit verwendeten Beispiele durchaus typische Aufgaben und Probleme adressieren. Modularisierung & Wiederverwendung Wenn man einmal von den geringen Ressourcen absieht, die einem unter leJOS zur Verfugung stehen, hat man (fast) alle Moglichkeiten die einem die Sprache Java bietet. Verstandlicherweise ist aber nicht das komplette API von Java implementiert, sondern nur die notigsten Klassen und auch dort nur die wichtigsten Methoden. Es ist aber kein Problem, mehrere Klassen zu erstellen. Alle notwendigen Java-Features sind vorhanden, auch innere Klassen sind kein Problem (was aber, angesichts der Tatsache, da innere Klassen ein Compiler-Feature und kein Runtime-Feature von Java sind, auch eher verwundern wurde). Das Einzige was einem im Weg steht ist der begrenzte Speicher und manche Limitierung des API zum Zugri auf die besondernen Eigenschaften des Brick, wie z.B. die Motoren oder Sensoren (package josx.platform.rcx). Als Basis fur mobile Roboter kann z.B. die folgende Klasse DriveTrain verwendet werden: 4 Und es ist wahrscheinlich das einzige Betriebssystem fur den Brick, das man mit Fug und Recht als "weltraumtauglich" bezeichnen kann. Siehe Artikel in der Online-Ausgabe der Suddeutschen Zeitung vom 14.12.2001: http://www.sueddeutsche.de/computer/neuetechnik/hardware/32701 28 3. PROGRAMMIERSYSTEME FUR ROBOTER Listing 2: code/leJOS/DriveTrain.java public class DriveTrain { // note that this is not a josx.platform.rcx.Motor protected Motor leftMotor; protected Motor rightMotor; public DriveTrain(Motor leftMotor, Motor rightMotor) { this.leftMotor = leftMotor; this.rightMotor = rightMotor; } public void forward() { leftMotor.forward(); rightMotor.forward(); } public void backward() { leftMotor.backward(); rightMotor.backward(); } public void leftSpin() { leftMotor.backward(); rightMotor.forward(); } public void rightSpin() { leftMotor.forward(); rightMotor.backward(); } public void stop() { leftMotor.stop(); rightMotor.stop(); } } In DriveTrain wird die Koordination von zwei Motoren in der Anordnung wie bei Trusty gekapselt. Sie bietet einfacher verwendbare und verstandlichere Methoden wie forward(), um den Roboter vorwarts zu bewegen, oder leftSpin() um den Roboter linksherum um die eigene Achse zu drehen. Doch bereits wenn man die Motoren andersherum gepolt anschliet, funktioniert diese Klasse nicht mehr, denn forward() ist dann ja die andere Drehrichtung. Hat man bei der Polung aus irgendeinem Grund nicht die Wahl (ganz profan: Das Kabel kann fur eine andere Polung manchmal zu kurz sein), mochte man im Grunde DriveTrain dafur nicht editieren mussen. Es ware besser die Polung in Software zu kapseln, indem man einem Motor mitteilen kann, welche Drehrichtung "forward" sein soll. Die in leJOS verhandene Klasse zur Ansteuerung eines Motors (josx.platform.rcx.Motor) bietet eine solche Moglichkeit nicht. Eine u bliche Vorgehensweise um das nachzurusten ware, eine Unterklasse von josx.platform.rcx. 3.2. TEXTBASIERTE PROGRAMMIERSYSTEME 29 Motor zu entwickeln. Sie besitzt eine neue Methode wie z.B. setReversed(boolean) um die Polung anzugeben und leitet die Aufrufe wie forward() an die jeweils passende backward()- oder forward()- Methode ihrer Unterklasse wei- ter. Da aber alle Methoden von josx.platform.rcx.Motor final deklariert sind, ist dieser Weg verbaut. Den Grund fur die Wahl von final kann man wohl folgendem Auszug aus den "performance-tips" von tinyVM entnehmen: "Declare your methods to be either private, nal or static whenever possible." Man kann Instanzen von josx.platform.rcx.Motor auch gar nicht selbst erzeugen, da der einzige Konstruktor der Klasse private deklariert ist und es keine Klassenmethode gibt, die eine indirekte Konstruktion ermoglichen wurde. Es existieren nur die drei Klassenvariablen A, B und C (jeweils vom Typ josx. platform.rcx.Motor) { eine f ur jeden Ausgang des Brick. Das Erstellen einer Unterklasse ist daher nicht moglich, da diese auf wenigstens einen Konstruktor zugreifen konnen musste. Die Klasse josx.platform.rcx.Motor hatte also ebensogut final deklariert werden konnen. Aus diesem Grund wurde eine komplett neue Klasse geschrieben, die nicht von josx.platform.rcx.Motor abstammt und daher auch nicht verwendet werden kann, als ware sie ein josx.platform.rcx.Motor { ein wichtiger Mechanismus der Objektorientierung ist damit ausgehebelt. Diese Klasse hat einen josx.platform.rcx.Motor als Ziel, an den sie alle benotigten Methoden explizit weiterleitet und in Bezug auf die Drehrichtung die erwahnte Anpassung vornimmt. Leider sind diese Probleme auch bei der Sensor-Klasse, sowie der Klasse Button, mit der die vier Knopfe des Brick abgefragt werden konnen, vorhanden. Die meisten anderen Klassen aus dem package josx.platform.rcx, wie z.B. zum Zugri auf das eingebaute Display oder zur Ausgabe von Tonen, bieten nur Klassenmethoden und sehen keine Instanz vor. Das heit, Modularisierung ja, aber Wiederverwendung nur eingeschrankt, da Rechenzeit und Speicher so knapp ist, da dafur oenbar nicht alle Moglichkeiten, die Java bietet, ausgeschopft werden (konnen). Threads & Synchronisierung Die Klasse java.lang.Thread ist in leJOS vorhanden und bietet nicht nur die Moglichkeit neue Threads zu erzeugen, sondern man kann diesen auch verschiedene Prioritaten zuweisen, sie unterbrechen und ein Thread kann auch uber yield() oder sleep() in der Ausfuhrung zwischendurch anhalten. Es gibt sogar daemon -Threads, welche die virtuelle Maschine nicht am laufen halten und somit dafur gedacht sind, "unbemerkt" im Hintergrund ablaufen zu konnen. Verschiedene Threads werden in Java uber einen Mechanismus synchronisiert, bei dem ein Thread einen sogenannten Monitor fur ein Objekt besitzen kann. Diesen Monitor kann zu einem Zeitpunkt nur ein Thread besitzen, womit die Synchronisierung bewerkstelligt wird. Den Monitor fur ein Objekt bekommt ein Thread, wahrend er einen synchronized-Block ausfuhrt, bzw. wenn er eine 30 3. PROGRAMMIERSYSTEME FUR ROBOTER Methode ausfuhrt, die mit dem Schlusselwort synchronized deklariert wurde. Das Objekt, auf das synchronisiert wird, ist im Falle der synchronizedMethode das Objekt dessen synchronized-Methode gerade ausgefuhrt wird, bzw. das Klassenobjekt der Klasse die die Methode implementiert, wenn es sich um eine Klassenmethode handelt. Im Falle des synchronized-Block wird das Objekt, auf das synchronisiert werden soll, explizit angegeben. Da es in leJOS keine Klassenobjekte gibt, wird das Synchronisieren auf ein solches, z.B. uber eine synchronized deklarierte Klassenmethode, nicht funktionieren. Die Methoden wait() und notify() sind seit Version 1.0.2 vom 4.9.2001 implementiert, womit einer vollstandigen Ausnutzung der Synchronisationsmoglichkeiten von Java nun nichts mehr im Wege steht. Fur die Beispielimplementierung von Ariadne wurde aber noch keine explizite Synchronisierung verwendet, so da hier dafur kein Beispiel gezeigt wird, solche mussten sich aber eigentlich in jedem Lehrbuch zu Java nden lassen. Um einen Algorithmus mit Subsumption (siehe 1.2 auf Seite 4) zu programmieren bieten sich wait() und notify() an. Wer daran Interesse hat, kann z.B. die fur Subsumption im Beispiel verwendete Klasse SubsumptionScheduler entsprechend umschreiben. Events Fur die Knopfe und fur die Sensoren des Brick gibt es in leJOS die Moglichkeit ahnlich wie beim AWT von Java sogenannte ButtonListener, bzw. Sensor Listener zu verwenden. Sobald eine Anderung des Werts des jeweiligen Sensors aufgetreten ist, bzw. der entsprechende Knopf gedruckt oder losgelassen wurde, wird die in den Listener-Interfaces denierte Methode aufgerufen. Im Unterschied zum AWT gibt es aber keine eigene Klasse, die die zum Event gehorenden Daten reprasentiert, sondern es wird nur die Quelle des Events, sowie bei den SensorListenern der alte und der neue Wert u bergeben. Der Aufruf der Listener-Methoden erfolgt aus einem Thread heraus, welcher automatisch erzeugt wird, wenn er wegen des Vorhandenseins von Listenern benotigt wird. Da die Methoden wait() und notify() inzwischen implementiert sind, existieren optimale Voraussetzungen, um weitere Threads auf beliebige Ereignisse warten zu lassen, ohne da sie unnotig Rechenzeit verbrauchen. Dies kann man z.B. verwenden, wenn man beim Auftreten eines bestimmten Ereignisses eine langere Berechnung "anstoen" mochte. Wie beim AWT auch, sollten namlich die Listener-Methoden recht zugig zuende gehen, da fur mehrere Listener nur ein einziger Thread vorhanden ist. Ein Design von LiSe mit mehreren Listenern (jede der Verhaltensweisen hat einen "eigenen" fur die von ihr verwendeten Sensoren) stot an eine Grenze. Die Klassen Button und Sensor erlauben nur maximal vier Listener. Damit wird aber eine Modularisierung erschwert, denn wenn ein anderes Modul auch Listener verwendet, so konnen es schnell mehr als vier werden, wenn man nicht genau aufpasst, welches Modul fur welchen Sensor wieviele Listener registriert. Damit werden die Grenzen zwischen den Modulen "verschmiert", da sie im Grunde ungewollt aufeinander Einuss nehmen. Daher ist es bei Algorithmen in Bezug auf die mogliche Wiederverwendung besser, weitestgehend auf die Verwendung von 3.2. TEXTBASIERTE PROGRAMMIERSYSTEME 31 Listenern zu verzichten, bzw. einen eigenen Event-Mechanismus zu implementieren (z.B. mittels der Klasse Poll, die so etwas wie den Unix-Systemaufruf select f ur Sensoren darstellt). Ariadne Der Algorithmus fur den Beispielroboter ist in Klassen fur Trusty, LiSe und Ariadne aufgeteilt, sowie in drei weitere Klassen. Dies sind die bereits gezeigte Klasse DriveTrain, zur Kapselung des Antriebsmechanismus von Trusty, die bereits erwahnte Klasse Motor, die einen "umpolbaren" Motor reprasentiert, und eine Klasse zur Koordination der einzelnen Verhaltensweisen bei Subsumption (siehe Abschnitt 1.2 auf Seite 4). Sowohl Trusty als auch LiSe sind nach dem Subsumption-Konzept programmiert worden. Einzeln funktionieren LiSe und Trusty auch sehr gut. Es ist auch sehr schon moglich, Trusty und LiSe fur Ariadne wiederzuverwenden, doch leider ist die Implementierung von Ariadne so nicht lauahig { leJOS geht schlicht der Speicher aus. Man kann Ariadne aber trotzdem ausfuhren. Kurz bevor es zu dem OutOfMemoryError kommt, halt die Ausf uhrung an und gibt auf dem Display des RCX das Resultat von System.freeMemory() aus. Nach einem Druck auf die "Prgm"-Taste (nach einer kurzen Wartezeit) wird das Ergebnis von System. totalMemory() ausgegeben. Weiteres Drucken auf "Prgm" (mehrmals) fuhrt dazu, da letztenendes die Ausfuhrung bei dem OutOfMemoryError ankommt. Dabei wird ganz rechts im Display des Brick die Zahl, die normalerweise die Nummer des aktuellen Programms anzeigt, hochgezahlt. So kann man, wenn man dabei den Code der Methode start() in Ariadne.java anschaut, ganz gut erkennen wo der OutOfMemoryError auftritt: Listing 3: code/leJOS/Ariadne.java public void start() { // just show memory and ’step’ by pressing the PRGM-Button // because this method results in an OutOfMemoryError // on leJOS 1.0.4.alpha LCD.showProgramNumber(1); LCD.showNumber((int)System.getRuntime().freeMemory()); try { Button.PRGM.waitForPressAndRelease(); } catch (InterruptedException ie) { } LCD.showProgramNumber(2); LCD.showNumber((int)System.getRuntime().totalMemory()); try { Button.PRGM.waitForPressAndRelease(); } catch (InterruptedException ie) { } LCD.showProgramNumber(3); try { lise.start(); } catch(OutOfMemoryError e) { LCD.showProgramNumber(4); ➥ 32 3. PROGRAMMIERSYSTEME FUR ROBOTER Listing 3: code/leJOS/Ariadne.java (Fortsetzung) try { Button.PRGM.waitForPressAndRelease(); } catch (InterruptedException ie) { } throw e; } try { super.start(); } catch(OutOfMemoryError e) { LCD.showProgramNumber(5); try { Button.PRGM.waitForPressAndRelease(); } catch (InterruptedException ie) { } throw e; } } Die Beispiele wurden nicht auf Speicherverbrauch optimiert entwickelt. So wird z.B. in der Implementierung von Ariadne Speicher "verschwendet", weil Instanzenvariablen von Trusty neue Objekte zugewiesen bekommen. Die alten Objekte konnten im Grunde aus dem Speicher verschwinden, da sie nicht mehr gebraucht werden, doch in leJOS ist keine garbage collection implementiert. Die fehlende garbage collection vereinfacht die Realisierung von leJOS, vor allem in Bezug auf vorhersagbare Ausfuhrungs- und damit auch Reaktionszeiten. Dies steht aber einer modularen, objektorientierten Entwicklung etwas im Weg, denn man darf z.B. nicht fur jedes Ereignis ein neues Objekt erzeugen. Der Algorithmus fur die garbage collection wird oft mit mark-sweep realisiert und darf in diesem Fall, wenn die garbage collection erst einmal in Bewegung gesetzt wurde, nicht mehr unterbrochen werden. Dieses Problem ist fur Java in Bezug auf Echtzeitfahigkeit nicht ungewohnlich (Brich et al., 2000). Auf den Abdruck des restlichen Codes wurde verzichtet. Dieser Abschnitt ist ja auch nicht als Tutorial fur die Benutzung von leJOS gedacht. Der interessierte Leser mag aber vielleicht in die beiliegenden Quellen schauen und seine eigenen Versuche mit leJOS durchfuhren. Ob es wohl moglich ist, einen in eigenstandige Trusty- und LiSe-Klassen aufgeteilten Algorithmus zu schreiben, der fur Ariadne wiederverwendet werden kann, so da trotzdem der Speicher reicht? 3.3 Vorlauges Fazit Es ist bereits moglich Roboter objektorientiert zu programmieren. Mogliche Systeme fur Lego und Fischertechnik sind z.B. Spirit.ocx/Fishface (Windows/VB, VC++), legOS (C++), leJOS (Java). Dies gilt aber nur fur die grundsatzlichen Moglichkeiten, die die jeweiligen Sprachen bieten. In Bezug auf die Schnittstelle zum Roboter werden die Moglichkeiten von Objektorientierung nicht ausgenutzt. Nur leJOS, legOS und das Java-package "ft" bieten echte Klassen fur 3.3. VORLAUFIGES FAZIT 33 Sensoren und Motoren. Bei leJOS ist aber z.B. keine wirkliche Unterst utzung fur Unterklassen davon vorhanden und legOS hat noch nicht einmal richtige Motor-Klassen. Das ft-package wiederum hat zwar vernunftige Klassen fur Sensoren und Motoren, bietet aber nur die allernotigtste Funktionalitat und keine Unterscheidung zwischen verschiedenen Sensoren oder anderen an die Ausgange angeschlossenen Aktuatoren. Die Unterstutzung eines Event-Mechanismus nimmt hingegen zu. Selbst das Standard-System von Lego hat seit der neuesten Verion (RCX 2.0) eine Form von Events integriert, so da alle darauf beruhenden Systeme wie z.B. MindScript und NQC die Verwendung von Events unterstutzen. Die Verwendung von Threads ist teilweise recht einfach umgesetzt (LLWin/leJOS) und wird von praktisch allen Systemen erlaubt. Leider existieren aber meistens nur unzureichende Kontrollmechanismen (Synchronisierung). Andere Systeme als die Java-basierten bieten zwar auch Synchronisierung, sind aber schwieriger zu benutzen (pbForth/legOS). Wenn die jeweilige Sprache eine Modularisierung und Abstrahierung unterstutzt, ist diese, solange es auf Performance oder Groe wirklich ankommt, wenn uberhaupt nur unsauber zu realisieren (siehe Performance-Tips zu tinyVM: http://tinyVM.sourceforge.net/userguide.html). Gerade die graschen Systeme sind jedoch einfach nicht fur sehr umfangreiche Software-Projekte ausgelegt. Wird die Software lokal auf dem Brick ausgefuhrt, wiegen Faktoren wie verfugbarer Platz und Rechenkapazitat in vielen Fallen auch schwerer, d.h. um moglichst weitgehende Modularisierung und Abstraktion zu ermoglichen, ist fur die Roboterhardware von Lego und Fischertechnik eine Steuerung von einem externen Computer aus notwendig. In Bezug auf die Hardware-Unabhangigkeit der Systeme kommt es zum Teil auf die Sichtweise an: iCon-L/LabView gibt es fur verschiedene Hardware und viele Systeme isolieren die hardware-abhangige Schnittstelle, wie z.B. leJOS, das dafur ein separates package (josx.platform.rcx) enthalt. Die eigentliche Schnittstelle zu der Hardware ist aber nirgendwo abstrahiert. Es wird immer fur ein bestimmtes Zielsystem programmiert, da die Verwendung von Motoren und Sensoren immer unterschiedlich und sehr auf die konkrete Hardware bezogen umgesetzt ist. Naturlich kann man einige der Systeme mit viel Eigenarbeit auch benutzen um portabel zu programmieren, man wird dabei aber nicht gezielt unterstutzt und wurde, bei den lokal auf dem Brick ausgefuhrten Systemen, auch schnell an Kapazitatsgrenzen stoen. 34 3. PROGRAMMIERSYSTEME FUR ROBOTER 4 Die Idee des Abstract Robot Toolkit Wenn in dieser Arbeit von Robotern die Rede ist, so ist das in gewisser Weise sehr allgemein gemeint. Zu dem Begri Roboter wei die Encyclopdia Britannica (www.britannica.com) Folgendes zu sagen: "Any automatically operated machine that replaces human effort, though it may not resemble human beings in appearance or perform functions in a humanlike manner. The term is derived from the Czech word robota, meaning 'forced labour'. Modern use of the term stems from the play R.U.R., written in 1920 by the Czech author Karel Capek, which depicts society as having become dependent on mechanical workers called robots that are capable of doing any kind of mental or physical work." Nach dieser Denition kann bereits ein Computer als Roboter aufgefat werden. Ein Roboter im Sinn dieser Arbeit besitzt Sensoren (wie z.B. einen Licht- oder einen Beruhrungssensor), mit denen die Umwelt wahrgenommen wird, und Aktuatoren (wie z.B. einen Motor oder eine Lampe), mit denen auf die Umwelt Einu genommen werden kann. Die Aktuatoren werden von einem Algorithmus gesteuert, der mehr oder weniger von den Messungen der Sensoren abhangt. Diese Sichtweise auf das, was einen Roboter ausmacht, pat gut zu Robotern wie man sie mit den Lego- und Fischertechnik-Baukasten bauen kann. Die Baukasten enthalten Sensoren und Motoren und einen kleinen Computer fur den Steuerungsalgorithmus, bzw. ein Hardware-Interface, uber das ein "richtiger" PC die Steuerung u bernehmen kann. Interessanterweise ist dieses Konzept der Sensoren und Aktuatoren im Grunde auch auf jede andere Maschine anwendbar, die genau genommen sogar nicht einmal computergesteuert sein mu. Diese Auassung steht einem auch nicht im Weg, wenn man sich von dem externen Hardware-Interface entfernt und wieder den Computer an sich bereits als Roboter auat. Im Grunde sind eine Tastatur und eine Maus auch nichts weiter als Sensoren, genauso wie ein Bildschirm, Drucker oder Lautsprecher im Prinzip als Aktuatoren aufgefat werden konnen. Doch um die Arbeit nicht ausufern zu lassen, werden Sensoren und Aktuatoren 35 36 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT RobotInterface keeps state Actuator Output1 Output2 setValue(...) informs about state−change in sync Input1 Input2 Algorithm Sensor may depend on getValue() addListener(...) Robot (Hardware) setting of output−state input−state Classes (Software) Abbildung 4.1: Regelkreis, wie er durch die Modellierung des Roboters in Software entsteht. vorerst hauptsachlich im engeren, "greifbareren" Sinne verwendet und es wird nicht genauer untersucht, was diese Sichtweise fur die Programmierung eines Computers an sich bedeuten kann. Ziel der Arbeit ist es, ein System zur Roboter-Programmierung zu schaen, mit dem Software-Problemlosungen fur Roboter moglichst weitgehend wiederverwendbar sind. So wie in dem Lego-Baukasten viele Bausteine enthalten sind, die sich auf vielfaltige Weise immer wieder neu zusammenstecken lassen, sollen in dem Programmiersystem Software-Bausteine { als Objekte realisiert { vorhanden sein, die sich ebenso zusammenstecken lassen. Auerdem soll ein einmal gebautes "Ding" mit einer bestimmten Funktion (z.B. ein Chassis fur ein Fahrzeug) als Modul wiederverwendet werden konnen. In Hardware mu man das zwar meistens immer wieder neu bauen, da der Vorrat an Bausteinen normalerweise beschrankt ist und man die Steine daher zwischendurch fur etwas anderes gebraucht hat, bei Software ist es aber leicht (und billig) etwas einmal "Gebautes" aufzuheben, fur den Fall, da man es noch einmal brauchen kann. Der Steuerungsalgorithmus wird sehr gut wiederverwendbar, wenn moglichst weit von der konkreten Hardware abstrahiert wird. Dies fuhrt zwar praktisch immer zu einem groeren Hardware-Aufwand, bzw. einer langsameren Steuerung, entspricht aber einer Entwicklung, wie sie auch in anderen Bereichen der Softwareentwicklung { wie zum Beispiel bei Programmiersprachen { stattndet, bzw. stattgefunden hat. Es ist einfach wesentlich produktiver, Software in einer Umgebung wie Java zu entwickeln, als Maschinencode fur einen bestimmten Prozessor von Hand zu schreiben. Ein groer Teil der Rechenkapazitat heutiger Computer wird in diesem Sinne einer schnelleren und beherrschbareren Softwareentwickung "geopfert", wodurch aber komplexere Software erst ermoglicht wird. 4.1 Tutebot Kommt es mehr auf Geschwindigkeit statt auf Flexibilitat und Wiederverwendbarkeit an, so konnte man auch ganz auf einen Computer verzichten. Ein Roboter, dessen "Steuerungsalgorithmus" rein aus einer Schaltung von diskreten Bauelementen besteht, wird in Jones et al. (1998) beschrieben. Die fertig aufgebaute Schaltung, montiert auf einem Fischertechnik-Chassis, ist in Abbil- 4.1. TUTEBOT 37 dung 4.3 auf der nachsten Seite dargestellt; Abbildung 4.2 zeigt das SchaltkreisDiagramm. Mit dieser Schaltung lat sich ein Roboter wie Trusty steuern, der mittels Tastsensoren registrierten Hindernissen ausweicht. Die Steuerung ist aber nur fur einen einzelnen Sensor ausgelegt, so da nicht zwischen einem Hindernis auf der rechten oder der linken Seite unterschieden wird. Diese Verhaltensweise wird auch als wall-following bezeichnet. Abbildung 4.2: Der Steuerungs-Algorithmus von Tutebot ist mit dieser diskreten Schaltung realisiert. (entnommen aus Jones et al. (1998)) Solch eine Robotersteuerung durch eine diskrete Schaltung ist sozusagen die primitivste Form eines Algorithmus. Sie reagiert sehr schnell, ist billig, verbraucht wenig Energie und nimmt kaum Platz ein. Dafur ist es aber nicht einfach, sich solch eine Schaltung auszudenken (vor allem dann, wenn die Steuerungen komplizierter werden), und sie ist sehr unexibel, was Erweiterungen oder Anderungen angeht. Auerdem steht man vor dem gleichen Problem, das man bereits mit den Lego-, bzw. Fischertechnik-Bausteinen hat: Der Vorrat an Bauteilen ist begrenzt und um neue Schaltungen zu bauen, mu man entweder die alten zerstoren, um an ihre Bauteile zu kommen, oder man mu standig neue Bauteile nachkaufen. Hebt man also viele solche "Algorithmen" auf, um sie spater wieder zur Verfugung zu haben, geht das entweder nur als Bauplan oder ist mit erheblichen Kosten verbunden, da man standig neue Bauteile kaufen mu. Wenn also auch die Entwicklungszeit wichtig oder der Algorithmus sehr 38 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT Abbildung 4.3: Die Weie Platine ist der "Steuerungsalgorithmus" des Tutebot aus Jones et al. (1998). Rechts oben im Bild sieht man die Sensoren, mit denen der Roboter z.B. Tischkanten registrieren kann. komplex ist, es einfacher sein soll Anderungen vorzunehmen oder einmal erstellte Algorithmen (fur eine eventuelle spatere Verwendung) aufgehoben werden sollen, nimmt man einen "richtigen" Computer. In der Verbindung zu den Sensoren und Aktuatoren wird auch dort wieder auf verschiedenen Abstraktionsebenen agiert. So kann man direkt die elektrischen Signale (bzw. das Protokoll) programmieren, um die Interface-Hardware dazu zu veranlassen, zum Beispiel einen Motor an- und auszuschalten. Haug gibt es auch eine SoftwareSchnittstelle, die von den genauen elektrischen Signalen, bzw. dem Kommunikationsprotokoll, abstrahiert. Diese sind dann aber immer noch sehr stark an die konkrete Hardware angelehnt. So hat man zum Beispiel bei der Verwendung des Active-X Control Spirit.ocx von Lego eine Funktion zur Steuerung des Power-Level eines Motors zur Verfugung, welche es bei den FischertechnikTreibern FishFace oder 30402Drive (die noch am ehesten als mit Spirit.ocx vergleichbar angesehen werden konnen) nicht gibt, da die Interface-Hardware von Fischertechnik eine Drosselung der Motoren nicht direkt vorsieht. Mit dem Abstract Robot Toolkit soll noch weiter abstrahiert werden, um z.B. zu einem Motor immer die gleiche Schnittstelle zur Verfugung stellen, egal ob dieser nun von Lego, Fischertechnik oder irgendeiner anderen Hardware gesteuert wird. Das erfordert naturlich, sich daruber Gedanken zu machen, was zum Beispiel einen Motor auszeichnet. Welche Funktionalitat soll ein Motor z.B. immer aufweisen und welche nur optional ? Welche Basisfunktionalit at ist notwendig um alles weitere darauf aufbauend "in Software" realisieren zu k onnen? Dies alles kostet naturlich Rechenzeit und Speicherplatz im Computer. Auerdem wird der komplette Vorgang weniger transparent, denn es ist nicht mehr 4.2. FISCHERTECHNIK UND LEGO MINDSTORMS 39 so leicht, wirklich alles, jede Komponente, die an der Steuerung beteiligt ist, zu verstehen. In gewisser Weise ist das eine ahnliche Entwicklung wie bei Programmiersprachen: von Assembler u ber C bis Java. 4.2 Fischertechnik und Lego Mindstorms Axel Schreiner entwickelte im Sommer 1998 eine Art "Java-Treiber" fur das parallele Interface von Fischertechnik und stellte dabei fest, da sich Konzepte wie Events, Objekte und Threads fast schon auf naturliche Weise im Zusammenhang mit den Fischertechnik-Robotern verwenden lieen. Bereits kurze Zeit spater kam Lego Mindstorms auf den Markt. Im Gegensatz zu dem ersten Computer-Interface von Fischertechnik, das bereits 1984 auf den Markt kam, entwickelte sich sehr schnell eine groe Fan-Gemeinde aus vielen Teilen der Welt. Es entstanden recht schnell mehrere, von Lego unabhangige Programmiersysteme fur den Brick, von denen vor allem NQC und legOS inzwischen eine groe Bekanntheit und Verbreitung erreicht haben. Viele Dinge, wie zum Beispiel die Verbindung der Steuerung eines LegoRoboters mit einem Programm auf einem normalen PC, kann man mit NQC oder mit dem im RIS mitgelieferten RCX-Code nicht bewerkstelligen. Dies ist zwar mit dem von Lego veroentlichten Active-X Control Spirit.ocx moglich, aber nur unter Windows, mit einer der von Microsoft zur Verfugung gestellten Sprachen wie Visual Basic, Visual C++ usw. Auch legOS enthalt inzwischen eine integrierte Kommunikations-Schnittstelle, das LNP (siehe Abschnitt 3.2.3 auf Seite 25). Im Internet war auch Java-Code zu nden (Dario Laverde, "RCX Java API", http://www.escape.com/~dario/java/rcx/) mit dem man den Brick steuern konnte. Das ging zwar nur sehr rudimentar, weil man immer noch die Bytecodes kennen mute, die der Brick versteht, aber im Gegensatz zu der Ansteuerung des Fischertechnik-Interface war man nicht einmal auf plattformabhangigen C-Code angewiesen, da es bereits das damals noch relativ neue Java Communications API verwendete. Die Bytecodes konnte man der Webseite von Kekoa Proudfoot entnehmen (Proudfoot, 1998). Mit all diesen Informationen war es schlielich moglich, eine Lego-Umsetzung fur das im Wintersemester 1999/2000 in der Vorlesung "Oberachen programmieren" verwendete Beispiel "Niki, der Roboter"1 , zu entwickeln. Das eigentliche Beispiel diente dazu, objektorientierte Design-Pattern und deren Anwendung in der Oberachenprogrammierung zu demonstrieren. Daher ist der Roboter dort { rein virtuell { ein Objekt, welches gewisse Aktionen ausfuhren und unter anderem mit einer graschen Oberache dargestellt und gesteuert werden kann. Die Lego-Umsetzung wurde in dieses System als eine weitere View (siehe Abschnitt 4.7 auf Seite 56) fur das Roboter-Objekt integriert, welche die Aktionen des virtuellen Roboters in der Wirklichkeit nachvollzieht. Dies geschah alles in Java, da das zu diesem Zeitpunkt bereits die in dieser Vorlesung 1 Niki beruht seinerseits auf "Karel the Robot", einer Idee die ursprunglich aus Pattis (1981) stammt. 40 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT hauptsachlich verwendete Programmiersprache war. Doch die Ansteuerung von Lego aus Java heraus war mit viel Arbeit verbunden. Daraus entstand das Bedurfnis einen vernunftigen, universell wiederverwendbaren "Java-Treiber" auch fur Lego zu entwickeln. Da dies nicht an einem Nachmittag zu bewerkstelligen ist, war spatestens dann klar, als zudem eine elegante Reprasentierung der Elemente eines Roboters in Objekten erreicht werden sollte. Es ging also gleich um eine ganze Sammlung von Objekten { eine Klassenbibliothek, bzw. ein objektorientiertes API. Das Fischertechnik-Interface ist aber viel zu verschieden von dem Brick, als da es einfach moglich gewesen ware, die Klassen des ft-package fur Fischertechnik in dieser Form auch fur Lego zu verwenden. Von Java war man aber bereits gewohnt, da man Software nicht fur jede Plattform neu entwickeln mu. Daher war klar, da das API auf eine Weise realisiert werden sollte, die es erlaubt, sowohl fur Lego als auch fur Fischertechnik die gleichen Klassen zu benutzen. Das Ziel war nun die Schaung einer abstrahierten Schnittstelle zur Steuerung von Robotern. Dabei sollten die Vorteile objektorientierter Programmierung ausgenutzt werden. Die Unterstutzung fur eine ereignisgesteuerte Programmierung, wie sie z.B. auch das Event-Modell des AWT vorsieht, verstand sich in diesem Zusammenhang beinahe von selbst. Da dafur dann mehrere Threads verwendet werden (mussen) ist klar, sofern eine Programmierung nicht nur noch ereignisgesteuert m oglich sein sollte. Grundsatzlich ist die Idee bei Ereignissen (Events) ja auch die, da sie im Prinzip zu jeder Zeit, also auch gleichzeitig und nicht nur nacheinander auftreten konnen. Fur ein (scheinbar) gleichzeitiges Auftreten und vor allem fur eine gleichzeitige, dabei aber voneinander unabhangige Behandlung von Events sind Threads daher unerlalich. Die Implementierung sollte fur Lego und fur Fischertechnik erfolgen, da dies die vorhandene Hardware war. Dabei sollte aber darauf geachtet werden, da die entwickelten Abstraktionen, Modelle und Abhangigkeiten eine Implementierung fur andere Plattformen weitgehend oen halten. Da die Hardware von Lego und Fischertechnik relativ verschieden ist, ist deren Wahl (auch wenn sie mehr pragmatische Grunde hatte) relativ gut, um die entwickelte Abstraktion auf ihre Tauglichkeit zu testen. 4.3 Java Die Implementierung des Abstract Robot Toolkit(ART) wurde in Java realisiert. Die entwickelten Abstraktionen von den Bestandteilen eines Roboters sind aber im Prinzip von Java unabhangig, sie sollten sich auch in anderen objektorientierten Sprachen umsetzen lassen. Java besitzt jedoch den Vorteil, da es ahnliche Ziele verfolgt wie die, die hinter der Enwicklung ART stehen, denn Java ist "einfach, objekt-orientiert, [. . . ] architektur-neutral, multi-threaded, garbage collected, robust, [. . . ] erweiterbar und klar verstanden. Vor allem aber macht Java Spa!" (Entnommen aus Schreiner (2000a), dort nach van Ho, Shaio und Starbuck). Einigen dieser Punkte wird man in Bezug auf Java nicht unbedingt in vollem Umfang zustimmen. Die Vor- und Nachteile der Sprache Java sind auch nicht 4.4. DESIGN-PATTERN UND DAS AWT 41 Thema dieser Arbeit. Die Verwendung von Java bildet fur ART aber eine gute Basis, denn bis auf die Abstraktion eines Roboters und dessen Schnittstelle (die Roboter-Hardware-Unabhangigkeit), ist weitgehend alles vorhanden was man braucht. Der Vorteil einer objektorientierten Sprache liegt in der leichten Zuganglichkeit in Bezug auf die Denition und Verwendung von Software-Modulen. Objektorientierung kann man so verstehen, da reale Dinge in abgeschlossenen Softwarestucken abgebildet werden, die dann miteinander agieren. So wird ganz naturlich die Menge der Motoren auf eine entsprechende Klasse Motor abgebildet und ein einzelner realer Motor auf eine Instanz, ein Objekt dieser Klasse. Objekte werden verwendet, indem man mit ihnen u ber sogenannte Nachrichten kommuniziert. Durch die Verwendung von Objekten wird ein nichtlinearer Programmablauf unterstutzt { eine durch Ereignisse ausgeloste Interaktion von miteinander verbundenen Objekten. Die Idee einer virtuellen Maschine, bzw. von interpretiertem Code und einer Klassenbibliothek, die eine unabhangige Schnittstelle zur Hardware bereitstellt, ndet sich keineswegs nur bei Java. Bereits Smalltalk-80 beruhte auf einem solchen Konzept (Goldberg und Robson, 1983) und auch Klassen- bzw. Funktionsbibliotheken zur plattformubergreifenden Programmierung gibt es mehrere. Java ist aber einer der modernsten und weitgehendsten dieser Ansatze und ndet vor allem immer breitere Unterstutzung. 4.4 Design-Pattern und das AWT Wie der Name schon andeutet, besteht zwischen dem im Rahmen dieser Arbeit entwickelten Abstract Robot Toolkit (ART) und dem Abstract Window Toolkit (AWT) von Java eine gewisse Gemeinsamkeit. Das AWT ist eine Sammlung von Klassen fur die Programmierung eines Graphical User Interface (GUI). Es ermoglicht, eigene Unterklassen zu entwickeln und komplexe GUI's werden durch ein Zusammenspiel zwischen vielen einzelnen Objekten verschiedener Klassen realisiert. Instanzen der Klassen werden miteinander verbunden, gleichsam "zusammengesteckt". Jede Instanz ubernimmt dabei einen gewissen Teil in einem komplexen Zusammenspiel, welches im Falle des AWT am Ende zum Beispiel ein Fenster ergibt { das GUI eben. Solche Aufteilungen in verschiedene Klassen, die vor allem gemeinsam ihre volle Starke ausspielen konnen, bzw. dadurch wesentlich besser wiederverwendbar werden, sind ein Aspekt des Designs der Software. Aber sogar die Art der Aufteilung selbst kann man "wiederverwenden" { man spricht dann auch von einem Design-Pattern. Die klassische Auswahl und Erklarung von solchen Design-Pattern steht in Gamma et al. (1995). Der Aspekt, da ein Container fur Component-Instanzen selbst eine solche Component-Instanz ist und daher mit ihm mehrere Komponenten zu einer zusammengefat und auch als eine einzige behandelt werden konnen, wird dort als das Composite-Pattern beschrieben. Das sogenannte Strategy-Pattern wird mit der Kapselung des LayoutAlgorithmus und der Layout-Daten in separaten Klassen umgesetzt. Dieses Pattern wird auch manchmal als Delegate-Pattern bezeichnet, bzw. ist mit diesem 42 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT verwandt. Auch in ART nden sich solche bekannten Design-Pattern wieder, wobei nicht gezielt versucht wurde, bestimmte Design-Pattern aus Gamma et al. (1995) zu verwenden. Vielmehr soll der Verweis auf bestimmte Design-Pattern dazu dienen einen leichteren Zugang zu ART zu ermoglichen. So konnen Strukturen, die einem in der angegebenen Literatur behandelten Design-Pattern ahneln, leichter erklart werden, da fur sie bereits ein Name existiert. Ein Leser, dem diese Begrie nicht gelaug sind, kann in der angegebenen Literatur nachschlagen. Dies ermoglicht eine pragnantere Darstellung, da nur noch die konkrete Auspragung des Patterns erklart werden mu und die grundsatzliche Struktur sozusagen "mit einem Wort" klar ist. 4.5. 4.5 DAS ABSTRACT ROBOT TOOLKIT 43 Das Abstract Robot Toolkit Wie bereits erwahnt, diente das AWT in gewisser Weise als Vorbild bei der Implementierung von ART. Das AWT bietet Klassen wie Button oder Window, welche die entsprechenden GUI-Elemente unabhangig von der jeweiligen Plattform reprasentieren. Diese Klassen konnen leicht, z.B. durch subclassing, um plattformubergreifende Funktionalitat erweitert werden und Implementierung en fur weitere Plattformen konnen ohne Anderung an ihnen hinzugefugt werden. Dies ist naturlich auch fur ART eine wunschenswerte Eigenschaft. So gibt es z.B. Klassen fur einen BooleanSensor oder einen Motor, welche genau so wie beim AWT durch die Implementierung von Unterklassen spezialisiert und erweitert werden konnen. Dies geschieht unabhangig von der konkreten Ansteuerung eines Hardware-Interfaces und kann daher mit jeder Hardware wiederverwendet werden, auch mit zukunftigen Implementierungen fur weitere Hardware. Design-Pattern in der Architektur Sowohl im AWT als auch in ART wird dies durch Einsatz des sogenannten erreicht. Dies ist nicht ungewohnlich fur die Implementierung eines plattformubergreifenden API, da es genau die eben beschriebenen Vorteile eronet. Die Klassen wie Motor sind eine Art Proxy fur die eigentliche Hardware-abhangige Implementierung eines Motor. Ein Motor besitzt dazu einen Verweis auf ein Objekt, welches die eigentliche Funktionalitat, namlich die konkrete Hardware anzusteuern, u bernimmt. Dieses Objekt wird im AWT und daher auch in ART als der Peer bezeichnet, was sich auch im Klassennamen MotorPeer widerspiegelt. Um eine weitgehend exible Implementierung der Peers zu ermoglichen, sind deren Schnittstellen in Form von Java-Interfaces deniert. Welche Implementierung nun als konkreter Peer verwendet wird, mu aber irgendwo entschieden werden. Im Falle des AWT gibt es das sogenannte Toolkit-Objekt, welches als Factory fur alle Peers dient. Im AWT wird die Toolkit-Klasse ihrerseits als Factory fur ein konkretes Toolkit-Objekt verwendet. Die Klassenmethode getDefaultToolkit() wird dazu verwendet. Welche Unterklasse von Toolkit diese Methode zur Erzeugung der Toolkit-Instanz verwendet, kann u ber eine Property (awt.toolkit) gesteuert werden. Bridge-Pattern Das gewu nschte Toolkit Im Allgemeinen wird man bei der Verwendung des AWT nicht selbst angeben, welche Toolkit-Implementierung benutzt werden soll. Dies ist auch nicht notwendig, da die benotigte Implementierung im Wesentlichen nur von dem Betriebssystem, auf dem die Java-Maschine lauft, abhangig ist. Eine eventuelle Verteilung der Applikation, indem zum Beispiel das Fenster einer Applikation auf einem ganz anderen Rechner erscheint, wird nicht durch Verwendung unterschiedlicher Toolkits erreicht, sondern dadurch, da das entsprechende Toolkit dieses fur die Applikation unmerklich selbst realisiert. Ist sogar die gesamte Applikation verteilt programmiert, z.B. uber RMI, dann "lebt" trotzdem jedes 44 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT represents a specific type of actuator−hardware connected to the hardware−port represents a specific MotorPeer RobotInterface "Wire" (RS232, I2C, Bluetooth, Ethernet, ...) Output1 Output2 forward() backward() stop() creates kind of interpretation of the actuator−hardware Motor forward() backward() stop() & delivers Input1 Input2 ActuatorPort Robot (Hardware) represents a hardware−interface represents a specific port of the hardware−interface Abbildung 4.4: Das grundsatzliche Zusammenspiel und die Bedeutung der Komponenten des ART. Ein Steuerungsalgorithmus verwendet in erster Linie bestimmte Interpretationen der Elemente eines Roboters (in der Grak durch Motor dargestellt). Objekt zu einem bestimmten Zeitpunkt auch nur auf einem Rechner, bzw. in einer Java-Maschine, und daher werden im Allgemeinen auch nicht mehrere Toolkit-Implementierungen benotigt. Bei der Ansteuerung von Robotern verhalt sich das etwas anders. Welche Roboterhardware einem Programm zur Verfugung steht, hangt nicht mit dem Betriebssystem oder der jeweiligen Java-Implementierung zusammen. Dies kann sich im Prinzip sogar wahrend eines Programmablaufs andern.2 Nun konnte man zwar ahnlich wie bei Toolkit die konkrete Implementierung uber eine Property auswahlbar machen, diese Property musste aber dann eigentlich immer einen Wert enthalten, da es so etwas wie die Standardhardware zur Robotersteuerung nicht gibt und folglich auch keine Standardimplementierung gewahlt werden kann. Die in ART enthaltenen Implementierungen fur den Brick und das Intelligent Interface fur Fischertechnik konnen bis zu einem gewissen Grad automatisch herausnden, welche Interface-Hardware angeschlossen ist. Es ist aber auch vorstellbar, da man fur einen entsprechend aufwandigen Roboter mehrere Interfaces verwenden mochte, vielleicht sogar von verschiedenen Typen, so da letzten Endes Hardware von z.B. Lego und Fischertechnik gleichzeitig verwendet wird. Aus diesem Grund ist die Wahl der konkreten ToolkitImplementierung, also der Factory fur die Peers von Sensoren und Motoren, nicht so einfach automatisch zu bewerkstelligen wie beim AWT, wo man sich damit nicht notwendigerweise auseinandersetzen mu. Die meisten Hardware-Interfaces werden auerdem mehrere Anschlusse fur Sensoren oder Motoren haben und es mu daher auch ausgewahlt werden, an welchem dieser Anschlusse z.B. der entsprechende Motor angeschlossen sein soll. Den grundlegenden Aufbau illustriert Abbildung 4.4. Damit eine der FrontendKlassen zu ihrem Peer kommt sind drei Schritte notwendig: 1. Das Hardware-Interface mu ausgewahlt werden. Ein Objekt der Klas2 Bevor sich nun jemand falsche Honungen macht: Ein mehr oder weniger automatischer Wechsel der Roboterhardware zur Laufzeit wird, zumindest zum jetzigen Zeitpunkt, nicht von ART unterstutzt. 4.5. DAS ABSTRACT ROBOT TOOLKIT 45 se RobotInterfaceFactory hat mehrere Factory-Methoden mit denen Instanzen der Klasse RobotInterface erzeugt werden konnen. Eine Instanz von RobotInterface reprasentiert dann ein Hardware-Interface.3 Viele der Factory-Methoden von RobotInterfaceFactory erzeugen mehrere Interfaces, von denen man dann auswahlt, welche man verwenden mochte. 2. Ein RobotInterface besitzt mehrere Anschlusse, welche durch Instanzen der Klasse Port reprasentiert werden. Diese Anschlusse hangen aber ihrerseits so eng mit der jeweiligen Instanz von RobotInterface zusammen, da man sie nicht selbst erzeugt, sondern auch RobotInterface wieder eine Factory darstellt und zwar fur die Port-Objekte. 3. Instanzen von Motor, BooleanSensor usw. kommen u ber ein solches PortObjekt an ihren MotorPeer, bzw. BooleanSensorPeer, indem man sie mit diesem Port verbindet. Erst dann ist die Verbindung komplett, so da z.B. die BooleanSensor-Instanz den Zustand eines realen Schalters widerspiegelt. Somit hat das verwendete Port-Objekt als erstes Einu darauf, welchen Peer z.B. der Motor bekommt. Diese Aufteilung in mehrere Schritte, bis z.B. ein BooleanSensor auch wirklich komplett benutzbar ist, erscheint eventuell unnotig umstandlich. Es gibt aber einige Argumente, die diese Aufteilung unterstutzen: Diese Aufteilung entspricht sehr gut der Realitat, was eines der wichtig- sten Argumente uberhaupt ist. Dort wahlt man auch erst einmal einen Sensor und eine Interface-Hardware aus und schliesst dann den Sensor an einen der zur Verfugung stehenden Anschlusse an. Um einen der Software-Sensoren wie BooleanSensor mit etwas zu ver- binden reicht ein Port-Objekt, welches wesentlich weniger komplex als ein RobotInterface ist. Wenn man also z.B. einen BooleanSensor haben mochte, der gar nicht mit einer realen Hardware in Verbindung steht, sondern z.B. immer einen konstanten Wert liefert, so kann man das (fur den BooleanSensor vollig unmerklich) alleine dadurch erreichen, da man einen eigenen Port dafur erstellt. Ein ganzes RobotInterface ist in diesem Fall nicht notig. Im AWT bekommt ein Button seinen ButtonPeer, indem er einem Container (der letzten Endes in einem Window "steckt") hinzugefugt wird und dessen Toolkit u bernimmt;4 in ART bekommt ein Motor seinen MotorPeer, indem er mit einem Port verbunden wird. Ein Port-Objekt kann aber nicht 3 Eine Implementierung von RobotInterface mu nicht notwendigerweise mit einer realen Hardware im Zusammenhang stehen, bzw. genau ein Hardware-Interface reprasentieren (siehe Kapitel 6 auf Seite 107). 4 Falls der Container selbst noch kein Toolkit haben sollte, bekommt der Button erst dann ein solches, wenn auch der Container eines bekommt. Die Verbindung mit einem Peer, bzw. die Wahl eines Toolkit, wird in der Regel durch Aufruf der Methode pack() in der Klasse Frame veranlasst. 46 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT so direkt erzeugt werden wie zum Beispiel ein Frame, da (wie bereits erwahnt) kein default-RobotInterface zur Verfugung steht. Das macht die Handhabung zwar ein klein wenig aufwandiger, dafur erhalt man aber eine sichtbare Kontrolle daruber, welcher Anschlu an welcher Hardware, bzw. welche Implementierung von RobotInterface, verwendet wird. Jedem das Seine Ein weiterer Unterschied zwischen dem AWT und ART besteht darin, da das Toolkit des AWT nur Peers von bestimmten festgelegten Klassen erzeugt. Im wesentlichen gibt es im AWT in der Klasse Toolkit fur jedes GUI-Element wie Button oder TextField eine entsprechende Methode createButton(Button), bzw. createTextField(TextField). Diese Festlegung gibt es in ART so nicht. Es gibt genau einen Peer, den jedes RobotInterface, bzw. ein Port-Objekt, zur Verfugung stellen mu und zwar eine Implementierung von SensorPeer. Es wurde gar keinen Sinn machen, wenn jedes RobotInterface jede mogliche Form eines Sensors oder Actuators durch einen entsprechenden Peer zur Verfugung stellt, da sich die Hardware oft viel zu sehr unterscheidet. Manche Sensoren oder Actuatoren haben auf mancher Hardware einfach keine Unterstutzung. Auch in Java gibt es diesen Ansatz seit der Einfuhrung von Swing. Swing ist eine Implementierung von GUI-Elementen, die alle auf einem einzigen Peer fur Component beruhen, einem sogenannten LightweightPeer. Dieser stellt im Wesentlichen nur eine Flache auf dem Bildschirm zur Verfugung und verarbeitet Events des zugrundeliegenden Fenstersystems, so da z.B. Tastatureingaben verarbeitet werden konnen. In ART werden beide Ansatze miteinander kombiniert. Wie bereits erwahnt, mu eine Implementierung von RobotInterface nur einen SensorPeer bereitstellen.5 Daruber hinaus hat ein RobotInterface aber die Moglichkeit, beliebige Spezialisierungen eines SensorPeer zur Verfugung zu stellen. Je nachdem von welcher Klasse das Objekt ist, fur das ein Peer benotigt wird, oder auch je nachdem welche Peers bereits erzeugt wurden, konnen unterschiedliche Peers erzeugt (bzw. geliefert) werden. Es gibt in ART bereits Denitionen fur solche Spezialisierungen (z.B. MotorPeer oder LightSensorPeer) in Form von weiteren Java-Interfaces, die das Interface SensorPeer erweitern. Zudem gehoren zu jedem dieser Java-Interfaces gewisse Klassen (wie z.B. Motor oder LightSensor), die mindestens einen SensorPeer benotigen aber einen Peer, der das zugehorige Interface implementiert hat, bevorzugen. Wird ein Port eines bestimmten RobotInterface nun nach einem Peer fur ein bestimmtes SensorObjekt gefragt (man beachte, da auch Motoren als Sensoren fur ihren Zustand aufgefasst werden), dann gilt die folgende Vereinbarung : Wenn ein Peer geliefert wird, dann ist es "der Beste" der f ur diesen Port zu diesem Zeitpunkt und fur dieses Sensor -Objekt geliefert werden kann. Im schlechtesten Fall ist es ein einfacher SensorPeer. 5 Genaugenommen nicht mal das, da ein RobotInterface theoretisch auch 0 Ports haben kann { nur wird das kaum sinnvoll verwendet werden konnen. 4.5. 47 DAS ABSTRACT ROBOT TOOLKIT Welcher von den momentan moglichen Peers der Beste ist, wird anhand der Klasse des Sensor-Objektes entschieden. Peers fu r zuku nftige Sensor-Klassen Bisher sind in ART die in Abbildung 4.5 enthaltenen Peers deniert. Mochte man bei einer selbst implementierten Sensor-Klasse einen bereits existierenden Typ Peer bevorzugen, dann mu die eigene Sensor-Klasse von einer der SensorKlassen abstammen, die zusammen mit dem entsprechenden Peer deniert wurden. Man kann sich zwar auch eigene Peer-Typen in Form von Java-Interfaces ausdenken, mu dann aber auch dafur sorgen, da irgendeine Implementierung von RobotInterface, bzw. Port, diesen Peer auch unterstutzt (also eine Implementierung davon kennt und zuruckliefern kann) um etwas davon zu haben. SensorPeer Base interface − must be implemented by every peer LightSensorPeer For hardware which is able to measure light StateSensorPeer For discrete values CountSensorPeer For counting state−transitions AngleSensorPeer BooleanSensorPeer ActuatorPeer For hardware which is able to measure angles For hardware which can distinguish between true and false Base Interface for values that can be set (output of hardware) MotorPeer For hardware which can control motors StepperMotorPeer For hardware which can control stepper motors ServoPeer For hardware which can control servo motors Abbildung 4.5: Die Klassenhierarchie der Peers f ur die Frontend-Klassen. Die Abstammungsverh altnisse sind die gleichen wie bei den Sensor-Implementierungen (siehe Abbildung 4.6 auf Seite 52). Beispielsweise wird der in ART bereits vorhandene MotorPeer logischerweise von der Klasse Motor bevorzugt. Diese Bevorzugung ist aber nicht dynamisch, sondern an die Klassen gebunden. Hiermit ist nicht die dynamische Bindung in einer objektorientierten Sprache gemeint, in der eine Nachricht erst zur Laufzeit an eine bestimmte Methoden-Implementierung gebunden wird, sondern der Umstand, da die Zuordnung Klasse$Peer in die Implementierungen von RobotInterface fest hineincodiert ist. Eine Implementierung von RobotInterface mu die Klassen, fur die sie einen besonderen Peer liefern mochte, daher kennen. Im momentanen Entwicklungsstadium von ART ist es 48 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT nicht moglich, eine Implementierung von Sensor nach ihren bevorzugten PeerKlassen zu fragen, daher ist es bei einer eigenen Klasse, die z.B. einen MotorPeer bevorzugen mochte eben noch notwendig, da sie von Motor abstammt. Nur wenn man sich auch mit einem einfachen SensorPeer zufrieden geben kann ist es moglich, von einer beliebigen Klasse abzuleiten, denn Sensor ist ein JavaInterface und nur dessen Adaptierung ist Vorraussetzung dafur, da ein Objekt als Sensor im Zusammenhang mit ART verwendet werden kann. Interpretationen von Werten Es ist also moglich, da eine Implementierung der Ansteuerung eines Hardware-Interface nur genau die speziellen Peers enthalt, die fur diese Hardware auch sinnvoll sind. So gibt es zum Beispiel weder fur die Ansteuerung von Fischertechnik noch fur die Ansteuerung von Lego einen BooleanSensorPeer, denn ein spezieller "Wahrheitssensor" existiert fur diese Hardware hochstwahrscheinlich nicht. Ware die Interface-Hardware, die man ansteuert, aber z.B. ein Lugendetektor, dann konnte ein BooleanSensorPeer durchaus Sinn machen. Ein RobotInterface konnte auch spezielle Anschlusse (Instanzen von Port) anbieten, die einen Peer liefern der diese Interpretation in true oder false vornimmt. Es ist zum Beispiel denkbar, einen solchen Port anzubieten, um uber einen BooleanSensor zu erfahren, ob die Verbindung zwischen der Implementierung von RobotInterface und der eigentlichen Interface-Hardware noch steht oder ob sie momentan zusammengebrochen ist. Uberhaupt kann man feststellen, da "Sensoren" fur "Werte" ein Konzept ist, das nicht notwendigerweise mit Dingen aus der "greifbaren" Welt, wie z.B. einem Schalter, zu tun haben mu. Im Grunde konnen Sensoren auch rein virtuell sein und innerhalb der Komponenten einer Software zum Datenaustausch dienen. Grundsatzlich unterscheiden sich Sensor-Objekte in zwei fur die Abstraktion (und den damit verbundenen Moglichkeiten zur Wiederverwendung) wichtigen Punkten: Erstens ist es wichtig, ob sie nur einen Wert, oder sogar einen Wert mit einer bestimmten Interpretation reprasentieren, und zweitens, ob dieser Wert einer bestimmten Skala entspricht, also bereits kalibriert ist. Eine Interpretation liefern im Grunde alle Sensoren auer dem allereinfachsten (in ART der RawSensor). Bei einem BooleanSensor ist die Existenz einer gewissen Interpretation am oensichtlichsten, aber auch ein LightSensor steht dafur, da sein Wert der Intensitat eines Lichts entspricht. Die Interpretation geht aber unterschiedlich weit. So ist bei einem LightSensor z.B. noch nicht klar, welche Lichtquelle mit ihm gemessen wird, oder bei einem StateSensor ist zwar klar, da sein Wert einen gewissen (in ART als diskret denierten) Zustand ausdruckt, doch die Bedeutung des Zustands kennt er noch nicht. So mute der Zustand eigentlich noch eine Bedeutung bekommen, wie z.B. Rot, Grun, Blau (und je nach Anwendung auch noch "undeniert") fur einen Sensor der eine der drei Grundfarben des RGB-Modells erkennen soll. Dies ist deshalb wichtig, weil durch diese Unterscheidung ein Algorithmus unabhangig(er) von der konkreten Auspragung eines Sensors wird. Benotigt ein Algorithmus einen Sensor fur bestimmte Farben, weil er z.B. je nach registrierter 4.5. DAS ABSTRACT ROBOT TOOLKIT 49 Grundfarbe eine von drei Aktionen implementiert, so sollte er auch auf einem "PrimaryColorSensor" beruhen und nicht auf einem StateSensor oder gar einem LightSensor. So wird namlich das Problem, wie z.B. anhand des Werts eines Lichtsensors entschieden werden kann, welcher Farbe der gemessene Wert entspricht, nicht verschleppt. Wenn sich dann die Hardware andert, also der Lichtsensor auf einmal andere Werte liefert, weil er genauer wird oder weil man mehrere Sensoren mit Farbltern verwendet usw., so braucht man nur die Zuordnung zu den Farben neu zu implementieren, ohne an dem Algorithmus, der aufgrund der Farben entscheidet, etwas zu verandern. Naturlich ist es (gerade fur Zustande) nicht moglich, alle Interpretationen abzudecken { es gibt unendlich viele. Man mochte bestimmt auch nicht standig neue Sensor-Klassen implementieren, so da man sich im Falle der Zustande oft auch schon zufrieden geben wird, wenn man schlicht in der Dokumentation des Algorithmus deniert, wie die Zustande, also die "nackten" Zahlen, interpretiert werden. Viel sinnvoller ist die Verwendung spezieller Sensor-Klassen, wenn durch sie eine bestimmte physikalische Groe reprasentiert wird, also z.B. eine Lichtintensitat, Temperatur, Strecke oder ein Winkel. Dort wird dann auch die Skala wichtig (obwohl die Zustands-Interpretation im Grunde auch bereits eine Art Skala ist). Im Optimalfall, bei entsprechender Unterstutzung durch die Hardware, liefert der Sensor bereits richtig kalibrierte Werte. Dies ist zum Beispiel bei dem Temperatursensor von Lego fur den Brick der Fall. Die Lichtsensoren von Lego und Fischertechnik sind hingegen nicht kalibriert, so da man dies dort entweder "per Hand" vornehmen mu, oder ganz darauf verzichtet. Grundsatzlich wird bei Sensoren fur physikalische Groen in ART davon ausgegegangen, da diese auch kalibriert sind, trotzdem sollte ein Algorithmus aber dokumentieren, inwieweit er von einer Kalibrierung abhangig ist, damit man einschatzen kann, welche Hardware im Zusammenhang mit dem Algorithmus uberhaupt verwendbar ist. Die Programmierung des Beispielroboters LiSe in Abschnitt 5.6 auf Seite 94 verwendet z.B. nur die Grenzen des messbaren Bereichs sowie die Genauigkeit der Lichtsensoren. Sie kommt ansonsten allein mit dem Unterschied zwischen zwei (gleich skalierten) Lichtsensoren aus. Die Genauigkeit ist eine Eigenschaft die jeder Sensor besitzt und einen messbaren Bereich hat im Grunde jeder physikalische Sensor.6 Solche Werte, die die Eigenschaften des Sensors beschreiben, bekommen die Sensor-Objekte in der Regel von ihrem Peer (wenn er den jeweiligen Wert unterstutzt). Die Werte konnen aber auch manipuliert werden, um sie an die Erfordernisse eines bestimmten Algorithmus anzupassen. Fur den beschriebenen Algorithmus fur LiSe ist jedoch nur wichtig, da die beiden Lichtsensoren Werte auf der gleichen Skala liefern, also untereinander vergleichbar sind { welche Skala das ist, ist nicht relevant. Normalerweise sollte das bei gleicher Hardware sowieso schon der Fall sein. Verwendet man aber z.B. fur den einen Sensor Fischertechnik- und fur den anderen Lego-Hardware, so mu man dafur selbst sorgen. Welche Moglichkeiten die in ART enthaltenen SensorKlassen dafur unter anderem bieten wird im nachsten Abschnitt beschrieben. 6 Der in ART enthaltene AngleSensor kennt diese Eigenschaft bisher noch nicht. 50 4. 4.6 Die DIE IDEE DES ABSTRACT ROBOT TOOLKIT Sensor-Implementierungen in ART Die in ART existierenden Implementierungen des Java-Interface Sensor sind darauf ausgelegt, eine exible Nutzung, im Zusammenhang mit den jeweils moglichen Peer-Implementierungen, zu gestatten. Grundfunktionalit at SensorPeer Da jeder Peer eine Implementierung von SensorPeer sein mu, kann jeder Sensor im Grunde mit jedem Peer wenigstens soviel anfangen, da er an einen Wert kommt. Ein Sensor hat die Moglichkeit, mit dem Peer, den er bekommen hat, das zu simulieren, was er von seinem Wunsch-Peer erwartet hatte. Nicht jede Funktionalitat kann simuliert werden. Wenn z.B. ein Motor nur einen einfachen SensorPeer und nicht wenigstens einen ActuatorPeer bekommt, dann kann der Aufruf der Methode on() bei einem solchen Motor-Objekt keinen Eekt auf die Interface-Hardware haben, denn SensorPeer bietet fur das Setzen eines Werts keine Methode { die gibt es erst bei ActuatorPeer. Ein ActuatorPeer hingegen reicht dem in ART enthaltenen Motor bereits, um seine volle Funktionalitat zu besitzen. Damit wird weitgehend ein Problem adressiert, da bei Java unter anderem zur bereits erwahnten Entwicklung von Swing gefuhrt hat. Anfangs wurde mit dem AWT nur solche Funktionalitat angeboten, die auf jeder unterstutzten Plattform bereits vorhanden ist. Im Gegensatz dazu wird bei Swing (und auch in ART) versucht, mit dem was jede Plattform bietet, die grotmogliche Funktionalitat zu erreichen. Statt der Schnittmenge nun also die Vereinigungsmenge. Es ist klar, da dieser Ansatz auch seine Grenzen hat. Er bietet aber den Vorteil, da die Klassen des API bereits mit einer relativ geringen Peer-seitigen Unterstutzung auskommen. Zumindest in ART bleibt einem trotzdem noch der Weg oen, sich bei der Implementierung eines RobotInterface nicht nur auf die Simulation in der jeweiligen Sensor-Klasse zu verlassen, sondern spezielle Unterstutzung, durch entsprechende Peer-Implementierungen, zu liefern. In den meisten Fallen, in denen die Simulation erforderlich ist, wird es notig sein, dem Sensor dann noch weitere Parameter zur Verfugung zu stellen, die festlegen, wie diese Simulation aussehen soll. So wird z.B. der in ART enthaltene BooleanSensor, wenn er einen Peer hat, der kein BooleanSensorPeer ist, den Wert, den er von seinem Peer bekommt, selbst in true oder false umwandeln. Dafur interpretiert er den Wert 0 als false und alle anderen Werte als true. Dies ist aber nur sein Standard-Verhalten, wenn er fur die Interpretation keinen Delegate hat.7 Basisklasse RawSensor Alle in ART enthaltenen Implementierungen von Sensor stammen von der Klasse RawSensor ab. Die Klasse RawSensor ist bereits ein vollwertiger Sensor. Alle 7 In Gamma et al. (1995) wird kein Delegate-Pattern erwahnt. Ein vergleichbares Pattern heit dort Strategy. Der Name Delegate, bzw. Delegation wird aber z.B. in Schreiner (1999) und Garnkel und Mahoney (1993) dafur verwendet. 4.6. DIE SENSOR-IMPLEMENTIERUNGEN IN ART 51 notwendigen Dinge, wie die Verbindung zum Port bzw. SensorPeer herzustellen, Events zu verarbeiten und an SensorListener weiterzuleiten sowie ein paar einfache Kongurationsmoglichkeiten, werden von einem RawSensor zur Verfugung gestellt. Um den Wert, der vom Peer (also von der Hardware) kommt, auf eine spezielle Weise zu verarbeiten, bietet ein RawSensor im Wesentlichen drei Ansatzpunkte, welche bestimmten Design-Pattern entsprechen: Zu bestimmten festgelegten Zeitpunkten bei der Verarbeitung eines Werts vom Peer, werden bestimmte Methoden aufgerufen, die den Wert umwandeln konnen. Wenn z.B. ein Wert vom Peer ankommt, entweder durch einen Event oder weil der Peer explizit nach seinem Wert gefragt wurde, dann ruft RawSensor bei sich selbst die Methode convertToIncoming(double) auf und wandelt damit den gerade erhaltenen Wert um. Das bietet einer Unterklasse die Moglichkeit, diese Methode zu u berschreiben und eine eigene Umwandlung zu implementieren. Gegebenenfalls kann die neue Methode der Unterklasse auch die Methode der Oberklasse aufrufen, um ihr auch noch eine Chance fur eine eventuelle Umwandlung zu geben. Bei einem RawSensor ist dies auch sehr sinnvoll - nicht weil sie eine eigene Umwandlung durchfuhren wurde, sondern weil mit ihr der nachste Punkt implementiert ist: Template-Method : Ein RawSensor kann einen incomingConversionDelegate besitzen. Dieser Delegate mu ein Objekt einer Klasse sein, die das Java-Interface de.jaetzold.util.Conversion implementiert, welches im Wesentlichen vorschreibt, da eine Methode convert(double) existiert, die ihrerseits ein double zuruckliefert. Diese Methode wird dann zum Umwandeln des Wertes aufgerufen, vorausgesetzt, da u berhaupt ein Delegate angegeben wurde. Eine Conversion bietet auerdem noch Methoden, um zu erfahren ob ihre Umwandlung cacheable ist8 und ob man erwarten kann, da sie nicht nur ganzzahlige Resultate liefert (deliversFloatingPoint). Dies sind Informationen, die ein Sensor auch in Bezug auf sich selbst liefern mochte, und er ist daher darauf angewiesen, dies auch uber den Delegate zu wissen, da dieser den Wert ja umwandelt. Auerdem ist in RawSensor ein Mechanismus implementiert, der vom Peer erhaltene Werte sowie eben deren Umwandlungen zwischenspeichert, um bei haugen Anfragen schneller antworten zu konnen. Diese Zwischenspei cherung wird aktiviert, wenn der Peer sowieso bei jeder Anderung des Werts einen Event verschickt, denn dann mu er nicht noch extra gefragt werden, welchen Wert er nun momentan hat { es ist einfach der letzte, fur den ein Event angekommen ist. Strategy (Delegate): Man kann mehrere Sensor-Instanzen "hintereinanderschalten". Die Methode getSensorPort() liefert einen SensorPort der, wenn man dort einen weiteren Sensor anschliesst, diesem einen Peer liefert, der den Wert der ersten Sensor-Instanz reprasentiert. Dies ist der einzige Ansatzpunkt fur eine spezielle Behandlung des Sensorwertes, der bereits im Java-Interface Sensor Decorator : 8 Damit ist gemeint, ob sie durch eine unveranderliche Zuordnungstabelle reprasentiert werden kann und daher fur ein Argument in Zukunft immer das gleiche Resultat liefert. 52 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT deniert ist. Dies hat den Grund, da u ber diese Schnittstelle das Verhalten des ursprunglichen Objektes nicht wirklich beeinut wird. Angenommen, ein Sensor wird noch von anderen Objekten verwendet, von denen man vielleicht nicht einmal etwas wei (womit man im Grunde immer rechnen sollte), dann kann es zu Problemen fuhren, wenn man z.B. auf den Delegate des Sensors Einu nimmt, da dann die Berechnung des Sensorwerts fur alle, die diesen Sensor verwenden, beeinut wird. Ein Beispiel hierfur und wieso das im Falle des Brick noch weitere Vorteile bietet, steht in Abschnitt 5.5 auf Seite 86. Unterklassen von RawSensor Die in ART enthaltenen Sensor-Klassen sind wie gesagt alle als Unterklassen von RawSensor realisiert. Sie nutzen das Template-Method-Pattern, um fur den Fall, da sie nicht "ihren" Peer bekommen haben, dessen Funktionalitat uber eine Umwandlung des Wertes zu simulieren. Hat aber z.B. ein LightSensor einen LightSensorPeer, dann braucht er nicht umzuwandeln, da der Peer bereits einen entsprechenden Licht-Wert liefert. implementing classes interfaces RawSensor StateSensor CountSensor AngleSensor BooleanSensor Sensor LightSensor RawActuator Actuator Motor StepperMotor Servo Abbildung 4.6: Die Klassenhierarchie der Actuator- und Sensorklassen. Das Strategy-Pattern hingegen dient mehr dazu eine Umwandlung zu verwenden, die von einer bestimmten Sensor-Klasse unabhangig ist und daher mit vielen Sensoren verwendet werden kann { z.B. eine einfache Skalierung des Wertes. Ein Delegate ist aber nicht verpichtet, uberhaupt eine Umwandlung vorzunehmen, die in irgendeiner Beziehung zu dem eigentlichen Wert steht. Es ware denkbar, da ein solcher Delegate z.B. als Resultat immer System. currentTimeMillis() liefert und plotzlich hat man eine Art TimeSensor. Fur einen TimeSensor ware es aber sinnvoller, eine eigene Unterklasse zu erstellen, um die Art des Sensorwertes deutlich zu machen. Der Delegate ist besser dafur geeignet, die entsprechenden Skalierungen vorzunehmen, z.B. fur den Fall, da ein LightSensor keinen LightSensorPeer bekommt und man trotzdem mochte, da der Wert einer bestimmten Skala entspricht. 4.6. DIE SENSOR-IMPLEMENTIERUNGEN IN ART 53 Das Decorator-Pattern kann man auch fur eine Umwandlung des Werts nutzen. Dies ist vor allem dann wichtig, wenn man nicht ausschlieen kann, durch die Umwandlung mit dem Bedurfnis anderer Objekte, die den Sensor verwenden, zu interferieren. Die Moglichkeit, mehrere Sensoren quasi "hintereinanderzuschalten", erlaubt einem auerdem, darauf Einu zu nehmen, uber welchen Peer ein Sensor letztendlich an seinen Wert kommt. So kann man ganz bewut zum Beispiel einen CountSensor verwenden, der an einen LightSensor angeschlossen ist und daher (wenn der LightSensor an einen entsprechenden Port angeschlossen ist) seinen Wert im Grunde auch von einem LightSensorPeer erhalt. Zudem gibt es einen CombinedSensor, welcher die Werte zweier SensorInstanzen uber eine Implementierung des Java-Interface de.jaetzold.util. BinaryConversion miteinander verknupft. Die eine dieser Sensor-Instanzen ist der CombinedSensor aber selbst, d.h. um beliebige Sensoren zu verwenden mu man auch den CombinedSensor an den SensorPort eines anderen Sensor anschlieen. Die andere Sensor-Instanz wird dem CombinedSensor als Parameter ubergeben. Die Reprasentation der Verknupfung zweier Objekte durch ein Einzelnes, das sich im Prinzip genau so verhalt wie die Objekte, deren Verknupfung es darstellt, ist ein Beispiel fur das bereits angesprochene Composite-Pattern. Die Peer-Interfaces Die bisher in ART denierten Peer-Interfaces sind in Abbildung 4.5 auf Seite 47 dargestellt. Das Verhalten der Sensor-Klassen in ART bei unterschiedlichen Peers kann man im Wesentlichen folgendermaen zusammenfassen: Hat ein Sensor einen genau zu ihm passenden Peer, dann wird der Wert, der vom Peer kommt, im Allgemeinen nicht verandert. Je nach Sensor konnen noch weitere Parameter vom Peer ubernommen werden (so z.B. der AngleSensor eines Servo oder die Grenzen des Messbereichs bei einem LightSensor). Hat ein Sensor nicht einen Peer, der ihm alle gew unschte Funktionalitat bietet, so ubernimmt er den Wert so, wie er von der Oberklasse bereits verarbeitet wurde, und nimmt dann noch eventuell geeignete Anderungen daran vor. Weitere Parameter haben entweder default-Werte oder mussen in diesem Fall dem Sensor-Objekt u bergeben werden. Zum Beispiel braucht ein StateSensor in einem solchen Fall ein Objekt, welches das Interface StateDecider implementiert hat. Den StateDecider verwendet der StateSensor, um aus dem Wert seiner Oberklasse RawSensor einen Wert zu erhalten, der einen gewissen Zustand reprasentiert. Solch ein StateDecider ist im Grunde eine Verknupfung von zwei Werten zu einem, welcher eine Verallgemeinerung einer Zustandsubergangsmatrix darstellt. Er bekommt den aktuellen Zustandswert sowie den Raw-Wert der Oberklasse als Parameter und liefert den neuen Zustandswert als Resultat. Ein StateSensor hat naturlich auch einen defaultStateDecider, welcher aber nichts weiter macht, als den Raw-Wert unverandert zuruckzugeben. 54 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT Hat ein Sensor nicht einen passenden Peer und ist die Umwandlung, die er dann an dem Wert vornimmt, um auch ohne einen speziellen Peer klarzukommen, fur die gewunschte Anwendung nicht geeignet, so wird es in den meisten Fallen ausreichen und auch am einfachsten sein, dem Sensor eine Conversion als conversionDelegate9 zu u bergeben. Zumindest die in ART enthaltenen Sensor-Klassen bieten diese Moglichkeit, da sie alle von RawSensor abstammen. Es kann auch vorkommen, da ein Sensor zwar seinen bevorzugten Peer- Typ bekommt, dessen Wert aber nicht richtig skaliert ist. Der LightSensorPeer { in den Implementierungen sowohl f ur Lego als auch fur Fischertechnik { ist solch ein Fall. Die Hardware besitzt zwar Lichtsensoren und mu im Falle von Lego den Eingang sogar entsprechend kongurieren, diese Sensoren liefern aber keinen Wert der auf eine geeignete Skala kalibriert ist. Fur Lichtsensoren ware wahrscheinlich Lux eine gute physikalische Einheit, doch konnte keine Quelle gefunden werden, welche die Werte der jeweiligen Lichtsensoren zu einer solchen Skala in Beziehung setzt. Man kann wohl auch davon ausgehen, da die Fertigungstoleranz und die Abhangigkeit von der Umgebung (Temperatur, Stromversorgung, . . . ) bei diesen Bauelementen eher hoch ist, da sie andernfalls sehr viel teurer waren. Aus diesem Grund liefert der LightSensorPeer bei Lego und Fischertechnik keinen kalibrierten Wert. Ein solcher ist aber trotzdem vorhanden um wenigstens die Grenzen des Messbereichs angeben zu konnen. Damit sind zumindest Informationen wie "kein Licht", "Licht" oder "sehr viel Licht" portabel, also von der Hardware unabhangig verarbeitbar. Wird doch eine Kalibrierung z.B. auf Lux-Werte gewunscht, kann man sich auch hier mit einer entsprechenden Conversion als Delegate helfen. Ein Port ohne RobotInterface Da jede der in ART enthaltenen Implementierungen von Sensor auch ohne ihren bevorzugten Peer auf eine bestimmte Weise versucht trotzdem ihre gesamte Funktionalitat, fur die sie gedacht ist, zur Verfugung zu stellen, ist es leicht, diese auch mit einfachen Datenquellen zu verwenden. So stellt die Klasse ConstantSensorPort zum Beispiel einen Port dar, der einen einfachen SensorPeer liefert, der immer einen konstanten Wert hat. Mit diesem Port kann man dann einfach Lichtsensoren, boolesche Sensoren usw. erzeugen, die ihren Wert nicht verandern. Besondere Beachtung verdient an dieser Stelle auch noch einmal das Decorator-Pattern. In den Sensor-Klassen ist das dadurch umgesetzt, da ein Sensor immer auch einen SensorPort liefern kann, an den weitere Sensoren angeschlossen werden konnen. Diese weiteren Sensoren haben dann einen Peer, der als Wert immer den Wert des Sensors liefert an dessen Port sie angeschlossen 9 Genau genommen hat ein RawSensor zwei solche Delegates, einen f ur die Konvertierung des Peer-Werts in einen "internen" Wert und einen f ur die Konvertierung des internen Werts in den Wert, den er nach auen hin repr asentiert (der z.B. von getValue() geliefert wird). 4.6. DIE SENSOR-IMPLEMENTIERUNGEN IN ART 55 sind. Da auch die Events weitergeleitet werden, macht es in der Benutzung am Ende keinen Unterschied, ob ein Sensor-Objekt direkt an den Port eines RobotInterface angeschlossen ist oder ob da noch weitere Sensor-Objekte gewissermaen "zwischengeschaltet" sind. Eine Anwendung dieser Technik ware zum Beispiel ein BooleanSensor, der immer dann den Wert true annimmt, wenn der CountSensor, an den er angeschlossen ist, einen bestimmten Wert erreicht. Fur den Brick, der seine Anschlusse fur die Sensoren unterschiedlich kongurieren kann (z.B. ob der angeschlossene Sensor mit Strom versorgt wird oder nicht), ist die Klasse des an einen Port angeschlossenen Sensor-Objektes ausschlaggebend fur die Konguration. Schliet man nun einen CountSensor direkt an einen Port der Implementierung von RobotInterface fur den Brick an, so wird der Anschlu so konguriert, wie das im Normalfall fur den Rotationssensor von Lego vorgesehen ist. Dies ist nicht immer wunschenswert, denn manchmal mochte man vielleicht zusatzlich noch einen Taster anschlieen oder mit dem Rotationssensor etwas anderes machen, als die Rotation zu messen, wofur man dann nicht mochte, da der Brick den Wert des Rotationssensors schon vorverabeitet. In diesem Fall kann man direkt an den Port des RobotInterface einen Sensor anschlieen, welcher der gew unschten Konguration entspricht (z.B. einen einfachen RawSensor). An diesen Sensor schliet man wiederum den CountSensor an, den man in diesem Fall naturlich "per Hand" fur das Zahlen kongurieren mu. Ein Beispiel dafur enthalt der Abschnitt 5.5.2. 56 4.7 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT Model, View, Controller Smalltalk-80 (Goldberg und Robson, 1983) enthalt das beruhmte Model-ViewController (MVC) Paradigma fur die Programmierung von Oberachen. Eine Applikation besteht demnach aus drei wesentlichen Bestandteilen: Ein Model enthalt die Funktionen und Daten, welche den Kern der Anwendung ausmachen. Der Model-Zustand wird auf der Oberache von einer View dargestellt. Der Controller ist schlielich fur die Interpretation von (meist durch Benutzereingaben ausgelosten) Events zustandig und folglich dafur verantwortlich, wie bestimmte Events von dem Model aufgenommen werden. registers Model notifies View retrieves state registers manipulates in response to events sends event Controller Abbildung 4.7: Die Beziehungen innerhalb einer Architektur nach Model-View- Controller. In Smalltalk-80 war es notwendig, da Model, View und Controller von speziellen Klassen abstammen, da auf diese Weise Applikationen besonders einfach in das System integriert werden konnten. Das eigentliche Design-Pattern dahinter hat sich inzwischen als sehr vorteilhaft erwiesen { auch unabhangig von speziellen dafur vorgesehenen Basisklassen und Smalltalk. Es deniert eine Trennung von Zustandsaufbewahrung (Model) und Zustandsdarstellung (View). Entsprechend wird durch den Controller die Zuordnung von Operationen auf dem Zustand (Model) zu den Auslosern (Events von der View) exibel. Das Standardbeispiel fur die Demonstration von MVC deniert zu einem Model mehrere Views. Diese Views konnen vollig unabhangig voneinander verwendet werden, da jede View nichts anderes macht, als den Zustand des Model darzustellen. Unter Umstanden bietet sie auerdem Ansatzpunkte fur Benutzereingaben, die an den Controller weitergeleitet und von diesem interpretiert werden. Die Views von dem gleichen Model erscheinen aus Benutzersicht so, als waren sie direkt untereinander verbunden, da eine Anderung an dem Model von allen seinen Views wiedergespiegelt wird. 4.8. SUBSUMPTION-ARCHITEKTUR 57 Obwohl MVC ursprunglich nur fur die Trennung von User-Interface und Algorithmus entwickelt wurde, ist diese Aufteilung auch in anderen Bereichen anwendbar. So ist in ART z.B. ein Motor im Grunde eine View fur den Zustand eines ActuatorPeer. Ein Servo ist eine andere View, die an den gleichen Peer angeschlossen sein kann. Beide Objekte reprasentieren den gleichen Zustand und sind, uber den gemeinsamen ActuatorPeer, aneinander gekoppelt. Nun kann es aber sinnvoll sein, eine Instanz von Motor wiederum selbst als ein Model anzusehen, nicht nur weil er (wie ein typisches Model) Methoden besitzt, um den Zustand des Motors zu manipulieren. Wenn man z.B. in einer graschen Oberache, zur Diagnose des Roboters, den Zustand seiner Motoren darstellen mochte, dann ist es vernunftig (in diesem Kontext) das Motor-Objekt als Model aufzufassen, dessen Zustand von einer View auf der Oberache dargestellt wird. Meiner Meinung nach mu die Zuordnung eines Objekts zu einer der drei Kategorien immer im Verhaltnis zu den anderen Objekten gesehen werden. Nicht immer ist ein Objekt eindeutig und ausschlielich nur ein Model, eine View oder ein Controller. In den Beziehungen der Objekte untereinander mu diese Zuordnung aber eindeutig sein, um den Vorteil von MVC, namlich die Unabhangigkeit und Austauschbarkeit der einzelnen Bestandteile, weiter aufrecht zu erhalten. 4.8 Subsumption-Architektur Wie bereits in Abschnitt 1.2 auf Seite 4 erwahnt ist Subsumption ein von Rodney A. Brooks (Brooks, 1985) erdachtes Konzept zur Steuerung von Robotern. Aus der Anlehnung an Reexe resultiert, da die Reaktionen schnell und unbewut erfolgen sollen. Angewandt auf Roboter bedeutet dieses Konzept, da keine langwierigen Berechnungen durchgefuhrt werden oder ein umfassendes Weltmodell entworfen wird. Stattdessen werden, fur eine einfache Verhaltensweise, schnell zu erkennende Zustande einer gewissen Teilmenge der Inputs mit eindeutigen Zustanden einer gewissen Teilmenge der Outputs verknupft. Da die einzelnen Verhaltensweisen zu verschiedenen Zustanden fuhren konnen, ist eine Vorrangregelung notwendig. Durch die Aufteilung in mehrere Verhaltensweisen wird eine gewisse Unabhangigkeit von Teilbereichen erreicht. Es ist einfach, neue "Reexe" hinzuzufugen, und fallt ein Modul aus, so konnen die davon unabhangigen Teile einfach weiterarbeiten. Die Bedingungen, die zur Aktivierung der Verhaltensweisen fuhren sowie deren Prioritaten, sind sogar u ber die Sofware lernbar (Maes und Brooks, 1990). Ein gewisses Problem entsteht durch die Forderung nach einem eindeutigen, schnell zu realisierenden Zustand der Ausgange. Teilt man Trusty z.B. in Verhaltensweisen auf, so da eine Verhaltensweise fur das Ausweichen zustandig ist, so stot man auf genau dieses Problem. Das Ausweichen ist ein Vorgang, der Zeit benotigt und mit einer ganzen Abfolge von Zustanden der Ausgange realisiert wird. Prinzipiell ergeben sich hier zwei unterschiedliche Losungsansatze: 1. Eine Verhaltensweise wird unterbrechbar. Sie kann dann solange brauchen 58 4. DIE IDEE DES ABSTRACT ROBOT TOOLKIT wie sie will, denn wenn sie nicht mehr aktiv sein soll, weil die Bedingungen sich geandert haben, dann wird sie eben unterbrochen. Dies ist, zumindest in Java, nicht so ohne weiteres realisierbar, da ein Thread nicht einfach von einem anderen unterbrochen werden darf. Hier mu man beachten, da zwar eine eventuell geeignete Methode (suspend()) existiert, diese aber seit Version 2 der Java Platform als deprecated gilt, da deren Verwendung leicht zu deadlocks fuhren kann. Weiterhin sollte ein Thread mitbekommen konnen, wenn er unterbrochen wurde, damit er wei, da der Zustand der Ausgange eventuell zwischendurch von auen verandert wurde. 2. Die Aufteilung in eine Verhaltensweise fur das Ausweichen ist nicht adaquat. Fur jede Zustandsanderung bei der Durchfuhrung von Ausweichen mu eine eigene Verhaltensweise implementiert werden. So konnte zum Beispiel eine Verhaltensweise a, die Trusty ruckwarts fahren lat, durch das Auslosen des Bumpers aktiviert werden und nach dem Auslosen fur einen gewissen Zeitraum aktiv sein. Eine weitere Verhaltensweise b konnte als Input fur die Entscheidung u ber ihre Aktivierung genau den Aktivierungszustand von a besitzen. Sobald a "lange genug" aktiv war oder von aktiv auf inaktiv wechselt, wird b aktiv und dreht Trusty ein wenig { dies naturlich wieder nach dem gleichen Prinzip, da b nicht andere Verhaltensweisen wahrenddessen blockiert. Fur den ersten beschriebenen Losungsansatz ware es notwendig, da eine Verhaltensweise, die langer braucht, haug genug selbst eine Moglichkeit zur Unterbrechung von auen vorsieht. Ob dies dann im Zusammenhang mit Subsumption uberhaupt noch eine sinnvolle Architektur ist, mu sich erst zeigen. Der 2. Losungsansatz geht zwar konform mit dem Subsumption-Konzept, ist aber auch nur mit etwas Aufwand zu implementieren. Hier konnte eventuell ein geeigenetes API die Idee, da manche Verhaltensweisen in einer gewissen zeitlichen Abfolge geschehen sollen, unterstutzen. 5 Ariadne & ART - Ein Tutorial Dieses Kapitel ist in erster Linie ein Tutorial fur die Benutzung des Abstract Robot Toolkit (ART). Es bietet sich aber an, dies mit der Ausarbeitung der Vor- und Nachteile der Benutzung dieses Systems im Gegensatz zu den Programmiersystemen aus Kapitel 3 auf Seite 13 zu verbinden. Anhand einiger kleinerer Beispiele wird zuerst die grundsatzliche Bedeutung der Frontend-Klassen erklart. Dazu gehoren alle Klassen, die dazu gedacht sind, da ein Algorithmus sie zur Steuerung eines Roboters verwendet, sowie die Klassen, mit denen man eine Verbindung zu der eigentlichen Hardwareansteuerung, dem Backend, herstellt. Auf dieser Ubersicht und den einfachen Beispielen bauen die folgenden Abschnitte auf, in denen es um die Programmierung des Beispielroboters Ariadne mit seinen Modulen Trusty und LiSe geht. Manch einem Leser wird die Anzahl der dabei erzeugten Klassen vielleicht unnotig gro vorkommen. Spatestens bei der Verbindung von Trusty und LiSe zu Ariadne sollte aber der Vorteil der verschiedenen Abstraktionsebenen sichtbar werden. Die vollstandigen Quelltexte fur die Beispiele sind auf der CD zu dieser Arbeit enthalten und im Internet verfugbar. Siehe dazu Abschnitt A.1 auf Seite 136 im Anhang zu dieser Arbeit. Zum Verstandnis dieses Kapitels sind Kenntnisse in objektorientierter Programmierung und der Programmiersprache Java notwendig. Zum Lernen von Java und zum Nachschlagen kann man z.B. Flanagan (1999), Sun Microsystems (2001) und Schreiner (2000a) verwenden. Weiterhin ist es fur das Verstandnis einiger der komplexeren Beispiele hilfreich, zu wissen was Subsumption ist, da dieses Programmier-Konzept fur Roboter mehrmals zur Anwendung kommt. Eine kurze Einfuhrung in dieses Konzept steht in Abschnitt 1.2 auf Seite 4. 5.1 "Hello, Robot!" Das Standardbeispiel bei der Einfuhrung in eine neue Programmiersprache ist "Hello, World!" (Kernighan und Ritchie, 1990, S. 5). Nun geht es hier zwar nicht um eine neue Sprache, aber doch immerhin um eine neue Klassenbibliothek zur Steuerung von Robotern. Ein moglichst einfaches Programm, das einen Motor 59 60 5. ARIADNE & ART - EIN TUTORIAL einschaltet, konnte folgendermaen aussehen: Listing 4: code/art/examples/HelloRobot.java package de.jaetzold.art.examples; import import import import de.jaetzold.art.RobotInterfaceFactory; de.jaetzold.art.RobotInterface; de.jaetzold.art.ActuatorPort; de.jaetzold.art.Motor; public class HelloRobot { public static void main(String[] argv) { RobotInterfaceFactory factory = new RobotInterfaceFactory(); RobotInterface hardware = factory.getInterface(); ActuatorPort port = hardware.getActuatorPorts(0); Motor motor = new Motor(); motor.connectWith(port); motor.on(); try { Thread.sleep(1000); } catch(InterruptedException ie) { } } } Zum Kompilieren und Ausfuhren mussen naturlich alle benotigten Klassen uber den Klassenpfad erreichbar sein. Falls also eine Fehlermeldung kommt, da irgendein Typ nicht deniert ist oder eine Klasse nicht gefunden wurde, empfehle ich Abschnitt A auf Seite 135 im Anhang. Funktioniert alles erwartungsgema, lauft ein (an dem ersten Ausgang des Interfaces angeschlossener) Motor kurz an und es erscheint in etwa die folgende Ausgabe auf der Konsole: Successfully initialized Fischertechnik-Interface, found on serialport COM2 Erscheint die Ausgabe nicht oder lauft der Motor nicht an, dann hilft vielleicht ein Blick in Anhang B auf Seite 139. Eine eventuelle NullPointerException weist darauf hin, da kein Interface gefunden wurde. Eine ArrayIndexOutOfBoundsException hat wahrscheinlich die Ursache, da das gefundene Interface keine ActuatorPorts bereitstellt, von denen man aber wenigstens einen benotigt, um den Motor daran anzuschlieen. Die NullPointerException entsteht, weil getInterface() einen nullVerweis liefert, wenn keine Interface-Hardware gefunden werden konnte. Besondere Fehlersituationen werden in Java eigentlich u ber Exceptions signalisiert und es ist kein guter Stil, wenn dies uber das Resultat geschieht. In diesem konkreten Fall kann man aber eine Ausnahme machen, da einerseits ausgeschlos- 5.1. "HELLO, ROBOT!" 61 sen werden kann, da fur diese Methode null als "regulares" Resultat jemals wirklich gebraucht wird und andererseits das Fehlen einer Interface-Hardware ja nicht unbedingt als eine Fehlersituation angesehen werden mu. Insbesondere gibt es noch andere Methoden, wie getInterfaces(), die ein ganzes Array mit den gefundenen RobotInterface zuruckliefert. Wird keins gefunden, ist es ein Array der Lange 0. Eine Exception ware fur diese Methode nicht sinnvoll und entsteht daher auch nicht in den anderen Methoden von RobotInterfaceFactory die ein RobotInterface als Resultat haben. Nun zur Beschreibung des Listing 4 auf der vorherigen Seite: Als erstes wird ein Objekt der Klasse RobotInterfaceFactory aus dem package de.jaetzold.art1 erzeugt. Damit fangt immer alles an bei der Programmierung mit ART, denn Roboter haben immer ein Hardware-Interface, das der Computer ansteuert, und dessen Reprasentierung, in Form eines RobotInterface, bekommt man durch den Aufruf getInterface() von der RobotInterfaceFactory. Eines der Anliegen von ART ist es, von der konkreten Interface-Hardware zu abstrahieren. Aus diesem Grund wird das Objekt, welches diese Hardware reprasentieren soll, nicht vom Programmierer selbst erzeugt. Der Programmierer soll am besten gar nicht wissen, welche Klasse welches Interface an welchem Anschluss ansteuert. Objekte der Klasse2 RobotInterface werden also nach dem Factory-Muster erzeugt. Zur naheren Erlauterung dieses Design Patterns siehe Gamma et al. (1995) oder auch Schreiner (1999). Will man aus irgendeinem Grund nur einen ganz bestimmten Typ Interface ansteuern oder legt besonderen Wert auf die Anschlusse, an denen nach Interfaces geschaut wird, kann man das durch einen Parameter zu getInterface() beeinussen, doch dazu kommen wir erst spater, auf Seite 68 in Abschnitt 5.2. Jetzt geht's erst einmal weiter mit HelloRobot. Der Verweis hardware ist also das Objekt, welches das Harware-Interface reprasentiert. Um einen Motor zu steuern, mu dieser aber an die Hardware angeschlossen werden, genauer, an einen Port des RobotInterface. Genaugenommen sogar an einen ActuatorPort, denn diese reprasentieren einzelne Ausgange der Interface-Hardware. Alle ActuatorPorts eines RobotInterface werden in einem Array gespeichert und man kann nach JavaBeans-Manier darauf zugreifen (sogenannte indexed property, siehe Sun Microsystems (1997)). Wenn das erste gefundene Interface also u berhaupt Ausgange hat, dann hat das Array der ActuatorPorts mindestens einen Eintrag, und den ersten davon bekommt man durch die Nachricht getActuatorPorts(0) an hardware. Nun kann ein Objekt der Klasse Motor mit dem port verbunden werden. Dafur schickt man dem motor die Nachricht connectWith mit dem port als Parameter. Der motor wird hier zwar erst kurz vorher erzeugt, das ist aber nicht notwendig. Man hatte den motor auch als allererstes erzeugen konnen, 1 Im folgenden Text werden, wenn es sich aus dem Kontext bereits erschlieen lat, keine vollqualizierten Klassennamen mehr angegeben. 2 Genaugenommen ist RobotInterface gar keine Klasse sondern ein Java-interface, an dieser Stelle kann man aber so tun, als ware es eine Klasse. 62 5. ARIADNE & ART - EIN TUTORIAL noch bevor die Factory erzeugt wird, doch wenn dann gar kein Anschlu fur den Motor zur Verfugung gestanden hatte, ware er ja umsonst erzeugt worden. Sobald ein Motor mit einem ActuatorPort eines RobotInterface erfolgreich verbunden wurde, kann er auch schon benutzt werden, um den Zustand eines realen, mit der Interface-Hardware verbundenen Motors zu manipulieren. Da die Aufgabe von HelloRobot war, einen Motor anzuschalten, ist das auch schon der nachste Aufruf: motor.on(). Der Rest des Codes dient nur dazu, da das Programm nicht sofort wieder zu Ende geht, denn dann wurde der Motor unter Umstanden, sofort nachdem er angeschaltet wurde, wieder ausgehen, weil die Interfacehardware keine Signale mehr vom Computer erhalt. Implementierungen von RobotInterface erzeugen zwar in den meisten Fallen eigene Threads zur Kommunikation mit der Interfacehardware, diese sollten aber Daemon-Threads sein, die die virtuelle Maschine nicht von sich aus am Laufen halten. 5.2 DriveTrain Wer sich im vorherigen Abschnitt gefragt hat, wie das denn funktionieren soll { einen Roboter zu steuern, ohne etwas uber ihn zu wissen { ist hier richtig. Es wird nun auch darum gehen, wie man einen Steuerungsalgorithmus fur mehrere Roboter wiederverwenden kann. In diesem Abschnitt wird ein Roboter programmiert, der fahren kann. Eine typische Bauweise fur einen einfachen fahrenden Roboter ist, zwei gegenuberliegende Rader oder Ketten unabhangig voneinander durch jeweils einen eigenen Motor anzutreiben. Auf diese Weise kann der Roboter vorwarts und ruckwarts fahren, sich auf der Stelle links- und rechtsherum drehen sowie { durch verschieden schnelles Drehen der Rader { richtige Kurven fahren. Die Fortbewegung von Kettenfahrzeugen, wie z.B. Baggern, funktioniert ahnlich. Abbildung 5.1: Zwei einfache fahrende Roboter aus den Bauanleitungen des Lego RIS 1.5 und Fischertechnik Mobile Robots. 5.2. 63 DRIVETRAIN Abbildung 5.1 auf der vorherigen Seite zeigt eine solche Konstruktion mit LEGO sowie mit Fischertechnik. Bauanleitungen fur ahnliche Roboter ndet man auch in Knudsen (1999) und Baum (2000). Das Java-Interface DriveTrain Letzten Endes ist es doch so, da man einen Roboter haben mochte, der sich fortbewegen kann { am besten in beliebige Richtungen (vorwarts, ruckwarts, seitwarts, hoch, runter, usw.). Auerdem soll er noch in der Lage sein, seine Orientierung zu verandern (sich drehen). Damit die Bewegung schon aussieht, ware es auerdem wunschenswert, wenn die Fortbewegung nicht nur auf Geraden sondern auch auf Kurven verlaufen konnte. Dabei ware es sinnvoll, dem Roboter nicht nur sagen zu konnen, wie weit er sich bewegen soll, sondern auch wie schnell. Das sind alles zusammen ziemlich groe Anspruche an den Roboter und, da sich ein Roboter beliebig im Raum bewegen und orientieren kann, erfordert einen groen Konstruktionsaufwand, um den es an dieser Stelle nicht geht. Um also realistisch zu bleiben, werden die Anforderungen an den Roboter folgendermaen festgelegt: Der Roboter soll vorwarts fahren, ruckwarts fahren, sich auf der Stelle linksherum drehen, sich auf der Stelle rechtsherum drehen und anhalten (!) konnen. Auerdem wird die Angabe einer Strecke und/oder Geschwindigkeit (zumindest vorerst) der Einfachheit halber auer Acht gelassen. Die eben denierte Funktionalitat erfordert einen Algorithmus, der sie implementiert. Auerdem mu ein Roboter (wie z.B. in Abbildung 5.1 auf der vorherigen Seite) her, der diese Funktionen hardware-technisch ermoglicht. Die Umsetzung von einem Befehl wie "Vorwarts" auf Befehle an die Komponenten des Roboters, wie z.B. die Motoren, hangt davon ab, wie der Roboter gebaut wurde. Daher ist es sinnvoll die Anforderungen von der konkreten Umsetzung zu trennen. In Java macht man dies uber die Denition eines sogenannten Interface, welches dann von verschiedenen Klassen adaptiert (und damit implementiert) werden kann:3 Listing 5: code/art/examples/DriveTrain.java package de.jaetzold.art.examples; public interface DriveTrain { public void forward(); ➥ 3 Eine andere Moglichkeit ware die Denition einer Basisklasse. 64 5. ARIADNE & ART - EIN TUTORIAL Listing 5: code/art/examples/DriveTrain.java (Fortsetzung) public public public public public void void void void void backward(); leftSpin(); rightSpin(); stop(); resume(); <see Listing 11 on page 73> } Anhand des Namens der Methoden ist leicht erkennbar, welche der eben denierten Anforderungen mit ihr umgesetzt werden soll. Die neu hinzugekommene Methode resume() mu noch deniert werden. Aus Grunden der Ubersichtlichkeit wurde auf die Angabe von JavaDoc-Kommentaren verzichtet.4 Die ersten vier Methoden forward() bis rightSpin() sollen alle dafur sorgen, da nicht nur die Bewegungsrichtung festgelegt wird sondern der Roboter sich dann auch wirklich in Bewegung setzt (falls er vorher still gestanden hat). Bis auf forward() ist eine Implementierung von DriveTrain aber nicht verpichtet, diesen Zustand auch wirklich umzusetzen. Es ware ja zum Beispiel ein Roboter vorstellbar, der nur vorwarts und ruckwarts fahren, sich aber nicht drehen kann. Knudsen's Minerva (Knudsen, 1999, S. 82) z.B. kann nur vorwarts fahren und sich in eine Richtung drehen. Aus diesem Grund soll hier darauf hingewiesen werden, da die Methoden backward(), leftSpin() und rightSpin() eine Exception auslosen konnen. Da keine throws-Klausel angegeben wurde, darf das nur eine RuntimeException sein. Eine java.util. UnsupportedOperationException bietet sich in diesem Fall an. Ein Algorithmus, der DriveTrain benutzt, fangt diese sinnvollerweise auch nur dann ab, wenn er auf die fehlende Funktionalitat selber reagieren mochte, indem er es z.B. mit einer Linksdrehung versucht, falls eine Rechtsdrehung nicht unterstutzt wird. Die Methode stop() halt die Bewegung des Roboters an. Die Methode resume() dient dazu, nach einem oder mehreren aufeinanderfolgenden stop(), wieder zu dem Zustand vor STOP zuruckzukehren. Zu Beginn startet DriveTrainSimpleAlgorithm im STOP-Zustand und es gibt keinen anderen Zustand in den resume() zuruckkehren konnte. In diesem Fall bewirkt resume() daher nichts. Der Code von DriveTrain wurde in Listing 5 nicht ganz vollstandig angegeben. Die ausgelassenen Zeilen denieren die Zustande in denen sich ein DriveTrain benden kann, und eine Methode um selbigen abzufragen. Dies kann verwendet werden um z.B. eine grasche View fur DriveTrain-Objekte zu implementieren (siehe Abschnitt 5.3 auf Seite 73). Implementierung von DriveTrain Ein Interface wie DriveTrain beschreibt nur, wie ein Objekt einer Klasse, die dieses Interface adaptiert, benutzt werden kann. Fur einen Roboter wie aus 4 Die Klassen aus dem package de.jaetzold.art.examples, in denen keine JavaDocKommentare enthalten sind, werden in diesem Tutorial beschrieben. 5.2. DRIVETRAIN 65 Abbildung 5.1 auf Seite 62 soll nun eine einfache Implementierung vorgestellt werden. Das entscheidende Kriterium fur einen solchen Roboter ist, da er zwei Motoren hat, einen linken und einen rechten, und da diese Motoren so eingebaut und angeschlossen sind, da folgende Bedingungen erfullt sind: Sind beide Motoren an und auf "forward" gestellt, fahrt der Roboter vorwarts. Sind beide Motoren an und auf "backward" gestellt, fahrt der Roboter ruckwarts. Sind beide Motoren an und drehen sich in verschiedene Richtungen, dann dreht sich auch der Roboter. Er dreht sich linksherum, wenn der linke Motor auf "backward" gestellt ist. Er dreht sich rechtsherum, wenn es der rechte Motor ist, der ruckwarts lauft. Dafur, da die Motoren diese Anforderungen erfullen, soll nicht die DriveTrain-Implementierung zustandig sein. Vielmehr sollen in dieser Implementierung erst einmal die Anforderungen des DriveTrain-Interfaces auf solche Motoren umgesetzt werden: Listing 6: code/art/examples/DriveTrainSimpleAlgorithm.java (Ref. in Listing 7 S. 66) protected Motor left; protected Motor right; protected int state; public void stop() { right.off(); left.off(); setState(state | STOP); } public void resume() { if(state != STOP) { right.on(); left.on(); setState(state & ˜ STOP); } } public void forward() { right.forward(); left.forward(); setState(FORWARD); resume(); } ➥ 66 5. ARIADNE & ART - EIN TUTORIAL Listing 6: code/art/examples/DriveTrainSimpleAlgorithm.java (Fortsetzung) ... public void leftSpin() { right.forward(); left.backward(); setState(LEFT SPIN); resume(); } ... Die Programmierung entspricht ziemlich genau der Funktionalitat, die von den Motoren verlangt wird. Die Verweise auf die Motoren sind mit left und right bezeichnet und besitzen Methoden f ur on(), off(), forward() und backward(), deren Bedeutung weitgehend selbsterklarend ist. Eine genaue Beschreibung ndet sich in der Klassendokumentation zu Motor (Siehe Anhang A.2 auf Seite 137). Nachdem die Motoren z.B. in der Methode leftSpin() auf entgegengesetzte Drehrichtung gesetzt wurden, wird der Zustand des DriveTrain durch den Aufruf von setState(int) neu gesetzt. Auf diese Methode wird in Abschnitt 5.3 eingegangen. Wichtig ist aber, da nach dem Setzen der Zustande5 der Motoren noch resume() aufgerufen wird. Die Zustande an/aus, sowie vorwarts/ruckwarts eines Motor-Objekts sind voneinander unabhangig und ein einfacher Aufruf von forward() f uhrt nicht dazu, da sich der entsprechende Motor einschaltet, sondern nur dazu, da er sich vorwarts dreht, wenn er bereits an ist bzw. sobald er angeschaltet wird. Die Methode resume() schaltet die Motoren an, sofern sich DriveTrain nicht in einem reinen STOP-Zustand bendet. Ein eventuell trotzdem vorhandenes STOP-Bit in der Variable state, welche den aktuellen Zustand speichert, wird geloscht. Entsprechend werden in stop() die Motoren ausgeschaltet und das STOP-Bit gesetzt. Die Stellen mit ". . . " markieren Auslassungen der Methoden backward() und rightSpin(), weil diese analog zu den abgedruckten Methoden implementiert sind, sowie Stellen, die fur ein Verstandnis der Implementierung nicht unbedingt notwendig sind. Der interessierte Leser wird ermuntert, selbst einen Blick in die Quellen zu werfen. Es fehlt noch der Teil des Codes, in dem das DriveTrainSimpleAlgorithmObjekt initialisiert wird, der Konstruktor : Listing 7: code/art/examples/DriveTrainSimpleAlgorithm.java package de.jaetzold.art.examples; import de.jaetzold.art.Motor; ... ➥ 5 Die Erklarung der dabei verwendeten Konstanten wie RIGHT SPIN und STOP bendet sich bei Listing 11 auf Seite 73. 5.2. 67 DRIVETRAIN Listing 7: code/art/examples/DriveTrainSimpleAlgorithm.java (Fortsetzung) public class DriveTrainSimpleAlgorithm implements DriveTrain { public DriveTrainSimpleAlgorithm(Robot robot) { this.left = robot.getLeftMotor(); this.right = robot.getRightMotor(); setState(STOP); stop(); } <see Listing 8 on page 67> <see Listing 6 on page 65> <see Listing 13 on page 75> ... } An dieser Stelle kommen wir der eigentlichen Roboterhardware naher. Der Konstruktor mu irgendwie die beiden Motor-Verweise left und right initialisieren. Dazu wird ein Robot-Objekt u bergeben, das man nach den Motoren fragen kann. Damit DriveTrainSimpleAlgorithm korrekt funktioniert, mussen die Motoren den Anforderungen genugen, die auf Seite 65 festegelegt wurden. Welcher Motor an welchem Anschlu der Hardware angeschlossen ist, sowie die Polung der Motoren, ist fur den Algorithmus DriveTrainSimpleAlgorithm jedoch gar nicht interessant. Es reicht vollig aus, wenn der Algorithmus bereits richtig angeschlossene und kongurierte Motoren bekommt und wei, welcher rechts und welcher links ist. Daher deniert der Algorithmus ein Interface Robot. Dieses Interface beschreibt damit sozusagen Roboter, die von dem Algorithmus gesteuert werden konnen. Listing 8: code/art/examples/DriveTrainSimpleAlgorithm.java (Ref. in Listing 7 S. 66) public static interface Robot { public Motor getLeftMotor(); public Motor getRightMotor(); } Ein Interface legt eigentlich nur die Signaturen, sowie die Resultattypen von Methoden fest. Trotzdem gibt es fur jede Denition eine Vereinbarung daruber, was von ihr erwartet wird. Diese Vereinbarung sollte, z.B. durch einen JavaDocKommentar, explizit gemacht werden, damit klar ist, wofur das Interface genau steht. Nur durch Angabe einer Signatur und des Resultattyps ist zwar klar, wie eine Methode benutzt werden kann, aber nicht wofur. Um ein moglichst gleichartiges und vorhersagbares Verhalten von verschiedenen Implementierungen eines Interfaces zu gewahrleisten ist es unerlalich, dieses Verhalten zusammen mit dem Interface zu denieren. Fur DriveTrainSimpleAlgorithm.Robot heit das, da die Methoden Motor-Objekte als Resultat liefern sollten, die den Anforderungen genugen von Seite 65 genugen. Eine solche Robot-Implementierung bendet sich auf einer "niedrigeren" Abstraktionsebene in Bezug auf den zu steuernden Roboter. Sie hangt starker als der vorher gezeigte Algorithmus von dem konkreten Roboter ab, den man 68 5. ARIADNE & ART - EIN TUTORIAL steuern mochte. Wichtig ist zum Beispiel, an welchen ActuatorPort welcher Motor angeschlossen werden mu, ob u berhaupt echte Motor-Objekte erzeugt werden, oder etwa eine Unterklasse davon. Schlielich mu auch die Drehrichtung eventuell an das Modell angepasst werden, damit "vorwarts" auch die korrekte Bedeutung fur den Roboter erhalt. Hier soll ein Beispiel fur eine Implementierung von DriveTrainSimpleAlgorithm.Robot gegeben werden, die davon ausgeht, da bei dem zu steuernden Roboter die entsprechenden, ganz normalen Motoren an den Ausgangen 0 und 1 angeschlossen sind. Welche Anschlusse damit an der jeweiligen Hardware gemeint sind, hangt naturlich von der Implementierung von RobotInterface ab. Bei der in ART enthaltenen Implementierung fur Fischertechnik sind das M1 und M2, beim Cybermaster die beiden eingebauten Motoren und beim Brick die Anschlusse 1 und 2. Die Drehrichtung wird in diesem Beispiel noch nicht angepasst. Im weiteren Verlauf dieses Kapitels wird aber auch darauf Rucksicht genommen. Listing 9: code/art/examples/DriveTrainSimpleRobot.java package de.jaetzold.art.examples; import import import import import de.jaetzold.art.Motor; de.jaetzold.art.ActuatorPort; de.jaetzold.art.RobotInterface; de.jaetzold.art.RobotInterfaceDefinition; de.jaetzold.art.RobotInterfaceStringDefinition; public class DriveTrainSimpleRobot implements DriveTrainSimpleAlgorithm.Robot { protected Motor leftMotor; protected Motor rightMotor; public DriveTrainSimpleRobot(RobotInterface iface) { ActuatorPort[] ports = iface.getActuatorPorts(); if(ports.length < 2) { throw new IllegalArgumentException("RobotInterface " +iface +" provides only " +ports.length +" Ports, minimum is 2."); } leftMotor = new Motor(); leftMotor.connectWith(ports[0]); rightMotor = new Motor(); rightMotor.connectWith(ports[1]); } public Motor getLeftMotor() { return leftMotor; ➥ 5.2. DRIVETRAIN 69 Listing 9: code/art/examples/DriveTrainSimpleRobot.java (Fortsetzung) } public Motor getRightMotor() { return rightMotor; } public DriveTrainSimpleRobot(RobotInterfaceDefinition definition) { this(new de.jaetzold.art.RobotInterfaceFactory() .getInterface(definition)); } public DriveTrainSimpleRobot(String interfacePortName) { this(new RobotInterfaceStringDefinition(interfacePortName)); } public DriveTrainSimpleRobot() { this(System.getProperty(className +".portName", "ALL")); } private static String className = "de.jaetzold.art.examples.DriveTrainSimpleRobot"; ... } Eine Implementierung fur einen weiteren, etwas anderen Roboter zeigt Listing 10 auf Seite 71. Die hier gezeigte Implementierung besitzt mehrere Konstruktoren, um auf verschiedenen Ebenen in die Erzeugung des RobotInterface eingreifen zu konnen: Der erste Konstruktor erwartet bereits ein fertiges RobotInterface, das nichts weiter aufweisen mu als zwei Ports, um die Motoren daran anzuschlieen. Wie das genau geht, wurde bereits in Abschnitt 5.1 auf Seite 59 erlautert. Der an Port 0 angeschlossene Motor mu der linke sein, der an Port 1 angeschlossene der rechte. Der zweite Konstruktor von DriveTrainSimpleRobot gibt einem die Moglichkeit, u ber ein String-Objekt festzulegen, welches Interface an welchem Port fur den Anschlu der Motoren verwendet werden soll. RobotInterfaceDefinition ist ein Interface, welches keine Methoden vorschreibt. Objekte von Klassen, die dieses Interface adaptiert haben, konnen aber der RobotInterfaceFactory als Parameter fur die Konstruktion eines RobotInterface ubergeben werden. Damit kann eingeschrankt werden, welche Implementierungen von RobotInterface die Factory zuruckliefert. Von einer Implementierung von RobotInterfaceDefinition wird nichts Besonderes erwartet, es hatten auch schlicht Objekte vom Typ Object verlangt werden konnen, doch ware damit der Sinn dieses Parameters schwieriger zuganglich gewesen. Welche Implementierung von RobotInterfaceDefinition benotigt wird, hangt von den Implementierungen von RobotInterface, die einen speziell interessieren, ab und sollte in deren Beschreibung dokumentiert sein. Die bereits vorhandenen Implementierungen fur LEGO und Fischertechnik reagieren auf Strings, welche von einer RobotInterfaceStringDefinition gekapselt werden. Zur einfacheren Benutzung wird daher auch gleich noch ein Konstruktor mit einem String als Parameter zur Verfugung gestellt. Strings als RobotInterfaceDefinition bieten den Vorteil, da uber soge- 70 5. ARIADNE & ART - EIN TUTORIAL nannte Properties wie z.B. im dritten, parameterlosen Konstruktor leicht gesteuert werden kann, welches Interface genommen werden soll. Dieser String beginnt mit einer Kennzeichnung fur das Interface, welches man haben mochte, und darauf folgt (optional) der Name des (seriellen) Ports, an dem nach dem Interface gesucht werden soll. Beispiele: "FT" { die Implementierung von Fischertechnik sucht auf allen moglichen Ports nach einem Interface "MSCOM2" { auf dem von JavaComm6 mit COM2 bezeichneten seriellen Port wird nach einem Brick gesucht "CM/dev/ttyS0" { auf dem Port mit Namen /dev/ttyS0 wird nach einem Cybermaster-Interface gesucht7 "RCX1" { auf COM1, /dev/ttyS0 sowie dem ersten von JavaComm gelieferten Portnamen fur einen seriellen Port wird nach einem Cybermaster oder einem Brick gesucht Das letzte Beispiel steht fur eine besondere Interpretation des Portnamens durch die Implementierungen fur Lego und Fischertechnik. Besteht der Portname ausschlielich aus einer Zahl "n", werden als Portnamen die beiden vordenierten Namen COMn und /dev/ttySn genommen und auerdem der n-te Name eines seriellen Ports aus der Liste von Portnamen, die man von JavaComm als Enumeration geliefert bekommt. Es ist zwar nicht genau speziziert, aber bei Tests wurden die seriellen Ports bisher immer in der Reihenfolge angegeben, wie sie auch vom jeweiligen System nummeriert werden. Die Namensgebung ist jedoch von der verwendeten Implementierung des Java Communications API abhangig. Die Implementierung von Sun fur Windows, sowie die Implementierung von IBM fur Linux und fur Windows, nennen serielle Ports COM1, COM2 usw. Die freie Implementierung "rxtx" ( http://www.rxtx.org ) nennt serielle Ports unter Linux wie die entsprechenden Device-Namen, also z.B. /dev/ttyS0, /dev/ttyS1 usw. Eine Einfuhrung in das Java Communications API gibt Jatzold (1999). Tests zeigen, da die Implementierung von Sun unter Windows extrem langsam ist. Die in Schreiner (2000b) beschriebenen Geschwindigkeitsprobleme treten sowohl mit rxtx unter Linux als auch mit der Implementierung von IBM unter Windows und Linux nicht auf. Zum Testen, ob alles funktioniert, kann die Klasse DriveTrainSimpleRobot auch direkt ausgefuhrt werden. Die main-Methode erzeugt sogar aus einem ubergebenen Argument die verwendete RobotInterfaceStringdefinition. Alternativ kann auch "von Hand" die Property de.jaetzold.art.examples.DriveTrainSimpleRobot.portName gesetzt werden: $ java -Dde.jaetzold.art.examples.DriveTrainSimpleRobot.port ➥ 6 Java Communications API. Siehe Sun Microsystems (1998) und Jatzold (1999). Der Cybermaster wird in der aktuellen Version von ART nicht mehr unterstutzt. Zukunftige Versionen konnten diese Unterstutzung aber moglicherweise wieder enthalten. 7 5.2. DRIVETRAIN 71 Name=RCX2 de.jaetzold.art.examples.DriveTrainSimpleRobot Successfully initialized LEGO-Mindstorms Interface, found on serialport COM2 $ Kommt eine vergleichbare Meldung, dann hat alles wie erwartet geklappt. Kommt aber zum Beispiel eine Exception, hilft ein Blick in den Anhang, Abschnitt B auf Seite 139 und/oder der Abschnitt A auf Seite 135, moglicherweise weiter. Das Fenster, das bei erfolgreicher Ausfuhrung geonet wird, kann man schon mal zum Testen verwenden. Wieso dieses Fenster u berhaupt auftaucht, wird am Ende dieses Abschnitts und in dem darauf Folgenden naher erlautert. Hat man aus irgendeinem Grund bereits ein fertiges Robotermodell, bei dem die Motoren nicht so angeschlossen sind, da DriveTrainSimpleRobot fur DriveTrainSimpleAlgorithm geeignet ist, mu man nicht unbedingt das gesamte Interface DriveTrainSimpleAlgorithm.Robot neu implementieren. Man kann sich die Fahigkeiten von objektorientierter Programmierung zunutze machen und einfach eine Unterklasse von DriveTrainSimpleRobot implementieren, die die Motoren auf geeignete Weise manipuliert. Der fur diese Arbeit gebaute Beispielroboter hat in seiner Lego-Version die Motoren anders angeschlossen: Listing 10: code/art/examples/DriveTrainSimpleMSDemoRobot.java public class DriveTrainSimpleMSDemoRobot extends DriveTrainSimpleRobot { public DriveTrainSimpleMSDemoRobot(RobotInterface iface) { super(iface); // the Motors on the MS-Robot are on different sides Motor tmp = leftMotor; leftMotor = rightMotor; rightMotor = tmp; // the Motors on the MS-Robot turn the other way round leftMotor.setReversed(true); rightMotor.setReversed(true); } public DriveTrainSimpleMSDemoRobot(String interfacePortName) { this(new de.jaetzold.art.RobotInterfaceFactory() .getInterface(interfacePortName)); } public DriveTrainSimpleMSDemoRobot() { this(System.getProperty(className +".portName", "MS")); } private static String className = "de.jaetzold.art.examples.DriveTrainSimpleMSDemoRobot"; ... } Da die Motoren aber auch an den ersten und den zweiten Ausgang angeschlossen sind, ist es nicht notig neue Motor-Objekte zu erzeugen. Die Vorhan- 72 5. ARIADNE & ART - EIN TUTORIAL denen werden einfach vertauscht und auf die umgekehrte Drehrichtung gesetzt. Ansonsten kann weiterhin zur Steuerung DriveTrainSimpleAlgorithm verwendet werden. RobotInterface "Wire" (RS232, I2C, Bluetooth, Ethernet, ...) Motor1 Motor2 Motor3 Motor4 SwitchSensor1 SwitchSensor2 Motor informs about state−change SwitchSensor8 AnalogSensor1 AnalogSensor2 forward() backward() stop() connects Motor with interface retrieved from RobotInterface DTSA.Robot creates, configures & delivers Motor getLeftMotor() getRightMotor() & connects with port DTS = DriveTrainSimple DTSA = DriveTrainSimpleAlgorithm leftMotor initialized with ActuatorPort Virtual Sensors: (e.g. CountSensor) DTSAlgorithm retrieved from factory DTSRobot is a (implements) Abbildung 5.2: Ein mogliches Design-Pattern fur die Programmierung mit ART. Der Algorithmus kann unterschiedliche "Roboter" steuern, sofern fur diese eine geeignete Implementierung des Robot-Interface vorliegt. Diese Aufteilung, in einen Algorithmus sowie eine Ansammlung von Parametern in Form eines Java-Interface, hat den Vorteil, da der Algorithmus fur verschiedene Roboter wiederverwendet werden kann. Der Algorithmus DriveTrainSimpleAlgorithm kann mit jedem Roboter verwendet werden, fur den eine Implementierung des Java-Interface DriveTrainSimpleAlgorithm.Robot existiert (bzw. erzeugt werden kann), die sicherstellt, da die auf Seite 65 beschriebenen Anforderungen an die Motoren erfullt sind. Die Abbildung 5.2 veranschaulicht die Verbindungen, Zusammenhange und Verantwortlichkeiten dieses Design-Patterns auf grasche Weise. Da die Klassennamen sehr lang sind, wurden Abkurzungen in Form von Buchstabenfolgen verwendet, deren Bedeutung in der Grak angegeben ist. Das dargestellte RobotInterface entspricht in etwa der Implementierung fur Fischertechnik. Auf die Anzahl und Art der dort angegebenen Motoren und Sensoren kommt es jedoch nicht an, sie dienen nur als Beispiel, zur Verdeutlichung wofur RobotInterface steht. Eine genauere Beschreibung von RobotInterface und dem Zusammenspiel zwischen diesem mit Motor, MotorPeer, ActuatorPort usw. bendet sich in Kapitel 6. Eine genauere Erklarung, wofur die Klassen jeweils stehen gibt auerdem Kapitel 4. Auch die Klasse DriveTrainSimpleMSDemoRobot aus Listing 10 auf der vorherigen Seite hat eine main-Methode, die man zum Testen direkt ausfuhren kann. Die Ubergabe eines Parameters fur das gewunschte Interface ist diesmal weggelassen, da DriveTrainSimpleMSDemoRobot sowieso als default ein Mindstorms-Interface verlangt (der zweite Parameter zu System.getProper- 5.3. UND WIE BENUTZT MAN DAS NUN? 73 ty() gibt den default-Wert an und lautet "MS"). $ java de.jaetzold.art.examples.DriveTrainSimpleMSDemoRobot Successfully initialized LEGO-Mindstorms Interface, found on serialport COM2 $ Im folgenden Text wird nun nicht mehr jedesmal auf die Moglichkeit der Ausfuhrung zum Testen hingewiesen und ein Beispiel gezeigt. Fast alle Klassen aus dem package de.jaetzold.art.examples, welche das Wort "Robot" im Namen enthalten, sind auf diese Weise ausfuhrbar. 5.3 Und wie benutzt man das nun? Im letzten Abschnitt wurden viele Klassen, bzw. Interfaces geschrieben, aber nichts hat sich bewegt. Objekte der Klasse DriveTrainSimpleAlgorithm machen von alleine ja nicht viel, sie steuern den Roboter nur, wenn sie durch einen Methodenaufruf explizit dazu aufgefordert werden. Abbildung 5.3: Testfenster f ur DriveTrain Nur wer vorhin wirklich die vorgestellten Roboter-Implementationen zur Ausfuhrung gebracht hat, wird wahrscheinlich bemerkt haben (wenn alles geklappt hat), da in diesem Fall ein Fenster auf der Oberache erscheint, welches in etwa so aussieht wie das in Abbildung 5.3 gezeigte. In diesem Abschnitt wird diese einfache View fur DriveTrain-Objekte auf Basis des AWT vorgestellt. Sie ist aufgeteilt in zwei Klassen. Die Klasse DriveTrainView stellt den Zustand eines DriveTrain in einem Textfeld dar. Die andere Klasse (DriveTrainCommands) bietet eine Art Armaturenbrett um einen DriveTrain zu beeinussen. Beide sind als Unterklassen von Panel realisiert und daher leicht in eine beliebige Oberache integrierbar. Welche Zustande es gibt, wurde bereits im Interface DriveTrain deniert: Listing 11: code/art/examples/DriveTrain.java (Ref. in Listing 5 S. 63) public int getState(); public static final int FORWARD = 1; public static final int BACKWARD = 2; ➥ 5. ARIADNE & ART - EIN TUTORIAL 74 Listing 11: code/art/examples/DriveTrain.java (Fortsetzung) public static final int LEFT SPIN = 3; public static final int RIGHT SPIN = 4; public static final int STOP = 8; Der Zustand kann mit getState() abgefragt werden und uber die Konstanten kann eine Zuordnung des dabei erhaltenen int-Werts, zu einer bestimmten Bedeutung, erfolgen. Die eine View macht nichts weiter, als den Zustand eines DriveTrainObjekts in einen String umzuwandeln und diesen in einem Textfeld auszugeben: Listing 12: code/art/examples/DriveTrainView.java package de.jaetzold.art.examples; import import import import java.awt.Panel; java.awt.TextField; java.beans.PropertyChangeListener; java.beans.PropertyChangeEvent; public class DriveTrainView extends Panel implements PropertyChangeListener { protected TextField stateOutput = new TextField("", 15); public void propertyChange(PropertyChangeEvent pce) { if(pce.getSource() instanceof DriveTrain && pce.getPropertyName().equals("state") && pce.getNewValue() instanceof Number) { String stateName = ""; int state = ((Number)pce.getNewValue()).intValue(); switch(state & ˜ DriveTrain.STOP) { case DriveTrain.FORWARD: stateName = "forward"; break; ... default: stateName = ""; } if((state & DriveTrain.STOP) == DriveTrain.STOP) { stateName = "stop " +stateName; } stateOutput.setText(stateName); } } ... } Die DriveTrainView ist ein PropertyChangeListener und versucht den neuen Wert eines PropertyChangeEvent als Zustand eines DriveTrain-Objekts zu interpretieren, sofern der Event von einem DriveTrain-Objekt kommt und die zugehorige Property den Namen state hat. 5.3. UND WIE BENUTZT MAN DAS NUN? 75 Nicht jedes DriveTrain-Objekt mu einen PropertyChangeListener fur die Beobachtung seines Zustands haben konnen, daher sind die Methoden zum Hinzufugen oder Entfernen eines solchen nicht bereits im Interface DriveTrain enthalten. Das hat den Grund, da das Hinzufugen von Views in der Regel am besten dort geschieht, wo das DriveTrain-Objekt erzeugt wurde. Daher reicht es, wenn nur die implementierenden Klassen die entsprechenden Methoden bieten: Listing 13: code/art/examples/DriveTrainSimpleAlgorithm.java (Ref. in Listing 7 S. 66) protected PropertyChangeSupport changes = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener l) { changes.addPropertyChangeListener(l); } public void removePropertyChangeListener(PropertyChangeListener l) { changes.removePropertyChangeListener(l); } protected void setState(int state) { int previousState = this.state; this.state = state; changes.firePropertyChange("state", previousState, state); } public int getState() { return state; } Das Setzen eines Zustands in DriveTrainSimpleAlgorithm sollte immer uber die Methode setState(int) geschehen, damit eventuelle java.beans. PropertyChangeListener automatisch informiert werden. Den notigen java. beans.PropertyChangeEvent erzeugt die mit changes bezeichnete Instanz von java.beans.PropertyChangeSupport. Dieses Vorgehen entspricht (bis auf die Verwendung von PropertyChangeSupport) dem Design-Pattern, wie es von der JavaBeans Specication 1.01 (Sun Microsystems, 1997) in Bezug auf sogenannte Bound-Properties vorgeschrieben ist. Zu ART im Zusammenhang mit JavaBeans siehe Abschnitt 7.8. Die DriveTrainView ist zum Beobachten des Zustands eines DriveTrainObjektes da. Damit etwas passiert, mu dieser Zustand aber auch manipuliert werden konnen. Fur eine grasche Oberache bietet die Klasse DriveTrainCommands diese Moglichkeit: Listing 14: code/art/examples/DriveTrainCommands.java package de.jaetzold.art.examples; import import import import java.awt.Panel; java.awt.Button; java.awt.event.ActionListener; java.awt.event.ActionEvent; ➥ 76 5. ARIADNE & ART - EIN TUTORIAL Listing 14: code/art/examples/DriveTrainCommands.java (Fortsetzung) public class DriveTrainCommands extends Panel { public DriveTrainCommands(DriveTrain driveTrain) { this(); setDriveTrain(driveTrain); } public DriveTrainCommands() { Button stop = new Button("stop"); stop.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { if(dt != null) { dt.stop(); } } } ); add(stop); ... } protected DriveTrain dt; public void setDriveTrain(DriveTrain dt) { this.dt = dt; } } DriveTrainCommands stammt genau wie die DriveTrainView von Panel ab, damit sie leicht in eine Oberache integriert werden kann. Fur jede Methode von DriveTrain, die dessen Zustand beeinusst, gibt es einen Button, uber den die entsprechende Methode aufgerufen werden kann. Textfelder oder andere Moglichkeiten zur weiteren Dateneingabe sind nicht notig, da die entsprechenden Methoden alle parameterlos sind. Der Code fur die Konstruktion der anderen Knopfe wurde hier im Text aus Grunden der Ubersichtlichkeit weggelassen. Die Klasse DriveTrainSimpleAlgorithm besitzt zum Testen eine mainMethode, die eine DriveTrainView und ein -Commands mit einem Objekt von sich verbindet und in einem Frame darstellt: $ java de.jaetzold.art.examples.DriveTrainSimpleAlgorithm RCX Successfully initialized LEGO-Mindstorms Interface, found on serialport COM2 $ Welche Implementierung des Robot-Interface verwendet wird, kann man auch hier wieder uber eine Property steuern (de.jaetzold.art.examples. DriveTrainSimpleAlgorithm.robotClass). Per Voreinstellung wird ansonsten die Klasse DriveTrainSimpleRobot genommen. Der Aufruf bringt das in Abblidung 5.3 auf Seite 73 gezeigte Fenster zum Vorschein, mit dem die Klasse schon ganz gut ausprobiert werden kann. 5.4. TRUSTY 5.4 77 Trusty Bisher wurden in diesem Tutorial nur Motoren, also Ausgange eines Roboters gesteuert. Ein "richtiger" Roboter mu aber auch Eingange haben, Sensoren mit denen die Umwelt wahrgenommen werden kann. Wie in Kapitel 2 beschrieben, hat Trusty dafur zwei Bumper mit denen er Hindernisse registrieren kann. Daher soll anhand seiner Programmierung der einfache Umgang mit Sensoren in ART demonstriert werden. Zunachst ist der prinzipielle Aufbau von Trusty wieder ahnlich wie bei DriveTrain, um eine moglichst weitgehende Unabhangigkeit des Algorithmus zu erreichen. Es gibt ein Interface, das die Fahigkeiten von Trusty deniert: Listing 15: code/art/examples/Trusty.java package de.jaetzold.art.examples; public interface Trusty { public void move(); public void stop(); public void avoidLeft(); public void avoidRight(); ... } Die Fahigkeiten von Trusty umfassen also ein irgendwie geartetes Fortbewegen (move()), Anhalten (stop()) sowie nach links bzw. rechts Ausweichen (avoidLeft(), avoidRight()). Die Zuordnung des Verhaltens "ausweichen" zu einem entsprechenden Ereignis, wie z.B. dem Auslosen eines der Bumper, wird an dieser Stelle vorerst auer Acht gelassen. Durch diese Trennung erreicht man eine groere Flexibilitat im Umgang mit den entwickelten Algorithmen, da die dann nur lose miteinander gekoppelten Verhaltensweisen und Ereignisse leichter neu gruppiert, sowie in einem anderen Kontext verwendet werden konnen. Dies wird aber auch durch eine weitere Verteilung des Codes auf noch mehr Klassen erkauft, was nicht nur Vorteile mit sich bringt. Weil die Zusammenhange nicht mehr zentral festgelegt werden, sind diese auch nicht immer sofort ersichtlich. Die einfache Beispiel-Implementierung von Trusty ist TrustySimpleAlgorithm, welche eine spezielle Form eines DriveTrain verwendet, die eine Angabe von Strecken bzw. Winkeln als Parameter zu Methoden wie forward bzw. leftSpin() erlaubt: Listing 16: code/art/examples/NormalizedDriveTrain.java package de.jaetzold.art.examples; public interface NormalizedDriveTrain extends DriveTrain { public void forward(double meters); ... } Wird bei einem NormalizedDriveTrain z.B. forward mit einem Parameter 78 5. ARIADNE & ART - EIN TUTORIAL aufgerufen, dann wird von einer Implementierung erwartet, da sich der Roboter die entsprechende Strecke in Metern vorwarts bewegt und danach anhalt. Entsprechendes gilt fur die Methoden zum Drehen des Roboters, die Maeinheit sind in diesem Fall ganze Umdrehungen. Ein Aufruf von backward(0.1) bewegt den Roboter also um 10cm nach hinten und ein Aufruf von leftSpin(0.25) dreht den Roboter um 90Æ nach links. Die Wahl der Maeinheit ist reine Geschmackssache. Die zugehorige Implementierung ist in der Klasse NormalizedDriveTrainSimpleAlgorithm enthalten, welche auch ein eigenes Robot-Interface deniert. Dieses Interface schreibt zusatzlich zu den Methoden aus DriveTrainSimpleAlgorithm.Robot noch zwei weitere vor: Listing 17: code/art/examples/NormalizedDriveTrainSimpleAlgorithm.java public interface Robot extends DriveTrainSimpleAlgorithm.Robot { public int getMeterMilliseconds(); public int getFullTurnMilliseconds(); } Diese Methoden dienen dazu, die Zeitdauer anzugeben, die der jeweilige Roboter braucht, um einen Meter vorwarts zu kommen bzw. eine volle Umdrehung zu machen. Damit ist eine einfache Implementierung von NormalizedDriveTrain moglich, die fur die entsprechende Zeitdauer das gewunschte Verhalten "einstellt" und danach anhalt: Listing 18: code/art/examples/NormalizedDriveTrainSimpleAlgorithm.java public void forward(double meters) { if(meters<0) { backward(meters*-1); } forward(); try { Thread.sleep((long)(meterMilliseconds*meters)); } catch(InterruptedException ie) { } stop(); } Auf die Angabe des restlichen Codes, insbesondere der Initialisierung, wird verzichtet, da dieser nach den nun bereits bekannten Schemata realisiert ist. Eine grasche Ubersicht uber die Zusammenhange zeigt Abbildung 5.4 auf der nachsten Seite. Wie es zu der Verbindung zum eigentlichen Roboter kommt, wurde bereits in Abbildung 5.2 auf Seite 72 gezeigt und ist an dieser Stelle zugunsten einer besseren Ubersichtlichkeit ausgespart worden. Es wird in diesem Zusammenhang vielleicht noch ein weiterer Vorteil der Zusammenfassung der Parameter des Algorithmus zu einem Java-Interface deutlich: Die Parameter sind nicht voneinander unabhangig und mussen daher als Einheit angesehen werden, was sich auf diese Weise auch syntaktisch widerspiegelt. Welche Zeit der Roboter z.B. fur das Zurucklegen der Strecke von einem Meter braucht und in welchem Verhaltnis diese zu der Zeit fur eine ganze Um- 5.4. 79 TRUSTY DTSRobot implements getLeftMotor() getRightMotor() extends & initializes uses DTSMSDemoRobot defines & DTSA.Robot DTSAlgorithm implements DriveTrain initializes with extends forward() leftSpin() extends to forward extends defines & NDTSA.Robot implements getMeterMillis() getTurnMillis() initializes with NDTSAlgorithm implements NormalizedDT forward(meters) leftSpin(turns) NDTSBaseRobot DTS = DriveTrainSimple DTSA = DriveTrainSimpleAlgorithm extends & initializes NDTSMSDemoRobot extends & initializes NDTSFTDemoRobot NDTS = NormalizedDriveTrainSimple NDTSA = NormalizedDriveTrainSimpleAlgorithm Abbildung 5.4: Design der Beispielimplementierung von NormalizedDriveTrain. drehung steht, hangt zum Beispiel direkt mit der konkreten Implementierung der verwendeten Motoren zusammen und kann daher auch nicht unabhangig von dieser sinnvoll verwendet werden. Zudem ware es vorstellbar, da ein Parameter erst erzeugt bzw. berechnet wird, wenn er gebraucht wird (d.h. wenn die Interface-Implementierung danach gefragt wird). Davon wird zwar bei keinem der Beispiele in diesem Kapitel Gebrauch gemacht, man sollte diese Moglichkeit aber im Hinterkopf behalten, wenn man mit einem solchen Robot-Interface arbeitet. Vielleicht andern sich manche Parameter ja sogar (noch wahrend einer Programmausfuhrung) mit der Zeit? Ich mochte auch noch auf eine Schwachstelle der gezeigten Implementierung von NormalizedDriveTrain hinweisen. Sie geht davon aus, da die Zeit, die der Roboter zum Fahren einer Strecke braucht, in einem linearen Zusammenhang mit der Strecke steht. Genau genommen ist es sogar so, da die angenommene Zeit ein Vielfaches der gewunschten Strecke ist. Da ein Roboter aber auch Zeit zum Beschleunigen braucht, bzw. die Kommandos wie forward() oder stop() einen gewissen zeitlichen "overhead" haben konnen, ist diese Implementierung nicht sehr prazise. Dies wird sich spater bei der Implementierung von Trusty fur den Lego-Roboter noch als ein Problem herausstellen.8 Doch nun wieder zuruck zu der Implementierung von Trusty, dem eigentlichen Anliegen in diesem Abschnitt. Mit einem NormalizedDriveTrain ist zum Beispiel eine einfache Implementierung von avoidLeft() folgendermaen moglich: Listing 19: code/art/examples/TrustySimpleAlgorithm.java (Ref. in Listing 20 S. 80) public void avoidLeft() { ➥ 8 Das Problem mit dem Brick liegt in der Zeit, die vergeht, bis ein Befehl an den Motor auch im Roboter angekommen und umgesetzt ist. 80 5. ARIADNE & ART - EIN TUTORIAL Listing 19: code/art/examples/TrustySimpleAlgorithm.java (Fortsetzung) setState(AVOID LEFT); driveTrain.backward(backupMeters); driveTrain.rightSpin(turnAngle); stop(); } Es wird einfach eine gewisse Strecke zuruckgefahren und dann eine Rechtsdrehung vollzogen. Wie weit zuruckgefahren wird und der Umfang der Drehung sind Parameter die wieder, ebenso wie driveTrain, bei der Konstruktion des TrustySimpleAlgorithm in Form eines entsprechenden Robot-Objektes u bergeben und auf diese Weise initialisiert wurden: Listing 20: code/art/examples/TrustySimpleAlgorithm.java package de.jaetzold.art.examples; ... public class TrustySimpleAlgorithm implements Trusty { protected NormalizedDriveTrain driveTrain; protected double backupMeters; protected double turnAngle; public TrustySimpleAlgorithm(Robot robot) { driveTrain = robot.getNormalizedDriveTrain(); backupMeters = robot.getBackupMeters(); turnAngle = robot.getTurnAngle(); } public static interface Robot { public NormalizedDriveTrain getNormalizedDriveTrain(); public double getBackupMeters(); public double getTurnAngle(); } <see Listing 19 on page 79> ... } In diesem Fall werden (wie schon bei NormalizedDriveTrain) gar keine Objekte mehr, die direkt aus ART stammen, ubergeben, sondern einfach nur statische Parameter fur den Algorithmus, sowie ein weiterer Algorithmus (in diesem Fall ein NormalizedDriveTrain), auf dem der Algorithmus arbeitet. Diese werden wieder, um das bisherige Robot-Design-Pattern beizubehalten, mittels einer interface-Denition gruppiert (siehe Abbildung 5.5 auf der nachsten Seite). Damit sind die grundsatzlichen Aktionen von Trusty bereits implementiert und konnen auch durch Ausfuhrung der jeweiligen Robot-Implementierung getestet werden. Zur Erinnerung: Die hier besprochenen Robot-Beispielimplementierungen enthalten alle eine main-Methode, welche eine Oberache zum Vorschein bringt, mit der man den Algorithmus fur den jeweiligen Roboter testen kann. In diesem Zusammenhang sei fur eigene Gehversuche nochmals darauf hingewiesen, da die in ART enthaltenen Implementierungen von RobotInterface 5.4. 81 TRUSTY implements TSBaseRobot defines & TSA.Robot getNDT() getTurnAngle() extends TSAlgorithm implements initializes with Trusty move() avoidLeft() extends & initializes & initializes TSMSDemoRobot TSFTDemoRobot TS = TrustySimple TSA = TrustySimpleAlgorithm creates/delivers appropriate creates/delivers appropriate DT = DriveTrain NDT = NormalizedDriveTrain NormalizedDT forward(meters) leftSpin(turns) Abbildung 5.5: Design der Beispielimplementierung von plementierung von NormalizedDriveTrain wird Trusty. Eine vorhandene Im- wiederverwendet. zwar eigene Threads erzeugen, diese aber allesamt sogenannte daemon -Threads sind, welche die Java-Maschine nicht von sich aus am Laufen halten. Damit wird die Tatsache, da u berhaupt eigene Threads verwendet werden, besser versteckt, denn ein Programm, das ART verwendet, geht genau auf die gleiche Weise zu Ende, wie das ohne die Verwendung von ART der Fall ware. Ansonsten mute man entweder System.exit() aufrufen, um ein Programm zu beenden { wie das z.B. bei Verwendung des AWT notwendig ist { oder in ART eine eigene Methode zur Verfugung stellen, mit der man die erzeugten nicht-daemon Threads beenden kann. Einer dieser Threads ist dafur zustandig, die Events, die eventuelle Veranderungen an den Hardware-Sensoren reprasentieren, an die Komponenten (die Sensoren "in Software") auszuliefern, die an den zugehorigen Port angeschlossen sind. An diese Sensor-Objekte konnen wiederum SensorListener angeschlossen sein, die ihrerseits dann auch von einem Thread informiert werden, indem bei ihnen die Methode processEvent(SensorEvent) aufgerufen wird. Bei den in ART vorhandenen Implementierungen von Sensoren ist dieser Thread normalerweise derselbe wie der, mit dem der Sensor selbst benachrichtigt wurde. Sensoren wurden in diesem Kapitel bisher noch gar nicht direkt behandelt. Trusty hat aber zwei "Bumper". Um die gewunschte Verhaltensweise von Trusty zu erreichen, da er einem auf diese Weise registrierten Hindernis automatisch ausweicht, mu der Event, da Trusty irgendwo angestoen ist, dem entsprechenden Ausweich-Verhalten zugeordnet werden. Der Zustand, ob Trusty mit einem seiner Bumper irgendwo angestoen ist, wird am einfachsten u ber einen BooleanSensor reprasentiert. Ein BooleanSensor interpretiert den von einem Sensor erhaltenen Wert per Voreinstellung so, da der Wert 0 false bedeutet und alle anderen Werte (insbesondere 1) true zugeordnet werden. Ist also der BooleanSensor f ur den linken Bumper im Zustand true, dann soll links ausgewichen werden, entsprechendes gilt fur den Sensor fur den rechten Bumper. Man kann dieses Verhalten auch so darstellen, da Trusty im allgemeinen geradeaus fahrt und nur dann ausweicht, wenn ein Event, der true entspricht, von einem der Sensoren eintrit. Das Ausweich-Verhalten uberdeckt dann das 5. ARIADNE & ART - EIN TUTORIAL 82 normale Fahrverhalten { es hat eine hohere Prioritat. Trusty eignet sich daher als ein einfaches Beispiel fur Subsumption. Auf Subsumption wird in Abschnitt 1.2 auf Seite 4 genauer eingegangen, an dieser Stelle soll mehr die Anwendung fur Trusty mittels der Hilfsklasse de.jaetzold. art.subsumption.SchedulerTask im Vordergrund stehen. Das Robot-Interface von SubsumptionTrusty liefert einen Trusty, sowie zwei BooleanSensor-Objekte, welche links und rechts zugeordent sind: Listing 21: code/art/examples/SubsumptionTrusty.java public interface Robot { public Trusty getTrusty(); public BooleanSensor getLeftSensor(); public BooleanSensor getRightSensor(); } Die Implementierung dieses Interface gestaltet sich genauso einfach wie bisher. Der Anschlu von Sensoren unterscheidet sich nur in den verwendeten Ports von dem Anschlu von Motoren:9 Listing 22: code/art/examples/SubsumptionTrustyBaseRobot.java public SubsumptionTrustyBaseRobot( Trusty trusty, RobotInterface iface) { this.trusty = trusty; SensorPort[] ports = iface.getSensorPorts(); if(ports.length < 2) { throw new IllegalArgumentException("RobotInterface " +iface +" provides only " +ports.length +" SensorPorts" +", minimum is 2."); } leftSensor = new BooleanSensor(); leftSensor.connectWith(ports[0]); rightSensor = new BooleanSensor(); rightSensor.connectWith(ports[1]); } Wieder wird in dieser Basis-Implementierung davon ausgegangen, da die Sensoren an den ersten beiden SensorPorts angeschlossen werden mussen und keiner weiteren Konguration bedurfen. Wie Sensoren konguriert werden mussen und worauf man dabei achten mu, bzw. welche Moglichkeiten man hat, erklart Abschnitt 5.5. Die booleschen Sensoren konnen im Konstruktor von SubsumptionTrusty direkt dazu verwendet werden, die Verhaltensweisen fur das Ausweichen zu dem SchedulerTask hinzuzufugen: 9 Genau genommen ist sogar jeder Motor ein Sensor f ur seinen eigenen Zustand. 5.4. TRUSTY 83 Listing 23: code/art/examples/SubsumptionTrusty.java // behavior for avoiding left side obstacles avoidLeft = new Task() { public Event perform() { trusty.avoidLeft(); return null; } }; scheduler.addBehaviorFor(new Double(1), robot.getLeftSensor(), avoidLeft); Als Prioritat erwartet SchedulerTask ein Comparable. Als aktiviert gilt eine Verhaltensweise dann, wenn der zugehorige BooleanSensor den Zustand true hat. Ausgef uhrt wird eine Verhaltensweise u ber den zugehorigen Task. Das Hinzufugen fur den rechten Sensor geschieht analog. Man verwendet einem Task der entsprechend avoidRight() aufruft und eine andere Prioritat hat. Ob hoher, gleich oder niedriger ist in diesem Fall zwar egal, die Klasse SchedulerTask ist aber so implementiert, da sie pro Prioritat nur einen Eintrag erlaubt. de.jaetzold.util.Task Das Interface Task beschreibt im Grunde eine Art Runnable mit dem Unterschied, da die Methode zum Ausfuhren des Task perform() heit und ein de.jaetzold.util.Event-Objekt als Resultat haben kann. Dieser Unterschied ist hier zwar bisher nicht von Bedeutung, da SchedulerTask dieses Resultat nicht weiter verarbeitet, wird aber an anderer Stelle (bei den Implementierungen von RobotInterface, siehe Kapitel 6 auf Seite 107) gebraucht. Man kann sich nun fragen: Wieso dann uberhaupt Task und nicht Runnable? Der Vorteil liegt in dem Umstand begrundet, da { wie der Name schon andeutet { SchedulerTask selbst ein Task ist. Damit wird es moglich, mehrere SchedulerTask-Instanzen zu verschachteln. Die Methode SchedulerTask. perform() wahlt von allen eingetragenen Verhaltensweisen, deren BooleanSensor den Zustand true hat, diejenige mit der hochsten Prioritat und fuhrt deren Task u ber perform() aus. Dies konnte dann naturlich auch ein weiterer SchedulerTask sein. Die Methode perform() einer Implementierung von Task darf auerdem { im Gegensatz zu run() von Runnable { jede Exception verursachen, da dies bereits von Task.perform() so deklariert wird. Fur Task-Instanzen gibt es auerdem die Moglichkeit diese uber einen de. jetzold.util.TaskPerformer und eine zugehorige de.jaetzold.util.Queue auszufuhren: Exkurs: Listing 24: code/art/examples/SubsumptionTrusty.java Queue taskQueue = new Queue(); schedulerPerformer = new TaskPerformer(taskQueue); // schedulerPerformer.start(); // start the performer from the outside ➥ 84 5. ARIADNE & ART - EIN TUTORIAL Listing 24: code/art/examples/SubsumptionTrusty.java (Fortsetzung) schedulerPerformer.haltPerforming(true); taskQueue.post(new RequeueTask(scheduler, taskQueue)); Ein TaskPerformer wird mit einer Queue initialisiert. Sobald die Queue ein Element enthalt, versucht der TaskPerformer dieses als Task zu interpretieren und fuhrt es gegebenenfalls aus. Zu einer Queue werden Elemente mittels post(Object) hinzugefugt. Ein TaskPerformer kann mittels haltPerforming(boolean) angehalten werden. Ist das Argument true, so wird auerdem dem ausfuhrenden Thread des TaskPerformer die Nachricht interrupt() geschickt. Auerdem geht die Methode in diesem Fall erst dann zu Ende, wenn der TaskPerformer auch wirklich keinen Task mehr ausf uhrt. Um einen Task wiederholt zur Ausfuhrung zu bringen, kann man wie in dem Beispiel einen de.jaetzold.util.RequeueTask benutzen, welcher den ubergebenen Task ausfuhrt und sich selbst danach immer wieder zu der Queue hinzufugt. Fur den hier genannten Zweck hatte man eventuell auch java.util.Timer und java.util.TimerTask verwenden konnen, doch die Klassen Task, TaskPerformer und Queue bieten daruber hinaus noch ein paar weitere Methoden, welche sie von der Klasse Timer und einem TimerTask unterscheiden. Auerdem sind Timer und TimerTask erst seit Version 1.3 im API der Java Platform enthalten. Die hier verwendeten Klassen sind aber mindestens mit Java 1.1 kompatibel und eignen sich daher auch zur Verwendung auf einer Maschine wie dem TINI-Board (siehe 7.6 auf Seite 130), die im wesentlichen nur diese Java-Version bietet. Weiter mit SubsumptionTrusty: Auer den Verhaltensweisen fur das Aus- weichen mu auch noch eine Verhaltensweise fur das normale Geradeausfahren hinzugefugt werden: Listing 25: code/art/examples/SubsumptionTrusty.java // standard always-active behavior ’move’ BooleanSensor constantTrue = new BooleanSensor(); constantTrue.connectWith(new ConstantSensorPort(1)); move = new Task() { public Event perform() { trusty.move(); return null; } }; scheduler.addBehaviorFor(new Double(0), constantTrue, move); Diese in Listing 25 gezeigte Verhaltensweise hat die niedrigste Prioritat (innerhalb von SubsumptionTrusty) und kommt daher immer dann zum Zuge, wenn Trusty nicht ausweichen "will". Eigentlich mute sie daher gar nicht extra aktiviert werden, weil sie immer aktiviert ist. Am einfachsten tragt man diesem Umstand Rechnung, indem ihr ein BooleanSensor zugeordent wird, 5.4. 85 TRUSTY der immer true ist. Zur Verwendung von Sensoren und der Klasse SensorPort ohne ein direkt zugeordentes RobotInterface, kann man auch in Abschnitt 5.5 und in Kapitel 4 mehr erfahren. STBaseRobot implements defines & ST.Robot getTrusty() getLeftSensor() SubsumptionTrusty uses to implement behavior extends & initializes creates/delivers STFTDemoRobot implements initializes with Trusty appropriate Trusty move() avoidLeft() uses to decide what behavior to exhibit move() avoidLeft() BooleanSensor getValue() addSListener() STMSDemoRobot 2x creates/delivers appropriate ST = SubsumptionTrusty Abbildung 5.6: Design der S = Sensor Beispielimplementierung von Trusty, welche auch selbst andig uber ein eventuelles Ausweich-Verhalten entscheiden kann. Eine vorhandene Implementierung von Trusty wird wiederverwendet. Die Zusammenhange der Roboter-Klassen bei SubsumptionTrusty wird in Abbildung 5.6 dargestellt. Bekommt SubsumptionTrusty die Nachricht move() wird der zugehorige TaskPerformer mit dem in einen RequeueTask verpackten SchedulerTask gestartet: Listing 26: code/art/examples/SubsumptionTrusty.java public void move() { // start algorithm if(!schedulerPerformer.isAlive()) { // it’ either has not been started yet try { schedulerPerformer.start(); } catch(IllegalThreadStateException itse) { // or already finished execution } } schedulerPerformer.resumePerforming(); } public void stop() { // stop algorithm schedulerPerformer.haltPerforming(); try { schedulerPerformer.waitForHalt(); } catch(InterruptedException ie) { } trusty.stop(); } public void avoidLeft() { ➥ 5. ARIADNE & ART - EIN TUTORIAL 86 Listing 26: code/art/examples/SubsumptionTrusty.java (Fortsetzung) // TBD: instead of stopping subsumption, the corresponding // BooleanSensor could deliver true, to initiate an ’avoid’ stop(); trusty.avoidLeft(); } Da der TaskPerformer schedulerPerformer nicht schon bei der Initialisierung gestartet wird, haben eventuelle Unterklassen von SubsumptionTrusty die Moglichkeit schedulerPerformer als daemon -Thread zu starten, bzw. ihn vielleicht sogar durch einen anderen TaskPerformer zu ersetzen. Die anderen vom Interface Trusty vorgeschriebenen Methoden halten die Ausfuhrung von schedulerPerformer an und leiten den Methodenaufruf ansonsten zu dem eigentlichen Trusty weiter. Wie immer kann naturlich auch dieser Roboter getestet werden. Ein Bild des Demo-Roboters aus Fischertechnik und aus Lego zeigt Abbildung 2.3 auf Seite 10 aus Kapitel 2. 5.5 Kongurierte Sensoren Die Verwendung von Sensoren wurde bisher nur sehr knapp behandelt. Dieser Abschnitt zeigt zuerst, worauf bei der Implementierung des SubsumptionTrusty.Robot fur den Beispielroboter auf Lego-Basis geachtet werden mute, damit an dem gleichen Anschlu auch noch "Platz" fur die Lichtsensoren von LiSe bleibt. Darauf folgt eine Beschreibung, wie man durch geschickte Konguration eines CountSensor den Rotationssensor aus dem Lego-Mindstorms Programm selbst zum zahlen benutzen kann, ohne einen Brick dafur zu benotigen. Zuletzt wird noch eine Hilfsklasse vorgestellt, mit der es auf einfache Weise moglich ist, die Zahlrichtung bei der Verwendung eines Fischertechnik-Impulsrads mit der Drehrichtung eines Fischertechnik-Motors zu koppeln. 5.5.1 SubsumptionTrustyMSDemoRobot Wie bereits eingangs erwahnt, sollen bei dem Beispielroboter aus Lego die Lichtsensoren fur LiSe an die gleichen Eingange des Brick angeschlossen werden, wie die Beruhrungssensoren fur Trusty. Bei einem Aufruf von connectWith(Port) bei einem Sensor wird dieser mit "dem Besten" Peer verbunden, den der Port fur diese Sensor-Klasse zu bieten hat. Was "der Beste" jeweils genau zu bedeuten hat, sollte in der Dokumentation der jeweiligen Implementierung von RobotInterface, von der man das Port-Objekt erhalten hat, speziziert sein. Fur bisherigen Implementierungen steht das auch in Kapitel 4. Fur die in ART enthaltene Implementierung fur den Brick bedeutet das im Falle eines BooleanSensor, da der entsprechende Anschlu des Brick auf den Standard-Modus fur die normalen Lego-Beruhrungssensoren konguriert wird (zur Konguration von Sensoren im Brick siehe: The LEGO Group (1998) sowie 5.5. KONFIGURIERTE SENSOREN 87 alle Bucher uber Lego-Mindstorms in der Literaturliste usw.). Das bedeutet, der Sensor wird nicht mit Strom versorgt und der eigentliche Sensorwert wird in einen booleschen Wert verwandelt, welcher durch die Zahlenwerte 1 und 0 reprasentiert wird. Fur diesen BooleanSensor wird aber nicht der normale Sensor-Wert ausgelesen, sondern der Wert des Registers mit der Standard-Umwandlung in einen booleschen Wert. Dieses Register ist fur jede Sensorkonguration gultig (Baum, 2001). Auf diese Weise liefert der Sensor auch dann noch einen (mehr oder weniger) sinnvollen Wert, falls der Anschlu spater noch umkonguriert werden sollte. Diese Standard-Umwandlung ist aber im Falle einer Konguration des Anschlusses fur einen Lichtsensor nicht geeignet, um den Zustand des Beruhrungssensors zu erfahren. Vielmehr mu der unveranderte Sensorwert ausgelesen und dann entsprechend interpretiert werden: Listing 27: code/art/examples/SubsumptionTrustyMSDemoRobot.java public SubsumptionTrustyMSDemoRobot(Trusty trusty, RobotInterface iface) { super(trusty, iface); // switch left and right BooleanSensor tmp = leftSensor; leftSensor = rightSensor; rightSensor = tmp; // in anticipation of LiSe the BooleanSensor’s have to be // configured to handle the Raw value since they are connected // to the same ports as the LightSensor’s of LiSe Port leftPort = leftSensor.getPort(); Port rightPort = rightSensor.getPort(); leftSensor.disconnect(); rightSensor.disconnect(); // put a RawSensor in-between RawSensor leftRaw = new RawSensor(); RawSensor rightRaw = new RawSensor(); leftRaw.connectWith(leftPort); rightRaw.connectWith(rightPort); leftSensor.connectWith(leftRaw.getSensorPort()); rightSensor.connectWith(rightRaw.getSensorPort()); // first Range is for 0, second for 1 and third is hysteresis // these are non-standard RCX-values because Light-Sensors // can deliver values as low as 320 leftSensor.setStateDecider( new RCXRawSwitchStateDecider( new SingleRange(220), new SingleRange(0, 190), new SingleRange(190, 220) ➥ 88 5. ARIADNE & ART - EIN TUTORIAL Listing 27: code/art/examples/SubsumptionTrustyMSDemoRobot.java (Fortsetzung) )); rightSensor.setStateDecider( new RCXRawSwitchStateDecider( new SingleRange(220), new SingleRange(0, 190), new SingleRange(190, 220) )); // a different contructor could be nice here, which only needs // one Range which is the hysteresis } Als erstes wird der linke mit dem rechten Sensor vertauscht, da das fur den Demo-Roboter erforderlich ist. Danach wird jeweils ein RawSensor mit dem Port verbunden, an dem die Sensoren bisher angeschlossen waren. Der Anschlu eines RawSensor fuhrt dazu, da der entsprechende Anschlu am Brick auf den Raw -Modus konguriert wird. Als Wert bekommt ein solcher Sensor den Inhalt des Registers fur den unveranderten Sensorwert geliefert, welches genauso wie die boolesche Umwandlung auch fur jede Konguration des Port gultig ist. An diesen RawSensor, welcher nun (egal wie der Anschlu spater noch konguriert wird) immer den an dem Anschlu gemessenen Wert unverarbeitet bekommt, wird nun ihrerseits die entsprechende BooleanSensor-Instanz angeschlossen. Jeder Sensor liefert mit getPort() den Port, an dem er selber angeschlossen ist und mit getSensorPort() einen SensorPort, an den man weitere Sensoren anschlieen kann, die dann den von ihm bereits verarbeiteten Wert bekommen. Es wurde also sozusagen ein RawSensor "zwischendrin" eingefugt. Damit wird erreicht, da der BooleanSensor den Wert des Raw-Registers aus dem Brick bekommt und selbst zu einem booleschen Wert umwandeln kann. Die Umwandlung geschieht hier durch eine Wandlung des Sensorwertes in den Zustand 0 oder 1, welcher dann von dem BooleanSensor normal interpretiert wird (0 ist false, 1 ist true). Sie wird von der Hilfsklasse RCXRawSwitchStateDecider vorgenommen, welche drei Range-Instanzen bekommt. Eine fur den Wert 0, eine fur die Hysterese (siehe z.B. Baum (2000)) und eine fur den Wert 1. Instanzen der Klasse SingleRange reprasentieren Intervalle von (reellen) Zahlen. Wird eine SingleRange wie in dem Beispiel nur mit einer Zahl initialisiert, geht das Intervall von dieser Zahl bis Double.POSITIVE_INFINITY. Wird sie mit zwei Zahlen initialisiert bezeichnet der erste Parameter die untere Schranke und der zweite Parameter die obere Schranke eines geschlossenen Intervalls. Oene Intervalle sind auch moglich, wer mehr wissen will, schaue sich die JavaDoc-Dokumentation der entsprechenden Range-Klassen aus den package de.jaetzold.util an. 5.5. KONFIGURIERTE SENSOREN 5.5.2 89 Der Lego-Rotationssensor Wenn man einen Lego-Rotationssensor ganz normal mit dem Brick verwenden mochte, ist das einfach. Man braucht nur einen CountSensor mit einem SensorPort des entsprechenden RobotInterface zu verbinden und der Eingang wird automatisch richtig konguriert. Der CountSensor liefert dann den vom Brick bereits berechneten Wert. Ein CountSensor kann aber auch fur beliebige Ports zum Zahlen benutzt werden, auch wenn diese gar nicht speziell fur einen CountSensor gedacht sind. Dafur mu der CountSensor entsprechend konguriert werden. Um dem CountSensor erst einmal den unverarbeiteten Sensorwert zu liefern, wird dieser mit dem SensorPort eines RawSensor verbunden (welcher naturlich seinerseits auch mit einem Port verbunden werden mu). Das geschieht genau so wie bei den BooleanSensor-Instanzen von SubsumptionTrustyMSDemoRobot in Abschnitt 5.5.1 auf Seite 86: RawSensor raw = new RawSensor(); CountSensor count = new CountSensor(); count.connectWith(raw.getSensorPort()); Von der Oberklasse StateSensor von CountSensor wird der vom RobotInterface erhaltene Wert ohne weitere Verarbeitung als Zustand (State) interpretiert. Der CountSensor wiederum zahlt in seiner Voreinstellung den Absolutwert der Dierenz zwischen zwei Zustands-Werten. Ein StateSensor beginnt im Zustand 0, das heit, kommt vom RobotInterface z.B. als erstes der Wert 3 und dann der Wert 1, hat der CountSensor bereits bis 5 gezahlt. Kommt dann noch der Wert -5, ist der CountSensor bereits bei 11 mit dem Zahlen angelangt. Sowohl StateSensor als auch CountSensor haben einen Delegate der Klasse StateDecider (stateDecider und countDecider genannt). Der Delegate vom StateSensor bekommt den aktuellen (letzen) state -Wert des Sensors sowie den neuen Wert vom RobotInterface als Parameter und berechnet einen neuen state-Wert. Der Delegate vom CountSensor bekommt auch den aktuellen (letzen) state -Wert des Sensors, sowie den neuen bereits in einen state-Wert umgewandelten Wert vom StateSensor als Parameter und berechnet die Dierenz zum aktuellen count-Wert. Diese Dierenz wird immer zu dem letzten Wert hinzuaddiert, das heit, soll der count-Wert kleiner werden mu die Dierenz negativ sein.10 Um nun den raw-Wert des Rotationssensors zu verarbeiten, soll dieser erst einmal in einen von vier Zustanden umgewandelt werden (der Rotationssensor erzeugt immer einen Wert in einem von vier engen Wertebereichen): Listing 28: code/art/examples/RCXRotationSensorStateDecider.java public class RCXRotationSensorStateDecider implements StateDecider { protected StateDecider target; ➥ 10 Ist der Sensor auf reversed eingestellt, wird die Dierenz abgezogen anstatt hinzugezahlt 90 5. ARIADNE & ART - EIN TUTORIAL Listing 28: code/art/examples/RCXRotationSensorStateDecider.java (Fortsetzung) public RCXRotationSensorStateDecider() { target = new RangeStateDecider( new Range[] {null, new SingleRange(352,366), new SingleRange(520,535), new SingleRange(1020,1024), new SingleRange(770,785)}); } public double nextStateValue(double actualState, double value) { double newState = target.nextStateValue(actualState, value); if(newState == 0) { // The standard rcx-interpretation of such // ’false’ values seems to simply ignore them return actualState; } else { return newState; } } ... } Der bei der Initialisierung erzeugte RangeStateDecider liefert als Resultat von nextStateValue(double,double) immer den Index der ersten Range in dem ubergebenen Array, die nicht null ist und die den zweiten Parameter zu nextStateValue(double,double) (welcher der neu erhaltene Wert ist) enthalt. Enthalt keine der Range-Instanzen den Wert, so liefert ein RangeStateDecider das Resultat 0. Der RCXRotationSensorStateDecider wird dem CountSensor als StateDecider-Delegate u bergeben: count.setStateDecider(new RCXRotationSensorStateDecider()); Die vier Zustande des RCXRotationSensorStateDecider werden nun von einem weiteren StateDecider in die Dierenz zum alten count-Wert umgewandelt: Listing 29: code/art/examples/RCXRotationCountStateDecider.java public class RCXRotationCountStateDecider extends TransitionMatrixStateDecider { private static int[][] countMatrix = 0, 1, 2, 3, 4 */ new int[][]{/* l\n /* 0*/ { 0, 0, 0, 0, 0}, /* 1*/ { 0, 0, 1, 0,-1}, /* 2*/ { 0,-1, 0, 1, 0}, /* 3*/ { 0, 0,-1, 0, 1}, /* 4*/ { 0, 1, 0,-1, 0}}; ➥ 5.5. KONFIGURIERTE SENSOREN 91 Listing 29: code/art/examples/RCXRotationCountStateDecider.java (Fortsetzung) public RCXRotationCountStateDecider() { super(countMatrix); } } Die Bereiche fur die vier Zustande wurden so gewahlt, da ein Ubergang von 1 nach 2, von 2 nach 3 usw., sowie von 4 nach 1 bedeutet, da der countWert um 1 hochgezahlt werden muss. Zustandsubergange von 4 nach 3, 3 nach 2 usw., sowie von 1 nach 4 mussen eine Verringerung des count-Wertes um 1 nach sich ziehen, damit das gleiche Resultat wie im Brick erreicht wird. Zustande von 0, sowie groere Sprunge werden ignoriert, was dem beobachtbaren Verhalten des Brick in diesem Fall entspricht. Zustande von 0 konnen zum Beispiel auftreten, wenn ein Beruhrungssensor an den gleichen Eingang wie der Rotationssensor angeschlossen ist, weil dieser auch Werte auerhalb der 4 Bereiche des Rotationssensors liefern kann. Ob groere Sprunge vom Brick wirklich ignoriert werden, kann nicht sicher bestatigt werden, es ist aber so, da zumindest ab einer Geschwindigkeit von etwa 1000 U/min der vom Brick berechnete count-Wert nicht mehr korrekt ist. Bei der hier vorgestellten Variante naturlich bereits viel fruher (schatzungsweise so etwa im Bereich von 4-40 U/min, je nach sonstiger Rechnerauslastung). Mehr zu Sensoren fur den Brick, vor allem auf Hardware-Ebene, ndet man unter Gasperi (1998) und in Baum et al. (2000). 5.5.3 Rotationsmessung mit den Fischertechnik-Impulsr adern Um mit Fischertechnik die Rotation einer Achse zu messen, benutzt man uberlicherweise die dafur vorgesehenen Impulsrader. Abbildung 5.7 zeigt das Impulsrad von LiSe. Abbildung 5.7: Der schwarze Taster ist zusammen mit dem Impulsrad (das ist das kleine schwarze Zahnrad in der Mitte mit nur vier Zahnen) so montiert, da eine Drehung an dem Rad den Taster fortlaufend betatigt. 5. ARIADNE & ART - EIN TUTORIAL 92 Leider ist man damit nicht in der Lage, automatisch die Richtung, in die sich die Achse dreht, zu bestimmen. Zahlt man nur die am Schalter auftretenden Impulse, so wird der daraus resultierende Wert immer groer, egal in welche Richtung sich die Achse dreht. Haug ist solch eine Achse aber an einen Motor angeschlossen und in diesem Fall wei man eigentlich, in welche Richtung sich die Achse dreht. Damit nun die Zahlrichtung nicht immer per Hand eingestellt werden mu, kann man einen StateDecider wie im folgenden Beispiel als countDecider fur den CountSensor verwenden: Listing 30: code/art/examples/FTRotationCountStateDecider.java package de.jaetzold.art.examples; ... public class FTRotationCountStateDecider extends StateDeciderLinkedStateDecider { ... public FTRotationCountStateDecider(Sensor directionSensor) { try { setFirstDecider(new SensorStateDecider( directionSensor, true)); setSecondDecider( new BinaryOperatorStateDecider( new BinaryOperator() { public double combine( double first, double second) { return Math.abs(first-second); } }, true ) ); setLinkDecider( new BinaryOperatorStateDecider( new BinaryOperator() { private int seq; private int last; public double combine( double first, double second) { if(first>0) { last = 1; } else if(first<0) { last = -1; } else { ... } return last*second; ➥ 5.5. KONFIGURIERTE SENSOREN 93 Listing 30: code/art/examples/FTRotationCountStateDecider.java (Fortsetzung) } }, true ) ); } catch(PropertyVetoException pve) { } } } Die Klasse FTRotationCountStateDecider stammt von der Klasse StateDeciderLinkedStateDecider ab, welche ihrerseits drei StateDecider-Instanzen besitzt, an die die Entscheidung uber den neuen Zustand delegiert wird. Die ersten beiden Instanzen (firstDecider und secondDecider) bekommen die Parameter zu nextStateValue(double,double) jeweils direkt ubergeben. Die dritte Instanz (linkDecider), bekommt das Resultat von firstDecider als ersten und das Resultat von secondDecider als zweiten Parameter ubergeben. Bei der Initialisierung bekommt ein FTRotationCountStateDecider einen Sensor mit (das kann auch ein Motor sein, denn die sind Sensoren f ur ihren Zustand), erzeugt damit einen SensorStateDecider und initialisiert mit diesem wiederum den firstDecider der Oberklasse. Ein SensorStateDecider ist ein StateDecider der als Resultat immer den Wert des Sensors liefert. Als secondDecider wird ein StateDecider erzeugt, wie ihn ein CountSensor auch als Voreinstellung benutzt. Die absolute Dierenz zwischen aktuellem Zustand (Parameter first zu combine(double,double) des BinaryOperator) und dem neuen Wert (Parameter second, der dann sinnvollerweise auch ein Zustand sein sollte) liefert dieser als Resultat. Verbunden werden die Resultate der beiden ersten StateDecider uber den dritten, welcher { je nachdem ob der Sensorwert gerade positiv oder negativ ist { die Dierenz unverandert als Resultat ubergibt oder vorher mit -1 multipliziert. Ein Sonderfall tritt auf, wenn der Sensor den Wert 0 liefert. Damit der CountSensor in diesem Fall trotzdem zahlt, wird einfach angenommen, da in die gleiche "Richtung" wie beim letzten Aufruf gezahlt werden soll. Damit wird dem Umstand Rechnung getragen, da die Events von dem Sensor fur das Impulsrad zu einem Zeitpunkt ankommen konnen, zu dem der Motor bereits wieder aus ist. Diesen StateDecider kann man nun auf einfache Weise verwenden, um ein Impulsrad mit einem Motor zu koppeln: CountSensor count = new CountSensor(); Sensor motor = new Motor(); count.setCountDecider(new FTRotationCountStateDecider(motor)); Naturlich mussen motor und count auch noch mit einem Port verbunden werden. Das durfte mittlerweile aber klar sein. 5. ARIADNE & ART - EIN TUTORIAL 94 5.6 LiSe Die Programmierung von LiSe (und im folgenden Abschnitt auch Ariadne) wird langst nicht so ausfuhrlich behandelt wie das bei den bisherigen Beispielen der Fall war. Wer daran weitergehendes Interesse hat, sollte inzwischen auch in der Lage sein, den Code der Beispielimplementierungen selbst zu verstehen. Das Konzept ist im Grunde das gleiche wie auch schon bei DriveTrain und Trusty. Trotzdem gibt es einige Besonderheiten und Elemente des ART, die vorher noch nicht zu Tage getreten sind, so da auf diese hier noch eingegangen wird. LiSe ist besonders interessant als Beispiel fur die Verwendung von Sensoren. Ein BooleanSensor wird fur den Endabschalter verwendet, ein AngleSensor fur die Position des Turms, sowie zwei Objekte der Klasse LightSensor mit denen die Taschenlampe "gefunden" werden kann. Zur Bewegung des Turmes mit den Lichtsensoren benutzt LiSe einen Servo. Nun bieten sowohl der Brick, als auch das Intelligent Interface von Fischertechnik keine Anschlumoglichkeit fur echte Servos. Da der Turm sich aber nicht beliebig weit drehen lat und LiSe ihn nicht nur bewegen, sondern auch uber die (absolute) Position Bescheid wissen mochte, ware ein Servo eigentlich besonders gut fur die Turmbewegung geeignet. Um diese haug verwendete Konstruktionsweise (Motor mit Rotationssensor und Endabschalter) besonders einfach steuerbar zu machen, kann die Klasse Servo die Funktion eines echten Servo simulieren. Wird eine Servo-Instanz mit einem Port verbunden, der keine echten Servos unterstutzt, so mu man sie nur mit einem AngleSensor zur Positionsmessung einem BooleanSensor zur Kalibrierung der Position einer "Richtung", in die sich der Servo (Servo ist eine Unterklasse von Motor) drehen soll, um die Position des BooleanSensor anzufahren einer unteren und einer oberen Schranke f ur den Bewegungsbereich des Servo versorgen und kann sie danach ganz normal wie einen Servo verwenden, das heit, zumindest einigermaen so wie einen Servo. Im allgemeinen wird ein Servo wesentlich praziser und schneller als diese Simulation mit einem Motor usw. sein. Andere Implementierungen von RobotInterface konnten aber durchaus auch echte Servos unterstutzen. Was dabei unter anderem zu beachten ist beschreibt Abschnitt 6. Die Beispielimplementierung LiSeSimpleAlgorithm verlangt eine Implementierung des folgenden Interface zur Initialisierung: Listing 31: code/art/examples/LiSeSimpleAlgorithm.java public interface Robot { ➥ 5.6. 95 LISE Listing 31: code/art/examples/LiSeSimpleAlgorithm.java (Fortsetzung) public public public public public double getMaxRightAngle(); double getMaxLeftAngle(); double getSearchSpeed(); double getFollowSpeed(); Servo getTurnServo(); } Die Werte maxRightAngle und maxLeftAngle bezeichnen die Grenzen des Bereiches, in dem gesucht werden soll. Auerdem wird daruber festgelegt, ob negative Winkel links oder rechts sind. Mit searchSpeed und followSpeed wird die Geschwindigkeit der Towerbewegung zum Suchen des Lichtes und um einem bereits gefundenen Licht zu folgen angegeben. Ich bin mir nicht sicher, ob echte Servos normalerweise eine Regelung der Geschwindigkeit mit der sie sich bewegen zulassen. Die u ber die Motoren simulierten tun das auf jeden Fall. Es macht aber nichts, hier eine Geschwindigkeit zu verwenden, denn nach der Denition von ART ist jeder Servo auch ein Motor (Die Klasse Servo ist eine Unterklasse von Motor) und bietet daher die Moglichkeit, eine Geschwindigkeit anzugeben. Genau genommen ist es sogar so, da die Geschwindigkeit erst bei einem StepperMotor angegeben werden kann und bei einem Motor nur die Kraft (power), das hat aber bei den Implementierungen fur Lego und Fischertechnik die gleichen Auswirkungen, da sie StepperMotor nicht direkt unterstutzen.11 Damit konnen die vom Interface LiSe (siehe Sourcecode) vorgeschriebenen Methoden folgendermaen implementiert werden: Listing 32: code/art/examples/LiSeSimpleAlgorithm.java private boolean searchRight; public synchronized void seeLeft() { servo.setSpeed(robot.getFollowSpeed()); servo.setValue(robot.getMaxLeftAngle()); searchRight = false; setState(SEE LEFT); } public synchronized void seeRight() { servo.setSpeed(robot.getFollowSpeed()); servo.setValue(robot.getMaxRightAngle()); searchRight = true; setState(SEE RIGHT); } public synchronized void search() { // important for direction-change when reaching MaxAngle setState(SEARCH); if(servo.isPositionedAt(robot.getMaxRightAngle())) { ➥ 11 Fur weitere Informationen, insbesondere zu StepperMotor, dient die JavaDoc APIDokumentation von ART. 96 5. ARIADNE & ART - EIN TUTORIAL Listing 32: code/art/examples/LiSeSimpleAlgorithm.java (Fortsetzung) searchRight = false; } else if(servo.isPositionedAt(robot.getMaxLeftAngle())) { searchRight = true; } servo.setSpeed(robot.getSearchSpeed()); if(searchRight) { servo.setValue(robot.getMaxRightAngle()); } else { servo.setValue(robot.getMaxLeftAngle()); } } public synchronized void seeCenter() { servo.keepCurrentPosition(); setState(SEE CENTER); } public synchronized void stop() { servo.keepCurrentPosition(); setState(STOP); } public synchronized boolean isSeeing() { switch(getState()) { case STOP: case SEARCH: return false; default: return true; } } public AngleSensor getAngleSensor() { return servo.getAngleSensor(); } Die Methoden seeLeft() und seeRight() implementieren das Verhalten, einem Licht das bereits erkannt wurde nach links, bzw. rechts zu folgen. In der Variable searchRight wird die letzte Richtung, in der das Licht gesehen wurde festgehalten, damit in dieser Richtung von der Methode search() auch zuerst gesucht wird. Die Methoden seeCenter() und stop() halten die (eventuelle) Bewegung des Servos an und stellen ihn auf die aktuelle Position ein. Sie unterscheiden sich nur in dem Zustand von LiSe. Der Zustand, ob LiSe im Moment uberhaupt ein Licht "sieht", kann man uber isSeeing() erfragen. Der Ruckgabewert gibt damit an, ob der Wert des AngleSensor von LiSe als die Position eines Lichtes interpretiert werden kann. Die Methode search() bewegt den Tower in der aktuellen Suchrichtung bis an den Rand des Suchbereiches. Damit LiSe dort umdreht und den Tower an den anderen Rand bewegt, ist (indirekt) bei dem Servo ein SensorListener registriert, welcher search() bei Erreichen des Randes im SEARCH-Zustand einfach erneut aufruft: 5.6. 97 LISE Listing 33: code/art/examples/LiSeSimpleAlgorithm.java final BooleanSensor borderSensor = servo.getPositionReachedSensor(); borderSensor.addSensorListener( new SensorListener() { public void processEvent(SensorEvent se) { synchronized(LiSeSimpleAlgorithm.this) { boolean reached = borderSensor.convertToBoolean(se.getValue()); if(debug.debug) { debug.printInfo("reached=" +reached +", value=" +se.getValue() +", state=" +state); } if(reached && (state == SEARCH)) { search(); } } } } ); Dies zeigt ein besonderes Konzept: Sensoren in ART stehen immer fur einen Wert der eindimensional, also z.B. in einer Variable vom Typ double, abgebildet werden kann. Ist das nicht moglich, wird von dem eigentlichen Sensor nur eine einzige Dimension abgebildet und fur alle weiteren kann man spezielle Sensoren von dem eigentlichen Sensor bekommen. Dies ist bei Servo z.B. fur den Zustand, ob er sich an der zugewiesenen Position bendet der Fall. Da der Sensorwert des Servo selbst den Bewegungszustand des Servo reprasentiert, um mit Motor kompatibel zu bleiben, gibt es fur die Position einen weiteren Sensor, den man uber getAngleSensor() erhalten kann. Die Sensoren, sowie der Servo werden (wie ublich) in einer Basisimplementierung des Interface (LiSeSimpleAlgorithm.Robot) hinterlegt. Entsprechende Unterklassen fur den Beispielroboter aus Lego, sowie fur den aus Fischertechnik erzeugen, verbinden und kongurieren diese dann auf geeignete Weise. In der Fischertechnik-Variante von LiSe sieht das folgendermaen aus: Listing 34: code/art/examples/LiSeSimpleFTDemoRobot.java public class LiSeSimpleFTDemoRobot extends LiSeSimpleBaseRobot { public LiSeSimpleFTDemoRobot(RobotInterface ft) { // fake parameters and set them afterwards super(null, 0, 0, 0, 0); Port endSwitchPort = ft.getPort("E7"); Port countSensorPort = ft.getPort("E8"); Port motorPort = ft.getPort("M4"); // gear ratio Tower:Sensor = 1:6 ➥ 5. ARIADNE & ART - EIN TUTORIAL 98 Listing 34: code/art/examples/LiSeSimpleFTDemoRobot.java (Fortsetzung) double countsPerFullTurn = 48; AngleSensor turnSensor = new AngleSensor(countsPerFullTurn); int endSwitchDirection = 1; BooleanSensor endSwitch = new BooleanSensor(); // Angles are measured in full turns double positiveBorder = 21/48.0; double negativeBorder = -20/48.0; double endSwitchAngle = positiveBorder; maxRightAngle = 0.33; maxLeftAngle = -0.25; searchSpeed = 1; followSpeed = 0.33; turnServo = new Servo(turnSensor, endSwitch, endSwitchAngle, positiveBorder, negativeBorder, endSwitchDirection); turnSensor.setCountDecider( new FTRotationCountStateDecider(turnServo)); endSwitch.connectWith(endSwitchPort); turnSensor.connectWith(countSensorPort); turnServo.connectWith(motorPort); // let the servo be a bit more sloppy in positioning // (that (mostly) prevents it from occasionally moving // back and forth around the desired position) turnServo.setPrecision(turnServo.getPrecision()*2); } ... } Die nicht deklarierten Variablen maxRightAngle, maxLeftAngle, searchSpeed, followSpeed und turnServo sind die, die von einem LiSeSimpleAlgorithm.Robot geliefert werden sollen und stammen aus der Basisklasse LiSeSimpleBaseRobot, wo auch die zugehorigen get-Methoden implementiert sind. Die Port-Objekte fur den Anschlu des Endabschalters, des Impulsrades und des Motors werden hier auf eine sehr intuitive Art von dem Interface erfragt. Da an dieser Stelle eigentlich sowieso nur ein RobotInterface fur Fischertechnik benutzt werden soll, kann man die Ports auch anhand ihrer Bezeichnung identizieren. Welche Bezeichnungen gultig sind und mit welchem Anschlu an der Hardware sie korrespondieren, ist in der Dokumentation fur die jeweilige Implementierung von RobotInterface beschrieben. Im Allgemeinen entspricht sie einem 5.6. LISE 99 eventuellen Aufdruck auf der Hardware oder der Bezeichnung der Anschlusse, wie sie in der Dokumentation der Hardware verwendet werden. Auf diese Weise ist die Zuordnung eines von einem RobotInterface erhaltenen Port zu einem Anschlu an der Hardware am intuitivsten (und damit potentiell nicht fehlerbehaftet) handhabbar. Als nachstes wird der AngleSensor mit der Anzahl von Impulsen, die einer ganzen Umdrehung entsprechen initialisiert. Damit kann der count-Wert der Oberklasse CountSensor auch ohne spezielle Unterstutzung durch das RobotInterface in einen Winkel umgerechnet werden. Das Standardma fur Winkel in ART sind ganze Umdrehungen von 360Æ . Schlielich wird der Servo mit dem Endabschalter, dem Impulsrad und den sonstigen (vom Robotermodell abhangigen) notigen Werten initialisiert. Damit turnSensor auftretende Impulse je nach der aktuellen Bewegungsrichtung des Servo korrekt zum count-Wert dazuzahlt oder abzieht, wird er auerdem mit einer Instanz des im Abschnitt 5.5 auf Seite 86 besprochenen FTRotationCountStateDecider als Delegate zum Zahlen versorgt. Als Letztes wird der mangelnden Geschwindigkeit bei der Auslieferung von Events Rechnung getragen, indem die Prazision des Servo halbiert wird. Die Eigenschaft precision reprasentiert die Genauigkeit in Form des erwartbaren Fehlers des Sensorwertes und ist daher bei einer geringeren Prazision groer. Wenn man diese Einstellung nicht vornimmt, kommt es hauger vor, da der Motor erst zum Stillstand kommt, wenn er bereits uber die gewunschte Position hinaus gefahren ist. Das registriert der Servo naturlich und bewegt ihn wieder ein Stuck zuruck. Das kann wieder zu weit sein und sich theoretisch endlos hinziehen. Es ware gut, wenn Servo in der Nahe der gewunschten Position die Geschwindigkeit drosseln wurde, um diesen Eekt zu vermeiden, diese Funktionalitat ist in Servo aber (noch) nicht integriert. Eine entsprechende Implementierung von LiSeSimpleAlgorithm.Robot existiert auch fur die Lego-Variante von LiSe. Diese wird hier nicht gezeigt, da sie nichts wirklich Neues enthalt. Naturlich kann auch diese Beispielimplementierung wieder auf dem ublichen Weg getestet werden (was darunter zu verstehen ist wurde am Ende von Abschnitt 5.2 und in Abschnitt 5.3 auf Seite 73 naher beschrieben). SubsumptionLiSe Es fehlt noch die Integration der Lichtsensoren, damit LiSe auch automatisch einem Licht folgen kann. Das wird auf die gleiche Weise bewerkstelligt wie schon bei SubsumptionTrusty. Nur ist es diesmal mit etwas mehr Aufwand verbunden, die notigen BooleanSensor-Objekte fur die Initialisierung des SchedulerTask mit den unterschiedlichen Verhaltensweisen zu erzeugen. Diese mussen beide Lichtsensoren in Betracht ziehen. Stellvertretend wird hier nur eine Konstruktion gezeigt: Listing 35: code/art/examples/SubsumptionLiSe.java // behavior for seeing light on the left side ➥ 100 5. ARIADNE & ART - EIN TUTORIAL Listing 35: code/art/examples/SubsumptionLiSe.java (Fortsetzung) final LightSensor leftSensor = robot.getLeftSensor(); final LightSensor rightSensor = robot.getRightSensor(); BinaryOperator seeLeftOperator = new BinaryOperator() { public double combine( double leftValue, double rightValue) { // at least one of them has to ’see’ something if(leftValue >= leftSensor.getMeasurableMin() || rightValue >= rightSensor.getMeasurableMin()) { // Standard BooleanConversion: only 0 is false return leftValue>rightValue ? 1 : 0; } return 0; } }; CombinedSensor seeLeftSensor = new CombinedSensor(); seeLeftSensor.connectWith(leftSensor.getSensorPort()); seeLeftSensor.setOperator(seeLeftOperator); seeLeftSensor.setOperand(rightSensor); BooleanSensor seeLeftActivator = new BooleanSensor(); seeLeftActivator.connectWith(seeLeftSensor.getSensorPort()); seeLeftTask = new Task() { public Event perform() { lise.seeLeft(); return null; } }; scheduler.addBehaviorFor(new Double(1), seeLeftActivator, seeLeftTask); Ein CombinedSensor verknupft den Wert, den er von seinem eigenen Anschlu bekommt mit dem Wert, den er von einem weiteren Sensor (operand) bekommt, uber einen BinaryOperator (operator). Ein BinaryOperator ist hier als lokale anonyme innere Klasse realisiert. Die Methode combine(double,double) bekommt als ersten Parameter den Wert des linken LightSensor und als zweiten Parameter den des rechten LightSensor und liefert 1, falls einer der beiden Werte groer ist, als der Wert der Eigenschaft measurableMin des jeweiligen LightSensor und auerdem der Wert fur links groer ist als der Wert fur rechts. Die Werte measurableMin und measurableMax eines LightSensor bezeichnen die Grenzen des Bereichs in dem der LightSensor Werte liefert, die (im Rahmen seiner precision) genau sind. Im Prinzip sollte LightSensor innerhalb dieses Bereiches Werte liefern, die dem tatsachlich gemessenen Lux-Wert entsprechen. In combine() wird measurableMin dafur genutzt, um herauszunden, ob der Sensor u berhaupt ein starkeres Licht, wie z.B. von einer Taschenlampe, regi- 5.7. ARIADNE 101 striert hat. Dafur ist es notwendig, da measurableMin auch den momentanen Lichtverhaltnissen entspricht. Man kann diesen Wert, genauso wie measurableMax fur einen Lichtsensor, auch festlegen { LightSensor bietet daf ur eine Methode. Wird der Wert nicht festgelegt, liefert LightSensor das, was das RobotInterface, an dem er u ber einen Port angeschlossen wurde, liefert, sofern dieses einen LightSensor uberhaupt direkt unterstutzt. Ist beides nicht der Fall, ist der Wert von beiden Eigenschaften 0. An den CombinedSensor mu nun nur noch ein BooleanSensor angeschlossen werden. Dieser konvertiert in der Voreinstellung den Wert 0 zu false und alle anderen Werte zu true, was bei der Implementierung des BinaryOperator ein paar Zeilen daruber ausgenutzt wurde. Man kann einem BooleanSensor auch einen Delegate BooleanConversion fur die Konvertierung mitgeben, falls man das mochte. Die Erzeugung des Task und das Hinzufugen der Verhaltensweise zu dem SchedulerTask scheduler geschieht ansonsten auf die gleiche Weise wie schon bei SubsumptionTrusty und wurde dort bereits erklart. Neu bei SubsumptionLiSe ist nur die Art und Weise wie der BooleanSensor, der das Verhalten aktiviert, erzeugt bzw. angeschlossen wurde. 5.7 Ariadne Nun ist es soweit: Trusty und LiSe werden miteinander verknupft, damit sie zusammen Ariadne ergeben. Die Verknupfung soll im Ergebnis dazu fuhren, da Ariadne auf ein Licht zufahrt und dabei eventuellen Hindernissen ausweicht. Um dies zu erreichen, kann Ariadne sich an dem Winkel, in dem ein Licht ausgemacht wurde orientieren, indem der NormalizedDriveTrain (der auch von Trusty verwendet wird) um den entsprechenden Winkel gedreht wird, so da danach eine Bewegung nach vorne, in Richtung des Lichts geht. Wieder beginnt das Beispiel mit einem interface Ariadne, das die Schnittstelle zu der Fahigkeit von Ariadne, sich an einem Winkel zu orientieren, deniert: Listing 36: code/art/examples/Ariadne.java package de.jaetzold.art.examples; public interface Ariadne { public void orientByAngle(double angle); ... } Die Beispielimplementierung AriadneSimpleAlgorithm wird (wieder uber ein Robot-Interface) mit einem NormalizedDriveTrain, sowie einem Winkel initialisiert. Dieser Winkel stellt die Richtung dar, in die Ariadne gerne ausgerichtet ware. Die Methode orientByAngle(double) macht nun nichts weiter, als den ubergebenen Wert als die momentane Orientierung aufzufassen, und daher den NormalizedDriveTrain um die entsprechende Dierenz zu dem Winkel, mit dem AriadneSimpleAlgorithm initialisiert wurde, zu drehen: 102 5. ARIADNE & ART - EIN TUTORIAL Listing 37: code/art/examples/AriadneSimpleAlgorithm.java public void orientByAngle(double angle) { setState(ORIENTING); if((angle - orientationAngle)*leftDirection > 0) { driveTrain.leftSpin(Math.abs(angle - orientationAngle)); } else if((angle - orientationAngle)*leftDirection < 0) { driveTrain.rightSpin(Math.abs(angle - orientationAngle)); } setState(ORIENTED); } Der Wert leftDirection stammt auch von AriadneSimpleAlgorithm. Robot und sagt aus, ob negative Winkelwerte links oder rechts sind. Das mute inzwischen alles sehr einfach anmuten, da das Prinzip immer noch dasselbe ist und AriadneSimpleAlgorithm nichts enthalt, was nicht schon mal behandelt worden ist. Daher wird auch keine der Robot-Implementierungen (AriadneSimpleMSDemoRobot usw.) gezeigt. Diese sind aber vorhanden, so da man bei Interesse selbst nachschauen kann. Interessanter ist die Steuerung mit SubsumptionAriadne, welche als Unterklasse von SubsumptionTrusty realisiert wurde. Das zugehorige Robot-Interface ist auch eine Erweiterung von SubsumptionTrusty.Robot und kann daher einfach an den entsprechenden Konstruktor der Oberklasse ubergeben werden: Listing 38: code/art/examples/SubsumptionAriadne.java (Ref. in Listing 40 S. 103) public interface Robot extends SubsumptionTrusty.Robot { public Ariadne getAriadne(); public LiSe getLiSe(); public long getMinOrientInterval(); } Der Konstruktor von SubsumptionAriadne macht nicht mehr, als eine weitere Verhaltensweise in den SchedulerTask der Oberklasse einzufugen, diese ist aber etwas schwieriger zu realisieren, da die Methode orientByAngle(double) diesmal einen Parameter braucht: Listing 39: code/art/examples/SubsumptionAriadne.java (Ref. in Listing 40 S. 103) double angle; long lastOrientMillis; boolean oneShot; Object oneShotMonitor = new Object(); Task behavior = new Task() { public Event perform() { double localAngle; synchronized(oneShotMonitor) { // be sure to only orient once by each angle if(!oneShot) { return null; } lastOrientMillis = System.currentTimeMillis(); ➥ 5.7. 103 ARIADNE Listing 39: code/art/examples/SubsumptionAriadne.java (Fortsetzung) localAngle = angle; oneShot = false; } ariadne.orientByAngle(localAngle); // since it depends on some values // (lastOrientMillis, oneShot) it is better // not to assume that there will be an event // from liseAngle soon enough activator.update(); return null; } }; Dieser Parameter wird in der Variable angle gespeichert, wenn der zugehorige BooleanSensor die Verhaltensweise aktivieren mochte. Die Orientierung soll aber nur genau einmal stattnden und danach erst neu aktiviert werden. Daher ruft der Task behavior die Methode orientByAngle(double) nur dann auf, wenn oneShot true ist und setzt oneShot danach auf false. Damit behavior also u berhaupt etwas macht, mu der BooleanSensor auer selbst den Wert true anzunehmen auch noch oneShot auf true setzen: Listing 40: code/art/examples/SubsumptionAriadne.java public class SubsumptionAriadne extends implements SubsumptionTrusty Ariadne, Trusty { protected Ariadne ariadne; public SubsumptionAriadne(final Robot robot) { super(robot); this.ariadne = robot.getAriadne(); final LiSe lise = robot.getLiSe(); final AngleSensor liseAngle = lise.getAngleSensor(); // construct orient-behavior // behavior and activator need to exchange information SchedulerTask.Entry entry = new SchedulerTask.Entry() { <see Listing 39 on page 102> BooleanSensor activator = new BooleanSensor(); { activator.connectWith(liseAngle.getSensorPort()); activator.setBooleanConversionDelegate( new BooleanConversion() { public boolean convertToBoolean(double value) { synchronized(oneShotMonitor) { // only if lise sees something at all: if(!lise.isSeeing()) { return false; } // Future Idea: don’t orient if lise sees ➥ 5. ARIADNE & ART - EIN TUTORIAL 104 Listing 40: code/art/examples/SubsumptionAriadne.java (Fortsetzung) // light inthe direction we already are // or only orient if the seeing-angle // differs enough from the orient-to-angle (0) if((System.currentTimeMillis() - lastOrientMillis) >= robot.getMinOrientInterval()) { angle = value; oneShot = true; } return oneShot; } } public boolean isCacheable() { return false; } } ); } ... }; // insert orient-behavior into scheduler of superclass: scheduler.add(entry); } <see Listing 38 on page 102> ... } Der Roboter soll nicht standig neu orientiert werden, sondern SubsumptionTrusty soll zwischendurch auch ein wenig Zeit zum Fahren bekommen. Daher wird der BooleanSensor activator nur dann true, wenn der letzte Aufruf von orientByAngle(double) lange genug her ist. Welcher Zeitraum hier sinnvoll ist, hangt vom Roboter und der Implementierung von Trusty ab und ist daher auch eine Eigenschaft des Robot-Interface (minOrientInterval). Der Austausch zwischen activator und behavior u ber die Variablen oneShot, angle und lastOrientMillis erfordert, da diese f ur beide Objekte sichtbar sind. Daher sind Task, BooleanSensor und Comparable in einem SchedulerTask.Entry zusammengefat und werden als dieser gemeinsam an scheduler u bergeben. Die neu hinzugefugte Verhaltensweise bekommt die Prioritat 0.5. Das ist niedriger als die Prioritaten von SubsumptionTrusty fur das Ausweichen, aber hoher als fur das geradeaus Fahren. Die Implementierung von SubsumptionAriadne.Robot gestaltet sich sehr einfach, da nur die in den vorausgegangenen Abschnitten besprochenen Beispielalgorithmen mit den jeweiligen Robot-Implementierungen erzeugt werden mussen. Eine weitere Konguration ist (bis auf minOrientInterval) nicht mehr notwendig. Aus diesem Grund wird auf den Abdruck hier verzichtet. Naturlich kann auch SubsumptionAriadne auf die u bliche Weise durch einen Aufruf wie zum Beispiel 5.7. 105 ARIADNE $ java de.jaetzold.art.examples.SubsumptionAriadne Successfully initialized LEGO-Mindstorms Interface, found on serialport COM1 $ auf die u bliche Weise getestet werden. Die vorhandenen Beispiel-Implementierungen SubsumptionAriadneFTDemoRobot sowie SubsumptionAriadneMSDemoRobot erzeugen dabei ein (recht groes) Fenster, in dem die "State"- und "Commands"-Views fur NormalizedDriveTrain, Trusty, LiSe sowie Ariadne enthalten sind (siehe Abbildung 5.8. Damit SubsumptionAriadne loslegt, mu man wie bei SubsumptionTrusty auch, auf den Knopf move drucken, was dazu fuhrt, da bei SubsumptionAriadne (die ja ein Trusty ist) der Thread fur Subsumption gestartet wird. Abbildung 5.8: Testfenster f ur die Komponenten von SubsumptionAriadne 106 5. ARIADNE & ART - EIN TUTORIAL 6 Implementierung eines RobotInterface In der aktuellen Version von ART sind "Treiber" fur das Intelligent Interface von Fischertechnik und den Brick aus dem Robotics Invention System von Lego implementiert. Das parallele Interface von Fischertechnik, der Scout oder der Cybermaster sowie die Ansteuerung des Brick uber den neuen Tower mit USB-Anschlu, wie er im aktuellen RIS 2.0 enthalten ist, wird nicht direkt unterstutzt. Dieses Kapitel zeigt wie eine solche Unterstutzung implementiert wird. Die Moglichkeiten dazu sind naturlich nicht auf die eben genannten Beipiele beschrankt, sondern ART wurde mit dem Anspruch entwickelt, moglichst jede Form von Roboter-Hardware unterstutzen zu konnen. In ART besteht ein solcher Treiber im wesentlichen aus einer Implementierung des Java-Interface RobotInterface und einer geeigneten Menge von Port und SensorPeer-Klassen. Ein RobotInterface reprasentiert aus der Sicht von ART ein logisches Hardware-Interface. Die komplette Treiberimplementierung kann trotzdem aus mehreren Klassen bestehen und es ist auch moglich, da eine Implementierung mehrere echte Hardware-Interfaces nur logisch als eine Einheit reprasentiert. Genau genommen mu es sich nicht einmal um echte Hardware handeln. Zu Testzwecken z.B., kann es sogar sehr nutzlich sein, wenn die Roboter-"Hardware" nur virtuell im Computer existiert, denn so werden manche Tests einfacher, billiger und vor allem risikoloser. Es kann in solch einem Fall keine teure Roboter-Hardware zerstort werden, wenn der zu testende Steuerungsalgorithmus Fehler enthalt. 6.1 Ein virtuelles RobotInterface Die Implementierung eines virtuellen RobotInterface dient in diesem Kapitel als Beispiel. Es ist sehr einfach aufgebaut und nicht dazu gedacht, die LowLevel Ansteuerung von Roboterhardware z.B. u ber den seriellen Port in Java zu beschreiben. Das Beispiel zeigt lediglich, wie sich eine solche Ansteuerung in ART integrieren lat und worauf man dabei achten mu. Im allgemeinen wird man z.B. mit mehreren Threads hantieren mussen und 107 108 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE Abbildung 6.1: Screenshot der Oberache von AWTRobotInterface. Rechts sind die Textfelder fur die Ausgabe (Actuatoren) zu sehen, die Werte wurden durch den Steuerungsalgorithmus von Trusty gesetzt, der "Roboter" dreht sich also gerade. Byte-Strome codieren und decodieren. Die hier gezeigte Beispielimplementierung AWTRobotInterface nutzt zur Ein- und Ausgabe (als Gegenstuck zu Sensoren und Aktuatoren) einfache Textfelder in einem Fenster des AWT. Sensorwerte tragt man schlicht in das entsprechende Textfeld ein und u ber die Textfelder fur die Aktuatoren kann man deren Zustand beobachten. Es mu nicht einmal ein eigener Thread erzeugt werden, der die Events zustellt, denn ein solcher existiert im AWT schon. Ein ActionEvent der von einem der Textfelder kommt wird entsprechend von dem zugehorigen SensorPeer als SensorEvent an die angeschlossenen Sensor-Instanzen weitergeleitet. Bei einer Implementierung fur eine echte Roboterhardware ist es nicht unbedingt empfehlenswert, einen der Threads, die mit der Interface-Hardware direkt kommunizieren, auf diese Weise zu verwenden. Der Thread, der die Events zustellt, wird sozusagen "in die freie Wildbahn" hinausgelassen und wird beliebigen Code ausfuhren, uber den man, als Programmierer des entsprechenden RobotInterface, keine Kontrolle mehr hat. Die Implementierungen der Klassen de.jaetzold.art.platform.fischertechnik.Fischertechnik und de. jaetzold.art.platform.lego.Mindstorms nehmen eine solche Trennung der Threads vor, bei Interesse empfehle ich dem Leser, direkt in die entsprechenden Quelldateien hineinzusehen. Fur die Erklarung, wie ein neues RobotInterface implementiert und in ART integriert werden kann, ist dies nicht notwendig. Listing 41: code/art/examples/AWTRobotInterface.java package de.jaetzold.art.examples; // lots of imports ... public class AWTRobotInterface extends RobotInterfaceAdapter { private static Debug debug = new Debug("de.jaetzold.art.platform.awt.AWTRobotInterface"); protected TextField in0 = new TextField("0.0"); ➥ 6.1. EIN VIRTUELLES ROBOTINTERFACE 109 Listing 41: code/art/examples/AWTRobotInterface.java (Fortsetzung) protected TextField in1 = new TextField("0.0"); protected TextField out0 = new TextField("0.0"); protected TextField out1 = new TextField("0.0"); // RobotInterfaceDefinition: <see Listing 46 on page 117> // Factory-Method for creating an AWTRobotInterface: <see Listing 50 on page 121> // Factory-Methods for creating the Peers: <see Listing 48 on page 119> // isConnected: <see Listing 43 on page 112> // isAlive: <see Listing 49 on page 120> // Factory-Methods for the Ports: <see Listing 47 on page 118> // Inner classes for the Ports: <see Listing 45 on page 114> // Inner class for the SensorPeer: // maybe put outside of class because it is of more general use? <see Listing 42 on page 110> // Inner class for the ActuatorPeer: <see Listing 44 on page 113> } Listing 41 auf der vorherigen Seite zeigt den groben Korper der Beispielimplementierung AWTRobotInterface. Neben dem eigentlichen RobotInterface mussen aber auch noch Port- und SensorPeer-Klassen implementiert werden. Diese sind fur dieses Beispiel alle als innere Klassen von AWTRobotInterface realisiert worden. Die Verweise in Listing 41 fuhren unter anderem zu deren Implementierungen. AWTRobotInterface stammt von RobotInterfaceAdapter ab. Das ist zwar nicht notwendig, da nur das Java-Interface RobotInterface implementiert werden mute, aber diese Basisklasse enthalt Hilfsmethoden in der Behandlung von seriellen Ports. Serielle Ports werden fur das Beispiel nicht benotigt. InterfaceHardware mit seriellem Anschlu ist jedoch nicht unublich und in den Implementierungen fur Lego und Fischertechnik wird von den Hilfsmethoden Gebrauch gemacht. Ansonsten enthalt RobotInterfaceAdapter bereits implementierte Methoden, um Port-Objekte des RobotInterface u ber ein bestimmtes Identier-Object (siehe Dokumentation von Port.conformsTo(Object)) oder einen Index, im Stil einer indexed property von JavaBeans, auszuwahlen. Die Technik, sowohl ein Java-Interface als auch eine implementierende Ba- 110 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE sisklasse zu verwenden, wird unter anderem in Flanagan (1999, S. 115) vorgeschlagen. Sie wird zum Beispiel auch bei den { in der Java 2 Plattform neuen { Collection-Klassen angewandt. Ein Problem von Interfaces in Java ist, da man an ihnen kaum etwas andern kann, ohne implementierende Klassen anpassen zu mussen. Daher hat ein zwischengeschalteter Adapter einen weiteren Vorteil: Wenn das Java-Interface geandert wird, dann reicht es (unter Umstanden) den Adapter zu andern und alle davon abgeleiteten Klassen funktionieren sofort wieder, auch mit dem geanderten Java-Interface. 6.2 Die Peers Damit das Beispiel-RobotInterface auch ausreicht, um z.B. die Trusty-Algorithmen darauf laufen zu lassen, mu es mindestens zwei Anschlusse fur Sensoren und zwei fur Aktuatoren zur Verfugung stellen. In dem Beispiel werden nur die einfachsten Peers implementiert. Die ActuatorPorts liefern "reine" ActuatorPeer- und die SensorPorts "reine" SensorPeer-Instanzen. Es sind keine Spezialisierungen enthalten, wie z.B. ein BooleanSensorPeer, der auf der Seite der Oberache z.B. durch einen Button oder eine Checkbox reprasentiert werden konnte. Auf der Oberache (siehe Abbildung 6.1 auf Seite 108) kann man den Zustand der Actuatoren beobachten und auf den Wert der Sensoren Einu nehmen, indem man in das entsprechende Textfeld einen neuen Wert eintragt und mit Enter bestatigt. Das lost bei einem TextField im AWT einen ActionEvent aus und dieser wird von einer zugehorigen Instanz der inneren Klasse TextFieldSensorPeer verarbeitet: Listing 42: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) protected class TextFieldSensorPeer extends BaseSensorPeer { protected TextField input; public TextFieldSensorPeer(TextField input) { this.input = input; input.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { try { double oldValue = currentValue; currentValue = Double.parseDouble( ae.getActionCommand()); processEvent( new SensorEvent(this,// source null, // descriptor System.currentTimeMillis(), oldValue, currentValue, true, // isFloatingPoint null) // propagationId ➥ 6.2. 111 DIE PEERS Listing 42: code/art/examples/AWTRobotInterface.java (Fortsetzung) ); } catch(NumberFormatException nfe) { TextFieldSensorPeer.this.input .setText("" +currentValue); } } } ); } protected double currentValue; public double getValue() { return currentValue; } public boolean isConnected() { return AWTRobotInterface.this.isConnected(); } } Bei Abstammung von der abstrakten Klasse BaseSensorPeer, ist die einzige Methode, die implementiert werden mu, getValue(). Fur die meisten Falle wird es auerdem sinnvoll sein, die Methode isConnected() an das entsprechende RobotInterface weiterzuleiten. Wichtig ist auerdem getPrecision(). Der Ruckgabewert dieser Methode soll dem maximal erwarteten Fehler des Sensors entsprechen, der von diesem Peer reprasentiert wird. Die Voreinstellung von BaseSensorPeer ist ein Wert von 0 fur precision { im Zusammenhang mit realen Sensoren wird dieser Wert sicherlich groer ausfallen. In dem Beispiel hier ist die Voreinstellung ausreichend, da die Werte, die in das Textfeld eingetragen werden, ja so "genau" sind wie man mochte. Zudem ist es ein guter Test fur eventuelle Algorithmen, ob diese auch mit diesem Extremwert klarkommen. Man mu bedenken, da precision nur als Anhaltspunkt daf ur angesehen werden kann, wie genau der Sensor ist. Nicht fur jeden Sensor existieren genaue Spezikationen und dann ist man auf Schatzwerte und eigene Tests angewiesen. Der Sensor kann zudem von verschiedenen Faktoren, wie z.B. der Umgebungstemperatur, auf unvorhergesehene (oder zwar auf vorhergesehene, aber in der Peer-Implementierung nicht in Betracht gezogene) Weise beeinut werden, so da ein Algorithmus keine "lebenswichtigen" Funktionen von dessen Korrektheit abhangen lassen sollte. Ereignisse wie die Anderung eines Werts fur einen Sensor werden in Form von SensorEvent-Objekten dargestellt. Diese werden an alle SensorListener, die sich fur solche Ereignisse interessieren, weitergeleitet. Die Verteilung von Events und die Verwaltung von Listenern wird bereits in BaseSensorPeer soweit geregelt, da dies fur das Beispiel ausreicht. Uber einen Aufruf von processEvent(SensorEvent) wird der u bergebene SensorEvent an alle bei diesem Peer registrierten Listener weitergeleitet. An das TextField ist ein ActionListener angeschlossen. Er steht in Form einer anonymen lokalen inneren Klasse im Konstruktor von TextFieldSensor- 112 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE Peer. Die Methode actionPerformed(ActionEvent) des ActionListener wandelt den Inhalt des Textfelds in eine double um und verpackt diesen Wert in einen SensorEvent. Dieser Event enthalt neben dem neuen und dem alten Wert { die durchaus auch gleich sein konnen { einen Verweis auf seinen Ursprung und den Zeitpunkt zu dem er aufgetreten ist. Die anderen Parameter sind (bisher) nur von untergeordneter Bedeutung: descriptor ist vom Typ SensorEventDescriptor und wird momentan noch nicht wirklich benotigt. Er soll spater dazu dienen, verschiedene Events bestimmten Gruppen zuzuordnen, so da man als SensorEventListener angeben kann, nur bestimmte Events zu bekommen, bzw. da ein Peer nur bestimmte Events u berhaupt generiert (siehe SensorPeer. enableEvents(SensorEventDescriptor)). isFloatingPoint gibt an, ob der Event von einem Sensor stammt, von dem prinzipiell echte oatingpoint-Werte kommen konnen. Ist dieser Parameter false bedeutet das, da ein Wert, der durch diesen Event reprasentiert wird, ohne Genauigkeitsverlust auf int gecastet werden kann. propagationId wird benotigt, wenn ein SensorEvent als ein Property- ChangeEvent weitergeleitet wird (siehe JavaBeans API Denition: java. beans.PropertyChangeEvent.getPropagationId()). Zu guter Letzt wird der Wert in dem TextField auf den vorherigen Wert zuruckgesetzt, wenn der String aus dem TextField nicht korrekt in eine double umgewandelt werden kann. Der jeweils letzte erfolgreich in eine double umgewandelte Wert steht in currentValue, daher kann dieser Wert in der Methode getValue() auch direkt zuruckgegeben werden, ohne da das Textfeld nochmal "gefragt" werden mu. Somit wird auch kein String umgewandelt, den man z.B. gerade in dem Textfeld am editieren ist. Die Methode isConnected() dient dazu herauszunden, ob der Peer noch eine Verbindung zu dem Hardware-Sensor hat, den er reprasentiert. In der Regel wird das solange der Fall sein, wie noch Kontakt zur Interface-Hardware besteht. Es ist daher im Prinzip richtig diesen Methodenaufruf an die Implementierung von RobotInterface (also in diesem Fall AWTRobotInterface) weiterzuleiten. In diesem konkreten Fall ware es eventuell auch richtig gewesen z.B. das zugehorige TextField zu fragen, ob es u berhaupt noch sichtbar ist (isShowing()). Die genaue Bedeutung von isConnected() hangt von der jeweiligen Implementierung ab. Mit dem Peer verbundene Sensoren fragen diesen Wert ab, wenn sie ihrerseits nach isConnected() gefragt werden. Im Falle des AWTRobotInterface ist der Wert immer true, da das Fenster sowieso so lange auf dem Bildschirm bleibt wie die Java-Maschine lauft: Listing 43: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) public boolean isConnected() { return true; // maybe use Frame.isDisplayable() ? } 6.2. 113 DIE PEERS Fur Aktuatoren, wie z.B. einen Motor, wird ein weiterer Peer (ActuatorPeer) benotigt, da hier der Wert nicht nur abgefragt, sondern auch gesetzt werden kann: Listing 44: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) protected class TextFieldActuatorPeer extends TextFieldSensorPeer implements ActuatorPeer { protected TextField output; public TextFieldActuatorPeer(TextField output) { super(output); this.output = output; } public void setValue(double value) { setValue(value, true); } public synchronized void setValue( double value, boolean waitMode) { String tmp = "" +value; double oldValue = getValue(); output.setText(tmp); // beware if the TextField is changed by someone else // in this moment if(oldValue != value) { ActionEvent ae = new ActionEvent(output, ActionEvent.ACTION PERFORMED, tmp); // output.dispatchEvent(ae); // Don’t let this Thread // process the Event! // This way the Thread processing the Event is the // real AWT-Event-Thread: output.getToolkit().getSystemEventQueue() .postEvent(ae); } } } Der Parameter waitMode besagt (wenn er false ist), da bei Ruckkehr der Methode die eigentliche Operation, namlich das Setzen des neuen Werts, noch nicht unbedingt abgeschlossen sein mu. Dies kann z.B. dann moglich sein, wenn sich ein anderer Thread um die Fertigstellung kummert. Da das Setzen des neuen Werts in dem Beispiel aber sowieso kaum Zeit in Anspruch nimmt, wird waitMode nicht weiter beachtet. Falls sich der Wert geandert haben sollte, wird auerdem dafur gesorgt, da vom Event-Thread des AWT ein entsprechender ActionEvent verarbeitet wird. Damit bekommt die Oberklasse auf "naturliche" Weise mit, da sich der Wert geandert hat und kann eventuelle SensorListener, mittels eines SensorEvent, 114 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE uber die Anderung in Kenntnis setzen. Dieser Event wird nur bei einer Anderung des Werts erzeugt, da ansonsten der Event-Thread ziemlich u berlastet werden kann. Manche Algorithmen (z.B. Trusty mit Subsumption) setzen in manchen Situationen ziemlich schnell nacheinander den gleichen Wert. Das vom Algorithmus verwendete Actuator-Objekt leitet den Wert trotzdem jedesmal an den Peer weiter, da es ja sein kann, da jemand anderes zwischendurch den Wert beim Peer geandert hat, ohne da der Actuator es (schon) wei. Der Peer selbst kann den neuen Wert dann verwer fen, wenn sichergestellt ist, da eine Anderung an dem Textfeld nicht vonstatten geht, ohne da er informiert wird. Dies kann am einfachsten dadurch geschehen, da fur jedes Textfeld nur ein Peer erzeugt wird. Trotzdem sollte die Methode synchronized sein, denn sonst kann es sein, da die ActionEvents in einer anderen Reihenfolge verarbeitet werden als die Veranderung an dem Inhalt des Textfeldes. Der ActionEvent ist notwendig, damit die Oberklasse merkt, da sich an dem Inhalt des Textfeldes etwas geandert hat. Auf diese Weise kann dort der gleiche Mechanismus verwendet werden, der auch schon beim normalen editieren mit der Hand dafur sorgt, da ein neuer Wert zu einem SensorEvent fuhrt. Man konnte sich uberlegen, ob man nicht verschiedene Peers implementiert, die je nach dem, welcher Sensor angeschlossen wird, verwendet werden. Ein StateSensor konnte dann z.B. durch eine Instanz von Choice abgebildet werden und ein BooleanSensor mit einer Checkbox. 6.3 Die Ports Auer den Peers gehoren zu einer Implementierung von RobotInterface auch entsprechende Implementierungen von SensorPort, bzw. ActuatorPort. Es kann auch weitere Port-Typen geben, die dann aber noch zu denieren waren. Listing 45: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) protected class AWTSensorPort extends BaseSensorPort { AWTSensorPort(String portName, int portNumber, double minimumValue, double maximumValue, long granularity, Port[] excludedPorts) { super(AWTRobotInterface.this, portName, portNumber, minimumValue, maximumValue, granularity, excludedPorts); } ➥ 6.3. DIE PORTS 115 Listing 45: code/art/examples/AWTRobotInterface.java (Fortsetzung) public SensorPeer getPeerFor(Sensor sensor) { SensorPeer peer = getSensorPeer(this, sensor); return peer; } } protected class AWTActuatorPort extends BaseActuatorPort { AWTActuatorPort(String portName, int portNumber, String type, double minimumValue, double maximumValue, long granularity, Port[] excludedPorts) { super(AWTRobotInterface.this, portName, portNumber, type, minimumValue, maximumValue, granularity, excludedPorts); } public SensorPeer getPeerFor(Sensor sensor) { ActuatorPeer peer = getActuatorPeer(this, sensor); return peer; } public ActuatorPeer getPeerFor(Actuator actuator) { ActuatorPeer peer = getActuatorPeer(this, actuator); return peer; } } Die Klassen BaseSensorPort und BaseActuatorPort werden nicht extra beschrieben. Sie kapseln im Grunde nur die Parameter eines Ports, die dem Konstruktor u bergeben werden. Dazu gehort auch die Instanz von RobotInterface, zu der sie gehoren. Die jeweiligen Port-Objekte sind dafur zustandig, die PeerObjekte zu liefern, wenn ein Sensor-Objekt mit ihnen verbunden wird. Bis es zu einem Peer kommt, werden folglich mehrere Stufen eines FactoryPattern (Gamma et al., 1995) durchlaufen: Die RobotInterfaceFactory erzeugt das RobotInterface welches wiederum die Ports erzeugt, die dann ihrerseits die Peer-Objekte liefern. Wie der entsprechende Peer genau erzeugt wird ist dem Port, bzw. dem zugehorigen RobotInterface uberlassen. In dem Beispiel werden die Peers gar nicht direkt von einem Port erzeugt, sondern die Ports verwenden ihrerseits wieder "ihr" RobotInterface als Factory. Wichtig ist nur, da der Typ des Port aussagt, welchen Typ Peer man (mindestens) erwarten kann. In ART sind bisher nur SensorPort und ActuatorPort deniert. Ein Axiom der Port-Klassen soll es sein, da sie jeweils einen bestimmten Peer-Typ garan- 116 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE tieren. Fur den SensorPort kann dieser Peer-Typ noch als Resultat der Methode getPeerFor(Sensor) direkt angegeben werden. Fur ActuatorPort, als von SensorPort abgeleitetes Java-Interface, funktioniert das fur die gleiche Methode naturlich nicht mehr. Dort mu man dann entweder glauben bzw. uberprufen, da der zuruckgegebene SensorPeer auch wirklich ein ActuatorPeer ist, oder man verwendet gleich die Methode getPeerFor(Actuator). Mit "mindestens" ist in diesem Zusammenhang gemeint, da je nach den Umstanden (wie z.B. dem Typ des Objektes fur das der Peer erzeugt wird), auch eine entsprechende Unterklasse als Peer erzeugt werden kann. So liefert ein ActuatorPort der Implementierung fur den Brick einen MotorPeer, wenn ein Motor (oder eine Unterklasse davon) an den ActuatorPort angeschlossen wird.1 Die restlichen Parameter der Konstruktoren fur die Ports sind: portName: eine Bezeichnung fur den Port, die analog zur Bezeichnung der Interface-Hardware fur diesen Anschluss (wenn eine solche existiert) lauten sollte, um eine moglichst einfache Zuordnung durch einen Anwender zu ermoglichen. portNumber: eine weitere Bezeichnung zur Identizierung des Ports innerhalb der Implementierung von RobotInterface. type: eine Bezeichnung anhand derer die Art des Actuators identiziert wer- den kann. Bisher wird dies noch nicht weiter verwendet und dient nur dazu, zusatzliche Informationen u ber den ActuatorPort anzugeben, die beispielsweise fur einen Benutzer ausgegeben werden konnen (z.B. ob der Ausgang Gleich- oder Wechselstrom verwendet). minimumValue: der kleinste Wert, der von einem Peer geliefert wurde, den Sensor-Objekte bekommen, u ber deren Klasse sonst nichts weiter be- kannt ist (also die, die keinen speziellen Peer bekommen). maximumValue: entsprechend minimumValue, nur eben der grote Wert. granularity: die Anzahl von Abstufungen, die zwischen minimumValue und maximumValue fur diesen "einfachsten" Peer existieren. excludedPorts: ein Array von Port-Objekten, die Anschl usse reprasentieren, die nicht gleichzeitig mit dem Anschlu dieses Port-Objekts verwendet werden konnen. 1 MotorPeer ist ein subinterface Abschnitt 4.6 auf Seite 50) von ActuatorPeer (siehe Abbildung 4.5 auf Seite 47 und 6.4. 6.4 DIE METHODEN VON ROBOTINTERFACE Die Methoden von 117 RobotInterface In diesem Abschnitt geht es vor allem um die Methoden, die von dem JavaInterface RobotInterface vorgeschrieben werden. RobotInterfaceDenition Wie bereits in Kapitel 5 auf Seite 59 beschrieben wurde, kann man bestimmte Instanzen von RobotInterface uber die Angabe einer RobotInterfaceDefinition auswahlen: Listing 46: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) private static String definitionPrefix = "AWT"; private RobotInterfaceDefinition definition; public RobotInterfaceDefinition getInterfaceDefinition() { return definition; } public boolean conformsTo(RobotInterfaceDefinition definition) { if(this.definition.equals(definition) || new RobotInterfaceStringDefinition(definitionPrefix) .equals(definition)) { return true; } else { return false; } } private AWTRobotInterface(int interfaceNumber) { String definitionString = definitionPrefix +interfaceNumber; definition = new RobotInterfaceStringDefinition(definitionString); ... } Jedes RobotInterface besitzt eine Instanz von RobotInterfaceDefinition, die dazu geeignet ware, genau diese Instanz von RobotInterface auszuwahlen. Diese RobotInterfaceDefinition bekommt man u ber getInterfaceDefinition() und sie sollte eindeutig einer einzigen Instanz vom Typ RobotInterface zugeordnet sein, in dem Sinne, da nur eine einzige (erfolgreich erzeugte) Instanz vom Typ RobotInterface, bei Aufruf von conformsTo(RobotInterfaceDefinition) den Wert true zuruckliefert. Auf diese Weise bekommt man von RobotInterfaceFactory, bei Aufruf von getInterface(RobotInterfaceDefinition), f ur diese eine RobotInterfaceDefinition, immer die gleiche Instanz prasentiert. Fur diese Eindeutigkeit mu man in dem Beispiel beim Aufruf des Konstruktors sorgen. Da dieser private markiert ist, ist er aber nur innerhalb dieser Klasse u berhaupt sichtbar. Er wird von der Klassenmethode getInterfaces(RobotInterfaceDefinition[]) aufgerufen, welche in Abschnitt 6.5 auf Seite 120 naher beschrieben wird. 118 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE Im Gegenzug heit das jedoch nicht, da es keine RobotInterfaceDefinition geben kann, f ur die sich auch mehrere Instanzen vom Typ RobotInterface "zustandig" fuhlen konnen. Im Beispiel ware das fur eine RobotInterfaceStringDefinition der Fall, wenn diese den String "AWT" reprasentiert. RobotInterfaceDefinition ist nur ein sogenanntes tagging-interface, welches keine Methoden vorschreibt. Welche Klasse als RobotInterfaceDefinition verwendet wird, bleibt voll und ganz der jeweiligen Implementierung von RobotInterface u berlassen. Im Beispiel { entsprechend den anderen bereits in ART vorhandenen RobotInterface-Implementierungen { wird die Klasse RobotInterfaceStringDefinition verwendet, welche f ur die meisten Falle ausreichen sollte. Erzeugen der Ports Die Ports eines RobotInterface braucht man, um Sensoren anschlieen zu konnen. Von RobotInterface werden Methoden deniert, mit denen die PortInstanzen erfragt werden konnen. Diese sind nicht weiter aufwandig, es werden nur entsprechend initialisierte Sensor- bzw. ActuatorPort-Objekte zuruckgegeben: Listing 47: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) public SensorPort[] getSensorPorts() { return new SensorPort[]{ new AWTSensorPort( new AWTSensorPort( "TextFieldInput0", 0, Double.NEGATIVE INFINITY, Double.POSITIVE INFINITY, -1, null), "TextFieldInput1", 1, Double.NEGATIVE INFINITY, Double.POSITIVE INFINITY, -1, null)}; } public ActuatorPort[] getActuatorPorts() { return new ActuatorPort[]{ new AWTActuatorPort("TextFieldOutput0", 0, "", Double.NEGATIVE INFINITY, Double.POSITIVE INFINITY, -1, null), new AWTActuatorPort("TextFieldOutput1", 1, "", Double.NEGATIVE INFINITY, ➥ 6.4. DIE METHODEN VON ROBOTINTERFACE 119 Listing 47: code/art/examples/AWTRobotInterface.java (Fortsetzung) Double.POSITIVE INFINITY, -1, null)}; } Eine Erklarung der Bedeutung der einzelnen Parameter, der Konstruktoren fur die Ports, wurde bereits in Abschnitt 6.3 auf Seite 114 gegeben. Erzeugen der Peers Die Methoden zur Erzeugung der Peer-Instanzen in AWTRobotInterface sind nicht public und werden von dem Java-Interface RobotInterface auch nicht vorgegeben: Listing 48: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) private TextFieldSensorPeer inPeer0; private TextFieldSensorPeer inPeer1; SensorPeer getSensorPeer(SensorPort port, Sensor sensor) { switch(port.getPortNumber()) { case 0: if(inPeer0 == null) { inPeer0 = new TextFieldSensorPeer(in0); } return inPeer0; case 1: if(inPeer1 == null) { inPeer1 = new TextFieldSensorPeer(in1); } return inPeer1; default: return null; } } private TextFieldActuatorPeer outPeer0; private TextFieldActuatorPeer outPeer1; ActuatorPeer getActuatorPeer(ActuatorPort port, Sensor sensor) { switch(port.getPortNumber()) { case 0: if(outPeer0 == null) { outPeer0 = new TextFieldActuatorPeer(out0); } return outPeer0; case 1: if(outPeer1 == null) { outPeer1 = new TextFieldActuatorPeer(out1); } return outPeer1; default: return null; ➥ 120 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE Listing 48: code/art/examples/AWTRobotInterface.java (Fortsetzung) } } In diesen beiden Methoden mu der Port nicht darauf uberpruft werden, ob er "einer von uns" ist, da die Methode keinen public-access besitzt und daher nicht von beliebigem Code aus aufgerufen werden kann. Wie bereits erwahnt sollen Peer-Instanzen nur u ber die jeweiligen Port-Objekte "nach auen" gelangen. Auf diese Weise ist der konkrete Vorgang, wie ein Port zu dem Peer fur einen Sensor kommt, vor dem Anwender versteckt. Damit bekommt man die Freiheit, die Peers dort zu erzeugen wo man es fur sinnvoll halt. Es ware durchaus denkbar, da die Port-Klassen von AWTRobotInterface die Peer-Instanzen direkt selbst erzeugen. Dies ist nur eine Stil- und Designfrage innerhalb der Implementierung von RobotInterface. Die Verwendung zentraler Methoden hat den Vorteil, da die Erzeugung der Peers nicht uber mehrere Stellen verteilt geschieht. Wenn man z.B. eine Unterklasse von AWTRobotInterface erstellt, in der man auf die Erzeugung der Peers Einu nehmen will, so mu man nur eine der beiden Factory-Methods (Gamma et al., 1995) u berschreiben. Wurde man dafur neue Port-Klassen erstellen, so mute man auerdem alle die Stellen andern, an denen Instanzen dieser Klassen erzeugt werden. Die Bedeutung der Methode isConnected wurde bereits in Abschnitt 6.2 auf Seite 110 behandelt. Sie ist daher dort abgedruckt (Listing 43 auf Seite 112). Die letzte zu implementierende Methode isAlive() hat eine damit verwandte Bedeutung: Listing 49: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) public boolean isAlive() { return true; } Die Methode isAlive() dient dazu herauszunden, ob die Interface-Hardware noch wie erwartet antwortet, ob die Verbindung also nicht nur physikalisch, sondern auch logisch in Ordnung ist. Das ist bei Textfeldern im AWT im Prinzip immer der Fall. 6.5 Die Einbindung in ART: RobotInterfaceFactory Instanzen vom Typ RobotInterface erzeugt man in ART nicht direkt. Dafur ist die Klasse RobotInterfaceFactory vorgesehen. Diese hat mehrere Methoden, die alle eine oder mehrere Instanzen vom Typ RobotInterface zuruckliefern. Welche das sind, kann man hauptsachlich u ber die Angabe von einer (oder mehreren) RobotInterfaceDefinition beeinussen, denen die zuruckgelieferten Instanzen genugen mussen (siehe die Beschreibung zu Listing 46 auf Seite 117). Die Klasse RobotInterfaceFactory besitzt dafur eine Liste von Klassen, denen sie die Gelegenheit gibt, Instanzen fur die entsprechende Menge von RobotInterfaceDefinitions zu erzeugen. Die Methode die RobotInterface- 6.5. DIE EINBINDUNG IN ART: ROBOTINTERFACEFACTORY 121 Factory bei diesen Klassen daf ur aufruft ist getInterfaces(RobotInterfaceDefinition[]): Listing 50: code/art/examples/AWTRobotInterface.java (Ref. in Listing 41 S. 108) .. public static synchronized RobotInterface[] getInterfaces(RobotInterfaceDefinition[] ports) { // ’search’ port-array for the AWT-ones Vector interfaces = new Vector(); for(int i=0; i<ports.length; i++) { ... // don’t accept String ’ALL’, only return an // AWTInterface if explicitly asked for ... iface = new AWTRobotInterface(interfaceNumber); // was initialization successful ? if(iface.isConnected()) { interfaces.add(iface); portNamesToInterfaces .put(new Integer(interfaceNumber), iface); } else { if(debug.debug) { debug.printInfo(iface +" is not connected," +" not adding."); } } ... } RobotInterface[] result = new RobotInterface[interfaces.size()]; interfaces.copyInto(result); return result; } Diese Implementierung stammt aus dem Beispiel AWTRobotInterface. Der grote Teil ist hier fur eine bessere Ubersichtlichkeit nicht abgedruckt, da er zum grundsatzlichen Verstandnis nicht notwendig ist. Wenn man wirklich ein eigenes RobotInterface implementieren mochte, oder einfach nur Interesse daran hat, kann man ja selbst in die Quelldatei schauen. Wichtig ist, da die Methode ein Array von RobotInterface-Objekten zuruckliefert, die alle soweit erfolgreich initialisiert werden konnten. Fur jedes zuruckgelieferte RobotInterface sollte sichergestellt sein, da fur wenigstens eine RobotInterfaceDefinition in dem Array ports die conformsTo(RobotInterfaceDefinition)-Methode den Wert true liefert, damit die semantische Konsistenz gewahrt bleibt. Zudem sollte die Methode versuchen, moglichst fur alle angegebenen Robot- 122 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE InterfaceDefinition, die zu der entsprechenden Implementierung von RobotInterface gehoren, eine Instanz zu liefern. Dafur kann es notwendig sein, sich bei fruheren Aufrufen erzeugte Instanzen zu merken und bei Bedarf erneut zuruckzuliefern. Wenn z.B. ein echtes Hardware-Interface im Spiel ist, ware es wohl meistens nicht praktikabel mehrere Instanzen von RobotInterface zu erzeugen, die sich alle um dieselbe Hardware "kummern" wollen. Es kann aber durchaus sein, da ein und dasselbe RobotInterface von verschiedenen Programmteilen verwendet wird, die durch unterschiedliche Anfragen an RobotInterfaceFactory, Zugri auf dieselbe Hardware bekommen mochten. Die Liste der Klassen, die von RobotInterfaceFactory gefragt werden, stammt aus der Property de.jaetzold.art.RobotInterfaceFactory.platforms Diese Property sollte eine durch whitespace getrennte Liste von voll qualizierten Klassennamen enthalten. Alle diese Klassen werden uber reect darauf untersucht, ob sie die Klassenmethode getInterfaces(RobotInterfaceDefinition[]) besitzen. Diese werden dann dazu verwendet, die Instanzen von RobotInterface zu erzeugen. Dafur geht die RobotInterfaceFactory davon aus, da der Ruckgabetyp der Methode RobotInterface[] ist. Abbildung 6.2: Der Algorithmus von Trusty steuert einen virtuellen Roboter mit dem AWTRobotInterface. Um also das Beispiel AWTRobotInterface auch uber die RobotInterfaceFactory zuganglich zu machen, mu man nur die Property entsprechend setzen. Es gibt dafur einerseits die Kongurationsdatei de.jaetzold.art.properties, die von RobotInterfaceFactory gelesen wird, andererseits werden auch eventuelle System-Properties ausgewertet, und zwar mit Vorrang vor dem Inhalt der Kongurationsdatei. Fur einen Test reicht es also vollig, die Property beim Aufruf von java mit dem Parameter -D zu setzen: $ java -Dde.jaetzold.art.RobotInterfaceFactory.platforms=de. jaetzold.art.examples.AWTRobotInterface de.jaetzold.art.exam ples.SubsumptionTrustyAWTDemoRobot Genauso, wie fur die Implementierungen fur Fischertechnik und den Brick, gibt es im package de.jaetzold.art.examples fur die Beispielalgorithmen, 6.5. DIE EINBINDUNG IN ART: ROBOTINTERFACEFACTORY 123 die fur Trusty mit Subsumption benotigt werden, Implementierungen fur deren Robot Java-Interface, die mit dem AWTRobotInterface funktionieren. Auf der erscheinenden Oberache (siehe Abbildung 6.2 auf der vorherigen Seite) kann man dann, wie schon in Kapitel 5 auf Seite 59, die Funktionen von Trusty aufrufen bzw. mit move den Subsumption-Algorithmus in Bewegung setzen. Im Fenster des AWTRobotInterface kann man die Veranderung der Motoren beobachten und durch Eingabe von 1 in den Textfeldern fur die Sensoren, Trusty's Ausweichverhalten auslosen, denn der Wert 1 wird von einem BooleanSensor standardmaig als texttttrue interpretiert und daher von den Trusty-Algorithmen wie ein gedruckter Schalter verstanden. 124 6. IMPLEMENTIERUNG EINES ROBOTINTERFACE 7 Zusammenfassung Neben einem kurzen Fazit wird in diesem Kapitel das Abstract Robot Toolkit nochmal gegenuber den Systemen aus Kapitel 3 abgegrenzt. Zudem wird auf die Punkte naher eingegangen, in deren Zusammenhang fruchtbar erscheinende Entwicklungsperspektiven existieren. 7.1 Ergebnis Die Beispiele aus Kapitel 5 haben gezeigt, da es mit ART moglich ist, einen Algorithmus zur Robotersteuerung auf verschiedenen Hardware-Plattformen wiederzuverwenden. Es wurden dafur teilweise eigene, objektorientierte Designs entwickelt, mit denen weitgehend unabhangig von der konkreten Auspragung der Komponenten des jeweiligen Roboters, ein Steuerungsalgorithmus entwickelt werden kann. Die Verwendung von Objekten zur Reprasentation der Komponenten eines Roboters ist elegant und eine event-getriebene Programmierung unter Verwendung von mehreren Threads erweist sich fur die Abstraktion des Roboters und seines Verhaltens als sehr tauglich. Im Fachbereich Wirtschaftswissenschaften an der Universitat Osnabruck wird ART seit Oktober 2001 eingesetzt. In dem Projekt "Virtuelles Seminar" (http://vsem.oec.uni-osnabrueck.de/) werden u ber ein Fischertechnik-Interface Kameras und Mikrofone geschaltet, sowie "Meldungen" von Teilnehmern registriert. ART wird dort verwendet, weil es die einzige bekannte Java-Losung ist, die auch das Expansion-Modul zum Intelligent Interface von Fischertechnik ansteuern kann. 7.2 Geschwindigkeit Wenn an manchen Stellen, im Design und in der Implementierung, Kompromisse zwischen Performance und Flexibilitat eingegangen werden mussten, wurde meistens auf Kosten der Performance entschieden. So wird z.B. bei jeder re gistrierten Anderung eines Sensorwerts mindestens ein neues Event-Objekt erzeugt. Das logische Design und die strukturellen Moglichkeiten standen immer im Vordergrund. 125 126 7. ZUSAMMENFASSUNG Fur Anwendungen, bei denen es wirklich auf Geschwindigkeit ankommt, ist aber schon die gesamte Umgebung ungeeignet, da das normale Java Development Kit von Sun nicht echtzeitfahig ist. Es existieren zwar bereits Bestrebungen fur eine standardisierte Echtzeit-Erweiterung (Bollella et al., 2000), doch Implementierungen davon sind zumindest bisher nicht frei verfugbar. Zudem ist in diesem Zusammenhang z.B. die Kommunikation mit der Interface-Hardware uber Infrarot, wie das beim Brick der Fall ist, problematisch. Auerdem mu nicht nur Java, sondern auch das zugrunde liegende Betriebssystem echtzeitfahig sein, was zumindest fur Linux und Windows spezielle Erweiterungen erfordert. Mit der Performance eines Systems wie legOS kann ART auf keinen Fall mithalten, obwohl bestimmt noch einige Moglichkeiten zur Optimierung der Performance, in den Implementierungen fur den Brick und das Intelligent Interface, vorhanden sind. Kommt es auf einigermaen schnelle Reaktionszeiten an, ist ART im Zusammenhang mit Lego bisher nicht verwendbar, was aber zu einem erheblichen Teil an der langsamen und unzuverlassigen Infrarotkommunikation liegt. Bei der Ansteuerung des Brick vergeht dort viel Zeit. Bis ein Befehl an den Brick angekommen und umgesetzt ist, konnen leicht 100 Millisekunden vergehen. In Kombination mit der Tatsache, da der Brick eigentlich drei Befehle benotigt, um den kompletten Zustand eines Motors zu beschreiben, ist es z.B. kaum moglich zwei Motoren annahernd gleichzeitig zu starten oder zu stoppen. Zum Teil konnten aber auch noch bessere Strategien entwickelt werden, vor allem in der Ansteuerung der Motoren. Die Fahigkeit des Brick, eigene Prozesse auszufuhren, wird nicht genutzt. Zusammen mit der existierenden Moglichkeit, Speicherplatze fur Variablen im Brick zu lesen und zu schreiben, kann man im Grunde ein eigenes Kommunikationsprotokoll zur Ansteuerung der Motoren entwickeln. Damit lasst sich das Problem, da die Motoren nicht synchron geschaltet werden konnen, ganz gut umgehen. Fur die Steuerung eines fuballspielenden Lego Cybermaster, wurde diese Technik bereits einmal verwendet (Jatzold, 2002). Sie wurde jedoch nicht allgemein in ART integriert. Die Ansteuerung von Fischertechnik geht hingegen recht schnell. Trotzdem ist fur Zeiten unter 20 Millisekunden selbst Fischertechnik nicht mehr ausreichend. Das kann an der seriellen Kommunikation in Java liegen, wie der Unterschied zum parallelen Interface in Schreiner (2000b) zeigt. 7.3 Flexibilit at Es hat sich gezeigt, da die Menge von Dingen, die man mit Sensoren und Actuatoren abbilden kann, uber die Komponenten eines Roboters hinausgeht. Dies ist aber nur folgerichtig, wenn man bedenkt, da es das Ziel von ART war, von der konkreten Roboter-Hardware zu abstrahieren. So wird ein BooleanSensor verwendet, um dem Scheduler fur Subsumption die Bereitschaft einer Verhaltensweise zu signalisieren. Konstante Werte werden, durch einen ConstantSensorPort, fur beliebige Sensoren verfugbar gemacht. Die Moglichkeiten sind sehr weitgehend und konnen wahrscheinlich noch zu sehr interessanten Ergebnissen fuhren. 7.4. ARCHITEKTUR 127 Das Ariadne-Beispiel fur leJOS ist wesentlich einfacher gestrickt als Ariadne fur ART. Das hangt vor allem damit zusammen, da der Speicher auf dem Brick sehr begrenzt ist und daher in leJOS nur sehr kleine Teile des normalen Java-API verfugbar sein konnen. Daher konnten auch viele fur das ARTBeispiel verwendete Hilfsklassen nicht, bzw. nicht ohne weiteres, ubernommen werden. leJOs ist zwar schneller in den Reaktionszeiten, aber was Speicher, Klassenbibliothek und Rechengeschwindigeit angeht ist die ART-Losung auf einem modernen PC wesentlich leistungsfahiger. Das entwickelte Toolkit bietet eine sehr bequeme Moglichkeit, um einfache Fernsteuerungen von Lego und/oder Fischertechnik von einem PC aus in Java zu realisieren { beispielsweise die Bewegung einer Webcam. Andere Java-Losungen, die auf Interna der verwendeten Roboter-Hardware beruhen, sind deutlich schwieriger zu benutzen, zumindest wenn man die Interna erst kennen lernen mu. Bis man in ART soweit ist, da man einen Sensor auch wirklich verwenden kann, sind mehrere Schritte erforderlich. Dies erschwert eine sehr naive Verwendung von Sensoren ein wenig. Ein Sensor mu immer mit einem Port verbunden werden, sonst hat er keinen SensorPeer. Der Port wiederum wird, in den meisten Fallen, von einem RobotInterface stammen, das seinerseits von einer Factory erzeugt wurde. Im Prinzip ist es moglich, da ein Sensor-Objekt, wenn es erzeugt wird, von selbst versucht, eine Verbindung zu einem geeigneten Peer herzustellen. Es bleibt oen nach welchen Regeln dieser Peer dann ausgewahlt wird. Es ist bisher nicht klar, inwieweit diese Regeln kongurierbar sein mussten, damit eine eindeutige und trotzdem exible Verbindung zwischen den Sensor-Objekten der Software und den Sensoren der Hardware moglich bleibt. Naivere Verwendung: 7.4 Architektur Fur die Verwendung mit Lego und Fischertechnik ist die entwickelte Architektur von Sensor-Klassen gut geeignet. Es wurde auerdem weitgehend auf mogliche andere Hardware-Plattformen Rucksicht genommen. So existiert z.B. fur einen Servo ein eigener ServoPeer, obwohl weder Lego noch Fischertechnik Servomotoren auf der Hardware-Seite direkt unterstutzen. Es mute nun untersucht werden inwieweit die Architektur anderen Plattformen gerecht wird. Es ist nicht moglich auszuschlieen, da sich manches, insbesondere Teile der Hierarchie der Sensor-Klassen, fur eine bestimmte HardwarePlattform als schwierig herausstellt. Die Klasse AngleSensor ist moglicherweise nicht ideal in die Hierarchie der Sensor-Klassen eingeordnet. So ist ein AngleSensor in ART eine Unterklasse von CountSensor und damit auch von StateSensor. Ein StateSensor reprasentiert aber diskrete Zustande, ein AngleSensor hingegen eine physikalische Groe. Die momentane Position von AngleSensor in der KlassenHierarchie ist vor allem der Hardware von Lego und Fischertechnik angemessen, da bei beiden Winkel durch Rotationen und diese wiederum durch Folgen von 128 7. ZUSAMMENFASSUNG Zustandsanderungen gemessen werden. Somit ist ein AngleSensor als Unterklasse von CountSensor, welcher fur das Zahlen der Zustandsanderungen gedacht ist, sehr praktisch. Es ist aber auch vorstellbar, da ein Winkel z.B. durch ein Potentiometer gemessen wird. Das hat mit Zustanden und deren Anderungen nichts mehr zu tun. Eine erneute Implementierung sollte wohl besser einen AngleSensor denieren, der an einen CountSensor angeschlossen werden kann, anstatt einen, der ein CountSensor ist. Weiterhin konnen die bisher denierten, beschreibenden Eigenschaften der Sensoren, wie z.B. die Genauigkeit oder der Messbereich, wahrscheinlich nicht allen moglichen Fallen gerecht werden. Vor allem die Genauigkeit ist auch insofern ein Problem, da praktisch jeder Algorithmus von ihr abhangt, wenn auch in unterschiedlichem Ausma. Ein einzelner Wert, zur Beschreibung der Genauigkeit eines Sensors, ist aber selbst schon ungenau, denn ein Sensor kann z.B. in unterschiedlichen Messbereichen oder Umgebungsbedingungen unterschiedlich genau sein. Daher mute die Genauigkeit durch eine Funktion mit vielen Parametern beschrieben werden und nicht nur durch einen einzelnen Wert. Eventuell fuhrt das aber nun zu weit und man hat eine der Grenzen der hardware-unabhangigen Steuerung von Robotern gefunden. Programmierbare Tasks: Die Fahigkeiten des Brick wurden nicht komplett umgesetzt. Es gibt bisher z.B. keine eigene Klasse fur den Temperatursensor. Dies ware aber notwendig, wenn man den Eingang des Brick speziell dafur kongurieren konnen will. Das Betriebssystem von Lego fur den Brick bietet eine Umrechnungsmethode fur boolesche Sensorwerte an, die bisher auch nicht erreichbar ist (EdgeCount). Auf die Moglichkeit sogenannte Messages zu verschicken, auf lokale Variablen des Brick zuzugreifen oder gar eigene Tasks zu laden und auf dem Brick zur Ausfuhrung zu bringen, mu man bisher verzichten. Programme zu denieren und auf die Interface-Hardware zu ubertragen, ist mit ART bisher nicht moglich. Analog zu den Moglichkeiten von Spirit.ocx (siehe 3.2.1 auf Seite 23) konnte man aber eventuell ein Task-Objekt entwickeln. Vielleicht ist die Programmierung von Tasks auch uber eine entsprechende Erweiterung der Eigenschaft "waitForCompletion" moglich. So konnten eventuell Befehle, die man z.B. an einen Motor sendet, erst einmal gesammelt und dann als Block auf das Interface u bertragen und ausgefuhrt werden. Eine Integration der Fahigkeit des Bricks, eigene Tasks auszufuhren, durfte aber sehr interessant werden. Eventuell kann man damit viele der bisherigen Geschwindigkeitsprobleme mit dem Brick in den Gri bekommen. Es stellt sich hier jedoch die Frage, inwieweit diese Technik wirklich plattformunabhangig realisierbar ist. Eventuell ist es am geschicktesten, dafur eine eigene einfache Sprache zu entwickeln, die je nach Zielsystem entsprechend interpretiert werden kann. 7.5. 7.5 SICHERHEIT 129 Sicherheit Sowohl das Intelligent Interface von Fischertechnik, als auch das altere Interface mit einem Anschluss fur die parallele Schnittstelle, haben eine Sicherheitsfunktion eingebaut. Werden vom PC aus an das Interface fur eine gewisse Zeit (etwa 300-500 Millisekunden) keine Daten mehr verschickt, so werden alle Ausgange abgeschaltet. Dies ist solange der Fall bis das Interface wieder einen Befehl vom PC erhalt. Eine solche Schutzfunktion ist im Grunde wichtig. Software enthalt leider auch Fehler. Wenn solch ein Fehler dann dazu fuhrt, da der Roboter vollig unkontrollierte Aktionen ausfuhrt und sich selbst (oder sogar seine Umgebung) "zerlegt", kann das teuer werden. Der Brick hat eine solche Schutzfunktion nicht. Auch Fischertechnik ist naturlich durch die beschriebene Technik nicht vor allen Fehlern geschutzt. Wenn z.B. ein wichtiger Schalter nicht richtig angeschlossen ist, oder die Software zwar mit dem Interface kommuniziert, aber eben nicht wie erwartet, so greift der Schutzmechanismus nicht. Gerade beim Brick ware diese Funktion aber bei der Steuerung vom PC aus hilfreich, denn allzuoft passiert es, da er aus dem Sendebereich des Towers herausfahrt oder aus irgendeinem anderen Grund die Infrarotkommunikation gestort ist. In ART ist solch eine Schutzfunktion bisher nicht implementiert. Auch der eingebaute Schutz von Fischertechnik wird teilweise ausgehebelt, da es durchaus sein kann, da der Steuerungsalgorithmus stehengeblieben oder abgesturzt ist, ohne die Threads, die bei Fischertechnik mit der Interface-Hardware kommunizieren, in Mitleidenschaft gezogen zu haben.1 Um eine solche Schutzfunktion zur Verfugung zu stellen, ware es also notwendig, da eine Implementierung von RobotInterface regelmaig gewisse Bedingungen uberpruft und gegebenenfalls einige oder alle Ausgange abschaltet. Im Optimalfall wurden nur genau die Ausgange stillgelegt, fur die ein steuernder Algorithmus in irgendeiner Form nicht mehr "richtig" funktioniert. Falls der Steuerungsalgorithmus sich wieder aufrappeln sollte, oder ein anderer dafur sorgt, da die Ausgange vernunftige Werte haben, ware es wunschenswert wenn die Ausgange auch wieder aktiviert wurden. Ob der Algorithmus noch korrekt funktioniert, kann { wenn uberhaupt { nur der Algorithmus selbst wissen. Zum Beispiel konnte ein Algorithmus regelmaig checken, ob er sich noch in einem konsistenten Zustand bendet oder alle seine Threads noch laufen. Wenn er sich aber nicht mehr in einem konsistenten Zustand bendet und erst recht wenn keiner seiner Threads mehr aktiv ist, kann er sich wahrscheinlich nicht mehr von sich aus "melden". Damit nun aber nicht jeder Algorithmus darauf achten mu, z.B. alle 300 Millisekunden eine Nachricht an das RobotInterface zu schicken, kann man auch den umgekehrten Weg gehen: Das RobotInterface, bzw. die Actuator-Objekte, fragen den Algorithmus von sich aus regelmaig danach, ob noch alles in Ordnung ist. Dies ist wieder ein typischer Fall fur das Delegate-Design-Pattern, welches 1 Genaugenommen werden die Threads, die mit der Interface-Hardware kommunizieren, sogar niemals durch das Versagen eines Steuerungsalgorithmus gest ort. 130 7. ZUSAMMENFASSUNG auch schon bei der Beschreibung der Sensor-Klassen, in Abschnitt 4.6 auf Seite 50, erwahnt wurde. Ein RobotInterface (bzw. ein Actuator, wenn man eine feinere "Auosung" haben mochte) konnte eine Art aliveDelegate besitzen. Dieser wurde dann regelmaig gefragt ob noch alles in Ordnung ist und falls nicht, wurde sich das RobotInterface, bzw. der Actuator, temporar abschalten, bis eine Anfrage an den Delegate ergibt, da wieder alles in Ordnung ist. Moglicherweise wurde der oben beschriebene optimale Fall auch noch weitere Kongurationsmoglichkeiten oder Delegate-Methoden erfordern. Solch ein Delegate wird von ART bisher nicht unterstutzt. Vor allem die Umsetzung fur den Brick wurde einen Mehraufwand erfordern, fur den einfach keine Zeit mehr vorhanden war. 7.6 Andere Hardware Es existiert heute Hardware, die einerseits zwar immer noch klein ist und weniger Rechenkapazitat als ein moderner PC besitzt, auf der anderen Seite aber deutlich schneller ist und vor allem mehr Speicher enthalt, als dies fur den Brick der Fall ist. So gibt es fur viele aktuelle Mobiltelefone oder die sogenannten PDA's (Personal Digital Assistant) eine Implementierung der J2ME (Java 2 Platform, Micro Edition). Speziell fur die Verwendung von Java auf einem embedded device innerhalb eines Netzwerks, ist auch das TINI-Board (http: //www.ibutton.com/TINI/) geeignet. Es zeichnet sich durch einen integrierten seriellen Anschluss, seine Ethernetfahigkeit (TCP/IP inkl. FTP-, HTTPund Telnet-Server) und durch die Unterstutzung weiterer Bussysteme aus. Das TINI-Board gibt es mit 0.5 oder 1.0 MB Speicher und einer beinahe kompletten Implementierung der Java-packages java.lang, java.io, java.net und java.util. Kann man etwas loten oder kommt man mit einem der bereits vorhandenen Bussysteme aus, so sollte es sich gut fur die Steuerung von Robotern verwenden lassen. Ein naiver Versuch das Abstract Robot Toolkit auf TINI zu ubertragen, war nicht erfolgreich. Zur Fehlersuche kann man zwar ganz normal, aus dem Programm heraus, Text zur Ablaufverfolgung ausgeben, jedoch wird speziell durch diese Art von I/O-Operationen das TINI-Board besonders stark beansprucht. Aus diesem Grund andert sich das Zeitverhalten signikant. Dies ist aber bei der verwendeten Interface-Hardware von schwerwiegender Bedeutung, denn das Intelligent Interface schaltet sich z.B. ab, wenn es fur etwa 300 Millisekunden nichts mehr uber die serielle Leitung empfangen hat. 7.7 Subsumption Im Rahmen dieser Arbeit wurde Subsumption (siehe Abschnitt 1.2) zwar angewandt, als wichtiges Konzept, das eventuell ganz neue Moglichkeiten eronet, aber nur gestreift. Fur die Programmierung mit ART ist die Klasse de. jaetzold.art.subsumption.SchedulerTask ein Ansatz, um auf einfache Weise Subsumption als Design-Pattern fur verhaltensbasierte Robotersteuerung zu verwenden. 7.8. JAVABEANS 131 Es konnte sich durchaus als fruchtbar erweisen, in dieser Richtung ein eigenes API zu entwickeln. Das mu nicht unbedingt sehr umfangreich sein, aber wenn die Schnittstellen zu den Verhaltensweisen und dem Scheduler wohldeniert sind, dann ist eine Wiederverwendung der Verhaltensweisen { und naturlich auch des Schedulers { leichter. So konnte sich mit der Zeit eine ganze Bibliothek von fertigen Verhaltensweisen ansammeln. Die Moglichkeit, einen Roboter zu programmieren, indem bereits fertige Algorithmen praktisch "zusammengesteckt" werden, ist vor allem auch im Hinblick auf die Programmierung mittels einer graschen Oberache sehr attraktiv. Subsumption wird nicht nur fur die Programmierung echter, physikalischer Roboter verwendet, sondern z.B. auch im Bereich der agentenbasierten Modellierung (Wooldridge, 1999). Aus diesem Grund ware es vorteilhaft, wenn das gleiche API { im Optimalfall sogar die gleichen Verhaltensweisen { sowohl fur die Simulation im Computer als auch fur die Steuerung echter RoboterHardware verwendet werden kann. Mit ART existiert dafur eine Basis. In Kapitel 6 wurde gezeigt, da die Algorithmen fur Trusty ohne groen Aufwand auch mit dem rein softwarebasierten AWTRobotInterface verwendet werden konnen. Inwieweit aber ansonsten zwischen echten Robotern und Softwareagenten uberhaupt Gemeinsamkeiten bestehen, die eine Verwendung von Algorithmen in beiden Bereichen rechtfertigen, bendet sich auerhalb des Themas dieser Arbeit. 7.8 JavaBeans Nach der Spezikation von Sun (Sun Microsystems, 1997) bezeichnet das JavaBeans API eine Menge von Design-Pattern und Hilfsklassen und soll als Komponentenmodell fur Java dienen. Das Ziel sind portable, wiederverwendbare Softwarekomponenten, die als solche verkauft werden konnen und vom Kaufer je nach Anforderungen zu fertigen Applikationen zusammengebaut werden { am besten in einem (graschen) Editor. Dies erinnert an die Paletten fur den Interface-Builder unter OpenStep (Garnkel und Mahoney, 1993) und nun MacOS-X. Auerdem sollen Beans als eine Art Plugin dienen um Applikationen zu erweitern. Diese Funktionalitat kann auch in plattformabhangige Komponentenarchitekturen wie OpenDoc, OLE, COM, Active-X usw. integriert werden. Im Zusammenhang mit der Programmierung von Robotern halte ich vor allem das Arrangement und die Manipulation von Komponenten in einem graschen Editor fur interessant. Nicht von ungefahr werden von Lego und Fischertechnik in erster Linie grasche Programmierumgebungen zur Ansteuerung ihrer Interface-Hardware angeboten. Die groeren Geschwister von RoboLab und LLWin (LabView und iCon-L) werden in Labors zur Datenerfassung und Auswertung sowie zur Industrieautomation eingesetzt. Es besteht also offenbar Bedarf an Moglichkeiten zur graschen Programmierung von Robotern. Dabei bieten Beans noch den Vorteil, da sie auf ganz normalen Java-Klassen beruhen und daher auch auf "konventionellem" Wege als Bibliothek von Objek- 132 7. ZUSAMMENFASSUNG ten verwendet werden konnen. Zudem konnen die Moglichkeiten der graschen Umgebung durch Entwicklung weiterer Beans leicht erweitert werden. Eines der Ziele von ART ist die Moglichkeit der Wiederverwendung von Steuerungsalgorithmen fur verschiedene Roboter. Solch ein Roboter mu dann aber gewisse, vom Algorithmus denierte Anforderungen erfullen. So mu ein Trusty-Roboter z.B. irgendwelche Motoren haben, mit denen der Roboter bewegt werden kann, sowie Sensoren um Hindernisse zu registrieren. Der Algorithmus deniert, was passieren soll, wenn die Motoren auf gewisse Weise gesteuert werden, und welche Sensoren er benotigt bzw. wie deren Wert interpretiert wird. Dazu kann es notwendig sein, die Motor- und Sensor-Objekte vor der Ubergabe an den Algorithmus geeignet zu kongurieren. Auerdem mu festgelegt werden, an welchem Anschluss und (aufgrund der Hardware-Unabhangigkeit von ART) an welcher Hardware der Motor angeschlossen ist. Dies kann sicherlich in vielen Fallen, auch bei erfahrenen Programmierern, am einfachsten mit einem graschen Tool geschehen. Es ist aber auch denkbar, da der Algorithmus selbst als Bean realisiert ist. So konnen nicht nur die Hardware-Komponenten des Roboters, sondern auch seine gesamte Steuerung, in solch einem Tool gleichsam "zusammengeklickt" werden. Mit Subsumption (siehe Abschnitt 4.8 auf Seite 57) gibt es sogar ein Konzept, das die Kombination von vielen (einfachen) Steuerungsalgorithmen zu einem komplexen Verhalten vereinfacht, so da es moglich sein sollte, mit zunehmendem Vorrat an Steuerungsalgorithmen immer mehr Losungen allein durch das "zusammenstecken" von bereits vorhandenen Komponenten zu realisieren. Die Vorteile einer graschen Umgebung sehe ich in der leichteren Zuganglichkeit fur Neulinge, dem schnelleren "eben mal" Ausprobieren auch fur erfahrenere Entwickler, fur Demos und rapid Prototyping, da man praktisch "online" einfache Algorithmen zusammenklicken kann, wahrend man z.B. mit einem Kunden fur die Software (bzw. den Roboter) die Anforderungen diskutiert. Zumindest im Bereich der Oberachenprogrammierung haben sich grasche Tools inzwischen in den meisten IDE's einen festen Platz erobert. Neben all den Vorteilen, die man von Beans hat, die sich vor allem aus der exiblen Wiederverwendung ergeben, steht { wie so oft { der Nachteil, da sie einen recht hohen Entwicklungsaufwand benotigen. Dieser ist sogar noch groer als bei einer normalen Software-Bibliothek, da fur jede Eigenschaft einer Komponente bestimmte Design-Pattern eingehalten werden mussen, um die grotmogliche Interaktion und Integration von (sich im Grunde vollig unbekannten) Objekten zu erreichen. Genauer fuhre ich die Design-Pattern und die Verwendung der Hilfsklassen hier nicht aus, verweise aber auf das Tutorial von Sun (Campione et al., 1998) sowie einen Seminarvortrag (von Toschanowitz, 1999) in dem ich zum ersten mal Naheres uber JavaBeans erfahren habe. 7.9 XML Die Implementierungen der Robot Java-Interfaces, die einen konkreten Roboter fur einen bestimmten Steuerungsalgorithmus zuganglich machen, kapseln 7.10. PERSISTENZ 133 haug nur statische Informationen u ber den jeweiligen Roboter. So wird dort z.B. festgelegt, welche Klassen fur die Aktuatoren und Sensoren an welchen Port angeschlossen sind und wie diese konguriert werden sollen. Naturlich konnen auch noch weitere Parameter fur den Algorithmus enthalten sein, wie das z.B. bei der gezeigten Implementierung fur NormalizedDriveTrain der Fall ist. Dort wurde die Zeit, die der Roboter braucht, um einen Meter weit zu fahren bzw. eine ganze Umdrehung durchzufuhren, in der Implementierung des Java-Interface NormalizedDriveTrainSimpleAlgorithm.Robot gespeichert. Solche Implementierungen sollten aber eigentlich auch automatisch generierbar sein, wenn die entsprechenden Informationen in einem entsprechenden Format gespeichert sind { zum Beispiel in XML. Am elegantesten stelle ich mir eine Losung vor, in der man den Roboter grasch zusammenklickt und konguriert. Ob die so erstellte Information dann in XML oder wie bei JavaBeans ublich, als Strom von persistenten Objekten, gespeichert wird { oder in einem ganz anderen Format { beruht letzten Endes auch auf der Frage, inwieweit es Tools gibt, die einen bei der Verwendung des jeweiligen Formats unterstutzen. Fur JavaBeans und XML gibt es bereits grasche Editoren und insbesondere XML strebt an, durch eine immer gleiche Syntax, das Format der Zukunft fur die Speicherung und den Austausch von Dokumenten zu werden. 7.10 Persistenz Wenn etwas in einem Editor erstellt wurde, dann mochte man diese Arbeit auch speichern konnen und zu einem spateren Zeitpunkt auf einem anderen Rechner weiter be- bzw. verarbeiten. Im Falle von Beans sind das Objekte sowie deren Konguration und Abhangigkeiten untereinander, die gespeichert werden mussen. Sie zusammen bilden z.B. das Programm, welches man grasch erstellt hat. Da die Beans im Editor echte "lebendige" Objekte sind, kann man sie dort, noch wahrend man mit ihnen eine Applikation erstellt, gleich richtig verwenden. Jede Konguration und Verbindung die man erstellt, kann sofort ausprobiert werden, so da das Programm, bzw. der Programmteil, den man im Editor erstellt, dort im Grunde schon lauft. Nun konnte man zwar im Prinzip alle Arbeitsschritte, die zu diesem "Bean-Dokument" gefuhrt haben, abspeichern und immer wiederholen, wenn man dieses "Dokument" braucht, man kann aber auch den aktuellen Zustand der Objekte abspeichern und dann spater genau so restaurieren, wie er vorher im Editor war. Letzteres bezeichnet man mit Persistenz. Die Notwendigkeit, da JavaBeans persistent (serialisierbar) sein mussen, ist der hauptsachliche Grund dafur, da die Objekte von ART (noch) keine Beans sind. Wie eine Serialisierung von Objekten, die einen Roboter reprasentieren, uberhaupt aussehen kann, mu noch untersucht werden. Da diese RoboterObjekte z.B. von zusatzlicher Hardware abhangen, die nicht auf jedem Computer gleich (bzw. unter Umstanden gar nicht) vorhanden ist, mu bei der Deserialisierung irgendwie die Verbindung zur Roboter-Hardware neu hergestellt werden. 134 7. ZUSAMMENFASSUNG Inwieweit sich eine Serialisierung von Roboter-Objekten umsetzen lasst und wieviel zusatzliche (Benutzer-)Information dafur erforderlich ist, wurde im Rahmen dieser Arbeit nicht weiter verfolgt. Die Design-Pattern zum Zugri auf Events und Properties wurden in ART aber an den meisten Stellen bereits berucksichtigt, so da bereits eine gute Grundlage zur Entwicklung echter JavaBeans fur Roboter geschaen wurde. Anhang A Installation des Abstract Robot Toolkit Dieser Abschnitt gibt eine Schritt-fur-Schritt Ubersicht uber die notwendigen Schritte und Voraussetzungen, damit das Abstract Robot Toolkit verwendet werden kann. Java ART braucht Java um lauahig zu sein. ART wurde vor allem mit dem jdk1.3 von IBM unter Linux entwickelt. Auerdem wurde es auf die grundsatzliche Lauahigkeit in Verbindung mit dem jdk1.2.2 von Sun unter Linux und dem jdk1.3 von IBM unter Windows getestet. Java Development Kits oder Java Runtime Environments fur verschiedene Plattformen in unterschiedlichen Versionen bekommt man z.B. unter http://java.sun.com/j2se/ oder http://www.ibm.com/developerworks/java/jdk/index.html In Bezug auf die Installation mu deren eigene Dokumentation konsultiert werden. Das jdk1.3 von IBM wird fur ART empfohlen, denn vor allem unter Windows kann man so am einfachsten Schwierigkeiten mit den seriellen Ports umgehen. ART beruht auf dem Java Communications API in Bezug auf die Ansteuerung der seriellen Schnittstelle. Dieses API ist nicht in jeder JavaInstallation enthalten, man mu eine Version wahlen die zum installierten Java passt. Fur das jdk1.3 von IBM kann man eine Implementierung des CommAPI an der gleichen Stelle bekommen wie das jdk selbst: JavaComm http://www.ibm.com/developerworks/java/jdk/index.html Fur Windows und Solaris bietet Sun eine Implementierung an: http://java.sun.com/products/javacomm/index.html Fur Linux (und wahrscheinlich auch Windows) gibt es eine freie Implementation: http://www.rxtx.org/ 135 136 ANHANG A. INSTALLATION DES ABSTRACT ROBOT TOOLKIT Auch hier wird fur die Installation auf die in den Paketen enthaltene Dokumentation verwiesen. ART Das Archiv art.jar enthalt die bereits compilierten Klassendateien und ist auf der CD zu dieser Arbeit enthalten. Neuere Versionen sind uber das Internet (http://www.jaetzold.de/art/) zu bekommen. Unter der Voraussetzung, da Java ab Version 1.2 und das Java Communications API installiert sind, mu man, um ART verwenden zu konnen, im Prinzip nichts weiter tun als das Archiv art.jar in den Klassenpfad aufzunehmen. Man mu jedoch darauf achten, da die property de.jaetzold.art. RobotInterfaceFactory.platforms die gewunschte Liste von Klassennamen fur die Treiber-Implementierungen enthalt. Wenn die Datei de. jaetzold.art.properties im aktuellen Pfad vorhanden ist wird versucht die entsprechende property aus dieser Datei zu laden. Eine Version dieser Datei bendet sich an den gleichen Stellen wie auch art.jar. Ansonsten hat man noch die Moglichkeit die System-Property de.jaetzold. art.RobotInterfaceFactory.platforms z.B. beim Start der Java-Maschine uber den Parameter -D zu setzen. Die Quelldateien zu ART sind ausgepackt im Verzeichnis code/art auf der CD oder als Archiv im Verzeichnis archive zu nden. Auch von den Quelldateien werden neuere Versionen u ber http://www.jaetzold. de/art/ zum download bereit stehen. Es existiert ein makele, dessen Pfade per Hand an das jeweilige System, auf dem u bersetzt werden soll, angepasst werden mussen. Roboter Momentan kann man mit ART den gelben "Brick", manchmal auch "RCX" genannt, aus dem Lego-Mindstorms Robotics Invention System und das Intelligent Interface von Fischertechnik steuern. Fur die Steuerung des Brick ist der Infrarot-Tower in der Variante mit einem seriellen Anschluss notig. Ob und wann weitere Systeme unterstutzt werden, ist nicht bekannt. Uber http://www.jaetzold.de/art/ werden diese gegebenenfalls bekannt gemacht. Sonst Im Allgemeinen wird man auch die Dokumentation zu ART benotigen, siehe dazu Abschnitt A.2 auf der nachsten Seite. A.1 Installation der Beispiele Die Beispiele fur die Systeme aus Kapitel 3 benden sich in entsprechend benannten Unterverzeichnissen auf der CD zu dieser Arbeit. Sie sind nicht allgemein im Internet verfugbar sondern nur auf Anfrage. Sie konnen mit dem jeweiligen Programmiersystem direkt geladen, bzw. verwendet werden. Die ART-Beispiele aus den Kapiteln 5 und 6 benden sich ebenfalls auf der CD zu dieser Arbeit. Sie liegen im Verzeichnis code/art/examples. Alternativ kann man diese auch aus dem WWW laden (http://www.jaetzold.de/art/) oder als gepacktes Archiv dem Verzeichnis archive auf der CD entnehmen. A.2. KLASSENDOKUMENTATION 137 Die ART-Beispiele erfordern nicht mehr Installation als andere Java-Klassen auch, vorausgesetzt, da ART bereits korrekt lauft. Die Klassen mussen ubersetzt und uber den Klassenpfad erreichbar sein. A.2 Klassendokumentation Einerseits stellt das vorliegende Dokument eine Dokumentation von ART dar, doch existiert auch eine direkte JavaDoc-Dokumentation der Klassen von ART. Sie ist auf der CD u ber die Datei java/index.html erreichbar. Zusatzlich ist sie auch im Verzeichnis archive archiviert. Eine druckbare Postscript- und eine PDF-Version der wichtigsten packages stellt die Datei package-docs.ps, bzw. package-docs.pdf dar. Sie wurde mit dem LATEX-doclet von 2C Technologies, Inc. generiert (http://www.c2-tech. com/java/TexDoclet/). Im Zusammenhang mit neueren Versionen von ART kann die entsprechende neue Dokumentation naturlich auch wieder u ber die Web-Seite http://www. jaetzold.de/art/ bezogen werden. 138 ANHANG A. INSTALLATION DES ABSTRACT ROBOT TOOLKIT Anhang B Troubleshooting Dieses Kapitel stellt die haugsten Fragen bei Problemen in der Verwendung von ART zusammen und gibt Antworten. Naturlich konnen nicht alle Fragen und Probleme vorausgesehen werden. Taucht eine wichtige Frage bzw. eine Antwort hier nicht auf, kann man unter http://www.jaetzold.de/art/ nach einer Antwort suchen. Dort wird dieser Abschnitt Stuck fur Stuck vervollstandigt. Die Antworten hier sind nicht erschopfend, sondern sollten nur helfen, die Ursache fur ein Problem zu erkennen. Es kann unter Umstanden erforderlich sein, um z.B. zu verstehen was ein Klassenpfad ist, weiter Dokumentationen zu Rate zu ziehen. Fur weitere Fragen und Anregungen steht der Autor unter der e-mail [email protected] zur Verfugung. Fur jeden Hinweis im Zusammenhang mit dieser Arbeit und dem Abstract Robot Toolkit an diese Adresse ist er dankbar. B.1 Exceptions de.jaetzold.util.NotConnectedException: No driver selected. Check value of property de.jaetzold.art.RobotInterfaceFactory. platforms Kommt diese Exception konnte die RobotInterfaceFactory keine Treiberklassen laden, weil die property de.jaetzold.art.RobotInterfaceFactory. platforms leer ist. Entweder man setzt diese property explizit, z.B. beim Start der Java-Maschine u ber die Option -D, oder erstellt eine Datei de.jaetzold. art.properties, die u ber den aktuellen Pfad erreichbar ist (siehe auch Ab- schnitt 6.5 auf Seite 120). Eine Version dieser Datei mit den aktuellen Treibern bekommt man uberall dort, wo man auch ART herbekommt, also z.B. unter http://www.jaetzold.de/art/. Wenn diese Exception mit einer anderen Meldung kommt, dann stimmt etwas mit der Verbindung zwischen einem Software-Sensor, seinem Peer und dem zugehorigen RobotInterface nicht. Es kann auch sein, da der Kontakt zur Interface-Hardware unterbrochen wurde. Die jeweilige Meldung sollte einen Anhaltspunkt fur die genaue Ursache liefern. 139 140 ANHANG B. TROUBLESHOOTING java.lang.NoClassDefFoundError: <irgendein Klassenname> Das liegt in den allermeisten Fallen an einem falschen Klassenpfad. Den Klassenpfad setzt man z.B. mit der Option -classpath beim Aufruf der JavaMaschine. Alternativ kann man auch die System-Variable CLASSPATH verwenden. Der Kassenpfad mu alle jar-Archive enthalten, sowie alle Verzeichnisse in denen Java-Klassen in den ihren package-Namen entsprechenden Unterverzeichnissen enthalten sind. Es kann auch sein, da der Klassenpfad zwar richtig ist, aber die Klasse noch gar nicht kompiliert wurde. Es soll auch vorkommen, da man sich bei der Angabe des Klassennamens vertippt . . . Zu guter Letzt kann es auch sein, da das Java Communications API nicht richtig installiert wurde. In diesem Fall weist der in der Fehlerausgabe angegebene Klassenname auf die Ursache hin. java.lang.NullPointerException java.lang.ArrayIndexOutOufBoundsException Dies kann ein Hinweis darauf sein, da keine Interface-Hardware gefunden wurde. Siehe dazu die Erklarungen in Abschnitt B.3. B.2 Geschwindigkeit Probleme mit der Geschwindigkeit konnen verschiedene Ursachen haben. Einige wurden z.B. in Abschnitt 7.2 auf Seite 125 beschrieben. Fur viele Geschwindigkeitsprobleme gibt es momentan keine einfache Losung. Die besten Erfahrungen wurden aber mit dem jdk1.3 von IBM und der zugehorigen CommAPIImplementierung gemacht, daher wird empfohlen es erst einmal damit zu versuchen. Es sind manchmal durchaus Steigerungen um den Faktor 10 moglich. B.3 Automatische Erkennung Es kann sein, da kein Hardware-Interface gefunden wird, bzw. da das falsche Hardware-Interface gefunden wird. In diesem Fall kann es helfen, den Strom der Interface-Hardware abzuschalten. Vor allem das Intelligent Interface kann in einen Zustand geraten, in dem es nicht mehr antwortet und aus dem es nur durch einen solchen Reset wieder herauszubekommen ist. Um nur ein ganz bestimmtes Interface zu bekommen, kann man bei RobotInterfaceFactory bestimmte Parameter angeben. Damit kann man verhindern, da die automatische Erkennung nach bestimmten Hardware-Interfaces sucht. Die Parameter sind in der Klassendokumentation zu RobotInterfaceFactory und in Abschnitt 5.2 auf Seite 70 beschrieben. Abbildungsverzeichnis 2.1 Schema von Trusty . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.2 Schema von LiSe . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.3 Ariadne aus LEGO und Fischertechnik . . . . . . . . . . . . . . . 10 3.1 3.2 3.3 3.4 3.5 3.6 Synchronisation in LLWin (Initialisierung) Semaphore mit LLWin . . . . . . . . . . . Synchronisation in LLWin (Subsumption) Positionsmessung in LLWin . . . . . . . . RIS/RCX-Code . . . . . . . . . . . . . . . Robolab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 16 17 18 20 21 4.1 4.2 4.3 4.4 4.5 4.6 4.7 Regelkreis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schaltplan von Tutebot . . . . . . . . . . . . . . . . . . . . . . Tutebot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das grundsatzliche Zusammenspiel der Komponenten des ART Hierarchie der Peers . . . . . . . . . . . . . . . . . . . . . . . . Hierarchie der Frontend-Klassen . . . . . . . . . . . . . . . . . Model, View, Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 37 38 44 47 52 56 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 Einfacher fahrender Roboter aus Lego oder Fischertechnik . Design-Pattern fur die Programmierung mit ART . . . . . . Testfenster fur DriveTrain . . . . . . . . . . . . . . . . . . Design von NormalizedDriveTrain . . . . . . . . . . . . . Design von Trusty . . . . . . . . . . . . . . . . . . . . . . . Design von SubsumptionTrusty . . . . . . . . . . . . . . . . Impulsrad von Fischertechnik . . . . . . . . . . . . . . . . . Testfenster fur die Komponenten von SubsumptionAriadne . . . . . . . . 62 72 73 79 81 85 91 105 . . . . . . . . . . . . . . . . 6.1 Screenshot der Oberache von AWTRobotInterface . . . . . . . . 108 6.2 Die Trusty-Steuerung und das AWTRobotInterface . . . . . . . 122 141 142 ABBILDUNGSVERZEICHNIS Literaturverzeichnis Baum, D. Apress. , (2000). Dave Baum's denitive guide to LEGO MINDSTORMS. Baum, D. , (2001). NQC Programmers Guide, Version 2.3. http://www. enteract.com/~dbaum/nqc/doc/NQC_Guide.pdf. , (2000). Extreme MINDSTORMS: An Advanced Guide to LEGO MINDSTORMS. Apress. Baum, D., M. Gasperi, R. Hempel und L. Villa Bollella, G., J. Gosling, B. M. Brosgol, P. Dibble, S. Furr, D. Har- , (2000). The Realtime Specication for Java. Addi- din und M. Turnbull son Wesley. , (2000). Echtzeitprogrammierung in JAVA : Automatisieren im Millisekundenbereich. Publicis MCD Verlag, erste Au. Brich, P., G. Hinsken und K.-H. Krause , (1985). A Robust Layered Control System for a Mobile Robot. http://www.ai.mit.edu/people/brooks/papers/AIM-864.pdf. Brooks, R. A. , (1998). The Java Tutorial. Sun Microsystems. http://java.sun.com/docs/books/tutorial/. Campione, M., K. Walrath, A. Huml et al. , (1965). Co-operating Sequential Processes. In F. Genuys (Hrsg.), Programming Languages. London : Academic Press. Dijkstra, E. W. , (2002). Building Robots with Lego Mindstorms : The Ultimate Tool for Mindstorms Maniacs! Syngress Publishing, Inc. Ferrari, M. und G. Ferrari , (1999). Java in a nutshell. O'Reilly, dritte Au. Flanagan, D. , (1995). Design Patterns : Elements of Reusable Object-Oriented Software. Addison Wesley. Gamma, E., R. Helm, R. Johnson und J. Vlissides , (1993). Nextstep Programming : Step One, Object-Oriented Applications. Springer-Verlag New York. Garfinkel, S. L. und M. K. Mahoney , (1998). Mindstorms Sensor Input. http://www.plazaearth. com/usr/gasperi/lego.htm. Gasperi, M. , (1983). Smalltalk-80 : The Language and its Implementation. Addison Wesley, erste Au. Goldberg, A. und D. Robson 143 LITERATURVERZEICHNIS 144 , (2000). The Java Language Gosling, J., B. Joy, G. Steele und G. Bracha Specication. Addison Wesley, zweite Au. , (1999). JavaComm, Das Java Communications API. http:// www.vorlesungen.uos.de/informatik/javaapi/javacomm/index.html. J atzold, S. J atzold, S. , (2002). Programming Robots with Java. http://www.jaetzold. de/talks/2002-01-rit/. , (1998). Mobile Robots : Inspiration to Implementation. A.K. Peters Ltd., zweite Au. Jones, J. L., A. M. Flynn und B. A. Seiger , (1990). Programmieren in C. Carl Hanser Verlag Munchen Wien, zweite Au. Kernighan, B. W. und D. M. Ritchie Knudsen, J. O'Reilly. , (1999). The UnoÆcial Guide to LEGO MINDSTORMS Robots. , (1999). The Java Virtual Machine Specication. Addison Wesley, zweite Au. Lindholm, T. und F. Yellin , (1990). Learning to coordinate behaviors. http: Maes, P. und R. A. Brooks //www.ai.mit.edu/people/brooks/papers/learning.pdf. M uller, U. , (2000). FishFace Handbuch, Version 3.1. ftComputing.de/zip/fishma31.zip. http://www. , (1999). Roboter programmieren { ein Kinderspiel : Bewegt sich auch etwas in der Allgemeinbildung? Informatik Spektrum. Nievergelt, J. , (1999). Designing the legOS Multitasking Operating System : An OS for the Hitachi H8 microcontroller. Dr. Dobb's Journal. http://www. noga.de/legOS. Noga, M. L. , (1999). Mindstorms : Children, Computers, and Powerful Ideas. Basic Books, zweite Au. Papert, S. , (1981). Karel the Robot: A Gentle Introduction to The Art of Programming with Pascal. Wiley and Sons, erste Au. Pattis, R. E. , (1998). RCX Internals. http://graphics.stanford.edu/~ Proudfoot, K. kekoa/rcx/. , (1999). Oberachen benutzen und programmieren : Java und die Yellow Box. http://www.vorlesungen.uos.de/informatik/c99/ html/skript.html. Schreiner, A.-T. A.-T., (2000a). Programmieren mit Java (1.)2. http:// www.vorlesungen.uos.de/informatik/java00/html/skript/0_Preface% .htmld/index.html. Schreiner, , (2000b). Roboter programmieren. http://www. vorlesungen.uos.de/informatik/robot00/html/skript.html. Schreiner, A.-T. LITERATURVERZEICHNIS Sun Microsystems 145 , (1997). JavaBeans 1.01 Specication. http://java.sun. com/products/javabeans/docs/spec.html. , (1998). Java Communications API. http://java.sun. Sun Microsystems com/products/javacomm/. , (2001). Java 2 Platform, Standard Edition, v 1.3.1 API Specication. http://java.sun.com/j2se/1.3/docs/api/index.html. Sun Microsystems Tanenbaum, A. S. : Hanser. , (1990). Betriebssysteme - Entwurf und Realisierung. Wien , (1998). Controlling LEGO Programmable Bricks : Spirit.ocx technical reference. http://www.legomindstorms.com/sdk. The LEGO Group , (1999). JavaBeans. http://www.vorlesungen. uos.de/informatik/javaapi/javabeans/index.html% . von Toschanowitz, K. T. , (1999). Intelligent Agents. In G. Weiss (Hrsg.), Multiagent Systems : A Modern Approach to Distributed Articial Intelligence, S. 27{77. MIT Press, zweite Au. Wooldridge, M.