Mocek Thesis

1.806 Aufrufe

Veröffentlicht am

Veröffentlicht in: Technologie, Bildung
0 Kommentare
1 Gefällt mir
Statistik
Notizen
  • Als Erste(r) kommentieren

Keine Downloads
Aufrufe
Aufrufe insgesamt
1.806
Auf SlideShare
0
Aus Einbettungen
0
Anzahl an Einbettungen
4
Aktionen
Geteilt
0
Downloads
2
Kommentare
0
Gefällt mir
1
Einbettungen 0
Keine Einbettungen

Keine Notizen für die Folie

Mocek Thesis

  1. 1. Christian Mocek Erweiterung des CASE-Werkzeugs DAVE um einen Code-Generator für LEGO Mindstorms Diplomarbeit 7. Mai 2006 Gutachter: Universität Dortmund Lehrstuhl für Software-Technologie Prof. Dr. Ernst-Erich Doberkat Baroper Straße 301 Dipl.-Inform. Jörg Pleumann 44227 Dortmund
  2. 2. II
  3. 3. Vorwort Dass mir die Leidenschaft meiner Kindheit am Ende des Studiums begegnet, hätte ich mir niemals träumen lassen. Schon als kleines Kind war ich von LEGO, insbesondere LEGO Technik, begeistert und freue mich auch heute immer wieder, wenn ich mit meinem Neffen die kleinen bunten Steine zusammensetzen kann. Auf der Suche nach einem Thema für meine Diplomarbeit stieß ich dann auf eins, welches mich vom ersten Augenblick an interessierte: Die Steuerung von LEGO Mindstorms-Robotern mit Hil- fe von Zustandsdiagrammen. Nach einigen Vorgesprächen mit meinem Betreuer sowie Gutachter Jörg Pleumann und langen Überlegungen - mit zugegeben einigem Zwei- fel, ob sich dieses Unterfangen überhaupt realisieren lässt - entschied ich mich dafür, diese Herausforderung anzunehmen. Denn wer hat schon die Möglichkeit, in seiner Di- plomarbeit mit LEGO spielen zu dürfen? Natürlich von einem rein wissenschaftlichen Standpunkt betrachtet, versteht sich. So begann eine Zeit unzähliger Stunden Arbeit, einigen frustrierenden Momenten und vielen Gesprächen über das Thema mit meinem Betreuer. Doch letztlich haben sich all die Mühen gelohnt, als die ersten Roboter ih- re Aufgaben mit Hilfe von Zustandsdiagrammen lösten und die letzten Zeilen dieser Arbeit getippt waren. An dieser Stelle bedanke ich mich für die Hilfe all jener Personen, die mich in der langen Zeit in irgendeiner Weise unterstützt haben diese Arbeit zu erstellen. Zunächst bedanke ich mich bei meinen Eltern, die immer an mich geglaubt haben und denen diese Arbeit gewidmet ist. Ganz besonderer Dank gilt zudem Jörg Pleumann, welcher mir jederzeit verständnisvoll geholfen und meine häufigen Fragen geduldig beantwortet hat. Insbesondere möchte ich auch Peter Segschneider und seiner Frau Christel sowie Nina Schilf für ihre Freundschaft und moralischen Unterstützung in der langen Zeit danken. Ein weiterer Dank gilt meinen Korrekturlesern, die sich eisern auf der Suche nach Fehlern durch diesen Text gelesen haben. Herten, im Mai 2006 Christian Mocek
  4. 4. IV
  5. 5. Inhaltsverzeichnis 1 Thema der Arbeit 1 1.1 Motivation der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Zielsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.3 Geplantes Vorgehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2 Grundbegriffe 5 2.1 Zustandsdiagramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.1.1 Elemente und Semantik von Zustandsdiagrammen . . . . . . . . 5 2.1.2 Implementierungsansätze . . . . . . . . . . . . . . . . . . . . . . 11 2.2 LEGO Mindstorms Roboter . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.2.1 Aufbau der Hardware . . . . . . . . . . . . . . . . . . . . . . . . 14 2.2.2 Programmierung der LEGO-Roboter . . . . . . . . . . . . . . . . 15 3 Lösungsansatz 17 3.1 Zielplattform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2 Ansatzpunkte zur Integration in DAVE . . . . . . . . . . . . . . . . . . 18 3.2.1 Die Simulationsmaschine . . . . . . . . . . . . . . . . . . . . . . . 18 3.2.2 Wahl der zu verwendenden Datenstruktur . . . . . . . . . . . . . 19 3.3 Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.4 Gesamtaufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4 Das Statechart-Framework 21 4.1 Konzeptioneller Aufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.1.1 Zeitgesteuerte Transitionen . . . . . . . . . . . . . . . . . . . . . 22 4.1.2 Segmentierte Transitionen . . . . . . . . . . . . . . . . . . . . . . 23 4.1.3 Hierarchie und Nebenläufigkeit . . . . . . . . . . . . . . . . . . . 25 4.2 Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.2.1 Speicherplatzoptimierungen . . . . . . . . . . . . . . . . . . . . . 31 4.2.2 Berechnung des LCA . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.2.3 Verwendung des Frameworks . . . . . . . . . . . . . . . . . . . . 33 4.3 Unittests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5 Integration der RCX-Elemente in Zustandsdiagramme 41 5.1 Startsymbol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 5.2 Grundregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 5.3 Initialisieren der Sensoren . . . . . . . . . . . . . . . . . . . . . . . . . . 43 5.4 Verarbeiten eines Ereignisses . . . . . . . . . . . . . . . . . . . . . . . . 44 5.5 Aktionen und Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . 44
  6. 6. Inhaltsverzeichnis VI 5.5.1 Ausgänge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 5.5.2 Sensoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 5.5.3 Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 5.5.4 LC-Display . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 6 Steuerung des RCX mit Hilfe des Frameworks 49 6.1 Ausgänge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 6.1.1 Aktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 6.1.2 Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 6.2 Sensoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 6.2.1 Notwendige Optimierungen . . . . . . . . . . . . . . . . . . . . . 51 6.2.2 Ereignisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 6.2.3 Aktionen und Bedingungen . . . . . . . . . . . . . . . . . . . . . 53 6.3 Variablen und einfache Arithmetik . . . . . . . . . . . . . . . . . . . . . 54 7 Codegenerierung 57 7.1 Das XML-Format von DAVE . . . . . . . . . . . . . . . . . . . . . . . . 57 7.2 Verarbeitung der XML-Datei . . . . . . . . . . . . . . . . . . . . . . . . 59 7.2.1 Implementierung in Java . . . . . . . . . . . . . . . . . . . . . . . 59 7.2.2 Freemarker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.2.3 Transformationen mittels XSL und XPath . . . . . . . . . . . . . 60 7.3 Die Codegenerierung im Detail . . . . . . . . . . . . . . . . . . . . . . . 62 7.3.1 Umsetzung mittels XSLT . . . . . . . . . . . . . . . . . . . . . . 62 7.3.2 Interpretieren der EBNF-Regeln . . . . . . . . . . . . . . . . . . 65 7.3.3 Aktivieren und Deaktivieren der Sensoren . . . . . . . . . . . . . 66 7.3.4 Aufbau des Zustandsdiagramms . . . . . . . . . . . . . . . . . . . 68 7.3.5 Aktionen und Bedingungen . . . . . . . . . . . . . . . . . . . . . 73 8 Anbindung an DAVE mittels Ant 75 9 Anwendungsbeispiele 79 9.1 Aufgaben für die LEGO-Testunterlage . . . . . . . . . . . . . . . . . . . 79 9.1.1 Aufgabe 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 9.1.2 Aufgabe 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 9.1.3 Aufgabe 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 9.2 Eine Sortieranlage für LEGO-Steine . . . . . . . . . . . . . . . . . . . . 83 9.2.1 Aufgabe 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 9.2.2 Aufgabe 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 9.2.3 Aufgabe 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 10 Fazit 89 11 Ausblick 91
  7. 7. Inhaltsverzeichnis VII A Testfälle 93 A.1 Einfache Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 A.2 Hierarchische Zustände . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 A.3 Nebenläufige Zustände . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 B Baupläne 101 B.1 Ein mobiler Roboter für die Testunterlage . . . . . . . . . . . . . . . . . 101 B.1.1 Linker Motorblock . . . . . . . . . . . . . . . . . . . . . . . . . . 101 B.1.2 Rechter Motorblock . . . . . . . . . . . . . . . . . . . . . . . . . 104 B.1.3 Zusammenbau der Komponenten . . . . . . . . . . . . . . . . . . 106 B.2 Eine Sortiermaschine für LEGO-Steine . . . . . . . . . . . . . . . . . . . 110 B.2.1 Klappe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 B.2.2 Förderband . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 B.2.3 Unterbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 B.2.4 Zusammenbau der Komponenten . . . . . . . . . . . . . . . . . . 119 B.2.5 Kiste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
  8. 8. Inhaltsverzeichnis VIII
  9. 9. Abbildungsverzeichnis 1.1 Das CASE-Werkzeug DAVE . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.2 Verwendung von Zustandsdiagrammen in eingebetteten Systemen . . . . 3 2.1 Umsetzung eines endlichen Automaten in prozeduralen Quelltext . . . . 12 2.2 Umsetzung des Beispielautomaten mit Hilfe des State-Patterns . . . . . 13 2.3 Beispiel der Abbildung von Hierarchie und Nebenläufigkeit in die Klas- senhierarchie objektorientierter Sprachen auf Basis des State-Patterns . 13 2.4 Klassendiagramm des FSM-Frameworks von Bosch und van Gurp . . . . 14 2.5 Der RCX-Baustein . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.1 Aufbau des Gesamtkonzepts . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.1 Konzeptioneller Aufbau des Frameworks ohne Berücksichtigung hierar- chischer und nebenläufiger Zustände . . . . . . . . . . . . . . . . . . . . 22 4.2 Erweiterung der Klassen State und Transition für zeitgesteuerte Tran- sitionen, sowie Einführung der Klasse PseudoState . . . . . . . . . . . . 23 Verarbeitung segmentierter Transitionen für den Fall x = 0 . . . . . . 4.3 . 24 4.4 Ereignisverarbeitung auf dem Oder-Zustand . . . . . . . . . . . . . . . . 25 4.5 Transitionsverhalten bei hierarchischen Zuständen . . . . . . . . . . . . 26 4.6 Erweiterung des Frameworks um Hierarchie . . . . . . . . . . . . . . . . 27 4.7 Ablauf der execute-Methode einer Transition . . . . . . . . . . . . . . . 28 4.8 Erweiterung des Frameworks um Nebenläufigkeit . . . . . . . . . . . . . 29 4.9 Klassendiagramm der Implementierung des Frameworks . . . . . . . . . 30 4.10 Beispieldiagramm für die Umsetzung in den Quelltext . . . . . . . . . . 35 4.11 Klassendiagramm zur Implementierung der Testfälle . . . . . . . . . . . 37 4.12 Beschreibung des Pfads, welcher beim Durchlauf durch das Diagramm zurückgelegt wird . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.13 Beispieldiagramm für einen Testfall . . . . . . . . . . . . . . . . . . . . . 39 4.14 Ablauf eines Testfalls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 5.1 Beispieldiagramm zur Verwendung der spezifizierten Syntax . . . . . . . 47 6.1 Framework-Erweiterung zur Steuerung der Ausgänge des RCX . . . . . 50 6.2 Framework-Erweiterung zur Steuerung der Sensoren des RCX . . . . . . 52 6.3 Framework-Erweiterung zum Rechnen mit dem RCX . . . . . . . . . . . 55 8.1 DAVE nach der Integration und Ausführung des Ant-Skripts . . . . . . 78 9.1 LEGO-Testunterlage für den Lichtsensor . . . . . . . . . . . . . . . . . . 79
  10. 10. Abbildungsverzeichnis X 9.2 Modell des Roboters für die Testunterlage . . . . . . . . . . . . . . . . . 80 9.3 Ablauf einer einfachen Rundfahrt auf der Testunterlage . . . . . . . . . 80 9.4 Modifizierte Testunterlage . . . . . . . . . . . . . . . . . . . . . . . . . . 81 9.5 Ablauf einer Rundfahrt auf der modifizierten Testunterlage . . . . . . . 81 9.6 Zählen der grünen Blöcke auf der Testunterlage . . . . . . . . . . . . . . 82 9.7 Zustandsdiagramm für Aufgabe 3 . . . . . . . . . . . . . . . . . . . . . . 83 9.8 LEGO-Modell der Sortieranlage für LEGO-Steine. . . . . . . . . . . . . 84 9.9 Konzept einer einfachen Sortieranlage für LEGO-Steine . . . . . . . . . 84 9.10 Aufbau des Rotationssensors . . . . . . . . . . . . . . . . . . . . . . . . 85 9.11 Sortieren verschiedener LEGO-Steine anhand ihrer Farbe . . . . . . . . 86 9.12 Erweiterung des bisherigen Sortierers . . . . . . . . . . . . . . . . . . . . 87 9.13 Sequenzielles Füllen der Kisten mit Hilfe der Sortiermaschine . . . . . . 88
  11. 11. Kapitel 1 Thema der Arbeit Da die Anforderungen an heutige Softwaresysteme immer weiter ansteigen, wird auch deren Entwicklung immer komplexer und kostspieliger. Um den Überblick über Struk- turen und Abläufe in dem System behalten zu können, ist deren Dokumentation schon in der Planungsphase enorm wichtig. Denn zum einen erleichtert dies anderen Ent- wicklern sich einzuarbeiten und zum anderen können durch solch eine Dokumentation frühzeitig Designfehler oder -mängel entdeckt werden. Die Abstraktionsebene reicht von der allgemeinen Beschreibung von Anwendungsfällen bis hin zur konkreten Im- plementierung und Beschreibung von Objektverhalten. Ein hierbei immer wichtiger gewordenes Hilfsmittel ist die von der Object Management Group eingeführte Unified Modelling Language (OMG, 2003). Aus diesem Grund ist es daher unerlässlich, die Studenten im Rahmen von Übungen und Projektarbeiten an die Modellierung von Software mittels UML heranzuführen. Da jedoch die normalen Modellierungstools für die Lehre aufgrund ihrer Komplexi- tät und des hohen Preises ungeeignet sind, wurden im Rahmen des MuSoft-Projektes (Doberkat und Engels, 2002) mehrere Werkzeuge entwickelt, welche im Funktions- umfang explizit auf die Lehre ausgelegt sind. Eines dieser Programme ist der an der Dortmunder Universität entstandene Dortmunder Automatenvisualisierer und -editor (Pleumann, 2003). DAVE (siehe Abbildung 1.1) beschränkt sich auf die Modellierung und Simulation von Zustandsdiagrammen1 - einer Verallgemeinerung von endlichen Automaten, die unter anderem um Hierarchie und Nebenläufigkeit erweitert wurden. 1.1 Motivation der Arbeit Das Ziel von DAVE ist das anschauliche Vermitteln der Syntax und Semantik von Zustandsdiagrammen. Hierbei ist eine wesentliche Komponente die Simulation: Die Studenten sind in der Lage, ihre Modelle auszuführen und somit deren Laufzeitver- halten zu beobachten. Um einen besseren Bezug zwischen der abstrakten graphischen Notation und dem Objekt, dessen Verhalten beschrieben wird, herzustellen, unter- stützt DAVE die Simulation zusätzlich durch multimediale Animationen (zum Bei- spiel eine Waschmaschine). Aufgrund dieses Bezugs von etwas Abstraktem zu et- 1 Essei angemerkt, dass sich DAVE auf die in der UML-Spezifikation beschriebene Syntax und Semantik bezieht (OMG, 2003). Diese unterscheidet sich etwas von der von Harel beschriebenen (Harel, 1987). Die wesentlichen Konzepte sind jedoch gleich.
  12. 12. 1. Thema der Arbeit 2 Abbildung 1.1.: Das CASE-Werkzeug DAVE was Anschaulichem kann den Studenten der Nutzen und die Semantik von UML- Zustandsdiagrammen besser vermittelt werden. Eine genaue Beschreibung des Werk- zeugs findet sich in (Pleumann, 2004). Umfangreiche Untersuchungen haben gezeigt, dass DAVE, und damit auch dessen didaktisches Konzept, von den Studenten mehr- heitlich positiv aufgenommen wurde (Kamphans u. a., 2004). In der Realität werden Zustandsdiagramme oft im Bereich eingebetteter Systeme zur Spezifikation und Verifikation des Systemverhaltens verwendet (siehe Abbildung 1.2), da sich Fehler nachträglich nicht oder nur zu hohen Kosten korrigieren lassen. Anima- tionen können also dazu genutzt werden, die real existierenden eingebetteten Systeme nachzuahmen, zu testen und im Rahmen der Lehre den Studierenden Aufgaben zu stellen, die an lebensnahe Anforderungen angelehnt sind. Letztlich ist diese Art der Visualisierung aber auch nur ein Versuch, die Kluft zwi- schen der abstrakten Notation von Zustandsdiagrammen und der Realität zu überwin- den, um das Lernen zu erleichtern. Daher stellt sich die Frage, ob es nicht möglich ist, den Studenten ein reales eingebettetes System zur Verfügung zu stellen, an dem sie die praktische Anwendung von Zustandsdiagrammen trainieren können. Ein kostengünsti- ger Ansatz ist die Verwendung des Mindstorms Robotics Invention System 2.0 (LEGO, 2005), welches von der Firma LEGO im Rahmen ihrer Produktreihe LEGO Technik eingeführt wurde. Dieses System verfügt über einen programmierbaren Baustein und kann dazu genutzt werden einfache Roboter zu bauen. Die Programmierung erfolgt über eine proprietäre graphische Sprache, welche für Kinder ausgelegt ist. Alterna-
  13. 13. 1.2. Zielsetzung 3 (a) Zwei Sensoren tasten den Boden ab und (b) Das zugehörige Zustandsdiagramm des melden dem Roboter, wenn die Fahrbahn Roboters. Die beiden Motoren reagieren erfasst wird auf die Farbe des Untergrunds, welche die Sensoren melden Abbildung 1.2.: Beispiel für die Verwendung von Zustandsdiagrammen in eingebetteten Systemen: Die Aufgabe besteht darin, dass ein Roboter einem vorgegebenen Weg folgt tiven, welche eine klassische Programmierung zulassen, finden sich im Open-Source- Bereich und haben als Zielgruppe Programmierer, die eine vollständige Kontrolle über den Baustein erhalten wollen. Neben der praktischen Anwendung bietet das System einen für die Lehre sehr wich- tigen Zusatzfaktor, nämlich die Steigerung der Lernmotivation. Die LEGO-Roboter fügen eine spielerische Komponente zu den Lernaufgaben hinzu. Durch jene und die Möglichkeit, dass die Studenten selbst etwas kreieren können, kann die intrinsische Motivation gesteigert werden. Bei den Lernenden erhöht sich durch Neugier, Spaß und Interesse die Motivation, die Übungen zu erledigen (Heidelberg, 2000). Ein weiterer Vorteil ist die Förderung der Gruppenarbeit: Es können Aufgaben gestellt werden, die vom Umfang her komplex sind und in Gruppen gelöst werden müssen. Ein Beispiel hierfür ist die Realisierung eines Hochregallagers in einer Fallstudie an der Universi- tät Paderborn. Die dort gemachten Erfahrungen zeigen, dass der Einsatz der LEGO- Roboter von den Studenten mehrheitlich positiv aufgenommen wurde (Magenheim und Scheel, 2004). 1.2 Zielsetzung Da die LEGO-Roboter didaktisch erfolgreich eingesetzt wurden, sie ein einfaches ein- gebettetes System sind und die Verbindung zu Zustandsdiagrammen naheliegend ist, bietet es sich an Mindstorms in der Lehre einzusetzen und in ein didaktisches Werkzeug zu integrieren. Aufgrund der Tatsache, dass die Syntax und Semantik von Zustandsdia- grammen und nicht die Programmierung von LEGO-Robotern im Vordergrund stehen soll, muss nach einer Lösung gesucht werden, die es ermöglicht die Programmierung
  14. 14. 1. Thema der Arbeit 4 vollkommen transparent zu gestalten. Die vorliegende Diplomarbeit behandelt die Inte- gration der LEGO-Roboter in das didaktische Werkzeug DAVE unter Berücksichtigung eben genannter Prämisse. Die Studenten sollen in der Lage sein, ihre Zustandsdiagram- me in DAVE zu modellieren und über diese das Verhalten der Roboter zu spezifizieren. Um eine höchstmögliche Akzeptanz seitens der Studenten zu erreichen, ist es eben- falls erforderlich, dass die Syntax zur Steuerung der Roboter-Elemente sehr einfach zu erlernen ist und sich problemlos in die Notation von Zustandsdiagrammen integrieren lässt. Der zusätzliche Lernaufwand muss also möglichst minimal sein. Bei näherer Betrachtung lässt sich erkennen, dass aus den in DAVE gegebenen Infor- mationen über das modellierte Zustandsdiagramm ein Quellcode generiert und kom- piliert werden muss, welcher als ausführbares Programm in den Roboter eingeladen werden kann. Um dieses Ziel zu erreichen, müssen im Rahmen der Diplomarbeit die folgenden Fragen geklärt werden: • Welche Elemente von Zustandsdiagrammen sollen unterstützt werden? • In welcher Relation stehen die gewählten Elemente von Zustandsdiagrammen zu den elektro-mechanischen Bauteilen der LEGO-Roboter und wie lässt sich diese Relation im Diagramm darstellen? • Wie können Zustandsdiagramme und deren Semantik in Quellcode abgebildet werden? • Welche Daten stellt DAVE zur Verfügung und wie können diese verarbeitet wer- den, um daraus den benötigten Quellcode zu generieren? 1.3 Geplantes Vorgehen Die Arbeit gliedert sich in drei Teile. Im ersten Teil werden die notwendigen Grundbe- griffe zum Thema Zustandsdiagramme und dem LEGO Mindstorms Invention System aufgearbeitet, damit eine Integration von LEGO in DAVE geplant und umgesetzt wer- den kann. Anschließend werden im zweiten Teil die Konzepte und deren Umsetzung erläutert. Kapitel 3 beschreibt das gewählte Gesamtkonzept der Kopplung beider Wel- ten. Auf dem gewählten Ansatz aufbauend werden in den darauf folgenden Kapiteln 4 bis 8 die einzelnen Teile des Konzepts und deren Umsetzung behandelt. In Kapitel 9 des dritten Teils werden exemplarisch einige praktische Aufgaben für den Übungs- betrieb vorgestellt. Zum Abschluss der Arbeit werden rückblickend die Ergebnisse zusammengefasst und ein Ausblick auf mögliche Erweiterungen gegeben.
  15. 15. Kapitel 2 Grundbegriffe Wie im vorherigen Abschnitt bemerkt wurde, ist es notwendig einige Grundbegriffe aufzuarbeiten, ohne die die notwendigen Designentscheidungen zur Realisierung der Aufgabe nicht getroffen werden können. Dieses Kapitel ist in zwei Teile aufgeteilt, wel- che die wichtigen Themengebiete behandeln. In Abschnitt 2.1 wird näher auf die für die Arbeit relevanten Elemente von Zustandsdiagrammen eingegangen und es werden verschiedene Ansätze zu deren Implementierung vorgestellt. Abschnitt 2.2 widmet sich dem LEGO Mindstorms Robotics Invention System und geht zunächst auf die techni- schen Details der Hardware ein, bevor verschiedene Möglichkeiten der Programmierung vorgestellt werden. 2.1 Zustandsdiagramme Zustandsdiagramme wurden erstmals 1987 von David Harel erwähnt und basieren auf einer Verallgemeinerung der Konzepte von endlichen Automaten (Harel, 1987). Sie können als gerichtete Graphen angesehen werden, die das Verhalten eines Objekts be- schreiben und komplementär zu Sequenz- und Kollaborationsdiagrammen sind, welche die Interaktion verschiedener Objekte darstellen. Die beiden wesentlichen Erweiterun- gen seitens Harel waren die Einführung von Hierarchie und Nebenläufigkeit, auf die im weiteren Verlauf noch eingegangen wird. Von der OMG wurden die vorgestellten Konzepte aufgegriffen und für die Verwendung in der UML leicht modifiziert. Im Folgenden werden die Elemente und deren Semantik behandelt, wie sie in der Version 1.5, einer Untermenge der aktuellen Version 2.0, der UML-Spezifikation (OMG, 2003) aufgeführt sind und für die eine Unterstützung in DAVE und LEGO Mindstorms vorgesehen ist. Eine genauere Beschreibung findet sich in (Hitz und Kappel, 2003). 2.1.1 Elemente und Semantik von Zustandsdiagrammen Zustände und Aktionen Aufgrund der Graphenstruktur besitzen die Diagramme Knoten und (gerichtete) Kan- ten. Die Knoten repräsentieren die Zustände, die das Objekt einnehmen kann, während die Kanten Zustandswechsel, die so genannten Transitionen, darstellen. Zustände wer- den im Diagramm durch Rechtecke mit gerundeten Ecken dargestellt und können mit
  16. 16. 2. Grundbegriffe 6 einem, den Zustand beschreibenden, Namen und Aktionen versehen werden. Hierbei werden im Rahmen der Diplomarbeit zwei Typen von Zustandsaktionen unterschie- den: Die entry-Aktion wird ausgeführt, sobald der Zustand aktiviert, d. h. erreicht wird. Analog hierzu existiert die exit-Aktion, welche beim Verlassen des Zustands ausgeführt wird. Zusätzlich existiert noch die do-Aktion, die nach der entry-Aktion ausgeführt wird, während der Zustand aktiv ist. Für diese ist keine Unterstützung vorgesehen. Sie wird an dieser Stelle nur der Vollständigkeit halber aufgeführt. Die Aktionen haben keine spezielle Form und können im Prinzip beliebiger Natur sein. Sprich es ist möglich, Pseudocode ebenso zu verwenden wie konkrete Methodenaufru- fe in einer Programmiersprache oder Ausgaben von „Ereignissen“. Neben den normalen Zuständen beschreibt die UML noch eine weitere Art von Zustandstypen, die so genannten Pseudozustände. Sie dienen einzig der Modellierung und sind keine echten Zustände des Objekts. Daher kann ihnen keine Aktion zugeordnet werden bzw. das Objekt in ihnen verweilen. Damit bekannt ist, welcher Zustand bei Beginn der Bearbeitung aktiviert werden muss, wird dem Diagramm ein Pseudozustand, der so genannte Startzustand zugewiesen. Für diesen gilt, dass von ihm nur Transitionen abgehen dürfen. Analog zum Startzustand existiert der Endzustand. Dieser ist ein normaler Zustand, der jedoch keine Aktionen beinhalten kann. Für diesen gilt, dass keine Transition den Zustand verlassen darf. Zustandsübergänge Ereignisse Der Wechsel zwischen zwei Zuständen wird in der Regel über ein Er- eignis angestoßen. Dies ist laut OMG (2003) „das Ergebnis einer beliebigen Aktion innerhalb des Systems oder in der das System umfassenden Umgebung“. Obwohl die Form eines Ereignisses nicht explizit festgelegt ist, unterscheidet die UML zwischen vier verschiedenen Ereignistypen, von denen hier nur die beiden für die Arbeit rele- vanten erwähnt werden sollen: Das SignalEvent umfasst den zuletzt genannten Fall, dass das Ereignis durch eine Aktion außerhalb des Systems generiert wurde, während das TimeEvent auf einer Aktion innerhalb des Systems beruht. Ereignisse werden nur vom jeweils aktuell aktiven Zustand verarbeitet. Gehen von einem Zustand nur mit Ereignissen markierte Transitionen ab, so ver- weilt das Objekt so lange in dem Zustand, bis ein Ereignis auftritt, welches einen Zustandsübergang auslöst. Ereignisse, welche im aktuellen Zustand keiner Transition zugeordnet sind, gehen verloren. Für Übergänge ohne Ereignismarkierung gilt die Be- endigung aller Aktionen des Zustands als Trigger. Tritt ein Ereignis auf, wird die derzeit aktive Aktion sofort unterbrochen und der Zustand verlassen. Bedingungen und Aktionen Neben einem Ereignis kann einer Transition eine Bedingung und eine Aktion zugeordnet werden. Erstere wird mit- tels der Schreibweise [y] und letztere durch /z an der Transition kenntlich gemacht. Bedingungen sorgen dafür, dass das Schalten nur dann möglich ist, wenn diese erfüllt ist. So lässt sich beispielsweise verschiedenen Tran-
  17. 17. 2.1. Zustandsdiagramme 7 sitionen eines Zustands dasselbe Ereignis zuordnen. Abhängig von den Bedingungen können aber unterschiedliche Aktionen ausgeführt oder verschiedene Zielzustände er- reicht werden. Prinzipiell ist es bei Zustandsübergängen allerdings möglich, ein nichtdeterminis- tisches Zustandsdiagramm zu modellieren, in welchem mehrere Transitionen gleichzei- tig schalten können. In diesem Fall wird seitens der UML-Spezifikation keine genaue Semantik vorgegeben. Somit bleibt es frei zu entscheiden, welche der Transitionen schalten kann. Für die Markierung von Zustandsübergängen ergibt sich als Schreib- weise also Ereignis[Bedingung]/Aktion. Zeitgesteuerte Transitionen Durch das Schlüsselwort after (x) wird das TimeEvent gekennzeichnet. Dieses defi- niert einen bestimmten Typ Zustandsübergang, die zeitge- steuerte Transition. Nach Ablauf der konstanten Zeitspan- ne x wird ein Ereignis getriggert, sodass die Transition bei erfüllter Bedingung schalten kann. Als zeitlicher Bezug wird bei diesem Ereignistyp der Zeitpunkt des Eintritts in den aktuellen Zustand angenommen. Segmentierte Transitionen Ein weiterer Aspekt von Zu- standsdiagrammen sind die so genannten segmentierten Tran- sitionen. Mit ihnen ist es möglich, mehrere Zustandsübergänge miteinander zu verketten, um beispielsweise zu erreichen, dass in jeder Teiltransition eine Aktion ausgeführt wird. Ebenfalls bieten sie die Möglichkeit, Verzweigungen zu modellieren. Die Verknüpfung segmentierter Transitionen wird im Diagramm durch einen Pseudozustand in Form eines schwarzen Kreises dargestellt, der Verbindungsstelle. Eine segmentierte Transition ist immer atomar, d. h. sie schaltet entweder ganz oder gar nicht. Vor dem Zustandsübergang muss von der segmentierten Transition geprüft werden, ob es einen Weg zum nächsten Zustand gibt. Doch es gibt noch eine weitere Besonderheit, die berücksichtigt werden muss: Nur dem ersten Segment der Transition darf ein Ereignis zugewiesen werden, während alle Segmente Bedingungen und Aktionen halten können. Hierarchische Zustände In komplexen Systemen ist es häufig notwendig, Sachverhalte zunächst zu abstrahieren und deren tatsächliche Arbeitsweise in einem nachfolgenden Schritt genauer zu mo- dellieren. Zustandsdiagramme bieten die Bildung von Zustandshierarchien an. Diese Oder-Verfeinerung ermöglicht es, einen komplexen Zustand in Unterzustände zu zer- legen, die den Sachverhalt genau beschreiben. Sobald das Objekt diesen Oder-Zustand erreicht, befindet es sich automatisch immer in genau einem Unterzustand der Verfei- nerung. Durch diese Schachtelung treten neue Besonderheiten auf, die im Folgenden behandelt werden. Start- und Endzustände Innerhalb einer Oder-Verfeinerung muss bekannt sein, welcher Unterzustand bei Erreichen aktiviert wird bzw. wann der Zustand beendet
  18. 18. 2. Grundbegriffe 8 ist. Diese Information wird, genau wie beim Diagramm selbst, mittels eines Start- und Endzustands dargestellt. Das Vorhandensein dieser Zustände ist allerdings nicht immer notwendig. Warum dies so ist, wird im nächsten Abschnitt deutlich. Betreten und Verlassen des Zustands Es lassen sich zwei Arten unterscheiden, wie ein komplexer Zustand aktiviert werden kann: 1. Eine ankommende Transition endet direkt am Oder-Zustand. In einem solchen Fall ist es notwendig, dass in der Verfeinerung ein Startzustand spezifiziert ist. 2. Eine ankommende Transition endet an einem beliebigen Subzustand der Verfei- nerung. In diesem Fall wird die Grenze des Oder-Zustands überschritten. Daher ist kein Startzustand notwendig, da dieser implizit durch den Zielzustand der Transition gegeben ist. Das Verlassen des Zustands findet im Wesentlichen analog statt: 1. Der Zustand kann über eine Transition verlassen werden, die von der Oder- Verfeinerung abgeht. Handelt es sich um einen Übergang ohne Ereignismarkie- rung, die so genannte Beendigungstransition, kann diese nur schalten, falls die Bearbeitung in der Verfeinerung abgeschlossen ist. In diesem Fall ist das Vor- handensein eines Endzustands notwendig. 2. Der Zustand kann über eine Transition verlassen werden, die von einem Un- terzustand abgeht und als Ziel einen Zustand außerhalb der Oder-Verfeinerung besitzt. Zustandsübergänge Transitionen in Hierarchien füh- ren bei näherer Betrachtung zu zwei Fragen, für die ei- ne Semantik bestimmt werden muss. Zunächst muss ge- klärt werden, was für ereignismarkierte Transitionen gilt, die vom Oberzustand wegführen. Denn laut Semantik be- findet sich das Objekt zu jedem Zeitpunkt, während der Oder-Zustand aktiv ist, in genau einem der Unterzustän- de. Damit diese „top-level“-Ereignisse trotzdem verarbei- tet werden können, werden die von der Oder-Verfeinerung abgehenden Transitionen an alle Unterzustände „vererbt“. Anders ausgedrückt bedeutet dies, dass jeder Unterzustand alle ereignismarkierten Zustandsübergänge der Oberzustände kennt, die sich auf dem Pfad vom Zustand zur Wurzel des Baums befinden, der durch die Hierarchie aufge- spannt wird. Hieraus leitet sich die nächste Frage ab: Was passiert, wenn in einem Unterzustand eine geerbte Transition „überschrieben“ wird, das heißt auf dasselbe Ereignis reagiert wie die vom Oberzustand geerbte Transition? In diesem Fall existieren ggf. mehrere mögliche Übergänge, die in einem Schritt schalten können. Es wird eine Vorrangsre- gel benötigt, die festlegt, welche der Transitionen schalten kann. Hierzu werden den Zustandsübergängen Prioritäten zugeordnet, was zu folgender Regel führt:
  19. 19. 2.1. Zustandsdiagramme 9 Definition: Vorrangsregel Sei t1 eine vom Zustand s1 wegführende Transition und s1 ein transitiv erreichbarer Unterzustand von s2 . t2 ist ein von s2 wegführender Zustandsübergang mit dersel- ben Ereignismarkierung wie t1 . Dann hat die verschachtelte Transition t1 eine höhere Priorität als t2 (OMG, 2003). Es werden immer die Übergänge schalten, die in der Hierarchie weiter unten ange- siedelt sind und das Verhalten des komplexen Zustands spezifizieren. Anhand dieser Regel lässt sich die Idee der Verfeinerung von Zuständen erkennen. History-Zustände In vielen Systemen ist es zum Teil notwendig zu wissen, in welcher Konfiguration sich der komplexe Zustand beim letzten Verlassen befun- den hat. Mit dieser Information kann die Bearbeitung an derselben Stelle fortgeführt werden, an der sie zu- letzt unterbrochen wurde. Diese „Ablaufhistorie“ wird in hierarchischen Zuständen über Pseudozustände mo- delliert. Diese History-Zustände werden im Diagramm durch H bzw. H ∗ gekennzeichnet und lassen sich in zwei Kategorien unterteilen: 1. Die flache Historie (H) eines Oder-Zustands speichert den zuletzt aktiven Sub- zustand. 2. Die tiefe Historie (H ∗ ) eines Oder-Zustands speichert alle Zustände für die gilt, dass sie aktiv sind und sich auf dem Pfad vom hierarchischen Zustand zu einem Blatt des Hierarchiebaums befinden. Im Gegensatz zu klassischen Statecharts muss für die History-Zustände der UML- Zustandsdiagramme eine Starttransition angegeben werden. Diese geht vom History- Zustand aus und führt zu dem Zustand, der aktiviert werden soll, falls noch keine Historie vorliegt. Dies ist genau dann der Fall, wenn der Oder-Zustand das erste Mal betreten wird. Andernfalls wird der Zustand aus der Historie bzw. die Zustände der tiefen Historie aktiviert. Nebenläufige Zustände Eine weitere Neuerung gegenüber endlichen Automaten ist die Modellierung von Ne- benläufigkeit. Hierbei handelt es sich um einen Zustand, welcher mindestens zwei gleich- zeitig aktive Subzustände besitzt. Diese bilden die einzelnen Regionen des Zustands. Daher wird dieser Typ auch als Und-Zustand bezeichnet. Die Regionen werden über ei- ne gestrichelte Linie im Diagramm voneinander getrennt. Nebenläufigkeit zieht jedoch konzeptionelle Konsequenzen nach sich, auf die im Folgenden eingegangen wird. Regionen In einer Und-Verfeinerung werden die einzelnen Regionen durch hierar- chische Zustände beschrieben, sodass die dort beschriebene Semantik hier ebenfalls anzuwenden ist. Grundsätzlich sind alle Regionen der Verfeinerung aktiv, sobald der
  20. 20. 2. Grundbegriffe 10 Und-Zustand betreten wird. Sie können als eine Art Prozesse angesehen werden, die parallel innerhalb der Verfeinerung laufen. Es können also nicht einzelne Bereiche „ausgeschal- tet“ werden. Ankommende Ereignisse werden immer von allen Regionen verarbeitet, was dazu führen kann, dass mehrere Regionen das Ereignis verarbeiten und somit potenziell verschiedene Transitionen gleichzeitig schal- ten können. Zusätzlich werden wie bei hierarchischen Zuständen die wegführenden ereignismarkierten Transi- tionen der Verfeinerung an jede Region - also an alle Unterzustände - vererbt und Übergänge mit höherer Priorität beim Schalten bevorzugt. Betreten und Verlassen des Und-Zustands Wird der Zustand aktiviert, muss dafür gesorgt werden, dass alle Regionen ebenfalls aktiviert werden. Analog müssen alle Bereiche deaktiviert werden, sobald der Zustand verlassen wird. Wie bei der Oder-Verfeinerung lassen sich je zwei Fälle unterscheiden: 1. Endet die ankommende Transition am Und-Zustand, muss für alle Regionen ein Startzustand angegeben werden. 2. Endet die ankommende Transition an einem Unterzustand genau einer Region, wird die Oder-Verfeinerung implizit betreten und alle anderen Regionen müssen an dem jeweiligen Startzustand aktiviert werden. Es müssen n − 1 Startzustände vorhanden sein. Das Verlassen des Zustands findet im Wesentlichen analog statt: 1. Erreichen alle Regionen ihre Endzustände, kann der Und-Zustand über die Be- endigungstransition verlassen werden. Die Regionen werden an den jeweiligen Endzuständen synchronisiert. 2. Schaltet eine Transition von einem Unterzustand einer beliebigen Region und hat ein Ziel außerhalb der Und-Verfeinerung, werden alle Regionen unabhän- gig von ihrem derzeit aktuellen Zustand verlassen und der nebenläufige Zustand deaktiviert. Eben diese Semantik gilt auch für vom Superzustand geerbte ereig- nismarkierte Transitionen. Komplexe Transitionen Bei genauerer Betrachtung wurden bisher zwei Fälle be- handelt, wie Regionen aktiviert werden können. Zum einen explizit und zum anderen implizit. Im ersten Fall beginnen alle Bereiche an ihren Startzuständen, im letzte- ren n − 1 Regionen an ihrem Startzustand und eine an einem beliebigen Subzustand, nämlich dem Ziel der Transition. Es fehlt noch der Fall, dass m Regionen an einem beliebigen Unterzustand beginnen, wobei 1 < m < n ist. Sicherlich können die Startzu- stände so angelegt werden, dass sie das gewünschte Verhalten widerspiegeln, doch was ist, wenn dies nicht gewünscht ist? Die Startzustände können beispielsweise die Stan- dardkonfiguration modellieren, während in einem Spezialfall die Oder-Verfeinerung in einer anderen Konfiguration beginnen soll.
  21. 21. 2.1. Zustandsdiagramme 11 Um dieses Verhalten abbilden zu können, existieren die komplexen Transitionen. Sie zeichnen sich dadurch aus, dass der Kontrollfluss aufgeteilt wird, also aus einer ankommen- den Transition mehrere generiert werden. Somit kann pro Region maximal eine neue Transition entstehen, deren Ziel ein Zustand in der Region ist. Ein analoger Fall lässt sich natürlich auch für das Verlassen der Oder-Verfeinerung beschreiben. Somit bieten kom- plexe Transitionen zwei Funktionsarten: 1. Das Aufteilen einer in mehrere Transitionen, um direkt Unterzustände der einzel- nen Regionen anspringen zu können. Bereiche, die von der komplexen Transition nicht berücksichtigt werden, also keine Zustandsübergänge in diese führen, wer- den automatisch an ihren Startzuständen aktiviert. 2. Das Synchronisieren einzelner Regionen, um den Zustand zu verlassen, sobald bestimmte Unterzustände erreicht werden. Bereiche, die von der komplexen Tran- sition nicht berücksichtigt werden, also keine Übergänge aus diesen Regionen führen, werden unabhängig von ihrem aktuellen Zustand automatisch beendet. In der Modellierung werden hierfür zwei Pseudozustände berücksichtigt, die beide durch einen schwarzen Synchronisationsbalken gekennzeichnet werden. Dieser Balken entspricht dem Fork-Zustand genau dann, wenn er eine 1 : n-Abbildung repräsentiert, d. h. aus einer ankommenden Transition mehrere abgehende werden. Als Join-Zustand wird er dann bezeichnet, wenn er eine n : 1-Abbildung repräsentiert, also mehrere ein- gehende Transitionen synchronisiert und zu einer abgehenden zusammenfasst. 2.1.2 Implementierungsansätze Nachdem die für die Arbeit relevanten Elemente von Zustandsdiagrammen und deren Semantik erörtert wurden, sollen im Folgenden verschiedene Möglichkeiten behandelt werden, diese in Quellcode abzubilden. Die vorgestellten Konzepte basieren auf den klassischen Methoden der Implementierung endlicher Automaten. Prozedurale Implementierung Mittels prozeduraler Programmiersprachen lassen sich endliche Automaten einfach und effizient über geschachtelte switch-case-Anweisungen implementieren. Hierzu wird der aktuelle Zustand in einer globalen Variable gehalten, während Aktionen als Prozedur implementiert werden. Ein einfacher endlicher Automat ließe sich wie in Abbildung 2.1 darstellen. Um ein Ereignis verarbeiten zu können, wird zunächst der aktuelle Zustand gewählt und für diesen geprüft, ob er das Ereignis verarbeiten kann. Durch die Kapselung der Aktionen in einzelne Prozeduren ist es möglich diese wiederzuverwenden. Des Weiteren lässt sich insgesamt ein Programm erzeugen, welches wenig Speicher benötigt und in der Regel schnell ist. Jedoch hat diese Implementierung auch essenzielle Nachteile: • Der Quelltext wird schnell unübersichtlich, schlecht wartbar und fehleranfällig.
  22. 22. 2. Grundbegriffe 12 i n t s t a t e = ’A ’ ; . . . void fsm ( e v e n t ) { switch ( s t a t e ) case A: switch ( e v e n t ) case x : a cti on X ( ) ; s t a t e = ’B ’ ; break ; case y : =⇒ a cti on Y ( ) ; s t a t e = ’A ’ ; break ; break ; case B : switch ( e v e n t ) case y : a c t i o n Z ( ) ; s t a t e = ’A ’ ; break ; break ; } Abbildung 2.1.: Umsetzung eines endlichen Automaten in prozeduralen Quelltext • Oder- bzw. Und-Verfeinerungen lassen sich nicht abbilden und müssen zunächst aus dem Diagramm entfernt werden, ohne die Semantik einzuschränken (Harel, 1987). Dieses „Plätten“ ist allerdings problematisch, da die resultierende Anzahl der Zustände enorm groß werden kann. Objektorientierte Ansätze Das State-Pattern Eine Alternative bietet der objektorientierte Ansatz. Für end- liche Automaten hat sich das von Erich Gamma vorgestellte State-Entwurfsmuster etabliert (Gamma u. a., 1995). Das zentrale Konzept ist die Kapselung der Zustände in einzelne Objekte, welche von einer abstrakten Basisklasse alle Methoden erben und bei Bedarf überschreiben. Methoden repräsentieren die Aktionen einer Transition. Zu- dem gibt es den Kontext, welcher den Zugriff zum endlichen Automaten anbietet und eine Referenz auf den derzeit aktuellen Zustand hält. Die Aufgabe des Kontext besteht darin, ankommende Ereignisse (die Methodenaufrufe) an den aktuellen Zustand weiter zu delegieren. Der oben dargestellte Beispielautomat kann mit Hilfe des State-Patterns wie in Abbildung 2.2 implementiert werden. Auch hier lässt sich erkennen, dass die essenziellen Elemente nicht unterstützt wer- den. Es muss eine Lösung gefunden werden, die es erlaubt das Entwurfsmuster sowie Hierarchie und Nebenläufigkeit zu verwenden. In (Ali und Tanaka, 1999) wird eben dieses Problem behandelt und eine Lösung vorgestellt. Die Idee ist das Verwenden der Vererbungshierarchie zur Darstellung der Oder-Verfeinerung. Das heißt, eine von A erbende Klasse B ist Unterzustand von A. Hier ist zu erkennen, dass die Semantik der Vererbung von Transitionen erfüllt ist, da sich dies eins zu eins auf die Klassenhier-
  23. 23. 2.1. Zustandsdiagramme 13 Abbildung 2.2.: Umsetzung des Beispielautomaten mit Hilfe des State-Patterns archie abbilden lässt. Offen bleibt, wie sich nebenläufige Zustände realisieren lassen. Die Lösung ist naheliegend: In Zustandsdiagrammen besteht eine Und-Verfeinerung aus mindestens zwei hierarchischen Zuständen. Also hält der Und-Zustand eine Refe- renz auf eine Oder-Verfeinerung. Genau dies wird in dem Ansatz von Ali und Tanaka verwendet. Der Und-Zustand ist eine Klasse, welche Referenzen auf die Regionen hält und die einzelnen Ereignisse an die Regionen delegiert (siehe Abbildung 2.3). =⇒ Abbildung 2.3.: Beispiel der Abbildung von Hierarchie und Nebenläufigkeit in die Klassenhierarchie objektorientierter Sprachen auf Basis des State-Patterns Der Ansatz von Gurp und Bosch Beim State-Pattern ist das zentrale Konzept der Zustand. Dies führt dazu, dass Elemente wie Aktionen und Transitionen nicht als eigenes Objekt repräsentiert werden, sondern implizit in der Zustandsklasse „versteckt“ sind. Ein alternativer Ansatz wurde von Jilles van Gurp und Jan Bosch vorgestellt (van Gurp und Bosch, 1999). Dieser verlagert das Konzept auf die explizite Modellierung der Elemente endlicher Automaten als eigene Objekte. Hier wird nicht der Zustand, sondern die Transition als zentrales Element betrachtet. Diese kennt den Zielzustand und die auszuführende Aktion, während der Zustand seine abgehenden Transitionen kennt und diese mit einem Ereignis verknüpft. Der Kontext dient als Schnittstelle des Zustandsdiagramms zur Anwendung und leitet die Ereignisse an den jeweils aktuellen Zustand weiter. Dieser prüft in seiner Liste
  24. 24. 2. Grundbegriffe 14 Abbildung 2.4.: Klassendiagramm des FSM-Frameworks von Bosch und van Gurp aus (Ereignis, T ransition)-Tupeln, ob das Ereignis verarbeitet werden kann und führt den Übergang ggf. aus. Aktionen werden mittels des Command-Patterns (Gamma u. a., 1995) ausgeführt, sodass verschiedenartige Aktionen implementiert, aber diese auch wiederverwendet werden können. Da ein Ziel in dem Ansatz die Wiederverwendbarkeit des Zustandsdiagramms ist, werden ablaufspezifische Daten nur im Kontext verwaltet. Dies bedeutet, dass sich verschiedene Kontexte dasselbe Diagramm teilen können. Aus diesem Grund wird in der Implementierung der Kontext als Referenz mit übergeben (siehe Abbildung 2.4). Jedoch fehlt auch in diesem Ansatz die Unterstützung der wesentlichen Elemente von Zustandsdiagrammen und beschränkt sich auf die Abbildung endlicher Automa- ten. Die Laufzeitsemantik, Hierarchie und Nebenläufigkeit muss explizit implementiert werden, während sie beim Ansatz von Ali und Tanaka implizit auf Elemente der Pro- grammiersprache abgebildet werden. 2.2 LEGO Mindstorms Roboter Das LEGO Mindstorms Robotics Invention System wurde erstmals im Jahr 1998 vorge- stellt und basiert auf einer Zusammenarbeit der Firma LEGO mit dem Massachusetts Institute of Technology (MIT). Es erweitert die Produktlinie LEGO Technik um einen frei programmierbaren Baustein und ermöglicht so das Erstellen elektro-mechanischer Komponenten wie zum Beispiel Roboter. Aufgrund der Freiheiten, die LEGO mit sei- nem System bietet, ist es möglich eine Vielfalt verschiedenster Roboter zu bauen. Daher soll in diesem Abschnitt zunächst auf die Hardware näher eingegangen werden, bevor anschließend die verfügbaren Möglichkeiten der Programmierung beleuchtet werden. 2.2.1 Aufbau der Hardware Bei dem von LEGO verwendeten RCX-Baustein handelt es sich um einen Mikrocon- troller der Firma Hitachi mit 16MHz. Der Controller besitzt zur Interaktion mit der Umwelt drei Eingänge für Sensoren und drei Ausgänge. Derzeit existieren Druck-, Rotations-, Licht- und Temperatursensoren sowie Motoren und Lampen, welche an
  25. 25. 2.2. LEGO Mindstorms Roboter 15 Abbildung 2.5.: Der RCX-Baustein die Ausgänge angeschlossen werden können. Es stehen dem Anwender 32KB RAM und 16KB ROM zur Verfügung. Im ROM werden low-level-Funktionen und Basistrei- ber zur Steuerung der Ausgänge, Sensoren und der Infrarot-Schnittstelle bereitgestellt. Über letztere wird der RCX mit einer Firmware versorgt, die das Betriebssystem dar- stellt und die Programmierung ermöglicht. Die 32KB Arbeitsspeicher stehen nicht aus- schließlich für eigene Programme zur Verfügung, sondern halten neben dem Programm auch dessen Laufzeitobjekte und die Firmware. Somit steht je nach verwendeter Firm- ware unterschiedlich viel Speicher für eigene Programme und Daten zur Verfügung. 2.2.2 Programmierung der LEGO-Roboter Die Firma LEGO bietet ein eigenes Betriebssystem an, welches jedoch nicht mittels einer Hochsprache programmiert werden kann. Stattdessen wird eine graphische Mo- dellierungssprache angeboten, die über eine proprietäre Windows-Software verwendet werden kann. Diese erstellt aus dem Modell schließlich das Programm für den RCX- Baustein. Im Wesentlichen wird also dasselbe Vorgehen verwendet, welches im Rah- men dieser Diplomarbeit behandelt werden soll: Aus einer graphischen Modellierung ein ausführbares Programm zu erstellen. Da die Modellierungssprache von LEGO für Kinder ausgelegt ist, lassen sich kom- plexe Abläufe nur umständlich bis gar nicht beschreiben. Ziel verschiedener Entwickler war es eine Möglichkeit zu schaffen, mit der die Roboter klassisch programmiert werden können. Aus diesem Grund wurden alternative „Betriebssysteme“ entwickelt, wobei die drei bekanntesten jeweils kurz beleuchtet werden. Not Quite C Basierend auf der original LEGO-Firmware implementiert NQC (Baum und Hansen, 2004) eine eigene prozedurale Programmiersprache, die an C angelehnt ist. Objektori- entierte Konzepte lassen sich hier also nicht umsetzen.
  26. 26. 2. Grundbegriffe 16 BrickOS Das ursprünglich von Markus L. Noga entwickelte Betriebssystem BrickOS (Noga, 2002) ersetzt das Original der Firma LEGO. Als Compiler wird der GCC verwendet, sodass der Quellcode nativ für den Hitachi-Prozessor kompiliert und gelinkt wird. Pro- gramme können in der Hochsprache C++ entwickelt werden und bietet demnach auch die Möglichkeiten dieser Sprache, inklusive Bibliotheken wie der Standard Template Library und der manuellen Speicherverwaltung. LeJOS Das auf TinyOS aufbauende LEGO Java Operating System (Solorzano, 2000) imple- mentiert eine virtuelle Maschine für Java und ersetzt wie BrickOS die Firmware von LEGO. Programme lassen sich in Java, und somit objektorientiert, implementieren. LeJOS greift auf eine abgespeckte Java API zurück, die die essenziellen Klassen der Sun API nachbildet. Im Gegensatz zum Original bietet diese virtuelle Maschine keine Garbage Collection an, die das Aufräumen von Objekten übernimmt. Einmal angelegte Objekte bleiben die komplette Laufzeit des Programms im Speicher bestehen. Diese drei Möglichkeiten der Programmierung des RCX gehören zu den bekanntes- ten und am meisten eingesetzten. Im nächsten Kapitel werden zunächst die Vor- und Nachteile der einzelnen Lösungen diskutiert. Auf diesem Ergebnis aufbauend, werden die vorgestellten Ansätze zur Implementierung von Zustandsdiagrammen verglichen und begutachtet, um daraus ein Konzept und eine Vorgehensweise für diese Diplom- arbeit abzuleiten.
  27. 27. Kapitel 3 Lösungsansatz Das Konzept zur Durchführung der Integration von LEGO Mindstorms in DAVE ba- siert auf mehreren Voraussetzungen und Anforderungen. Zunächst soll die Frage disku- tiert werden, auf welcher technischen Basis die Programmierung des RCX durchgeführt werden soll. 3.1 Zielplattform Es wurden die drei Laufzeitumgebungen NQC, BrickOS und LeJOS kurz vorgestellt. Natürlich hängt die Auswahl auch mit dem gewünschten Programmierkonzept und dem verfügbaren Speicherplatz des RCX zusammen. Ausgehend von den vorgestellten Implementierungsansätzen lässt sich zusammenfassen, dass der prozedurale Ansatz den Vorteil hat, dass die resultierenden Programme in der Regel klein sind. Das objektori- entierte Programmierparadigma erleichtert hingegen die Implementierung, da auf das Plätten von Zustandsdiagrammen verzichtet werden kann und somit ein komplexer Schritt entfällt. Daher werden im Rahmen dieser Arbeit nur objektorientierte Ansätze verfolgt, was zur Konsequenz hat, dass NQC nicht näher betrachtet wird. BrickOS bietet die Vorteile, dass mittels C++ der Speicher des RCX frei verwaltet werden kann und nativer Maschinencode für den im RCX verbauten Hitachi-Prozessor generiert wird. Diese beiden Punkte haben zur Folge, dass sehr kleine Programme ent- stehen, sodass der verfügbare Speicher effizient ausgenutzt werden kann. Allerdings bedingt C++ durch seine Konzepte und dem freien Speicherzugriff potenziell eine re- lativ hohe Fehleranfälligkeit. Es müssen sprachbedingte Probleme beachtet werden, wie zum Beispiel zyklische Abhängigkeiten bei der Einbindung der Header-Dateien. Diese Argumente erschweren zwar die Implementierung, bilden aber noch kein Aus- schlusskriterium. Das wichtigste Argument gegen BrickOS ist, dass der Einsatz in der Lehre geplant ist und somit gewährleistet sein muss, dass die Studenten ohne zusätz- lichen Aufwand die Funktionalität aus DAVE heraus auch zu Hause nutzen können. Dies ist bei BrickOS in dieser geforderten Form allerdings nicht ohne weiteres möglich, da diese Plattform auf freien Unix-Werkzeugen basiert, welche unter Windows nur mit erheblichem Aufwand verwendet werden können. Selbst die Installation von BrickOS unter Unix-Systemen ist nicht trivial. LeJOS hingegen wird in Form von jar-Dateien ausgeliefert, welche keine weitere Installation erforderlich machen. Daher können die benötigten Elemente von LeJOS
  28. 28. 3. Lösungsansatz 18 direkt in DAVE mitgeliefert werden, ohne dass die Studenten zusätzliche Installations- routinen ausführen müssen. Zudem wird mit Java eine moderne Programmiersprache angeboten, welche nicht nur betriebssystemunabhängig ist, sondern sich auch gut in DAVE einbinden lässt, weil dieses in selbiger Sprache implementiert ist. Da es sich bei LeJOS nur um eine abgespeckte Variante einer Java-Laufzeitumgebung handelt, müssen allerdings Einschränkungen hingenommen werden: • Einmal angelegte Objekte können nicht mehr freigegeben werden und belegen den allokierten Speicher bis zur Programmbeendigung. • Die Firmware verbraucht einen hohen Anteil an Speicher des RCX, sodass für eigene Programme und Daten nur ca. 9 Kilobyte zur Verfügung stehen. Durch die fehlende Garbage Collection, die große Firmware und die Tatsache, dass Java-Programme im Allgemeinen relativ groß sind, besteht die Gefahr, dass nur klei- nere Zustandsdiagramme umgesetzt werden können. Da dies für die Lehre keine we- sentliche Einschränkung ist, erscheint LeJOS als erste Wahl und soll als Basis für die weiteren konzeptionellen Entscheidungen dienen. 3.2 Ansatzpunkte zur Integration in DAVE Ein weiterer wichtiger Aspekt ist die Analyse der Konzepte von DAVE, um mögliche Ansatzpunkte zu finden, an denen die Integration angesetzt werden kann. Da ein we- sentliches Alleinstellungsmerkmal dieser Software die Simulation der Diagramme ist, existiert in DAVE eine Simulationsmaschine für Zustandsdiagramme. Sie basiert auf dem um Hierarchie und Nebenläufigkeit erweiterten Ansatz von Bosch. Die Persistenz- schicht zum Speichern wird durch das LIMO-Framework realisiert. Dieses schreibt die Datenstruktur zur Beschreibung eines Zustandsdiagramms in einem einfachem XML- Format auf die Festplatte. Es ergeben sich zwei Fragestellungen: Kann die vorhandene Simulationsmaschine verwendet werden und welche Form der Datenstruktur soll als Basis der Integration dienen? Die nächsten beiden Abschnitten behandeln diese Fragen. 3.2.1 Die Simulationsmaschine Zunächst stellt sich die Frage wie sich die in DAVE vorhandene Laufzeitumgebung für Zustandsdiagramme verwenden lässt. Dies lässt sich leicht beantworten, denn die vorhandene Implementierung ist zu umfangreich, um sie im RCX verwenden zu können. Modifikationen an der existierenden Laufzeitumgebung könnten zwar durchgeführt werden, können aber zu unerwünschten Seiteneffekten innerhalb DAVEs führen, sodass zusätzliche Tests des gesamten Programms notwendig sind. Diese Aspekte implizieren die Planung und Realisierung einer eigenen, speicherminimalen Simulationsmaschine, die völlig unabhängig von DAVE ist.
  29. 29. 3.3. Implementierung 19 3.2.2 Wahl der zu verwendenden Datenstruktur Der zweite Aspekt ist die zu verwendende Datenstruktur. Als Basis können entweder die Klassen verwendet werden, welche DAVE zur Beschreibung des Diagramms zur Verfügung stellt, oder aber die rein textuelle Repräsentation in Form der XML-Datei. Beim ersten Ansatz muss die Simulationsmaschine entwickelt werden, während beim letzteren zusätzlich eine geeignete Datenstruktur geplant und programmiert werden muss. Trotzdem ist die Verwendung der XML-Datei sinnvoller. Denn für die existie- renden DAVE-Klassen ist nicht klar, ob sich diese in einer speicherplatzoptimierten Simulationsmaschine ohne Modifikation verwenden lassen. Änderungen der Quellen führen jedoch zu schon oben genanntem Problem. Somit ist letztlich eine sehr lose Kopplung an DAVE der zu wählende Weg. Einzig die Codegenerierung ist an spezi- fische Teile, nämlich das XML-Format, gekoppelt, während der Rest eine Wiederver- wendbarkeit außerhalb von DAVE gewährleistet. Diese Datei wird als Eingabe für ein Ant-Skript (Davidson, 1999) dienen, welches die notwendigen Schritte zur Fertigstel- lung des RCX-Programms automatisiert (siehe Kapitel 8). 3.3 Implementierung Da eine sehr lose Kopplung an DAVE gewählt wird, soll dieser Ansatz auch auf die LEGO-spezifischen Teile ausgedehnt werden. Diese Entscheidung basiert ebenfalls auf der Idee, einzelne Komponenten wiederverwenden zu können. Wie lässt sich die Implementierung realisieren? Hierzu werden im ersten Schritt die von LeJOS generierten Programme näher betrachtet. Dabei fällt auf, dass die Ver- wendung von Klassen speichertechnisch teuer ist. Pro Klasse entsteht je nach deren Umfang ein Speicherverbrauch von rund 500 bis 1024 Bytes. Jede Instanz kostet hin- gegen im Durchschnitt nur wenige Bytes. Das Ziel ist daher die Anzahl an Klassen möglichst minimal zu halten und stattdessen viele Objekte zu erzeugen. Der Implementierungsansatz von Ali und Tanaka fällt dadurch auf, dass er eine große Anzahl an Klassen generiert, nämlich mindestens so viele wie Zustände existieren. Zudem erfordert dieser Ansatz, dass die Codegenerierung den Quelltext jedes einzelnen Zustands in Form einer Klasse erzeugt. Denn innerhalb dieser existiert spezifischer Quelltext für die Transitionen und Aktionen des Zustandsdiagramms. Dies führt zu einer enormen Steigerung der Komplexität bei der Codegenerierung. Wünschenswert wäre hingegen ein Framework, welches eine einfache Programmierschnittstelle bietet, die bei der automatischen Erzeugung des Quelltexts aus der XML-Datei verwendet werden kann. 3.4 Gesamtaufbau Die Hauptprobleme liegen in der Entwicklung eines Frameworks zur Repräsentation und Ausführung von Zustandsdiagrammen, welches mit den begrenzten Ressourcen des RCX verwendbar ist und der Überlegung, wie aus der XML-Datei Quellcode generiert
  30. 30. 3. Lösungsansatz 20 Abbildung 3.1.: Aufbau des Konzepts. Die Nummern in den Kreisen geben die Kapitel an, in denen der jeweilige Teil behandelt wird, während die mit Ant gekennzeichneten Pfeile die Schritte angeben, die über das Ant-Skript automatisiert werden. werden kann. In Abbildung 3.1 ist der geplante Aufbau zu sehen. Nach dem Spei- chern des Diagramms in DAVE wird die XML-Datei in Quelltext transformiert. Die- ser variable Teil verwendet die Programmierschnittstellen des statischen Teils, nämlich des Frameworks. Aus dem generierten Quelltext kann schließlich mittels vorhandener Werkzeuge das Programm kompiliert, gelinkt und in den RCX hochgeladen werden. Diese Schritte werden mittels Ant automatisiert. Das Framework selbst soll auf dem Ansatz von Bosch und van Gurp basieren, jedoch um die benötigten Elemente für Zustandsdiagramme erweitert. Durch die Verwendung dieses Ansatzes kann die Anzahl der Klassen auf ein Minimum reduziert werden, indem die einzelnen Elemente von Zustandsdiagrammen explizit durch eine Klasse repräsen- tiert werden. Somit steigt nur die Anzahl der Instanzen. Zusätzlich kann das Frame- work in zwei Teile getrennt werden. Zum einen die eigentliche Laufzeitumgebung für Zustandsdiagramme und zum anderen die LEGO-spezifischen Teile. So lässt sich errei- chen, dass das Framework auch unabhängig von LEGO wiederverwendet werden kann, etwa in anderen eingebetteten Umgebungen. Weitere Vorteile dieser Vorgehensweise sind erkennbar: Die Codegenerierung verein- facht sich erheblich, da sie sich auf die Erzeugung genau einer einzigen Klasse beschrän- ken kann. Und der statische Teil des Systems kann automatisiert getestet werden, was bei komplexem, dynamisch generierten Quelltext nur mit Aufwand möglich ist. Es lassen sich insgesamt vier Teilaufgaben entdecken, die in den folgenden Kapiteln bearbeitet werden. Die erste Aufgabe besteht in der Planung der Laufzeitumgebung für Zustandsdiagramme (Kapitel 4). Die zweite Aufgabe lässt sich in zwei Bereiche splitten. Zunächst muss überlegt werden, wie die RCX-spezifischen Elemente in die Notation von Zustandsdiagrammen eingebettet werden können (Kapitel 5), bevor de- ren technische Umsetzung, also die LEGO-Erweiterung des Frameworks, beschrieben werden kann (Kapitel 6). Die dritte Aufgabe ist die Planung und Umsetzung der Code- generierung (Kapitel 7), gefolgt von der letzten Aufgabe, der Integration der einzelnen Teile in Form eines Automatisierungs-Skripts (Kapitel 8).
  31. 31. Kapitel 4 Das Statechart-Framework 4.1 Konzeptioneller Aufbau Die genannten Anforderungen an das zu entwickelnde Framework für Zustandsdia- gramme erfordern zunächst konzeptionelle Vorüberlegungen. Als Basis dient das von Bosch und van Gurp vorgestellte Framework für endliche Automaten, dessen Imple- mentierung in Abbildung 2.4 zu sehen ist. Wie beim State-Pattern wird auch bei diesem ein Kontext eingesetzt, welcher den aktuellen Zustand kennt und Ereignisse an diesen delegiert. Allerdings wird dem Kontext ein weiterer Zweck zugeteilt: Bosch und van Gurp adressieren das Problem der mehrfachen Nutzung des Automaten und kapseln ablaufspezifische Daten, wie z. B. Variablenwerte, in dem Kontext. Dies bedeutet, dass ein Zustand nur ein Teil der Infrastruktur des Automaten ist und somit verschiedene Kontexte gleichzeitig auf denselben Automaten zugreifen können. Daher wird in den Methoden dispatch und execute jeweils eine Referenz des aktuellen Kontexts überge- ben, sodass Aktionen und Zustände die Möglichkeit haben, Daten in diesem zu setzen und abzufragen. Der für dieses Framework gewählte Ansatz sieht hingegen nicht die mehrfache Nutzung desselben Diagramms mit unterschiedlichen Kontexten vor. Im Gegensatz zu endlichen Automaten können Zustandsdiagramme Hierarchien be- inhalten, sodass es möglich ist, dass mehrere Zustände gleichzeitig aktiv sind. Die Menge der aktuell aktiven Zustände wird im Folgenden als Konfiguration bezeichnet. Das Zustandsdiagramm befindet sich zu jedem Zeitpunkt in einer eindeutigen Konfigu- ration. Da nicht die Infrastruktur des Diagramms mehrfach genutzt werden soll, ist es nicht notwendig die Information über die Konfiguration im Kontext zu speichern, son- dern kann in die Zustände des Diagramms verlagert werden. Das heißt, jeder Zustand weiß zu jedem Zeitpunkt, ob er aktiviert ist. Wozu dient also der Kontext? Werden zunächst Hierarchien und nebenläufige Zu- stände außer acht gelassen, fällt diesem dieselbe Aufgabe wie beim State-Pattern zu, nämlich ein eingehendes Ereignis an den aktuell aktiven Zustand weiterzuleiten. Da ein Zustand ebenfalls ein Ereignis verarbeitet, lässt sich der Kontext als ein Spezial- fall eines Zustands ansehen, in dem nur die Ereignisverarbeitung überschrieben wird und die Zusatzinformation des aktuell aktiven Unterzustands bekannt ist. Der Kontext kann als zusammengesetzter Zustand betrachtet werden. Diese Sichtweise wird später bei der Implementierung der hierarchischen und nebenläufigen Zustände helfen.
  32. 32. 4. Das Statechart-Framework 22 Abbildung 4.1.: Konzeptioneller Aufbau des Frameworks ohne Berücksichtigung hierarchischer und nebenläufiger Zustände Ereignisse können in Zustandsdiagrammen beliebiger Natur sein. Dieser Umstand und die Tatsache, dass als Zielsetzung die Erweiterbarkeit des Frameworks zugrunde liegt, stellt entsprechende Anforderungen an die Konzeption. Bei Bosch werden Er- eignisse durch eine Zeichenkette repräsentiert, was jedoch unflexibel ist. Stattdessen eignet sich das Command-Entwurfsmuster: In einer Schnittstelle wird eine Methode deklariert, welche in einer implementierenden Klasse ausprogrammiert wird und vom Framework verwendet werden kann. Dieses Verfahren wird von Bosch für die Aus- führung von Aktionen verwendet. Da sich dieses Muster für Aktionen eignet, sollen hiermit ebenfalls Bedingungen und Ereignisse realisiert werden. Beim Aktivieren bzw. Deaktivieren werden mehrere Schritte durchgeführt (Setzen der Status-Variablen, ggf. Ausführen der Entry- bzw. Exit-Aktion), weshalb diese in einer jeweiligen Methode gekapselt werden. In Abbildung 4.1 ist eine Implementie- rung des bisherigen Konzepts in Form eines Klassendiagramms dargestellt. Bis hierher ähnelt der Aufbau dem von Bosch bis auf wenige Details. Allerdings sind einige für Zustandsdiagramme spezifische Elemente noch nicht berücksichtigt worden. 4.1.1 Zeitgesteuerte Transitionen Bei zeitgesteuerten Transitionen ist die Zeit relevant, die seit dem Betreten des aktuell aktiven Zustands vergangen ist. Als Besonderheit sei angemerkt, dass im Falle von Hierarchien der Oberzustand einen längeren Zeitraum aktiv sein kann als seine Unter- zustände. Daher muss jeder Zustand die Zeit kennen, die seit seiner letzten Aktivierung vergangen ist und die Klasse muss um diese Information erweitert werden. Um möglichst flexibel zu sein, soll das Hochzählen der Zeit von außen über einen Zeitgeber gesteuert werden können. Hier gibt es prinzipiell zwei Lösungsansätze. Beim ersten Ansatz wird eine konkrete Ausprägung der Event-Schnittstelle implementiert, während der zweite Ansatz eine zusätzliche Methode timeout(ms) vorsieht. Da das Ziel
  33. 33. 4.1. Konzeptioneller Aufbau 23 Abbildung 4.2.: Erweiterung der Klassen State und Transition für zeitgesteuerte Transitionen, sowie Einführung der Klasse PseudoState ist, die Anzahl der Klassen möglichst gering zu halten, wird der zweite Ansatz verfolgt. Hieraus resultiert, dass eine Transition die zu wartende Zeit nicht als Instanz der Event- Klasse speichert, sondern als reinen Integer-Wert. Beim Schalten der Transition wird zunächst geprüft, ob die zu wartende Zeit abgelaufen ist. Neben der Verwaltung der Zeit ist es in beiden Fällen notwendig, beim Betreten des Zustands in der Methode activate() die Zeit zurückzusetzen. Abbildung 4.2 zeigt die Änderungen an den Klassen. 4.1.2 Segmentierte Transitionen Eine weitere Besonderheit in Zustandsdiagrammen ist die Verbindungsstelle als Pseu- dozustand für segmentierte Transitionen. Pseudozustände haben zwei Gemeinsamkei- ten, nämlich dass diesen keine Entry- bzw. Exit-Aktion zugewiesen und nicht in ihnen verweilt werden kann. Der erste Punkt trifft ebenso auf den Endzustand zu, obwohl es sich hier streng genommen um keinen Pseudozustand handelt. Trotzdem wird nur eine neue Klasse PseudoState ergänzt, welche auch den Endzustand repräsentiert (siehe Abbildung 4.2). Die zur Verbindungsstelle hinführende Transition darf nur dann schalten, wenn es einen Weg zu einem normalen Zustand gibt. Diese Prämisse muss durch das erste Segment sichergestellt werden, sodass dieses vor der tatsächlichen Ausführung solch einen Weg suchen muss. Hierzu bedient sich das Framework einer Tiefensuche. Eine auszuführende Transition überprüft grundsätzlich mit ihrer Methode allowed() die ggf. vorhandene Bedingung und schließlich, ob es sich bei dem Zielzustand um einen ech- ten Pseudozustand, also nicht dem Endzustand, handelt. Falls ja, wird die Tiefensuche gestartet, indem auf dem Pseudozustand die Methode lookup() aufgerufen wird, die ihrerseits für alle ausgehenden Transitionen die Methode allowed() aufruft. Erst, wenn ein Weg zu einem normalen Zustand gefunden wurde, wird der Quellzustand de- und der Zielzustand aktiviert. Da es sich bei der Verbindungsstelle um einen Pseudozu-
  34. 34. 4. Das Statechart-Framework 24 x=0 −→ Abbildung 4.3.: Verarbeitung segmentierter Transitionen für den Fall x = 0
  35. 35. 4.1. Konzeptioneller Aufbau 25 Abbildung 4.4.: Ereignisverarbeitung auf dem Oder-Zustand stand handelt, wird dieser automatisch bei Aktivierung sofort wieder verlassen, indem mittels dispatch(null) die Ereignisverarbeitung neu angestoßen wird. In Abbildung 4.3 ist dieser Ablauf dargestellt. 4.1.3 Hierarchie und Nebenläufigkeit Im Gegensatz zu Bosch und van Gurp muss das zu entwickelnde Framework noch um die beiden essenziellen Elemente der Hierarchiebildung und der Verwendung von Nebenläufigkeit erweitert werden. Hierarchische Zustände Die von Oder-Verfeinerungen aufgebaute Struktur ist die eines Baums. Ziel ist es somit innerhalb des Frameworks solch eine Baumstruktur aufzubauen. Hier ist die oben vorgestellte Sichtweise des Kontexts als Spezialfall eines Zustands hilfreich. Bisher stellt der Kontext auf oberster Hierarchieebene das Zustandsdiagramm dar und kennt den aktuell aktiven Zustand auf der darunterliegenden Ebene. Dieses Kon- zept lässt sich auf die Oder-Verfeinerung anwenden: Diese ist ein Spezialfall eines Zustands, welcher jeweils genau einen aktiven Unterzustand hat. Also genau das, was der Kontext auf oberster Hierarchieebene abbildet. Da die Klasse Context eine Generalisierung der Klasse State ist, kann vom Kontext einfach eine neue Klasse abgeleitet werden, die das Spezialverhalten eines hierarchi- schen Zustands repräsentiert. Der Kontext wird zu einer abstrakten Basisklasse für hierarchische Zustände. Beim Aktivieren eines Zustands meldet er dies seinem Vater- knoten. Zur Abbildung der Baumstruktur muss die Zustandsklasse erweitert werden. Jedem Zustand muss sein Vaterknoten bekannt sein. Der einzige „Zustand“ ohne Va- terknoten ist das Diagramm selbst, welches künftig durch die Klasse Statechart re- präsentiert wird. Das Diagramm ist also ein zusammengesetzter Zustand und bildet immer den Wurzelknoten der Baumstruktur. Ereignisverarbeitung Im Gegensatz zu normalen Zuständen ist die Verarbeitung eines eingehenden Ereignisses komplexer, da die in Abschnitt 2.1.1 beschriebene Vor- rangsregel berücksichtigt werden muss. Hierzu muss die entsprechende Methode in der abgeleiteten Klasse überschrieben werden, um das Ereignis zunächst an den aktiven Unterzustand zu propagieren. Die Vererbung von Transitionen wird implizit durchgeführt. D. h. beim Aufbau des Diagramms müssen vererbte Transitionen nicht explizit für die Unterzustände ange-
  36. 36. 4. Das Statechart-Framework 26 =⇒ Abbildung 4.5.: Transitionsverhalten bei hierarchischen Zuständen legt werden. Falls der Unterzustand das Ereignis nicht verarbeiten kann, wird geprüft, ob die Oder-Verfeinerung eine Transition besitzt, die auf das Ereignis reagieren kann. Jedoch muss eine Besonderheit berücksichtigt werden, um die Semantik korrekt ab- zubilden: Ist der Endzustand der Oder-Verfeinerung noch nicht erreicht worden, müs- sen alle Beendigungstransitionen von der Verarbeitung ausgeschlossen werden. Konnte hingegen der Unterzustand das Ereignis verarbeiten, ist dessen Behandlung auf dem Oder-Zustand abgeschlossen. Abbildung 4.4 zeigt den Ablauf solch einer Verarbeitung. Transitionen Wie bei Bosch kennen die Übergänge bisher nur den Zielzustand, um diesen zu aktivieren. Bei der Verwendung von Hierarchien reicht diese einfache Sichtweise nicht mehr aus, da es jetzt möglich ist, dass beim Schalten einer Transition mehrere Zustände (de-)aktiviert werden müssen. Im Beispiel aus Abbildung 4.5 werden beim Ausführen der Transition zum Zustand r automatisch auch die Zustände p und q aktiviert. Analoges gilt für den Übergang von r nach y. Hierbei werden q und p automatisch de- und x aktiviert. Eine Transition muss also den Pfad vom Quell- zum Zielzustand im durch die Hier- archie aufgespannten Baum berücksichtigen. Hierzu wird der kleinste gemeinsame Vor- fahre (LCA = Least common ancestor) berechnet (OMG, 2003). Alle Zustände von der Quelle zum LCA werden deaktiviert, analog werden alle Zustände auf dem Pfad vom LCA zum Ziel aktiviert. In obigem Beispiel ist der gemeinsame Vorfahre bei allen drei Transitionen das Zustandsdiagramm selbst, also der Wurzelknoten. Für die Implementierung bedeutet dies, dass bei der Erzeugung einer Transition der Quell- und Zielzustand übergeben werden muss und anhand der Hierarchie zwei Zustandsmengen berechnet werden. History-Zustände Die beiden History-Zustandsarten können sehr einfach imple- mentiert werden. Hierzu wird eine Tiefensuche auf dem Oder-Zustand ausgeführt, welche alle derzeit aktiven Knoten in einer Liste speichert. Die Suchtiefe beschränkt sich bei einem normalen History-Zustand auf Eins. Diese Sicherung wird beim De- aktivieren der Oder-Verfeinerung durchgeführt. Beim Betreten des History-Zustands werden entsprechend der gespeicherten Liste die Zustände wieder aktiviert bzw. die- ser beim erstmaligen Betreten automatisch über seine ausgehende Transition verlassen. Die Implementierung beschränkt sich daher auf die Erweiterung der Klasse für Pseudo- zustände um eine Tiefensuche und die Möglichkeit der Speicherung einer Zustandsliste. Abbildung 4.6 zeigt die entsprechenden Erweiterungen des Frameworks.
  37. 37. 4.1. Konzeptioneller Aufbau 27 Abbildung 4.6.: Erweiterung des Frameworks um Hierarchie Nebenläufige Zustände Nebenläufigkeit als der zweite zentrale Aspekt von Zustandsdiagrammen wird durch das Hinzufügen einer neuen Klasse ConcurrentState erreicht. Diese ist wie die Oder- Verfeinerung ein Kontext. Der essenzielle Unterschied ist, dass nicht genau ein Unterzu- stand aktiv ist, sondern alle Unterzustände gleichzeitig. Regionen wiederum sind Oder- Verfeinerungen, denen als Vaterknoten der nebenläufige Zustand zugewiesen wird. Ereignisverarbeitung Die Verarbeitung eines eingehenden Ereignisses funktioniert im Wesentlichen genauso wie bei hierarchischen Zuständen (siehe Abbildung 4.4). Der Hauptunterschied ist der, dass das Ereignis nicht an einen Unterzustand, sondern an alle Regionen delegiert und kein Startzustand gesetzt wird. Hierbei muss beachtet werden, dass ein Oder-Unterzustand das Ereignis nur dann verarbeiten kann, wenn die Und-Verfeinerung aktiv ist: Wird beispielsweise der ne- benläufige Zustand aus einer Region heraus über eine Transition verlassen, dürfen die restlichen Bereiche das Signal nicht weiter verarbeiten. Sobald ein Unterzustand das Ereignis behandeln konnte, gelten dieselben Abläufe wie bei hierarchischen Zuständen. Das Framework selbst unterstützt in dieser Form keine echte Nebenläufigkeit, son- dern bearbeitet wie Ali und Tanaka die einzelnen Regionen sequenziell. Die Neben- läufigkeit entsteht dadurch, dass alle Regionen im gleichen Schritt der Simulation auf das Ereignis reagieren können. Transitionen Diese haben bisher die Eigenschaft, dass ihnen eine Liste an Zustän- den bekannt ist, die beim Schalten aktiviert bzw. deaktiviert werden müssen. Bei der
  38. 38. 4. Das Statechart-Framework 28 Abbildung 4.7.: Ablauf der execute-Methode einer Transition Und-Verfeinerung entsteht ein Problem: Wird direkt ein Unterzustand einer Region angesprungen, stehen in der Liste der zu aktivierenden Zustände die Und-Verfeinerung an Position n, die Region an n + 1 und der Unterzustand an n + 2. Da beim Betreten des Und-Zustands automatisch alle Regionen aktiviert werden müssen, entsteht ein Konflikt, denn die Aktivierung erfolgt immer an den Startzuständen der Bereiche. Es muss also eine Möglichkeit geschaffen werden, die es erlaubt einzelne Regio- nen bei der Aktivierung des Und-Zustands zu ignorieren. Hierzu wird in der Klasse ConcurrentState eine Liste von Regionen gepflegt, die nicht automatisch aktiviert wer- den sollen. Diese Liste wird dynamisch zur Laufzeit von der Transition gefüllt, indem geprüft wird, ob der Zielzustand des Übergangs in einer Region liegt oder die Und- Verfeinerung selbst ist. Bei der Deaktivierung des Zustands wird diese Liste geleert. Abbildung 4.7 zeigt den Ablauf beim Schalten einer Transition. Gabelung einer Transition Die Implementierung des Fork-Zustands ist mit der Erweiterung der Und-Verfeinerung um eine Menge der nicht automatisch zu aktivie- renden Regionen einfach zu erreichen. Es wird dasselbe Prinzip angewendet wie bei Transitionen: Zunächst wird ein Weg zu einem normalen Zustand gesucht. Ist dieser gefunden worden, wird bei Erreichen des Fork-Zustands die Liste der Transitionen durchlaufen und die jeweiligen Regionen ermittelt, die ignoriert werden müssen. Erst danach werden die ausgehenden Transitionen nacheinander ausgeführt. Es reicht, die- ses Vorgehen in der Klasse PseudoState in der Methode activate() zu programmieren. Vereinigung mehrerer Transitionen Das Gegenstück zur Gabelung ist die Syn- chronisation der einzelnen Regionen am Join-Zustand. Die Beendigungstransition wird automatisch ausgeführt, wenn alle Regionen ihren jeweiligen Endzustand erreicht ha- ben. Diese Prämisse ist beim Join-Zustand nicht erfüllt. Stattdessen bilden all diejeni- gen Zustände einen gültigen „Endzustand“ der Und-Verfeinerung, die eine Transition zum Join-Zustand besitzen. Im Folgenden wird diese Zustandsmenge als Endkonfigu- ration bezeichnet. Es gibt prinzipiell zwei Stellen, an denen solch eine Endkonfiguration gespeichert
  39. 39. 4.2. Implementierung 29 Abbildung 4.8.: Erweiterung des Frameworks um Nebenläufigkeit werden kann. Entweder im Und- oder im Join-Zustand. Da diese Menge mit letzterem Typ assoziiert wird, soll diese dort gehalten werden. Die Klasse PseudoState bietet hierfür zudem schon die benötigte Infrastruktur an. Der Anwender des Frameworks ist dafür verantwortlich, dass dem Join-Zustand diese Endkonfiguration bekannt gemacht wird. Die Methode lookup() führt neben der obligatorischen Tiefensuche im Falle des Typs Join zusätzlich die Prüfung aus, ob die angegebenen Zustände aktiv sind. Diese einfache Implementierung birgt jedoch den Nachteil, dass nicht alle Aktionen ausgeführt bzw. Bedingungen geprüft werden, die an den zum Join-Zustand hinfüh- renden Transitionen stehen. Das Klassendiagramm aus Abbildung 4.8 zeigt die Ände- rungen an dem Framework, die zur Realisierung der Nebenläufigkeit notwendig sind. 4.2 Implementierung Im Folgenden wird die Implementierung des Frameworks beschrieben und auf Opti- mierungen aufmerksam gemacht, die durch die Beschränkungen des RCX notwendig wurden. Die Programmquellen sind in dem Paket statechart.base untergebracht. In Abbildung 4.9 ist die Implementierung des Frameworks zu sehen. Diese unterscheidet sich vom prinzipiellen Aufbau her nicht von dem vorgestellten Konzept. Aufgrund der Anforderungen an das Framework wurden jedoch einige Änderungen erforderlich.
  40. 40. 4. Das Statechart-Framework 30 Abbildung 4.9.: Klassendiagramm der Implementierung des Frameworks
  41. 41. 4.2. Implementierung 31 4.2.1 Speicherplatzoptimierungen Insgesamt besteht das vorgestellte Framework aus einer möglichst minimalen Anzahl an Klassen. Allerdings ist dies nur der erste Schritt um Speicherplatz zu sparen. Die nächsten Abschnitte beschreiben die weiteren wesentlichen Optimierungen. Verwaltung von Listen Zunächst fällt auf, dass die zu verwaltenden Listen in einer Klasse über den vom JDK zur Verfügung gestellten Vektor einen enormen Speicherplatzverbrauch verursachen. Selbiges gilt für die Reimplementierung der Vektor-Klasse in LeJOS. Die Gründe hier- für liegen an zwei Stellen: 1. Es werden sehr viele Komfortmethoden angeboten, die die Handhabung der Klas- se vereinfachen sollen. Hierzu zählen z. B. unterschiedliche Parameterlisten. 2. Die Methoden enthalten im Allgemeinen einen Programmcode, der prüft, ob die angegebenen Parameter im Definitionsbereich liegen. Da die Verwaltung der Listen ausschließlich im Framework stattfindet und der An- wender keinen direkten Zugriff auf diese erhält, können die beiden aufgeführten Punkte als Ansatz zur Platzersparnis verwendet werden. Hierzu werden unnötige Methoden nicht implementiert und Sicherheitsüberprüfungen ausgeklammert. Dies führt dazu, dass eine eigene Klasse Vector verwendet wird, die minimale Funktionalität besitzt und somit platzsparend ist. Zusätzlich wird durch die ausschließliche Sichtbarkeit der Klasse in dem Paket statechart.base sichergestellt, dass diese nur innerhalb des Pakets Verwendung findet. Als Basis dient die gleichnamige Klasse aus dem LeJOS-Paket. Verwendung statischer Methoden An einigen Stellen werden Berechnungsmethoden verwendet, die in allen Instanzen einer Klasse verwendet werden und nicht auf Klassenvariablen zugreifen. Hier ist ein einfacher Trick die Verwendung von statischen Methoden. Zeitereignis Da wie in der Konzeption beschrieben kein Timeout-Ereignis als Klasse realisiert ist, die Verarbeitung der Ereignisse aber über die dispatch-Methode stattfindet, wird als Übergabeparameter null als Zeitereignis vereinbart. Die timeout-Methoden erhöhen bei Aufruf nur die aktuelle Zeit im Zustand. Anschließend wird seitens der Klasse Statechart auf dem derzeitigen Zustand die Ereignisverarbeitung angestoßen (siehe Listing 4.1). Objekterzeugung innerhalb des Frameworks Ein essenzieller Punkt ist das Vermeiden von Objekt-Instanziierungen mittels new innerhalb des Frameworks bei der Ereignisverarbeitung. Dies führt zu potenziell un- überschaubar vielen Objekten und somit im Rahmen des RCX schnell zu einem Spei- cherüberlauf. Objekte werden im Framework daher nur während der Konstruktion des
  42. 42. 4. Das Statechart-Framework 32 Listing 4.1: Implementierung des Zeitereignis in den Klassen Statechart und State // S t a t e c h a r t −K l a s s e public void t i m e o u t ( i n t time ) { c u r r e n t S t a t e . t i m e o u t ( time ) ; c u r r e n t S t a t e . d i s p a t c h ( null ) ; } // S t a t e −K l a s s e protected void t i m e o u t ( i n t time ) { currentTime += time ; } Zustandsdiagramms instanziiert. Sobald alle Zustände, Transitionen, Aktionen, Er- eignisse und Bedingungen angelegt wurden, findet nur noch die Ereignisverarbeitung mittels der timeout- und dispatch-Methoden statt. Neue Objekte werden zu diesem Zeitpunkt seitens des Frameworks nicht mehr instanziiert. 4.2.2 Berechnung des LCA Eines der bisher noch nicht berücksichtigten Elemente ist die Berechnung des kleins- ten gemeinsamen Vorfahrens zweier Knoten. Diese findet in der statischen Methode calculateStateSet der Klasse Transition statt. Auffällig ist der Rückgabewert. Hier- bei handelt es sich um ein zweidimensionales Feld, welches die zu (de-)aktivierenden Zustände beinhaltet. Dieses Array wird für jede Transition einmal in der benötigten Größe erzeugt, sodass hier eine optimale Speicherausnutzung stattfindet. Die Berech- nung selbst folgt einem einfachen Algorithmus: 1. Speichere den Pfad vom Startzustand zum Wurzelknoten der Hierarchie. 2. Speichere den Pfad vom Endzustand zum Wurzelknoten der Hierarchie. 3. Wähle die Länge min des kürzeren Pfads. 4. Setze i = 0. 5. Solange i < min ist, vergleiche von der Wurzel ausgehend die Elemente der beiden Pfade an Position i. a) Falls die Elemente gleich sind, setzte i = i + 1. b) Falls die Elemente ungleich sind, stoppe den Vergleich. i − 1 ist der LCA. Für die Umsetzung fällt auf, dass in den Schritten eins und zwei temporäre Variablen benötigt werden, die den Pfad sichern. Da dieser beliebig lang werden kann, muss in
  43. 43. 4.2. Implementierung 33 der Implementierung ein dynamisches Feld verwendet werden. Die Klasse Vector bietet sich hier an, bringt aber auch ein Problem mit sich: In jedem Aufruf des Algorithmus werden zwei Objekte mittels new angelegt. Insgesamt werden bei n Transitionen 2n temporäre Vektoren angelegt und somit unnötiger Speicherplatz verschwendet. Statt- dessen werden außerhalb der statischen Methode zwei ebenfalls statische Hilfsvektoren angelegt, sodass diese wiederverwendet werden können und 2n − 2 Objekte wegfallen. Nach der Berechnung des LCA müssen die zu (de-)aktivierenden Zustände nur noch in das hierzu angelegte Feld kopiert werden. Listing 4.2 zeigt den zugehörigen Quelltext. 4.2.3 Verwendung des Frameworks Ein weiteres Ziel ist die einfache Verwendung des Frameworks. Aus diesem Grund werden öffentlich sichtbare Hilfsfunktionen in den Klassen zur Verfügung gestellt, die die Handhabung erleichtern und bei der Konstruktion syntaktisch korrekter Zustands- diagramme helfen sollen. Hierzu zählen zum Beispiel die Methoden getStart() und respektive getEnd() auf dem Kontext. Hierdurch wird nicht nur sichergestellt, dass der Zugriff auf diese beiden Zustände einfach ist, sondern auch dass ein Kontext nur einen Start- bzw. Endzustand1 besitzen kann. Ähnliche Komfortmethoden bieten die hierarchischen Zustände zur Ermittlung des History-Zustands und die Pseudozustän- de, um für die Vereinigung von Transitionen die Endkonfiguration angeben zu können. Nebenläufige Zustände bieten die Möglichkeit, mittels addRegion() eine neue Region zu ergänzen. Für die so ermittelten Zustände wird der Kontext automatisch gesetzt, sodass der Anwender diesen nicht explizit setzen muss. Eine Besonderheit bieten Zustandsübergänge. Die Verknüpfung von einem Zustand mit einer Transition findet automatisch beim Anlegen selbiger statt. Das heißt, es wird dem Startzustand automatisch mitgeteilt, dass dieser eine neue abgehende Transition erhält. So können Zustandsübergänge mit nur einer Programmzeile angelegt werden. Insgesamt bietet das Framework aufgrund dieser wenigen Hilfsfunktionen und mit der Erweiterbarkeit durch die Interfaces Action, Guard und Event eine sehr einfa- che Programmier-Schnittstelle für den Anwender. Die Verwendung funktioniert immer nach dem selben Muster: 1. Anlegen der Zustände (einfache, hierarchische, nebenläufige, Regionen und Pseu- dozustände). 2. Aufbau der Hierarchie mittels der Methode setContext(). 3. Falls Join-Zustände verwendet werden, die Endkonfigurationen angeben. 4. Erzeugen der Transitionen. Anschließend ist das Zustandsdiagramm einsatzbereit und kann in der Hauptpro- grammschleife verwendet werden, welche Timeout-Signale und Ereignisse an das Dia- gramm sendet. Ein Beispiel einer möglichen Programmschleife ist in Kapitel 7.3 zu 1 DieModellierung lässt mehrere Endzustände zu. Diese können jedoch immer zu einem zusammen- gefasst werden, sodass dies keine Einschränkung darstellt.
  44. 44. 4. Das Statechart-Framework 34 Listing 4.2: Quelltext aus der Klasse Transition zur Berechnung des LCA private s t a t i c V e c tor s t a = new Vector ( 2 0 ) ; private s t a t i c V e c tor s t d = new Vector ( 2 0 ) ; private s t a t i c S t a t e [ ] [ ] c a l c u l a t e S t a t e S e t ( S t a t e s t a r t , S t a t e end ) { // Pfad vom S t a r t zum Wurzelknoten e r m i t t e l n std . c l e a r ( ) ; State s = s t a r t ; while ( s != nu ll ) { s t d . add ( 0 , s ) ; i f ( s . c o n t e x t instanceof H i e r a r c h i c a l S t a t e | | s . c o n t e x t instanceof C o n c u r r e n t S t a t e ) { s = ( State ) s . context ; } e l s e s = nu ll ; } ... // Q u e l l t e x t f ü r s t a a n a l o g // b e r e c h n e n d e s LCA i n t min = s t a . s i z e ( ) < s t d . s i z e ( ) ? s t a . s i z e ( ) : s t d . s i z e ( ) ; i n t l c a = min − 1 ; // G i l t f a l l s s t a r t=end . min i s t mind . 1 i f ( s t a r t != end ) { f o r ( l c a = 0 ; l c a < min ; l c a ++) i f ( s t a . g e t ( l c a ) != s t d . g e t ( l c a ) ) break ; } // a n l e g e n d e s A u s g a b e a r r a y s S t a t e [ ] [ ] s t a t e s = new S t a t e [ 2 ] [ ] ; s t a t e s [ 0 ] = new S t a t e [ s t d . s i z e ( ) − l c a ] ; s t a t e s [ 1 ] = new S t a t e [ s t a . s i z e ( ) − l c a ] ; // k o p i e r e n d e r zu ( de −) a k t i v i e r e n d e n Zustände f o r ( i n t i = s t a t e s [ 0 ] . l e n g t h − 1 , j = l c a ; i >= 0 ; i −−, j ++) s t a t e s [ 0 ] [ i ] = ( State ) std . get ( j ) ; f o r ( i n t i = 0 , j = l c a ; i < s t a t e s [ 1 ] . l e n g t h ; i ++, j ++) s t a t e s [ 1 ] [ i ] = ( State ) sta . get ( j ) ; return s t a t e s ; }
  45. 45. 4.3. Unittests 35 Abbildung 4.10.: Beispieldiagramm für die Umsetzung in den Quelltext finden. Das Beispiel aus Listing 4.3 soll den Aufbau des Quelltextes verdeutlichen. Hierzu wird das in Abbildung 4.10 dargestellte Zustandsdiagramm mit dem Frame- work umgesetzt2 . Durch diesen einfachen und vor allem strukturell immer gleich aufgebauten Quell- text, ist eine weitere Anforderung erfüllt: Die Vereinfachung der Codegenerierung da- hingehend, dass nur eine Klasse generiert werden muss, in der das Framework verwen- det wird. Eine genaue Beschreibung findet sich in Kapitel 7. 4.3 Unittests Im Rahmen der Entwicklung ist das Testen der Software ein stetiger Begleiter. Da- her soll dieser Schritt vollkommen automatisiert durchgeführt werden. Hierzu bietet sich die Verwendung von JUnit (Clark u. a., 2006) als Testumgebung an. Allerdings soll an dieser Stelle nur ein Verfahren erläutert werden, mit dem sich die Seman- tik von Zustandsdiagrammen testen lässt und wie dieser Testaufbau mittels JUnit umgesetzt werden kann. Die eigentlichen Testfälle sind im Anhang A zu finden. Da der RCX-Baustein keine Möglichkeit zum automatisierten Testen anbietet, werden die Prüfungen auf die Laufzeitumgebung für Zustandsdiagramme beschränkt. Das Testen von Zustandsdiagrammen basiert in diesem Ansatz auf einer einfachen Erkenntnis: Der Durchlauf durch das Diagramm lässt sich komplett als eindeutiger Pfad beschreiben. Hierbei wird jeweils registriert und aufgezeichnet, wann ein Zustand aktiviert, deaktiviert oder eine Aktion ausgeführt wird. Das Testen besteht letztlich darin, den durchlaufenen Pfad mit dem Soll-Pfad zu vergleichen. In letzterem wird vor- gegeben, in welcher Reihenfolge die Zustände (de-)aktiviert und Aktionen ausgeführt werden müssen. Hierbei scheinen die nebenläufigen Zustände Probleme zu bereiten, denn theoretisch kann die Reihenfolge, in der die Regionen ein eingehendes Ereignis verarbeiten, immer unterschiedlich sein, je nachdem welcher Prozess als nächstes die CPU-Zeit zugewiesen bekommt. Dies würde ein wesentlich komplexeres Vorgehen erfordern, um die beiden Pfade zu vergleichen, da für jede nebenläufige Region der Pfad aufgeteilt werden müss- te. Um dies zu vermeiden, wird im Folgenden ohne Beschränkung der Allgemeinheit 2 Die verwendeten Aktionen und Signale sind mit Pseudocode angegeben.

×