1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
-- lzb.lua
-- Enforced and/or automatic train override control, providing the on_train_approach callback
--[[
Documentation of train.lzb table
train.lzb = {
trav_index = Current index that the traverser has advanced so far
checkpoints = table containing oncoming signals, in order of index
{
pos = position of the point
index = where this is on the path
speed = speed allowed to pass. nil = no effect
callback = function(pos, id, train, index, speed, lzbdata)
-- Function that determines what to do on the train in the moment it drives over that point.
-- When spd==0, called instead when train has stopped in front
-- nil = no effect
lzbdata = {}
-- Table of custom data filled in by approach callbacks
-- Whenever an approach callback inserts an LZB checkpoint with changed lzbdata,
-- all consecutive approach callbacks will see these passed as lzbdata table.
}
trav_lzbdata = currently active lzbdata table at traverser index
}
each step, for every item in "oncoming", we need to determine the location to start braking (+ some safety margin)
and, if we passed this point for at least one of the items, initiate brake.
When speed has dropped below, say 3, decrease the margin to zero, so that trains actually stop at the signal IP.
The spd variable and travsht need to be updated on every aspect change. it's probably best to reset everything when any aspect changes
]]
local params = {
BRAKE_SPACE = 10,
AWARE_ZONE = 50,
ADD_STAND = 2.5,
ADD_SLOW = 1.5,
ADD_FAST = 7,
ZONE_ROLL = 2,
ZONE_HOLD = 5, -- added on top of ZONE_ROLL
ZONE_VSLOW = 3, -- When speed is <2, still allow accelerating
DST_FACTOR = 1.5,
SHUNT_SPEED_MAX = advtrains.SHUNT_SPEED_MAX,
}
function advtrains.set_lzb_param(par, val)
if params[par] and tonumber(val) then
params[par] = tonumber(val)
else
error("Inexistant param or not a number")
end
end
local function resolve_latest_lzbdata(ckp, index)
local i = #ckp
local ckpi
while i>0 do
ckpi = ckp[i]
if ckpi.index <= index and ckpi.lzbdata then
return ckpi.lzbdata
end
end
end
local function look_ahead(id, train)
local acc = advtrains.get_acceleration(train, 1)
local vel = train.velocity
local brakedst = ( -(vel*vel) / (2*acc) ) * params.DST_FACTOR
local brake_i = advtrains.path_get_index_by_offset(train, train.index, brakedst + params.BRAKE_SPACE)
--local aware_i = advtrains.path_get_index_by_offset(train, brake_i, AWARE_ZONE)
local lzb = train.lzb
local trav = lzb.trav_index
-- retrieve latest lzbdata
local lzbdata = lzb.trav_lzbdata
if lzbdata.off_track then
--previous position was off track, do not scan any further
end
while trav <= brake_i do
local pos = advtrains.path_get(train, trav)
-- check offtrack
if trav - 1 == train.path_trk_f then
lzbdata.off_track = true
advtrains.lzb_add_checkpoint(train, trav - 1, 0, nil, lzbdata)
else
-- run callbacks
-- Note: those callbacks are defined in trainlogic.lua for consistency with the other node callbacks
advtrains.tnc_call_approach_callback(pos, id, train, trav, lzb.trav_lzbdata)
end
trav = trav + 1
end
lzb.trav_index = trav
end
-- Flood-fills train.path_speed, based on this checkpoint
local function apply_checkpoint_to_path(train, checkpoint)
if not checkpoint.speed then
return
end
-- make sure path exists until checkpoint
local pos = advtrains.path_get(train, checkpoint.index)
local brake_accel = advtrains.get_acceleration(train, 11)
-- start with the checkpoint index at specified speed
local index = checkpoint.index
local p_speed -- speed in path_speed
local c_speed = checkpoint.speed -- calculated speed at current index
while true do
p_speed = train.path_speed[index]
if (p_speed and p_speed <= c_speed) or index < train.index then
--we're done. train already slower than wanted at this position
return
end
-- insert calculated target speed
train.path_speed[index] = c_speed
-- calculate c_speed at previous index
advtrains.path_get(train, index-1)
local eldist = train.path_dist[index] - train.path_dist[index-1]
-- Calculate the start velocity the train would have if it had a end velocity of c_speed and accelerating with brake_accel, after a distance of eldist:
-- v0² = v1² - 2*a*s
c_speed = math.sqrt( (c_speed * c_speed) - (2 * brake_accel * eldist) )
index = index - 1
end
end
--[[
Distance needed to accelerate from v0 to v1 with constant acceleration a:
v1 - v0 a / v1 - v0 \ 2 v1^2 - v0^2
s = v0 * ------- + - * | ------- | = -----------
a 2 \ a / 2*a
]]
-- Removes all LZB checkpoints and restarts the traverser at the current train index
function advtrains.lzb_invalidate(train)
train.lzb = {
trav_index = atround(train.index),
trav_lzbdata = {},
checkpoints = {},
}
end
-- LZB part of path_invalidate_ahead. Clears all checkpoints that are ahead of start_idx
-- in contrast to path_inv_ahead, doesn't complain if start_idx is behind train.index, clears everything then
function advtrains.lzb_invalidate_ahead(train, start_idx)
if train.lzb then
local idx = atfloor(start_idx)
local i = 1
while train.lzb.checkpoints[i] do
if train.lzb.checkpoints[i].idx >= idx then
table.remove(train.lzb.checkpoints, i)
else
i=i+1
end
end
-- re-apply all checkpoints to path_speed
train.path_speed = {}
for _,ckp in train.lzb.checkpoints do
apply_checkpoint_to_path(train, ckp)
end
end
end
-- Add LZB control point
-- lzbdata: If you modify lzbdata in an approach callback, you MUST add a checkpoint AND pass the (modified) lzbdata into it.
-- If you DON'T modify lzbdata, you MUST pass nil as lzbdata. Always modify the lzbdata table in place, never overwrite it!
function advtrains.lzb_add_checkpoint(train, index, speed, callback, lzbdata)
local lzb = train.lzb
local pos = advtrains.path_get(train, index)
local lzbdata_c = nil
if lzbdata then
-- make a shallow copy of lzbdata
lzbdata_c = {}
for k,v in pairs(lzbdata) do lzbdata_c[k] = v end
end
local ckp = {
pos = pos,
index = index,
speed = speed,
callback = callback,
lzbdata = lzbdata_c,
}
table.insert(lzb.checkpoints, ckp)
apply_checkpoint_to_path(train, ckp)
end
advtrains.te_register_on_new_path(function(id, train)
advtrains.lzb_invalidate(train)
look_ahead(id, train)
end)
advtrains.te_register_on_invalidate_ahead(function(id, train)
advtrains.lzb_invalidate_ahead(train, start_idx)
end)
advtrains.te_register_on_update(function(id, train)
if not train.path or not train.lzb then
atprint("LZB run: no path on train, skip step")
return
end
look_ahead(id, train)
--apply_control(id, train)
end, true)
|