HSG

Aktuelle Seite: HSG/Fächer/Informatik/Material

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