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
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
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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-
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. 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. 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. 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. 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.
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. 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. 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. 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. 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.