ampelsteuerung.zip
Ampelsteuerung
Gruppe: mk, 11.5.03
Aufgabenstellung
Es ist ein Programm zur Ampelsteuerung zu erstellen.
Diese Aufgabenstellung ist viel zu allgemein und bedarf einer Präzisierung. Ein wirklicher Auftraggeber wird wohl schon etwas genauer wissen, was er will.
Hier soll die Freiheit auch als Chance verstanden werden, die Aufgabenstellung zu beeinflussen.
Systemanalyse
"Aufgabe der Analyse ist die Ermittlung und der Beschreibung der Anforderungen eines Auftraggebers an ein Softwaresystem. Das Ergebnis soll die Anforderungen vollständig, widerspruchsfrei, eindeutig, präzise und verständlich beschreiben." [
OM, S.534]
Bevor man Anforderungen beschreiben kann, muss man etwas von der Aufgabenstellung verstehen.
Einige Links zu "Ampelsteuerung" aus dem Internet:
Auch ohne diese Links merkt man, dass die Aufgabenstellung leicht uferlos werden kann.
Teilziele
1. Eine einzige Ampel soll nach Drücken eines Buttons in die nächste Phase schalten.
2. Die Ampel soll von einem Zeitgeber gesteuert automatisch schalten.
3. Die Ampel soll zu einer Fußgänger-Ampel mit Bedarfstaste (ohne Bedarf immer grün) erweitert werden.
a) ohne Fußgängerlampen b) mit Fußgängerlampen
Teilziele ermöglichen, die Komplexität zu verringern und zu zuverlässigen Lösungen zu kommen.
Auf diesen Lösungen kann man aufbauen und eine weitere Teillösung hinzufügen.
Pflichtenheft zur 1.Teilaufgabe
/1/ Eine Ampel soll durch Drücken eines Buttons weitergeschaltet werden.
/2/ Die Ampel ist auf dem Bildschirm grafisch darzustellen.
"interne Pflichten":
/3/ Die Dokumentation ist fortlaufend (Prototyp,..), ausgehend von diesem Dokument zu
erstellen.
/4/ Das MVC-Muster (zunächst Polling, dann mit Ereignissen) ist zu benutzen.
/5/ Ein Zustandsdiagramm ist mit Dia oder Violet zu erstellen. (siehe UML-Tutorial
Uni-Magdeburg)
/6/ Ein Klassendiagramm ist mit UMLEd zu erstellen.
/7/ Die Fachkonzept-Unit ist automatisch zu erstellen.
/8/ Die Zustände sollen mit selbstdefinierten Datentypen implementiert werden.
Prototyp
|
Die Ampel (besser: die Ansicht der Ampel) besteht aus zwei Panels und
3 Shapes.
Mit shRot.Brush.Color := clRed läßt sich die Farbe einstellen. Die Farbe wird nach
einer Änderung sofort aktualisiert |
Objektorientierte Analyse
Zustandsautomat
erstellt mit
Dia
Objektorientiertes Design
Klassendiagramm
|
|
mit Polling |
mit Ereignis |
Exemplarisch soll der Entwurf der Methode
switchZustand aus der Klasse
TAmpel gezeigt werden. Algorithmen können grafisch oder textuell dargestellt werden. Hier soll ein
Struktogramm Verwendung finden.
erstellt mit
StruktEd
Implementierung
mit Polling:
ampel1.zip,
mit Ereignis:
ampel2.zip
Die folgende Fachkonzept-Unit wurde mit UMLEd erzeugt und von Hand erweitert.
Rot hervorgehoben sind die Vereinbarung eines eigenen Datentyps TZustand, die Erweiterung des
Konstruktors zur Initialisierung und die Verwendung der case-Anweisung, die obiges Struktogramm
implementiert.
UNIT mTAmpel1;
interface
type
TZustand = (rot,rotgelb,gruen,gelb);
TAmpel = CLASS
// Attribute
private
FZustand : TZustand;
// Methoden
public
procedure SetZustand(z : TZustand);
procedure switchZustand;
function GetZustand : TZustand;
constructor Create;
end;
implementation
//+---------------------------------------------------------------------
//| TAmpel: Methodendefinition
//+---------------------------------------------------------------------
constructor TAmpel.Create;
begin
inherited Create;
FZustand := rot;
end;
//-------- SetZustand (public) -----------------------------------------
procedure TAmpel.SetZustand(z : TZustand);
begin
FZustand := z;
end;
//-------- switchZustand (public) --------------------------------------
procedure TAmpel.switchZustand;
begin
case FZustand of
rot : FZustand := rotgelb;
rotgelb : FZustand := gruen;
gruen : FZustand := gelb;
gelb : FZustand := rot;
end;
end;
//-------- GetZustand (public) -----------------------------------------
function TAmpel.GetZustand : TZustand;
begin
Result := FZustand;
end;
end.
|
Test
Die "Ampel" zeigte bei beiden Versionen das erwartete Verhalten. Getestet wurden alle Phasen.
Pflichtenheft zur 2.Teilaufgabe
/1/ Die Ampel soll automatisch weiterschalten.
/2/ Die Länge der einzelnen Phasen soll einstellbar sein.
"interne Pflichten":
/3/ Die Dokumentation ist fortlaufend (Prototyp,..), ausgehend von diesem Dokument zu
erstellen.
/4/ Das MVC-Muster (zunächst Polling, dann mit Ereignissen) ist zu benutzen.
/5/ Ein Zustandsdiagramm ist mit Dia oder Violet zu erstellen. (siehe UML-Tutorial
Uni-Magdeburg)
/6/ Ein Klassendiagramm ist mit UMLEd zu erstellen.
/7/ Die Fachkonzept-Unit ist automatisch zu erstellen.
/8/ Die Zustände sollen mit selbstdefinierten Datentypen implementiert werden.
Prototyp
wie bei Teilaufgabe1, nur ohne Button
Objektorientierte Analyse
Klassendiagramm
Zustandsdiagramm
erstellt mit
Violet
Objektorientiertes Design
Wie aus dem Zustandsdiagramm ersichtlich wird mit jedem Zustandsübergang der Counter auf einen neuen Wert gesetzt.
Der Timer repräsentiert die unabhängige Zeit. Er hat nichts mit den Ampelphasen zu tun.
Für die Ampelphasen ist nur das Ampelobjekt zuständig.
Der Timer sendet an die Ampel regelmäßig
DecCounter, was irgendwann das Ereignis
OnCounterNull auslöst. Dieses Ereignis wird mit
SwitchZustand verbunden.
Implementierung
Das Ereignis OnCounterNull kann wie ein anderes Ereignis z.B. OnChange behandelt werden.
Dh. die Schritte 1. Ereignistyp vereinbaren, 2. Referenzvariable vereinbaren, 3. Auslöse-Stellen festlegen, 4. Ereignis-Behandlungs-Routine einhängen werden auch hier eingehalten. Als Besonderheit stellt man fest, dass die Ereignis-Behandlungs-Routine (hier: SwitchZustand) im Objekt selbst liegt. Diese Lösung ist im Projekt
Ampel3 verwirklicht.
ampel3.zip
Wahrscheinlich wird die beschriebene Lösung als zu formal angesehen. Denn im vorliegenden Fall genügt es, an der Stelle, wo das Ereignis eintritt, sofort die Ereignis-Behandlung einzufügen.
Der Quelltext vereinfacht sich dadurch. Diese Lösung ist im Projekt
Ampel4 verwirklicht.
ampel4.zip
Test
Sowohl Ampel3 als auch Ampel4 scheinen einwandfrei zu funktionieren.
Pflichtenheft zur 3.Teilaufgabe
/1/ Die Ampel zeigt normalerweise grün.
/2/ Die Ampel durchläuft nach Drücken der Bedarfstaste einen Zyklus.
/3/ Ampel und Fußgängerlampen (in GUI) sollen angesteuert werden.
"interne Pflichten":
/4/ Ein Zustandsdiagramm ist zu erstellen.
Prototyp
Objektorientierte Analyse
Zustandsautomat
Objektorientiertes Design
Hier wird man die Objekte in ihrer Hierarchie und ihrem Zusammenspiel festlegen. Das Ergebnis der Festlegungen wird in UML-Diagrammen dargestellt.
Ebenso geschieht hier die Auswahl beziehungsweise die Konstruktion der benötigten Algorithmen. Die Algorithmen werden durch Angabe der Fundstellen und/oder durch Struktogramme dokumentiert.
Ein Design im Hinblick auf besondere Möglichkeiten einer Programmiersprache ist zu vermeiden. Übertragbarkeit ist wichtiger als Eleganz der Einzellösung.
mehr zum Dokumentationsgerüst
Implementierung
ampel5.zip
UNIT mTAmpel5; { mk, 9.5.03, Fußgängerampel 1}
interface
type
TZustand = (rot,rotgelb,dauergruen,gruen,gelb);
TEingabe = (OCN,Taste);
TEreignis = procedure of object;
TAmpel = CLASS
// Attribute
private
FZustand : TZustand;
FCounter : word;
public
OnChangeZustand : TEreignis;
// Methoden
public
procedure SetZustand(z : TZustand);
procedure switchZustand(eingabe : TEingabe);
function GetZustand : TZustand;
constructor Create;
procedure SetCounter(t : word);
function GetCounter : word;
procedure DecCounter;
end;
implementation
//+---------------------------------------------------------------------
//| TAmpel: Methodendefinition
//+---------------------------------------------------------------------
constructor TAmpel.Create;
begin
inherited Create;
FZustand := dauergruen; FCounter := 0;
if Assigned(OnChangeZustand) then OnChangeZustand;
end;
//-------- SetZustand (public) -----------------------------------------
procedure TAmpel.SetZustand(z : TZustand);
begin
FZustand := z;
if Assigned(OnChangeZustand) then OnChangeZustand;
end;
//-------- switchZustand (public) --------------------------------------
procedure TAmpel.switchZustand(eingabe : TEingabe);
begin
if eingabe = OCN
then
case FZustand of
rot : begin FZustand := rotgelb; setCounter(2); end;
rotgelb : begin FZustand := gruen; setCounter(10); end;
gruen : begin FZustand := dauergruen; setCounter(0); end;
dauergruen : { leer } ;
gelb : begin FZustand := rot; setCounter(10); end;
end
else { eingabe = Taste }
case FZustand of
rot : { leer } ;
rotgelb : { leer } ;
gruen : { leer } ;
dauergruen : begin FZustand := gelb; setCounter(2); end;
gelb : { leer } ;
end;
if Assigned(OnChangeZustand) then OnChangeZustand;
end;
//-------- GetZustand (public) -----------------------------------------
function TAmpel.GetZustand : TZustand;
begin
Result := FZustand;
end;
procedure TAmpel.SetCounter(t : word);
begin
FCounter := t;
end;
procedure TAmpel.DecCounter;
begin
if FCounter > 0
then
begin
FCounter := FCounter - 1;
if FCounter = 0 then switchZustand(OCN);
end;
end;
function TAmpel.GetCounter : word ;
begin
Result := FCounter;
end;
end.
|
Test
Für die Tests ist es wünschenswert, in den "Automat hineinsehen" zu können. Insbesondere interessiert uns der aktuelle Zustand, um den Ablauf mit dem Zustandsdiagramm vergleichen zu können, und der Counter-Stand, um das Setzen des Zählers und das Reagieren auf OnCounterNull (OCN) kontollieren zu können. Die Ausgabe dieser Informationen ist nur in der Test- bzw. Debug-Phase erwünscht. Eine Möglichkeit wäre, die notwendigen Programmteile nach erfolgreichen Tests zu löschen. Das wäre im Hinblick auf eine Dokumentation und Wiederholbarkeit der Tests ungünstig.
Die Lösung liegt in der
bedingten Compilierung:
Zu Beginn kann durch Hinzufügen eines Compilerschalters das Symbol DEBUG gesetzt werden.
Später werden Programmanweisung in Abhängigkeit von der Existenz des Symbols ausgeführt oder weggelassen.
Der folgende Quelltext-Ausschnitt zeigt auch, wie Komponenten (hier zwei Labels) unabhängig vom Objektinspektor während der Laufzeit erzeugt und ihre Attribute gesetzt werden können. Ungewohnt ist, dass im Konstruktor der Eigentümer (AOwner) der Komponente angegeben werden muss. Ebenso muss die Eigenschaft
parent gesetzt werden.
unit uAmpel5GUI; { mk, 11.5.03 , Fußgängerampel 1 }
{$DEFINE DEBUG}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, mTAmpel5;
type
TGUI = class(TForm)
pAmpel: TPanel; shRot: TShape; shGelb: TShape; shGruen: TShape;
pStange: TPanel; Timer1: TTimer;
bTaste: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure bTasteClick(Sender: TObject);
private
{$IFDEF DEBUG}
lCounter, lZustand : TLabel;
{$ENDIF}
Ampel : TAmpel;
procedure updateView;
public
{ Public-Deklarationen }
end;
var
GUI: TGUI;
implementation
{$R *.DFM}
procedure TGUI.FormCreate(Sender: TObject);
begin
Ampel := TAmpel.Create;
Ampel.OnChangeZustand := updateView;
updateView;
{$IFDEF DEBUG}
lCounter := TLabel.Create(self); // hier: self = GUI
lCounter.Parent := self; lCounter.top := 380; lCounter.Left := 90;
lCounter.caption := '0';
lZustand := TLabel.Create(self);
lZustand.Parent := self; lZustand.top := 400; lZustand.Left := 90;
lZustand.caption := 'dauergruen';
{$ENDIF}
end;
.......
procedure TGUI.updateView;
const
clGreen = $0000FF00; // Hilfe zu TColor
begin
case Ampel.GetZustand of
rot : begin
shRot.Brush.Color := clRed;
shGelb.Brush.Color := clBlack;
shGruen.Brush.Color := clBlack;
{$IFDEF DEBUG} lZustand.caption := 'rot'; {$ENDIF}
end;
rotgelb : begin
shRot.Brush.Color := clRed;
shGelb.Brush.Color := clYellow;
shGruen.Brush.Color := clBlack;
{$IFDEF DEBUG} lZustand.caption := 'rotgelb'; {$ENDIF}
end;
dauergruen : begin
shRot.Brush.Color := clBlack;
shGelb.Brush.Color := clBlack;
shGruen.Brush.Color := clGreen;
{$IFDEF DEBUG} lZustand.caption := 'dauergruen'; {$ENDIF}
end;
gruen : begin
shRot.Brush.Color := clBlack;
shGelb.Brush.Color := clBlack;
shGruen.Brush.Color := clGreen;
{$IFDEF DEBUG} lZustand.caption := 'gruen'; {$ENDIF}
end;
gelb : begin
shRot.Brush.Color := clBlack;
shGelb.Brush.Color := clYellow;
shGruen.Brush.Color := clBlack;
{$IFDEF DEBUG} lZustand.caption := 'gelb'; {$ENDIF}
end;
end;
end;
procedure TGUI.Timer1Timer(Sender: TObject);
begin
Ampel.DecCounter;
{$IFDEF DEBUG} lCounter.caption := IntToStr(Ampel.GetCounter); {$ENDIF}
end;
.........
end.
|
Der Test der Fußgängerampel ergab, dass die Zustände gemäß dem Diagramm durchlaufen werden.
Auch die Zählerstände sind immer korrekt. Was auffiel ist, dass die Taste nur im Zustand
dauergruen eine Wirkung hat. Das ist kein Fehler, sondern entspricht unserer Modellierung.
Sollte die Modellierung nicht so geändert werden, dass ein Drücken der Bedarfstaste gemerkt
wird?
Auch fehlt die Darstellung der Fußgängerlampen.
Die Fußgängerampel mit Fischertechnik
Als Beispiel, wie einfach eine MSR-Erweiterung implementiert werden kann, sei die synchrone
Ansteuerung einer Fischertechnik-Ampel gezeigt.
Download des Projekts:
ftAmpel.zip