kskit/README.md

13 KiB
Executable File

author title lang
Nero KsKit für Eisenbahn.exe de

KsKit für Eisenbahn.exe

Ich hatte mir nicht lange nach EEP16 die Zusatz-Modelle V15NGK30267 und V15NGK30268 dazugekauft. Die darin enthaltenen Ks-Signale haben sehr viele Stellungen, mit denen sich komplexe Signalsituation wie die herabgestufte Signalisierung oder den Halbregelabstand realisieren lassen. Der Einbau auf meinen Anlagen war aber alles andere als einfach:

  • EEP16 kann nur einfache Signalverknüpfungen mit einer Bedingung und einer Folge. Das reicht nicht für Mehrabschnittssignale und auch nicht für die Verwendung der Zusatzanzeiger. Eine Lösung mit Schaltkringeln ist möglich, habe ich auch hinbekommen, war mir aber vom Bauaufwand wesentlich unangenehmer als die Callbacks dazu händisch zu schreiben. Im Bahnhofsvorfeld wird das auch mit den Callbacks sehr schwer überschaubar.
  • Damit die Mehrabschnittssignale auch für sie normale Signalbilder zeigen dürfen, müssen auch mehrere Abschnitte geschaltet sein. Bisherige Lua-Schaltungen für Fahrstrassen schalten lediglich einen einzelnen Abschnitt.

Daraus ergaben sich dann die zwei Kernbestandteile von KsKit:

  • Die Callback-Steuerung On: Ürsprünglich als Bequemlichkeitstool für schnelles Lua-Scripting entwickelt, hat sich dieses Modul in vielen meiner Experimente und Anlagen bewährt. Mit der späteren Erweiterung um die Multi-Funktionen kristallisierte sich eine Lösung für die teilweise sehr komplexen Verschaltungen von Ks-Signalen und deren Zusatzanzeigern.
  • Die Fahrstrassen-Logik: Eine mit dem RuS-Packet vergleichbare Schaltung, welche aber auch das Schalten von Fahrstrassenketten sicher beherrscht. Damit können an jedem Signal beliebig viele Folgeabschnitte abhängig voneinander geschaltet werden.

Alle Scripte können hier als Zip-Datei heruntergeladen werden, die lässt sich dann wie ein Modell installieren. Die Scripte werden unterhalb vom LUA-Ordner im EEP-Stammverzeichnis installiert. Demoanlagen sind auch mit drin, die benötigen aber teilweise das Ks-Signalset von GK3.

On-Modul

Bisher konnte an jedes Signal oder Weiche nur ein Callback gebunden werden. Das ist kein Problem, wenn man das Anlagenscript komplett selber schreibt. Sobald man aber mehrere Module hat, welche unabhängig voneinander Callbacks definieren, braucht man einen Mechanismus, um die alle unter einen Hut zu kriegen.

Als Lösung dafür erfolgt mit dem On-Modul ein Paradigmenwechsel. Anstelle den Callback direkt als Funktion zu definieren, wird der Callbackname und eine anonyme Funktion an das Modul übergeben.

Die bisherige Syntax für Callbacks und die EEPMain sah wie folgt aus:

EEPRegisterSignal(5)
function EEPOnSignal_5(Stellung)
  ...
end

function EEPMain()
  ...
end

Die Verwendung des On-Modules erfordert eine andere Syntax.

require("kskit\\On")

OnSignal(5, function(Stellung)
  ...
end)

Main(function()
  ...
end)

Die Callback-Funktion wird nicht mehr auf der globalen Ebene definiert, sondern als Argument an eine andere Funktion übergeben. Daher auch die sich schließende Klammer nach dem end - sie schließt den Funktionsaufruf von OnSignal/Main ab.

Das ist umständlich, das ist ungewohnt, ermöglicht aber einige Vorteile:

  • Die On-Funktionen können mehrmals aufgerufen werden, mehrere Funktionen an einem EEP Callback sind kein Problem
  • Module können unabhängig voneinander Code durch einen Callback ausführen lassen
  • Module können unabhängig voneinander ihre eigene EEPMain definieren

Ebenfalls werden zwei größere Fehlerquellen ausgeschlossen:

  • Der Aufruf von EEPRegisterSignal/EEPRegisterSwitch wird vom On-Module automatisch gemacht
  • Bei dem Anlegen von Signal/Weichencallbacks wird die Existenz dieser Objekte überprüft

Zum Beispiel können eine Vorsignalsteuerung und eine Türsteuerung beide einen Callback auf das Abfahrtssignal registrieren, ohne voneinander zu wissen zu müssen.

On(CallbackName, Funktion)

Bildet das Rückgrat dieses Modules. Diese Funktion wird im Hintergrund von allen anderen Funktionen des On-Moduls benutzt. In der Regel verwendet man eine der folgenden Funktionen.

  • Parameter 1: Name des Callbacks als String
  • Parameter 2: Callback-Code als Funktion
  • Rückgabewert: keiner

Ist Parameter 1 EEPOnSignal_x oder EEPOnSwitch_x, wird für das jeweilige x der Callback mittels EEPRegister... bei EEP registriert.

Der Funktion im Parameter 2 werden die Parameter übergeben, welche das On-Modul von EEP erhalten hat.

require("kskit\\On")

On("EEPOnTrainCoupling", function(Zug_A, Zug_B, Zug_neu)
  print("Aus "Zug_A.." und "..Zug_B.." wurde "..Zug_neu)
end)

Main(Funktion)

Bindet ein Stück Programmcode an die EEPMain.

  • Parameter 1: Der Code in Form einer Funktion
  • Rückgabewert: keiner

Beispielcode:

require("kskit\\On")
require("Zugtuersteuerung_FS2.lua")

Main(function()
  print(EEPTime)
end)

-- RUS-Packet von Parry36 aktivieren
Main(inEEPMain)
-- Zugtuersteuerung vom Fried aktivieren
Main(BewegeZugtueren)

OnSignal(SignalID, Funktion)

Bindet ein Stück Programmcode an einen Signal-Callback.

  • Parameter 1: Die ID des Signals als Zahl
  • Parameter 2: Der Code in Form einer Funktion
  • Rückgabewert: keiner

Der Funktion im Parameter 2 wird die Stellung des Signales als Parameter übergeben.

Ist ID kein Signal, wird eine Warnung im Ereignisfenster ausgegeben.

require("kskit\\On")

OnSignal(3, function(Stellung)
  print("Signal 3 zeigt "..tonumber(Stellung))
end)

OnSignal(3, function(Stellung)
  print("Signal umgestellt")
end)

OnSwitch(WeichenID, Funktion)

Bindet ein Stück Programmcode an einen Weichen-Callback. Das selbe wie OnSignal, nur für Weichen statt für Signale.

  • Parameter 1: Die ID der Weiche als Zahl
  • Parameter 2: Der Code in Form einer Funktion
  • Rückgabewert: keiner

Der Funktion im Parameter 2 wird die Stellung der Weiche als Parameter übergeben.

Ist ID keine Weiche, wird eine Warnung im Ereignisfenster ausgegeben.

MultiOn(IDs, Funktion)

Diese Funktion bindet ein Stück Code an mehrere Signale/Weichen.

  • Parameter 1: Eine Liste von Signal/Weichen-IDs als Tabelle mit Zahlen
  • Parameter 2: Der Code in Form einer Funktion
  • Rückgabewert: keiner

Vergleichbar mit OnSignal/OnSwitch, jedoch zwei große Unterschiede:

  • An Stelle einer ID wird eine Tabelle von IDs übergeben
  • Die IDs dürfen sich sowohl auf Weichen als auch auf Signale beziehen
  • Der Funktion in Parameter 2 werden alle Stellungen der Signale/Weichen als einzelne Parameter übergeben

Daraus folgt, das die Funktion in Parameter 2 genauso viele Parameter nehmen muss, wie IDs in Parameter 1 eingetragen sind.

Anwendungsbeispiel:

require("kskit\\On")

MultiOn({7,8,2}, function(HS, Zs3, Weiche)
  print("Hauptsignal zeigt ",HS,", Zs3 ",Zs3)
  print("Weiche ist auf ",Weiche," gestellt")
end)

Diese Funktion wurde entworfen, damit einzeln platzierbare Vorsignale auch im Weichenbereich vorbildnahe Begriffe zeigen können. Für diesen Anwendungsfall werden in der ID-Liste alle Fahrwegelemente angegeben, welche das Vorsignal beeinflussen können. Das sind z.b. Weichen, Zs3-Zusatzanzeiger von Hauptsignalen, einzeln stehende Zs3-Anzeiger und vor allem Hauptsignale. Im Funktionskörper können die Stellungen dann überprüft werden und abhängig davon das Vorsignal und mögliche Zs3v-Zusatzanzeiger gestellt werden. Ein Beispiel dafür (ohne Weichen, benötigt Ks-Signale von GK3) findet sich in der mitgelieferten Demo MultiOn_KS_Vorsignal_GK3.

Werden mehrere Fahrwegelemente gleichzeitig umgestellt (z.b. durch Fahrstrasse), werden die Callbacks der Einzelelemente als ein Aufruf zusammengefasst.

In Verbindung mit eingestellten Signalverzögerungen können die Stellungen der Fahrwegelemente falsche Werte enthalten. Dies passiert, wenn Elemente aus der Gruppe umgestellt werden, bevor die Signalverzögerungen aller Elemente abgelaufen sind. Dies ist eine Limitation der EEPGetSignal-Funktion.

MultiSchalten(Wenn, Dann)

Diese Funktion erstellt eine Signalverknüfung mit mehr als zwei Elementen.

  • Parameter 1: Bedingungs-Liste von Signal/Weichen-IDs und deren Stellungen
  • Parameter 2: Folgen-Liste von Signal/Weichen-IDs und deren Stellungen
  • Rückgabewert: keiner

Beide Parameter haben das gleiche Schema. {4,2,7,1} bedeutet Signal/Weiche 4 in Stellung 2 und Signal/Weiche 7 in Stellung 1. Der erste Parameter gibt die Stellungen an, welche erfüllt sein müssen. Ist dies der Fall, werden die Stellungen aus dem zweiten Parameter auf der Anlage angewendet.

Diese Funktion stellt einen häufigen Spezialfall von MultiOn dar: Blockabschnitte mit Mehrabschnittssignalen. Mehrabschnittssignale sind Hauptsignale mit Vorsignalfunktion. Wird ein Blockabschnitt freigeschaltet, wird das Blocksignal auf Halt erwarten gestellt, um das nächste Signal anzukündigen. Wird der Folgeabschnitt freigeschaltet, wird das Folgesignal ebenfalls auf Halt erwarten gestellt, und unser Blocksignal kann auf Fahrt gestellt werden, wenn es noch nicht Aufgrund der Zugdurchfahrt auf Halt zurückgefallen ist. Daraus ergeben sich zwei Bedingungen, beide Signale müssen Halt erwarten zeigen, damit das vorherige auf Fahrt gestellt werden darf.

Mit dieser Funktion geht das mit einem Einzeiler je Blockabschnitt:

require("kskit\\On")

-- 4 und 5 sind identische MA-Signale
-- 2 ist Halt erwarten, 3 ist Fahrt
MultiSchalten({4,2,5,2},{4,3})

Eine Demo mit AH1-Mehrabschnittsignalen (Grundbestand, denke ich) wird unter dem Namen MultiSchalten_MAS mitgeliefert.

Serializer: Tabellen in EEP-Slots speichern

Die Lua-Umgebung wird von EEP in bestimmten Situationen zurückgesetzt und verliert dabei die Inhalte aller Variablen. Daher müssen persistente Werte via EEPSaveData gespeichert und nach dem Reset wieder geladen werden. Bei Zeichenketten und Zahlen ist das kein Problem, das Speichern von Tabellen ist nicht ohne Umwege möglich.

KsKit bringt einen Serializer mit, der in der Lage ist, die gängigen Lua-Daten und Tabellen in eine Zeichenkette zu serialisieren.

serialize(Tabelle)

Die serialize-Funktion ist das Herz des Serializers. Sie nimmt ein Argument und gibt eine Zeichenkette zurück.

Der Return-Wert ist gültiges Lua und kann mittels load()-Funktion wieder in die Tabellenform zurückgewandelt werden.

require("kskit\\Serializer")

Tabelle={
  str = "abcdef",
  lst = {1,2,3},
  bol = true
}

print(serialize(Tabelle))
-- Ausgabe: {bol=true,lst={1,2,3},str="abcdef"}

Unterstützt werden Wahrheitswerte, Zeichenketten, Zahlen und einfach verschachtelte Tabellen. Lambda-Funktionen werden ignoriert, ebenso wie rekursiv in sich selbst verschachtelte Tabellen.

speicherTabelle(Slot, Tabelle)

Diese Funktion macht genau das, was der Name vermuten lässt. Eine Lua-Tabelle wird in einem EEPSlot abgespeichert.

Man beachte, das ein Slot maximal etwa 1000 Zeichen abspeichern kann, bei zu großen Tabellen werden die Slotdaten abgeschnitten und sind dann ungültig.

Das erste Argument zu der Funktion ist dabei die Slotnummer, das zweite Argument eine Lua-Tabelle.

Die Tabelle wird mittels serialize in eine Zeichenkette umgewandelt. Die EEPSlots unterstützen jedoch nicht alle möglichen Zeichen. Daher wird das Zwischenergebnis nochmal in ein urlencode-ähnliches Format umkonvertiert. Dabei werden sämtliche Steuerzeichen und Hochkommas sicher verpackt.

require("kskit\\Serializer")

Tabelle={
  str = "abcdef",
  lst = {1,2,3},
  bol = true
}

speicherTabelle(1, Tabelle)

-- So sieht der Datenslot hinterher in der Lua-Datei aus:
-- [EEPLuaData]
-- DS_1 = "{bol=true,lst={1,2,3},str=%22abcdef%22}"

ladeTabelle(Slot)

Das pendant zu speicherTabelle. Als Argument wird die Slotnummer übergeben, als Return-Wert erhält man die vorher gespeicherte Tabelle zurück.

Dabei wird das urlencode wieder entfernt und die Daten mittels load-Funktion wieder eingelesen.

Ist der Slot unleserlich oder wurde in diesen noch keine Tabelle geschrieben, wird eine Warnmeldung in das Ereignisfenster geschrieben und eine leere Tabelle zurückgegeben.

require("kskit\\Serializer")

print(serialize(ladeTabelle(1)))
-- Ausgabe: {bol=true,lst={1,2,3},str="abcdef"}

Praxisbeispiel

Es ist nicht unbedingt notwendig, eine Tabelle vor jeder Benutzung zu laden und wieder zu speichern.

Viel schneller ist es, die Tabelle als globale Variable zu halten und nur beim Lua-Start einmal einzulesen. Die Tabelle kann dann wie jede andere Tabelle verwendet werden.

Die EEPMain wird innerhalb eines Zyklus zuletzt aufgerufen. Die Kontakte und Callbacks werden davor abgearbeitet. Daher reicht es aus, wenn die Tabelle nur einmalig am Ende der EEPMain zurückgeschrieben wird.

require("kskit\\Serializer")

-- Die Tabelle wird nur beim Starten von Lua einmal geladen
Zugdaten_Slotnummer = 1
Zugdaten = ladeTabelle(Zugdaten_Slotnummer)

-- Diese Funktion wird in Kontakten eingetragen
function Richtung_Merken(Zugname)
  local ok, V = EEPGetTrainSpeed(Zugname)
  Zugdaten[Zugname].V = V
end

function Zug_Wenden(Zugname)
  local Vneu = -Zugdaten[Zugname].V
  EEPSetTrainSpeed(Zugname, Vneu)
  Zugdaten[Zugname].V = Vneu
end

function EEPMain()
  -- andere Dinge tun
  -- ...

  -- Wir sind am Ende des EEP-Zyklus, nur einmal hier speichern
  speicherTabelle(Zugdaten_Slotnummer, Zugdaten)
  return 1
end