353 lines
13 KiB
Markdown
Executable File
353 lines
13 KiB
Markdown
Executable File
---
|
|
author: Nero
|
|
title: KsKit für Eisenbahn.exe
|
|
lang: 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](https://github.com/nero/kskit/archive/refs/heads/master.zip) 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
|
|
```
|