Datenübertragung Teil 2

zurück zur Hauptseite Datenübertragung
Gruppe: dj, 14.06.03

Aufgabenstellung

Automatisches Übertragen einer Bitfolge
a) Ein Sender-Programm soll eine Folge von Bits vom Benutzer entgegennehmen und gemäß der Absprachen versenden,
b) ein Empfänger soll die Bitfolge entgegennehmen und anzeigen.

Ressourcen

Die Klasse TBitFolge stellt den Datentyp "Folge von 8 Bits" und Methoden zur Typ-Umwandlung von und in Strings zur Verfügung.
Klassendiagramm:

Pflichtenheft zur 2. Teilaufgabe

Zu a)
/1/ Der Benutzer soll eine Folge von 8 Bits eingeben können.
/2/ Durch Drücken eines Buttons soll die Übertragung dieser Bitfolge gestartet werden.
/3/ Die Übertragung geschieht für 8 Bits, mit einem Anfangsbegrenzer (Startbit), einem Endebegrenzer (Stopbit) und einem geraden Paritätsbit.
Zu b)
/3/ Der Empfänger soll die Folge von Bits empfangen und dem Benutzer anzeigen. Ein Button, mit dem das Lauschen auf der Leitung gestartet wird, ist freiwillig.
/4/ Er hält sich dabei an das selbe Protokoll wie der Sender.

"interne Pflichten":

/4/ Die Dokumentation ist konsequent weiterzuführen.
/5/ Im Ruhezustand soll die Leitung "an" sein, das Senden einer logischen 0 geschieht durch das "Aus"-Schalten der Leitung.
/6/ Die Realisierung soll sich an die untenstehenden Automatengraphen mit Zuständen fü jedes einzelne Bit anlehnen.
/7/ Die Netz-Hardware wird Teil von TGUI, Sender und Empfänger kennen TNetzHw nur. (Begründung siehe unten.)

Prototyp

Zu Teilproblem a)

Zu Teilproblem b)

Klärung

Folgende Skizze soll den zeitlichen Verlauf des Sendens und Empfangens verdeutlichen:

Objektorientierte Analyse

Automatengraphen

Die Automaten für Sender und Empfänger haben die gleiche Struktur, es muss lediglich das Wort "Sende" durch "Empfange" ausgetauscht werden.



Objektorientiertes Design

Klassendiagramme

Im Hinblick auf weitere Teilstufen wird die Klasse TNetzHw der TGUI zur Verfügung gestellt. Da aber später Sender und Empfänger in einem Programm vereint werden sollen, darf die Netzhardware nur einmal genutzt werden (Laden von Treibern, Öffnen des Interfaces, ...). Daher vorerst die Vorgabe: TGUI hat TNetzHw und TEmpfaenger kennt TNetzHw. Das kennen lernen geschieht durch die Methode TSender.Binde( NetzHw : TNetzHw ).



Das oben abgebildete Modell scheint das Prinzip der Trennung von Model und View zu verletzten. Die beiden unteren Modelle stellen Alternativen dar, wobei die hinter der rechts abgebildeten Struktur die Idee steht, die in der BitFolge der GUI stehenden Daten beim Beginn des Sendevorgangs in die BitFolge des Senders zu kopieren.
Überlegungen zu den Aufgabenbereichen der einzelnen Objekte (z.B.: Es ist nicht Aufgabe der GUI, aus einem String eine Bitfolge zu machen) und zur Erweiterbarkeit (z.B. um zusätzliche Sicherungen vor Übertragungsfehlern) führen zur Erkenntnis, dass es auch nicht Aufgabe des Senders sein kann, einen String zu akzeptieren und in die zu versendende Bitfolge umzuwandeln. Es fehlt also ein weiteres Objekt, das sich diesen Aufgaben annimmt.
Klar ist: später soll die GUI wirklich nur einen "echten" String weitergeben und sich um nichts weiteres kümmern müssen. Der Sender, so wie wir ihn modelliert haben, soll nichts weiter als eine "echte" Bitfolge annehmen und versenden.
Als Ausblick daher das vorläufige Ziel des nächsten Schrittes:

Folgende Abbildung soll verdeutlichen, wie die Objekte dann zusammenspielen sollen. Der Unterschied zu dieser Fassung liegt darin, dass im Moment die GUI sich noch selbst um die Umsetzung des "Strings" (der hier ja nur aus Nullen und Einsen bestehen darf) kümmern muss.

Detaillierte Klassendiagramme

Zu Teilproblem a) Sender

Zu Teilproblem b) Empfaenger

Ereignisse

Es gibt hier drei verschiedene Arten von Ereignissen: Beim Sender führt das Drücken des Buttons zum Beginn der Übertragung, indem der zu Grunde liegende Automat vom Zustand "Warte" nach "SendeAB" wechselt.
Der Empfaenger benötigt diesen Button nicht unbedingt. Er lauscht nach Programmstart die ganze Zeit auf der Empfangsleitung.
Der Sender braucht die OnTimerTick-Ereignisse, um die einzelnen Bitlängen festlegen zu können. Dazu verwendet er einen Counter, der entsprechend oft erhöht wird, bis die richtige Zeitspanne erreicht wurde (Bsp: Wenn der Timer alle 5ms das Ereignis OnTimerTick auslöst, und eine Bitlänge 300ms betragen soll, dann wird der Counter bei jedem OnTimerTick-Ereignis um 5 erhöht, bis die 300 erreicht wurde).
Der Empfaenger verfährt ähnlich, muss jedoch unterschiedliche Zeiten berücksichtigen (s.o.).

Implementierung

Hinweis: Unsere Instanz der Klasse TSender darf nicht Sender heissen, da Delphi ihn sonst mit dem Ver-Sender einer Nachricht verwechseln kann. Siehe z.B. unten in OnTimerTick( Sender: TObject );
Die Implementierung richtet sich nach dem unteren Klassendiagramm: Sowohl GUI als auch Sender und Empfänger haben Bitfolgen. Im GUI werden die Daten in die Bitfolge eingegeben; beim Aufruf der Methode Sende dem Sender übergeben, der sie sich in einen Puffer kopiert und von dort aus bitweise versendet. Der Empfaenger verfährt ähnlich, er sammelt die Bits wiederum in einer eigenen Bitfolge auf, die das GUI sich nach Ende des Empfangsvorgangs (z.B. benachrichtigt duch das Ereignis OnBitFolgeEmpfangen) abholen kann.

Sender

procedure TGUI.FormCreate(Sender: TObject);
begin
  // alle benoetigten Objekte erschaffen  
  Daten := TBitFolge.Create;
  NetzHw := TNetzHw.Create;
  Send := TSender.Create;
  // und den Sender an die Netzhardware binden
  Send.Binde( NetzHw );
end;

procedure TGUI.FormDestroy(Sender: TObject);
begin
  // die Objekte wieder freigeben
  Send.Free;
  NetzHw.Free;
  Daten.Free;
end;

procedure TGUI.OnTimerTick(Sender: TObject);
begin
  // gibt den Tick weiter an den Sender.
  Send.TimerTick;
end;
constructor TSender.Create;
begin
  inherited Create;
  Puffer := TBitFolge.Create;
  CountDown := 0;
  Zustand := Warte;
end;

procedure TSender.Binde( Netz : TNetzHw );
// dann kennt der Sender die Netzhardware, die eigentlich dem GUI gehoert.
begin
  NetzHw := Netz;
end;

procedure TSender.Sende( Daten : TBitFolge );
begin
  // das ganze eigentlich nur, wenn wir nicht schon gerade am Senden sind
  // Der Aufruf der Methode Sende fuert zum Uebergang in den Zustand Sende
  zustand := SendeAB;
  Countdown := Taktlaenge;
  // Wir kopieren die zu sendenden Daten in einen Puffer
  Puffer.fromBitFolge( Daten );
end;

procedure TSender.TimerTick;
// Das GUI teilt uns mit, dass der Timer getickt hat.
begin
  // Wir verarbeiten dies.
  case Zustand of
    Warte : // nichts
    begin
      NetzHw.SetSendeLeitung(Aus);
    end;
    SendeAB : // AB ist der Anfangsbegrenzer
    begin
      NetzHw.SetSendeLeitung(An);
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        zustand := SendeBit1;
        CountDown := Taktlaenge;
      end;
    end;
    SendeBit1 :
    begin
      case Puffer.getBit(1) of
        Null : NetzHw.SetSendeLeitung( Aus );
        Eins : NetzHw.SetSendeLeitung( An );
      end;
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        zustand := SendeBit2;
        CountDown := Taktlaenge;
      end;
    end;
    SendeBit2 :
    begin
      case Puffer.getBit(2) of
        Null : NetzHw.SetSendeLeitung( Aus );
        Eins : NetzHw.SetSendeLeitung( An );
      end;
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        zustand := SendeBit3;
        CountDown := Taktlaenge;
      end;
    end;
und so weiter für SendeBit3, SendeBit4, SendeBit5, SendeBit6, SendeBit7
    SendeBit8 :
    begin
      case Puffer.getBit(8) of
        Null : NetzHw.SetSendeLeitung( Aus );
        Eins : NetzHw.SetSendeLeitung( An );
      end;
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        zustand := SendePB;
        CountDown := Taktlaenge;
      end;
    end;
    SendePB :
    begin
      case Puffer.getParitaet of
        Null : NetzHw.SetSendeLeitung( Aus );
        Eins : NetzHw.SetSendeLeitung( An );
      end;
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        zustand := SendeEB;
        CountDown := Taktlaenge;
      end;
    end;
    SendeEB :
    begin
      NetzHw.SetSendeLeitung( Aus );
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        zustand := Warte;
      end;
    end;
  end;
end;

Empfänger

procedure TGUI.FormCreate(Sender: TObject);
begin
  // alle benoetigten Objekte erschaffen  
  Daten := TBitFolge.Create;
  NetzHw := TNetzHw.Create;
  Empfaenger := TEmpfaenger.Create;
  // und den Empfaenger an die Netzhardware binden
  Empfaenger.Binde( NetzHw );
end;

procedure TGUI.FormDestroy(Sender: TObject);
begin
  // die Objekte wieder freigeben
  Empfaenger.Free;
  NetzHw.Free;
  Daten.Free;
end;

procedure TGUI.OnTimerTick(Sender: TObject);
begin
  // gibt den Tick weiter an den Empfaenger.
  Empfaenger.TimerTick;
end;
const Takt=10;

type TEmpfaengerZustand = ( Warte, EmpfangeAB, EmpfangeBit1, EmpfangeBit2,
       EmpfangeBit3, EmpfangeBit4, EmpfangeBit5, EmpfangeBit6, EmpfangeBit7,
       EmpfangeBit8, EmpfangeEB );

procedure TEmpfaenger.Binde( Netz : TNetzHw );
// dann kennt der Empfaenger die Netzhardware, die eigentlich dem GUI gehoert.
begin
  NetzHw := Netz;
end;

procedure TEmpfaenger.TimerTick;
// Das GUI teilt uns mit, dass der Timer getickt hat.
begin
  // Wir verarbeiten dies.
  // Was ist zu tun, wenn der Timer tickt?
  Warte :
    begin
      if ( NetzHw.GetEmpfangsLeitung = An ) then
      begin
        zustand := EmpfangeAB;
        CountDown := Takt;
      end;
    end;
  EmpfangeAB :
    begin
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        zustand := EmpfangeBit1;
        CountDown := Takt div 2;
      end;
    end;
  EmpfangeBit1 :
    begin
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        // Leitung auslesen
        case NetzHw.GetEmpfangsLeitung of
          An  : BitFolge.setBit(1, Eins);
          Aus : BitFolge.setBit(1, Null);
        end;
        // in Folgezustand wechseln und dort warten
        zustand := EmpfangeBit2;
        CountDown := Takt;
      end;
    end;
  EmpfangeBit2 :
    begin
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        // Leitung auslesen
        case NetzHw.GetEmpfangsLeitung of
          An : BitFolge.setBit(2, Eins);
          Aus : BitFolge.setBit(2, Null);
        end;
        // in Folgezustand wechseln und dort warten
        zustand := EmpfangeBit3;
        CountDown := Takt;
      end;
    end;
und so weiter für EmpfangeBit3, EmpfangeBit4, EmpfangeBit5, EmpfangeBit6, EmpfangeBit7
   
    EmpfangeBit8 :
    begin
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        // Leitung auslesen
        case NetzHw.GetEmpfangsLeitung of
          An : BitFolge.setBit(8, Eins);
          Aus : BitFolge.setBit(8, Null);
        end;
        // in Folgezustand wechseln und dort warten
        zustand := EmpfangeEB;
        CountDown := Takt;
      end;
    end;
    EmpfangeEB :
    begin
      CountDown := CountDown-1;
      if ( CountDown = 0 ) then
      begin
        zustand := Warte;
        CountDown := 0;
      end;
    end;
  end;
end;

Test