Home
Dokumentation
ElViSS in Aktion
Downloads
ElViSS-Shooter
Kontakt
 

Dokumentation

1    Idee

2    Worum geht´s?

2.1    Was gibt es dazu schon

3    Bedienungsanleitung

4    ElViSS – Architektur

4.1    Bedeutung der Schnittstellen

4.2    Ablauf: Wie wandert der Passagier durch das System

4.3    Thread-Konzept

5    ConfigPool

6    Zeitmaschine

7    Steuerung / Kabine

7.1    Gesamtsteuerung

7.2    Kabinensteuerung

7.3    Kabine

7.4    Etagensteuerung

7.5    Strategie

8    Simulation

8.1    Personenmanager und -generator

8.2    Simulation

9    Monitor

9.1    Kabinenmonitor

10    Visualisierung

10.1    Konzept

10.2    Realisierung

11    Projektorganisation

Quellenverzeichnis

Schlusswort

Anhang 2: Abbildungs- / Tabellenverzeichnis


top

1 Idee

[Aufgabenstellung: Prof. Dr. J. Siedersleben]

Wir betrachten eine Situation mit N parallelen Fahrstühlen (N >= 1) und M Stockwerken (M >= 2).

Wir bauen einen Software-Fahrstuhl, der sich wie ein richtiger Fahrstuhl verhält: Er kann in einem bestimmten Stockwerk halten, er kann nach oben oder unten fahren und in einem bestimmten Stockwerk stehen bleiben. Die Türen sind offen, geschlossen, sie schließen sich oder sie gehen gerade auf. Von besonderer Bedeutung ist dabei das Zeitverhalten: Der Fahrstuhl braucht von einem Stockwerk zum nächsten eine bestimmte Zeit. Ein wichtiges Element dieser Aufgabe ist die virtuelle Zeit, die es erlaubt, ein Testszenario, das in Wirklichkeit Stunden dauern würde, in Sekunden abzuwickeln.

Aufgaben

a) Entwerfen und programmieren Sie die Klasse Fahrstuhl. Verwenden Sie besonderes Augenmerk auf das Zeitverhalten dieser Klasse. Unterscheiden Sie dabei die virtuelle Zeit des Systems (mit der man Tests langsam oder schnell laufen lassen kann), und die Geschwindigkeit des Fahrstuhls (es gibt schnelle und langsame Fahrstühle).

b) Entwerfen und programmieren Sie die Fahrstuhlsteuerung. Diese akzeptiert die Beförderungswünsche der Passagiere (aus Stockwerk K nach oben oder unten) und ermittelt daraus einen Befehl an einen der Fahrstühle.

c) Entwerfen und programmieren Sie eine sinnvolle Visualisierung: Wir wollen sehen, wie die Fahrstühle fahren.

d) Besonders wichtig sind hier die Testszenarien. Bauen Sie Testszenarien, die einen ganzen Geschäftstag simulieren: Die Leute wollen morgens in ihre Büros, gehen mittags zum Essen und abends nach Hause. Ermitteln Sie wichtige Kennzahlen, etwa die durchschnittliche Wartezeit der Passagiere in Abhängigkeit von der Anzahl der Fahrstühle.

e) Überlegen Sie sich, wie sie mögliche Strategien in Regel auslagern können (wann muss welcher Fahrstuhl umkehren?).

f) Weitere Verfeinerungen nach Belieben: Fast Line für die oberen Stockwerke; Fahrstühle nur für die geraden bzw. ungeraden Stockwerke.


top

2Worum geht´s?

Die Verbindung zum realen Fahrstuhlsystem

Bei der Planung des Systems wurde stets im Auge behalten, soweit es ging die Möglichkeit offen zu halten, Softwarekomponenten durch reale Fahrstuhleinheiten austauschen zu können. Dies wurde durch vorteilhaftes Schnittstellendesign und genauer Abgrenzung der einzelnen Komponenten gewährleistet. Somit können einzelne Softwarekomponenten gegebenenfalls durch eine technische Einheit (die durch JNI angebunden wird) ausgetauscht werden. Der Aspekt „Hardware-Software-Schnittstelle“ bleibt in diesem Dokument allerdings außen vor, da er nicht zur Bearbeitung vorgesehen war und womöglich den Rahmen gesprengt hätte. Wer sich dafür interessiert kann sich hier informieren: http://java.sun.com/docs/books/tutorial/native1.1/TOC.html.

Möglichkeiten der Simulation

Das Ziel der Simulation ist es, anhand ihrer Ausgaben das virtuelle Fahrstuhlsystem analysieren zu können und dadurch Rückschlüsse auf reale Systeme ziehen zu können. Dies kann natürlich, aufgrund der sehr komplexen Natur solcher Systeme, nur in begrenztem Rahmen geschehen. Aber auch durch eine kleine Anzahl ausgesuchter Parameter, kann ein echtes Fahrstuhlsystem gut angenähert werden.

Ganz allgemein kann durch die Simulation eines virtuellen Systems sehr schnell und günstig das Verhalten verschiedener Steuerungsalgorithmen auf verschiedene Lasten zu einem meist ausreichenden Maß an Realismus geprüft werden. Zudem kommt noch hinzu, dass durch zweckmäßige Programmausgaben auch dort Engpässe gefunden werden können, wo sie in einem realen System nur schwer zu erkennen sind.

2.1 Was gibt es dazu schon

Nach dem Überblick über die Anforderungen an ElViSS werden hier bereits veröffentlichte Fahrstuhlsimulationen unter die Lupe genommen. Hierbei kommt es in erster Linie darauf an, ihren Leistungsumfang zu zeigen und mit dem von ElViSS zu vergleichen. Dadurch wird ersichtlich inwieweit diese Open Source Projekte die geforderte Funktionalität bieten können.

Ferner wird auf veröffentlichte Literatur eingegangen, die sich mit der grundlegenden Fahrstuhlproblematik beschäftigt, sowie Lösungsansätze zu Konzeption und Implementierung enthält.

Sim Elevator

(Quelle: http://www.kbs.uni-hannover.de/Lehre/Info1/WS97/Projekte/Celtics/index.html)

An der Uni Hannover wurde im Wintersemester 1997 eine Fahrstuhlsimulation als Java-Applet realisiert.

Konzept des "Sim Elevator" (Zitat)

Das vorliegende Programm stellt eine Fahrstuhlsimulation dar. Es besteht aus zwei voneinander unabhängig gesteuerten Fahrstühlen, die sich über vier Etagen hinweg bewegen können. Die maximale Anzahl der Personen pro Fahrstuhl beträgt 5. Die Fahrstühle fahren auf Ruf, d.h. es werden Personen in beliebiger Anzahl zufällig in den einzelnen Etagen generiert, welche die Möglichkeit besitzen ihren Fahrtrichtungswunsch über einen Etagenwahlschalter geltend zu machen. Die Fahrstühle halten nicht in einer Etage, in der sich keine Personen befinden. Hat ein Fahrstuhl alle Aufträge erfüllt und ist leer, so fährt er ins Erdgeschoss zurück und wartet auf einen neuen Fahrtauftrag. Das Programm wurde als Applet realisiert. Die fahrenden Fahrstühle, ein- und aussteigende Personen sowie die Anzeige von Statussymbolen sind grafisch unterstützt worden.

Abbildung 2.1:„Sim Elevator“ als Java-Applet

Programmbeschreibung (Zitat)

Beim Aufruf des Programms wird ein Applet ausgeführt, welches verschiedene Anzeigeelemente beinhaltet. Dies sind im Einzelnen:

  • Der "Starte Simulation" -Knopf, welcher zum Start der Simulation gedrückt werden muss. Möchte man die Simulation beenden, so drückt man auf den "Stoppe Simulation"-Knopf.
  • Die Bezeichnung der Etagen auf der linken Bildschirmseite, die zur Laufzeit unveränderlich sind.
  • Die Listenelemente auf der linken Bildschirmseite (für jede Etage eines) beinhalten die Identifikationsnummern der Personen, die in der jeweiligen Etage warten (ID:*). Steigt eine Person in einen Fahrstuhl ein wird ihre Identifikationsnummer aus dem Listenelement der Etage gelöscht und geht in das des jeweiligen Fahrstuhls über. Die Pfeile geben an, welche Fahrtrichtungswünsche die Personen in den einzelnen Etagen haben.
  • Die Listenelemente der beiden Fahrstühle befinden sich am unteren Bildschirmrand. Hier werden die Identifikationsnummern der Personen eingetragen, die sich in dem jeweiligen Fahrstuhl befinden (ID:*). Steigt eine Person aus dem Fahrstuhl aus, so wird ihre Identifikationsnummer aus dem Listenelement gelöscht. Die Pfeile in den eckigen Kästchen der Listenelemente geben die Fahrtrichtung vor. Wird in ihnen ein "STOP"-Icon dargestellt, so steht der Fahrstuhl in einer Etage.
  • Das große Grafikelement in der Bildschirmmitte symbolisiert das Haus, in dem die Fahrstühle fahren und in dem die Personen erzeugt werden.

Für den Fall, dass eine Person in den Fahrstuhl einsteigt, wird eine kurze Animationssequenz angezeigt. Nachdem der Fahrstuhl die betreffende Etage erreicht hat, in der eine Person wartet, öffnen sich zuerst die Etagentüren, dann die Fahrstuhltüren und die Person (dargestellt durch eine abgeänderte Form des englischen Pfunds) steigt in den Fahrstuhl ein. Beim Aussteigen der Person verläuft es genauso. Mit dem Unterschied, dass eine andere Animationssequenz geladen wird, die eine Darstellung vom Aussteigen der Person ermöglicht.

Vergleich mit ElViSS

ElViSS ist für die Simulation eines gesamten Tages konzipiert. Die Ablaufgeschwindigkeit kann stark erhöht werden, damit der Anwender nicht den ganzen Tag auf seine Simulationsergebnisse warten muss. Im SimElevator fehlt diese Art von Zeitbeschleunigung. ElViSS stellt ein zuvor definiertes Lastprofil von Passagieren über einen bestimmten Zeitraum dar. Der SimElevator ist hier komplett zufällig und somit für die Simulation von realen Fahrstuhlsystemen nur bedingt geeignet, da er keine Stosszeiten mit dichterem Passagierverkehr berücksichtigt. Es können keine unterschiedlichen Steuerungsstrategien für die Kabinen gewählt werden, wie das bei ElViSS der Fall ist. Auch ist der SimElevator auf vier Etagen und zwei Kabinen festgelegt wohingegen ElViSS mit beliebiger Anzahl an Etagen und Kabinen umgehen kann. Darüber hinaus liefert ElViSS eine grafische Auswertung der abgelaufenen Simulation.

Elevator Simulation

(Quelle: http://www.inf.ethz.ch/personal/biere/applets/elsim/)

Die folgende Beschreibung wurde aus dem Englischen übersetzt und möglichst textnah wiedergegeben.

Die Simulation arbeitet in zwei Modi. Der erste Modus wird durch das Drücken des "go" -Knopfes gestartet. Daraufhin läuft die Simulation, von einer Uhr angesprochen, bis der "stop"- oder "reset" -Knopf gedrückt wird. Im zweiten Modus bewegt man sich schrittweise durch die Simulation durch das Drücken des "stepp" -Knopfes.

Der Benutzer kann die Kabine durch Drücken des entsprechenden "press" -Knopfes zur gewünschten Etage rufen. Diese Knöpfe sind gesperrt wenn eine Anforderung in dieser Etage besteht. Beachten sie, dass die Knöpfe zum Rufen der Kabine und die Bedienelemente innerhalb der Kabine nicht getrennt modelliert wurden. Deshalb zeigen wir letztere gar nicht.

Die Simulation beinhaltet vier verschiedene Controller. Der SimpleController bewegt die Kabine zur untersten Etage mit einer Anforderung. Er ist nicht sehr vorsichtig wenn es darum geht die Türbewegungen und das Rauf- und Runterfahren zu koordinieren. Der zweite Controller SafeController ist eine Weiterentwicklung des ersten, weil er nicht das Sicherheitskriterium verletzt, dass die Türen geschlossen bleiben müssen solange die Kabine fährt. Er hat nichts desto trotz immer noch Fehler die mit dem dritten Controller behoben werden.

Der LiveController erfüllt die "Echtheitsanfor-derung", dass jede Kabinenanforderung sofort berücksichtigt wird nachdem der Knopf in einer Etage gedrückt wurde. Die Kabine erreicht diese Etage und öffnet ihre Türen.

Der letzte Controller MinMaxController implementiert das wohlbekannte Fahrstuhlprinzip. Im Wesentlichen hält die Kabine in jeder Etage in der eine Anforderung besteht und fährt nach oben bis die höchste Etage mit einer Anforderung erreicht ist. Dann fährt die Kabine wieder runter bis die niedrigste Etage mit einer Anforderung erreicht ist. Sie hält dabei wieder in jeder Etage in der eine Anforderung besteht.

(Übersetzung des englischen Originaltextes)


 

Abbildung 2.2: Elevator Simulation als Java-Applet (1)

 

Vergleich mit ElViSS

Diese Fahrstuhlsimulation bietet wie der SimElevator keine Zeitbeschleunigung, keine Ergebnisauswertung, keine Möglichkeit verschiedene Lastprofile und Steuerungsstrategien zu verwenden. Sie ist auf eine Kabine für 5 Etagen beschränkt. Im Gegensatz zum SimElevator werden hier die Fahrtwünsche von Hand erteilt und nicht von Programm selbst simuliert. Die Simulation läuft in ersten Modus automatisch ab, wobei sich die Kabine erst in Bewegung setzt wenn ein Fahrtwunsch abgegeben wurde. Im zweiten Modus wird erst ein Fahrtwunsch abgegeben und dieser dann schrittweise durch Knopfdruck abgearbeitet.

Elevator Simulation 2

(Quelle: http://www.iit.edu/~malimuh2/Projects/elevator.htm)

Abbildung 2.3: Elevator Simulation als Java-Applet (2)

Hier wird der Fahrstuhl eines Gebäudes mit zwei Etagen simuliert. Personen werden durch Klicken auf einen der beiden Etagen-Knöpfe (Floor1 und Floor2) erzeugt. Wenn sich der Fahrstuhl nicht in der Etage befindet in der die Person erzeugt wurde fährt er in diese. Die Person steigt ein und der Fahrstuhl bewegt sich in die andere Etage (entweder Etage 1 oder 2). Die Türen öffnen sich und die Person verlässt den Fahrstuhl und die Etage. Die Bewegung des Fahrstuhls macht einen ganzen Uhrzyklus aus, der auf der Uhr links angezeigt wird. Die Etagen- und Fahrstuhllichter werden mit der Bewegung des Fahrstuhls und seiner Fahrtrichtung synchronisiert. Meistens wird jeweils nur eine Person für eine Etage erzeugt.

Diese Anwendung ist ein Applet (erstellt mit Java).

Vergleich mit ElViSS

Wie das vorige so ist auch dieses Programm in seiner Funktionalität auf das manuelle erteilen von Fahrtwünschen beschränkt. Darüber hinaus können nur eine Kabine und zwei Etagen dargestellt werden. Eine Auswertungsmöglichkeit fehlt völlig.


top

3Bedienungsanleitung

ElViSS bietet die Möglichkeit, verschiedene Lastprofile zu erstellen und auszuwerten. Dadurch gibt es Nutzern die Möglichkeit, ihr eigenes Gebäude weitgehend individuell abzubilden und einen Tagesablauf darin zu simulieren.

Einstellungen

Beim Starten des Programms erscheint folgender Dialog zum Einstellen der Profile:

Abbildung 3.1:Konfigurationsoberfläche

Auf der linken Seite können bereits vorhandene Profile ausgewählt werden. Die Einstellungen erscheinen dann in den einzelnen Feldern. Man kann die Einstellungen ändern oder ein neues Profil mit Neues Profil anlegen. Anschließend muss das Profil immer mit Profil speichern gespeichert werden. Überflüssige Profile können mit Profil löschen auch gelöscht werden.

Die einzelnen Felder haben folgende Bedeutung:

Feld Beschreibung Mögliche Werte
Profilname Hier wird der Name des Profils eingegeben Text
Kabinen Anzahl der Kabinen größer 0
Etagen Anzahl der Etagen größer 1
Bildschirmbreite / px Breite des Fensters, in dem die Simulation dargestellt werden soll. In der Regel < als die eingestellte Bildschirm-Auflösung. Werte größer 400 sinnvoll.
Bildschirmhöhe / px Höhe des Fensters, in dem die Simulation dargestellt werden soll. In der Regel < als die eingestellte Bildschirm-Auslösung. Werte größer 300 sinnvoll.
Zeitfaktor Faktor, mit dem die Echtzeit multipliziert werden soll. Einstellungen sind möglich von Echtzeit bis 1000 * Echtzeit.
Virtuelle Zeit für
… Fahrtdauer pro Etage Zeitangabe, wie viele virtuelle Sekunden die Fahrtdistanz von 1 Etage dauern soll. größer 1
… öffnen der Türen Zeitangabe, wie viele virtuelle Sekunden das Öffnen der Türen dauern soll. (Innen/Außentüren werden hier nicht weiter unterschieden) größer 1
… schließen der Türen Zeitangabe, wie viele virtuelle Sekunden das schließen der Türen dauern soll. (Innen/Außentüren werden hier nicht weiter unterschieden) größer 1
… warten der Kabinen in Etage Zeitangabe, wie viele virtuelle Sekunden die Kabine in einer Etage verweilt, bevor sie losfährt. größer 1
Kantinen Etage Etage, in der sich die Kantine befindet. größer 1, aber kleiner als die Anzahl der Etagen.
Anzahl der im Haus beschäftigten Personen Anzahl der Personen, die im Haus beschäftigt sind

größer 1

(Standardeinstellung: 1000)

Maximale Anzahl Passagiere pro Kabine

Anzahl der Personen, die Maximal in einer Kabine Platz finden.

größer 1

(Standardeinstellung: 8)

Arbeitsbeginn

Uhrzeit, zu der die Personen beginnen zu Arbeiten.

Uhrzeit in der Form hh:mm Hier kann keine Gleitzeit angegeben werden. Bsp: 08:00

Mittagszeit

Uhrzeit, zu der die Hauptmittagszeit ist.

Uhrzeit in der Form hh:mm Hier wird die Mittagsstoßzeit angegeben. Bsp: 12:00

Arbeitsende

Uhrzeit, zu der die Personen ihre Arbeit beenden.

Uhrzeit in der Form hh:mm Hier kann keine Gleitzeit angegeben werden. Bsp: 18:00

Strategie für Kabinenzuteilung

Strategie, nach der die Kabinenzuteilung erfolgen soll.

Die SmartStrategy kann zusätzlich noch Parametriert werden.

RandomStrategy : Zufällige Zuteilung

SmartStrategy: Einfache Zuteilungsstrategie

Tabelle 3.1: Konfigurationsfelder

Einstellen der SmartStrategy:

Bei dieser Strategie der Kabinenzuteilung sind 2 Komponenten bedeutend:

  • Distanz der Kabinenposition zum Kabinenfahrtziel
  • Wartezeit des Passagiers, bzw. „Alter“ der Kabinenanforderung

Anhand dieser beiden Kriterien wird eine Auswahlkenngröße ermittelt, anhand derer die beste Kabine ausgewählt wird.

Durch Klicken auf Strategie-Bearbeiten wird ein neuer Dialog geöffnet, mit dem es möglich ist, genau diese beiden Komponenten zu beeinflussen.

Abbildung 3.2: Feineinstellung der SmartStrategy

Hier wird entschieden, welcher Komponente mehr Bedeutung zukommen soll, indem man mit dem Schieberegler die Gewichtung auf Distanz oder Wartezeit setzt. Wenn der Regler ganz links, bzw. ganz rechts steht, wird die jeweils andere Komponente ausgeschaltet.

Einstellen der Visualisierung:

Unabhängig von der Profilerstellung kann man die Visualisierung auswählen. Dazu gibt es 3 Möglichkeiten:

  • keine Visualisierung: wird benötigt, wenn eine hohe Anzahl von Etagen und/oder Kabinen eingestellt ist, bzw. eine kleine Bildschirmauflösung besteht, so dass eine vernünftige Darstellung nicht möglich ist.

    Bei dieser Darstellung wird lediglich die aktuelle Uhrzeit angezeigt.

    Abbildung 3.3: keine Visualisierung

  • einfache Visualisierung: wird benötigt, wenn die Anzahl der Etagen und Kabinen noch darstellbar sind, jedoch Werte überschritten haben, bei denen mit der erweiterten Visualisierung keine sinnvolle Darstellung mehr möglich ist.

    Abbildung 3.4: Einfache Visualisierung

  • Erweiterte Visualisierung: wird benötigt, wenn eine kleine Anzahl von Etagen und Kabinen gewählt wurde. Diese Visualisierungsstufe ist auch mit maximalem Zeitfaktor möglich.

    Abbildung 3.5: Erweiterte Visualisierung

Welche Visualisierung ausgewählt wird, muss am besten selbst ausgetestet werden.

Nachdem die Visualisierung ausgewählt wurde, kann die Simulation mit „Simulation starten“ gestartet werden.

Während der Simulation

Während der Simulation ist es möglich diese zu stoppen (Pause). Anschließend kann man entweder mit der Auswertung weitermachen (Simulation auswerten) oder die Simulation weiterlaufen lassen (Start).

Auswertung der Simulation

Abbildung 3.6: Auswertung

Im Fenster „Auswertung“ können nun 3 beliebige Auswertungen gestartet werden:

Nach Beenden der Auswertung und dem Schließen des Auswertungsfensters, kann die Simulation an der Stelle fortgesetzt werden, an der zuvor die Pause gemacht wurde.


top

4ElViSS – Architektur

Die Architektur ist in 5 Grobkomponenten gegliedert.

  • Zeitmaschine (TimeMachine): stellt die zeitliche Abhängigkeit der Komponenten zueinander her. In ihr wird die Zeitdauer der verschiedenen Funktionen simuliert. Gibt der Simulation die virtuelle Simulationszeit.
  • Simulation: generiert Requests über den gesamten Simulationszeitraum und wertet die gesammelten Daten aus.
  • Monitor: verwaltet die Requests aller Etagen und alle Personen in den Kabinen. Fordert Kabinen bei der Steuerung an.
  • Steuerung (Control): Verwaltet und steuert alle Kabinen und Etagen. Bestimmt welche Kabine wohin fahren muss und welche Türen geöffnet/geschlossen werden müssen. Enthält außerdem eine Strategiekomponente, die entscheidet welches Ziel die Kabine als nächstes anfährt bzw. welche Kabine für eine Kabinenanforderung ausgewählt wird.
  • Visualisierung (Visualization): stellt die Abläufe grafisch dar.

Abbildung 4.1: Übersicht Architektur

4.1Bedeutung der Schnittstellen

IElevator

Bietet alle Funktionen, die ein gewöhnlicher Fahrstuhl hat. Man kann eine Kabine in einer Etage in eine bestimmte Richtung anfordern und ein Fahrtziel für eine Kabine angeben.

IControl

Umfasst die Methoden, die eine Fahrstuhlsteuerung zur Kommunikation mit anderen Komponenten benötigt. Man kann sich an der Steuerung als Beobachter registrieren, um beim Eintreten bestimmter Ereignissen (Ankunft einer Kabine, Schließen/Öffnen der Türen) informiert zu werden. Außerdem können die Positionen aller Kabinen abgefragt werden.

IControlObserver

Klassen, die diese Schnittstelle implementieren können sich an der Steuerung registrieren, um bei bestimmten Ereignissen informiert zu werden.

IFunctionObject

Definiert ein Objekt, das der Zeitmaschine zur Simulation eines Wartevorgangs übergeben werden kann. Nach Ablauf der Wartezeit ruft die Zeitmaschine die Methode execute() des Objekts auf.

ITimeMachine

Stellt die Möglichkeit dar, eine virtuelle Zeit zu simulieren und abzufragen. Außerdem kann ein Wartevorgang ausgeführt werden.

ITimeMachineCtrl

Bietet die Möglichkeit die virtuelle Zeit in der Zeitmaschine zu setzen, den Umrechnungsfaktor zwischen virtueller Zeit und realer Zeit einzustellen sowie die Zeitmaschine zu starten, anzuhalten und zu beenden.

ISimulationCtrl

Mit Hilfe dieser Schnittstelle kann die Simulation gestartet, angehalten, fortgesetzt und beendet werden.

IMonitor

Hiermit kann dem Monitor ein neuer Request übermittelt werden. Dieser reiht in dann in die entsprechende Warteschlange ein.

IMonitorStateInformation

Liefert die Anzahl der Personen pro Etage sowie die Anzahl der ein- und aussteigenden Personen pro Kabine.

IMonitorCfg

Schnittstelle um den Monitor mit den Profildaten zu konfigurieren. Es wird beispielsweise die Anzahl der Kabinen und Schächte gesetzt.

IRequestCollector

Abgeschlossene Requests werden dem IRequestCollector hinzugefügt, damit sie bei der statistischen Auswertung am Ende berücksichtigt werden.

4.2Ablauf: Wie wandert der Passagier durch das System

Der Durchlauf eines Request durch das System ist in 5 Phasen unterteilbar.

Phase 1: Erzeugung des Request und Weitergabe an den Monitor

Abbildung 4.2: Erzeugung des Request und Weitergabe an den Monitor

1. Der Personengenerator erzeugt Personen für den gesamten Tag. Alle Requests werden dem Personenmanager übermittelt.

2. Wenn der Request zur Abarbeitung ansteht, wird dessen Anforderungszeit gesetzt und der Request aus dem Personenmanager geholt. Der Request wird an den Monitor weitergereicht.

3. Der Monitor hängt den Request an die Warteschlange der entsprechenden Etage an und überprüft, ob bereits eine Kabine für die entsprechende Fahrtrichtung angefordert ist. Wenn nicht, fordert er eine neue Kabine bei der Steuerung an.

Phase 2: Ermittlung einer Kabine und Fahrt der Kabine zur Anforderungsetage

Abbildung 4.3: Ermittlung einer Kabine und Fahrt der Kabine zur Anforderungsetage

4. Die Gesamtsteuerung übergibt der Strategie die angeforderte Etage und Fahrtrichtung, sowie die Zustände (aktuelle Etage, aktuelle Fahrtrichtung, und Aufträge) aller Kabinen. Die Strategie übermittelt daraus die optimale Kabine für diese Anforderung und gibt die Kabinennummer x an die Gesamtsteuerung zurück.

5. Die Gesamtsteuerung übergibt der Kabinensteuerung x die Zieletage als neues Ziel und den Befehl zum Losfahren.

6. Die Kabinensteuerung gibt der Kabine den Befehl, dass sie eine Etage in Richtung der Zieletage fahren soll. Die Kabine wartet mit Hilfe eines Funktionsobjektes die entsprechende Zeitdauer in der Zeitmaschine. Nach Ende der Wartezeit überprüft die Kabinensteuerung, ob sie bereits in der Zieletage angekommen ist. Wenn nicht, erteilt sie der Kabine nochmals den Befehl zum Warten. Wenn ja, dann weist sie die Kabine an, die Innentüren zu öffnen und gibt anschließend der Gesamtsteuerung Bescheid, dass sie in ihrer Zieletage angekommen ist.

7. Die Gesamtsteuerung gibt nun der Etagensteuerung der Zieletage den Befehl zum Öffnen der Außentüren im Schacht x. Die Etagensteuerung wartet mit Hilfe eines Funktionsobjektes die dafür benötigte Zeit in der Zeitmaschine. Nachdem die Wartezeit beendet ist, gibt sie an die Gesamtsteuerung zurück, dass die Außentüren offen sind.

8. Die Gesamtsteuerung gibt an alle ihre Beobachter weiter, dass die Kabine x in der Zieletage angekommen ist.

Phase 3: Einsteigen der Personen in die Kabine und Auswählen aller Zieletagen

Abbildung 4.4: Einsteigen der Personen in die Kabine und Auswählen aller Zieletagen

9. Der Monitor setzt in allen Requests, die in die angeforderte Richtung fahren wollen, die Warteendzeit und schickt sie in den Kabinenmonitor x.

10. Alle Requests geben ihre Zieletage an die Gesamtsteuerung weiter. Doppelte Requests werden jedoch nur einmal angegeben.

Phase 4: Fahrt zur Zieletage

Abbildung 4.5: Fahrt zur Zieletage

11. Die Gesamtsteuerung nimmt die Fahrtziele in die Liste der Fahrtziele für Kabine x auf. Anschließend wird der Etagensteuerung der Befehl zum Schließen der Außentür im Schacht x erteilt. Die Etagensteuerung wartet mit Hilfe eines Funktionsobjektes in der Zeitmaschine die Zeitdauer des Türschließens. Nach Ablauf der Wartezeit gibt sie die Meldung „Außentür im Schacht x geschlossen“ an die Gesamtsteuerung zurück.

12. Die Gesamtsteuerung übermittelt der Kabinensteuerung x den ersten Eintrag aus der Liste der Fahrtziele als neues Ziel.

13. Die Kabinensteuerung gibt der Kabine den Befehl, dass sie eine Etage in Richtung der Zieletage fahren soll. Die Kabine wartet mit Hilfe eines Funktionsobjektes die entsprechende Zeitdauer in der Zeitmaschine. Nach Ende der Wartezeit überprüft die Kabinensteuerung, ob sie bereits in der Zieletage angekommen ist. Wenn nicht, erteilt sie der Kabine nochmals den Befehl zum Warten. Wenn ja, dann weist sie die Kabine an, die Innentüren zu öffnen und gibt anschließend der Gesamtsteuerung Bescheid, dass sie in ihrer Zieletage angekommen ist.

14. Die Gesamtsteuerung gibt nun der Etagensteuerung der Zieletage den Befehl zum Öffnen der Außentüren im Schacht x. Die Etagensteuerung wartet mit Hilfe eines Funktionsobjektes die dafür benötigte Zeit in der Zeitmaschine. Nachdem die Wartezeit beendet ist, gibt sie an die Gesamtsteuerung zurück, dass die Außentüren offen sind.

15. Die Gesamtsteuerung gibt an alle ihre Beobachter weiter, dass die Kabine x in der Zieletage angekommen ist.

Phase 5: Aussteigen der Personen und Merken des Requests zur späteren Auswertung

Abbildung 4.6: Aussteigen der Personen und Merken des Requests

16. Der Kabinenmonitor x übergibt alle Requests aus der Kabine x, die in der Zieletage aussteigen möchten an den Monitor zurück. Der Monitor setzt in jedem zurückgegebenen Request die Done-Time. Die so abgestempelten Requests werden an die Request-Auswertung übergeben.

17. In der Request-Auswertung werden alle abgearbeiteten Requests nach festgelegten Kriterien ausgewertet.

4.3Thread-Konzept

Die Anzahl der Threads in ElViSS wird maßgeblich durch die Implementierung der Zeitmaschine beeinflusst. Die Zeitmaschine realisiert die virtuelle Zeit des Systems und stellt die zeitlichen Abhängigkeiten der Funktionen her. Aus den beiden unterschiedlichen Implementierungen der Zeitmaschine ergeben sich für das System zwei unterschiedliche Thread-Konzepte.

Threads in ElViSS:

Unabhängig von der Implementierung der Zeitmaschine gibt es immer folgende Struktur:

Der Main-Thread steuert das Hauptprogramm und umfasst alle Komponenten außer der Zeitmaschine und der Visualisierung.

Die Visualisierung bringt 2 weitere Threads mit:

  • Der AWT-Event-Thread wird automatisch erzeugt, sobald die Oberfläche gestartet wird. Dieser Thread ermöglicht es dem Benutzer im laufenden Betrieb über die Oberfläche auf das System einzuwirken, er horcht sozusagen auf die Benutzereingaben.
  • Für die Animation benötigt die Visualisierung einen eigenen Thread, der unabhängig vom Hauptprogramm, 24 Mal in der Sekunde für die Aktualisierung der Daten und deren erneute Darstellung sorgt.

Im Lauf der Arbeit an ElViSS sind zwei unterschiedliche Implementierungen für die Zeitmaschine entstanden (ausführliche Beschreibung, siehe Kap 6 Zeitmaschine ):


top

5ConfigPool

Wegen der Einfachheit und Flexibilität wurde für die globale Konfiguration des Systems beschlossen, eine XML-Datei als Medium zu verwenden. In dieser sind unterschiedliche Profile für unterschiedliche Konfigurationen gespeichert. Für eine komplette Übersicht der Konfigurationsmöglichkeiten siehe Kapitel 3 – Bedienungsanleitung. Als XML-Parser wurde der DOM-Parser von XERCES gewählt, da er überall verfügbar und leicht zu handhaben ist.

Ein Ausschnitt aus der Konfigurationsdatei:

<profile>
<profilename>test2</profilename>
<environmentconfig>
<logfileconfigpath>elev_cfg\log4j.config</logfileconfigpath>
<minimalFrequency>1</minimalFrequency>
<screenWidth>1000</screenWidth>
<screenHeight>800</screenHeight>
<timedelay>100</timedelay>
</environmentconfig>
<commonconfig>
<elevators>4</elevators>
<floors>5</floors>
</commonconfig>
<controlconfig>
<velocity>3</velocity>
<dooropentime>2</dooropentime>
<doorclosetime>2</doorclosetime>
<cabintimeout>5</cabintimeout>
<strategy>
<name>SimpleStrategy</name>
<strategyfactor>0.28</ strategyfactor>
</strategy>
</controlconfig>
<simulatorconfig>
<peoplequantity>1000</peoplequantity>
<cafeteriafloor>3</cafeteriafloor>
<maxnumpass>5</maxnumpass>
<inrush>
<time>08:00</time>
<param>0.0</param>
<seed></seed>
</inrush>
<outrush>
<time>18:30</time>
<param>0.0</param>
<seed></seed>
</outrush>
<caferush>
<time>12:00</time>
<param>0.0</param>
<seed></seed>
</caferush>
</simulatorconfig>
</profile>

Das Auslesen der Konfigurationsdatei wird durch die Singleton-Klasse ConfigPool bewirkt. Mit deren Methoden kann auf die einzelnen Parameter der verschiedenen Profile zugegriffen werden.

Die Klasse ProfileWriter fügt der Konfigurationsdatei ein neues Profil hinzu. Dies geschieht durch einfache Dateiausgabe von Zeichenketten.


top

6Zeitmaschine

Abbildung 6.1: Die Komponente Zeitmaschine

Um eine Zustandsänderung, welche eine bestimmte Zeitspanne benötigt, zu programmieren, gibt es mehrere Ansätze. Ich werde hier 2 Arten vorstellen, eine Variante basiert auf mehreren Threads, die andere auf einem einzigen Thread.

Grundlegendes

In einem Punkt sind alle Ansätze gleich: eine lang andauernde Zustandsänderung wird programmiert, indem eine bestimmte Zeit gewartet wird, und danach eine Methode aufgerufen wird, welche die Zustandsänderung bewirkt. Die Ansätze unterscheiden sich jedoch in der Art des Wartens.

Konzept

Die Zeitmaschine nimmt über eine Methode schedule() ein Funktionsobjekt und eine Zeitdifferenz entgegen, um ihr mitzuteilen dass dieses Funktionsobjekt nach Ablauf dieser Zeitspanne aufgerufen werden soll (Funktionsobjekte sind dazu da um ausgeführt zu werden!). Diese Zeitspanne muss nicht der realen Zeit entsprechen, vielmehr wird die reale Zeit intern auf eine virtuelle Zeit abgebildet. Somit ist schnellerer oder langsamerer Ablauf der Zustandsänderung gegenüber der realen Zeit möglich. Die Zeitmaschine soll zudem gestartet und wieder angehalten werden können und die Abbildung reale Zeit zu virtuelle Zeit sollte verändert werden können um sie zu beschleunigen.

Variante 1 – Zustandsänderungen multithreaded

Bei dieser Variante erzeugt die Methode schedule() für jedes entgegengenommene Funktionsobjekt einen eigenen Thread, der mit der übergebenen Zeitdifferenz gestartet wird und nach Ablauf dieser das Funktionsobjekt ausführt.

Variante 2 – Zustandsänderungen singlethreaded

Hier wird in der Methode schedule() aus der Zeitdifferenz zuerst der Ausführzeitpunkt des Funktionsobjektes berechnet. Das Funktionsobjekt wird danach in einer sortierten Datenstruktur abgelegt, welche den Ausführzeitpunkten Funktionsobjekte zuordnet. Ein einzelner Thread durchläuft eine getaktete Endlosschleife, in der ständig geprüft wird, ob die momentane (virtuelle) Zeit mit der Zeit der ersten Funktionsobjekte in der Datenstruktur übereinstimmt. Ist dies der Fall, werden diese Funktionsobjekte ausgeführt und aus der Datenstruktur entfernt.

Vergleich der zwei Varianten

Singlethreaded

Multithreaded

Deterministisch: wegen des rein sequenziellen Ablaufs erhält man stets das gleiche Ergebnis.

Nichtdeterministisch: Ergebnisse können auch bei gleicher Konfiguration voneinander abweichen. Abhängig vom Umfeld: Betriebssystem, Betriebssystemauslastung, Java-Virtual-Machine-Einstellungen.

Der Grad der Komplexität bleibt gleich, jedoch ist der Code unterschiedlich verteilt:

Komplexität im Algorithmus

Komplexität in Datenstrukturen

Tabelle 6.1: Unterschiede Multi- Singlethread-Zeitmaschine

Realisierung

Variante 1 – multihreaded

Realisierung der schedule-Methode:

public void schedule(IFunctionObject fo, int delaySec) {
final int ds = delaySec;
final IFunctionObject fo = o;
new Thread() {
public void run() {
int timeout = 0;
try {
timeout = (int) (stretchFac * (ds * 1000));
sleep(timeout);
fo.execute();
}
catch (Exception e) {...}
}
}.start();
}

Erläuterung: das Funktionsobjekt fo wird nach dem Ablauf der Zeit delaySec durch ihre Methode execute() ausgeführt. Der Faktor stretchFac ist für die Zeitmaschine klassenweit bekannt und ist zur Beschleunigung des Vorgangs gedacht. Außer der schedule()-Methode ist prinzipiell nichts mehr nötig. Die Java-Virtual-Machine wird für die Wartezustände genutzt.

Variante 2 - singlethreaded

Realisierung der schedule-Methode:

public void schedule(IFunctionObject fo, int delaySec) {
// Zeit des Aufrufs in der Zukunft
Long i = new Long(time + delaySec);

// Mehrere zeitgleiche Tasks werden in Listen zusammengeführt
PriorityQueue p = (PriorityQueue) taskMap.get(i);

if (p == null) {
p = new PriorityQueue();
}
p.insert(fo, fo.getPriority());
taskMap.put(i, p);
}

Erläuterung: der Parameter delaySec stellt die Wartezeit dar, nach der das Funktionsobjekt fo ausgeführt werden soll. Zuerst wird der Zeitpunkt in der Zukunft berechnet, an dem es ausgeführt werden soll. Danach wird es in eine Datenstruktur der Klasse PriorityQueue übergeben, die Prioritäten sortiert ist. Diese Datenstruktur sammelt alle Funktionsobjekte gleichen Ausführzeitpunktes. Die Prioritäten werden in diesem System von den anderen Komponenten zwar ignoriert, sind aber aus Gründen der Erweiterbarkeit implementiert. Danach wird die Datenstruktur einer weiteren Datenstruktur der Klasse TreeMap zugeführt, die nach Ausführzeitpunkten sortiert ist.

Realisierung des Arbeits-Threads (prinzipieller Ablauf):

public void run() {
while (true) {
try {
waitSomeTime();
runTasks();
}
catch (InterruptedException e) {…}
}
}
}

Erläuterung: nach Ablauf eines Zeitintervalls werden die Funktionsobjekte aufgerufen. Dazu sieht die Methode runTasks() in der Datenstruktur taskMap, in der sich die Datenstrukturen mit den Funktionsobjekten befinden, an der Wurzel nach, ob sie zum momentanen Zeitpunkt ausgeführt werden soll. Wenn ja, dann wird die Wurzel entnommen und alle Funktionsobjekte in ihr werden ausgeführt.


top

7Steuerung / Kabine

Die Steuerung enthält eine Komponente Gesamtsteuerung (CommonControl), welche die Koordination und Informationshaltung der Steuerung übernimmt. Sie weiß über alle Kabinenzustände, Ziele der einzelnen Kabinen und über die Zustände der Etagentüren Bescheid. Sie kommuniziert mit einer Kabinensteuerung (CabinControl) pro Kabine (Cabin) bzw. einer Etagensteuerung für jede Etage. Von außen werden über die Schnittstelle IElevator Kabinenanforderungen und Fahrtwünschean sie übermittelt. Objekte, welche die Schnittstelle IControlObserver implementieren, können sich bei der Gesamtsteuerung anmelden, um über Ereignisse informiert zu werden. Die benötigten Methoden stellt das Interface IControl zur Verfügung Bei Ankunft einer Kabine und Öffnen/Schließen einer Etagentür werden alle diese Beobachter benachrichtigt.

Die Wahl, welche Kabine für eine Kabinenanforderung ausgewählt wird bzw. welches ihrer Ziele die Kabine als nächstes anfährt, wird in der Komponente Strategie (Strategy) getroffen.

Eine Kabinensteuerung hat die Aufgabe, die Innentüren einer ihr zugeordneten Kabine zu öffnen und zu schließen. Außerdem muss sie ein von der Gesamtsteuerung gesetztes Ziel anfahren und bei Ankunft der Kabine in der Zieletage dies der Gesamtsteuerung melden. Diese Funktionen, welche unterschiedlich lange dauern und mehr Zeit benötigen wie ein normaler Funktionsaufruf, werden mit Hilfe eines Funktionsobjektes (FunctionObject) in der Zeitmaschine simuliert.

Die Etagensteuerung (FloorControl) wird von der Gesamtsteuerung beauftragt, die Außentüren in einem Schacht zu öffnen und zu schließen. Dies wird wie in der Kabinensteuerung mit Hilfe von Funktionsobjekten in der Zeitmaschine simuliert.

Abbildung 7.1: Übersicht Steuerung

7.1Gesamtsteuerung

Konzept

Die Gesamtsteuerung nimmt Kabinenanforderungen sowie Fahrtwünsche mit einer Kabine in eine Etage an. Sie stellt also den eigentlichen, aus der Realität bekannten Fahrstuhl dar.

Die Gesamtsteuerung enthält für jede Kabine eine Kabinensteuerung und für jede Etage eine Etagensteuerung. Außerdem verfügt sie über eine Strategiekomponente. Die Gesamtsteuerung weiß die Fahrtziele und die aktuellen Positionen aller Kabinen. Sie fordert die Kabinensteuerung auf, ein bestimmtes Ziel anzufahren. Den Etagensteuerungen übermittelt sie Befehle zum Öffnen und Schließen der Außentüren eines Fahrstuhlschachtes.

Andere Komponenten können sich an der Gesamtsteuerung registrieren, damit sie bei Ankunft einer Kabine in einer Etage bzw. beim Öffnen/Schließen einer Außentüre informiert werden.

Abbildung 7.2: Übersicht Gesamtsteuerung

Realisierung

IElevator ist die Außensicht der Steuerung. Es bietet alle Möglichkeiten, die ein realer Fahrstuhl besitzt. Es kann eine Kabine angefordert werden und eine Fahrt mit einer Kabine in eine Zieletage durchgeführt werden.

public interface IElevator {
// mit Kabine cabin in Etage floor fahren
void orderDestination(int cabin, int floor);

// aus Etage floor wird eine Kabine in Richtung direction // angefordert
void orderCabin(int floor, Direction direction);
}

Wird eine Kabinenanforderung entgegengenommen, werden der Strategiekomponente die Anforderungsetage, die Anforderungsrichtung sowie die aktuellen Standorte, Beschäftigungsstatus und Fahrtziele aller Kabinen übermittelt. Diese ermittelt daraus diejenige Kabine, welche für die Anforderung am Besten geeignet ist. Anschließend wird ein Fahrtwunsch mit dieser Kabine in die Anforderungsetage abgegeben.

public void orderCabin(int floor, Direction direction) {
List currentFloorsOfCabinControl = new ArrayList();

// Positionen der Kabinen abfragen
getCabinPositions();
for (int i = 0; i < cabinControls.length; i++) {
currentFloorsOfCabinControl.add(
new Integer((int) positions[i]));
}

// Beschäftigungsstatus aller Kabinen abfragen
List cabinBusyStates = new ArrayList();
for (int i = 0; i < cabinControls.length; i++)
cabinBusyStates.add(new Boolean(cabinControls[i].isBusy()));

// über die Strategie wird eine Kabine für diese Anforderung ausgewählt
int cabinNr = strategy.findOptimalCabin(floor, direction,
destinationsOfCabinControls,
currentFloorsOfCabinControl,cabinBusyStates);

// Zieletage für die Kabine hinzufügen (Anforderungsetage
// als Ziel in der Kabine drücken)
orderDestination(cabinNr, floor);
}

Bei einem Fahrtwunsch wird die übermittelte Zieletage in die Liste der Zieletage der Kabinensteuerung eingefügt. Ist die Kabine unbeschäftigt, werden die Außentüren im aktuellen Standort der Kabine geschlossen.

public void orderDestination(int cabin, int floor) {

// Ziel in die Liste der Zieletagen der Kabinensteuerung // eintragen
addDestinationToCabinControl(cabin, floor);

// wenn Kabinensteuerung nicht beschäftigt, nächstes Ziel // setzen und Außentüren in der Etage schließen
if (cabinControls[cabin].isBusy() == false) {
cabinControls[cabin].setBusy(true);
updateCabinControlDestination(cabin);
closeOuterDoor(cabinControls[cabin].getCurrentFloor(),cabin);
}
}

Nach Abschluss des Türschließens wird die Kabine in Bewegung gesetzt.

public void outerDoorsClosed(int floor, int shaft) {
if (cabinControls[shaft].hasDestination() == false)
updateCabinControlDestination(shaft);
cabinControls[shaft].move();
}

Kommt eine Kabine an ihrer Zieletage an, wird die Zieletage aus der Liste der Zieletagen dieser Kabinensteuerung entfernt, über die Strategiekomponente die nächste Zieletage ermittelt und der Kabinensteuerung übergeben. Dann werden die Außentüren in der Zieletage geöffnet.

public void cabinArrived(int cabin, int floor) {

// Ankunftsetage aus Zielliste entfernen
removeDestinationFromCabinControl(cabin);

// neues Ziel setzen
updateCabinControlDestination(cabin);

// Außentüren in der Etage öffnen
openOuterDoor(floor, cabin);
}

Sind die Außentüren geöffnet, werden alle Beobachter über die Ankunft der Kabine informiert.

public void outerDoorsOpened(int floor, int shaft) {
notifyObserversCabinArrived(shaft, floor);
}

Bei Ankunft in der Zieletage startet die Kabinensteuerung einen Timer. Läuft dieser ab, wird die Gesamtsteuerung informiert. Diese schließt dann die Außentüren in der aktuellen Etage der Kabine.

public void waitingTimeOutRaised(ICabinControl cabinControl) {

// Außentüren in der aktuellen Etage der Kabine schließen
closeOuterDoor(cabinControl.getCurrentFloor(), cabinControl.getNumber());
}

Nachdem die Türen geschlossen sind, wird der Kabinensteuerung ein neues Fahrtziel, falls noch weitere Fahrtziele für diese Kabinensteuerung vorhanden sind, übermittelt. Anschließend wird die Kabinensteuerung zum Fahren aufgefordert.

7.2Kabinensteuerung

Konzept

Die Kabinensteuerung kennt die aktuelle Position der Kabine, das aktuelle Ziel, die Bewegungsrichtung sowie den Zustand der Innentüren ihrer Kabine. Die Kabinensteuerung kann angewiesen werden, eine neue Zieletage zu setzen, die Kabine losfahren zu lassen sowie die Innentüre der Kabine zu schließen oder zu öffnen.

Abbildung 7.3: Übersicht Kabine/Kabinensteuerung

Realisierung

Wenn beim Losfahren keine Zieletage gesetzt ist, wird der Status der Kabine auf „unbeschäftigt“ gesetzt. Ist dagegen eine Zieletage gesetzt, werden als erstes die Innentüren geschlossen und die Kabine auf den Status „beschäftigt“ gesetzt.

public boolean move() {
if (moves)
return false;

// wenn kein aktuelles Ziel mehr vorhanden ist, dann wird der
// Status der Kabine auf notBusy gesetzt
if (!hasDestination) {
setBusy(false);
return false;
}

// sonst Türe schließen (und losfahren)
else {
closeDoor();
return true;
}
}

Ist der Schließvorgang beendet wird überprüft, ob die Kabine ihre Zieletage erreicht hat.

public void doorsClosed() {
checkCurrentFloor();
}

Ist die Zieletage noch nicht erreicht, wird die Kabine angewiesen, genau 1 Etage in Richtung der Zieletage zurückzulegen und der Zustand der Kabine auf „fahren“ gesetzt. Wurde die Zieletage erreicht, so wird das Öffnen der Innentüren der Kabine veranlasst und der Zustand der Kabine auf „nicht fahren“ gesetzt.

private void checkCurrentFloor() {
if (currentFloor < currentDestination) {
movingDirection = up;
cabin.move(up);
moves = true;
}
if (currentFloor > currentDestination) {
movingDirection = down;
cabin.move(down);
moves = true;
}
if (currentFloor == currentDestination) {
moves = false;
openDoor();
}
}

Hat die Kabine 1 Etage zurückgelegt wird die aktuelle Etage aktualisiert und abermals überprüft, ob die Zieletage erreicht ist.

public void arrived() {
if (movingDirection.equals(Direction.UP))
currentFloor++;
else
currentFloor--;
checkCurrentFloor();
}

Wenn das Öffnen der Kabinentüren beendet ist wird der Status der Kabine auf „ Türe offen und kein Ziel“ gesetzt, ein Wartetimer gestartet und die Gesamtsteuerung über das Erreichen der Zieletage informiert.

public void doorsOpened() {
doorOpen = true;
cabin.startWaitTimeOut();
hasDestination = false;
commonControl.cabinArrived(cabinNumber, currentFloor);
}

Ist der Wartetimer abgelaufen, wird die Gesamtsteuerung darüber informiert wird, dass die Wartezeit in dieser Etage zu Ende ist.

public void waitTimeOut() {
commonControl.waitingTimeOutRaised(this);
}

7.3Kabine

Konzept

Die Kabine kann genau eine Etage in eine bestimmte Richtung fahren, ihre Innentüren öffnen und schließen sowie ihre bisher zurückgelegte Wegstrecke zwischen 2 Etagen zurückgeben. Das Fahren und Öffnen/Schließen der Türen wird in der Zeitmaschine mit Hilfe eines Funktionsobjektes simuliert.

Abbildung 7.4: Übersicht Kabine

Realisierung

Wird eine Kabine zum Fahren aufgefordert, wird die Abfahrtszeit gesetzt und die Bewegungsrichtung festgehalten. Außerdem wird ein Funktionsobjekt erzeugt und der Zeitmaschine mit der benötigten Zeitdauer eines Fahrvorganges übergeben. Mit Hilfe des Funktionsobjektes kann der Fahrvorgang in der Zeitmaschine nachgebildet werden.

public void move(Direction direction) {
movingDirection = direction;
startTime = timeMachine.getTime().getTime();
IFunctionObject arrived = new CabinArrived(cabinControl);
timeMachine.schedule(arrived, velocity);
}

Nach Ablauf der benötigten Zeitdauer ruft die Zeitmaschine die execute() Methode des Funktionsobjektes auf. In unserem Falle wird die Kabinensteuerung über die Ankunft der Kabine in der nächsten Etage informiert.

public class CabinArrived implements IFunctionObject {
private ICabinControl cabinControl;

public CabinArrived(ICabinControl cabinControl) {
this.cabinControl = cabinControl;
}

public void execute() {
cabinControl.arrived();
}
}

Das Öffnen und Schließen der Türen funktioniert analog zum Fahrvorgang.

Die bisher zurückgelegte Wegstrecke wird aus der Zeit, die seit der Abfahrt aus der Etage vergangen ist, und der Geschwindigkeit der Kabine berechnet. Dabei wird die bisher vergangene Zeit durch die Geschwindigkeit (Sekunden pro Etage) dividiert. Als Resultat erhält man einen Wert zwischen 0 und 1, welcher den bisher zurückgelegten Weg in Prozent darstellt. Dabei bedeutet 0, dass man sich noch in der Abfahrtsetage befindet und 1, dass die nächste Etage erreicht wurde.

public double getPosition() {
int millis = timeMachine.getMillis();
long time = timeMachine.getTime().getTime();
int diff = (int) (time - startTime);

// wenn seit Startzeitpunkt keine Zeit vergangen ist 0
// zurückgeben, sonst Wert zwischen 0 und 1 ausrechnen
if ((diff == 0) && (millis == 0))
return 0;
else {

// Zeit in ms, die seit dem Losfahren der Kabine verstrichen ist
double past = (double) (diff * 1000 + millis);

// noch schnell die Geschwindigkeit in double
double dspeed = (double) (velocity * 1000);

// aus der Geschwindigkeit des Fahrstuhls die Wegstrecke errechnen
double ret = past / dspeed;

// Werte > 1 und < 0 korrigieren (z.B. 1.0000423)
if (ret > 1.0)
ret = 1.0;
if (ret < 0.0)
ret = 0.0;

// bei Fahrt nach unten, zurückgelegten Weg negativ zurückgeben
if ((movingDirection != null) && (movingDirection.equals(Direction.DOWN))) {
ret *= -1;
}
return ret;
}
}

7.4Etagensteuerung

Konzept

Eine Etagensteuerung ist nur für das Öffnen/Schließen ihrer Türen zuständig. Für jeden Fahrstuhlschacht existiert jeweils eine Tür pro Etage.

Abbildung 7.5: Übersicht Etagensteuerung

Realisierung

Das Öffnen einer Etagentür erfolgt mit Hilfe eines Funktionsobjektes und der Zeitmaschine. Dem Funktionsobjekt wird mitgegeben, in welcher Etage und in welchem Schacht eine Tür geöffnet wird. Anschließend wird es der Zeitmaschine mit einer für das Schließen benötigten Zeit übergeben. Es ist zu beachten, dass für die Wartezeit immer der halbe Wert der Öffnungs-/Schließzeiten für die Türen aus dem ConfigPool genommen wird. Im ConfigPool steht die gesamte Wartezeit für die Innen- und Außentüren. In der Etagensteuerung wird diese Wartezeit durch 2 dividiert und dann abgerundet.

public void openDoor(int shaft) {
timeMachine.schedule(new DoorsOpened(this, shaft), doorOpenTime);
}

Nach Ablauf der benötigten Zeitdauer ruft die Zeitmaschine die execute() Methode des Funktionsobjektes auf. In unserem Falle wird die Etagensteuerung darüber informiert, dass die Tür in diesem Schacht nun offen ist.

public class DoorsOpened implements IFunctionObject {
private IFloorControl floorControl = null;
private int shaft;

public DoorsOpened(IFloorControl floorControl, int doornumber) {
this.floorControl = floorControl;
this.shaft = doornumber;
}

public void execute() {
floorControl.doorsOpened(shaft);
}
}

Sind die Außentüren in einem Schacht geöffnet, wird die Gesamtsteuerung informiert.

public void doorsOpened(int shaft) {
commonControl.outerDoorsOpened(floorNumber, shaft);
}

7.5Strategie

Konzept

In der Verantwortung der Kabine liegt es, für eine Kabinenanforderung diejenige Kabine zu finden, die am Besten diese Anforderung erfüllen kann. Zusätzlich entscheidet sie noch, welche Etage aus einer Liste von Zieletagen eine Kabine als nächstes anfahren soll.

Zum Auswählen der optimalen Kabine für eine Anforderung wird der Strategie die Anforderungsetage, die Richtung in der die Person fahren will und für jede Kabine eine Liste der bisherigen Ziele, die aktuellen Etage und der Beschäftigungsstatus mitgegeben.

Für das Auswählen des nächsten Fahrzieles erhält die Strategie eine Liste mit allen Zieletagen für diese Kabine. Aus dieser Liste wird abhängig von den verschiedenen Implementierungen ein Eintrag dieser Liste als neue Zieletage zurückgegeben.

Abbildung 7.6: Übersicht Strategie

Realisierung

Die Strategie wurde in zwei verschiedenen Implementierungen realisiert. RandomStrategy gibt sowohl für die optimale Kabine bei einer Kabinenanforderung als auch für die nächste Zieletage immer einen gültigen Zufallswert zurück.

SmartStrategy liefert für die optimale Kabine bei einer Kabinenanforderung eine Kabine nach folgendem Algorithmus.

a) Kabine suchen, die in der Anforderungsetage steht

b) Kabine suchen, die nicht beschäftigt ist und am nächsten zur Anforderungsetage steht

c) Kabine suchen, welche die Anforderungsetage als Zieletage anfährt

d) Kabine suchen, welche an der Anforderungsetage vorbeifährt (sie aber nicht als Ziel hat) und in die gleiche Richtung, wie in der Anforderung angegeben wurde, fährt

e) Kabine suchen, die am wenigsten beschäftigt ist (am wenigsten Ziele hat)

Der nächste Schritt wird dabei immer nur ausgeführt, wenn im aktuellen Schritt keine Kabine gefunden wurde, welche auf die Bedingung passte. Im letzten Schritt wird auf jeden Fall eine Kabine gefunden. Es wird das Ergebnis des Suchlaufs zurückgegeben, welcher als erstes eine Kabine findet.

Für die Auswahl der nächsten Zieletage aus der Liste der Zieletagen einer Kabine wird für jedes Ziel eine Auswahlkenngröße berechnet. Das Ziel mit der höchsten Auswahlkenngröße wird ausgewählt. Falls es die höchste Auswahlkenngröße mehrfach gibt, wird die erste Kabine mit dieser Auswahlkenngröße ausgewählt. Die Auswahlkenngröße eines Zieles wird mit Hilfe folgender Formel berechnet:

Auswahlkenngröße = Gewichtungsfaktor * (maximale Anzahl Etagen - (Differenz zur aktuellen Etage)) + (1-Gewichtungsfaktor) * (Wartezeit)

Der Gewichtungsfaktor wird verwendet um eine Bevorzugung der "Differenz zur aktuellen Etage" bzw. der "Wartezeit" zu ermöglichen. Der Gewichtungsfaktoren darf nicht negativ sein und muss kleiner als 1 sein. Ein Gewichtungsfaktor = 0,7 hat beispielsweise eine Bevorzugung der "Differenz zur aktuellen Etage" zur Folge. Ein Gewichtungsfaktor = 0 hat eine Nichtberücksichtigung der "Differenz zur aktuellen Etage" zur Folge. Der Gewichtungsfaktor wird aus dem ConfigPool ermittelt.

public int chooseNextDestination(Collection destinations, int currentFloor) {
if (destinations.size() > 0) {
// maximale Auswahlkenngröße
float maxSelectSize = -1;

// Etagennummer mit der maximalen Auswahlkenngröße
int destinationMaxSelectSize = -1;

// Auswahlkenngröße der aktuellen Kabine
float selectSize = -1;

// Wartezeit der Zieletage (entspricht ungefähr Index
// in der Collection + 1, da die Einträge in ihrer
// zeitlichen Reihenfolge in die Collection hinzugefügt
// werden. Nur grobe Näherung!)
int waitingTimeOfDestination = 1;

// alle Auswahlkenngrößen berechnen und die größte merken
Iterator iterator = destinations.iterator();
while(iterator.hasNext()){
Integer destination = (Integer) iterator.next();
selectSize=calculateSelectSize(destination.intValue(),
currentFloor, waitingTimeOfDestination);
if (selectSize > maxSelectSize) {
destinationMaxSelectSize = destination.intValue();
maxSelectSize = selectSize;
}
waitingTimeOfDestination++;
}
return destinationMaxSelectSize;
}
else
return IStrategy.DESTINATIONS_EMPTY;
}
}


private float calculateSelectSize(int destination, int currentFloor, int waitingTime) {
int difference = Math.abs(destination - currentFloor);
difference = numberOfFloors - difference;
return factorDifference * difference + (1-factorDifference) * waitingTime;
}


top

8Simulation

Abbildung 8.1: Gesamtbild der Komponente Simulation

8.1Personenmanager und -generator

Motivation

Wie im richtigen Leben, müssen die Leute, die schließlich einen der Fahrstühle benutzen werden, irgendwo erzeugt werden. Hier kommt der Personenmanager, zusammen mit dem Personengenerator, ins Spiel. Der Personengenerator erzeugt Personen, die zu Fahrtwunsch-Objekten reduziert sind, da sie nur die für das System relevanten Daten, wie Zielstockwerk oder Abfahrtszeitpunkt enthalten. Der Personenmanager hingegen koordiniert die erzeugten Fahrtwünsche und bereitet sie für die Weitergabe an die Simulation vor.

Konzept

Im Prinzip erzeugt der Personengenerator aufgrund bestimmter Grundvoraussetzungen einen Satz von Fahrtwünschen. Die erwähnten Grundvoraussetzungen bestimmen die Art und Weise, wie die Daten erzeugt werden.

Zu den Grundvoraussetzungen gehören:

  • Anzahl der Leute die an einem Arbeitstag das Gebäude betreten: die Gesamtlast.
  • Die Lage einer Kantine: damit die Leute, nachdem sie zu Beginn der Simulation ihr Zielstockwerk erreicht haben, nicht bis zum Ende der Simulation dort bleiben müssen, wird ein spezielles Stockwerk, die Kantine, eingeführt. Somit können die Personen zu einem bestimmten Zeitpunkt die Kantine als neues Ziel ansteuern und dem System wird dadurch eine realitätsnahe Eigendynamik zugewiesen.
  • Anzahl der Stockwerke: ein Gebäude besteht aus einer festen Anzahl von Stockwerken. Der Personengenerator wird direkt durch diese Anzahl beeinflusst, da die Leute auf diese Stockwerke verteilt werden müssen.
  • Verteilungskriterien: die Art und Weise, wie die Leute an einem Arbeitstag auf das Gebäude verteilt werden, dazu gehört:
    • Verteilung nach Uhrzeit: es muss Zeitpunkte geben, an denen sich die Aktivitäten der Personen häufen, z. B. Morgens um 8.00 Uhr häufen sich die am Gebäude ankommenden Personen, um 12.00 Uhr häufen sich die zur Kantine gehenden Personen und um 17.00 Uhr die nach Hause gehenden Personen.
    • Verteilung auf die Stockwerke: die Personen werden auf die Stockwerke gleichmäßig verteilt (ausgenommen der Kantine, welche in der Realität auch nur von 2 Köchen und 3 Bedienungen belegt wird und hier vernachlässigt werden). Die Möglichkeit besteht dennoch, die Personen nicht gleichmäßig auf die Stockwerke zu verteilen, sondern bestimmten Stockwerken eine höhere Personendichte zuzuschreiben.

Personengenerator

Der Fahrtwunsch

Ein Fahrtwunsch besteht im einfachsten Fall aus einer Startzeit, zu der die Fahrt erfolgen soll, einem Ziel- und einem Quellstockwerk. Das Zielstockwerk ist notwendig um dem Monitor mitzuteilen für welches Stockwerk er eine Kabine anfordern muss. Das Quellstockwerk braucht der Monitor, um den Fahrtwunsch in die richtige Warteschlange einzureihen.

Hinzu kommen noch zu statistischen Zwecken:

  • Der Zeitpunkt, an dem der Auftrag erledigt wurde, d. h. die Person ist im gewünschten Stockwerk angekommen und hat den Fahrstuhl verlassen.
  • Der Zeitpunkt, an dem die Person in den Fahrstuhl eingestiegen ist. Dies ist notwendig, um die reine Fahrtzeit ausrechnen zu können, denn nach dem Absenden des Fahrtwunsches kann die Person noch eine gewisse Zeit in einer, zum Stockwerk gehörenden, Warteschlange verbringen, bis sie tatsächlich im Fahrstuhl „steht“, welche die reine Fahrtzeit verfälschen würde.
  • Die Kabinennummer: damit die Belastung der einzelnen Kabinen nachgewiesen werden kann.
  • Das Quellstockwerk.

Das Java-Interface des Fahrtwunsches:

public interface IRequest {
Time getTrigger();
Time getDone();
void setDone(Time t);
Time getEntering();
void setEntering(Time t);
int getDestination();
int getSource();
Direction getDirection();
int getCabinNumber();
void setCabinNumber(int number);
}

Der Personengenerator erzeugt unter den oben bereits genannten Voraussetzungen die Fahrtwünsche, welche die Personen darstellen.

Pro Person werden 4 Fahrtwünsche erzeugt:

  1. Fahrtwunsch zum Ankunftszeitpunkt: Zielstockwerk ist eines der Stockwerke, ausschließlich des Kantinenstockwerkes. Das Quellstockwerk ist Stockwerk 0, das Erdgeschoss. Die Stockwerksnummer wird gleichmäßig auf die Fahrtwünsche verteilt. Der Ankunftszeitpunkt wird um einen festen Zeitpunkt, z. B. 8.00 Uhr, normalverteilt. Um dies zu erreichen, kommt die Methode nextGaussian() aus der Klasse java.util.Random() zum Einsatz. Der Rückgabewert dieser Funktion wird auf das Intervall –1 bis 1 begrenzt. Ein weiterer fester Wert, z. B. 2 Stunden, wird mit dem begrenzten Rückgabewert multipliziert. Beispiel: Rückgabewert: -0.5, fester Wert: 2 Stunden = 7200 Sekunden => 7200 * -0.5 = -3600. Das Ergebnis wird vom Ankunftszeitpunkt, z. B. 8.00 Uhr, abgezogen: 8.00 Uhr = Sekunde 8 * 3600 = 28800; 28800 – 3600 = 25200. D. h. die Person kommt um 7.00 Uhr (= Sekunde 25200) an. Zum Zeitpunkt 8.00 Uhr hin häufen sich die Ankünfte (durch die Normalverteilung).
  2. Fahrtwunsch am „Arbeitsende“: ähnlich dem Fahrtwunsch bei Ankunftszeitpunkt werden nun die Quellstockwerke gleichmäßig auf die Fahrwünsche verteilt. Die Arbeitsende-Zeitpunkte werden ebenso wieder um einen festen Zeitpunkt, wie beispielsweise 17.00 Uhr, normalverteilt. Das Zielstockwerk ist hier Stockwerk 0, also das Erdgeschoss, denn die Leute verlassen das virtuelle Gebäude durch den Haupteingang. Der Zeitpunkt des Verlassens wird genauso errechnet wie der des Ankunftszeitpunktes.
  3. Fahrtwunsch zur Kantine: wie schon aus den vorigen 2 Punkten bekannt, gibt es einen festen Zeitpunkt, um die sich die Fahrtwünsche zur Kantine normal verteilen. Das Quellstockwerk ist eines der Stockwerke, ausgenommen des Kantinenstockwerks, Zielstockwerk ist das Kantinenstockwerk. Ausgegangen wird davon, dass alle sich im Gebäude befindlichen Personen etwa um 12.00 Uhr, je nach Zufall etwas (maximal 1 Stunde) davor oder danach, zur Kantine begeben.
  4. Fahrtwunsch von der Kantine zurück ins Arbeitsstockwerk: der logische Schluss auf den Fahrtwunsch zur Kantine ist, dass es einen Fahrtwunsch zurück geben muss. Quellstockwerk ist das Kantinenstockwerk und das Zielstockwerk ist eines der restlichen Stockwerke und wird wieder gleichmäßig auf die Fahrtwünsche verteilt. Der feste Zeitpunkt ist abhängig vom Zeitpunkt des Fahrtwunsches zur Kantine. Er wird zu diesem addiert und die Zeitpunkte der einzelnen Fahrtwünsche werden wieder um diesen Normalverteilt.

Realisiert wird die Fahrtwunscherzeugung durch eine Methode entsprechend etwa folgendem Pseudo-Code:

GENERIERE_FAHRTWUENSCHE()
FOREACH PERSON
STOCKWRK = ERZEUGE_GLEICHVERTEILTE_ZUFALLSZAHL()
ZEITPKT = ERZEUGE_NORMALVERTEILTE_ZUFALLSZAHL()
FOREACH FAHRTWUNSCHTYP
ERZEUGE_NEUEN_FAHRTWUNSCH(FWNSCHTYP, STOCKWRK, ZEITPKT)
FUEGE_NEUEN_FAHRTWUNSCH_ZU_LISTE_HINZU
SORTIERE_FAHRTWUNSCHLISTE_NACH_STARTZEIT()

Zusätzlich werden noch zufällig Fahrtwünsche erzeugt, die den Arbeitsverkehr zwischen den Stockwerken darstellen. Ein Beispiel: Person X muss um 9.22 Uhr von Stockwerk 3 nach Stockwerk 2.

Realisierung

Für jede Sekunde des Tages (insgesamt 86400) wird per Zufall bestimmt, ob an dieser Sekunde ein Fahrtwunsch ausgelöst werden soll. Dazu wird eine feste Prozentzahl verwendet. Eine Zahl von 1.1% bzw. 0.011 würde bedeuten, dass ungefähr alle 15 Minuten ein Fahrtwunsch erzeugt wird. Die Ziel- und Quellstockwerke werden auf die Fahrtwünsche gleichmäßig verteilt. Folgender Pseudo-Code stellt diesen Sachverhalt noch mal dar:

FOREACH SEKUNDE_DES_TAGES
ZUFALLSZAHL = ERZEUGE_GLEICHVERTEILTE_ZUFZAHL_ZWISCHEN_0_UND_1()
IF ZUFALLSZAHL <= 0.011 THEN
ERZEUGE_FAHRTWUNSCH()
ENDIF

Personenverwalter

Der Personenverwalter implementiert folgende, einfache Schnittstelle:

public interface IPersonManager {
public int getWorkingQueue(int floor);
public int getNextRequests(RequestPackage requests);
}

Der Personenmanager notiert alle Fahrtwünsche, die er mit der ausgegeben hat. Dies ist implementiert durch Zähler für jedes Stockwerk, die jedes mal inkrementiert werden, wenn ein Fahrtwunsch ausgegeben wird, der das jeweilige Stockwerk als Ziel hat, und dekrementiert, wenn es im Fahrtwunsch als Quellstockwerk angegeben ist. Die Methode getWorkingQueue() gibt den Zustand der Zähler zurück. Da die Stockwerksnummern zufällig vergeben werden, ist mit diesen Werten vorsichtig umzugehen. Sie repräsentieren lediglich im Mittel vernünftige Werte und sind zur visuellen Darstellung ungeeignet, weil es hier sogar möglich wäre, dass sich eine negative Anzahl Personen in einem Stockwerk befindet. Außerdem nehmen sie die Ankunft im Zielstockwerk, sowie die tatsächliche Abfahrtszeit vorweg, was auch nicht der Tatsache entspricht.

Die Fahrtwünsche werden durch die Methode getNextRequests() ausgegeben. Dies erfolgt nach folgendem Schema:

Die aufrufende Klasse (z. B. die Simulation) bekommt von getNextRequests() Fahrtwünsche. Diese Fahrtwünsche enthalten alle denselben Startzeitpunkt in der Zukunft, oder im Minimalfall in der Gegenwart, an dem sie bearbeitet werden sollen. Aufgabe der aufrufenden Klasse ist, diese zum besagten Zeitpunkt zu bearbeiten oder bearbeiten zu lassen.

Realisierung

  • Die Fahrtwünsche befinden sich im Personenverwalter in einer sortierten Liste, welche vom Personengenerator erzeugt wurde.
  • Die ersten n > 0 Fahrtwünsche in der Liste besitzen den gleichen Startzeitpunkt.
  • Die Methode getNextRequests() extrahiert die ersten Fahrtwünsche mit gleicher Startzeit und gibt sie über das Objekt RequestPackage requests zurück.

8.2Simulation

Konzept

Die Simulation ist für die Bewältigung von Steueraufgaben, für einen zeitlich koordinierten Ablauf des Programms und der späteren Auswertung der Ergebnisse zuständig.

Steuermethoden

Als Steuermethoden stehen zur Verfügung:

  • Starten der Simulation: startet die Hauptschleife der Simulation.
  • Stopp/Pause der Simulation: hält die Simulation an.
  • Funktion zur Beeinflussung der Simulationsgeschwindigkeit.

Alle diese Steuermethoden werden direkt an die Zeitmaschine weitergegeben.

Die Hauptschleife

Solange die Simulation nicht beendet wurde, befindet sie sich in einer Schleife. In dieser Schleife werden kontinuierlich folgende Schritte abgearbeitet:

  1. Vom Personenmanager werden neue Fahrtwünsche abgeholt: diese Fahrtwünsche enthalten alle die gleiche Bearbeitungszeit in der Zukunft oder in der Gegenwart.
  2. Anhand der Bearbeitungszeit in den Fahrtwünschen wird ein Funktionsobjekt erzeugt, welches an die Zeitmaschine übergeben wird (Die Zeitmaschine wartet, bis der Bearbeitungszeitpunkt erreicht ist, und führt das Funktionsobjekt aus). Das Funktionsobjekt übergibt beim Ausführen die Fahrtwünsche an den Monitor.
  3. Wenn das Funktionsobjekt die Fahrtwünsche an den Monitor übergeben hat, ruft es erneut die Routine des Simulators auf, welche die Fahrtwünsche abholt. Somit ergibt sich eine Schleife und der Zyklus beginnt wieder bei Punkt 1.

Realisierung

mainloop = new IFunctionObject() {
public void execute() {
sendRequests(); // wenn aufgeweckt wird, Request senden
worker(); // weiter mit nächstem Job
}
};
worker(); // Simulation anstossen
...

private void worker() {
...
pm.getNextRequests(r);
...
timemachine.schedule(mainloop,delay);
}

private void sendRequests() {
for(i = 0; i < new_requests.length; i ++) {
monitor.acceptRequest(new_requests[i]);
}
}

Erläuterung: die Simulation besteht im Wesentlichen aus einer Hauptschleife (hier: mainloop), die als Funktionsobjekt realisiert ist. Dieses führt 2 Methode aus, eine Methode, die Fahrtwünsche an den Monitor übergibt (sendRequests()), und eine andere, welche die Fahrtwünsche abholt (worker()). Die Methode worker() dient zusätzlich noch dem Zweck, das Funktionsobjekt wieder der Zeitmaschine zu übergeben. Somit entsteht eine Schleife.


top

9Monitor

Die generierten Requests aus der Simulation können nicht direkt an die Steuerung weitergegeben werden. Es wird eine Komponente zur Verwaltung benötigt, die den jeweiligen Status der Requests kennt. Diese Komponente ist der Monitor.

Konzept

Allgemein:

Der Monitor stellt das Bindeglied zwischen der Simulation und der Steuerung dar. Er weiß wo sich jeder Request/Passagier gerade befindet und regelt das Ein- und Aussteigen in und aus den Kabinen. Hierbei teilt er jedem Passagier die Ein- und Ausstiegszeit mit. Der Monitor erhält anfangs aus dem ConfigPool (über IMonitorCfg) die Anzahl der Etagen und Kabinen die er überwachen soll.

Abbildung 9.1: Monitor mit Nachbarkomponenten

Konkret:

Der Monitor erhält einen Request von der Simulation (über IMonitor) und ordnet ihn dessen Startetage zu. Dann überprüft er ob für die Etage schon eine Kabine in die Richtung angefordert ist, die im Request enthalten ist. Wenn nicht fordert der Monitor von der Steuerung (über IElevator) eine Kabine in die entsprechende Richtung an. Ist die Kabine eingetroffen (über IControlObserver) lässt er alle Passagiere die in dieser Etage aussteigen wollen raus und so viele Wartende einsteigen bis die maximale Passagierkapazität erreicht oder die Etage leer ist. Hierbei werden den aussteigenden Passagieren die Ausstiegszeiten mitgegeben und den einsteigenden Passagieren die Einstiegszeiten. Sind nun noch Passagiere in der Warteschlange die ebenfalls einsteigen wollten, fordert der Monitor erneut eine Kabine von der Steuerung an. Die abgearbeiteten Requests (aussteigende Passagiere) gibt der Monitor an die Simulation (über IRequestCollector) zur Auswertung zurück.

Abbildung 9.2: Monitor, bildliche Darstellung

Realisierung

Der Monitor gibt je nach Passagierandrang Kabinenanforderungen (von außerhalb der Kabine) und Fahrtwünsche (aus dem Inneren) über das Interface IElevator an die Steuerung weiter. Bei einer Kabinenanforderung teilt der Monitor der Steuerung mit in welcher Etage eine Kabine benötigt wird und in welche Richtung Passagiere in dieser Kabine fahren möchten. Sendet der Monitor von innerhalb einer Kabine einen Zielwunsch ab, sagt er der Steuerung von welcher Kabine der Wunsch ausgegangen ist und welche Etage die gewünschte Zieletage darstellt.

Hat eine Kabine eine Zieletage erreicht muss das dem Monitor mitgeteilt werden. Die Steuerung kennt jedoch keinen konkreten Monitor sondern nur allgemeine Komponenten. Diese müssen eine bestimmte fest definierte Schnittstelle bereitstellen, über die sie von der Steuerung über das Eintreffen einer Kabine benachrichtigt werden können. Diese Schnittstelle heißt IControlObserver und sieht wie folgt aus:

public interface IControlObserver {
void cabinArrived(int cabin, int floor);
void openDoor(int cabin, int floor);
void closeDoor(int cabin, int floor);
}

Dem hinter IControlObserver stehenden Monitor wird über cabinArrived mitgeteilt, dass eine Kabine mit bestimmter Nummer in einer bestimmten Etage angekommen ist. Der Monitor arbeitet nun folgende Schritte der Reihe nach ab:

public void cabinArrived(int cabin, int floor) {
this.monitors[cabin].setCurrentFloor(floor);

// die Personen die in diesem Stockwerk aussteigen möchten
// aussteigen lassen
setLeavingPassengers(cabin, floor);

// die einsteigenden Personen aus floor in cabin-monitor umhängen
List borders = new ArrayList(); // einsteigende Passagiere
borders = enterCabin(cabin, floor);

// alle Personen die in die Kabine eingestiegen sind geben
// ihre Fahrtwünsche ab
orderDestination(cabin, borders);

// alle Personen die keinen Platz mehr in der Kabine haben
// (abhängig von der Fahrtrichtung) ordern eine neue Kabine
if (borders.size() > 0){
IRequest request = (IRequest) borders.get(0);
Direction direction = request.getDirection();
orderCabin(floor, direction);
}
}

Das Aussteigen der Passagiere überwacht dabei der Kabinenmonitor jeder Kabine (siehe Kapitel 9.1 Kabinenmonitor ).

Den Einsteigevorgang überwacht der Monitor selbst. Er holt sich die aktuelle Passagierkapazität der betreffenden Kabine und lässt wartende Passagiere einsteigen bis die Kapazität der Kabine erschöpft ist. Hierbei achtet der Monitor darauf, dass nur Passagiere einsteigen die in die Richtung wollen in welche die Kabine fährt. Ist die Kabine gefüllt und es stehen noch Passagiere draußen in der Warteschlange, die ebenfalls in der Kabine mitfahren wollten, fordert der Monitor eine neue Kabine in dieselbe Richtung an.

9.1Kabinenmonitor

Konzept

Jeder Kabinenmonitor weiß für seine Kabine die aktuelle Passagieranzahl, die maximale Passagierkapazität und in welcher Etage die Kabine zum Ein-/Aussteigen angehalten hat. Er regelt, dass jeder Passagier in der richtigen Etage wieder aussteigt.

Realisierung

Jeder Passagier weiß, in welcher Etage er die Kabine verlassen möchte. Hält die Kabine in einer Etage an, fragt der Kabinenmonitor jeden seiner Passagiere ob er aussteigen möchte. Antwortet einer mit true, lässt ihn der Kabinenmonitor aussteigen.

public List leaveCabin() {
List leavingPassengers = new ArrayList();
if (this.passengerInside.size() > 0) {
Object o;
IRequest r = new Request();
Iterator i = this.passengerInside.iterator();
while (i.hasNext()) {
o = i.next();
if (o.getClass().equals(r.getClass())) {
r = (Request) o;

// fragen ob Passagier in dieser Etage aussteigen möchte
if (r.leaveCabin(this.currentFloor)){
leavingPassengers.add(r);
}
}
}
this.passengerInside.removeAll(leavingPassengers);
}
return leavingPassengers;
}


top

10Visualisierung

Die Visualisierung bereitet die Daten der Simulation und der Steuerung grafisch auf.

10.1Konzept

In diesem Kapitel wird das Konzept der Visualisierung, die Idee, welche dahinter steckt und die Komponenten und deren Beziehungen beschrieben. Auf technische Details wie Klassen und Quellcode wird hier soweit wie möglich verzichtet. Informationen hierzu befinden sich in Kap. 10.2 Realisierung .

Die Visualisierung trägt selbst nichts zur Ablauflogik des Systems bei. Deshalb arbeitet sie nach dem Filmprinzip, das heißt der Zustand von Simulation und Steuerung wird 24mal pro Sekunde abgefragt und der Bildschirm neu gezeichnet. Da das menschliche Auge nicht mehr als 24 Bilder in der Sekunde verarbeiten kann entsteht der Eindruck eines Films, und die Simulation wird flüssig dargestellt.

ElViSS kann jederzeit ohne Visualisierung arbeiten. Dies macht besonders bei schnellen Simulationsläufen Sinn. Die Darstellung am Bildschirm kostet Zeit. Läuft die Simulation schneller als die neuen Daten gezeichnet werden können, ist eine visuelle Darstellung nicht mehr sinnvoll. Der Benutzer sieht nicht mehr die aktuelle Situation, da die Simulation im Hintergrund schon weiter gelaufen ist, beziehungsweise die Darstellung ist nicht mehr flüssig. Außerdem belegt die Visualisierung in dieser Situation zusätzliche Ressourcen, was zu einem Performanceverlust der Simulation führt.

Es kann zwischen drei Versionen der Visualisierung gewählt werden.

  • Version 1:
    In der einfachsten Version wird nur der Programmfortschritt durch die virtuelle Zeit angezeigt.
  • Version 2:
    Die einfache Visualisierung ist so minimal als möglich gehalten und stellt nur den statischen Zustand der Simulation dar. Mittels primitiver Grafikroutinen von Java2D werden hier die Personen gemäß ihres Zustandes auf den Etagen und in den Kabinen gezeichnet. Zustandsübergänge wie das Ein- und Aussteigen der Passagiere werden nicht dargestellt. Es bewegen sich nur die Kabinen und deren Insassen.
  • Version 3:
    Die erweiterte Visualisierung ist vollständig bebildert und stellt zusätzlich die verschiedenen Zustandsübergänge der Simulation dar. Der Benutzer kann das Ein- und Aussteigen der Passagiere verfolgen.

Das Konzept ist in Version 2 und 3 dasselbe. Es sind lediglich die Zeichenmethoden der einzelnen Klassen verschieden. Deshalb wird im Weiteren nur auf die erweiterte Visualisierung eingegangen. Zu jeder Komponente der erweiterten Visualisierung gibt es eine entsprechende Komponente mit der Namenserweiterung „simple“, die stattdessen verwendet werden kann.

Die Visualisierung ist in 3 Komponenten aufgeteilt:

Abbildung 10.1: Aufbau der Visualisierung

Haus

Das Haus stellt die Arbeitsumgebung der Visualisierung dar und ist deren zentrale Komponente. Hier werden die Simulationsdaten aufbereitet und alle weiteren Aktionen angestoßen. Bei der Initialisierung wird die Arbeitsumgebung eingerichtet. Die im ConfigPool angegebene Anzahl an Etagen und Kabinen wird erzeugt, und die zum Zeichnen des Hauses nötigen Bilder werden geladen.

Die animierte Darstellung der Simulations- und Steuerungsdaten findet in einem eigenen Thread, der Animation statt. Die Animation fragt 24 Mal in der Sekunde die aktuellen Werte der Simulation und Steuerung ab, verarbeitet diese und erzeugt den Film.

Die Simulation liefert Informationen über die wartenden Passagiere auf den jeweiligen Etagen, sowie über einsteigende und aussteigende Passagiere. Das Haus gibt diese Daten an die zuständigen Etagen und Kabinen weiter.

Die Steuerung liefert dem Haus Informationen, wann eine Kabine ihr Zielstockwerk erreicht hat, und wann die Türen zu öffnen und zu schließen sind.

Nachdem alle Daten aktualisiert sind wird das Haus neu gezeichnet.

Raum

Beim Zeichnen der Etagen werden alle Personen dieser Etagen neu gezeichnet. Dasselbe passiert beim Zeichnen der Kabinen. Wie viele Personen befinden sich in welcher Etage/ Kabine? Wie kann auf sie zugegriffen werden? Als Besitzer kennen Etagen und Kabinen ihre Personen.

Die Etagen und Kabinen benötigen dafür folgende Eigenschaften:

  • Personen können hinzugefügt werden
  • Personen können wieder entfernt werden (das entspricht einem Besitzerwechsel der Person beim Einsteigen in eine Kabine)
  • Die Etage/ Kabine muss Auskunft über ihre Personen geben.
  • Um die Personen richtig zu platzieren muss die Breite der Etage/ Kabine bekannt sein

Die Komponente Raum hat diese Eigenschaften und erfüllt somit alle Anforderungen eines Besitzers.

Etage

Eine Etage ist eine spezielle Ausprägung eines Raumes.

Im Haus werden entsprechend der Angaben im ConfigPool alle Etagen erzeugt, und jede Etage mit Ihrer Nummer initialisiert. Die Etage hat die Aufgabe die Personen auf ihr zu verwalten und sich selbst, sowie alle Personen zu zeichnen.

Kabine

Eine Kabine ist eine spezielle Ausprägung eines Raumes.

Das Haus erzeugt die im ConfigPool angegebene Anzahl von Kabinen und initialisiert sie mit einer Nummer. Kabinen verwalten ihre Personen und erhalten ihre Positionsdaten zum Zeichnen von der Steuerung. Gezeichnet werden die Kabine und alle Passagiere.

Person

Die Komponente Person stellt den Benutzer des Fahlstuhls dar.

Damit eine Person gezeichnet werden kann, muss bekannt sein ob sie sich auf einer Etage, oder in einer Kabine befindet.

Beispiel: Läuft eine Person auf der 3. Etage zur Kabinentür, verändert sie ihre horizontale Koordinate während die vertikale Koordinate immer die der Etage bleibt. Fährt eine Person mit in einer Kabine, so verändert sich ihre vertikale Position entsprechend der Kabinenposition.

Um sich zu zeichnen, muss die Person die Koordinaten der Kabine in der sie steht kennen.

Woher weiß die Person wo sie ist?

Um zu wissen wo sie sich befindet, wird dieser Person eine Etage bzw. Kabine als Besitzer zugeordnet. Über diesen Besitzer kann die Person Ihre vertikale Koordinate bestimmen. Bei der Erzeugung ist der Besitzer jeweils die Etage auf der die Person erscheint. Unter bestimmten Voraussetzungen kann eine Person ihren Besitzer wechseln. Das ist zum Beispiel der Fall, sobald die Person einen Auftrag zum Einsteigen erhalten hat und die Position der Kabine erreicht hat. Die Kabine wird dann der neue Besitzer der Person. Hat die Person das Ziel erreicht und verlässt sie die Kabine, wird die Zieletage Besitzer dieser Person.

Eine Person kann 3 Zustände annehmen: 0: Wurde gerade erzeugt 1: Auftrag zum Betreten einer Kabine 2: Auftrag zum Verlassen einer Kabine.

Abhängig von diesen Zuständen und dem aktuellen Besitzer werden die Koordinaten zur Darstellung der Person ermittelt. Besitzer einer Person können Etagen und Kabinen sein.

Dialoge und Hauptkomponenten zur Programmdarstellung

Die Programmoberfläche wird unabhängig von der Visualisierung aus folgenden Komponenten des Paketes main zusammengesetzt:

  • ProfileFrame: Einstiegsdialog, der die Auswahl und Konfiguration der Simulationsprofile ermöglicht.
  • HouseFrame: Hauptframe für die einzelnen Komponenten der Visualisierung. Dieser Rahmen bleibt während der gesamten Programmlaufzeit bestehen, nur die Inhalte werden verändert
  • HouseFrameSimple: wie HouseFrame, nur für die vereinfachte Visualisierung
  • ButtonView: Steuerbereich, der zur Interaktion mit der Simulation während des Betriebs zur Verfügung steht. Über diesen Steuerbereich kann ebenfalls die Anzeige der Simulationsergebnisse ausgewählt werden
  • EvaluationView: Dialog, der die verschiedenen Auswertungsmöglichkeiten bereitstellt.

Programmfluss durch die Visualisierung

In den folgenden Schritten erklären wir wie der Benutzer ab Systemstart durch die Komponenten der Visualisierung läuft.

a) Beim Systemstart in ElvissMain wird ein Konfigurations-Dialog, ProfileFrame, erzeugt. In diesem Dialog werden die Simulationsprofile angelegt und bearbeitet.

b) Ist ein Simulationsprofil ausgewählt, wird die Simulation gestartet. Dabei kann optional die Art der Visualisierung angegeben werden.

c) Der eigentliche Programmstart erfolgt in ElevatorStart. Hier wird alles erzeugt, was zur Simulation nötig ist. Für die Visualisierung ist hier die Erzeugung des HouseFrame wichtig. Der HouseFrame stellt die Umgebung für die Visualisierung bereitstellt.

d) Ein Bereich zur Interaktion mit der Simulation(Start, Stop, Beenden, Auswertungen) wird im HouseFrame zur Verfügung gestellt, der ButtonView.

Im HouseFrame wird die eigentliche Visualisierung über eine Fabrik, HouseFactory, erzeugt.

Abbildung 10.2: Programmfluss der Visualisierung a) bis e)

Die Fabrik kann 3 verschiedene Visualisierungen an den HouseFrame zurückgeben:

Version 1:

e) Die TimeView aktualisiert alle 42 ms die virtuelle Zeit von der Zeitmaschine.

Abbildung 10.3: Zeitanzeige

Version 2 und 3: Der Programmfluss in der einfachen und der erweiterten Visualisierung ist identisch. Diese beiden Versionen unterscheiden sich lediglich in ihren Zeichenmethoden. Deshalb ist die folgende Beschreibung für beide Versionen gültig.

f) Bei der Initialisierung von VHouse / VHouseSimple wird das Haus angelegt und die im ConfigPool angegebene Anzahl an Kabinen und Etagen erzeugt.

g) Beim Start der Animation wird ein eigener Thread gestartet. Dieser Thread fragt die Warteschlangen auf den Etagen, sowie ein- und aussteigende Passagiere von IMonitorStateInformation, sowie

h) die Kabinenpositionen von IControl ab.

i) VHouse iteriert über alle Etagen, ermittelt entsprechend der Warteschlange die Anzahl wartender Personen und fügt diese der Etage hinzu.

j) Personen auf den Etagen werden veranlasst in eine Kabine zu steigen, d.h. es wird über alle Etagen und Kabinen iteriert. Alle Personen die einen Auftrag zum Einsteigen haben wandern in die entsprechende Kabine.

k) Fahrende Personen steigen aus den Kabinen aus. Es wird über alle Etagen und Kabinen iteriert. In jeder Etage steigt nur die Anzahl an Personen aus den Kabinen, bei denen ein entsprechender Auftrag vorliegt.

Abbildung 10.4: Programmfluss in der einfachen/ erweiterten Visualisierung f) bis k)

Anschließend wird der Bildschirm neu gezeichnet:

l) Die Animation ruft paint() von VHouse auf. Diese besorgt von der Steuerung die Kabinenpositionen, und ruft die Zeichenmethoden aller

m) Kabinen und

n) Etagen auf. Die wiederum die

o) Zeichenmethoden aller ihrer Personen aufruft.

Abbildung 10.5: Aufrufsequenzen der Zeichenmethoden

p) Anschließend wird von der Zeitmaschine die virtuelle Zeit abgefragt und gleich gezeichnet.

q) Über das IControlObserver wird das VHouse über angekommene Kabinen, und

r) das Öffnen und Schließen der Türen informiert, und

s) die entsprechenden Aktionen der Kabinen werden angestoßen.

Abbildung 10.6: Programmfluss in der einfachen/ erweiterten Visualisierung l) bis t)

10.2Realisierung

Für alle Komponenten (Haus, Raum, Etage, Kabine, Person) gibt es je 1 Interface, welches die Methoden beschreibt und 2 Implementierungen des Interfaces (einfache und erweiterte). Im Folgenden wird nur auf die Erweiterte Visualisierung eingegangen.

Die Kabine besteht z.B. aus folgenden Klassen:

  • IVCabin (Interface)
  • VCabinSimple (einfache Visualisierung)
  • VCabin (erweiterte Visualisierung)

Um bei den Klassen eine Verwechslung z.B. mit der eigentlichen Kabine, Cabin, zu vermeiden, haben alle Klassen ein V für Visualization vorangestellt.

Haus

Das Haus selbst arbeitet als eigener Thread, da der Hauptthread auf das Eventhandling reagieren muss. Dieser Thread arbeitet 24-mal pro Sekunde (also jede 42. Millisekunde) folgende Aktionen ab:

  • Holen der Daten aus dem Monitor (getData()):

    // Anzahl der wartenden Personen pro Etage
    private int[] waitingQueue;

    // Anzahl Personen die aus Etage i in Kabine j einsteigen
    private int[][] boardingPersons, leavingPersons;
    waitingQueue = monitor.getWaitingQueue();
    boardingPersons = monitor.getBoardingPersons();
    leavingPersons = monitor.getLeavingPersons();

    Der Monitor gibt dabei nur die Differenz der neuen wartenden, bzw. ein- und aussteigenden Personen zurück.

    Anschließend werden die Personen auf den Etagen neu generiert oder angehalten ein- bzw. auszusteigen.

    // Personen zusteigen lassen
    for (int i = 0; i < floors.length; i++) {
    for (int j = 0; j < cabins.length; j++) {
    size = floors[i].getPersons().size();
    for (int k = 0; k < boardingPersons[i][j]; k++) {

    // Aus Etage "i" steigen "k" Personen in Kabine "j" ein
    try {
    ((VPerson) floors[i].getPersons().get(size- k -1)).getInCabin(cabins[j].getXPosition(), cabins[j]);
    }
    catch (IndexOutOfBoundsException e) {...}
    }
    }
    }
  • Zeichnen des Gesamten Hauses (repaint())

    In der paint()-Methode wird zuerst der gesamte Bildschirm gelöscht, dann nach und nach die einzelnen Bilder, bzw. Komponenten gezeichnet.

    Zuerst wird die Rückwand gezeichnet. Um größere Verzerrungen zu vermeiden, wird anhand der Größe des gif-Images ermittelt, wie oft das Bild untereinander gezeichnet werden muss und dann entsprechend skaliert:

    int wallheight = (config.getScreenHeight() - roof.getHeight(null)- leftWallBottom.getHeight(null));
    int wallPieceCount = wallheight / wall.getHeight(null);
    for (int i = 0; i < wallPieceCount; i++) {
    g.drawImage(wall,
    leftWall.getWidth(null),
    roof.getHeight(null)+i*wallheight/wallPieceCount,
    config.getScreenWidth()-2*rightWall.getWidth(null),
    wallheight / wallPieceCount,
    null);
    }

    Anschließend werden die Kabinenpositionen von der Steuerung geholt. Diese benötigt jede Kabine dazu, sich an der richtigen Position zu zeichnen. Die Visualisierung an sich stellt schließlich nur die Abläufe der anderen Hauptkomponenten grafisch dar, so auch die Kabinenpositionen:

    private double[] cabinPositions;
    cabinPositions = control.getCabinPositions();

    Die Steuerung gibt mit diesem Array für jede Kabine einen Double-Wert zurück. Der Wert 5,40 besagt z.B., dass sich die Kabine 40% über Etage 5 befindet.

    Diese Position wird nun den paint-Methoden der Kabinen mitgegeben:

    for (int i = 0; i < cabins.length; i++)
    cabins[i].paint(g, cabinPositions[i]);

    Nachdem auch die Seitenwände gezeichnet wurden, werden alle Etagen gezeichnet.

    Zum Schluss werden das Dach und die Uhrzeit gezeichnet.

Etage

Die Etage hat keine andere Aufgabe außer sich und seine Personen zu zeichnen.

Wenn die Etage die Kantine beinhaltet, muss zusätzlich das Cafeteria-Schild angebracht werden.

private boolean cafeteria = (config.getCafeteriaFloor() == floorNumber);
...
if (cafeteria)
g.drawImage(cafeteriaSign, cafeteriaXPos, y - (int) (floorHeight * 0.5) - 30, null);

Kabine

Die Kabine hat eine bestimmte Höhe, die bei großer Anzahl von Etagen über die Etagenhöhe hinausragt. Deshalb wird sie kleiner skaliert, sobald die Höhe der Etagen einen Grenzwert erreicht hat.

if ((floorHeight * 0.7) < 78){
cabinHeight = (int) (floorHeight * 0.7);
scaleFactor = ((double) cabinHeight) / 78;
}
else {
cabinHeight = 78;
scaleFactor = 1.0;
}

// Kabinenbreite entsprechend anpassen
cabinWidth = (int) (56 * scaleFactor);

Jede Kabine hat eine Türe, die sich öffnen und schließen lässt. Dazu gibt es 4 Zustände:

private static final int DOOR_CLOSED = 0;
private static final int DOOR_OPENING = 1;
private static final int DOOR_OPEN = 2;
private static final int DOOR_CLOSING = 3;

Beim Zeichnen der Kabine wird zuerst die y-Position errechnet, die aus der mitgegebenen Kabinenposition resultiert.

Danach werden zunächst die Schächte gezeichnet, davor dann die Kabinen mit den Kabinennummern und anschließend die Personen, die in den Kabinen stehen.

Zum Schluss werden die Türen, abhängig vom jeweiligen Zustand gezeichnet. Dabei wird die Tür jeweils in der Breite skaliert, um ein Öffnen bzw. Schließen zu simulieren. Wenn also der Türenstatus z.B. auf DOOR_CLOSING steht, wird bei jedem Zeichenvorgang die zu zeichnende Breite raufgezählt, bis die Kabinenbreite erreicht wird. Danach wird der Status auf DOOR_CLOSED gesetzt:

// Wenn die Türe gerade zugeht
if (doorState == DOOR_CLOSING) {
doorOpenPosition += doorCloseStep;
if (doorOpenPosition >= cabinWidth) {
doorOpenPosition = cabinWidth;

// Türe auf geschlossen setzen
doorState = DOOR_CLOSED;
}
}

// Zeichnen der Kabinentür vor den Personen
g.drawImage(cabinDoor, xPosition, yPosition, doorOpenPosition, cabinHeight, null);

doorOpenStep gibt dabei an, wie viel abgezogen, bzw. addiert werden soll. Somit ist es möglich die Geschwindigkeit des Türöffnens/ -schließens zu variieren.

Person

Beim Erzeugen einer Person wird ihr der Raum (hier: Etage) mitgegeben, in dem sie sich Anfangs befindet. Jede Person hat einen Wert xTarget, der besagt, wo sie sich in dem jeweiligen Raum platzieren soll und einen Wert xPosition, der besagt, wo sie gerade steht.

Anfangs kommt sie durch die linke Tür herein, d.h. ihre xPosition steht auf der Türposition. Ihr erstes Ziel wird bei der Initialisierung zufällig im Raum erzeugt:

xTarget = (int) (Math.random() * (this.owner.getWidth() – personWidth - leavingDoor * 2) + leavingDoor);

Bei jedem Zeichnvorgang nähert sich die Person immer mehr ihrem Ziel.

Beim Zeichnen wird unterschieden, in welchem Status sie sich gerade befindet und ob die Person noch zu ihrem Ziel unterwegs ist, oder es erreicht hat. Geht die Person, wird sie gehend gezeichnet, was durch 3 verschiedene Bilder simuliert wird. Hat sie ihr Ziel erreicht, wird ein Standbild gezeichnet. stepWidth gibt dabei ihre Geschwindigkeit an:

// Wenn die Person erzeugt wurde
if (state == 0) {
// Ziel noch nicht erreicht ==> nach rechts gehen
if (xPosition <= xTarget) {
xPosition += stepWidth;
g.drawImage(personWalkingRight[step], xPosition, yRoom - personHeight, null);

// den step erhöhen,damit nächstes Bild gezeichnet wird
step++;
if (step == 3)
step = 0;
}

// Ziel erreicht
else {
xPosition = xTarget;
g.drawImage(personStanding, xPosition++, yRoom – personHeight, null);
}
}

Soll eine Person einsteigen oder aussteigen, bekommt sie von außen (VHouse) den Befehl dazu und ihren neuen Besitzer mitgeteilt. Daraufhin errechnet sie sich ein neues Ziel, das zufällig irgendwo innerhalb des neuen Besitzers liegt. Außerdem ändert sich ihr Status.

Um keine zeitlichen Probleme beim Einsteigen zu bekommen (es wird zu kompliziert, wenn die Wartezeit der Kabine und der Weg der pro Person noch zurückzulegen ist noch mit berücksichtigt wird) wird die Geschwindigkeit verdreifacht.

Wenn ihr Ziel dann erreicht ist und ein Besitzerwechsel stattfinden soll, entfernt sich die Person aus der Liste ihres bisherigen Besitzers aus und klinkt sich beim neuen Besitzer ein.

if (changeOwner) {
if (inCabine()) {

// Besitzer wechseln
owner.leavePerson(this);
owner = newOwner;
owner.addPerson(this);
changeOwner = false;
}
}

Wenn die Person letztendlich aus der Türe nach draußen geht, muss sie nur aus der Liste ihres Besitzers entfernt werden.


top

11Projektorganisation

Die Organisation eines Projektes erfordert einen gewissen Erfahrungswert und ist großteils nicht einfach. Teams müssen koordiniert und zu verwendende Werkzeuge festgelegt werden. Während der gesamten Laufzeit des Projektes werden immer wieder Aufwandsschätzungen betrieben und Meilensteine gesetzt. Diese sind nicht fix und ändern sich immer wieder.

Im Folgenden wird die Projektorganisation von ElViSS beschrieben. Dabei wird auf alle eben genannten Gesichtspunkte eingegangen.

Team-Organisation:

Die Mitglieder eines Teams aufeinander abzustimmen, kann unter Umständen schwierig und nervenaufreibend sein. Mehrere solcher Teams zu organisieren stellt meist eine noch größere Herausforderung dar, weil über Teamgrenzen hinweg kommuniziert werden muss. Diese Kommunikation zwischen einzelnen Teams läuft in der Regel über einen verantwortlichen Projektleiter.

Das Projekt ElViSS wurden von zwei Teams aus Studenten bearbeitet. Jedes Team hatte seinen fest abgegrenzten Zuständigkeitsbereich, für den es alleine verantwortlich war. Zuerst war es wichtig, eindeutige Schnittstellen zwischen diesen Bereichen zu definieren, wodurch unabhängiges Arbeiten gewährleistet werden sollte. Kein Team musste wissen, was das andere Team machte bzw. wie weit deren Arbeit schon fortgeschritten war.

Im Verlauf des Projektes passierte ein wachsender Austausch zwischen den Teams. Einzelne Komponenten mussten logisch an andere angepasst, Aufgaben von einer in die andere Komponente verlagert werden. Auch holten sich Teammitglieder immer wieder Anregungen aus der jeweils anderen Gruppe, wenn sie nicht mehr weiter wussten.

Die beiden Teams sind letztendlich zusammengewachsen um Komponentenübergreifende Probleme schneller lösen zu können. Da diese Schwierigkeiten sich teilweise komplexer gestalteten als angenommen und mehrere Komponenten betrafen, besprachen sich einzelne Mitglieder beider Teams zusammen, um gemeinsam eine geeignete Lösung zu finden.

Verwendete Tools:

Die Einigung auf bestimmte Werkzeuge, im weiteren Verlauf Tools genannt, ist für jedes Projekt sehr wichtig. Gerade wenn mehrere Teams beteiligt sind, hilft diese gemeinsame Basis, dem Auftraggeber ein geschlossenes Auftreten zu vermitteln. Durch Verwendung der gleichen Tools sieht alles daraus Entstandene vom Erscheinungsbild her auch gleich aus.

Im Verlauf des Projektes wurden folgende Tools verwendet:

Meilensteine:

Meilensteine sind die Anhaltspunkte an denen man den Fortschritt eines Projektes misst. Zu Beginn werden die wichtigsten Meilensteine definiert. Im Verlauf des Projekts kommen weitere hinzu, bereits bestehende werden verschoben oder ersetzt bzw. verworfen.

Nachfolgend sind die Meilensteine von ElViSS in ihrer endgültigen Version dargestellt:

Abbildung 11.1: Meilensteine

Die Meilensteine bezeichnen hier die verschiedenen Projektphasen von ElViSS, deren grobe zeitliche Dauer und die Abfolge ihrer Entstehung. Sie sind für die Zeitplanung eines Projektes unverzichtbar, weil sie die Zeitspannen zu bevorstehenden Ereignissen zeigen. Solche Ereignisse können beispielsweise Release-Termine, aber auch Besprechungs- oder Präsentationstermine (wie hier) sein.

Aufwandsübersicht:

Jeder Auftraggeber erwartet vom Auftragnehmer Rechenschaft über dessen Arbeitszeit, die er schließlich in Rechnung gestellt bekommt. Im Allgemeinen haben alle an einem Projekt beteiligten Personen ein Pflichtenheft zu führen. In ihm sind alle erbrachten Leistungen mit zugehöriger zeitlicher Dauer aufgeschlüsselt. Am Anfang eines Projektes legt der Auftragnehmer dem Auftraggeber eine grobe Schätzung über wahrscheinliche zeitliche Aufwendungen vor. Am Ende werden alle tatsächlich angefallenen Arbeitszeiten nach einem fest definierten Schlüssel aufgeführt.

Hier folgt eine Aufwandsübersicht bei Projektende, nach Komponenten aufgeschlüsselt:

Tabelle 11.1: Aufwandsübersicht

Zu beachten ist bei dieser Aufwandsaufstellung, dass alle Werte in Bearbeitertagen, á 7 Stunden, angegeben wurden.


top

Quellenverzeichnis

Dieses Dokument beschreibt wie eine Fahrstuhlsteuerung realisiert wird. Es diskutiert Eingabe- und Systemdaten sowie zwei unterschiedliche Lösungsansätze für die Abarbeitungsstrategie der Steuerung. Es schließt mit einer Diskussion der Lösungen und allgemeinen Bemerkungen was für andere Lösungsversuche wichtig sein könnte.

  • Simulation of an elevator control (von Stefan Fromm, Christian Ehmke)

Hier stellen die Autoren verschiedene Anforderungen zur Implementierung einer Fahrstuhlsteuerung. Diese umfassen eine Aufwandsminimierung durch Kostenfunktionen sowie die optimale Reihenfolge der Abarbeitung von Fahrtwünschen.

  • Fahrstuhlproblem – Problemstellung und Lösungsansätze (von Vedran Divkovic, Universität Dortmund)

Vedran Divkovic versucht für eine Fahrstuhlsteuerung die optimale Abarbeitungsstrategie für Fahrtwünsche zu erschließen.

  • Entwurf verteilter Systeme (Max Göbel)

Max Göbel stellt in diesem Vortrag Entwurfsprinzipien für verteilte Systeme am Beispiel eines Fahrstuhlsystems dar.

(Quelle: http://www.inf.ethz.ch/personal/biere/applets/elsim/)

  • Elevator Simulation 2:

(Quelle: http://www.iit.edu/~malimuh2/Projects/elevator.htm)


Schlusswort

Das Projekt hat uns allen sehr viel Spaß gemacht. Es war zwar viel Arbeit und teilweise zum Haare raufen, aber sehr interessant und lehrreich.

Abbildung Schlusswort.1: Team MuffinSurround und getReality


top

Anhang 2: Abbildungs- / Tabellenverzeichnis

Abbildungen:

Abbildung 2.1:„Sim Elevator“ als Java-Applet

Abbildung 2.2: Elevator Simulation als Java-Applet (1)

Abbildung 2.3: Elevator Simulation als Java-Applet (2)

Abbildung 3.1: Konfigurationsoberfläche

Abbildung 3.2: Feineinstellung der SmartStrategy

Abbildung 3.3: keine Visualisierung

Abbildung 3.4: Einfache Visualisierung

Abbildung 3.5: Erweiterte Visualisierung

Abbildung 3.6: Auswertung

Abbildung 3.7: Personelle Auslastung der Kabinen

Abbildung 3.8: Auswertung Auslastung der Kabinen

Abbildung 3.9: Auswertung Zahl der Wartenden Personen im Intervall X

Abbildung 4.1: Übersicht Architektur

Abbildung 4.2: Erzeugung des Request und Weitergabe an den Monitor

Abbildung 4.3: Ermittlung einer Kabine und Fahrt der Kabine zur Anforderungsetage

Abbildung 4.4: Einsteigen der Personen in die Kabine und Auswählen aller Zieletagen

Abbildung 4.5: Fahrt zur Zieletage

Abbildung 4.6: Aussteigen der Personen und Merken des Requests

Abbildung 4.7: 3+N-Thread-Struktur

Abbildung 4.8: 3+1-Thread-Struktur

Abbildung 6.1: Die Komponente Zeitmaschine

Abbildung 7.1: Übersicht Steuerung / Kabine

Abbildung 7.2: Übersicht Gesamtsteuerung

Abbildung 7.3: Übersicht Kabine/Kabinensteuerung

Abbildung 7.4: Übersicht Kabine

Abbildung 7.5: Übersicht Etagensteuerung

Abbildung 7.6: Übersicht Strategie

Abbildung 8.1: Gesamtbild der Komponente Simulation

Abbildung 9.1: Monitor mit Nachbarkomponenten

Abbildung 9.2: Monitor, bildliche Darstellung

Abbildung 10.1: Aufbau der Visualisierung

Abbildung 10.2: Programmfluss der Visualisierung a) bis e)

Abbildung 10.3: Zeitanzeige

Abbildung 10.4: Programmfluss in der einfachen/ erweiterten Visualisierung f) bis k)

Abbildung 10.5: Aufrufsequenzen der Zeichenmethoden

Abbildung 10.6: Programmfluss in der einfachen/ erweiterten Visualisierung l) bis t)

Abbildung 11.1: Meilensteine

Abbildung Schlusswort.1: Team MuffinSurround und getReality


Copyright © 2004 MuffinSurround/GetReality < webmaster@groess.com >

top