Basissignale hinzufügen

This commit is contained in:
Nero 2022-04-08 19:49:24 +00:00
parent db03bc0307
commit 5e1476e385
5 changed files with 308 additions and 2 deletions

View File

@ -24,7 +24,53 @@ Die Einbindung vom Anlagenscript aus erfolgt mittels `require("kskit")`.
Findet EEP die Dateien von Lua nicht, wird im Ereignisfenster eine Liste von Orten ausgegeben, an denen die Dateien gesucht wurden.
Die Orte sind in diesem Fall mit dem Installationsort abzugleichen.
## Allgemeine Lua-Kniffe
## Allgemeine Hauptsignale
Damit KsKit ein Modell als Hauptsignal ansteuern kann, muss das Signalmodell über eine funktionierende Haltstellung verfügen.
Trotz "Ks" im Namen ist KsKit in der Lage, Signalmodelle unabhängig vom Signalsystem anzusteuern.
Um ein Signal ohne besondere Funktion in KsKit einzutragen, wird die Funktion `Basissignal` verwendet:
```
Basissignal(39, 1)
```
Der erste Parameter ist die ID-Nummer des Signals, der zweite Parameter ist die Haltstellung.
Als Haltstellung verwenden die meisten Signalmodelle die 1, es gibt allerdings Ausnahmen.
Das Aufrufen dieser Funktion legt die notwendigen Callbacks und Funktionen für das Signal an.
Daher darf die Funktion nur einmal während der Initialisierung des Lua-Scriptes aufgerufen werden, nicht aus der EEPMain, MainFunktionen oder vergleichbaren heraus.
![Basissignal mit Anmeldekontakt (gelb) und Signalzugschlussstelle (rot)](img/basissignal.png)
### Signalzugschlussstalle
Für jedes Hauptsignal muss eine Signalzugschlussstelle existieren.
Als Signalzugschlussstelle dient ein Signalkontakt (rot), welcher nach der Überfahrt eines Signals dieses wieder auf den Haltbegriff zurücksetzt.
Dieser Kontaktpunkt befindet nach dem Signal am Ende des Durchrutschweges.
Die Bezeichnung als Zugschlussstelle ist historisch gewachsen, in Epoche V und VI löst die erste Achse des Zuges durch Befahren eines Gleisfreimeldeabschnittes die Rotschaltung aus.
Das Einstellen auf 'Zugschluss' ist daher nicht notwendig.
An dem Signalkontakt muss keine Lua-Funktion eingetragen werden.
Auch die Wirkrichtung muss nicht beschränkt werden, dies bietet sich aber an, damit man über das Kontakt-Symbol leichter erkennen kann, in welcher Richtung das dazugehörige Signal steht.
### Anmelde-Kontakt
Optional zu einem jeden Hauptsignal kann ein Anmeldekontakt verwendet werden.
Ein Anmeldekontakt kann ein Kontakt jeglicher Art sein, an dem die Lua-Funktion "Anmeldung_X" eingetragen wird.
Anstelle "X" wird die ID-Nummer des folgenden Signales eingetragen.
EEP überprüft bei dem Eintragen der Lua-Funktion, ob diese auch existiert, daher kann dieser Kontakt erst korrekt eingerichtet werden, nachdem EEP die `Basissignal`-Funktion ausgeführt hat, z.B. durch Wechsel in den 3D-Modus.
Der Anmelde-Kontakt sollte eingerichtet werden, wenn das vorherige Signal keine automatische Anmeldung durch Weiterschaltung des Zugnamens durchgeführt hat.
Dies trifft auch zu, wenn es kein vorheriges Signal gibt, weil es sich um eine Aufgleisstrecke, eine Ausfahrt eines virtuelles Depots oder um einen ungesicherten Anlagenbereich handelt.
Wird keine Anmeldung durchgeführt und fährt ein Zug unangemeldet an einem logischen Vorsignal vorbei, wird die Anmeldung am jeweiligen Hauptsignal nachgeholt.
Wer sich nicht daran stört, das Signale erst nach Überfahrt des Vorsignales auf Fahrt schalten, kann auf Anmeldungen generell verzichten.
## Lua-Kniffe
Bei der Entwicklung des Basisscriptes haben sich einige Mechanismen als sehr praktisch erwiesen, um die Komplexität des Codes im Zaum zu halten.

2
grapho

@ -1 +1 @@
Subproject commit 5196199acba962cc6c47f08b8b86428ae88e38d1
Subproject commit 733ce1b119ed12fc2c755b3e2647c6bcbd4efff4

BIN
img/basissignal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

120
init.lua
View File

@ -2,6 +2,9 @@
-- Ich weiss jetzt noch nicht, wie viele verschiedene Versionen von dem Script spaeter herumfliegen werden.
KsKitVer=0
-- Uebrige Konstanten
Vmax=400
-- Tabelle, in der alle Lambda-Funktionen gespeichert werden
-- Die Eintragung erfolgt durch die MainFunktion() vom Zielort aus.
MainFunktionTabelle={}
@ -37,3 +40,120 @@ function SignalFunktion(Signal, Funktion)
-- In unsere eigene Tabelle eintragen
table.insert(SignalFunktionen[Signal], Funktion)
end
-- Lua-Serializer und Deserializer laden
serpent=require("kskit/serpent")
-- Lua-Tabelle aus EEP Slot laden
function ladeTabelle(Slot)
local roh, geparst
ok, roh = EEPLoadData(Slot)
if not ok then return {} end
ok, geparst = serpent.load(roh)
if not ok then return {} end
if type(geparst) ~= "table" then return {} end
return geparst
end
-- Lua-Tabelle in EEP Slot speichern
function speicherTabelle(Slot, Tabelle)
EEPSaveData(Slot, serpent.line(Tabelle, {comment = false}))
end
SignalLesenFunktionen={}
-- Definiere ein Signal
-- Signal: ID-Nummer
-- Haltstellung: Nummer der Haltstellung, meistens 1
-- SchaltFunk: Callback, wird wiederholt aufgerufen, um Fahrt-Stellung des Signals zu erwirken
-- AbmeldeFunk: Wird nach Durchfahrt am Signal aufgerufen
function Basissignal(Signal, Haltstellung, SchaltFunk, AbmeldeFunk)
if EEPGetSignal(Signal)==0 then
print("Anlagen-Fehler: ID ",Signal," ist kein Signal")
return
end
-- Funktion, damit anderer Programmcode das Signal lesen kann
SignalLesenFunktionen[Signal]=function()
local Stellung = EEPGetSignal(Signal)
if Stellung == 0 or Stellung == Haltstellung then
return false, 0
end
return true, Vmax
end
-- Funktion fuer Kontaktpunkte: Zug an Signal anmelden
_G["Anmeldung_"..tostring(Signal)] = function(Zug)
local Anmeldung
Anmeldung = ladeTabelle(Signal)
if Anmeldung.train and Anmeldung.train ~= Zug then return end
Anmeldung.train = Zug
speicherTabelle(Signal, Anmeldung)
end
-- Auftragsstatus pollen
MainFunktion(function()
local Anmeldung = ladeTabelle(Signal)
-- Wenn Zug vor Signal steht, Anmeldung ggf. nachholen
if EEPGetSignalTrainsCount(Signal) > 0 then
if Anmeldung.train == nil then
Anmeldung.train = EEPGetSignalTrainName(Signal, 1)
speicherTabelle(Signal, Anmeldung)
end
-- Ankunftszeit merken, sobald Zug am Signal angekommen ist
-- Wird im if von EEPGetSignalTrainsCount gemacht,
-- damit nur Halte an diesem Signal zaehlen
if Anmeldung.arrival == nil then
ok, speed = EEPGetTrainSpeed(Anmeldung.train)
if math.abs(speed) < 5 then
Anmeldung.arrival = EEPTime
speicherTabelle(Signal, Anmeldung)
end
end
end
-- Nichts tun wenn kein Zug da
if Anmeldung.train == nil then return end
-- SchaltFunk
if SchaltFunk and EEPGetSignal(Signal) == Haltstellung then
r=SchaltFunk(Anmeldung.train)
if r and r > 0 and r ~= Haltstellung then
EEPSetSignal(Signal, r, 1)
end
end
end)
-- Reaktion auf Signal-Umstellungen
SignalFunktion(Signal, function(Stellung)
if Stellung == Haltstellung then
-- Wenn Signal auf Halt gestellt wurde, mache eine Abmeldung
if AbmeldeFunk then
local Anmeldung = ladeTabelle(Signal)
AbmeldeFunk(Anmeldung.train)
end
speicherTabelle(Signal, {})
end
end)
end
-- Funktion zum Erklaeren, was ein Signal gerade so tut
-- Nimmt die Nummer des Signals als Argument
-- Gibt einen Menschenlesbaren Text zurueck
function SignalBeschreibung(Signal)
local Anmeldung = ladeTabelle(Signal)
local Fahrt, V = SignalLesenFunktionen[Signal]()
Text = "Signal " .. tonumber(Signal) .. ": "
if Fahrt then
if V < Vmax then
Text = Text .. "Fahrt mit " .. tonumber(V) .. " Km/h\n"
else
Text = Text .. "Fahrt\n"
end
else
Text = Text .. "Halt\n"
end
if Anmeldung.train then
Text = Text .. "Zug angemeldet: " .. Anmeldung.train .. "\n"
end
if Anmeldung.arrival then
local Dauer = EEPTime - Anmeldung.arrival
if Dauer < 0 then Dauer = Dauer + 24*60*60 end
Text = Text .. "Ankunft vor " .. Dauer .. " Sekunden\n"
end
return Text
end

140
serpent.lua Normal file
View File

@ -0,0 +1,140 @@
local n, v = "serpent", "0.302" -- (C) 2012-18 Paul Kulchenko; MIT License
local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
local badtype = {thread = true, userdata = true, cdata = true}
local getmetatable = debug and debug.getmetatable or getmetatable
local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+
local keyword, globals, G = {}, {}, (_G or _ENV)
for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end
local function s(t, opts)
local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring
local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge)
local numformat = opts.numformat or "%.17g"
local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0
local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)",
-- tostring(val) is needed because __tostring may return a non-string value
function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end
local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s))
or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end
local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end
local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
local n = name == nil and '' or name
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
local safe = plain and n or '['..safestr(n)..']'
return (path or '')..(plain and path and '.' or '')..safe, safe end
local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding
local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end
table.sort(k, function(a,b)
-- sort numeric keys first: k[key] is not nil for numerical keys
return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
< (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
local function val2str(t, name, indent, insref, path, plainindex, level)
local ttype, level, mt = type(t), (level or 0), getmetatable(t)
local spath, sname = safename(path, name)
local tag = plainindex and
((type(name) == "number") and '' or name..space..'='..space) or
(name ~= nil and sname..space..'='..space or '')
if seen[t] then -- already seen this element
sref[#sref+1] = spath..space..'='..space..seen[t]
return tag..'nil'..comment('ref', level) end
-- protect from those cases where __tostring may fail
if type(mt) == 'table' and metatostring ~= false then
local to, tr = pcall(function() return mt.__tostring(t) end)
local so, sr = pcall(function() return mt.__serialize(t) end)
if (to or so) then -- knows how to serialize itself
seen[t] = insref or spath
t = so and sr or tr
ttype = type(t)
end -- new value falls through to be serialized
end
if ttype == "table" then
if level >= maxl then return tag..'{}'..comment('maxlvl', level) end
seen[t] = insref or spath
if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end
local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
for key = 1, maxn do o[key] = key end
if not maxnum or #o < maxnum then
local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end
if maxnum and #o > maxnum then o[maxnum+1] = nil end
if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end
local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output)
for n, key in ipairs(o) do
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing
or opts.keyallow and not opts.keyallow[key]
or opts.keyignore and opts.keyignore[key]
or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types
or sparse and value == nil then -- skipping nils; do nothing
elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then
if not seen[key] and not globals[key] then
sref[#sref+1] = 'placeholder'
local sname = safename(iname, gensym(key)) -- iname is table for local variables
sref[#sref] = val2str(key,sname,indent,sname,iname,true) end
sref[#sref+1] = 'placeholder'
local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']'
sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path))
else
out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1)
if maxlen then
maxlen = maxlen - #out[#out]
if maxlen < 0 then break end
end
end
end
local prefix = string.rep(indent or '', level)
local head = indent and '{\n'..prefix..indent or '{'
local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
local tail = indent and "\n"..prefix..'}' or '}'
return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level)
elseif badtype[ttype] then
seen[t] = insref or spath
return tag..globerr(t, level)
elseif ttype == 'function' then
seen[t] = insref or spath
if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end
local ok, res = pcall(string.dump, t)
local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level)
return tag..(func or globerr(t, level))
else return tag..safestr(t) end -- handle all other types
end
local sepr = indent and "\n" or ";"..space
local body = val2str(t, name, indent) -- this call also populates sref
local tail = #sref>1 and table.concat(sref, sepr)..sepr or ''
local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or ''
return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end"
end
local function deserialize(data, opts)
local env = (opts and opts.safe == false) and G
or setmetatable({}, {
__index = function(t,k) return t end,
__call = function(t,...) error("cannot call functions") end
})
local f, res = (loadstring or load)('return '..data, nil, nil, env)
if not f then f, res = (loadstring or load)(data, nil, nil, env) end
if not f then return f, res end
if setfenv then setfenv(f, env) end
return pcall(f)
end
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
load = deserialize,
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }