Stellwerk

Hier geht es um die Entwicklung eines zukünftigen Stellwerks mit Zusi-Anschluss.
Nachricht
Autor
Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#41 Beitrag von Roland Ziegler »

Christopher Spies hat geschrieben:Roland bastelt sich seine eigene Skriptsprache und zugehörigen Interpreter aus Spaß.
Spaß ist in der Tat der treibende Faktor. Für meine gesamte Tätigkeit in der Eisenbahnsimulations- und Kartenwelt.
Reflection ist mir bekannt. Mir ist jedoch nicht klar, was im Rahmen des Stellwerk-Projekts der Vorteil davon ist. Die API steht doch fest, oder irre ich mich da?
Um das API statisch zu nutzen, muss die Anwendung zur Compilezeit entsprechend zugeschnitten sein. Genau das wäre für meine Zwecke unpraktisch
Aber so musst Du den Source-Code der Testskripte ändern, wie spart das Arbeit?
Für mich geht es schneller, in einem kurzen, kompakten Skript-Quelltext zu ändern, der für die angestrebten Zweck keinen Overhead mit sich führt, als im wesentlich komplexeren C#-Quelltext die verteilten Stellen heraussuchen zu müssen.
Die Skripte sind also nur Konfigurationsdateien für allgemein gehaltene Testfälle. Früher hätte man so etwas vielleicht als Textdatei im INI-Format realisiert.
Es gibt ja so eine teils juristische Definition zur Unterscheidung von Daten und Programm. Ein Programm soll demnach gegenüber einer reinen Datendefinition logische Abläufe, einschließlich Verzweigung, enthalten. Und ein "if" werde ich brauchen.
Die Skriptsprache muss so simpel sein, dass Nichtprogrammierer als Testsklaven herhalten können.
Getestet wird Software sinnvollerweise auch von Nicht-Entwicklern. Ein Merkmal von QS.

So wie geliefert, sehe ich für die Skriptsprache über meine eigene Entwicklung hinaus die denkbare Anwendung für den Stellwerks-Konfigurierenden auf der Server-Seite, also in der Emulation, auch ohne Zusi.

Zudem wird die Assembly, auf die der Interpreter wirkt, frei wählbar sein. Man wird also seine eigenen Adapterklassen schreiben können. (Der Interpreter selbst arbeitet nach dem Command-Pattern.) Mit dieser Eigenschaft könnte das Teil auch denjenigen Softwareentwicklern helfen, die selber einen neuen Client entwickeln wollen.

Aber all das ist derzeit noch gar nicht meine Intention. Die Idee ist schlicht aus der Skizzierung von sinnvollen Testfällen geboren.

Die BNF-Analyse-Klassen werden entweder noch heute oder sonst morgen wohl in einer ersten Version laufen. Der Klassengenerator des Interpreters kann das Beispiel-Statement bereits generieren. Allerdings gefiel mir mein Ansatz nicht, und deswegen habe ich auf BNF erweitert. Das ist also insgesamt vom Arbeitsaufwand keine Frage von Wochen, sondern Tagen (die zeitliche Verteilung dieses Aufwandes steht immer auf einem anderen Blatt).




Michael_Poschmann hat geschrieben:komplette stellwerksbezogene Fachliteratur innerhalb der Stadtgrenzen
Ich versteh schon, es wird Zeit, sich endlich mit Handfesterem zu beschäftigen. Es ist wohl eine Krux heutiger Software, dass das Sichtbare auch im übertragenden Sinne nur die Oberfläche darstellt. Das ist allerdings eine Erfahrung, die mich mein schon mein ganzes Berufsleben begleitet. Auch da gibt es so eine 80/20-Regel (je nach Branche und Projektart): 80% der Software sieht man nicht. (Ich denke, das Verhältnis kann noch weitaus drastischer ausfallen.)
Zuletzt geändert von Roland Ziegler am 15.08.2008 20:51:10, insgesamt 1-mal geändert.

Benutzeravatar
Carsten Hölscher
Administrator
Beiträge: 33384
Registriert: 04.07.2002 00:14:42
Wohnort: Braunschweig
Kontaktdaten:

Re: Stellwerk

#42 Beitrag von Carsten Hölscher »

Ich kann mich nicht erinnern, dass Roland mal irgendwo erwähnt hätte, dass sein Erzeugnis für lau sein wird.
Die bisherigen Ziegler-Tools sind ja auch nicht für lau. Ihr könnt aber davon ausgehen, daß es wie bisher alles in einem Rutsch auf einer CD geben wird.

Carsten

Christopher Spies
Beiträge: 775
Registriert: 26.01.2005 16:10:18
Wohnort: Darmstadt

Re: Stellwerk

#43 Beitrag von Christopher Spies »

Hallo Roland,
Roland Ziegler hat geschrieben:Spaß ist in der Tat der treibende Faktor. Für meine gesamte Tätigkeit in der Eisenbahnsimulations- und Kartenwelt.
na ja, es macht sicher nicht immer nur Spaß.
Ich habe für den privaten Gebrauch für verschiedene Zusi-Datenformate Parser in Java und VB geschrieben -- und das hat mich ziemlich geschlaucht. Die Zusi-Grammatiken sind leider nicht kontextfrei, das macht es nicht einfacher...
Ich selbst bin ja viel zu unkreativ, mir eigene Sprachen auszudenken. Ich hätte Brainfuck als Skriptsprache genommen...
Roland Ziegler hat geschrieben:Für mich geht es schneller, in einem kurzen, kompakten Skript-Quelltext zu ändern, der für die angestrebten Zweck keinen Overhead mit sich führt, als im wesentlich komplexeren C#-Quelltext die verteilten Stellen heraussuchen zu müssen.
OK, akzeptiert ;D .
Roland Ziegler hat geschrieben:Getestet wird Software sinnvollerweise auch von Nicht-Entwicklern. Ein Merkmal von QS.
Ja, aber doch eher auf einer höheren Ebene (z.B. Benutzeroberfläche). Unit-Tests machen die Entwickler doch üblicherweise selbst, oder?

Gruß,
- Christopher

P.S.:
Michael_Poschmann hat geschrieben:da ich nahezu meine komplette stellwerksbezogene Fachliteratur innerhalb der Stadtgrenzen verliehen habe, reicht es nicht mal mehr für Viertelwissen
Ich hatte das auch eher auf "sich nicht in den Niederungen von Entwicklungsumgebungen auskennen" bezogen. Aber ich sehe gerade, die neuronalen Netze hast Du gar nicht selbst programmiert...

Benutzeravatar
Michael_Poschmann
Beiträge: 19877
Registriert: 05.11.2001 15:11:18
Aktuelle Projekte: Modul Menden (Sauerland)
Wohnort: Str.Km "1,6" der Oberen Ruhrtalbahn (DB-Str. 2550)

Re: Stellwerk

#44 Beitrag von Michael_Poschmann »

@ Roland, bitte nicht das gebührenpflichtige Unwort "Pareto-optimal" erwähnen. X(
OT:Wie sieht eigentlich eine Pareto-optimale Einfahrt eines narrow boats in eine ecluse aus - stecken da vier Fünftel des Schiffchens in der Schleusenkammer fest?

@ Christopher: Ich komme eher von der Nicht-bitschiebenden Zunft der Elektrik. Und bevorzug(t)e ordentliche physikalische Einheiten, also mit Kilo oder Mega vorneweg.

Wieder halbwegs on topic: Erfahrungsgemäß findet derjenige, der sich unbefangen und vor allem unbeleckt von Wissen um die Implementierungsdetails und Restriktionen einer Weichware nähert, am ehesten mögliche Eigenwilligkeiten oder gar Lücken. Der Entwickler nennt das mit einem Seufzer "DAU-Phänomen"... :P

Michael

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#45 Beitrag von Roland Ziegler »

Christopher Spies hat geschrieben:Ich selbst bin ja viel zu unkreativ, mir eigene Sprachen auszudenken. Ich hätte Brainfuck als Skriptsprache genommen...
Das wäre dann so ungefähr das Gegenteil von dem, was mir als Lösung vorschwebt :hat2. Auch wenn es auf den ersten Blick nicht so aussehen mag, mir liegt tatsächlich weniger an der akademischen Übung, als am praktischem Nutzen des Unterfangens. Wenn man es noch mit etwas Eleganz koppeln kann, um so besser. Und sooo aufwändig ist mein Ansatz ja auch nicht. :]

Konkrete Zwischenergebnisse zur Skriptsprache liegen mittlerweile vor:

Der Grammatik-Baum funktioniert, und den Syntax-Baum eines Skriptes kann ich ebenfalls aufstellen. Noch ist die Grammatik sehr einfach und kennt nur einen Befehl. Aber die Klassenstruktur darunter ist schon recht allgemein.

Meine Grammatik orientiert sich ein wenig an Pascal. Das Stichwort für die Begründung fiel schon: Kontext-Freiheit. Das ist die einfachste Form für derartige Aufgabenstellungen.

Im Beispiel zunächst meine bescheidene BNF-Definition, mit etwas abgewandelter Syntax:

Code: Alles auswählen

<program> ? main begin { <statement> } end .

<statement> ? <invoke statement>

<invoke statement> ? <word> . <word> <param list> ;
<param list> ? () | ( <arg> ) | ( <arg> {, <arg>} )
<arg> ? <word> | <enum> | <literal> 
<literal> ? <number> | <float> | <boolean> | <string>
<enum> ? @ <word> : <word>

<word>
<number>
<float>
<boolean>
<string>
Hieraus errechne ich folgenden Baum:

Code: Alles auswählen

BNF-Grammatik-Baum:
  BNFDefinition <literal> 
    BNFSequence
      BNFElement <number>
    BNFSequence
      BNFElement <float>
    BNFSequence
      BNFElement <boolean>
    BNFSequence
      BNFElement <string>
  BNFDefinition <number> Number
  BNFDefinition <boolean> Boolean
  BNFDefinition <invoke statement> 
    BNFSequence
      BNFElement <word>
      BNFSymbol Symbol=.
      BNFElement <word>
      BNFElement <param list>
      BNFSymbol Symbol=;
  BNFDefinition <enum> 
    BNFSequence
      BNFSymbol Symbol=@
      BNFElement <word>
      BNFSymbol Symbol=:
      BNFElement <word>
  BNFDefinition <program> 
    BNFSequence
      BNFKeyword KeyWord=main
      BNFKeyword KeyWord=begin
      BNFRepeatedSequence
        BNFElement <statement>
      BNFKeyword KeyWord=end
      BNFSymbol Symbol=.
  BNFDefinition <statement> 
    BNFSequence
      BNFElement <invoke statement>
  BNFDefinition <word> Word
  BNFDefinition <string> String
  BNFDefinition <arg> 
    BNFSequence
      BNFElement <word>
    BNFSequence
      BNFElement <enum>
    BNFSequence
      BNFElement <literal>
  BNFDefinition <float> Float
  BNFDefinition <param list> 
    BNFSequence
      BNFSymbol Symbol=(
      BNFSymbol Symbol=)
    BNFSequence
      BNFSymbol Symbol=(
      BNFElement <arg>
      BNFSymbol Symbol=)
    BNFSequence
      BNFSymbol Symbol=(
      BNFElement <arg>
      BNFRepeatedSequence
        BNFSymbol Symbol=,
        BNFElement <arg>
      BNFSymbol Symbol=)
Jetzt ein Skript, ohne Anspruch auf stellwerkstechnisch einwandfreie Semantik:

Code: Alles auswählen

main begin
	Kontext.ruecksetzen ();
	Block.vorblocken ("Dstadt");
	Signal.stellen ("P3", @Signalbild:HP2);
	Gleis.belegenAbschnitt ("feld 3", 300, true);
end.
Und so analysiert es mein Parser:

Code: Alles auswählen

Syntax-Baum:
  (Wurzel)
    <program>  << main begin Kontext . ruecksetzen ( ) ; Block . vorblocken ( "Dstadt" ) ; Signal . stellen ( "P3" , @ Signalbild : HP2 ) ; Gleis . belegenAbschnitt ( "feld 3" , 300 , true ) ; end . >>
      Wdlg=4  << Kontext . ruecksetzen ( ) ; Block . vorblocken ( "Dstadt" ) ; Signal . stellen ( "P3" , @ Signalbild : HP2 ) ; Gleis . belegenAbschnitt ( "feld 3" , 300 , true ) ; >>
        <statement>  << Kontext . ruecksetzen ( ) ; >>
          <invoke statement>  << Kontext . ruecksetzen ( ) ; >>
            <word>  << Kontext >>
            <word>  << ruecksetzen >>
            <param list>  << ( ) >>
        <statement>  << Block . vorblocken ( "Dstadt" ) ; >>
          <invoke statement>  << Block . vorblocken ( "Dstadt" ) ; >>
            <word>  << Block >>
            <word>  << vorblocken >>
            <param list>  << ( "Dstadt" ) >>
              <arg>  << "Dstadt" >>
                <literal>  << "Dstadt" >>
                  <string>  << "Dstadt" >>
        <statement>  << Signal . stellen ( "P3" , @ Signalbild : HP2 ) ; >>
          <invoke statement>  << Signal . stellen ( "P3" , @ Signalbild : HP2 ) ; >>
            <word>  << Signal >>
            <word>  << stellen >>
            <param list>  << ( "P3" , @ Signalbild : HP2 ) >>
              <arg>  << "P3" >>
                <literal>  << "P3" >>
                  <string>  << "P3" >>
              Wdlg=1  << , @ Signalbild : HP2 >>
                <arg>  << @ Signalbild : HP2 >>
                  <enum>  << @ Signalbild : HP2 >>
                    <word>  << Signalbild >>
                    <word>  << HP2 >>
        <statement>  << Gleis . belegenAbschnitt ( "feld 3" , 300 , true ) ; >>
          <invoke statement>  << Gleis . belegenAbschnitt ( "feld 3" , 300 , true ) ; >>
            <word>  << Gleis >>
            <word>  << belegenAbschnitt >>
            <param list>  << ( "feld 3" , 300 , true ) >>
              <arg>  << "feld 3" >>
                <literal>  << "feld 3" >>
                  <string>  << "feld 3" >>
              Wdlg=2  << , 300 , true >>
                <arg>  << 300 >>
                  <literal>  << 300 >>
                    <number>  << 300 >>
                <arg>  << true >>
                  <literal>  << true >>
                    <boolean>  << true >>
Daraus kann ich jetzt die eigentlichen Befehle generieren, was ich letzte Woche schon angefangen hatte, bevor ich mich für den formaleren Unterbau entschied. Der Parser liefert mir jetzt systematisch die Konstrukte frei Haus, die ich mir im ersten labormäßigen Ansatz noch mühsam zu Fuß extrahiert habe, um schnell zu erkennen, dass selbst für meine geringen Anforderungen die Bastellösung nicht sinnvoll war.

Wie man ahnen wird, spielen für die hier gezeigten Aufgaben Collections (Strukturen zur Verwaltung von vielen Objekten gleicher Art) eine nicht unerhebliche Rolle. Ich habe mich nun doch entschieden, die C5-Klassen zu verwenden, hier vor allem deshalb, weil sie auf Listenstrukturen Views setzen können, die zusammenhängende Ausschnitte einer Liste definieren, ohne die Liste zu verändern. Ausgesprochen hilfreich für die Übungen hier.
Zuletzt geändert von Roland Ziegler am 17.08.2008 18:47:10, insgesamt 1-mal geändert.

Benutzeravatar
Herbert Brüser
Beiträge: 367
Registriert: 23.01.2004 17:54:13
Aktuelle Projekte: Hamm-Bielefeld_KBS400_Neu
Wohnort: 59227 Ahlen
Kontaktdaten:

Re: Stellwerk

#46 Beitrag von Herbert Brüser »

Hallo Roland,
es ist offensichtlich, dass Du in deinem Stellwerk-Programm sehr viel Arbeit investierst. Von den Ansätzen deiner Programmierung versteht ein nicht Programmierer nun Garnichts. Um einen Einblick an dem Stand deines Werkes zu kennen wäre es vielleicht für den nicht Programmierer zum Vorteil, wenn Du dein Programm-Stand in einer Kurzfassung, wie Text oder Grafik vorstellen könntest?
Für die Modell-Eisenbahn wurde ein rechnergestütztes Spurplan-Stellwerk ESTWGJ (Leit- und Sicherungstechnik) von Ing. Arnold Hübsch, A-1030 Wien erstellt. Eine Testversion für 60 Tage kannst Du von der Homepage herunterladen. http://www.estwgj.com/" target="_blank
Vielleicht kannst Du hiervon einige fetched übernehmen oder inspirativ weiter entwickeln!
mfg
Herbert

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#47 Beitrag von Roland Ziegler »

Hallo Herbert,

sei versichert, auch die von Dir gewünschten allgemein verständlichen Informationen werden bereitgestellt werden, sobald es sie denn gibt.

Der derzeitige Stellwerkprojektumfang konzentriert sich auf das mechanische Stellwerk. Dieser Ursprung aller Stellwerkstechniken bietet die breiteste Grundlage.

In Braunschweig im letzten Jahr haben wir drei Nutzergruppen skizziert:
  1. Der Endanwender. Er nutzt fertige Stellwerke mit fertigen Zusi-Strecken.
  2. Der Konfigurator: Er nutzt die Editoren zum Stellwerk, um eigene Stellwerke zu konfigurieren, für bestehende oder neu erstellte Zusi3-Module.
  3. Der Entwickler. Er nutzt die Softwarebibliotheken des Stellwerkprojektes, um eigene Stellwerksbauformen zu programmieren.
Auch wenn es schon einen Editor gibt, meine Arbeiten konzentrieren sich weiterhin und vornehmlich auf den "Unterbau". Der ist zugegebenermaßen reichlich abstrakt. Aber gehört dazu. Siehe den bei Michael unbeliebten 80/20-Spruch. Momentan sind damit die Fortschrittsberichte hauptsächlich für die Nutzergruppe 3 von Interesse.

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#48 Beitrag von Roland Ziegler »

Meine kleine Skriptsprache entwickelt sich weiter.

Die jüngsten Schritte waren der Code-Generator und die Entwicklung einer Hierarchie von Code-Elementen. Ein schöne Anwendung für die klassischen Entwurfsmuster "Composite" und "Command" (auch im Klassendiagramm mit ein wenig Hinschauen zu identifizieren):
Bild

Mein Test-Skript sieht derzeit so aus (immer noch ohne Anspruch auf konkrete stellwerkstechnische Relevanz):

Code: Alles auswählen

main begin
	Kontext.ruecksetzen ();
	
	Block.hatErlaubnis ("Dstadt", 300);
	if false then begin 
		Meldung.zugAnbieten ("E4134");
		Block.hatErlaubnis ("Dstadt", 300);		
		if false then 
			break; 
	end else
		Global.warten (30);
	
	Block.vorblocken ("Dstadt");
	Signal.stellen ("P3", @Signalbild:HP2);
	Gleis.belegenAbschnitt ("feld 3", 300, true);
end.
Und das macht der Code-Generator daraus:

Code: Alles auswählen

Code-Baum:
  CmdRoot <root>
  -Ausführbare Kinder:
    CmdProgram <program>
    -Komponenten:
      CmdCompound <compound statement>
      -Ausführbare Kinder:
        CmdInvoke <invoke statement>
        -Komponenten:
          CmdCompConstVar <word> String <<Kontext>>
          CmdCompConstVar <word> String <<ruecksetzen>>
          CmdCompParamList <param list>
        CmdInvoke <invoke statement>
        -Komponenten:
          CmdCompConstVar <word> String <<Block>>
          CmdCompConstVar <word> String <<hatErlaubnis>>
          CmdCompParamList <param list>
          -Komponenten:
            CmdCompLiteral <string> String <<Dstadt>>
            CmdCompLiteral <number> Int32 <<300>>
        CmdIf <if statement>
        -Komponenten:
          CmdCompLiteral <boolean> Boolean <<False>>
          CmdCompound <compound statement>
          -Ausführbare Kinder:
            CmdInvoke <invoke statement>
            -Komponenten:
              CmdCompConstVar <word> String <<Meldung>>
              CmdCompConstVar <word> String <<zugAnbieten>>
              CmdCompParamList <param list>
              -Komponenten:
                CmdCompLiteral <string> String <<E4134>>
            CmdInvoke <invoke statement>
            -Komponenten:
              CmdCompConstVar <word> String <<Block>>
              CmdCompConstVar <word> String <<hatErlaubnis>>
              CmdCompParamList <param list>
              -Komponenten:
                CmdCompLiteral <string> String <<Dstadt>>
                CmdCompLiteral <number> Int32 <<300>>
            CmdIf <if statement>
            -Komponenten:
              CmdCompLiteral <boolean> Boolean <<False>>
              CmdBreak <break statement>
          CmdInvoke <invoke statement>
          -Komponenten:
            CmdCompConstVar <word> String <<Global>>
            CmdCompConstVar <word> String <<warten>>
            CmdCompParamList <param list>
            -Komponenten:
              CmdCompLiteral <number> Int32 <<30>>
        CmdInvoke <invoke statement>
        -Komponenten:
          CmdCompConstVar <word> String <<Block>>
          CmdCompConstVar <word> String <<vorblocken>>
          CmdCompParamList <param list>
          -Komponenten:
            CmdCompLiteral <string> String <<Dstadt>>
        CmdInvoke <invoke statement>
        -Komponenten:
          CmdCompConstVar <word> String <<Signal>>
          CmdCompConstVar <word> String <<stellen>>
          CmdCompParamList <param list>
          -Komponenten:
            CmdCompLiteral <string> String <<P3>>
            CmdCompEnum <enum>
            -Komponenten:
              CmdCompConstVar <word> String <<Signalbild>>
              CmdCompConstVar <word> String <<HP2>>
        CmdInvoke <invoke statement>
        -Komponenten:
          CmdCompConstVar <word> String <<Gleis>>
          CmdCompConstVar <word> String <<belegenAbschnitt>>
          CmdCompParamList <param list>
          -Komponenten:
            CmdCompLiteral <string> String <<feld 3>>
            CmdCompLiteral <number> Int32 <<300>>
            CmdCompLiteral <boolean> Boolean <<True>>
Im Wurzelelement könnte man jetzt prinzipiell schon execute () aufrufen und dann liefe es durch, allerdings noch ohne etwas zu tun. Der Kontext muss noch eingerichtet werden, Konstante und Variablen aufgelöst, aber das sind jetzt keine größeren Herausforderungen mehr.

Weiter geht es aber erst mal wieder mit Geländeformer, der Zusi3-Streckenbau fordert wie schon die letzten Wochen noch etwas Zuarbeit.

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#49 Beitrag von Roland Ziegler »

Das lange Wochenende habe ich genutzt, um nach mehrwöchiger Unterbrechung durch GF-Aktivitäten am Interpreter weiter zu bauen.

Hier ist mein kleines Test-Skript:

Code: Alles auswählen

main begin

	// Initialisierung
	
	// Name der Assembly, auf die sich das Skript bezieht
	Target.assembly ("StwInterpreterFassade");
	
	// Namespace für alle Klassen in der Ziel-Assembly
	Target.namespace ("Stw.Interpreter.Fassade");
	
	// Name der Kontext-Klasse in der Ziel-Assembly. 
	// Eine Instanz der Kontext-Klasse ist für die Konfiguration verantwortlich
	Target.context ("Kontext");
	
	// Parameter für die Kontext-Klasse, z.B. Konfigurationsdatei
	Target.configuration ("ConfigDstadt.xml");
	
	// Durchführung der Initialisierung
	Target.init ();
	
	
	const string Gegenstelle = "Dstadt";
	const string Eilzug = "E4134";

	const boolean False = false;
		
	Block.hatErlaubnis (Gegenstelle, 300);
	if false then begin 
		Meldung.anbietenZug (Gegenstelle, Eilzug);
		Block.hatErlaubnis (Gegenstelle, 300);		
		if False then 
			return; 
	end else
		Global.warten (30);
	
	
	Meldung.abmeldenZug (Gegenstelle, Eilzug);
	Signal.stellen ("P3", @Signalbild:Hp2);
	Block.vorblockenStrBlk (Gegenstelle);
	
	Gleis.belegenAbschnitt ("feld 3", true);
	
	Global.warten (30);
	Signal.stellen ("P3", @Signalbild:Hp0);
	Gleis.belegenAbschnitt ("feld 3", false);
	
	Block.istEntblockt (Gegenstelle, 300);
		
end.
Und hier das Trace-Log zur Laufzeit:

Code: Alles auswählen

************************ New Log ************************
16:54:31:171 0010 Here we are.
16:54:47:671 0010 Block.hatErlaubnis ( "Dstadt", 300 )
16:54:47:671 0010 Meldung.anbietenZug ( "Dstadt", "E4134" )
16:54:47:687 0010 Block.hatErlaubnis ( "Dstadt", 300 )
16:54:55:656 0010 Meldung.abmeldenZug ( "Dstadt", "E4134" )
16:54:55:656 0010 Signal.stellen ( "P3", Signalbild.Hp0 )
16:54:55:671 0010 Block.vorblockenStrBlk ( "Dstadt" )
16:54:55:671 0010 Gleis.belegenAbschnitt ( "feld 3", True )
16:55:25:671 0010 Signal.stellen ( "P3", Signalbild.Hp0 )
16:55:25:671 0010 Gleis.belegenAbschnitt ( "feld 3", False )
16:55:25:671 0010 Block.istEntblockt ( "Dstadt", 300 )
Zur Erklärung:

Mein Interpreter durchläuft zunächst drei Phasen der Quelltext-Analyse und Code-Generierung. In der ersten Phase wird der Quelltext in "Brocken" zerlegt, die sogenannten Tokens. In der zweiten Phase erfolgt für die Tokens die Syntaxanalyse, mit dem Ergebnis eines Syntax-Baums. In der dritten Phase wird daraus ein Code-Baum generiert, der zum Schluss nur noch ausführbare Objekte un deren Komponenten enthält. Den ruft man dann an der Wurzel auf, und dann läuft der generierte Code für das Skript ab.

Das Entscheidende sind die "Klasse.methode (parameter);"-Konstrukte.

Hier werden mittels Reflection für eine dynamisch zu ladende Assembly Instanzen erzeugt und Methoden aufgerufen. Diese Fähigkeit ist der eigentliche Kern meiner Interpreter-Sprache. Der Interpreter weiß selbst von der Ziel-Assembly gar nichts. Alle Zugriffe erfolgen über Reflection, nicht über Klassen-Interfaces oder Vererbung. Was der Interpreter wissen muss, bekommt er zur Laufzeit beigebracht, über die "Target"-Klasse. Dort wird auch ein Kontext-Objekt erstellt, dass in der Ziel-Assembly für die Konfiguration sorgt. Wenn irgendwas schief geht, und sich in der Ziel-Assembly das Erwartete nicht findet, gibt es Ausnahmemeldungen.

Für die Stellwerkszwecke ist die Ziel-Assembly des Interpreters auch nur eine Fassade, die selbst noch keine Stellwerksfunktionalität besitzt, diese aber kennt (kennen soll, noch ist es nicht so weit). Dafür ist auch die Auflösung der gesamten Namens-Parameter in konkrete maschinenlesbare IDs erforderlich. Dies wird über wird Tabellen der angegebenen Konfiguration erfolgen. Der Interpreter versorgt die Ziel-Assembly nur mit Konfigurationsparametern, kann damit semantisch auch damit nichts anfangen. Die Auflösung erledigt die Fassade. Im Moment ist die Fassade noch Baustelle, und besitzt nur einen Mitschreibmechanismus, der wiederum wieder mit Reflection arbeitet.

So eine noch leere Klasse sieht etwa so aus:

Code: Alles auswählen

  public class Meldung : AbstractContextBased {

    public Meldung (Kontext kontext) :
      base (kontext) { }

    public void anbietenZug (string gegenstelle, string zugNr) {
      trace (new object[] { gegenstelle, zugNr });

      // TODO implement
    }
}
Ach ja, alle Instanzen der referenzierten Klassen sind Singletons.

Benutzeravatar
Michael_Poschmann
Beiträge: 19877
Registriert: 05.11.2001 15:11:18
Aktuelle Projekte: Modul Menden (Sauerland)
Wohnort: Str.Km "1,6" der Oberen Ruhrtalbahn (DB-Str. 2550)

Re: Stellwerk

#50 Beitrag von Michael_Poschmann »

Hallo Roland,

ich sehe schon, w8ir müssen mal wieder nach Geländeformerfehlern suchen, Du wandelst sonst auf Abwegen. ;)
16:54:55:656 0010 Signal.stellen ( "P3", Signalbild.Hp0 )
...
16:55:25:671 0010 Signal.stellen ( "P3", Signalbild.Hp0 )
Beim Bemühen, den Lauf der Dinge nachzuvollziehen, wundert mich einzig, daß zweimal "Hp0" auftaucht. Das wird doch wohl keine Fahrt auf Ersatzsignal gewesen sein, oder bin ich auf dem Holzweg?

auf die Gefahr hin, in peinlicher Weise an der Syntax zu scheitern
Michael
Zuletzt geändert von Michael_Poschmann am 05.10.2008 21:21:31, insgesamt 1-mal geändert.

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#51 Beitrag von Roland Ziegler »

Fein beobachtet. Man sollte auch die enum-Auflösung implementieren. Stand sogar auf meinem Zettel. :(

So schaut's dann besser aus:

Code: Alles auswählen

************************ New Log ************************
22:20:45:125 0010 Here we are.
22:20:47:843 0010 Block.hatErlaubnis ( "Dstadt", 300 )
22:20:47:843 0010 Meldung.anbietenZug ( "Dstadt", "E4134" )
22:20:47:843 0010 Block.hatErlaubnis ( "Dstadt", 300 )
22:20:47:843 0010 Meldung.abmeldenZug ( "Dstadt", "E4134" )
22:20:51:015 0010 Signal.stellen ( "P3", Signalbild.Hp2 )
22:20:51:015 0010 Block.vorblockenStrBlk ( "Dstadt" )
22:20:51:015 0010 Gleis.belegenAbschnitt ( "feld 3", True )
22:21:25:062 0010 Signal.stellen ( "P3", Signalbild.Hp0 )
22:21:25:062 0010 Gleis.belegenAbschnitt ( "feld 3", False )
22:21:25:078 0010 Block.istEntblockt ( "Dstadt", 300 )

Benutzeravatar
Michael_Poschmann
Beiträge: 19877
Registriert: 05.11.2001 15:11:18
Aktuelle Projekte: Modul Menden (Sauerland)
Wohnort: Str.Km "1,6" der Oberen Ruhrtalbahn (DB-Str. 2550)

Re: Stellwerk

#52 Beitrag von Michael_Poschmann »

DAU im Einsatz. Keine Ahnung von der Materie, aber Formalkram. Ich gäbe bestimmt einen guten Consultant ab. Falls im Stellwerksbereich mal Bedarf besteht, nur zu. :P

Michael

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#53 Beitrag von Roland Ziegler »

Zeigt umso mehr, warum das ganze stattfindet. Mit Hilfe von Unit-Tests lässt sich Ergebnis dann maschinell überprüfen.

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#54 Beitrag von Roland Ziegler »

Ein paar Code-Schnipsel:

Eine den Zusianern wohlbekannte Aufgaben ist die der Konfiguration von Strecken und deren Einrichtungen. Dazu gehören auch die Außen- und Innenanlagen eines Stellwerks. Die Speicherung der Konfiguration erfolgt in XML-Dateien. Dabei werden sowohl Namen als auch numerische Schlüssel verwendet.

Bei der Serialisierung und der Zuordnung von Schlüsseln zu Instanzen kann man prima Generics einsetzen, also parametrierte Klassen und Methoden, und gelegentlich auch Reflection verwenden.

Das Software-Entwurfsmuster beruht auf der Zusammenfassung gleichartiger Objekte zu Collections (Container in C++). Wichtige Klassenstrukturen hierbei sind Dictionaries (in C++ und Java als Maps bekannt), die Schlüssel/Wert-Paare enthalten. Die kann man auch schachteln und damit Bäume bilden. Nur nicht direkt als XML serialisieren. Ich habe mich für diesen Zweck für einen Zwischenschritt über Listen (dynamische Arrays) entschieden. Listen oder Arrays selbst sind ausgesprochen ungeeignet, wenn man was suchen will, aber ganz praktisch, wenn man sie nur der Reihe nach abarbeiten muss.

Als Listentyp setze ich eine generische Klasse ein, die auch die Konvertierung zum Dictionary besorgt und berücksichtigt, dass die Liste auf mehreren Ebenen geschachtelt sein wird.

Der Inhaltstyp der Liste ist damit auch generisch:

Code: Alles auswählen

  [Serializable]
  public struct KeyValuePairXml<TKey, TValue> {
    [XmlAttribute]
    public TKey key;

    public TValue value;

    public KeyValuePairXml (TKey key, TValue value) {
      this.key = key;
      this.value = value;
    }
  }
Die Liste selbst muss die unterschiedlichen Wert-Typen der eigenen Liste und des Dictionaries verarbeiten:

Code: Alles auswählen

public abstract class KeyValueList<TKey, TDictValue, TListValue> : 
      ArrayList<KeyValuePairXml<TKey, TListValue>> {
    
    public void fromDictionary (IDictionary<TKey, TDictValue> dict) {
      fromDictionary (this, dict);
    }

    public static void fromDictionary (KeyValueList<TKey, TDictValue, TListValue> list,
                                       IDictionary<TKey, TDictValue> dict) {
      foreach (KeyValuePair<TKey, TDictValue> kvpD in dict) {
        TKey key = kvpD.Key;
        TDictValue dictValue = kvpD.Value;
        TListValue listValue = list.convertDictToListValue (dictValue);
        KeyValuePairXml<TKey, TListValue> kvpL = new KeyValuePairXml<TKey, TListValue> (key, listValue);
        list.Add (kvpL);
      }
    }

    protected abstract TListValue convertDictToListValue (TDictValue dictValue);
...
Die typspezifische Konvertierung muss man selbst schreiben, z.B.

Code: Alles auswählen

    // Liste Stellwerke
    public class StellwerkList : KeyValueList<string, AIA.TypgruppenMap, TypgruppenList> {
      protected override TypgruppenList convertDictToListValue (AIA.TypgruppenMap dictValue) {
        TypgruppenList list = new TypgruppenList ();
        KeyValueList<AIA.Typgruppen, AIA.NameIdModMap, NameIdList>.fromDictionary (list, dictValue);
        return list;
      }
...
Da steckt dann die Rekursion drin, die für solche Hierarchien in der Konvertierung von und zu Listen behandelt werden muss.


In XML sieht dann alles wieder einigermaßen lesbar aus - Sinn und Zweck der Übung ist ja "nur" die Auflistung der Innen- und Außenanlagen mit Namen und Schlüssel:

Code: Alles auswählen

<?xml version="1.0" encoding="utf-8"?>
<Stellwerk xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.rolandziegler.de/Stellwerk">
  <AussenInnenanlagen>
    <Module>
      <Modul key="Zusi3\Strecken\ReferenzModul">
        <Stellwerk key="Adorf">
          <Typgruppe key="BlockfelderAnfang">
            <Element key="Bhausen" value="488" />
          </Typgruppe>
          <Typgruppe key="BlockfelderEnd">
            <Element key="Bhausen" value="489" />
          </Typgruppe>
          <Typgruppe key="BlockfelderErlaubnisVor">
            <Element key="Bhausen" value="490" />
          </Typgruppe>
        </Stellwerk>
        <Stellwerk key="Bhausen F">
          <Typgruppe key="Weichen">
            <Element key="3" value="496" />
            <Element key="4" value="497" />
          </Typgruppe>
          <Typgruppe key="Fahrstrassen">
            <Element key="f1" value="516" />
            <Element key="f2" value="517" />
            <Element key="p1" value="514" />
            <Element key="p2" value="515" />
          </Typgruppe>
          <Typgruppe key="Signale">
...
Ganz spannend auch das Anlegen der Instanzen, die wiederum in flachen Dictionaries geführt werden: Schlüssel/Element. Irgendwie will man da nicht für jeden Untertyp viel Code schreiben. Eine Kombination aus Generik und Reflektion (letzteres nur zum Erzeugen der Instanz) schien mir das einfachste zu sein.

Code: Alles auswählen

      private void anlegenInstanzen<TElem, TDict> (TDict elemMap, NameIdModMap nidMap, uint modId)
        where TDict : IDictionary<ZusiId2, TElem>
        where TElem : AbstractElement {

        foreach (KeyValuePair<string, IdModul> kvp in nidMap) {
          string name = kvp.Key;
          uint objId = kvp.Value.id;
          if (objId == 0)
            continue;

          ZusiId2 zusiId2 = new ZusiId2 (objId, modId);

          Type type = typeof (TElem);
          TElem elem = Factory.CreateInstance (type, new object[] { name }) as TElem;
          if (elem != null)
            elemMap.Add (zusiId2, elem);

        }

      }
Die generischen Parameter besagen, dass Elemente vom Typ TElem verarbeitet werden, die als Wert eines Dictionaries TDict verwendet werden. TDict muss zudem als Schlüssel die konkrete Klasse ZusiId2 haben.

Die statische Klasse Factory ist hierbei meine Kapselung für die Instanziierung von Objekten per Reflection. Selbstverständlich kann man von einem generischen Parameter den Typ ermitteln, und, wenn es sich um eine Klasse handelt, davon auch Objekte erzeugen. Wenn man keine Parameter hat, geht das sogar ohne Reflection. Für einen parameter-behafteten Konstruktor habe ich allerdings keine Möglichkeit gefunden, daher Factory, die intern Activator aufruft.

Wird dann genutztin dieser Art:

Code: Alles auswählen

      private void anlegenInstanzen (NameIdModMap nidMap, Typgruppen tg, uint modId, NameNameIdMap namen) {
        switch (tg) {
          case Typgruppen.Weichen:
            anlegenInstanzen<Weiche, Weichen> (weichen, nidMap, modId);
            return;
          case Typgruppen.Fahrstrassen:
            anlegenInstanzen<Fahrstrasse, Fahrstrassen> (fahrstrassen, nidMap, modId);
            return;
          case Typgruppen.Signale:
            anlegenInstanzen<Signal, Signale> (signale, nidMap, modId);
            return;
          case Typgruppen.Gleiskontakte:
            anlegenInstanzen<Gleis, Gleise> (gleiskontakte, nidMap, modId);
            return;
          case Typgruppen.Gleisabschnitte:
...    
Soll reichen für heute, habe sicher wieder genug Verwirrung gestiftet.

Fazit: Generics und Reflection in .Net/C# bieten vielfältige Möglichkeiten. Es macht Spaß, sie zu erkunden. Und Collections/Container sind eh schon lange eines meiner Lieblingsspielzeuge. :D
Zuletzt geändert von Roland Ziegler am 18.10.2008 12:03:00, insgesamt 1-mal geändert.

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Application Domains

#55 Beitrag von Roland Ziegler »

In der späteren Laufzeitumgebung werden am Stellwerk mindestens zwei Prozesse beteiligt sein, zum einen Zusi selbst mit dem dort eingebunden Stellwerksserver, zum anderen ein oder mehrere Stellwerk-Clients, auf denen die eigentliche Action spielt.

Zum automatischen Testen sind solche Mehr-Prozess-Szenarien eher ungünstig, erfordern sie doch das koordinierte Starten und Stoppen der Prozesse und zur Auswertung von Rückmeldungen Inter-Prozess-Kommunikation. Letzteres ließe sich zwar über Remoting elegant lösen, ist natürlich trotzdem mit Aufwand verbunden.

Gewaltig vereinfachen könnte man es, wenn man Client und Server klassisch im selben Prozess laufen ließe, und bei einem gescheiten OO-Ansatz sollte dem auch nicht allzu viel im Wege stehen, wären da nicht die static-Objekte, ob zustandsbehaftete statische Klassen oder Instanzen von Singletons. Davon gibt es im klassischen Prozess-Speichermodell nur je Klasse genau ein (Pseudo-Klassen-)Objekt. Das ist für meinen Stellwerksansatz beim Testen hinderlich. So habe ich z.B. die Simulationszeitumgebung als statisch eingerichtet, denn die soll für alle anderen Objekte der jeweiligen Umgebung gelten. Will ich aber testen, dass das auch sauber funktioniert, muss ich die Simulationszeit für Client und Server getrennt verwalten können. Geht also so nicht.

Aber .Net wäre nicht .Net, wenn es nicht auch hierfür eine nette Lösung bereit hätte, die Application Domains. .Net spielt sich sich zur Laufzeit in einer virtuellen Maschine ab (wie Java), die selbst als Prozess läuft. Innerhalb der CLR (Common Language Runtime, so heißt die virtuelle Maschine in .Net) kann man eine weitere Speicher- und Zuständigkeitsaufteilung nutzen, besagte Application Domains. Die sind wohl ursprünglich für Web-Server-Anwendungen konzipiert worden, stehen aber auch der Allgemeinheit zur Verfügung. Sie verhalten sich nach unten fast wie ein Prozess, vor allem, was die Speicherverwaltung angeht. Nach oben hin sind allerdings deutlich einfacher zu benutzen als ein Prozess. Auch wenn Speicher und Objekte in den Domains komplett getrennt sind, so kann man doch mit den Objekten kommunizieren, über das bekannte Remoting. Das geschieht hier mehr oder weniger automatisch, ohne dass der Anwender mehr als eine Zeile Code schreiben muss.

Es hat eine Weile gedauert, bis es bei mir gestern Abend lief, und noch nicht im vollen Funktionsumfang aber im Grundprinzip. Die entscheidende Methode von AppDomain ist CreateInstanceAndUnwrap, bei der man auch in der Vorbereitung darauf einige dumme Fehler machen kann. :(

Übrigens benutzt NUnit ebenfalls diesen Weg über Domänen, um auf saubere Art Testumgebung und Testkandidaten zu trennen.

Benutzeravatar
Michael_Poschmann
Beiträge: 19877
Registriert: 05.11.2001 15:11:18
Aktuelle Projekte: Modul Menden (Sauerland)
Wohnort: Str.Km "1,6" der Oberen Ruhrtalbahn (DB-Str. 2550)

Re: Stellwerk

#56 Beitrag von Michael_Poschmann »

Moin Roland,

das klingt so, als ginge es voran. Den unbedeutenden Rest Deiner Aussagen lasse ich mir nachher mal durch Fachleute übersetzen. ;) Muß ich womöglich doch bald mal die gehörteten Blockfeld-Beschriftungen aus Wennemen "Wf" rauskramen, um eine passende Hardware-Testumgebung zu zimmern?

OT:Heute ist allerdings eine Konkurrenzveranstaltung dran. Falls Du noch Fragen zur garbage collection hast, kann ich diese gerne weiterreichen, schließlich spielt bei den Alemannia-Damen eine diplomierte und bald sogar vollwissenschaftliche Müllrührerin, pardon Abfallentsorgerin mit. Beziehungsweise Rohstoffingenieurin, wie's nunmehr heißt.

Somit hält Dich kein spontaner AC-Stammtisch vom Weiterbasteln ab. Gutes Gelingen!

Michael

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#57 Beitrag von Roland Ziegler »

Die Application Domains sind mittlerweile eingebaut, so dass der Unterbau für die Kombination von Client und Server in einem Prozess steht. Neben der mit Application Domains erreichten räumlichen Entkopplung (speichermäßig) wird auch eine zeitliche Entkopplung benötigt. Diese geschieht über Worker-Threads, der wohl bekanntesten und einfachsten Ausführung von Active Object, bei dem eine Methode eines Objektes asynchron, in einem separaten Thread ausgeführt wird. Ist die Methode beendet, wird auch der Thread beendet. Der aufrufende Thread wartet auf das Ende des Worker-Thread und holt sich das Ergebnis ab, ggf. auch ein Exception, die zwischendurch aufgetreten sein könnte. Mit Worker-Threads lassen sich alle Test-Instanzen, Server und Client, gleichzeitig starten. Dann wird auf das Ende aller Worker-Threads gewartet.

Der Testaufbau gestattet den Test von Server (Zusi-Emulation) mit einem und auch mehreren Clients (Stellwerken). Zudem kann ein Client mehrere Stellbezirke umfassen, logisch getrennte Stellwerke, die jedoch gemeinsam konfiguriert werden, z,B. Fahrdienstleiter und Wärter-Stellwerk an beiden Bahnhofsköpfen. Es wird später ein Testszenario geben (NUnit), mit dem die verschiedenen Fälle nacheinander durchlaufen.

Begonnen hatte ich die gegenwärtige Phase der Entwicklung mit der Fassade für Clients (schon im letzten Jahr, vor der Pause), der Schnittstelle, über die ein Stellwerk mit der Kommunikationsschicht interagiert. Hier war mein Ziel, dieses für den Stellwerksentwickler single-threaded zu machen, auch wenn die Kommunikation selber multi-threaded ist (siehe ältere Beiträge). GUI-Anwendungen (auch Windows) sind üblicherweise single-threaded. Sie werden über den "Event-Loop" synchronisiert, die berühmte Nachrichtenschleife, die den meisten GUI-Architekturen gemeinsam ist. Meine Testumgebung hat auf dieser Ebene kein GUI, die Nachrichtenschleife brauche ich trotzdem. Hier kommt nun die Klasse Synchronizer zum Einsatz, meine Implementierung der vollen Funktionalität von Active Object. Die Schnittstelle in .Net heißt ISynchronizeInvoke (auch noch mal hier erwähnt).

Bei meiner Client-Fassade finde ich auch noch ein paar Fehlerchen, die das Single-Threaded-Konzept verletzen, sprich, sich an der Synchronisierung vorbeischleichen. Auch das Aufräumen klappt noch nicht ganz, insbesondere wenn im Konstruktor bereits was schief läuft. Dann wird kein Objekt erzeugt, aber möglicherweise wurden bereits Ressourcen alloziert, die dann nicht wieder freigegeben werden. Aber dafür schreibt man ja Tests. 8o

Trotzdem ist es mir gestern Abend gelungen, über die Testumgebung sowohl Server als auch Client gemeinsam zu starten und vom Client die Verbindung zum Server aufbauen zu lassen. Im Skript für den Server war eine Funktion enthalten, die auf die Verbindung eines bestimmten Client explizit wartete. Nichtzustandekommen der Verbindung wäre ein Fehlerabbruchkriterium für den Test gewesen. Da ist man als Entwickler doch schon etwas fasziniert, wenn dieses komplexe Gebilde aus Aufrufhierarchieen, generischen und nicht-generischen Teilen, diversen unterschiedlich synchronisierten Threads und Remoting-Kommunikation auf zwei Ebenen schlussendlich tatsächlich zusammenspielt! :]

Natürlich experimentiere ich auch ganz gerne mit den Möglichkeiten von .Net und C#. Eine der wiederkehrenden Aufgaben bei meinen Test-Skripten sind Funktionen der Art "Liegt ein bestimmter Zustand vor? Wenn nicht, tritt der gewünschte Zustand innerhalb einer gegebenen Wartezeit ein?" (Siehe Beispiele in älteren Beiträgen.)

Meine Umsetzung beginnt ganz simpel, dass ich zunächst (thread-sicher) prüfe, ob der gewünschte Zustand bereits vorliegt. Dann warte ich auf Ereignisse. Hierzu existiert eine Warteschlange für Client oder Server, in die die jeweilige Emulation alle Ereignisse einträgt, wie z.B. beim Server An- und Abmeldung eines Client. Test und Client/Server-Emulation laufen immer in verschiedenen Threads, s.o. Für den Test selbst und den Skript-Ablauf ist es der Worker-Thread, für den Emulator der nachgemachte "Event-Loop". Die erwähnte Warteschlange, eine FIFO-Queue, dient der einfachen Rückmeldung von Ereignissen zwischen beiden. Im Ereignis wird nur die Kategorie übertragen, nicht der tatsächliche Zustand. Der tatsächliche Zustand wird nach Eintreffen eines passenden Ereignisses explizit durch Abfrage geprüft. Für die vorliegende Aufgabenstellung ist dies ein adäquates Verfahren.

Die Methoden, die für derartige Zustandsprüfung aufgerufen werden müssen, haben untereinander eine gewisse Ähnlichkeit, aber z.B. unterschiedliche Ausprägung von Parametern (Signaturen). Trotzdem wollte ich den Mechanismus "Prüfen, ggf warten auf Ereignis, dann erneut prüfen, usw. bis Zeit abgelaufen" nicht jedesmal neu implementieren. Ich habe mich für .Net/C#-Delegates entschieden, mit late Binding (zur Laufzeit). Delegates sind so ein Art Klassen, die nur aus einer einzigen Methode bestehen. Es sind immer einzelne Funktionen, die aufgerufen werden müssen. Und alle Funktionen liefern true oder false zurück. Die Signaturen aber sind unterschiedlich. Damit scheidet early Binding (zur Compilezeit) aus, denn der konkrete Aufruf steht zu diesem Zeitpunkt noch nicht fest. Delegates können beides. Late Binding ist langsamer, denn es nutzt Reflection. Early Binding mit einem konkreten Delegate wird in C# einfach mit dem Aufruf der Variablen selbst ausgeführt, late Binding mit dem Aufruf der Delegate-Methode DynamicInvoke ().

Soweit der heutige Fortschrittsbericht. 8)

Benutzeravatar
(Ar-) T-Rex
Beiträge: 4795
Registriert: 19.02.2003 21:07:56
Aktuelle Projekte: Seit 65 Millionen Jahren die Entwicklung der Eisenbahn beobachten
Wohnort: Österreich
Kontaktdaten:

Re: Stellwerk

#58 Beitrag von (Ar-) T-Rex »

Sehr gut! Danke!

:respekt
ZPA-Bereich Österreich

E-mail:
oesterreich@zpa.zusi.de

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Stellwerk

#59 Beitrag von Roland Ziegler »

Der Zweck von Tests liegt ja nun darin begründet, Fehler zu erkennen und beseitigen zu können. Und tatsächlich habe ich auch noch ein paar in meiner Kommunikationsumgebung gefunden. Systematik zahlt sich aus.

Mein Eilzug fährt nun von Adorf nach Bhausen, mit Bhausen F und Bhausen W als eigenständigen Stw-Clients. Für das Thema von neulich, Benachrichtigung des Wärters zur Erlaubnisabgabe, gibt es verschiedene Optionen. In diesem Beispiel habe ich es über Tastensperre und Anschalter konfiguriert. Außerdem hört der Wärter (sporadisch) die Zugmeldungen mit.

Das tut und sieht der Server:

Code: Alles auswählen

main begin

	// Initialisierung
	
	// Name der Assembly, auf die sich das Skript bezieht
	Target.assembly ("StwScriptFassade");
	
	// Namespace für alle Klassen in der Ziel-Assembly
	Target.namespace ("Stw.Script.Fassade");
	
	// Name der Kontext-Klasse in der Ziel-Assembly. 
	// Eine Instanz der Kontext-Klasse ist für die Konfiguration verantwortlich
	Target.context ("ServerKontext");
	
	// Parameter für die Kontext-Klasse, z.B. Konfigurationsdatei
	Target.configuration ("Testfeld1.xml", 0, 30);
	
	// Durchführung der Initialisierung
	Target.init ();
	
	const string Adorf = "Adorf";
	const string Bhausen_W = "Bhausen W";
	const string Bhausen_F = "Bhausen F";
	const string Bhausen = "Bhausen";
	const string Eilzug = "E4711";
	
	Sitzung.simulZeit ("16.09.87 16:40", 1);
	Sitzung.stellwerk ();
	Meldung.rundspruch ("Willkommen beim Stellwerkstest.");
	
	// Änderung Anfangszustand
	Sitzung.stellwerk (Adorf);
	Block.abgebenErlaubnis (Bhausen);
	
	// hier geht's los
	Sitzung.istAngemeldet (Bhausen_F, 30);
	if false then return;
	Sitzung.istAngemeldet (Bhausen_W, 30);
	if false then return;
	
	Sitzung.logAktuellerZustand ();
	
	// Perspektive Adorf
	Sitzung.stellwerk (Adorf);
	Block.hatErlaubnis (Bhausen, 10);
	if false then begin 
		Meldung.anbietenZug (Bhausen_F, Eilzug);
		Block.hatErlaubnis (Bhausen, 60);		
		if false then 
			return; 
	end 
	
	Global.warten (5);
	
	Meldung.abmeldenZug (Bhausen_F, Eilzug, "41");
	Meldung.hatZugmeldung (Bhausen_F, @Zugmeldung:AbmeldenBestaetigen, Eilzug, 30); 
	if false then return;
	Meldung.richtigZug (Bhausen_F);
	
	Global.warten ("16:41");
	Block.vorblocken (Bhausen);
	
	Sitzung.stellwerk (Bhausen_W);
	Gleis.belegenAbschnitt (Adorf, true, Eilzug);
	
	Signal.zeigtSignalbild ("A", @Signalbild:Hp2, 180);
	if false then return;
	Weiche.istGestellt ("1", true, 0);
	Weiche.istGestellt ("2", true, 0);
	Fahrstrasse.istGestellt ("a2", true, 0);
	
	Global.warten (20);
	Gleis.belegenAbschnitt (Adorf, false, Eilzug);
	Gleis.belegenKontakt ("a2", true, Eilzug);
	Global.warten (10);
	Gleis.belegenKontakt ("a2", false, Eilzug);
	
	Sitzung.stellwerk (Adorf);
	Block.istEntblocktAnf (Bhausen, 180);
	if false then return;
	
	Global.warten (90);
	
end.
Hier der Fahrdienstleiter:

Code: Alles auswählen

main begin

	Global.warten (5);

	// Initialisierung
	
	// Name der Assembly, auf die sich das Skript bezieht
	Target.assembly ("StwScriptFassade");
	
	// Namespace für alle Klassen in der Ziel-Assembly
	Target.namespace ("Stw.Script.Fassade");
	
	// Name der Kontext-Klasse in der Ziel-Assembly. 
	// Eine Instanz der Kontext-Klasse ist für die Konfiguration verantwortlich
	Target.context ("ClientKontext");
	
	// Parameter für die Kontext-Klasse, z.B. Konfigurationsdatei
	Target.configuration ("Testfeld1.xml", "localhost", "Bhausen F");
	
	// Durchführung der Initialisierung
	Target.init ();
		
	const string Adorf = "Adorf";
	const string Bhausen_W = "Bhausen W";
	const string Bhausen_F = "Bhausen F";
	const string Bhausen = "Bhausen";
	const string Eilzug = "E4711";

	Meldung.rundspruch ("Hier ist Bhausen F.");
	
	Meldung.hatZugmeldung (Adorf, @Zugmeldung:Anbieten, Eilzug, 120);
	if false then return;
	Global.warten (2);
	Meldung.annehmenZug (Adorf, Eilzug);
	Block.auftragErlaubnisabgabe (Adorf);
	Global.warten (2); 
	
	
	Meldung.hatZugmeldung (Adorf, @Zugmeldung:Abmelden, Eilzug, 30); 
	if false then return;
	Global.warten (2);
	Meldung.bestaetigenZug (Adorf, Eilzug);	
	Meldung.hatZugmeldung (Adorf, @Zugmeldung:Richtig, Eilzug, 30);
	if false then return;		
	Block.blockenBfBlk ("a2");
	
	Signal.zeigtSignalbild (Bhausen_W, "A", @Signalbild:Hp2, 300);
	if false then return;		
		
	Block.istEntblocktBfBlk ("a2", 60);
	if false then return;		
	
	Meldung.rueckmeldenZug (Adorf, Eilzug);
	
	Block.istGeblocktEnd (Bhausen_W, Adorf, true, 60);
	if false then return;	
	
	Global.warten (45);
	
end.
Und hier der Wärter, bei dem die meiste Arbeit anfällt:

Code: Alles auswählen

main begin

	Global.warten (5);

	// Initialisierung
	
	// Name der Assembly, auf die sich das Skript bezieht
	Target.assembly ("StwScriptFassade");
	
	// Namespace für alle Klassen in der Ziel-Assembly
	Target.namespace ("Stw.Script.Fassade");
	
	// Name der Kontext-Klasse in der Ziel-Assembly. 
	// Eine Instanz der Kontext-Klasse ist für die Konfiguration verantwortlich
	Target.context ("ClientKontext");
	
	// Parameter für die Kontext-Klasse, z.B. Konfigurationsdatei
	Target.configuration ("Testfeld1.xml", "localhost", "Bhausen W");
	
	// Durchführung der Initialisierung
	Target.init ();
		
	const string Adorf = "Adorf";
	const string Bhausen_W = "Bhausen W";
	const string Bhausen_F = "Bhausen F";
	const string Bhausen = "Bhausen";
	const string Eilzug = "E4711";

	Meldung.rundspruch ("Hier ist Bhausen W.");
	
	
	Block.hatAuftragErlaubnisabgabe (Adorf, 60); 
	if false then return; 
	Block.abgebenErlaubnis (Adorf); 
	Block.sperrenTaste (Adorf);
	
	Meldung.hatZugmeldungMitgehoert (Adorf, Bhausen_F, @Zugmeldung:Abmelden, Eilzug, 90); 
	if false then return; 
	
	Block.istEntblocktBfBlk ("a2", 30); 
	if false then return; 
	Global.warten (2); 
	Weiche.stellen ("1", true); 
	Global.warten (2); 
	Weiche.stellen ("2", true); 
	Global.warten (2); 
	Fahrstrasse.stellen ("a2", true); 
	
	Gleis.wartenAbschnitt (Adorf, Eilzug, 180);
	if false then return;
	
	Global.warten (2);   
	Signal.stellen ("A", @Signalbild:Hp2);
	
	Block.istEntblocktEnd (Adorf, 180);
	if false then return;  
	
	Gleis.wartenKontakt ("a2", 60);
	
	Global.warten (2); 
	Signal.stellen ("A", @Signalbild:Hp0);
	Global.warten (2); 
	Fahrstrasse.stellen ("a2", false);
	Global.warten (2); 
	Weiche.stellen ("2", false);
	Global.warten (2); 
	Weiche.stellen ("1", false);
	Global.warten (2); 
	Block.blockenBfBlk ("a2");
	
	Global.warten (2); 
	Block.rueckblocken (Adorf);
	
	Global.warten (45);
	
end.
Das ganze ist weiterhin ein reiner Kommunikationstest. Verschlusslogik spielt keine Rolle. Geprüft wird, ob die richtigen Informationshäppchen zur richtigen Zeit am richtigen Ort in richtiger Reihenfolge und vollständig eintreffen.

Unter der NUnit-Testumgebung stellt sich dann z.B. so dar:
Bild

Der unter NUnit gezeigte Trace-Output ist nur der Container. Die Application Domains von Server und Clients schreiben ihren eigene, aussagekräftigeren. Ein Ausschnitt daraus sieht so aus:

Bild

Ein wenig Kleinkram und Kosmetik noch, und das Vorhandene dokumentieren, dann ist der Meilenstein erreicht. :)

Benutzeravatar
Michael_Poschmann
Beiträge: 19877
Registriert: 05.11.2001 15:11:18
Aktuelle Projekte: Modul Menden (Sauerland)
Wohnort: Str.Km "1,6" der Oberen Ruhrtalbahn (DB-Str. 2550)

Re: Stellwerk

#60 Beitrag von Michael_Poschmann »

Hallo Roland,

so hat ein jeder sein Päckchen zu tragen. :]
Für das eine oder andere Getränk sollte also wieder genügend Gesprächsstoff vorhanden sein. Fein, wenn das Schlechtwetterwochenende sich offenbar gelohnt hat.

Drähteziehende Grüße
Michael

Antworten