13.01.2014 Aufrufe

Dokument 1.pdf - Staatliche Studienakademie Glauchau

Dokument 1.pdf - Staatliche Studienakademie Glauchau

Dokument 1.pdf - Staatliche Studienakademie Glauchau

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

TRANSIM TECHNOLOGY CORPORATION<br />

Bachelor Thesis<br />

Erweiterung des Engine Framework und der Web-Schematic<br />

Lösung der Transim Technology Corporation um eine<br />

Echtzeitkommunikation zwischen Simulator und Schematic<br />

Vorgelegt am: 30.08.2013<br />

Von:<br />

Martin Meyer<br />

Flemmingstraße 19<br />

09116 Chemnitz<br />

Studiengang:<br />

Technische Informatik<br />

Studienrichtung: Prozessinformatik<br />

Seminargruppe:<br />

4TI10-1<br />

Matrikelnummer: 4000652<br />

Praxispartner:<br />

Transim Technology Corporation<br />

Annaberger Straße 240<br />

09125 Chemnitz<br />

Gutachter:<br />

Herr Dipl.-Inf. (FH) Frank Vogel (Transim Technology Corp.)<br />

Herr Dr. Mathias Sporer (<strong>Staatliche</strong> <strong>Studienakademie</strong> <strong>Glauchau</strong>)


Inhaltsverzeichnis<br />

Inhaltsverzeichnis<br />

Inhaltsverzeichnis ................................................................................................... IV<br />

Abbildungsverzeichnis ........................................................................................... VI<br />

Tabellenverzeichnis ............................................................................................... VII<br />

Formelverzeichnis ................................................................................................. VIII<br />

Listingverzeichnis ................................................................................................... IX<br />

Abkürzungsverzeichnis ........................................................................................... X<br />

1 Einführung ................................................................................................... 1<br />

1.1 Hinführung zum Thema ................................................................................. 1<br />

1.2 Motivation ...................................................................................................... 1<br />

1.3 Einblick in das EngineFramework ................................................................. 2<br />

1.4 Hinweise zur Arbeit ....................................................................................... 3<br />

1.4.1 Verwendete Beispiele und Simulationen ....................................................... 3<br />

1.4.2 Gliederung ..................................................................................................... 5<br />

2 Analyse des bestehenden Systems .......................................................... 6<br />

2.1 Die Simulationsanwendung “Qucs” ............................................................... 6<br />

2.2 Der EngineServer .......................................................................................... 7<br />

2.2.1 Allgemein ...................................................................................................... 7<br />

2.2.2 Der EngineServer als Webdienst .................................................................. 8<br />

2.3 Die QucsEngine ............................................................................................ 8<br />

2.4 Die Darstellung der Simulationsdaten in der Webanwendung ...................... 9<br />

3 Änderungen an Qucs ................................................................................ 11<br />

3.1 Vorüberlegungen ......................................................................................... 11<br />

3.1.1 Kommunikation zwischen den Anwendungen ............................................. 11<br />

3.1.2 Verwendung des Netzwerkprotokolls .......................................................... 12<br />

3.1.3 Zuweisung der gesendeten Daten zu einer Simulation ............................... 13<br />

3.1.4 Formatierung der Simulationsergebnisse .................................................... 14<br />

3.1.4.1 Analyse der bisherigen Ausgabedateien von Qucs ..................................... 14<br />

3.1.4.2 Betrachtungen zu der Paketgröße und dem Datenformat ........................... 15<br />

3.1.5 Nachbilden zeitintensiver Simulationen ....................................................... 17<br />

3.1.6 Die Netzwerkkommunikation ....................................................................... 17<br />

3.2 Implementierung ......................................................................................... 18<br />

3.2.1 Der Programmeinstieg ................................................................................ 18<br />

3.2.2 Die Netzwerkklasse ..................................................................................... 18<br />

3.2.3 Einbinden der Netzwerkklasse .................................................................... 19<br />

3.2.3.1 Die Klassenhierarchie der Simulationssoftware .......................................... 19<br />

3.2.3.2 Erkennen der Simulationsergebnisse .......................................................... 20<br />

3.2.3.3 Erkennen der Abhängigkeiten ..................................................................... 20<br />

Seite IV


Inhaltsverzeichnis<br />

4 Die QucsLiveEngine ................................................................................. 22<br />

4.1 Vorbetrachtungen ........................................................................................ 22<br />

4.1.1 Starten der Simulation ................................................................................. 22<br />

4.1.2 Empfangen der Simulationsdaten ............................................................... 22<br />

4.1.3 Auswerten des XML-<strong>Dokument</strong>es ............................................................... 23<br />

4.1.4 Konvertieren der Daten ............................................................................... 24<br />

4.1.5 Erzeugen der Waveform Dateien ................................................................ 25<br />

4.1.6 Die Manifest Datei ....................................................................................... 26<br />

4.1.7 Speichern von Kurvenscharen .................................................................... 26<br />

4.2 Implementierung ......................................................................................... 27<br />

4.2.1 Starten des TCP-Servers und der Simulationsanwendung ......................... 27<br />

4.2.2 Empfangen der Daten ................................................................................. 28<br />

4.2.3 Auslesen des TCP-Stroms .......................................................................... 29<br />

4.2.4 Speichern der Simulationsergebnisse ......................................................... 30<br />

5 Der EngineServer ...................................................................................... 32<br />

5.1 Vorbetrachtungen ........................................................................................ 32<br />

5.1.1 Das Postprocessing .................................................................................... 32<br />

5.1.2 Bereitstellen der Simulationsdaten .............................................................. 34<br />

5.2 Implementierung ......................................................................................... 35<br />

5.2.1 Anpassen der QucsLiveEngine ................................................................... 35<br />

5.2.2 Speichern der Daten im Zwischenspeicher des EngineServers .................. 36<br />

5.2.3 Starten des Postprocessing ........................................................................ 37<br />

6 Anzeige der Simulationsergebnisse in WebScope ................................ 38<br />

6.1 Vorbetrachtung ............................................................................................ 38<br />

6.1.1 Anfrage der Simulationsdaten ..................................................................... 38<br />

6.1.2 Ausgabe der Simulationsdaten zu Laufzeit ................................................. 38<br />

6.1.2.1 Allgemein .................................................................................................... 38<br />

6.1.2.2 Erneutes Abfragen aller Ergebnisse (Polling).............................................. 39<br />

6.1.2.3 Abfragen nach neuen Ergebnissen (Long Polling) ...................................... 39<br />

6.1.2.4 Websocket-Protokoll ................................................................................... 40<br />

6.1.2.5 Senden der Ergebnisse vom Webserver ..................................................... 40<br />

6.2 Implementierung ......................................................................................... 40<br />

6.2.1 Auslesen des Zwischenspeichers im EngineServer .................................... 40<br />

6.2.2 Ausgabe der Simulationsdaten ................................................................... 41<br />

7 Bewertung und Ausblick .......................................................................... 43<br />

Quellenverzeichnis ................................................................................................. 44<br />

Anhangverzeichnis ................................................................................................. 46<br />

Seite V


Abbildungsverzeichnis<br />

Abbildungsverzeichnis<br />

Abbildung 1 Tiefpass erster Ordnung 3<br />

Abbildung 2 Transientsimulation des Tiefpasses 4<br />

Abbildung 3 Wechselstrom-Simulation des Tiefpasses 4<br />

Abbildung 4 Das Web-Schematic 9<br />

Abbildung 5 WebScope nach Abschluss einer Simulation 10<br />

Abbildung 6 Die Klassenhierarchie der Simulatoren von Qucs 19<br />

Abbildung 7 Speichern der Ergebnisse in der Waveform-Datei 25<br />

Seite VI


Tabellenverzeichnis<br />

Tabellenverzeichnis<br />

Tabelle 1 Vergleich des Overheads bei XML und JSON .................................... 16<br />

Tabelle 2 Format der Achsbezeichung in der Manifest-Datei ............................. 26<br />

Seite VII


Formelverzeichnis<br />

Formelverzeichnis<br />

Formel 1 Berechnung der Grenzfrequenz ............................................................ 5<br />

Formel 2 Differenz des Overheads von XML und JSON .................................... 17<br />

Seite VIII


Listingverzeichnis<br />

Listingverzeichnis<br />

Listing 1 Beispiel für eine Netzliste ...................................................................... 6<br />

Listing 2 Ausschnitt einer Qucs-Ausgabedatei .................................................. 14<br />

Listing 3 Startparameter für die Netzwerkkommunikation ................................. 18<br />

Listing 4 Auswahl des Ports für den TCP-Server .............................................. 28<br />

Listing 5 Lesen der XML-<strong>Dokument</strong>e aus dem TCP-Puffer ............................... 30<br />

Listing 6 Beispiel für die PostProcessingCommands ........................................ 32<br />

Listing 7 Von FlashGen erzeugte Datei zur Berschreibung der Diagramme ..... 33<br />

Listing 8 Änderungen an der generischen Klasse ............................................. 35<br />

Listing 9 Aufruf der Rückruffunktion in der QucsLiveEngine .............................. 36<br />

Listing 10 Änderungen an der EngineServer-Klasse ........................................... 36<br />

Listing 11 Polling in WebScope ........................................................................... 42<br />

Seite IX


Abkürzungsverzeichnis<br />

Abkürzungsverzeichnis<br />

AC<br />

Alternating current<br />

AJAX<br />

API<br />

CLI<br />

DLL<br />

DOM<br />

GPL<br />

HTML<br />

IIS<br />

IP<br />

JSON<br />

Qucs<br />

RFID<br />

SAX<br />

SimID<br />

TCP<br />

UserID<br />

UDP<br />

URL<br />

XML<br />

Asynchronous JavaScript and XML<br />

Application Programming Interface<br />

Common Language Infrastructure<br />

Dynamic Link Library<br />

Document Object Model<br />

GNU General Public License<br />

Hypertext Markup Language<br />

Internet Information Services<br />

Internet Protocol<br />

JavaScript Object Notation<br />

Quite Universal Circuit Simulator<br />

Radio-frequency identification<br />

Simple API for XML<br />

Simulation Identifikationsnummer<br />

Transmission Control Protocol<br />

Benutzeridentifikationsnummer<br />

User Datagram Protocol<br />

Uniform Resource Locator<br />

Extensible Markup Language<br />

Seite X


Einführung<br />

1 Einführung<br />

1.1 Hinführung zum Thema<br />

Die Transim Technology Corporation hat sich auf die Entwicklung von<br />

Webanwendungen spezialisiert, in denen unterschiedliche Simulationsmöglichkeiten,<br />

speziell für Firmen aus der Halbleiterindustrie, realisiert werden können. Für die<br />

Simulationen werden Anwendungen von Drittherstellern verwendet. Die<br />

Webanwendungen stellen dabei eine Schnittstelle zwischen den<br />

Simulationsanwendungen und der grafischen Oberfläche im Webbrowser des<br />

Endnutzers dar. Simulationen dienen der Analyse von realen Systemen durch die<br />

Verwendung von Modellen. Dadurch kann das Verhalten eines Systems untersucht<br />

werden, ohne dieses real nachbilden zu müssen. Ein weiterer Vorteil besteht darin,<br />

dass die Modelle parametrisiert werden können. In einer realen Nachbildung des<br />

Systems müssen Komponenten entsprechend ausgetauscht werden, im<br />

Simulationsmodell muss lediglich ein Parameter geändert werden. Es entsteht somit<br />

eine erhebliche Zeit- und Kostenersparnis. Für Firmen aus der Halbleiterindustrie<br />

bedeutet dies, dass sie ihren Kunden neue Bauelemente über eine<br />

Simulationsanwendung bereitstellen können. Die Kunden können über diese<br />

Anwendung testen, ob und in welcher Art und Weise die Bauelemente für ihre<br />

Zwecke verwendet werden können.<br />

Für die Simulation können hierbei unterschiedliche Arten angeboten werden. Die<br />

wichtigste Rolle spielen dabei die Simulationen von elektronischen Schaltungen. So<br />

stehen beispielsweise Transientsimulationen zur Verfügung, über die das<br />

Einschaltverhalten von Systemen analysiert werden kann, oder eine Wechselstrom-<br />

Simulation, die eine Analyse des Amplituden- und Phasengangs ermöglicht. Auf<br />

diese Simulationen wird in dieser Arbeit näher eingegangen. Es sind jedoch auch<br />

Simulationen möglich, über die zum Beispiel unterschiedliche Entwürfe von RFID-<br />

Antennen (engl.: radio-frequency identification) untersucht werden können.<br />

1.2 Motivation<br />

Neu entwickelte Bauelemente müssen für ihre Einsatzgebiete entsprechend<br />

angepasst werden. Ohne Simulationen müssten die Bauelemente in den realen<br />

Systemen umständlich getestet werden, bis sie ihre Funktion korrekt erfüllen. Die<br />

immer schneller voranschreitende Entwicklung der Technik setzt die Entwickler von<br />

elektronischen Geräten zunehmend unter Druck. Ohne die Verwendung von<br />

Simulationen müssten neue Bauelemente sehr zeit- und kostenaufwendig in ihren<br />

Einsatzgebieten getestet werden und würde zu einer langsameren Entwicklung<br />

führen.<br />

Die Komplexität der Bauelemente nimmt immer weiter zu, wodurch die Bedeutung<br />

der Simulation immer größer wird. Komplexere Bauelemente bedeuten jedoch auch<br />

Seite 1


Einführung<br />

komplexere Simulationen. Da die Rechenleistung eines Computers beschränkt ist,<br />

kommt es somit zu einer längeren Ausführungszeit der Simulation. In dem bisherigen<br />

System der Transim Technology Corporation werden die Simulationsergebnisse erst<br />

nach Abschluss der Simulation ausgegeben. Bei komplexen Simulationen führt dies<br />

zu einer langen Wartezeit des Endnutzers. Im Rahmen dieser Arbeit soll eine<br />

Möglichkeit gefunden werden, die Simulationsergebnisse während der Berechnung<br />

dem Endnutzer zur Verfügung zu stellen. Sieht dieser, dass beispielsweise<br />

Schwellwerte überschritten werden, kann er die Simulation abbrechen und mit<br />

geänderten Parametern erneut starten und muss nicht auf das Ende der gesamten<br />

Simulation warten.<br />

1.3 Einblick in das EngineFramework<br />

Wurde für eine Firma aus der Halbleiterindustrie eine Webanwendung entwickelt,<br />

kann der Endnutzer über diese zunächst die Bauelemente der Firma auswählen.<br />

Nach der Auswahl können für diese unterschiedliche Parameter festgelegt werden.<br />

Handelt es sich um eine elektronische Simulation wird dem Nutzer die Schaltung des<br />

Systems im Webbrowser angezeigt. Diese Anzeige wird WebSchematic genannt.<br />

Hier besteht die Möglichkeit, Elemente der Schaltung zu parametrisieren und eine<br />

Simulation zu Starten. Wurde die Simulation gestartet, werden die Parameter an den<br />

Webserver der Webanwendung übertragen. Um die Simulation zu starten, muss der<br />

Webserver zunächst eine Anfrage an den sogenannten JobManager stellen. Dieser<br />

verteilt die Simulationsanfragen auf unterschiedliche Simulationsserver auf. Diese<br />

Simulationsserver werden EngineServer genannt. Die Transim Technology<br />

Corporation nutzt dieses System, um die Simulationen auf mehrere Computer<br />

aufzuteilen. Steigt die Anzahl der Simulationen kann leicht ein neuer<br />

Simulationsserver in das System eingebunden werden. Nachdem der<br />

Simulationsanfrage ein EngineServer zugewiesen wurde, werden die<br />

Simulationsparameter an diesen weitergeleitet. In diesen ist unter anderem<br />

enthalten, welche Simulationsanwendung verwendet werden soll. Die Schnittstelle<br />

zwischen den Anwendungen und dem EngineServer erfolgt über die sogenannten<br />

Engines. Für jede Simulationsanwendung existiert eine eigene Engine. Nachdem die<br />

Parameter beim EngineServer eingegangen sind, startet dieser die entsprechende<br />

Engine. In dieser wird die Simulationsanwendung gestartet. Nach Abschluss der<br />

Simulation müssen die Simulationsergebnisse von der Engine in ein eigenes Format<br />

konvertiert und gespeichert werden. Ist die Simulation abgeschlossen, teilt der<br />

EngineServer dies dem Webserver der Webanwendung mit. Dieser kann nun die<br />

Simulationsergebnise vom EngineServer abfragen, und dem Endnutzer in der<br />

Webanwendung zur Verfügung stellen. Die graphische Darstellung der<br />

Simulationsergebnisse im Webbrowser erfolgt in dem sogenannten WebScope.<br />

Seite 2


Einführung<br />

1.4 Hinweise zur Arbeit<br />

1.4.1 Verwendete Beispiele und Simulationen<br />

Zum besseren Verständnis dieser Bachelor Thesis werden unterschiedliche<br />

Beispiele verwendet. Diese bauen auf den Tiefpass erster Ordnung aus der<br />

Elektronik auf. Unter einem Tiefpass ist ein Filter zu verstehen, der aus einem<br />

eingehenden Signal die Frequenzanteile abschwächt, die über der Grenzfrequenz<br />

des Tiefpasses liegen. Abbildung 1 zeigt die Schaltung eines Tiefpasses erster<br />

Ordnung.<br />

Abbildung 1 Tiefpass erster Ordnung<br />

Elektronische Schaltungen, wie der Tiefpass, lassen sich auf unterschiedliche Art<br />

und Weise simulieren. In dieser Arbeit wird lediglich auf die Transientsimulation und<br />

die Wechselstrom-Simulation, auch AC-Simulation (alternating current) genannt,<br />

eingegangen.<br />

Bei der Transientsimulation wird das zeitliche Verhalten eines Systems dargestellt.<br />

Sie wird verwendet um die Antwort eines Systems auf ein bestimmtes<br />

Eingangssignal zu analysieren. Als typische elektrische Kenngrößen lassen sich<br />

unter anderem die Flankensteilheit (engl.: slew rate) oder die Einschwingzeit (engl.:<br />

rise time) ablesen. Für diese Simulationsart muss die Endzeit angegeben werden.<br />

Bei einigen Simulatoren kann eine Startzeit angegeben werden. Diese beschränkt<br />

dabei lediglich die Ausgabedaten des Simulators. Da es sich um eine zeitliche<br />

Darstellung handelt, muss der Simulator stets bei t=0s beginnen, um das gesamte<br />

Verhalten des Systemes abzubilden. In Abbildung 2 ist das Ergebnis einer<br />

Transientsimulation zu dem in Abbildung 1 gezeigten Tiefpass aufgeführt (Startzeit:<br />

0s, Endzeit: 0.25ms).<br />

Seite 3


Einführung<br />

Abbildung 2 Transientsimulation des Tiefpasses<br />

Zu sehen ist, dass die Eingangsspannung Ue, sowie die Ausgangsspannung Ua über<br />

der Zeit abgebildet sind. In dem hier gewählten Beispiel ist zu erkennen, dass die<br />

Ausgangsspannung bereits deutlich geschwächt gegenüber der Eingangsspannung<br />

ist. Neben der Schwächung des Signales ist das Einschaltverhalten des Systems<br />

ersichtlich.<br />

Die Wechselstrom-Simulation ermöglicht es, den Verlauf des Amplituden- und<br />

Phasenganges eines Systems zu analysieren. Dazu wird eine Sinusspannungsquelle<br />

mit variabler Frequenz verwendet. Dem Simulator muss dabei die Anfangs- und<br />

Endfrequenz, sowie die Schrittweite mitgeteilt werden. Bei der Simulation werden für<br />

jede Frequenz die Ströme und Spannungen für jeden Knotenpunkt berechnet. In<br />

Abbildung 3 ist das Ergebnis einer Wechselstrom-Simulation für den Tiefpass aus<br />

Abbildung 1 dargestellt.<br />

Abbildung 3 Wechselstrom-Simulation des Tiefpasses<br />

Zu sehen ist der Amplituden- und Phasengang der Eingangs- und<br />

Ausgangsspannung über der Frequenz. Während die Eingangsspannung konstant<br />

bleibt, ist zu erkennen, dass die Ausgangsspannung bei der Grenzfrequenz einbricht<br />

Seite 4


Einführung<br />

und eine Phasenverschiebung erfolgt. Die Berechnung der Grenzfrequenz ist in<br />

Formel 1 aufgeführt. 1<br />

Formel 1 Berechnung der Grenzfrequenz<br />

Für jede Simulationsart besteht die Möglichkeit, Eigenschaften von Elementen zu<br />

parametrisieren. Für diesen Parameterdurchlauf muss ein Start- und Endwert, sowie<br />

die Anzahl der Zwischenschritte angegeben werden. Die Simulation wird für jeden<br />

Wert des Parameters durchgeführt. Als Ergebnis erhält man eine Kurvenschar. Die<br />

Parameterdurchläufe können dabei beliebig verschachtelt werden.<br />

Die Anwendung Qucs bietet die Möglichkeit, Gleichungen zu den Schaltungen<br />

anzugeben. Darüber lassen sich zusätzliche Werte berechnen, wie beispielsweise<br />

die Phasenverschiebung der Ausgangsspannung bei einer Wechselstrom-<br />

Simulation. Das Format der Transim Technology Corporation zur Darstellung von<br />

Schaltungen bietet derzeit noch keine Möglichkeit Gleichungen einzubinden. Aus<br />

diesem Grund wird im Rahmen dieser Arbeit nicht näher auf diese eingegangen.<br />

1.4.2 Gliederung<br />

Im Rahmen dieser Arbeit wird auf mehrere Programme bzw. Unterprogramme<br />

eingegangen die für die Realisierung der Aufgabe modifiziert werden müssen. In<br />

Kapitel 2 wird ein Überblick über die Programme gegeben und auf deren<br />

Besonderheiten eingegangen. Die anschließenden Kapitel gehen tiefgründiger auf<br />

die vorzunehmenden Modifizierungen an den Programmen ein. Hierbei werden<br />

zuerst Vorbetrachtungen getroffen und anschließend auf die Implementierung näher<br />

eingegangen. Die Reihenfolge der Kapitel ist an den Ablauf der Daten im System<br />

angepasst. So wird zunächst auf den Simulator Qucs eingegangen. Anschließend<br />

werden die Konvertierung und die Speicherung der Simulationsergebnisse im<br />

EngineFramework behandelt, bevor schließlich die Ausgabe der Daten im<br />

Webbrowser realisiert wird.<br />

1 vgl. online: Tiefpass auf Wikipedia.org (07.08.2013)<br />

Seite 5


2 Analyse des bestehenden Systems<br />

2.1 Die Simulationsanwendung “Qucs”<br />

Analyse des bestehenden Systems<br />

Die Anwendung Qucs (Quite Universal Circuit Simulator) dient der Simulation von<br />

Schaltungen. Der Quellcode ist unter der GPL (GNU General Public License) Lizenz<br />

frei verfügbar. 2 Im Rahmen der Studienarbeit wurde bereits näher auf die<br />

Anwendung eingegangen und Änderungen vorgenommen. 3 In dieser Bachelor<br />

Thesis wird die in der Studienarbeit angepasste Version von Qucs verwendet. Der<br />

Quellcode von Qucs ist in mehrere Teile gegliedert. In dieser Arbeit wird<br />

ausschließlich der Simulator „qucsator.exe“ betrachtet. Dieser kann unabhängig von<br />

den anderen Teilen erstellt und anschließend ausgeführt werden. Die<br />

Simulationsanwendung wurde in der Sprache C++ programmiert.<br />

Um eine Simulation zu starten, müssen dem Programm der Zielpfad zu einer<br />

Ausgabedatei, sowie der Quellpfad einer Netzlistendatei angegeben werden. In einer<br />

Netzliste werden alle Komponenten einer Schaltung in textueller Form gespeichert.<br />

Dazu zählen zum einen die verwendeten Bauteile. Dazu zählen sowohl analoge<br />

Elemente (wie Strom- oder Spannungsquellen, Kondensatoren, usw.), als auch<br />

digitale Elemente (wie Taktgeneratoren, Logikgatter, FlipFlops, usw.). Neben den<br />

Bauteilen befinden sich die Daten zu den entsprechenden Simulationen in der<br />

Netzliste wieder. Ein Beispiel für eine solche Netzliste ist in Listing 1 aufgeführt.<br />

# Qucs 0.0.17 C:/Qucs/Tiefpass/AC_Sweep.sch<br />

# Bauelemente:<br />

Vac:V1 gnd Ue U="1 V" f="1 GHz" Phase="0" Theta="0"<br />

R:R Ue Ua R="R1" Temp="26.85" Tc1="0.0" Tc2="0.0" Tnom="26.85"<br />

C:C gnd Ua C="1 nF" V=""<br />

# Simulationen:<br />

.AC:AC1 Type="log" Start="1 Hz" Stop="10 GHz" Points="50" Noise="no"<br />

.SW:SW1 Sim="AC1" Type="log" Param="R1" Start="5 Ohm" Stop="5 MOhm"<br />

Points="3"<br />

Listing 1 Beispiel für eine Netzliste<br />

Hier wurden, zur besseren Darstellung, die Komponenten nach Art sortiert und<br />

Kommentare eingefügt. Für die Simulationsanwendung sind die Art und die<br />

Reihenfolge der Komponenten nicht entscheidend.<br />

Wurde der Anwendung beim Start eine Netzliste übergeben, werden alle<br />

Simulationen ausgeführt. Die Ergebnisse werden anschließend in eine Ausgabedatei<br />

geschrieben. Für zeitaufwändige Simulationen bedeutet dies, dass lange auf die<br />

Ergebnisse gewartet werden muss. Eine Ausgabe der Zwischenergebnisse während<br />

der Simulation ist nicht möglich. Diese Funktion soll im Rahmen dieser Bachelor<br />

2 online: Qucs auf Sourceforge (12.07.2013)<br />

3 vgl. Studienarbeit MEYER, 2013<br />

Seite 6


Analyse des bestehenden Systems<br />

Thesis in die Anwendung implementiert werden. Die Zwischenergebnisse sollen<br />

dabei anderen Programmen zur Verfügung gestellt werden.<br />

2.2 Der EngineServer<br />

2.2.1 Allgemein<br />

Die Transim Technology Corporation nutzt Simulationsanwendungen von<br />

Drittherstellen. Somit können den Kunden unterschiedliche Simulationsmöglichkeiten<br />

zur Verfügug gestellt werden. Der EngineServer stellt dabei die Verbindung zwischen<br />

der Webanwendung und dem Simulator her. Zu den verwendeten<br />

Simulationsanwendungen zählen beispielsweise SIMetrix, Portunus, LTSpice, Qucs,<br />

etc. Diese benötigen zum Starten eine Abbildung der Schaltung in Form einer<br />

Netzliste, oder einer Schematic-Datei. Das Format dieser Dateien ist für jede<br />

Anwendung unterschiedlich. Ebenso werden die Simulationsergebnisse in einem,<br />

von der Software abhängigem Format gespeichert. Zur Darstellung der Schaltungen<br />

und der Simulationsergebnisse, hat die Transim Technology ein eigenes<br />

Datenformat entwickelt. Dies bedeutet, dass vor einer Simulation die Schaltung in<br />

das für die Anwendung spezifische Format konvertiert wird. Nach der Simulation<br />

müssen schließlich die Ergebnisse wieder in das Transim-spezifische Format<br />

konvertiert werden. Die Konvertierung der Schaltung in eine Qucs-Netzliste ist<br />

bereits in dem EngineFramework implementiert. Auf diese wird im Rahmen dieser<br />

Arbeit nicht näher eingegangen.<br />

Aufgrund der unterschiedlichen Formate der einzelnen Simulationsanwendungen,<br />

muss die Schnittstelle zu dem EngineServer für jede Software angepasst werden.<br />

Aus diesem Grund hat die Transim Technology Corporation eine Generische Klasse<br />

entwickelt, welche die grundlegenden Funktionen, wie das Starten der Simulation,<br />

oder das Konvertieren der Ergebnisse, bereits deklariert. Für jeden Simulator<br />

existiert eine eigene Klasse, welche von dieser generischen Klasse erbt. Diese<br />

Klassen, die die Schnittstelle zwischen dem Simulator und dem EngineServer<br />

darstellen werden folgend Engine genannt. Auf die Engine, die Qucs steuert, wird im<br />

folgenden Kapitel näher eingegangen.<br />

Neben dem Steuern der Simulationsanwendungen hat der EngineServer ebenfalls<br />

die Aufgabe, die Simulationsergebnisse in unterschiedlichen Arten weiter zu<br />

verarbeiten (genannt PostProcessing). So können beispielsweise die Ergebnisse, in<br />

einem kartesischen Koordinatensystem dargestellt, als Bilddatei gespeichert werden.<br />

Ebenfalls werden die Simulationsergebnisse für die Darstellung in einer<br />

Webanwendung vorbereitet. Darauf wird im späteren Verlauf der Arbeit näher<br />

eingegangen.<br />

Seite 7


Analyse des bestehenden Systems<br />

2.2.2 Der EngineServer als Webdienst<br />

Der EngineServer wurde als Webdienst implementiert. Webdienste dienen zur<br />

Interaktion zwischen unterschiedlichen Anwendungen über ein Netzwerk. Der<br />

Webdienst verfügt über eine Schnittstellenbeschreibung, in der definiert ist, wie mit<br />

dem Webdienst interagiert werden kann. Andere Programme können Anfragen an<br />

den Webdienst stellen, auf die dieser mit der entsprechenden Information antwortet.<br />

Der Webdienst wurde in der von Microsoft entwickelten C++/CLI programmiert.<br />

Dabei handelt es sich um eine Abwandlung der Programmiersprache C++, die um<br />

Sprachelemente erweitert wurde um einen Zugriff auf die Laufzeitumgebung der<br />

.NET-Plattform zu ermöglichen. Somit können Besonderheiten der Common<br />

Language Infrastructre (CLI), wie Ereignisse, automatische Speicherbereinigung,<br />

generische Klassen, etc. verwendet werden.<br />

Zum Ausführen des Webdienstes werden die Internet Information Services (IIS) von<br />

Microsoft benötigt. Diese werden von Microsoft für die eigenen Betriebssysteme zur<br />

Verfügung gestellt.<br />

Wird eine neue Simulation gestartet wird dieser eine eindeutige<br />

Identifikationsnummer für die Simulation (das JobHandle) zugewiesen. Diese wird<br />

benötigt, um eine Zuordnung zwischen der ursprünglichen Simulationsanfrage und<br />

den Simulationsergebnissen herstellen zu können.<br />

2.3 Die QucsEngine<br />

Im Rahmen der Studienarbeit wurde die QucsEngine zur Steuerung der Anwendung<br />

Qucs entwickelt. 4 Diese startet zunächst über eine Netzliste die Simulation. Ist diese<br />

abgeschlossen, werden die Simulationsergebnisse aus der Ausgabedatei<br />

ausgelesen und in das framework-spezifische Format konvertiert und gespeichert.<br />

Dazu wird die Ausgabedatei zeilenweise eingelesen und ausgewertet. Dieser<br />

Algorithmus ist für die Verarbeitung der Simulationsergebnisse zur Laufzeit nicht<br />

weiter verwendbar.<br />

Der bisherige Ablauf der QucsEngine soll weiterhin möglich sein. Dies bedeutet,<br />

dass der Engine mitgeteilt werden muss, welche Art von Simulation sie starten soll.<br />

Die QucsEngine ist von einer generischen Klasse abgeleitet. In dieser ist bisher noch<br />

keine Möglichkeit integriert zwischen unterschiedlichen Arten einer Simulation zu<br />

unterscheiden. Dazu muss sich zunächst firmenintern abgesprochen werden, wie<br />

diese Unterteilung erfolgen soll. Aus diesem Grund wurde sich vorerst dazu<br />

entschieden, eine neue Engine, die QucsLiveEngine, zu entwickeln.<br />

Die QucsEngine ist ebenfalls wie der EngineServer in C++/CLI programmiert. Somit<br />

können die Funktionen des .NET-Frameworks verwendet werden. Um die Engine auf<br />

4 vgl. Studienarbeit MEYER, 2013<br />

Seite 8


Analyse des bestehenden Systems<br />

dem EngineServer nutzen zu können, muss diese kompiliert und erstellt werden. Der<br />

Erstellprozess erzeugt eine dynamische Programmbibliothek (engl.: Dynamic Link<br />

Library – DLL). Diese kann nun im EngineServer verwendet werden. Dazu muss in<br />

einer Konfigurationsdatei ein Verweis auf die Bibliothek gesetzt und der<br />

EngineServer neu gestartet werden. Da die Engine von einer generischen Klasse<br />

abgeleitet ist, die dem EngineServer bekannt ist, muss dieser nicht neu erstellt<br />

werden, um die neue Engine verwenden zu können.<br />

2.4 Die Darstellung der Simulationsdaten in der Webanwendung<br />

Um die Darstellung einer Schaltung in einem Webbrowser zu ermöglichen, hat die<br />

Transim Technology Corporation das sogenannte Web-Schematic entwickelt. Dieses<br />

bietet neben der Darstellung der Schaltung ebenfalls die Möglichkeit, Parameter für<br />

einzelne Bauteile anzupassen. Über das Web-Schematic können die Simulationen<br />

der Schaltungen gestartet werden. In Abbildung 4 ist ein Beispiel für das Web-<br />

Schematic zu sehen.<br />

Abbildung 4 Das Web-Schematic<br />

Wurde eine Simulation gestartet muss der Nutzer der Webanwendung warten bis die<br />

Simulation beendet wurde. In dieser Zeit wird ein Dialogfenster angezeigt, das über<br />

die ungefähre restliche Bearbeitungszeit der Simulation Auskunft gibt. Ist die<br />

Simulation abgeschlossen werden die Simulationsdaten in einem zusätzlichen Modul<br />

angezeigt. Dieses Modul wird WebScope genannt. Hier können die<br />

Simulationsergebnisse als Kurvenverläufe in unterschiedlichen Diagrammen<br />

Seite 9


Analyse des bestehenden Systems<br />

abgebildet werden. Abbildung 5 zeigt ein Beispiel für die Ausgabe der<br />

Simulationsdaten in WebScope<br />

Abbildung 5 WebScope nach Abschluss einer Simulation<br />

Im Rahmen dieser Arbeit soll eine Möglichkeit entwickelt werden, dem Nutzer die<br />

Simulationsdaten zur Laufzeit der Simulation anzuzeigen.<br />

Seite 10


Änderungen an Qucs<br />

3 Änderungen an Qucs<br />

3.1 Vorüberlegungen<br />

3.1.1 Kommunikation zwischen den Anwendungen<br />

Zum Datenaustausch zwischen zwei oder mehreren Programmen, auch<br />

Interprozesskommunikation genannt, gibt es unterschiedliche Methoden die im<br />

Folgenden kurz näher erläutert werden: 5<br />

Shared Memory<br />

Bei dieser Methode wird ein Bereich im Arbeitsspeicher festgelegt, auf den mehrere<br />

Prozesse zugreifen können. Um ein gleichzeitiges Lesen/Schreiben von mehreren<br />

Prozessen zu verhindern wird ein Mechanismus zum Wechselseitigem Ausschluss<br />

(auch Mutex genannt) verwendet. Dem Empfänger muss dabei mitgeteilt werden,<br />

wann neue Daten vorliegen. Diese Methode eignet sich besonders bei großen<br />

Datenmengen, jedoch müssen Sender- und Empfängerprozess auf den<br />

gemeinsamen Speicher zugreifen können. Somit ist diese Methode nicht geeignet,<br />

wenn die Programme auf unterschiedlichen Computern ausgeführt werden.<br />

Ereignisse<br />

Bei dieser Methode kann der sendende Prozess ein sogenanntes Ereignis auslösen.<br />

Der empfangende Prozess kann sich für dieses Ereignis registrieren. Dazu muss ein<br />

Unterprogramm (auch Behandlungsroutine genannt) angegeben werden, welches<br />

bei Eintreten des Ereignisses ausgeführt werden soll. Ein Beispiel dieses Konzeptes<br />

stellen die Software Interrupts dar. Diese Methode wird nur von einigen<br />

Betriebssystemen unterstützt, wobei jedes Betriebssystem die Methode<br />

unterschiedlich implementiert hat.<br />

Funktionsaufrufe<br />

Eine weitere Methode besteht darin, einem anderen Prozess Zugriff auf einzelne<br />

Funktionen zu gewähren. Bei dieser Methode ist es möglich, dass sich Sender- und<br />

Empfängerprogramm auf unterschiedlichen Computern befinden. Diese Methode ist<br />

in vielen Programmiersprachen über zusätzliche Bibliotheken und Dienstprogramme<br />

eingebunden und somit weitestgehend vom Betriebssystem unabhängig.<br />

Datenströme<br />

Als letzte Methode stehen die Datenströme zur Verfügung. Diese bieten die<br />

Möglichkeit Daten sequentiell von dem Sender zum Empfänger weiter zu reichen.<br />

Dabei schreibt der Sender in den Datenstrom, während der Empfänger die Daten<br />

aus diesem Strom liest. Diese Methode kann über Pipes, Named Pipes oder<br />

5 vgl. online: Interprozesskommunikation auf Wikipedia.org (15.07.2013)<br />

Seite 11


Änderungen an Qucs<br />

verbindungsorientierte Netzwerkprotokolle realisiert werden. Hier spricht man auch<br />

von Sockets. Dabei kann über Pipes lediglich innerhalb eines Prozesses<br />

kommuniziert werden. Mithilfe von Named Pipes ist dies auch zwischen<br />

unterschiedlichen Prozessen möglich, jedoch müssen diese auf demselben<br />

Computer ausgeführt werden. Durch die Nutzung von Sockets können auch<br />

Prozesse, die auf unterschiedlichen Computern ausgeführt werden untereinander<br />

kommunizieren.<br />

Für die Wahl der Methode sind zunächst die Anforderungen zu betrachten. Es ist<br />

denkbar, dass Qucs die Simulationsergebnisse an eine Anwendung senden soll, die<br />

nicht auf demselben Computer ausgeführt wird. Somit fällt die Nutzung eines<br />

gemeinsamen Speichers aus der Auswahl. Des Weiteren soll eine hohe<br />

Kompatibilität der Kommunikation erreicht werden. Da die ereignisgesteuerte<br />

Kommunikation stark von dem Betriebssystem abhängt entfällt somit auch diese. Für<br />

die Verwendung von Funktionsaufrufen werden in C++ zusätzliche<br />

Programmbibliotheken benötigt. Die Wahl viel somit auf die Verwendung von<br />

Sockets, weil diese in den meisten Betriebssystemen in C++ zur Verfügung stehen.<br />

Um die Simulationsergebnisse über einen Socket einer anderen Anwendung zur<br />

Verfügung zu stellen, benötigt Qucs die Adressinformationen des Empfängers. Zu<br />

diesen Adressinformationen zählen die Adressfamilie, die IP-Adresse des Computers<br />

auf dem der Empfängerprozess ausgeführt wird, sowie der Port, auf den der<br />

Empfängerprozess gebunden ist. Die Adressfamilie wird dabei auf „Internetadresse“<br />

festgelegt. Die IP-Adresse, sowie der Port des Empfänger-Computers sollen variabel<br />

gehalten werden. Befinden sich Qucs und Empfängerprozess auf dem gleichen<br />

Computer muss als IP-Adresse 127.0.0.1 angegeben werden.<br />

3.1.2 Verwendung des Netzwerkprotokolls<br />

Wie aus Kapitel 3.1.1 hervorgeht, soll für die Übertragung der Dateien eine Socket-<br />

Kommunikation implementiert werden. Hierbei gibt es Stream- und Datagram-<br />

Sockets, die jeweils durch ein Protokoll vereinbart sind<br />

Transmission Control Protocol (TCP) – Stream Socket<br />

Das TCP stellt zwischen zwei Endpunkten eine Verbindung her, es ist somit ein<br />

verbindungsorientiertes Protokoll. Dabei können Daten in beide Richtungen<br />

übertragen werden. Treten bei der Verbindung Datenverluste auf, werden diese<br />

automatisch erkannt und behoben. Ebenfalls wird sichergestellt, dass die<br />

gesendeten Pakete in der gleichen Reihenfolge bei dem Empfänger eingehen in der<br />

sie beim Sender verschickt wurden.<br />

User Datagram Protocol (UDP) – Datagram Socket<br />

UDP ist, im Gegensatz zu TCP, ein verbindungsloses Netzwerkprotokoll. Alle Daten<br />

werden versendet, ohne zu prüfen, ob diese entgegengenommen werden.<br />

Seite 12


Änderungen an Qucs<br />

Datenverluste werden nicht erkannt, und somit auch nicht behoben. Die Reihenfolge<br />

der empfangenen Pakete muss nicht der Reihenfolge der gesendeten Pakete<br />

entsprechen.<br />

Der Vorteil von TCP besteht in dem Sicherheitsaspekt. Jedoch kann es zu<br />

Verzögerungen kommen, da ein weiteres Paket erst übertragen wird, wenn<br />

sichergestellt ist, dass das vorherige am Empfänger eingetroffen ist. Somit hat UDP<br />

einen erheblichen Vorteil, wenn es darum geht schnell Daten zu übertragen.<br />

Da zu diesem Zeitpunkt der Entwicklung nicht abzusehen ist, ob es darauf ankommt,<br />

die Ergebnisse sicher zu übertragen und dafür eine längere Übertragungszeit<br />

akzeptiert wird, oder ob es auf eine schnelle Übertragung ankommt, bei der auch<br />

Pakete verloren gehen können, ist die Entscheidung gefallen zunächst beide<br />

Protokolle in Qucs zu implementieren.<br />

3.1.3 Zuweisung der gesendeten Daten zu einer Simulation<br />

Aus den bisher getroffenen Überlegungen ergibt sich der folgende Ablauf auf dem<br />

EngineServer. Wird eine neue Simulation angefordert, wird eine neue QucsEngine<br />

erstellt. Diese startet Qucs und empfängt während der Simulation über eine<br />

Netzwerkschnittstelle die Ergebnisse. Es ist jedoch denkbar, dass während eine<br />

Simulation läuft, eine weitere gestartet wird. Somit werden weitere<br />

Simulationsergebnisse über das Netzwerk übertragen. Dies bedeutet, dass die<br />

übertragenen Daten, einer QucsEngine zugeordnet werden müssen. Bei der Lösung<br />

dieses Problems kamen die folgenden zwei Lösungen in Betracht.<br />

Zuweisung über eine Identifikationsnummer<br />

Eine Überlegung besteht darin, auf dem EngineServer eine Serverinstanz<br />

auszuführen, die alle Simulationsergebnisse entgegen nimmt. Anschließend weißt<br />

dieser die Ergebnisse an die entsprechende QucsEngine zu. Dafür muss jedoch eine<br />

Unterscheidung der eingehenden Daten erfolgen. Um dies zu erreichen kann Qucs<br />

eine eindeutige Identifikationsnummer beim Starten übergeben werden. Diese<br />

Nummer wird anschließend bei der Übertragung jedes einzelnen<br />

Simulationsergebnisses mit an den Server übertragen. Dieser kann anhand der<br />

Identifikationsnummer die Ergebnisse den Simulationen zuweisen. Bei dieser<br />

Variante wird lediglich ein Server zur Entgegennahme der Ergebnisse benötigt. Der<br />

Nachteil dieser Variante besteht jedoch darin, dass eine erhöhte Netzwerkauslastung<br />

stattfindet, und Qucs um einen zusätzlichen Startparameter erweitert werden muss.<br />

Zuweisung über einen Port<br />

Als zweite Möglichkeit kam in Betracht, dass jede QucsEngine eine eigene<br />

Serverinstanz startet. Diese bindet sich an einen beliebigen freien Port. Dieser Port<br />

wird beim Starten von Qucs übergeben. Somit werden die Simulationsergebnisse nur<br />

an den entsprechenden Port übertragen und es entstehen keine zusätzlichen<br />

Seite 13


Änderungen an Qucs<br />

Netzwerkdaten. Der Nachteil besteht jedoch darin, dass für jede Simulation eine<br />

eigene Serverinstanz erstellt werden muss.<br />

Die Wahl fiel schließlich auf die portbasierte Zuweisung. Da Qucs zum Starten des<br />

Netzwerkdienstes bereits die IP-Adresse und der Port der Serverinstanz übergeben<br />

werden müssen, entfällt hier der zusätzliche Startparameter. Die Serverinstanz kann<br />

geschlossen werden sobald die Simulation beendet ist. Bei der Verwendung der<br />

anderen Überlegung müsste die Instanz entweder permanent ausgeführt werden,<br />

oder eine Steuerung implementiert werden, die diese erst beendet, wenn keine<br />

Simulation ausgeführt wird.<br />

3.1.4 Formatierung der Simulationsergebnisse<br />

3.1.4.1 Analyse der bisherigen Ausgabedateien von Qucs<br />

Betrachtet man die Ausgabedateien von Qucs, erkennt man, dass die Ergebnisse in<br />

einer XML-ähnlichen Struktur gespeichert werden. In Listing 2 ist eine Ausgabedatei<br />

in gekürzter Form aufgeführt. Die vollständige Datei befindet sich in Anhang 1. Die<br />

Ergebnisse einer Simulation werden in den -Tags gespeichert. Als Attribute<br />

wird in ihnen angegeben, von welchen Eingangsparametern diese abhängen. Die<br />

Werte für die Eingangsparameter sind dabei in den -Tags hinterlegt.<br />

<br />

<br />

+1.00000000000e+000<br />

...<br />

<br />

<br />

...<br />

<br />

...<br />

<br />

-4.93480220054e-017-j3.14159265359e-009<br />

-4.93479002439e-007-j3.14158490204e-004<br />

...<br />

<br />

<br />

+1.00000000000e+000-j1.57079632679e-008<br />

...<br />

<br />

...<br />

Listing 2 Ausschnitt einer Qucs-Ausgabedatei<br />

Bei der Netzwerkausgabe der Simulationsergebnisse müssen diese Abhängigkeiten<br />

ebenfalls übergeben werden, da sonst keine Zuordnung der einzelnen Ergebnisse zu<br />

den Eingangsparametern möglich ist. Es ergibt sich daraus die Überlegung, dass<br />

nach einem erfolgten Simulationsschritt eine Liste für die Abhängigkeiten, sowie eine<br />

Liste für die Ergebnisse erstellt wird und anschließend Beide an die Serverinstanz<br />

übertragen werden.<br />

Seite 14


3.1.4.2 Betrachtungen zu der Paketgröße und dem Datenformat<br />

Änderungen an Qucs<br />

Wie bereits erläutert, sollen die Simulationsergebnisse über ein Netzwerk<br />

ausgegeben werden. Die Übertragung der Daten kann dabei im gleichen Thread wie<br />

die Berechnung erfolgen, oder in einen neuen Thread verlagert werden. Wird nur ein<br />

Thread dafür verwendet kommt es zu einer Verlangsamung der gesamten<br />

Simulation, da während der Übertragungszeit keine neuen Ergebnisse berechnet<br />

werden. Lagert man das Senden der Daten in einen extra Thread aus, benötigt man<br />

einen zusätzlichen Puffer, in dem die Simulationsergebnisse zwischengespeichert<br />

werden. Für die Übertragung würde dann ein Ergebnis aus dem Puffer entnommen<br />

und an den Server übertragen werden. Läuft die Berechnung der Ergebnisse jedoch<br />

deutlich schneller, als das Senden, kann es passieren, dass keine Elemente mehr in<br />

den Puffer geschrieben werden können. Ebenfalls muss hier dafür gesorgt werden,<br />

dass nicht gleichzeitig vom Puffer gelesen und darauf geschrieben wird (dies wird<br />

auch Threadsicherheit genannt) und ist aufwändiger zu implementieren. Aus diesem<br />

Grund wurde sich für die Verwendung eines Threads entschieden. Um die Dauer des<br />

Blockierens zu verringern muss eine Optimierung an der Paketgröße der<br />

übertragenen Daten erfolgen.<br />

Der Netzzugang im späteren Einsatzgebiet wird das Ethernet sein. Ethernet regelt<br />

den Datenaustausch zwischen zwei Endpunkten über Pakete. Um eine möglichst<br />

schnelle Ausgabe der Simulationsergebnisse zu erreichen, muss die Anzahl der<br />

übertragenen Datenpakete gering gehalten werden. Laut den Spezifikationen des<br />

Ethernet-Rahmens, stehen pro Paket für die zu sendenden Daten maximal 1.500<br />

Byte zu. 6<br />

Eine Übertragung der Ergebnisse soll nach einem Simulationsschritt erfolgen. Neben<br />

den einzelnen Ergebnissen werden auch die entsprechenden Abhängigkeiten<br />

gesendet. Dazu wird ein Datenformat entworfen, das ermöglicht, mehrere<br />

Ergebnisse, sowie die zugehörigen Abhängigkeiten abzubilden. Hier haben sich zwei<br />

Formen etabliert, auf die im Folgenden kurz eingegangen wird.<br />

XML (Extensible Markup Language)<br />

Die XML ist eine Auszeichnungssprache und kann hierarchisch strukturierte Daten in<br />

Textform darstellen. Sie wurde von dem World Wide Web Consortium (W3C)<br />

spezifiziert.<br />

JSON (JavaScript Object Notation)<br />

Die JavaScript Object Notation ist ein Datenformat. Im Gegensatz zu XML ist JSON<br />

typisiert, wobei jedoch nur grundlegende Typen unterstützt werden. Ein JSON-<br />

<strong>Dokument</strong> lässt sich mit JavaScript direkt in ein Objekt umwandeln.<br />

6 vgl. IEEE 802.3-2012-Section One, 2013, S. 53-57<br />

Seite 15


Änderungen an Qucs<br />

Über beide Formate lassen sich Daten in Textform abbilden. Sowohl XML als auch<br />

JSON werden von vielen Programmiersprachen unterstützt. Durch die<br />

unterschiedlichen Formatierungen entsteht jedoch bei beiden Formen ein<br />

unterschiedlicher Overhead. Als Overhead werden alle Daten bezeichnet, die nicht<br />

zu den Nutzdaten gehören. In Tabelle 1 sind ein XML-, sowie ein JSON-Format<br />

aufgeführt. Ebenfalls wurden die Zeichen, sowie die daraus resultierende Größe des<br />

Overheads bestimmt.<br />

Übersicht<br />

XML<br />

<br />

<br />

<br />

...<br />

<br />

...<br />

<br />

...<br />

<br />

...<br />

<br />

JSON<br />

{"qucs":<br />

{"v":"0.0.17",<br />

"i":[<br />

{"n":"...","v":"..."},<br />

...<br />

],<br />

"d":[<br />

{"n":"...","v":"..."},<br />

...<br />

]<br />

}<br />

}<br />

Overhead<br />

allgemein<br />

<br />

<br />

...<br />

<br />

{"qucs":<br />

{"v":"0.0.17",<br />

...<br />

}<br />

}<br />

Overhead pro<br />

Abhängigkeit<br />

<br />

...<br />

<br />

"i":[...],<br />

{"n":"...","v":"..."} ( )<br />

, ( )<br />

Overhead pro<br />

Ergebnis<br />

<br />

...<br />

<br />

( ) ( ( ) ( ))<br />

"d":[...]<br />

{"n":"...","v":"..."} ( )<br />

, ( )<br />

( ) ( ( ) ( ))<br />

Summe ( ) ( )<br />

Anmerkung<br />

Tabelle 1 Vergleich des Overheads bei XML und JSON<br />

Betrachtet man die Differenz beider Werte (siehe Formel 2), wird deutlich, dass der<br />

Overhead des JSON-Formates nur günstiger ist, wenn ein Ergebnis und eine<br />

Seite 16


Änderungen an Qucs<br />

Abhängigkeit vorhanden sind. Steigt die Anzahl der Ergebnisse, bzw. Abhängigkeiten<br />

ist das XML-Format besser geeignet.<br />

( ) ( ) ( ( ) (<br />

( )))<br />

( ( ))<br />

Formel 2 Differenz des Overheads von XML und JSON<br />

In Anhang 2 sind die resultierenden Paketgrößen in Abhängigkeit von der Anzahl der<br />

Simulationsergebnisse, sowie deren Abhängigkeiten tabellarisch aufgelistet. Nach<br />

der Konvertierung der Werte in eine Zeichenkette haben diese eine maximale Länge<br />

von 39 Zeichen. Aus diesem Grund wurden hier zusätzlich 39 Byte in die<br />

Berechnung eingebunden. Aus den Tabellen ist zu erkennen, dass durch die<br />

Nutzung des XML-Formates mehr Daten in einem Ethernet-Paket übertragen werden<br />

können, als bei der Verwendung des JSON-Formates.<br />

3.1.5 Nachbilden zeitintensiver Simulationen<br />

Die Betrachtungen in Kapitel 3.1.4.2 bezogen sich speziell auf Simulationen deren<br />

Zwischenergebnisse schnell berechnet werden. Es gibt jedoch auch Simulationen die<br />

eine längere Zeit benötigen um ein Zwischenergebnis zu erzeugen. Für den späteren<br />

Entwicklungsverlauf empfiehlt es sich, dieses Verhalten in dem Simulator<br />

nachzuahmen. So kann die Visualisierung der Zwischenergebnisse auch mit<br />

einfachen Beispielen getestet werden.<br />

3.1.6 Die Netzwerkkommunikation<br />

Als Grundlage für die Modifizierungen an Qucs wird die Quelltextversion verwendet,<br />

die im Rahmen der Studienarbeit aus dem fünften Semester entstanden ist. In der<br />

Studienarbeit wurde erörtert, dass die Implementierung einer<br />

Netzwerkkommunikation entweder mit dem Einbinden einer entsprechenden<br />

Klassenbibliothek oder durch die Nutzung von betriebssystem-abhängigen<br />

Funktionen vorgenommen werden kann. Im Rahmen der Studienarbeit kam es bei<br />

der Verwendung von zusätzlichen Klassenbibliotheken zu komplexen Problemen. 7<br />

Aus diesem Grunde wurde die Plattformunabhängigkeit für die Fortsetzung der Arbeit<br />

aufgegeben, und sich dafür entschieden die betriebssystem-spezifischen Funktionen<br />

zu nutzen.<br />

Um die Ausgabe der Simulationsdaten über das Netzwerk testen zu können, wurde<br />

ebenfalls in der Studienarbeit ein TCP- sowie ein UDP-Server erstellt, der<br />

eingehende Daten in der Windows-Konsole ausgibt. Die Server wurden in JavaScript<br />

7 vgl. Studienarbeit MEYER, 2013<br />

Seite 17


Änderungen an Qucs<br />

geschrieben. Zum Ausführen wird Node.js benötigt. An den Servern wurden einige<br />

Veränderungen vorgenommen. So wird nun die Systemzeit zu der eine Nachricht<br />

erhalten wurde ebenfalls mit ausgegeben. Diese Server dienen ausschließlich zum<br />

Testen der Netzwerkausgabe von Qucs. Der Quelltext der Server ist in Anhang 3<br />

aufgeführt.<br />

3.2 Implementierung<br />

3.2.1 Der Programmeinstieg<br />

Wie aus Kapitel 3.1.2 hervorgeht, soll sowohl eine Übertragung über TCP, als auch<br />

über UDP möglich sein. Ebenso wurde in Kapitel 3.1.1 erörtert, dass für das<br />

Herstellen einer Verbindung, sowohl die IP-Adresse des Servers, sowie der Port auf<br />

den die Serveranwendung gebunden ist, bekannt sein müssen. Um diese<br />

Informationen an die Anwendung zu übertragen wurden die zwei neuen<br />

Starparameter -udp und -tcp hinzugefügt. Zusätzlich wurde ein Paramter<br />

hinzugefügt, über den die in Kapitel 3.1.5 angesprochene Verzögerungszeit in<br />

Millisekunden übergeben werden kann. Die Anwendung wartet nach einem<br />

Simulationsschritt die Zeit ab, bevor der nächste Simulationsschritt folgt. Listing 3<br />

zeigt die hinzugefügten Startparameter.<br />

Usage: qucsator.exe [OPTION]...<br />

-udp IP:PORT use UDP to send the simulation results to an UDP-Server<br />

-tcp IP:PORT use TCP to send the simulation results to a TCP-Server<br />

-d DELAY set delay in [ms] between simulation steps<br />

Listing 3 Startparameter für die Netzwerkkommunikation<br />

Der Programmeinstieg befindet sich in der Datei ucs.cpp. Hier befindet sich die<br />

Bearbeitung der bereits definierten Startparameter. Die Daten, zu der Zieladresse,<br />

sowie der Verzögerungszeit, werden hier in globalen Variablen der Netzwerkklasse<br />

hinterlegt. Dadurch ist im späteren Verlauf der Programmabarbeitung ein Zugriff auf<br />

diese möglich. Die am Quelltext getätigten Änderungen finden sich in Anhang 4.<br />

3.2.2 Die Netzwerkklasse<br />

Um die in Kapitel 3.1.6 angesprochenen Probleme zu verringern wurde eine eigene<br />

Klasse entwickelt, in der die Kommunikation zu dem Server umgesetzt wird. Wird<br />

später eine plattformunabhängige Klassenbibliothek eingebunden, muss somit nur<br />

die Netzwerkklasse angepasst werden. Alle Änderungen im Simulator die auf die<br />

Klasse zugreifen müssen nicht geändert werden.<br />

Im Anhang 5 befindet sich die Header-, sowie die Quelltext-Datei für die entwickelte<br />

Netzwerkklasse. Wie zu erkennen ist, wird im Rahmen dieser Arbeit die Windowsspezifische<br />

winsock2.h eingebunden, die Methoden zur Herstellung und Verwendung<br />

einer Netzwerkkommunikation bereitstellt. Zur Zuweisung des Protokolls wurde ein<br />

Enumerationstyp definiert. Dem Konstruktor muss die Ziel-Adresse, sowie das zu<br />

nutzende Protokoll übergeben werden. Zu der Ziel-Adresse gehören die IP-Adresse,<br />

Seite 18


Änderungen an Qucs<br />

sowie der Port des Ziels. Diese werden, wie unter Listing 3 beschrieben, als<br />

Startargument dem Programm als Zeichenkette übergeben und kann über die<br />

statische Funktion getAddr(...) in IP-Adresse und Port aufgeteilt werden. Die<br />

weiteren öffentlichen Methoden stellen die Verbindung zu einem Server her und<br />

ermöglichen das Senden von Daten. Eine Methode zum Empfangen wurde nicht<br />

implementiert, da keine Daten empfangen werden müssen. 8<br />

Zusätzlich wurden in der Header-Datei Deklarationen für externe, globale Variablen<br />

getroffen. Auf diese kann in jeder Quellcode-Datei zugegriffen werden, in welcher die<br />

network.h eingebunden wird. So können bereits zum Programmstart die Daten für<br />

die Zieladresse gespeichert werden.<br />

3.2.3 Einbinden der Netzwerkklasse<br />

3.2.3.1 Die Klassenhierarchie der Simulationssoftware<br />

Für die Übertragung der Simulationsergebnisse muss der aktuelle Simulationsschritt<br />

abgeschlossen sein. Bei der Analyse des Quellcodes von Qucs hat sich gezeigt,<br />

dass für jede Simulationsart eine eigene Klasse existiert. Damit nicht in jeder dieser<br />

Klassen das Senden der Daten implementiert werden muss, wurde zunächst die<br />

Klassenhierarchie von Qucs näher untersucht. Das Resultat ist in Abbildung 6 als<br />

vereinfachtes Klassendiagramm dargestellt.<br />

object<br />

analysis<br />

...<br />

nasolver parasweep ...<br />

dcsolver acsolver trsolver<br />

Abbildung 6 Die Klassenhierarchie der Simulatoren von Qucs<br />

Zu sehen ist, dass die Klassen für die Simulationen von der Klasse nasolver<br />

abgeleitet sind. Diese ist, ebenso wie die Klasse parasweep, von der Klasse analysis<br />

abgeleitet. Die Klasse parasweep wird für den Parameterdurchlauf benötigt. Die<br />

Berechnung der Simulationsergebnisse erfolgt jedoch ausschließlich in den von<br />

nasolver abgeleiteten Klassen. Es bietet sich daher an, die Netzwerkkommunikation<br />

in die nasolver–Klasse zu implementieren.<br />

8 Bei der Entwicklung der Netzwerkklasse wurde sich an die Anleitungen von c-worker.ch zum Thema<br />

„Winsock“ gehalten – vgl c-worker.ch<br />

Seite 19


Änderungen an Qucs<br />

Dazu muss in dieser die Netzwerkklasse eingebunden werden. Zusätzlich wird eine<br />

private Membervariable vom Typ network angelegt. Über diese soll die<br />

Kommunikation zum Server realisiert werden. In Anhang 6 sind die Änderungen<br />

aufgeführt, die an der Klasse vorgenommen wurden. In den Konstruktoren wird die<br />

Verbindung zu dem Server hergestellt. Bei den Variablen networkUse, networkAddr<br />

und networkProtocol handelt es sich um die externen globalen Variablen der<br />

Netzwerkklasse. Diese wurden bereits beim Programmstart in der Datei ucs.cpp<br />

gesetzt. Das Senden der Daten erfolgt in der Funktion saveResults(). An dieser<br />

Stelle sind alle Ergebnisse der Simulation berechnet worden. Vor dem Senden wird<br />

das XML-<strong>Dokument</strong> erzeugt. In der Variable netPrint sind an diesem Punkt des<br />

Programmablaufes alle XML-Konten für die Abhängigkeiten und Ergebnisse<br />

enthalten. Auf diese Variable wird in den folgenden beiden Kapiteln näher<br />

eingegangen.<br />

3.2.3.2 Erkennen der Simulationsergebnisse<br />

Bei der Analyse des Quelltextes von Qucs hat sich gezeigt, dass in der Datei<br />

analysis.cpp unter der Funktion saveVariable(...) die Simulationsergebnisse für<br />

die spätere Verwendung gespeichert werden. Um die Simulationsergebnisse für die<br />

Übertragung vorzubereiten wurde die Funktionen getDNode(...) implementiert.<br />

Diese erzeugt einen XML-Knoten und hängt diesen an die Zeichenkette netPrint an.<br />

Übergeben wird der Funktion der Name des Ergebnisses, sowie dessen Wert. Zu<br />

Beginn wird der Wert in eine Zeichenkette umgewandelt. Anschließend erfolgt die<br />

Erzeugung und Speicherung des Knotens. In C++ werden Zeichenketten als Felder<br />

vom Typ char behandelt. Für ein Feld wird nur ein begrenzter Bereich des<br />

Arbeitsspeichers reserviert. Werden der Zeichenkette neue Elemente angefügt, kann<br />

es zu einem Zugriff auf nicht reservierten Speicher kommen. Aus diesem Grund wird<br />

hier die existierende Zeichenkette in der Hilfvariable help gespeichert. Anschließend<br />

wird der Speicherbereich der ursprünglichen Zeichenkette netPrint freigegeben. Für<br />

diese wird dann ein neuer Speicherbereich allokiert, welcher der Länge der alten<br />

Zeichenkette plus der des neuen Knotens entspricht. Wurde der Speicher allokiert<br />

kann die neue Zeichenkette erstellt werden. Zum Schluss wird der Speicherbereich,<br />

den die Hilfsvariable belegt, wieder freigegeben und der XML-Knoten als<br />

Zeichenkette übergeben. Die netPrint-Variable wird nach jedem Simulationsschritt<br />

wieder zurückgesetzt.<br />

3.2.3.3 Erkennen der Abhängigkeiten<br />

Für die spätere Verarbeitung der Simulationsergebnisse, müssen deren<br />

Abhängigkeiten mit übertragen werden. Als Abhängigkeit ist beispielsweise für eine<br />

Transientsimulation die Zeit, oder für eine Wechselstromsimulation die Frequenz<br />

anzusehen. Werden in einer Simulation ein oder mehrere Parameterdurchläufe<br />

verwendet, entstehen zusätzliche Abhängigkeiten. Die Abhängigkeiten können dabei<br />

Seite 20


Änderungen an Qucs<br />

nicht in netPrint gespeichert werden. Obwohl für jeden Simulationsschritt ein neuer<br />

Wert für beispielsweise die Frequenz vorliegt, muss beachtet werden, dass ein<br />

übergeordneter Parameterdurchlauf existieren kann. Wird der aktuelle Wert des<br />

Parameters in die Variable netPrint geschrieben, wäre dieser nur im ersten<br />

Simulationsschritt bekannt. Es müssen daher die Werte der übergeordneten<br />

Simulationen gespeichert werden.<br />

Dazu wurde die solve()-Funktion der analysis-Klasse überladen. Dieser kann nun<br />

eine Zeichenkette übergeben werden, die in die zusätzliche Membervariable Indeps<br />

kopiert wird. Zusätzlich wurde eine Funktion getINode() implementiert. Dieser<br />

werden der Name, sowie der aktuelle Wert des aktuellen Simulationsschrittes<br />

übergeben. In der Funktion wird der XML-Knoten für die Abhängigkeit erzeugt. Die<br />

Funktion gibt anschließend die Zusammenführung der bereits existieren Indeps-<br />

Variable und des neuen XML-Knotens zurück. Der zuletzt hinzugefügt Knoten steht<br />

dabei an erster Stelle. Dies ist für die spätere Auswertung des XML-<strong>Dokument</strong>es von<br />

Bedeutung. Für jeden Simulationsschritt sind somit die übergeordneten Parameter,<br />

sowie der aktuelle Wert bekannt und kann vor der Berechnung der Ergebnisse in die<br />

netPrint-Variable geschrieben werden. Die Änderungen an der analysis-Klasse<br />

sowie ein Beispiel für die Verwendung der getINode()- und getDNode()-Funktion<br />

befinden sich in Anhang 7.<br />

Seite 21


Die QucsLiveEngine<br />

4 Die QucsLiveEngine<br />

4.1 Vorbetrachtungen<br />

4.1.1 Starten der Simulation<br />

Eine Aufgabe der QucsLiveEngine besteht darin, die Simulationsanwendung zu<br />

starten. Dazu muss dieser der Speicherort der qucsator.exe-Datei bekannt sein. Der<br />

Pfadverweis soll, wie bereits in der QucsEngine, über einen Eintrag in der Windows-<br />

Registrierdatenbank zur Verfügung gestellt werden. Wäre der Speicherort im<br />

Quellcode hinterlegt, müsste die Anwendung, bei einer Änderung des Speicherortes,<br />

neu erstellt werden. Ebenfalls können durch diese Methode Startparameter einfach<br />

bearbeitet werden. Der Befehl zum Starten der Simulation ist dabei wie folgt<br />

aufgebaut:<br />

[QucsDir]\bin\qucsator.exe –i -o –tcp 127.0.0.1:<br />

{-d x}<br />

Dabei steht QucsDir für das Installationsverzeichnis von Qucs. Die Zeichenketten<br />

und werden durch die Pfadangaben zu der Netzliste und der<br />

Ausgabedatei ersetzt. Über wird der Anwendung mitgeteilt, auf welchem Port<br />

der Server die Ergebnisse entgegennimmt. Zusätzlich kann über „–d x“ eine<br />

Verzögerung zwischen den einzelnen Simulationsschritten in x Millisekunden erzeugt<br />

werden.<br />

4.1.2 Empfangen der Simulationsdaten<br />

Um die Simulationsdaten von der Simulationsanwendung entgegennehmen zu<br />

können, muss zunächst ein Server implementiert werden. Qucs bietet die Möglichkeit<br />

die Daten sowohl über TCP als auch UDP zu übertragen. Für die Wahl des<br />

entsprechenden Protokolls muss nun betrachtet werden, wie die Simulationsdaten in<br />

der QucsLiveEngine weiter verarbeitet werden. Zunächst müssen die Daten in das<br />

framework-spezifische Format konvertiert und anschließend gespeichert werden. Es<br />

ist nun die Situation denkbar, dass die Konvertierung und Speicherung der Daten<br />

mehr Zeit in Anspruch nimmt, als die Berechnung des nächsten Simulationsschrittes.<br />

Bei der Verwendung von UDP würde dies bedeuten, dass die Daten schneller in den<br />

UDP-Puffer geschrieben werden, als sie daraus gelesen werden. Da durch UDP<br />

nicht sichergestellt wird, dass alle Daten korrekt beim Empfänger eingehen, kann es<br />

zu einem Überlauf des Puffers führen. TCP hingegen stellt sicher, dass alle<br />

gesendeten Daten beim Empfänger eingehen. Das Senden der Daten über TCP<br />

wurde in Qucs über einen blockierenden Algorithmus implementiert. Dies bedeutet,<br />

dass der nächste Simulationsschritt erst erfolgt, wenn für die zuletzt übertragenen<br />

Daten eine Empfangsbestätigung (Acknowledgement) eingegangen ist. Da Qucs auf<br />

die Bestätigung des Empfängers warten muss, kann es zu einer längeren<br />

Bearbeitungszeit der Simulation kommen.<br />

Seite 22


Die QucsLiveEngine<br />

Ein weiterer Aspekt, der für die Verwendung des TCP spricht, ist die Verarbeitung<br />

der Daten. Diese werden fortlaufend in einer Datei hinterlegt. Somit ist die korrekte<br />

Reihenfolge der empfangen Daten von hoher Bedeutung. TCP stellt im Gegensatz<br />

zu UDP sicher, dass die Daten in der Reihenfolge beim Empfänger eingehen, wie sie<br />

beim Sender versandt wurden. Somit fiel die Wahl auf TCP, weil dieses sicherstellt,<br />

dass alle Daten korrekt empfangen werden. Der daraus resultierend längeren<br />

Bearbeitungszeit ist eine wesentlich niedrigere Priorität zuzuordnen, und kann daher<br />

in Kauf genommen werden.<br />

Vom Programmablauf her ist zu beachten, dass der TCP-Server gestartet wurde und<br />

sich im Listen-Modus befindet, bevor die ersten Daten übertragen werden. Da es<br />

sich bei Programmen um eine sequentielle Abarbeitung der Befehle handelt muss<br />

dementsprechend zuerst der TCP-Server gestartet werden, bevor die<br />

QucsLiveEngine die Simulation startet. Betrachtet man die Funktionsweise des TCP-<br />

Servers kommt es hier jedoch zu einem Problem. Befindet sich der Server im Listen-<br />

Modus wird die Anwendung blockiert bis eine Nachricht eingegangen ist. Da Qucs<br />

jedoch erst gestartet werden kann, nachdem der TCP-Server sich in dem Listen-<br />

Modus befindet, entsteht hier ein Blockieren der gesamten QucsLiveEngine. Eine<br />

Lösung dieses Problems bietet die Verwendung von Threads. So wird zunächst der<br />

Server gestartet. Bevor dieser in den Listen-Modus gesetzt wird, wird ein neuer<br />

Thread erstellt. In diesem wird der Server in den Listen-Modus gesetzt und kann die<br />

von Qucs gesendeten Nachrichten empfangen und weiter verarbeiten Somit kann im<br />

ursprünglichen Thread die Funktion aufgerufen werden, die schließlich Qucs startet.<br />

Anschließend muss darauf gewartet werden, dass zum einen die Anwendung<br />

geschlossen wurde, und zum anderen der zusätzliche Thread beendet wurde. Es<br />

bietet sich hier an, diesen zu beenden, sobald die TCP-Verbindung vom Client<br />

unterbrochen wurde. Anschließend kann die QucsLiveEngine wie gewohnt<br />

ausgeführt werden.<br />

4.1.3 Auswerten des XML-<strong>Dokument</strong>es<br />

Aus Kapitel 3.1.4.2 geht hervor, dass die Daten in einem XML-<strong>Dokument</strong> übertragen<br />

werden. Um die Simulationsergebnisse aus dem XML-<strong>Dokument</strong> zu erhalten wird ein<br />

XML-Parser benötigt. Für die Programmiersprache C++ existieren bereits viele XML-<br />

Parser, die sich in ihrem Umfang und der Funktionalität unterscheiden. Der Zugriff<br />

auf das <strong>Dokument</strong> erfolgt dabei üblicherweise über die DOM-API (Document Object<br />

Model – Application Programming Interface) oder die SAX-API (Simple API for XML).<br />

Bei der DOM-API wird das XML-<strong>Dokument</strong> als Baumstruktur abgebildet. Dadurch ist<br />

ein sowohl lesender, als auch schreibender Zugriff auf alle Elemente des<br />

<strong>Dokument</strong>es möglich. Dies hat jedoch den Nachteil, dass große XML-<strong>Dokument</strong>e viel<br />

Seite 23


Die QucsLiveEngine<br />

Arbeitsspeicher beanspruchen. 9 Bei der SAX-API hingegen wird das XML-<strong>Dokument</strong><br />

als Datenstrom abgebildet. Treten beim Lesen des Streams vordefinierte Ereignisse<br />

auf, werden den Ereignissen zugewiesene Funktionen aufgerufen. Der Vorteil liegt<br />

im niedrigen Speicherverbrauch, jedoch ist der Programmieraufwand deutlich größer<br />

gegenüber der DOM-API. 10 Da es sich bei den übertragenen Simulationsergebnissen<br />

um kleine XML-<strong>Dokument</strong>e handelt, wurde sich dazu entschieden, einen XML-Parser<br />

zu verwenden, der die DOM-API nutzt.<br />

Die XML-Parser unterscheiden sich zusätzlich im Umfang ihrer Funktionalität. So<br />

werden beispielsweise Namensräume oder <strong>Dokument</strong>typdefinitionen nur von einigen<br />

XML-Parsern unterstützt. Da diese Besonderheiten in dem entworfenen Datenformat<br />

jedoch nicht vorkommen, wurde sich für die Verwendung des XML-Parsers TinyXML-<br />

2 entschieden. Ebenfalls steht die Bibliothek unter der Zlib-Lizenz, was die<br />

Verwendung im kommerziellen Bereich ermöglicht. 11 Ein weiterer Vorteil besteht<br />

darin, dass die Bibliothek keine betriebssystemspezifischen Funktionen verwendet<br />

und somit plattformübergreifend verwendet werden kann.<br />

4.1.4 Konvertieren der Daten<br />

Da die Transim Technology Corportaion ein eigenes Format zur Speicherung der<br />

Simulationsdaten verwendet, müssen die Ergebnisse der Simulation in dieses<br />

Format konvertiert werden. Bisher erfolgte dies nachdem die Simulation beendet<br />

wurde und die Ausgabedatei erzeugt wurde. Um die Zwischenergebnisse dem<br />

EngineFramework zur Verfügung zu stellen, müssen jedoch diese bereits beim<br />

Eintreffen konvertiert werden und gespeichert werden. Ebenfalls müssen die Daten<br />

dem Schematic zur Verfügung gestellt werden. In den folgenden Kapiteln wird näher<br />

auf das Speichern der Daten eingegangen. Das Bereitstellen für das Schematic wird<br />

in einem späteren Kapitel näher erläutert.<br />

Aus Kapitel 3.1.4.2 geht hervor, dass für jeden Simulationsschritt ein XML-<strong>Dokument</strong><br />

übertragen wird. In diesem befinden sich neben den Ergebnissen (-Tags) auch<br />

die zugehörigen Abhängigkeiten (-Tags). Eine Besonderheit tritt ein, wenn zu den<br />

Ergebnissen mehrere Abhängigkeiten vorliegen. Darauf wird jedoch in einem<br />

späteren Kapitel näher eingegangen. Für das Konvertieren der Daten ist hier zu<br />

beachten, dass die erste übergebene Abhängigkeit den X-Wert des aktuellen<br />

Simulationsschrittes enthält. In den Ergebnis-Tags befinden sich die entsprechenden<br />

Y-Werte. Die Werte liegen zunächst als Zeichenkette vor und müssen in eine<br />

Fließkommazahl (float) umgewandelt werden. Als Attribut befindet sich in den<br />

Ergebnis-Tags der Name des entsprechenden Kurvenverlaufs (engl.: Waveform). Für<br />

9 vgl. online “Document Object Model” auf Wikipedia.org (09.08.2013)<br />

10 vgl. online “Simple API for XML” auf Wikipeida.org (09.08.2013)<br />

11 vgl online “The zlib/libpng License (Zlib)” auf Opensource.org<br />

Seite 24


Die QucsLiveEngine<br />

jeden Kurvenverlauf wird eine Datei angelegt in die die Ergebnisse geschrieben<br />

werden.<br />

4.1.5 Erzeugen der Waveform Dateien<br />

Das Speichern der Simulationsergebnisse in Dateien ist notwendig, um später auf<br />

diese zugreifen zu können. Diese Dateien werden in einem Unterordner gespeichert<br />

und müssen nach dem folgenden Schema gespeichert werden:<br />

/[UserID]/Waveform/[SimID]_[JobHandle]_group0_c[i]<br />

Die UserID stellt die Identifikationsnummer des Benutzers dar. Diese wird aus der<br />

Webanwendung übergeben. Bei der SimID handelt es sich um eine Kennzeichnung<br />

der Simulation. Diese wird beim Starten der Simulation der QucsLiveEngine<br />

übergeben. Auf diese soll hier nicht weiter eingegangen werden. Das JobHandle ist<br />

die bereits in Kapitel 2.2.2 angesprochene Identifikationsnummer der Simulation. Das<br />

i stellt eine durchlaufende Ganzzahl dar. Diese dient der Identifikation der<br />

unterschiedlichen Kurvenverläufe. Wie die Zuweisung dieser Nummer zu den<br />

Kurvenverläufen erfolgt wird im nächsten Kapitel näher erläutert. Zu beachten ist,<br />

dass die Dateien über keine Dateinamenserweiterung verfügt.<br />

In den Waveform-Dateien werden die Werte byteweise hintereinander gespeichert.<br />

Dazu müssen diese Werte zunächst von einer Fließkommazahl in ein vier Byte<br />

großes Array des Types Byte umgewandelt werden. Bei dem Speichern ist darauf zu<br />

achten, ob es sich bei dem Y-Wert um eine reelle oder komplexe Zahl handelt. Bei<br />

einer komplexen Zahl erfolgt eine Trennung zwischen Real- und Imaginärteil. Beide<br />

Anteile werden anschließend in der Datei gespeichert. Abbildung 7 zeigt dabei die<br />

Reihenfolge in der die Zahlen zu speichern sind.<br />

Start<br />

Schreibe(XValue->Real())<br />

YValue->Complex()<br />

ja<br />

Schreibe(XValue->Real())<br />

nein<br />

Schreibe(YValue->Real())<br />

YValue->Complex()<br />

ja<br />

Schreibe(YValue->Real())<br />

nein<br />

Ende<br />

Abbildung 7 Speichern der Ergebnisse in der Waveform-Datei<br />

Seite 25


Die QucsLiveEngine<br />

Ist der Y-Wert komplex, muss ebenfalls für den X-Wert ein komplexer Wert<br />

gespeichert werden. Handelt es sich um eine reale Zahl ist der Imaginärteil als „0.0“<br />

zu speichern.<br />

4.1.6 Die Manifest Datei<br />

Um eine Zuordnung der Waveform-Dateien zu den ursprünglichen Knotenpunkten zu<br />

erhalten muss eine sogenannte Manifest-Datei angelegt werden. Dabei ist der<br />

Dateiname wie folgt anzulegen:<br />

/[UserID]/Waveform/[SimID]_[JobHandle]_group0.manifest<br />

In diese Datei werden die Zuordnungen zeilen weise eingetragen. Eine Zuordnung<br />

muss nach dem folgenden Format angelegt werden:<br />

[Name],[Dateiname],[X-Typ],[Y-Typ],1,[Komplex]<br />

Der Name steht dabei für den Namen des Kurvenverlaufes. Dieser kann<br />

beispielsweise eine Ausgangsspannung, ein Eingangsstrom, oder ein beliebiger in<br />

der Schaltung angegebener Abtastpunkt (engl.: Probe) sein. Der Dateiname steht für<br />

die erzeugte Waveform-Datei und muss dem in Kapitel 4.1.5 erläuterten Format<br />

entsprechen. Anschließend folgen die Bezeichnungen für den physikalischen Typ der<br />

X- und Y-Achse. Eine Auswahl der möglichen Bezeichnungen findet sich in Tabelle<br />

2. Die vollständige Liste der Bezeichnungen befindet sich in Anhang 8.<br />

# Bezeichnungen<br />

3 amp current<br />

4 second time<br />

… …<br />

Tabelle 2 Format der Achsbezeichung in der Manifest-Datei<br />

Dabei kann in der Manifest-Datei entweder die Bezeichnung oder die zugehörige<br />

Zahl aufgeführt werden. Um einen Typ zuordnen zu können, muss eine Angabe zu<br />

diesem bereits von dem Simulator übertragen werden. Zuletzt muss angegeben<br />

werden, ob es sich bei den gespeicherten Werten um reelle, oder komplexe Zahlen<br />

handelt. Sind die Zahlen reell, wird der Wert 1 eingetragen, sind sie komplex, der<br />

Wert 2.<br />

4.1.7 Speichern von Kurvenscharen<br />

Eine gesonderte Rolle spielen Simulationen, bei denen die Ergebnisse mehrere<br />

Abhängigkeiten besitzen. Dieser Fall tritt ein, wenn für gewisse Bauteile ein<br />

Parameterdurchlauf ausgeführt wird. Das bedeutet, dass einem Bauteil ein<br />

Parameter zugewiesen wird. Dieser ist mit einem Start- und Endwert, sowie der<br />

Anzahl an Schritten definiert. Die Simulation muss für jeden dieser Parameter<br />

ausgeführt werden. Für die Ergebnisse bedeutet dies, das nicht ein Kurvenverlauf<br />

entsteht, sondern eine Kurvenschar. In diesem Fall muss jede Kurve in einer<br />

separaten Waveform-Datei gespeichert werden. Die Benennung dieser Datei erfolgt<br />

Seite 26


Die QucsLiveEngine<br />

zunächst wie in 4.1.5 angegeben, jedoch wird dem Dateinamen zusätzlich folgende<br />

Zeichenkette angehängt:<br />

/[UserID]/Waveform/[SimID]_[JobHandle]_group0_c[i]_div[j]<br />

Der Parameter j ist dabei ein fortlaufender Zähler der für die Kurve in der<br />

Kurvenschar steht. Die Manifest-Datei muss ebenfalls angepasst werden, um eine<br />

Zuordnung der einzelnen Kurven zu den Parametern zu ermöglichen. Das Format<br />

der Daten bleibt wie in 4.1.6 bestehen, jedoch muss in der ersten Zeile ein Hinweis<br />

angegeben werden, dass für einen Knotenpunkt mehrere Kurven vorliegen. Dieser<br />

Hinweis muss nach dem folgenden Format aufgebaut sein:<br />

# Division Labels (comma separated) : [Name Div1],[Name Div2], ...<br />

Die Namen für die unterteilten Kurven kann dabei beliebig erfolgen, es bietet sich<br />

jedoch an, einen Verweis auf die zugehörige Abhängigkeit anzugeben. Die Anzahl<br />

der Namen muss mit der Anzahl der einzelnen Kurven (s.o. j) übereinstimmen. Da<br />

die Namen durch Kommata getrennt werden, dürfen die Namen selbst keine<br />

Kommata enthalten. Neben der Manifest-Datei muss ebenfalls eine Division-Datei<br />

angelegt werden. Dieser ähnelt der Manifest-Datei. In der ersten Zeile befindet sich<br />

die Angabe zu den Kurvenscharen. Diese kann aus der Manifest-Datei übernommen<br />

werden. Die Angabe zu den Waveform-Dateien erfolgt anschließend wie folgt:<br />

[Name],[Dateiname],0<br />

Zur besseren Verdeutlichung befindet sich in Anhang 9 ein umfangreiches Beispiel.<br />

Hier handelt es sich um eine Transientsimulation des Tiefpass-Filters. Neben dieser<br />

Simulation wurde zusätzlich ein Parameterdurchlauf mit vier Schritten für den<br />

Wiederstand hinzugefügt. In der Ausgabe sind neben der Eingangsspannung die vier<br />

Ausgangsspannungen zu erkennen. In dem übertragenen Zwischenergebnis finden<br />

sich neben den Ergebnissen Ue.It, Ue.Vt und Ua.Vt auch deren Abhängigkeiten<br />

wieder. Dabei steht time.s für den aktuellen Zeitwert der Transientsimulation und R1<br />

für den aktuellen Widerstandswert des Parameterdurchlaufs. In den erzeugten<br />

Waveform-Dateien sind die drei Ergebnisse mit jeweils vier unterschiedlichen Kurven<br />

zu erkennen. Schließlich findet sich die Zuordnung der Waveform-Dateien in der<br />

Manifest-Datei wieder.<br />

4.2 Implementierung<br />

4.2.1 Starten des TCP-Servers und der Simulationsanwendung<br />

Für die Implementierung der Netzwerkkommunikation wird in der QucsLiveEngine<br />

die winsock2-Bibiliothek verwendet. Zum Starten eines TCP-Servers wird ein SOCKET-<br />

Objekt benötigt. Diesem wird ebenfalls als Adressfamilie „Internetadresse“<br />

zugewiesen. Als IP-Adresse wird für einen Server die Konstante INADDR_ANY<br />

zugewiesen. Anschließend muss der Server an einen Port gebunden werden. Ist<br />

Seite 27


Die QucsLiveEngine<br />

dieser Port bereits an eine andere Anwendung gebunden, wird versucht sich an den<br />

nächsten Port zu binden. Listing 4 zeigt die Implementierung zur Auswahl des Ports.<br />

// Get the minimum and maximum value for the Port<br />

int minPort, maxPort;<br />

if(!GetPort(&minPort, &maxPort))<br />

return false;<br />

// Find Free Port to bind on:<br />

int port = minPort-1;<br />

do {<br />

port++;<br />

local.sin_port=htons((u_short)port);<br />

} while(port maxPort)<br />

return false;<br />

Listing 4 Auswahl des Ports für den TCP-Server<br />

Die Funktion GetPort() liest aus der Registrierungsdatenbank einen Bereich in dem<br />

der Port liegen darf. Anschließend wird versucht den Server an den Port zu binden.<br />

Schlägt diese Funktion fehl, wird der Port um eins inkrementiert und es wird erneut<br />

versucht. Überschreitet die port-Variable den maximal zulässigen Wert, wird die<br />

Simulation abgebrochen. War die Bindung an einen Port erfolgreich, kann der Server<br />

gestartet werden. Wie bereits in Kapitel 4.1.2 erläutert, muss dieser in einem<br />

zusätzlichen Thread erzeugt werden. Für eine bessere Übersicht wurde dies in einer<br />

zusätzlichen Klasse implementiert. Zum Starten des neuen Threads wird die<br />

Funktion AfxBeginThread() verwendet. Diese wird durch das .NET-Framework<br />

bereitgestellt. Als Ergebnis liefert diese einen Zeiger auf ein Objekt vom Typ<br />

CWinThread. Ist der Thread gestartet, wird der Server in den Listen-Modus gesetzt<br />

und wartet auf eine eingehende Verbindung.<br />

Nach dem Start des neuen Threads wird in dem ursprünglichen Thread die<br />

Anwendung Qucs gestartet. Dies erfolgt wie bisher, jedoch wird ihr als zusätzlichen<br />

Startparameter die Adresse, sowie der Port des TCP-Servers übergeben. Da die<br />

Anwendung und der TCP-Server auf der gleichen Maschine ausgeführt werden, ist<br />

die IP-Adresse die 127.0.0.1. Als Port wird der ermittelte Port übergeben.<br />

4.2.2 Empfangen der Daten<br />

Wurde die Verbindung zwischen Client und Server hergestellt, können die Daten<br />

übertragen werden. Um Daten entgegen zu nehmen, muss im Server die Funktion<br />

recv(...) aufgerufen werden. Diese Funktion blockiert den Programmablauf, bis<br />

Daten eingehen. Als Parameter müssen ihr, die Client-ID, ein Zeiger auf einen<br />

Puffer, die Größe des Puffers übergeben werden. Die Größe des Puffers wurde hier<br />

anhand der Eigenschaften von TCP bestimmt. Diese sind die dem Request for<br />

Comments (RFC) 793 aufgeführt. 12 Einen Teil des TCP-Headers stellt das Window-<br />

12 vgl. RFC 793<br />

Seite 28


Die QucsLiveEngine<br />

Feld dar. Dieses Feld gibt an, wie viele Daten vom Server empfangen werden<br />

können, bevor dieser eine Bestätigung (Acknowledgement) an den Client senden<br />

muss. Bei einem kleinen Wert muss demzufolge der Client oft auf die Bestätigung<br />

des Servers warten. Somit würde das gesamte System verlangsamt. Wird ein hoher<br />

Wert gewählt, müssen weniger Bestätigungen geschickt werden, und eine schnellere<br />

Übertragung ist gewährleistet. Der Nachteil eines großen Wertes tritt ein, wenn es zu<br />

einer fehlerhaften Übertragung kommt. In diesem Fall müssen alle Daten, die nicht<br />

bestätigt wurden, erneut gesendet werden. Bei störanfälligen Netzwerken würde sich<br />

ein großer Wert negativ auf die Geschwindigkeit auswirken. Da sich der Client und<br />

der Server hier jedoch auf der gleichen Maschine befinden können fehlerhafte<br />

Übertragungen nahezu ausgeschlossen werden. Somit kann ein hoher Wert gewählt<br />

werden. Im TCP-Header sind 16 Bit für den Window-Wert vorgesehen. Dies<br />

entspricht einer maximalen Größe von 65.535 Byte, was ebenfalls als Größe für den<br />

Puffer gewählt wird.<br />

Die recv()-Funktion liefert als Rückgabewert die Anzahl der empfangen Daten. Wird<br />

die Verbindung zum Client beendet liefert sie den Wert 0. Werden mehr Daten<br />

gesendet als in den übergeben Puffer geschrieben werden, muss die recv()-<br />

Funktion erneut aufgerufen werden um die nächsten Daten zu empfangen. Daraus<br />

resultierend wurde eine Schleife entwickelt, die abgebrochen wird, wenn die<br />

Verbindung beendet wurde.<br />

4.2.3 Auslesen des TCP-Stroms<br />

Zum Übertragen der Simulationsergebnisse werden diese in einem XML-<strong>Dokument</strong><br />

gespeichert. Wurden die Daten auf dem Server empfangen befinden sie sich in dem<br />

bereits erwähnten Puffer. Bei schnellen Simulationen ist es aufgrund des großgewählten<br />

Puffers möglich, dass mehrere Datensätze in den Puffer geschrieben<br />

werden. Mithilfe der TinyXML-2-Bibliothek kann eine Zeichenkette zu einem XML-<br />

Objekt geparst werden. Dazu darf in der Zeichenkette jedoch nur ein XML-<strong>Dokument</strong><br />

aufgeführt sein. Aus diesem Grund müssen die Datensätze aus dem Puffer zunächst<br />

getrennt werden. Jedes XML-<strong>Dokument</strong> beginnt mit der XML-Deklaration und<br />

beginnt demzufolge mit der Zeichenkette „


Die QucsLiveEngine<br />

// position-variable required to split the received string.<br />

int pos1=0, pos2 = 0, size;<br />

char* pch;<br />

pch = strchr(data, '?');<br />

while (pch != NULL) {<br />

if(*(pch-1)=='


Die QucsLiveEngine<br />

Dabei muss eine Zuordnung der Ergebnisse zu den Dateien erfolgen. In 4.1.5 wurde<br />

erläutert, dass sich die Dateien in dem c-Wert unterscheiden. Es wurde eine Klasse<br />

zur Verwaltung der Ergebnisnamen und den c-Werten entwickelt. Sie bietet eine<br />

Funktion, der der Name des Ergebnisses und eine Referenz auf eine Zeichenkette<br />

übergeben werden. In der Zeichenkette wird der zum Namen zugehörige c-Wert<br />

zurückgegeben. Die Funktion überprüft, ob bereits eine Zuordnung vorhanden ist.<br />

Falls nicht wird eine neue hinzugefügt. Dabei werden diese fortlaufend<br />

durchnummeriert. Wurde ein neuer Eintrag hinzugefügt, wird von der Funktion ein<br />

true zurückgegeben. War die Zuordnung bereits vorhanden ein false. Dieser<br />

Rückgabewert wird gespeichert und gibt später an, ob eine neue Manifest-Datei<br />

erzeugt werden muss. Ist der c-Wert bekannt, kann das Ergebnis in der Waveform-<br />

Datei gespeichert werden. Dies erfolgt wie in der bisherigen QucsEngine.<br />

Wie in 4.1.7 erläutert, muss eine Unterscheidung bei Kurvenscharen erfolgen. Diese<br />

treten auf, wenn in dem vector mehr als ein Eintrag vorhanden ist. In diesem Fall<br />

erfolgt eine Zuordnung der zusätzlichen Abhängigkeiten zu dem entsprechenden<br />

div-Wert. Diese Zuordnung erfolgt ähnlich wie die Zuordnung der Ergebnisse zu den<br />

c-Werten der Waveform-Dateien. Wird eine neue Zuordnung angelegt, muss<br />

ebenfalls eine neue Manifest-Datei, sowie eine Division-Datei erzeugt werden.<br />

Wurden alle Ergebnisse in die Waveform-Dateien geschrieben muss geprüft werden,<br />

ob eine neue Manifest- und Divison-Datei erstellt werden müssen. Sind div-<br />

Zuordnungen vorhanden muss die erste Zeile der Manifest- und Division-Datei<br />

erzeugt werden. Anschließend werden die c-Zuordnungen durchlaufen und die<br />

entsprechenden Einträge in der Manifest- und Division-Datei gesetzt. Das neue<br />

Erstellen der Dateien ist notwendig, damit WebScope Zugriff auf die neu erstellen<br />

Waveform-Dateien erhält.<br />

Seite 31


Der EngineServer<br />

5 Der EngineServer<br />

5.1 Vorbetrachtungen<br />

5.1.1 Das Postprocessing<br />

Um die gespeicherten Simulationsdaten weiter verwenden zu können muss eine<br />

Nachbearbeitung (engl.: Postprocessing) stattfinden. Das Postprocessing wird dabei<br />

stets auf dem EngineServer ausgeführt. Es sorgt dabei für die Umwandlung der<br />

Simulationsergebnisse in eine verständliche Darstellung. Dazu zählt unter anderem<br />

die Darstellung der Kurvenverläufe in unterschiedlichen Diagrammen. Neben den<br />

Möglichkeiten Bild-Dateien zu erzeugen ist das Postprocessing ebenfalls dafür<br />

zuständig die Daten für die spätere Ausgabe in dem Webbrowser vorzubereiten.<br />

Dem Postprocessing können dazu unterschiedliche Befehle, sogenannte<br />

PostProcessingCommands, übergeben werden.<br />

Für die Darstellung von Kurvenverläufen bedeutet dies zunächst, dass ein<br />

entsprechendes Diagramm erzeugt wird. Es können unterschiedliche Arten von<br />

Diagrammen erzeugt werden, jedoch soll in dieser Arbeit lediglich auf das Erstellen<br />

von kartesischen Diagrammen eingegangen werden. Diese bestehen aus einer X-<br />

Achse und einer oder mehreren Y-Achsen. Der X-Achse wird der sich ändernde Wert<br />

zugewiesen. Für eine Transientsimulation wäre die beispielsweise die Zeit, bei einer<br />

Wechselstrom-Simulation die Frequenz. Auf der/den Y-Achse/n werden die<br />

Ergebnisse zugeordnet. Um solch eine Beschreibung, die später von WebScope<br />

interpretiert werden kann, zu erzeugen wird das bereits existierende Unterprogramm<br />

„FlashGen“ verwendet. Dieses Unterprogramm entstand, als die<br />

Simulationsergebnisse in dem Browser noch über den Adobe Flash Player<br />

ausgegeben wurde. Dafür musste der entsprechende Flash Player auf dem PC des<br />

Nutzers installiert sein. Um eine Unabhängigkeit von zusätzlicher Software zu<br />

erreichen hat die Transim Technology die Ausgabe der Daten über HTML und<br />

JavaScript realisiert. Dies wird von jedem Browser unterstützt und benötigt keine<br />

Installation von zusätzlicher Software. Der Name FlashGen stellt daher lediglich ein<br />

historisches Zeichen dar. Die Aufgabe von FlashGen besteht darin, die Beschreibung<br />

von allen benötigten Diagrammen zu erzeugen. Dazu müssen diesem mehrere<br />

Befehle, die sogenannten PostProcessingCommands, übergeben werden. In Listing<br />

6 ist ein Beispiel für diese Kommandos aufgeführt.<br />

oFlashGen = CreateInstance("FlashGen")<br />

oFlashGen.AddGroup(Name="Voltage", XMax="5E-4")<br />

oFlashGen.AddWaveform(Name="Ue.Vt", Group="Voltage", DisplayName="Ua.Vt",<br />

Color="#FF0000", Enabled="true", YType="Voltage",<br />

YMin="-1.1", YMax="1.1")<br />

oFlashGen.AddWaveform(Name="Ua.Vt", Group="Voltage", ...)<br />

oFlashGen.CreateFlashManifest()<br />

Listing 6 Beispiel für die PostProcessingCommands<br />

Seite 32


Der EngineServer<br />

Es muss zuerst eine Instanz von FlashGen erzeugt werden. Danach folgen die<br />

Definitionen für die Ergebnisgruppen. Hier wird eine Gruppe für die Ausgabe der<br />

Spannungen erstellt. Bisher wurden die Grenzen für die X-Achse aus den<br />

vorhandenen Waveform-Dateien ermittelt. Diese werden jetzt zur Laufzeit mit neuen<br />

Werten gefüllt. Wird ein neuer Wert in die Dateien eingetragen müsste FlashGen neu<br />

gestartet werden, um die neuen Grenzen zu ermitteln. Um dies zu verhindern wurde<br />

der Funktion AddGroup() ein neuer Parameter hinzugefügt um den Maximalwert für<br />

die X-Achse setzen zu können. 14 Nach Erstellen der Gruppen können diesen mit der<br />

Funktion AddWaveForm() Kurvenverläufe hinzugefügt werden. Der Name muss dabei<br />

dem Namen in der Manifest-Datei entsprechen. Neben dem Namen kann der Kurve<br />

ein Anzeigename, eine Farbe, sowie weitere Eigenschaften zugewiesen werden.<br />

Über den Befehl CreateFlashManifest() wird schließlich von FlashGen eine XML-<br />

Datei erzeugt, in der sich die Beschreibung aller benötigten Dateien befindet. Diese<br />

Datei wird unter dem folgenden Namen gespeichert:<br />

[SimID]_[JobHandle]_JS_VIEWER.xml<br />

In Listing 7 ist ein Beispiel für eine solche Datei aufgeführt. Zur besseren Übersicht<br />

wurden hier einige Attribute aus den Knoten entfernt.<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

Listing 7 Von FlashGen erzeugte Datei zur Berschreibung der Diagramme<br />

In dem XML-<strong>Dokument</strong> befindet sich der Wurzelknoten . Dieser kann<br />

mehrere -Knoten beinhalten. Diese Knoten stehen jeweils für ein<br />

Diagramm. Innerhalb dieser Knoten befindet sich in den -Knoten die<br />

Beschreibung der jeweiligen Achsen. Zu erkennen sind die Attribute die die<br />

Maximum- und Minimumwerte der Achsen angeben. Es können ebenfalls mehrere<br />

Definitionen für Y-Achsen vorhanden sein. Dies wird benötigt, wenn mehrere Kurven<br />

unterschiedlichen Typs, oder mit unterschiedlicher Achseinteilung in einem<br />

14 Anm.: Die Änderungen an FlashGen wurden von einem Mitarbeiter der Transim Technology<br />

durchgeführt.<br />

Seite 33


Der EngineServer<br />

Diagramm dargestellt werden sollen. In den -Knoten befindet sich die<br />

Beschreibung der Kurvenverläufe. Als Attribut dieses Knotens werden der Name, die<br />

zu verwendende Farbe, sowie die Information ob die Kurve angezeigt werden soll<br />

angegeben. In dem Attribut locator befindet sich ein Uniform Resource Locator (URL)<br />

der auf die zugehörige Waveform-Datei verweist. In den -Knoten befinden<br />

sich schließlich die -Knoten, in denen die kurvenspezifischen Achsen definiert<br />

sind. Über die Attribute sind hier ebenfalls der Name sowie die Maximum- und<br />

Minimumwerte angegeben. Die Zuordnung zu der Achsengruppe erfolgt über das<br />

Attribut type.<br />

Im bisherigen Verlauf des Simulationsvorganges wurde das Postprocessing<br />

gestartet, nachdem die Simulationsanwendung beendet wurde und alle Waveform-<br />

Dateien, sowie die Manifest-Datei erzeugt wurden. Für den Start von WebScope wird<br />

jedoch die von FlashGen erzeugte XML-Datei benötigt. Diese muss demzufolge<br />

ebenfalls zur Laufzeit der Simulation erstellt werden. Die Minimum- und Maximum-<br />

Werte können FlashGen über die PostprocessingCommands übermittelt werden. Es<br />

muss daher nicht auf alle Simulationsergebnisse gewartet werden. Das Erzeugen der<br />

XML-Datei setzt die Manifest-Datei voraus. Kommt es zu einer Änderung an der<br />

Manifest-Datei, beispielsweise bei einer Simulation mit einem Parameterdurchlauf,<br />

muss eine neue XML-Datei erzeugt werden. Zusammenfassend bedeutet dies, dass<br />

FlashGen nach dem Erzeugen, sowie nach jeder Änderung der Manifest-Datei<br />

ausgeführt werden muss.<br />

5.1.2 Bereitstellen der Simulationsdaten<br />

Ist die Simulation abgeschlossen, kann WebScope auf die Ergebnisse zugreifen.<br />

Wird in WebScope in die erzeugten Diagramme herein- oder aus ihnen<br />

herausgezoomt, werden die Simulationsdaten erneut vom EngineServer abgefragt.<br />

Somit wäre ein erneuter Zugriff auf die Waveform-Dateien nötig. Es müssten die<br />

Werte ebenso aus der Byte-Darstellung in ein Ganzzahlen-Objekt konvertiert<br />

werden. Aus diesem Grund werden die Simulationsergebnisse nur bei der ersten<br />

Anfrage ausgelesen und konvertiert. Anschließend werden die Ergebnisse im<br />

Arbeitsspeicher in einem Zwischenspeicher, folgend Map genannt, hinterlegt. Es wird<br />

dadurch ein Geschwindigkeitsvorteil erreicht, da zum Einen die Ergebnisse nicht<br />

erneut konvertiert werden müssen, und zum Anderen die Zugriffzeiten auf den<br />

Arbeitsspeicher, im Vergleich zu Festplatten, deutlich niedriger sind.<br />

Für die Darstellung der Simulationsergebnisse zur Laufzeit ergibt sich hier das<br />

Problem, dass neue Ergebnisse nicht angezeigt werden, da diese zwar in die<br />

Waveform-Dateien geschrieben werden, der EngineServer für die Darstellung jedoch<br />

die Werte aus dem Arbeitsspeicher verwendet. Die Ergebnisse müssen demzufolge<br />

sowohl in den Waveform-Dateien auf der Festplatte, als auch in der Map im<br />

Arbeitsspeicher gespeichert werden.<br />

Seite 34


Der EngineServer<br />

5.2 Implementierung<br />

5.2.1 Anpassen der QucsLiveEngine<br />

In Kapitel 4 wurde bereits auf die Entwicklung der QucsLiveEngine eingegangen. In<br />

dieser werden die Simulationsdaten von der Anwendung Qucs entgegen genommen<br />

und in die Waveform-Dateien geschrieben. Nach einem Zugriff auf die<br />

Simulationsdaten über den EngineServer müssen die Simulationsergebnisse<br />

ebenfalls in den, in Kapitel 5.1.2 beschriebenen, Zwischenspeicher geschrieben<br />

werden. Dazu ist eine Kommunikation zwischen EngineServer und der<br />

QucsLiveEngine erforderlich. In der bereits bestehenden generischen Klasse, von<br />

der alle Engines erben, sind bereits Rückruffunktionen definiert, die der Engine eine<br />

Kommunikation zum EngineServer ermöglicht. Um die Simulationsdaten zur<br />

Verfügung zu stellen, wird hier eine zusätzliche Rückruffunktion definiert. In Listing 8<br />

sind die Änderungen an der generischen Klasse aufgeführt.<br />

/* ALRESULT - AddLiveResult */<br />

PROC_ALRESULT_CALLBACK g_pfnALResultCallBack = NULL;<br />

/* ... */<br />

extern "C" __declspec( dllexport )<br />

void SetALResultCallBack(PROC_ALRESULT_CALLBACK pfnCallBack ) {<br />

g_pfnALResultCallBack = pfnCallBack;<br />

}<br />

/* ... */<br />

bool CGenericEngine::CallALResultCallback(const char* WFFilename,<br />

float XData, float YData, float XDataImag,<br />

float YDataImag, bool IsComplex) {<br />

if ( g_pfnALResultCallBack == NULL)<br />

return false;<br />

g_pfnALResultCallBack(WFFilename, XData, YData, XDataImag, YDataImag,<br />

IsComplex);<br />

return true;<br />

}<br />

Listing 8 Änderungen an der generischen Klasse<br />

Die Funktion SetALResultCallBack() wird als extern definiert, damit der<br />

EngineServer diese nutzen kann. Über diese Funktion registriert sich der<br />

EngineServer für den Rückruf. Damit der EngineServer die Daten verarbeiten kann,<br />

muss hier ein Zeiger auf eine Funktion übergeben werden. Die Funktion<br />

CallALResultCallback() wird von der QucsLiveEngine benötigt. Wurde ein neues<br />

Zwischenergebnis berechnet, muss diese Funktion aufgerufen werden. In dieser wird<br />

geprüft, ob eine Rückruffunktion definiert wurde. Ist dies der Fall wird diese<br />

ausgeführt. Da die Definition der Funktion in einem anderen Programm erfolgt, ist<br />

diese dem Compiler hier nicht bekannt. Es muss daher auf die korrekte<br />

Parameterliste geachtet werden. Die Parameter bestehen hier aus dem Dateinamen<br />

der zugehörigen Waveform-Datei, die aktuellen X- und Y-Werte, inklusive reeller und<br />

imaginärer Anteil, sowie die Angabe, ob es sich um komplexe Werte handelt.<br />

Seite 35


Der EngineServer<br />

Um den Rückruf in der QucsLiveEngine auszuführen, muss in dieser noch der<br />

entsprechende Aufruf eingebunden werden. In Listing 9 ist der entsprechende<br />

Funktionsaufruf aufgeführt.<br />

CallALResultCallback(waveFormFilename,<br />

curIndep.getValue()->getReal(), numb->getReal(),<br />

curIndep.getValue()->getImag(), numb->getImag(),<br />

numb->isComplex()<br />

);<br />

Listing 9 Aufruf der Rückruffunktion in der QucsLiveEngine<br />

Der Aufruf der Funktion erfolgt dabei an der Stelle im Programmablauf, an der die<br />

Ergebnisse in die Waveform-Datei geschrieben werden.<br />

5.2.2 Speichern der Daten im Zwischenspeicher des EngineServers<br />

Nachdem die Daten von der QucsLiveEngine bereitgestellt werden muss der<br />

EngineServer modifiziert werden, um die Simulationsdaten gegebenenfalls in den<br />

Zwischenspeicher zu schreiben. In Listing 10 sind die Änderungen an der<br />

EngineServer-Klasse aufgeführt.<br />

// EngineServerWSClass.h:<br />

public delegate void ALResultCallBackDel(const char* WFFilename,<br />

float XData, float YData, float XDataImag,<br />

float YDataImag, bool IsComplex);<br />

namespace EngineServerWS {<br />

/* ... */<br />

private:<br />

static ALResultCallBackDel ^c_ALResultDelegate;<br />

void ALResultCallBack(const char* WFFilename, float XData,<br />

float YData, float XDataImag, float YDataImag, bool IsComplex);<br />

/* ... */<br />

}<br />

// EngineServerWSClass.cpp:<br />

[DllImportAttribute("GenericEngine.dll", CharSet=CharSet::Ansi)]<br />

extern "C" void SetALResultCallBack(ALResultCallBackDel ^d);<br />

Int32 EngineServerWSClass::Start() {<br />

/* ... */<br />

c_ALResultDelegate = gcnew ALResultCallBackDel(this,<br />

&EngineServerWSClass::ALResultCallBack);<br />

SetALResultCallBack( c_ALResultDelegate );<br />

/* ... */<br />

}<br />

Int32 EngineServerWSClass::StopServer() {<br />

/* ... */<br />

c_ALResultDelegate = nullptr;<br />

/* ... */<br />

}<br />

void EngineServerWSClass::ALResultCallBack(const char* WFFilename,<br />

float XData, float YData, float XDataImag,<br />

float YDataImag, bool IsComplex) {<br />

/* ... */<br />

}<br />

Listing 10 Änderungen an der EngineServer-Klasse<br />

Seite 36


Der EngineServer<br />

In der Header-Datei der Klasse befinden sich neue Deklarationen. Es wird ein<br />

Methodenzeiger (engl.: delegate) deklariert. Anschließend wird eine globale Variable<br />

von dem neuen Typ, sowie ein Funktionskopf deklariert.<br />

In der Quelltext-Datei muss zunächst der Verweis auf die Rückruffunktion der<br />

generischen Klasse angegeben werden. Wird der EngineServer gestartet wird ein<br />

neuer Methodenzeiger angelegt, der auf die Funktion ALResultCallBack() verweist.<br />

Anschließend wird diese der Rückruffunktion der generischen Klasse zugewiesen.<br />

Wird eine von der generischen Klasse abgeleitete Engine gestartet, kann in dieser<br />

nun die Rückruffunktion aufgerufen werden. Anschließend wird die<br />

ALResultCallBack()-Funktion im EngineServer aufgerufen. Somit ist die<br />

Kommunikation zwischen dem EngineServer und der Engine hergestellt. Wird der<br />

Server gestoppt, wird der Methodenzeiger zurückgesetzt.<br />

Der Zwischenspeicher wird bei der ersten Anfrage der Simulationsergebnisse<br />

angelegt. Die Daten werden in der Struktur WFData hinterlegt, die aus vier Feldern<br />

vom Typ float besteht. In der Funktion ALResultCallBack() müssen die<br />

Simulationsdaten in den Zwischenspeicher geschrieben werden. In Anhang 10<br />

befindet sich die Struktur- sowie die Funktionsdefinition. Bei dem Erstellen des<br />

Zwischenspeichers werden die Felder, in denen die Werte gespeichert werden,<br />

angelegt. Dies bedeutet, dass die Größe der Felder auf die Anzahl, der zu diesem<br />

Zeitpunkt, vorhandenen Ergebnisse gesetzt wird. Durch die Echtzeitkommunikation<br />

werden jedoch neue Werte empfangen, die in den Zwischenspeicher geschrieben<br />

werden müssen. Da ein Feld nicht beliebig erweitert werden kann, wurde eine neue<br />

Struktur WFLiveData entworfen. Diese ähnelt der WFData, jedoch werden hier die<br />

Daten in Listen gespeichert. Diese können beliebig erweitert werden. Das Erstellen<br />

des Zwischenspeichers musste an die neue Struktur angepasst werden. Die<br />

Änderungen zum Lesen der Daten wird in Kapitel 6 näher erläutert. Das Speichern<br />

der Simulationsergebnisse in den Zwischenspeicher ist in Anhang 10 aufgeführt.<br />

Zunächst muss in allen vorhandenen Simulationen die aktuell benötigte gesucht<br />

werden. Diese hat den Typ ESJobObject. In dieser ist eine Hashtable aller<br />

vorhandenen Waveform-Daten enthalten. In den entsprechenden Eintrag der<br />

Hashtable werden schließlich die neuen Simulationsergebnisse hinzugefügt.<br />

5.2.3 Starten des Postprocessing<br />

Wie in Kapitel 5.1.1 erläutert, muss nach einer Änderung an der Manifest-Datei das<br />

Postprocessing gestartet werden. Im bisherigen Ablauf wird das Postprocessing<br />

einmalig nach Abschluss der Simulations ausgeführt. Dazu ist in der generischen<br />

Klasse eine Rückruffunktion definiert. Ist die Simulation beendet wird diese von der<br />

Engine aufgerufen. Anschließend wird auf dem EngineServer das Postprocessing<br />

ausgeführt. In der QucsLiveEngine muss nun der Funktionsaufruf lediglich nach dem<br />

Erstellen der Manifest-Datei erfolgen.<br />

Seite 37


Anzeige der Simulationsergebnisse in WebScope<br />

6 Anzeige der Simulationsergebnisse in WebScope<br />

6.1 Vorbetrachtung<br />

6.1.1 Anfrage der Simulationsdaten<br />

Die Ausgabe der Simulationsergebnisse in WebScope muss in zwei Bereiche<br />

unterteilt werden. Zum einen müssen die Ergebnisse zur Laufzeit dargestellt werden.<br />

Darauf wird im folgenden Kapitel näher eingegangen. Zum anderen können beim<br />

Laden von WebScope bereits Simulationsergebnisse vorliegen. Diese vorhandenen<br />

Ergebnisse müssen separat geladen werden. Eine direkte Darstellung der<br />

Simulationsdaten im Webbrowser ist nicht möglich. Stattdessen muss die Größe des<br />

Ausgabebereiches an den Webserver übergeben werden. Dieser leitet, über einen<br />

Proxy, die Werte an den EngineServer weiter. Hier werden anhand der<br />

Simulationsergebnisse die darzustellenden Punkte berechnet. Der Grund für diese<br />

Darstellung liegt in der Anzahl der Simulationsergebnisse. Würde die Berechnung<br />

der Darstellung im Webbrowser erfolgen müssen alle Simulationsergebnisse an<br />

diesen übertragen werden. Bei Simulationen mit einer großen Anzahl an<br />

Ergebnissen würde dies zu einem erheblichen Datentransfer führen. Durch die<br />

Berechnung, der auszugebenden Punkte, im EngineServer bleibt die Größe der zum<br />

Browser zu übertragenden Daten konstant. Dies bedeutet jedoch, dass beim hereinund<br />

herauszoomen in Webscope die Daten erneut vom Server geladen werden<br />

müssen. Ebenfalls führt ein verschieben des angezeigten Bereiches zu einem<br />

Neuladen der Daten. Für die Anfrage muss daher neben der Größe des<br />

Ausgabebereiches auch die Skalierung der X- und Y-Achsen übergeben werden.<br />

In Kapitel 5 wurde bereits erläutert, dass die Simulationsergebnisse auf dem<br />

EngineServer nach der ersten Anfrage zwischengespeichert werden. Um die<br />

Simulationsergebnisse zur Laufzeit in den Zwischenspeicher schreiben zu können,<br />

musste dieser vom Aufbau her modifiziert werden. Werden über den Webserver<br />

neue Daten angefordert, wird auf den Zwischenspeicher lesend zugegriffen.<br />

Dementsprechend muss die Funktion zum Auslesen der Daten an den neuen Aufbau<br />

angepasst werden.<br />

6.1.2 Ausgabe der Simulationsdaten zu Laufzeit<br />

6.1.2.1 Allgemein<br />

Die Anzeige der Simulationsergebnisse erfolgt bei dem Endnutzer im Webbrowser<br />

über ein HTML-<strong>Dokument</strong> (Hypertext Markup Language). Bei HTML handelt es sich<br />

um eine textbasierte Auszeichnungssprache. Sie dient der Strukturierung von<br />

Bildern, Text und anderen Elementen in einem <strong>Dokument</strong>. HTML trägt aktuell die<br />

Versionsnummer 4.01, wobei an HTML5 entwickelt wird, und dies bereits von vielen<br />

Seite 38


Anzeige der Simulationsergebnisse in WebScope<br />

Layout-Engines unterstütz wird. 15 HTML bietet keine Möglichkeit zur Darstellung von<br />

dynamischen Elementen. Um dies zu erreichen müssen zusätzliche Techniken<br />

verwendet werden. Hier hat sich die Skriptsprache JavaScript etabliert. Sie wird von<br />

allen gängigen Webbrowser unterstützt. Es existieren viele JavaScript-Bibliotheken<br />

die das Erstellen von dynamischen Inhalten auf Webseiten vereinfachen. Als<br />

Konzept für die asynchrone Datenübertragung hat sich Ajax (Asynchronous<br />

JavaScript and XML) in der Webentwicklung durchgesetzt 16 und soll auch hier für die<br />

Übertragung der Simulationsergebnisse genutzt werden. Zur Darstellung von<br />

Echtzeitdaten auf einer Webseite haben sich unterschiedliche Methoden entwickelt.<br />

Im Folgenden wird auf einige dieser Methoden näher eingegangen.<br />

6.1.2.2 Erneutes Abfragen aller Ergebnisse (Polling)<br />

Eine Möglichkeit, Daten von einem Webserver abzufragen, besteht darin, in einem<br />

bestimmten Zeitabstand sämtliche Daten erneut vom Server abzufragen. In der<br />

Antwort befinden sich die alten, sowie die neuen Daten. Dieses Verfahren wird<br />

Polling genannt. Der Vorteil besteht in einer einfachen Implementierung. Jedoch<br />

kommt es so zu einem hohen Datenverkehr, da die alten Daten jedes Mal neu<br />

übertragen werden. Um zu verhindern, dass der EngineServer stets die neuen<br />

Ausgabedaten berechnet, kann hier ein Puffer eingerichtet werden, in dem die<br />

Ausgabedaten hinterlegt werden. Bei Eintreffen neuer Simulationsergebnisse<br />

müssten diese sowohl in den Zwischenspeicher als auch in den Puffer übernommen<br />

werden.<br />

6.1.2.3 Abfragen nach neuen Ergebnissen (Long Polling)<br />

Eine Abwandlung des in Kapitel 6.1.2.2 beschriebenen Verfahrens stellt das<br />

sogenannte Long Polling dar. Hier werden nicht alle Daten vom Webserver<br />

angefragt. Stattdessen wird eine Anfrage gesendet, ob neue Daten vorliegen. Liegen<br />

diese nicht vor, wird die Anfrage zunächst nicht beantwrotet. Gehen neue Ergebnisse<br />

ein, kann die Anfrage bestätigt werden. Da lediglich neue Daten übertragen werden,<br />

wird hier der Datenverkehr im Vergleich zum herkömmlichen Polling stark reduziert.<br />

Hier ist jedoch ein höherer Implementierungsaufwand nötig. So muss eine<br />

Differenzierung erfolgen, welche Daten auf dem Server als neu, und welche als alt<br />

eingestuft werden. Des Weiteren geschieht das Abfragen der Ergebnisse über eine<br />

HTTP-Anfrage. Erhält diese nach einer gewissen Zeitüberschreitung (engl. timeout)<br />

keine Antwort wird eine Ausnahme geworfen, die entsprechend behandelt werden<br />

muss.<br />

15 vgl. online: Hypertext Markup Language auf Wikipedia.org (22.08.2013)<br />

16 vgl. online: Ajax (Programmierung) auf Wikipedia.org (22.08.2013)<br />

Seite 39


Anzeige der Simulationsergebnisse in WebScope<br />

6.1.2.4 Websocket-Protokoll<br />

Bei dem Websocket-Protokoll handelt es sich um ein Netzwerkprotokoll, das auf TCP<br />

basiert. Mit diesem kann eine bidirektionale Verbindung zwischen einem Webserver<br />

und einer Webanwendung hergestellt werden. Durch das Herstellen einer<br />

Verbindung kann der Server aktiv Daten an den Client schicken, und muss nicht, wie<br />

bei einer HTTP-Verbindung, auf eine Anfrage des Clients warten. Ebenfalls entfallen<br />

bei WebSockets zusätzliche Daten, die durch den HTTP-Header verursacht<br />

werden. 17 Der Nachteil dieser Methode besteht jedoch darin, dass sowohl Browser<br />

als auch Webserver das WebSocket-Protokoll unterstützen müssen. Die Transim<br />

Technology Corporation verwendet als WebServer die Internet Information Services<br />

(IIS) von Microsoft in den Versionen bis 7.5. Das WebSocket-Protokoll wird jedoch<br />

erst ab der IIS-Version 8.0 unterstützt. Ältere Versionen des IIS können durch die<br />

Integration von beispielsweise Node.js erweitert werden, wodurch das WebSocket-<br />

Protokoll genutzt werden kann. Das WebSocket-Protokoll wird ebenfalls nur von<br />

neuen Webbrowsern unterstützt. Da die Webanwendungen der Transim Technology<br />

Corporation jedoch auch auf älteren Webbrowser ausgeführt werden soll, müssen<br />

hier zusätzliche JavaScript-Bibliotheken eingebunden werden, damit das Protokoll<br />

verwendet werden kann.<br />

6.1.2.5 Senden der Ergebnisse vom Webserver<br />

Neben den bisher genannten, besteht die Möglichkeit, Daten vom Webserver direkt<br />

an den Webbrowser zu senden. Diese Technik ist unter dem Begriff „Server-Sent<br />

Events“ bekannt. Im Gegensatz zu WebSockets wird keine bidirektionale Verbindung<br />

hergestellt. Nachdem eine Verbindung vom Client hergestellt wurde, können<br />

anschließend vom Server Daten an den Client übertragen werden. Diese Methode<br />

wird ebenfalls nicht vom IIS unterstütz, jedoch existierten Bibliotheken, die die<br />

Verwendung von „Server-Sent Events“ im IIS ermöglichen. Größere Probleme gibt es<br />

hier bei den Webbrowsern. Der Internet Explorer von Microsoft unterstützt bis zu der<br />

aktuellen Version 10 keine „Server-Sent Events“. Auch liegen derzeit keine<br />

JavaScript-Bibliotheken vor, über die die Funktion in den Browser eingebunden<br />

werden kann. 18<br />

6.2 Implementierung<br />

6.2.1 Auslesen des Zwischenspeichers im EngineServer<br />

In Kapitel 5.2.2 wurde beschrieben, wie die Simulationsergebnisse zur Laufzeit in<br />

den Zwischenspeicher geschrieben werden. Sendet WebScope eine Anfrage an den<br />

EngineServer, müssen die Ergebnisse aus dem Zwischenspeicher gelesen werden.<br />

In Anhang 11 befindet sich der Quelltext zum Auslesen der Simulationsergebnisse.<br />

17 vgl. online: WebSocket auf Wikipedia.org (25.08.2013)<br />

18 vgl. online: Server-Sent Events auf Wikipedia.org (25.08.2013)<br />

Seite 40


Anzeige der Simulationsergebnisse in WebScope<br />

Hier wird davon ausgegangen, dass bereits das entsprechende Objekt für die<br />

Simulation (ESJobObject) bereits geladen wurde. Zu Beginn werden die Waveform-<br />

Daten geladen. Für die erste Anfrage sind diese Daten leer. In diesem Fall werden<br />

die Listen für die Werte angelegt. Anschließend werden die Daten aus dem bisher<br />

verwendeten Zwischenspeicher (vgl. wfdat) in den neuen kopiert. Da es sich um die<br />

erste Anfrage handelt, befinden sich in diesem alle bisherigen Ergebnisse.<br />

Anschließend werden die Daten aus den Listen in die Ausgabevariablen<br />

geschrieben. Die Ausgabedaten müssen dabei als Feld vorliegen. Hier bietet jedoch<br />

das .NET-Framework die ToArray()-Methode, mit der eine Liste in ein Feld<br />

konvertiert werden kann.<br />

6.2.2 Ausgabe der Simulationsdaten<br />

In Kapitel 6.1.2 wurden mehrere Möglichkeiten aufgezeigt, wie die<br />

Simulationsegergebnisse im Webbrowser zur Laufzeit dargestellt werden können. Da<br />

die Transim Technology Corporation eine Kompatibilität für ältere Webbrowser<br />

anbietet entfällt als Methode die Verwendung der „Server-Sent Events“. Die<br />

Verwendung von Polling würde zu einem erhöhten Datenverkehr kommen und sollte<br />

daher ebenfalls nicht verwendet werden. Somit bleiben als Alternative das Long-<br />

Polling, sowie die Verwendung des WebSocket-Protokolls übrig. Beide Varianten<br />

haben ihre Vor- und Nachteile.<br />

Für die Verwendung von WebSockets müssen Änderungen an den Webservern der<br />

Transim Technology Corporation vorgenommen werden. Ebenfalls muss eine<br />

zusätzliche JavaScript-Bibliothek in WebScope eingebunden werden, um ältere<br />

Webbrowser zu unterstützen. Der Vorteil liegt jedoch in der bidirektionalen<br />

Verbindung zwischen Webbrowser und Webserver. Es ist gegenüber Long-Polling<br />

schneller, und erzeugt einen niedrigeren Datenverkehr. Ebenfalls ist es möglich,<br />

dass mehrere Client-Verbindungen zu dem Webserver hergestellt werden. So kann<br />

die Ausgabe der Simulationsergebnisse auch von anderen Modulen, wie<br />

beispielsweise dem WebSchematic zur Laufzeit genutzt werden. Long Polling<br />

dagegen kann ohne Änderungen am Webserver implementiert werden. Da die<br />

Anfragen an den Webserver über HTTP versandt werden, entsteht durch den HTTP-<br />

Header ein höherer Datenverkehr. An dieser Stelle der Entwicklung müssen weitere<br />

firmeninterne Besprechungen getroffen werden, welche Methode verwendet werden<br />

soll. Da es zum Zeitpunkt der Fertigstellung dieser Bachelor-Thesis noch keine<br />

Entscheidung vorlag ist die finale Implementierung hier nicht aufgeführt.<br />

Um dennoch die Ausgabe der Simulationsdaten testen zu können, wurde zunächst<br />

die Polling-Methode implementiert. Hierzu wurde eine neue Schaltfläche in<br />

WebScope implementiert, über die die Aktualisierung angehalten, oder gestartet<br />

werden kann. In Listing 11 ist der JavaScript-Quelltext aufgeführt, über den das<br />

Polling realisiert wird.<br />

Seite 41


Anzeige der Simulationsergebnisse in WebScope<br />

function liveRef(idx) {<br />

checkBounds(idx);<br />

SimProxy.Graph(createJSON(idx), responseLiveGraph, null, 0);<br />

}<br />

function responseLiveGraph(data) {<br />

// save the results<br />

/* ... */<br />

if (liveRefresh) {<br />

setTimeout(function () { liveRef(0); }, 500);<br />

}<br />

}<br />

Listing 11 Polling in WebScope<br />

Die Funktion liveRef() startet über den Aufruf SimProxy.Graph() die Ajax-Anfrage<br />

an den Webserver. Über den Proxy wird die Anfrage an den EngineServer<br />

weitergeleitet. Dieser berechnet alle darzustellenden Punkte. Wurde in WebScope<br />

keine Skalierung angegeben, werden als Minima- und Maxima-Werte die aus der<br />

erzeugten *_JS_VIEWER.xml-Datei genommen. Wurden die Daten generiert werden<br />

diese an den Webbrowser zurück geschickt. Hier wird die Funktion<br />

responseLiveGraph() aufgerufen. In dieser Werden die übertragenen Daten in dem<br />

Ausgabebereich der Webseite ausgegeben. Ist der Knopf für die Echtzeitausgabe<br />

aktiviert, wird 500 Millisekunden gewartet, bevor eine neue Anfrage an den Server<br />

geschickt wird. Diese Methode ist in der Praxis nicht zu verwenden, da ein sehr<br />

hoher Datenverkehr entsteht. Er wurde hier lediglich implementiert, um die Ausgabe<br />

der Simulationsergebnisse zu testen.<br />

Seite 42


Bewertung und Ausblick<br />

7 Bewertung und Ausblick<br />

Im Rahmen dieser Arbeit wurden mehrere Programme und Unterprogramme<br />

modifiziert, um eine Ausgabe von Simulationsergebnissen zur Laufzeit der<br />

Simulation zu ermöglichen. Als Übertragung der Daten von dem Simulator zu der<br />

Engine wurde sich für die Verwendung von Datenströmen entschieden. Im Verlauf<br />

der Arbeit hat sich gezeigt, dass TCP gegenüber UDP als Protokoll besser geeignet<br />

ist. Werden mehrere Simulationen auf einem Simulationsserver gleichzeitig<br />

ausgeführt, muss eine Zuweisung der Simulationsdaten zu der ursprünglichen<br />

Simulation erfolgen. Diese Zuordnung wird nun erreicht, indem für jede Simulation<br />

ein eigener TCP-Server erstellt wird, an den die Simulationsanwendung die<br />

Ergebnisse sendet. Für die Kommunikation zwischen Anwendung und Engine<br />

musste ein Format entworfen werden, indem die Ergebnisse abgebildet werden. Hier<br />

wurde XML-Format entworfen, das einen minimalen Overhead erzeugt und somit die<br />

Netzwerkauslastung verringert. Die Implementierung eines TCP-Servers in der<br />

Engine hat ergeben, dass es zu einem Blockieren der Engine kommt, wenn keine<br />

Daten empfangen werden. Dies wurde umgangen, indem das Empfangen der Daten<br />

in einen zusätzlichen Thread ausgelagert wurde. Um die empfangenen Daten in dem<br />

Webbrowser zugänglich zu machen, musste die bestehende Lösung zur Ausgabe<br />

der Daten modifiziert werden. Um einen Geschwindigkeitsvorteil zu erreichen,<br />

werden die Ausgabedaten in einen Zwischenspeicher geschrieben. Der<br />

EngineServer wurde so modifiziert, dass neue Simulationsergebnisse in diesen<br />

Zwischenspeicher eingetragen werden. Um die Ergebnisse aus der Engine an den<br />

EngineServer weiterzuleiten wurden Rückruffunktionen verwendet. Die eigentliche<br />

Ausgabe der Simulationsergebnisse in WebScope greift auf die Daten in dem<br />

Zwischenspeicher des EngineServers zu. Um die Simulationsergebnisse WebScope<br />

zur Laufzeit zur Verfügung zu stellen, wurden mehrere Methoden vorgestellt. Dabei<br />

hat sich die Verwendung von Long-Polling oder die Nutzung des WebSocket-<br />

Protokolls als günstig erwiesen. Die Entscheidung für eine der Methoden muss in<br />

weiteren firmeninternen Besprechungen erfolgen. Aus diesem Grund ist die<br />

Implementierung aufgrund des zeitlich begrenzten Rahmens in dieser Arbeit nicht<br />

möglich gewesen.<br />

Seite 43


Quellenverzeichnis<br />

Quellenverzeichnis<br />

Ajax (Programmierung) auf Wikipedia.org<br />

http://de.wikipedia.org/wiki/Ajax_(Programmierung) (22.08.2013)<br />

c-worker.ch Winsock Tutorial<br />

http://www.c-worker.ch/tuts.php (08.07.2013)<br />

Document Object Model auf Wikipedia.org<br />

http://de.wikipedia.org/wiki/Document_Object_Model (09.08.2013)<br />

Hypertext Markup Language auf Wikipedia.org<br />

http://de.wikipedia.org/wiki/Hypertext_Markup_Language (22.08.2013)<br />

IEEE 802.3-2012 – Section One, 2012<br />

Interprozesskommunikation auf Wikipedia.org<br />

http://de.wikipedia.org/wiki/Interprozesskommunikation (15.07.2013)<br />

MEYER, Martin: Studienarbeit – Integration des „Quite Universal Circuit Simulator“ in<br />

das Engine Framework der Transim Technology Corporation. <strong>Glauchau</strong>, 2013<br />

Qucs auf Sourceforge.net:<br />

http://sourceforge.net/p/qucs/git/ci/master/tree/ (12.07.2013)<br />

RFC 793 – Transmission Control Protocol<br />

http://tools.ietf.org/html/rfc793 (13.08.2013)<br />

Server-sent events auf Wikipedia.org<br />

http://en.wikipedia.org/wiki/Server-sent_events (25.08.2013)<br />

Simple API for XML auf Wikipedia.org<br />

http://de.wikipedia.org/wiki/Simple_API_for_XML (09.08.2013)<br />

Seite 44


Quellenverzeichnis<br />

The zlib/libpng License (Zlib) auf opensource.org<br />

http://opensource.org/licenses/Zlib (09.08.2013)<br />

Tiefpass auf Wikipedia.org<br />

http://de.wikipedia.org/wiki/Tiefpass (07.08.2013)<br />

WebSocket auf Wikipedia.org<br />

http://de.wikipedia.org/wiki/WebSocket (25.08.2013)<br />

Seite 45


Anhangverzeichnis<br />

Anhangverzeichnis<br />

Anhang 1<br />

Anhang 2<br />

Anhang 3<br />

Anhang 4<br />

Anhang 5<br />

Anhang 6<br />

Anhang 7<br />

Anhang 8<br />

Anhang 9<br />

Anhang 10<br />

Anhang 11<br />

Qucs-Ausgabedatei<br />

Größe der Ergebnisdaten bei XML und JSON<br />

Node.js Quelltext für den TCP- und UDP-Testserver<br />

Änderungen am Programmeinstieg „ucs.cpp“<br />

Die Header- und Quelltext-Dateien der Qucs-Netzwerkklasse<br />

Erweiterung der nasolver-Klasse<br />

Speichern der Ergebnisse und Abhängigkeiten in Qucs<br />

Typen<br />

Beispiel für eine Kurvenschar<br />

Änderungen an der EngineServer-Klasse<br />

Auslesen der Simulationsergebnisse aus dem Zwischenspeicher<br />

Seite 46


Qucs-Ausgabedatei Anhang 1<br />

Anhang 1 Qucs-Ausgabedatei<br />

<br />

<br />

+1.00000000000e+000<br />

+1.00000000000e+005<br />

+1.00000000000e+010<br />

<br />

<br />

+5.00000000000e+000<br />

+5.00000000000e+006<br />

<br />

<br />

+5.00000000000e-010<br />

+1.50000000000e-009<br />

<br />

<br />

-4.93480220054e-017-j3.14159265359e-009<br />

-4.93479002439e-007-j3.14158490204e-004<br />

-1.99991894634e-001-j1.27318794437e-003<br />

-4.93358488727e-011-j3.14081768789e-009<br />

-1.99999918943e-007-j1.27323902871e-010<br />

-2.00000000000e-007-j1.27323954474e-015<br />

-4.44089209850e-016-j9.42477796077e-009<br />

-4.44122335597e-006-j9.42456867305e-004<br />

-1.99999099371e-001-j4.24411270385e-004<br />

-4.43148116314e-010-j9.40389509777e-009<br />

-1.99999990994e-007-j4.24413162466e-011<br />

-2.00000000000e-007-j4.24413181578e-016<br />

<br />

<br />

+1.00000000000e+000-j1.57079632679e-008<br />

+9.99997532605e-001-j1.57079245102e-003<br />

+4.05268309663e-005-j6.36593972186e-003<br />

+9.99753320756e-001-j1.57040884394e-002<br />

+4.05284570314e-007-j6.36619514355e-004<br />

+4.05284734569e-017-j6.36619772368e-009<br />

+1.00000000000e+000-j4.71238898038e-008<br />

+9.99977793883e-001-j4.71228433652e-003<br />

+4.50314343905e-006-j2.12205635192e-003<br />

+9.97784259418e-001-j4.70194754888e-002<br />

+4.50316351465e-008-j2.12206581233e-004<br />

+4.50316371744e-018-j2.12206590789e-009<br />

<br />

<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />

+1.00000000000e+000<br />


Größe der Ergebnisdaten bei XML und JSON Anhang 2<br />

Anhang 2 Größe der Ergebnisdaten bei XML und JSON<br />

( ) ( ( ) ( )) ( ( ) ( ))<br />

d<br />

XML<br />

in Byte<br />

i<br />

1 2 3 4 … 23 24 25 26 27 28<br />

1 147 198 249 300 … 1269 1320 1371 1422 1473 1524<br />

2 198 249 300 351 … 1320 1371 1422 1473 1524 1575<br />

3 249 300 351 402 … 1371 1422 1473 1524 1575 1626<br />

4 300 351 402 453 … 1422 1473 1524 1575 1626 1677<br />

… … … … … … … … … … … …<br />

23 1269 1320 1371 1422 … 2391 2442 2493 2544 2595 2646<br />

24 1320 1371 1422 1473 … 2442 2493 2544 2595 2646 2697<br />

25 1371 1422 1473 1524 … 2493 2544 2595 2646 2697 2748<br />

26 1422 1473 1524 1575 … 2544 2595 2646 2697 2748 2799<br />

27 1473 1524 1575 1626 … 2595 2646 2697 2748 2799 2850<br />

28 1524 1575 1626 1677 … 2646 2697 2748 2799 2850 2901<br />

( ) ( ( ) ( )) ( ( ) ( ))<br />

d<br />

JSON<br />

in Byte<br />

i<br />

1 2 3 4 … 23 24 25 26 27 28<br />

1 145 200 255 310 … 1355 1410 1465 1520 1575 1630<br />

2 200 255 310 365 … 1410 1465 1520 1575 1630 1685<br />

3 255 310 365 420 … 1465 1520 1575 1630 1685 1740<br />

4 310 365 420 475 … 1520 1575 1630 1685 1740 1795<br />

… … … … … … … … … … … …<br />

23 1355 1410 1465 1520 … 2565 2620 2675 2730 2785 2840<br />

24 1410 1465 1520 1575 … 2620 2675 2730 2785 2840 2895<br />

25 1465 1520 1575 1630 … 2675 2730 2785 2840 2895 2950<br />

26 1520 1575 1630 1685 … 2730 2785 2840 2895 2950 3005<br />

27 1575 1630 1685 1740 … 2785 2840 2895 2950 3005 3060<br />

28 1630 1685 1740 1795 … 2840 2895 2950 3005 3060 3115


Node.js Quelltext für den TCP- und UDP-Testserver Anhang 3<br />

Anhang 3 Node.js Quelltext für den TCP- und UDP-Testserver<br />

Der TCP-Server:<br />

var net = require('net');<br />

var host = '127.0.0.1';<br />

var port = 12346;<br />

net.createServer(function(sock) {<br />

sock.name = sock.remoteAddress + ":" + sock.remotePort<br />

console.log(Date.now() + ' | CONNECTED: ' + sock.name);<br />

sock.on('data', function(data) {<br />

console.log(Date.now() + ' | DATA ' + sock.name + ' - ' + data);<br />

});<br />

sock.on('error', function(data) {<br />

console.log(Date.now() + ' | ERROR ' + sock.name + ' - ' + data + '\n');<br />

});<br />

sock.on('end', function() {<br />

console.log(Date.now() + ' | END: ' + sock.name + '\n');<br />

});<br />

}).listen(port, host);<br />

console.log('Server listening on ' + host + ':' + port + '\n');<br />

Der UDP-Server:<br />

var port = 12345;<br />

var host = '127.0.0.1';<br />

var dgram = require('dgram');<br />

var server = dgram.createSocket('udp4');<br />

server.on('listening', function () {<br />

var address = server.address();<br />

console.log('UDP Server listening on ' + address.address + ':' + address.port<br />

+ '\n');<br />

});<br />

server.on('message', function(message, remote) {<br />

console.log(Date.now() + ' | DATA ' + remote.address + ':' + remote.port<br />

+ ' - ' + message);<br />

});<br />

server.bind(port, host);


Änderungen am Programmeinstieg „ucs.cpp“ Anhang 4<br />

Anhang 4<br />

Änderungen am Programmeinstieg „ucs.cpp“<br />

/* ... */<br />

#include "network.h"<br />

/* ... */<br />

int main (int argc, char ** argv) {<br />

}<br />

/* ... */<br />

// check program arguments<br />

for (int i = 1; i < argc; i++) {<br />

/* ... */<br />

if (!strcmp (argv[i], "-d")) {<br />

i++;<br />

msDelay = atoi(argv[i]);<br />

}<br />

else if (!strcmp (argv[i], "-udp")) {<br />

// Look for valid Arguments<br />

if(i+1 >= argc)<br />

{<br />

fprintf (stdout,<br />

"Usage: %s [OPTION]...\n\n"<br />

" -udp IP:PORT using UDP to send the simulation results "<br />

"to a server\n", argv[0]);<br />

return 1;<br />

}<br />

i++;<br />

networkAddr = (char *)malloc(strlen(argv[i])+1);<br />

strcpy(networkAddr, argv[i]);<br />

networkUse = true;<br />

networkProtocol = network::UDP;<br />

}<br />

else if (!strcmp (argv[i], "-tcp")) {<br />

// Look for valid Arguments<br />

if(i+1 >= argc)<br />

{<br />

fprintf (stdout,<br />

"Usage: %s [OPTION]...\n\n"<br />

" -tcp IP:PORT using TCP to send the simulation results"<br />

" to a server\n", argv[0]);<br />

return 1;<br />

}<br />

i++;<br />

networkAddr = (char *)malloc(strlen(argv[i])+1);<br />

strcpy(networkAddr, argv[i]);<br />

networkUse = true;<br />

networkProtocol = network::TCP;<br />

}<br />

/* ... */<br />

}<br />

/* ... */


Die Header- und Quelltext-Dateien der Qucs-Netzwerkklasse Anhang 5<br />

Anhang 5<br />

Die Header-Datei:<br />

#ifndef __NETWORK_H__<br />

#define __NETWORK_H__<br />

Die Header- und Quelltext-Dateien der Qucs-Netzwerkklasse<br />

#include <br />

#pragma comment(lib,"ws2_32.lib")<br />

class network<br />

{<br />

public:<br />

// Enumeration Declarations<br />

enum Protocol {UDP, TCP};<br />

// Consturctor & Destructor Declarations<br />

network(char const* str, network::Protocol protocol);<br />

network(char const* ip, int port, network::Protocol protocol);<br />

~network(void);<br />

// static member function Declarations<br />

static int getAddr(char const* str, char* ip, int* port);<br />

// member function Declarations<br />

int netStartSocket(void);<br />

int netConnect(void);<br />

int netSend(char* str);<br />

int netClose(void);<br />

private:<br />

// private member function Declarations<br />

int startWinsock(void);<br />

// private member variables Declaration<br />

Protocol m_protocol;<br />

char m_strIP[15];<br />

int m_iPort;<br />

long m_lRC;<br />

SOCKET m_s;<br />

};<br />

// externalize global variable<br />

extern bool networkUse;<br />

extern char * networkAddr;<br />

extern network::Protocol networkProtocol;<br />

extern int msDelay;<br />

#endif /* __NETWORK_H__ */


Die Header- und Quelltext-Dateien der Qucs-Netzwerkklasse Anhang 5<br />

Die Quelltext-Datei:<br />

#include "network.h"<br />

// Global variables.<br />

bool networkUse = false;<br />

char * networkAddr = NULL;<br />

network::Protocol networkProtocol = network::UDP;<br />

int msDelay = 0;<br />

network::network(void)<br />

{<br />

strcpy(m_strIP, "127.0.0.1");<br />

m_iPort = 12345;<br />

m_protocol = UDP;<br />

}<br />

network::network(char const* str, network::Protocol protocol)<br />

{<br />

char ip[15];<br />

int port;<br />

if(getAddr(str, ip, &port) == 0)<br />

{<br />

strcpy(m_strIP, ip);<br />

m_iPort = port;<br />

m_protocol = protocol;<br />

}<br />

}<br />

network::network(char const* ip, int port, network::Protocol protocol)<br />

{<br />

strcpy(m_strIP, ip);<br />

m_iPort = port;<br />

m_protocol = protocol;<br />

}<br />

network::~network(void)<br />

{<br />

}<br />

int network::startWinsock()<br />

{<br />

WSADATA wsa;<br />

return WSAStartup(MAKEWORD(2,0),&wsa);<br />

}<br />

/* Page 1 of 3 */


Die Header- und Quelltext-Dateien der Qucs-Netzwerkklasse Anhang 5<br />

int network::getAddr(char const* str, char* ip, int* port)<br />

{<br />

// copy str to temporary variable<br />

char* tmp = (char*)malloc(strlen(str)+1);<br />

strcpy(tmp, str);<br />

// split the string at ':'<br />

int cnt = 0;<br />

char* pch = strtok(tmp, ":");<br />

while (pch != NULL)<br />

{<br />

if(cnt == 0)<br />

strcpy(ip, pch);<br />

if(cnt == 1)<br />

*port = atoi(pch);<br />

cnt++;<br />

pch = strtok(NULL, ":");<br />

} free(tmp);<br />

if(cnt != 2)<br />

return 1;<br />

}<br />

return 0;<br />

int network::netStartSocket(void)<br />

{<br />

// start WinSock<br />

m_lRC = startWinsock();<br />

if(m_lRC != 0)<br />

return m_lRC;<br />

// create socket<br />

if(m_protocol == UDP)<br />

m_s = socket(AF_INET, SOCK_DGRAM, 0);<br />

else if(m_protocol == TCP)<br />

m_s = socket(AF_INET, SOCK_STREAM, 0);<br />

else<br />

return -1;<br />

if(m_s == INVALID_SOCKET)<br />

return WSAGetLastError();<br />

}<br />

return 0;<br />

/* Page 2 of 3 */


Die Header- und Quelltext-Dateien der Qucs-Netzwerkklasse Anhang 5<br />

int network::netConnect(void)<br />

{<br />

// connect to server<br />

SOCKADDR_IN addr;<br />

memset(&addr, 0, sizeof(SOCKADDR_IN));<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(m_iPort);<br />

addr.sin_addr.s_addr = inet_addr(m_strIP);<br />

m_lRC = connect(m_s, (SOCKADDR*)&addr, sizeof(SOCKADDR_IN));<br />

if(m_lRC == SOCKET_ERROR)<br />

return WSAGetLastError();<br />

}<br />

return 0;<br />

int network::netSend(char* str)<br />

{<br />

char * buf = (char *)malloc(strlen(str)+1);<br />

strcpy(buf, str);<br />

m_lRC = send(m_s, buf, strlen(buf), 0);<br />

if(m_lRC == SOCKET_ERROR)<br />

return WSAGetLastError();<br />

free(buf);<br />

return 0;<br />

}<br />

int network::netClose()<br />

{<br />

closesocket(m_s);<br />

WSACleanup();<br />

return 0;<br />

}<br />

/* Page 3 of 3 */


Erweiterung der nasolver-Klasse Anhang 6<br />

Anhang 6<br />

Erweiterung der nasolver-Klasse<br />

// Constructor creates an unnamed instance of the nasolver class.<br />

template <br />

nasolver::nasolver () : analysis () {<br />

/* ... */<br />

if(networkUse)<br />

{<br />

myNet = network(networkAddr, networkProtocol);<br />

myNet.netStartSocket();<br />

myNet.netConnect();<br />

}<br />

}<br />

// Constructor creates a named instance of the nasolver class.<br />

template <br />

nasolver::nasolver (char * n) : analysis (n) {<br />

/* ... */<br />

if(networkUse)<br />

{<br />

myNet = network(networkAddr, networkProtocol);<br />

myNet.netStartSocket();<br />

myNet.netConnect();<br />

}<br />

}<br />

// Destructor deletes the nasolver class object.<br />

template <br />

nasolver::~nasolver () {<br />

/* ... */<br />

if(networkUse)<br />

myNet.netClose();<br />

}<br />

/* ... */<br />

/* This function saves the results of a single solve() functionality<br />

into the output dataset. */<br />

template <br />

void nasolver::saveResults (const char * volts, const char * amps,<br />

int saveOPs, vector * f) {<br />

/* ... */<br />

//Send Result to Server:<br />

if(networkUse)<br />

{<br />

// Create XML-Node:<br />

const char vers[] = {PACKAGE_VERSION};<br />

int len;<br />

len = strlen(netPrint)+strlen(vers)+39;<br />

char * xnode = (char *)malloc(len + 1);<br />

sprintf(xnode, "%s", vers,<br />

netPrint);<br />

xnode[len] = '\0';<br />

}<br />

myNet.netSend(xnode);<br />

}<br />

// Add delay to simulation:<br />

usleep(msDelay*1000);


Speichern der Ergebnisse und Abhängigkeiten in Qucs Anhang 7<br />

Anhang 7 Speichern der Ergebnisse und Abhängigkeiten in Qucs<br />

Änderungen an der analysis.cpp:<br />

int analysis::solve (char const * str)<br />

{<br />

netPrint = (char*)malloc(strlen(str)+1);<br />

strcpy(netPrint, str);<br />

return solve();<br />

}<br />

Aufruf der getDNode()-Funktion:<br />

void analysis::getDNode (char const * n, nr_complex_t val)<br />

{<br />

char * out;<br />

char * tmp;<br />

double creal = real(val);<br />

double cimag = imag(val);<br />

int len = 0;<br />

if(cimag != 0) {<br />

tmp = (char *)malloc(40);<br />

char sign = '+';<br />

if(cimag < 0) {<br />

sign = '-';<br />

cimag = -cimag;<br />

}<br />

sprintf(tmp, "%+.11e%cj%.11e", creal, sign, cimag);<br />

}<br />

else {<br />

tmp = (char *)malloc(20);<br />

sprintf(tmp, "%+.11e", creal);<br />

}<br />

if(netPrint2==NULL) {<br />

len = strlen(n)+strlen(tmp)+12;<br />

netPrint2 = (char *)malloc(len+1);<br />

sprintf(netPrint2, "%s", n, tmp);<br />

}<br />

else {<br />

len = strlen(netPrint2);<br />

char * help = (char*)malloc(len+1);<br />

strcpy(help, netPrint2);<br />

free(netPrint2);<br />

len = strlen(help)+strlen(n)+strlen(tmp)+12;<br />

netPrint2 = (char *)malloc(len+1);<br />

sprintf(netPrint2, "%s%s", help, n, tmp);<br />

free(help);<br />

}<br />

free(tmp);<br />

}<br />

return;


Speichern der Ergebnisse und Abhängigkeiten in Qucs Anhang 7<br />

Beispiel für die Verwendung der getINode()-Funktion:<br />

char * analysis::getINode (char const * IName, char const * unit, nr_double_t v)<br />

{<br />

char * out;<br />

char* tmpName;<br />

int len;<br />

char val[20];<br />

sprintf(val, "%+.11e", v);<br />

if(strlen(unit)>0) {<br />

len = strlen(IName)+strlen(unit)+1;<br />

tmpName = (char *)malloc(len+1);<br />

sprintf(tmpName, "%s.%s", IName, unit);<br />

}<br />

else {<br />

len = strlen(IName);<br />

tmpName = (char *)malloc(len+1);<br />

sprintf(tmpName, "%s", IName);<br />

}<br />

tmpName[len] = '\0';<br />

if(netPrint == NULL) {<br />

len = strlen(tmpName)+strlen(val)+12;<br />

out = (char *)malloc(len+1);<br />

sprintf(out, "%s", tmpName, val);<br />

}<br />

else {<br />

len = strlen(tmpName)+strlen(val)+strlen(netPrint)+12;<br />

out = (char*)malloc(len+1);<br />

sprintf(out, "%s%s", netPrint, tmpName, val);<br />

}<br />

out[len] = '\0';<br />

return out;<br />

}


Typen Anhang 8<br />

Anhang 8<br />

Typen<br />

# Bezeichnungen<br />

0 UNKNOWN<br />

1 NONE<br />

2 volt voltage<br />

3 amp current<br />

4 second time<br />

5 hertz frequency<br />

6 ohm resistance<br />

7 siemens conductance<br />

8 farad capacity<br />

9 henry inductance<br />

10 joule energy<br />

11 watt power<br />

12 coulomb charge<br />

13 weber magnetic flux<br />

14 volt_squared<br />

15 volt_squared_over_hertz<br />

16 volt_over_square_root_of_hertz<br />

17 amp_squared<br />

18 amp_squared_over_hertz<br />

19 amp_over_square_root_of_hertz<br />

20 volt_over_second<br />

21 constant<br />

22 centigrad<br />

23 digital<br />

24 tesla<br />

25 amp_turns_over_meter<br />

26 temperature<br />

27 length<br />

28 angle


Beispiel für eine Kurvenschar Anhang 9<br />

Anhang 9 Beispiel für eine Kurvenschar<br />

Die Schaltung:<br />

Ausgabe der Simulation:<br />

Auszug der übertragenen Zwischenergebnisse:<br />

<br />

<br />

<br />

+6.07902735562e-006<br />

<br />

<br />

+1.00000000000e+001<br />

<br />

<br />

-2.57362469582e-002<br />

<br />

<br />

+4.59512013373e-001<br />

<br />

<br />

+2.02149543791e-001<br />

<br />


Beispiel für eine Kurvenschar Anhang 9<br />

Liste der Waveform-Dateien:<br />

Die Manifest-Datei:<br />

# Division Labels (comma separated) : P1 +1.00e+000, P1 +3.16e+001, P1 +1.00e+003<br />

Ua.Vt,Sim1_6373452_group0_c0_div_0,4,2,1,1<br />

Ua.Vt,Sim1_6373452_group0_c0_div_1,4,2,1,1<br />

Ua.Vt,Sim1_6373452_group0_c0_div_2,4,2,1,1<br />

Ue.Vt,Sim1_6373452_group0_c1_div_0,4,2,1,1<br />

Ue.Vt,Sim1_6373452_group0_c1_div_1,4,2,1,1<br />

Ue.Vt,Sim1_6373452_group0_c1_div_2,4,2,1,1<br />

Ue1.It,Sim1_6373452_group0_c2_div_0,4,3,1,1<br />

Ue1.It,Sim1_6373452_group0_c2_div_1,4,3,1,1<br />

Ue1.It,Sim1_6373452_group0_c2_div_2,4,3,1,1


Änderungen an der EngineServer-Klasse Anhang 10<br />

Anhang 10 Änderungen an der EngineServer-Klasse<br />

ESTypes.h:<br />

public ref struct WFData : public Object<br />

{<br />

public:<br />

array ^m_XDataReal;<br />

array ^m_YDataReal;<br />

array ^m_XDataImag;<br />

array ^m_YDataImag;<br />

};<br />

public ref struct WFLiveData : public Object<br />

{<br />

public:<br />

List ^m_XDataReal;<br />

List ^m_YDataReal;<br />

List ^m_XDataImag;<br />

List ^m_YDataImag;<br />

};<br />

EngineServerWSClass.cpp:<br />

void EngineServerWSClass::ALResultCallBack(const char* WFFilename, float XData,<br />

float YData, float XDataImag, float YDataImag,<br />

bool IsComplex)<br />

{<br />

IDictionaryEnumerator ^DicEnum = c_JobHandleToJobObject->GetEnumerator();<br />

while ( DicEnum->MoveNext() )<br />

{<br />

ESJobObject ^jo = safe_cast(DicEnum->Value);<br />

if ( jo != nullptr ) {<br />

String ^WaveFormFilename = gcnew String(WFFilename);<br />

if(!String::IsNullOrEmpty(jo->m_strFileTag)) {<br />

if(jo->m_mapWFLiveData == nullptr) {<br />

jo->m_mapWFLiveData = gcnew Hashtable();<br />

}<br />

WFLiveData ^wfldat = (WFLiveData ^)jo->m_mapWFLiveData[WaveFormFilename];<br />

if(wfldat != nullptr) {<br />

wfldat->m_XDataReal->Add(XData);<br />

wfldat->m_YDataReal->Add(YData);<br />

if(IsComplex) {<br />

wfldat->m_XDataImag->Add(XDataImag);<br />

wfldat->m_YDataImag->Add(YDataImag);<br />

}<br />

jo->m_mapWFLiveData[WaveFormFilename] = wfldat;<br />

}<br />

}<br />

}<br />

}<br />

}


Auslesen der Simulationsergebnisse aus dem Zwischenspeicher Anhang 11<br />

Anhang 11 Auslesen der Simulationsergebnisse aus dem Zwischenspeicher<br />

WFLiveData ^wfldat = nullptr;<br />

if(jo->m_mapWFLiveData != nullptr)<br />

wfldat = (WFLiveData ^)jo->m_mapWFLiveData[WFFileName];<br />

if(wfldat == nullptr)<br />

{<br />

wfldat = gcnew WFLiveData();<br />

wfldat->m_XDataReal = gcnew List();<br />

wfldat->m_YDataReal = gcnew List();<br />

if(bComplex)<br />

{<br />

wfldat->m_XDataImag = gcnew List();<br />

wfldat->m_YDataImag = gcnew List();<br />

}<br />

else<br />

{<br />

wfldat->m_XDataImag = nullptr;<br />

wfldat->m_YDataImag = nullptr;<br />

}<br />

for(int i=0; i< wfdat->m_XDataReal->Length; i++)<br />

{<br />

wfldat->m_XDataReal->Add(wfdat->m_XDataReal[i]);<br />

wfldat->m_YDataReal->Add(wfdat->m_YDataReal[i]);<br />

}<br />

}<br />

if(bComplex)<br />

{<br />

wfldat->m_XDataImag->Add(wfdat->m_XDataImag[i]);<br />

wfldat->m_YDataImag->Add(wfdat->m_YDataImag[i]);<br />

}<br />

XData = wfldat->m_XDataReal->ToArray();<br />

YData = wfldat->m_YDataReal->ToArray();<br />

if(bComplex)<br />

{<br />

XDataImag = wfldat->m_XDataImag->ToArray();<br />

YDataImag = wfldat->m_YDataImag->ToArray();<br />

}<br />

return ES_ERROR_NOERROR;


Ehrenwörtliche Erklärung<br />

Ehrenwörtliche Erklärung<br />

„Ich erkläre hiermit ehrenwörtlich“,<br />

1. dass ich meine Bachelor Thesis mit dem Thema<br />

Erweiterung des Engine Framework und der Web-Schematic Lösung der<br />

Transim Technology Corporation um eine Echtzeitkommunikation zwischen<br />

Simulator und Schematic<br />

ohne fremde Hilfe angefertigt habe.<br />

2. dass ich die Übernahme wörtlicher Zitate aus der Literatur sowie die<br />

Verwendung der Gedanken anderer Autoren an den entsprechenden Stellen<br />

innerhalb der Arbeit gekennzeichnet habe und<br />

3. dass ich meine Bachelor Thesis bei keiner anderen Prüfung vorgelegt habe.<br />

Ich bin mir bewusst, dass eine falsche Erklärung rechtliche Folgen haben wird.<br />

____________________<br />

Ort, Datum<br />

____________________<br />

Unterschrift


Thesen<br />

Thesen<br />

1. Die Kommunikation zwischen der Simulationsanwendung Qucs und der<br />

entsprechenden Engine des EngineServers erfolgt mithilfe von Datenströmen.<br />

2. Als Protokoll für die Kommunikation zwischen Simulationsanwendung und<br />

Engine ist das sicherere TCP bevorzugt gegenüber dem schnelleren UDP.<br />

3. Bei mehreren gestarteten Simulationen muss eine Zuweisung der<br />

übertragenen Daten zu den Simulationen erfolgen. Diese Zuweisung erfolgt,<br />

indem für jede Simulation ein eigener TCP-Server erstellt wird, der an einen<br />

eigenen Port gebunden ist.<br />

4. Als Format für die Übertragung der Daten von Qucs an das EngineFramework<br />

ist XML, aufgrund eines kleineren Overhead, gegenüber JSON zu verwenden.<br />

5. Zum Empfangen der Simulationsergebnisse in der Engine wird ein TCP-<br />

Server benötigt. Um ein blockieren der Engine zu verhindern, muss dieser in<br />

einem zusätzlichen Thread ausgeführt werden.<br />

6. Um die Simulationsergebnisse von der Engine an den EngineServer zur<br />

Laufzeit weiterzuleiten eignet sich die Verwendung von Rückruffunktionen.

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!