zurück zum Artikel

Delphi im Einsatz bei der weltweit größten Modellbahnanlage

Gerrit Braun

Eine Megacity der besonderen Art befindet sich mit dem Miniatur Wunderland in der Hamburger Speicherstadt. heise Developer lässt dessen Chefentwickler Gerrit Braun über die zum Einsatz kommende Software berichten, die überraschenderweise mit Delphi erstellt wurde.

Delphi im Einsatz bei der weltweit größten Modellbahnanlage

Eine Megacity der besonderen Art befindet sich mit dem Miniatur Wunderland in der Hamburger Speicherstadt. heise Developer lässt dessen Chefentwickler Gerrit Braun über die zum Einsatz kommende Software berichten, die überraschenderweise mit Delphi erstellt wurde.

Laut dem Guinness-Buch der Rekorde handelt es sich beim Miniatur Wunderland um die größte Modellbahnanlage der Welt. Hier donnern nicht nur Hunderte von Zügen über Brücken und durch Tunnel, sondern es fährt auch eine unübersehbare Schar von Modellautos durch Städte und Dörfer. An roten Ampeln halten sie an, sie fahren weiter, wenn das Licht auf Grün springt, sie biegen um Ecken und beachten die Vorfahrt anderer Fahrzeuge. In den Häfen und Kanälen schwimmen Schiffe, und richtig große Pötte legen an den Kais an. Auf dem Flughafen heben im Minutenrhythmus Flugzeuge ab, und wenn das Space Shuttle im regelmäßigen Turnus zu einer Notlandung ansetzt, rückt die gesamte Flughafenfeuerwehr zum Großeinsatz aus.

Wie in einer realen Megacity ist es die Software, die im kleinen Hamburger Pendant den Betrieb am Laufen hält. Die Autos finden mithilfe von Mikroprozessoren und Algorithmen ihren Weg, die Fahrpläne der Züge werde über ein Programm gesteuert. Verkehrsampeln, Weichen, Signale – sie alle gehorchen den Anweisungen der Software. Darüber hinaus versuchen weitere Programme, in den 6400 Quadratmeter großen Hallen die Besucherströme und das Klima zu regeln. Und auch die kaufmännischen Belange der Anlage mit ihren mehr als 300 Mitarbeitern und 1,2 Millionen Besuchern jährlich werden per Software erfasst und bearbeitet.

Alle Programme zusammen umfassen etwa 620.000 Lines of Code (LoC), die der Autor des Artikels und Mitgründer des Miniatur Wunderland in weiten Teilen allein entwickelt hat. Mit Ausnahme der internen Steuersoftware in den Autos und Flugzeugen wurde die gesamte Software mit der Entwicklungsumgebung Delphi konzipiert, geschrieben, kompiliert und getestet.

Der Grund dafür ist recht trivial: Während seines Studiums zum Wirtschaftsinformatiker in den 90er-Jahren lernte der Autor, mit C/C++ und Pascal zu programmieren, wobei letzteres zu seinem Favoriten wurde. Er kam so ganz zwanglos zum damals wie heute in der Pascal-Welt dominierenden Entwicklungssystem Delphi.

Nahezu alles ist selbst erstellt; zugekauft wurde lediglich die Railware-Software zur Steuerung der Züge. Aber auch diese wurde mit Delphi entwickelt. Mit einem Anteil von 120.000 LoC nimmt Railware den kleineren Teil der Gesamtsoftware ein, der deutlich größere Teil entfällt auf die Steuerung des Straßen-, Wasser- und Luftverkehrs in der Modellanlage sowie auf die der internen betrieblichen Prozesse.

In der Modellwelt tummeln sich über 8000 Autos – vom PKW bis zum Feuerwehr-Einsatzfahrzeug und zum Schwertransporter; etwa 300 davon sind fahrtüchtig. Mehrere PCs in der Anlage steuern die Fahrzeuge. Die Software enthält mehr als 25.000 Einzeldaten, die nicht in einer Datenbank strukturiert abgelegt, sondern als Flat File vorgehalten werden, womit sich eine erheblich bessere Echtzeit-Performance erzielen lässt.

Mehr als hundert Rechner teilen sich die Aufgaben, die im Miniatur Wunderland anfallen. Für den Betrieb der Anlage sind Fahrzeugsteuerungs-, die Eisenbahn- und Lichtrechner besonders kritisch (Abb. 1).

Mehr als hundert Rechner teilen sich die Aufgaben, die im Miniatur Wunderland anfallen. Für den Betrieb der Anlage sind Fahrzeugsteuerungs-, die Eisenbahn- und Lichtrechner besonders kritisch (Abb. 1).

Über die Streckenführung einschließlich Vorfahrtregeln, Geschwindigkeitsbegrenzungen und Zulässigkeit des jeweiligen Streckenabschnitts nach verschiedenen Fahrzeugkategorien. Zwanzig Mal pro Sekunde berechnet die Software, welche Aktion jedes Fahrzeug als Nächstes tun soll, und übermittelt diese Daten über eine Infrarotverbindung an das Auto. Weil die Verbindung regelmäßig abreißt, wenn das Fahrzeug durch ein Hindernis abgeschattet wird, wird im Miniatur Wunderland über alternative Kommunikationstechniken nachgedacht, etwa per Funk oder Induktion. Die Streckenführung der kleinen Autos erfolgt durch einen magnetisierten Draht unter der Fahrbahn; an Abzweigungen liegt unsichtbar eine Weiche unter der Fahrbahn, die der Computer schaltet.

Die Fahrzeugsteuerung kontrolliert alle Autos, Flugzeuge und Schiffe mit Infrarot-Signalsendern. Die Rückmeldung erfolgt über Kontakte in der Straße (Abb. 2).

Die Fahrzeugsteuerung kontrolliert alle Autos, Flugzeuge und Schiffe mit Infrarot-Signalsendern. Die Rückmeldung erfolgt über Kontakte in der Straße (Abb. 2).

In den Fahrzeugen befindet sich ein Mikroprozessor, der die Infrarotbefehle in Funktionen umsetzt. Unter anderem steuert er Fahrlicht, Bremslicht, Fahrtrichtungsanzeiger, Geschwindigkeit, Ladezustand des Akkus und einiges andere mehr. Die Software für die Steuerung der Fahrzeuge umfasst etwa 162.000 LoC.

Häuser, Straßen, Fahrzeuge, Start- und Landebahnen werden per LEDs beleuchtet. Insgesamt kommen dabei ungefähr 300.000 Miniatur-Leuchtkörper zusammen, die mit Software gesteuert sind. Allerdings nicht jede LED einzeln – viele Leuchten werden nach logischen Zusammenhängen gruppiert und zusammengefasst. Beispielsweise werden in den Verkehrsampeln die jeweils zusammengehörigen und einander gegenüber liegenden Rot-, Gelb- und Grünlichter gemeinsam angesteuert. Dennoch kommt die Software auf 20.000 separat anzusprechende LED-Gruppen. Außer den LEDs in der Modellanlage steuert das Programm die Beleuchtung in der Halle. Dabei wird der Tag-Nacht-Rhythmus im 15-Minuten-Takt durchfahren.

Anders als der Name vermuten lässt, ist das Kassenprogramm nicht nur für Ticketverkauf und Abrechnung zuständig. Weil es an Wochenenden und in der Ferienzeit zu teils erheblichen Wartezeiten vor dem Einlass kommen kann, steuert das Kassensystem auch die Bildschirme für das Informations- und Unterhaltungsprogramm in der Warteschlange. Selbst die Bildschirminformationen zur Steuerung der Besucherströme in den Wunderland-Hallen fallen in den Zuständigkeitsbereich der Kassensoftware, ebenso ein System zum Management von Ausnahmesituationen: Bei 1,2 Millionen Besuchern jährlich kommt es jeden Tag im Durchschnitt einmal zu einem medizinischen Notfall unter den Besuchern. Das Spektrum der Situationen, für die ein jeweils definiertes Eingreifen erforderlich ist, reicht vom Schwächeanfall in der Warteschlange am Eingang bis zur Totalevakuierung des Gebäudes im Brandfall. Für diese Fälle hält die Software entsprechende Handlungsanweisungen und Kontaktlisten bereit.

Das Zentralsystem steuert den Datenfluss zwischen den einzelnen Teilsystemen. Das Notfallsystem (links daneben) läuft auf jedem Rechner im Miniatur Wunderland (Abb. 3).

Das Zentralsystem steuert den Datenfluss zwischen den einzelnen Teilsystemen. Das Notfallsystem (links daneben) läuft auf jedem Rechner im Miniatur Wunderland (Abb. 3).

Wo sich bis zu 1000 Menschen gleichzeitig in einem geschlossenen Raum aufhalten und zudem elektrische Verbraucher mit etlichen Kilowatt Gesamtleistung aktiv sind, muss klimatisiert werden. Die Klimaanlage des Miniatur Wunderland wälzt pro Stunde bis zu 90.000 Kubikmeter Luft um. Software sorgt dafür, dass niemand ins Schwitzen gerät.

Die Softwarebestände auf den über den Campus verteilten Rechnern greifen, wo nötig, über einen globalen MySQL-Server auf die kaufmännischen und betrieblichen Serverdaten zu. Ein anderer Ansatz wurde bei der Modellanlage gewählt. Als zugekauftes Produkt bezieht auch die Railware-Software ihre Daten von einem globalen Datenbankserver unter Linux mit MySQL 5. Die SQL-Anbindung ist eine proprietäre Entwicklung speziell für die Belange des Miniatur Wunderland. Alle anderen Steuerungsprogramme jedoch arbeiten primär mit lokalen Daten. Die zugehörigen Parametersätze werden zur Sicherheit auch auf Hintergrundservern gespiegelt.

Zentrale Server liefern außerdem die virtuelle Uhrzeit im Spielzeugland, in dem der gesamte Tagesablauf mit allen Beleuchtungs- und Aktivitätsphasen innerhalb einer Viertelstunde nachgebildet wird. Dabei nutzen die Steuerungsprogramme der Modell-Landschaft einen Windows-Server, auf dem sie ihre Binärdateien ablegen.

Für die Echtzeitsteuerung der Fahrzeuge jedoch sind abschnittsweise lokale PCs zuständig, die sämtliche relevanten Sensordaten und Parameter im Hauptspeicher halten. Vom Einsatz von Datenbanken gleich welcher Architektur in den Echtzeit-Regelschleifen nimmt das Miniatur Wunderland aus Performancegründen Abstand, denn keine Datenbank bietet die notwendigen kurzen Antwortzeiten, um die Fahrzeuge sicher durch das Verkehrsgewühl zu steuern.

Eine wichtige Rolle spielt die Visualisierung der Vorgänge in der Modelllandschaft: Neben der eigentlichen Steuerung der Fahrzeuge, Flugzeuge und Beleuchtungseinrichtungen auf der Anlage erfolgt eine Ausgabe sämtlicher Signale, Fahrzeugpositionen und Zustände über mehrere Bildschirme. Besucher können sich so leicht einen Überblick verschaffen, aber die Programmierung der Grafikfunktionen erfordert einen hohen Aufwand. Das Miniatur Wunderland nutzt dazu unter anderem die GUI-Komponentenbibliothek FireMonkey. Alle weiteren Bibliotheken hat der Autor selbst geschrieben.

Die Embedded-Software, die auf den ARM-Mikroprozessoren in den Fahrzeugen und Flugzeugen läuft, nimmt in gewisser Weise eine Sonderstellung ein: Im Gegensatz zu allen anderen Softwarebestandteilen wurden diese kleinen, speicheroptimierten Programme nicht mit der Entwicklungsumgebung Delphi entwickelt und auch nicht in dem von Delphi erzeugten Object Pascal geschrieben. Die Entwickler des Wunderland-Teams nutzten stattdessen C, teilweise sogar Assembler, was für diese Art von Software ja nicht unüblich ist. In der Softwarelandschaft der kleinen Hamburger Megacity nimmt diese Embedded-Software jedoch nur einen vergleichsweise kleinen Raum ein, gemessen an der insgesamt implementierten Funktionsvielfalt.

Meist werden solche umfangreichen Softwarebestände wie im Miniatur Wunderland von großen Teams nach genau festgelegten Prozessen erarbeitet. Im Miniatur Wunderland ist das nicht ganz so. Der Autor hat das meiste selbst geschrieben. Verfahren, die in größeren Teams sicherstellen, dass jeder alles verstanden hat und alle Teilnehmer den gleichen Stand haben, sind im Miniatur Wunderland nicht nötig. Ein Requirements Management entfällt daher. Bei der Dokumentation der Software begnügt sich der Autor im Wesentlichen mit Inline-Kommentaren – eine Vorgehensweise, die in größeren Teams sicherlich so nicht möglich wäre. Ein geordneter Übergabeprozess ist lediglich im Zusammenwirken mit den Entwicklern der Embedded-Software für die Fahrzeuge etabliert.

Der Autor legte von Anfang an großen Wert auf die Wiederverwendbarkeit seiner Routinen. Im Lauf der Jahre ist eine umfangreiche Sammlung von Funktionsaufrufen entstanden, die sich fortwährend nutzen lassen. Die Prozeduren selbst wurden so konzipiert, dass sie sich weitgehend durch die Übergabe von Parametern anpassen lassen. Bei der Erstellung der Funktionen geht der Autor von der Benutzeroberfläche beziehungsweise der Bildschirmdarstellung aus und arbeitet sich dann nach und nach zu den dahinter liegenden Algorithmen vor. Neue Funktionen und Programmerweiterungen werden auf einem Entwicklungsrechner geschrieben und in einer speziellen Umgebung getestet. Hierzu wurde ein Simulator entwickelt, über den man sämtliche Funktionen testen kann. Alle Programme besitzen einen internen Flag, der den Testbetrieb aktiviert und über eine Schnittstelle die entsprechenden Daten ausgibt. Für das Debuggen werden die entsprechenden Funktionen von Delphi genutzt.

Auch für die Versionierung begnügt sich das Miniatur Wunderland mit Bordmitteln: Die Softwarestände werden auf einem Server gesichert, darüber hinaus greift der Autor auf die Versionsverwaltung von Delphi zurück.

Der Autor verwendet nur eine kleine Auswahl der Tools, die die Delphi-Community zur Unterstützung von Programmierern bereithält. Unumwunden zugegeben liegt das an der mangelnden Zeit, sich hinzusetzen, Tutorials anzusehen und in neue Tools einzuarbeiten. Probleme mit dem Entwicklungssystem oder der Umsetzung der Algorithmen hat er immer über die Foren im Delphi-Umfeld lösen können.

Die wichtigsten Gründe, warum Delphi über all die Jahre die Treue gehalten wurde, liegen in dessen Vielseitigkeit und Konstanz. So hat der Autor bisher für alle von ihm entwickelten technischen Funktionen die adäquate Unterstützung in den Funktionsbibliotheken von Delphi gefunden. Mindestens ebenso wichtig ist die verlässliche Unterstützung älterer Softwarebestände und selbst entwickelter Funktionsbibliotheken. So konnte das Wunderland die frühen Versionen seiner Software, die noch in Turbo Pascal entwickelt worden waren, problemlos unter neueren Ausgaben von Delphi übernehmen – selbst nach der Einführung von Object Pascal, das schon vor geraumer Zeit das leicht angegraute Turbo Pascal ersetzt hat.

Derart umfangreiche Softwarebestände lassen sich ohne hochwertige, die Produktivität steigernde Tools nicht realisieren. Delphi stellt die nötigen Werkzeuge bereit, um nicht nur große Programme zu schreiben, sondern auch welche mit komplexen Funktionen. Das gilt für den gesamten Entwicklungsprozess von der Konzeption über die Codegenerierung und den Test bis zum Debuggen. Das trifft auch die Versionsverwaltung zu, denn im Miniatur Wunderland wird ständig an Verbesserungen und neuen Funktionen gearbeitet.

Die Entwicklungsprozesse im Wunderland sind sicherlich in mancherlei Hinsicht etwas undogmatisch und lassen sich nicht unbeschränkt auf größere Entwicklungsteams übertragen. Aber die täglich nachprüfbare Tatsache, dass das alles reibungslos funktioniert, spricht unzweideutig für die Solidität der Software und auch der verwendeten Entwicklungsumgebung.

Gerrit Braun
ist Mitgründer und Mitbetreiber des Miniatur Wunderland in Hamburg.

Fast die gesamte Software des Miniatur Wunderlands wurde mit dem Pascal-System Delphi erzeugt:

Procedure TSchiffeSteuernThread.SchiffEinblendenImOriginalBild;
Var j:integer;
b2:tbitmap;
x,y:integer;
Breite,hoehe:integer;
VerschiebX,VerschiebY:integer;
cos1,
sin1,
cos2,
cos3,
cos4:double;
help:string;
Drehen:array[0..2] of tPoint;

begin
b2:=tbitmap.create;
b2.PixelFormat:=pf24Bit;
if SchiffeTMP[EinOderAuszublendeneNummer].GedrehtesBildMaske=NIL then
begin
SchiffeTMP[EinOderAuszublendeneNummer].GedrehtesBildMaske:
=tbitmap.create;
SchiffeTMP[EinOderAuszublendeneNummer].GedrehtesBildMaske.PixelFormat:
=pf24Bit;
end;
if SchiffeTMP[EinOderAuszublendeneNummer].BildVonOben=NIL then
begin
SchiffeTMP[EinOderAuszublendeneNummer].BildVonOben:=tbitmap.create;
SchiffeTMP[EinOderAuszublendeneNummer].BildVonOben.PixelFormat:
=pf24Bit;
help:=strint(Schiffe[EinOderAuszublendeneNummer].nr,0);
while length(help)<3 do
help:='0'+help;
if ifexist('Daten\bilder schiffe\'+help+'-o1.bmp') then
SchiffeTMP[EinOderAuszublendeneNummer].BildVonOben.
LoadFromFile('Daten\bilder schiffe\'+help+'-o1.bmp');
end;
if SchiffeTMP[EinOderAuszublendeneNummer].BildMaske=NIL then
begin
help:=strint(Schiffe[EinOderAuszublendeneNummer].nr,0);
while length(help)<3 do
help:='0'+help;
SchiffeTMP[EinOderAuszublendeneNummer].BildMaske:=tbitmap.create;
SchiffeTMP[EinOderAuszublendeneNummer].BildMaske.PixelFormat:=pf24Bit;
if ifexist('Daten\bilder schiffe\'+help+'-o2.bmp') then
SchiffeTMP[EinOderAuszublendeneNummer].BildMaske.
LoadFromFile('Daten\bilder schiffe\'+help+'-o2.bmp');
end;
{Zuerst die Breite des Quadrats, das das gedrehte Schiff einnimmt, ermitteln:}
cos1:=Cos(DegToRad(Schiffe[EinOderAuszublendeneNummer].Lage));
sin1:=sin(DegToRad(Schiffe[EinOderAuszublendeneNummer].Lage));
Breite:=SchiffeTMP[EinOderAuszublendeneNummer].BildVonOben.width;
Hoehe:=SchiffeTMP[EinOderAuszublendeneNummer].BildVonOben.height;
if ((Schiffe[EinOderAuszublendeneNummer].Lage>=0)
and (Schiffe[EinOderAuszublendeneNummer].Lage<=90)) or
((Schiffe[EinOderAuszublendeneNummer].Lage>180)
and (Schiffe[EinOderAuszublendeneNummer].Lage<=270)) then
begin
cos2:=Cos(DegToRad(90-Schiffe[EinOderAuszublendeneNummer].Lage));
x:=runden(Breite*Cos1+Hoehe*Cos2);
y:=runden(Hoehe*Cos1+Breite*Cos2);
end
else
begin
cos3:=Cos(DegToRad(90-Schiffe[EinOderAuszublendeneNummer].Lage));
cos4:=Cos(DegToRad(180-Schiffe[EinOderAuszublendeneNummer].Lage));
y:=runden(Breite*Cos3+Hoehe*Cos4);
x:=runden(Hoehe*Cos3+Breite*Cos4);
end;
{jetzt die Drehpunkte ermitteln (oben links, oben rechts, unten links)}
Drehen[0].X:=0;
Drehen[0].Y:=0;
Drehen[1].X:=runden(Breite*Cos1);
Drehen[1].Y:=runden(Breite*Sin1);
Drehen[2].X:=-runden(Hoehe*Sin1);
Drehen[2].Y:=runden(Hoehe*Cos1);
{die genauer Positionierung innerhalb des Rechteckes ermitteln}
if Schiffe[EinOderAuszublendeneNummer].Lage<=90 then
begin
Verschiebx:=-Drehen[2].x;
Verschieby:=0;
end
else if Schiffe[EinOderAuszublendeneNummer].Lage<=180 then
begin
Verschiebx:=x;
Verschieby:=-Drehen[2].y;
end
else if Schiffe[EinOderAuszublendeneNummer].Lage<=270 then
begin
Verschiebx:=-Drehen[1].x;
Verschieby:=-y;
end
else {if Schiffe[EinOderAuszublendeneNummer].Lage<=360 then }
begin
Verschiebx:=0;
Verschieby:=-Drehen[1].y;
end;
for j:=0 to 2 do
begin
Drehen[j].X:=Drehen[j].X+verschiebx;
Drehen[j].Y:=Drehen[j].y+verschieby;
end;
Breite:=abs(x);
Hoehe:=abs(y);
b2.Width:=Breite;
b2.Height:=Hoehe;
{Maske in GedrehtesBild.Maske reindrehen}
SchiffeTMP[EinOderAuszublendeneNummer].GedrehtesBildMaske.Width:=Breite;
SchiffeTMP[EinOderAuszublendeneNummer].GedrehtesBildMaske.Height:=Hoehe;
BitBlt(SchiffeTMP[EinOderAuszublendeneNummer].GedrehtesBildMaske.
canvas.handle,0,0,Breite,Hoehe,SchiffeTMP
[EinOderAuszublendeneNummer].GedrehtesBildMaske.
canvas.handle, 0, 0, WHITENESS);
PlgBlt(SchiffeTMP[EinOderAuszublendeneNummer].GedrehtesBildMaske.
Canvas.Handle,Drehen[0],SchiffeTMP
[EinOderAuszublendeneNummer].BildMaske.Canvas.Handle,
0,0,SchiffeTMP[EinOderAuszublendeneNummer]
.BildMaske.Width,SchiffeTMP[EinOderAuszublendeneNummer]
.BildMaske.Height,0,0,0);
{Maske auf Hauptbildausschnitt setzen}
BitBlt(QuellbildMitSchiffen.canvas.handle,runden(Schiffe
[EinOderAuszublendeneNummer].PositionX/10-Breite/2),
runden(Schiffe[EinOderAuszublendeneNummer]
.PositionY/10-Hoehe/2), Breite, Hoehe,
SchiffeTMP[EinOderAuszublendeneNummer]
.GedrehtesBildMaske.canvas.handle, 0, 0, SRCand);

{BildVonOben in B2 reindrehen}
B2.Canvas.Brush.Color:=clblack;
B2.Canvas.Pen.Color:=clblack;
B2.Canvas.Rectangle(0,0,B2.Width,B2.Height);
PlgBlt(b2.Canvas.Handle,Drehen[0],SchiffeTMP[EinOderAuszublendeneNummer]
.BildVonOben.Canvas.Handle,
0,0,SchiffeTMP[EinOderAuszublendeneNummer].BildVonOben
.Width,SchiffeTMP[EinOderAuszublendeneNummer]
.BildVonOben.Height,0,0,0);

{Jetzt B2 drüber invertieren}
BitBlt(QuellbildMitSchiffen.canvas.handle,
runden(Schiffe[EinOderAuszublendeneNummer].positionx/10-Breite/2),

runden(Schiffe[EinOderAuszublendeneNummer].positiony/
10-Hoehe/2),Breite,Hoehe, b2.canvas.handle, 0,0,SRCInvert);

SchiffeTMP[EinOderAuszublendeneNummer].PositionBildx:
=runden(Schiffe[EinOderAuszublendeneNummer].PositionX/10-Breite/2);
SchiffeTMP[EinOderAuszublendeneNummer].PositionBildy:
=runden(Schiffe[EinOderAuszublendeneNummer].PositionY/10-Hoehe/2);
SchiffeTMP[EinOderAuszublendeneNummer].PositionBildBreite:=Breite;
SchiffeTMP[EinOderAuszublendeneNummer].PositionBildHoehe:=Hoehe;
b2.free;
end; (ane [1])

URL dieses Artikels:
https://www.heise.de/-1921899

Links in diesem Artikel:
[1] mailto:ane@heise.de