-- init code for LW timetable env -- stop, scheduled departure every -- d_int: Departure every n seconds (epoch modulo) -- d_off: Departure time offset function F.stop_sd(st_name, doors, departcommand, minstoptime, d_int, d_off) if event.train then local timenow = os.time() local timerdy = timenow + minstoptime local wait = d_int - ((timerdy-d_off) % d_int) local waitcorr = math.floor(wait*0.66) digiline_send("monitor", "Departure scheduled for: | "..os.date("%H:%M:%S", timenow+wait)) atc_send("B0 W O"..doors.." D"..waitcorr.." OCD1"..departcommand) else local timenow = os.time() digiline_send("monitor", "Time: "..os.date("%H:%M:%S", timenow)) end end function F.stop_sd_sched(st_name, doors, departcommand, minstoptime, d_int, d_off) depart = false if event.train then local time_now = rwt.now() local next_dep_time = rwt.next_rpt(rwt.add(time_now, minstoptime), d_int, d_off) digiline_send("monitor", "Departure scheduled for: | "..rwt.to_string(next_dep_time, true)) atc_set_text_inside(st_name.."\nDeparture: "..rwt.to_string(next_dep_time, true)) atc_send("B0 W O"..doors) schedule(next_dep_time, "depart") elseif event.schedule then atc_send("OCD1"..departcommand) digiline_send("monitor", "Last Departure: | "..rwt.to_string(rwt.now(), true)) atc_set_text_inside("") depart = true end end function F.timedisplay() digiline_send("time", "Time: | "..rwt.to_string(rwt.now(),true).." | "..os.date("%H:%M:%S")) schedule(rwt.next_rpt(rwt.now(),5,0), "") end -- Stat counter and timetaking utilities -- Stat from subway F.stat=function(line, init) --statistics -- init if init then reftrain = atc_id a_tbt = 30 a_tbtmax = 30 a_rtt = 500 a_not = 0 c_not = 0 c_tbtmax = 0 time_lt = os.time() time_rt=os.time() end if not a_tbtmax then a_tbtmax = 30 end if not c_tbtmax then c_tbtmax = 0 end --real code if event.train then local time = os.time() c_not = c_not + 1 a_tbt = (a_tbt + (time - time_lt)) / 2 c_tbtmax = math.max(c_tbtmax, (time - time_lt)) if atc_id == reftrain then a_rtt = (a_rtt*0.2 + (time - time_rt)*0.8) a_not = c_not c_not = 0 a_tbtmax = (a_tbtmax + c_tbtmax) / 2 c_tbtmax = 0 end digiline_send("stats", "Stat: "..line.. " NoT:"..a_not.."("..c_not..")".. " TbT:"..math.floor(a_tbt).."("..(time-time_lt)..")".. " Tmx:"..math.floor(a_tbtmax).."("..c_tbtmax..")".. " R:"..math.floor(a_rtt).."("..(time - time_rt)..")" ) time_lt = time if atc_id == reftrain then time_rt = time end end end S.timetake = {} function F.timetake_start(ttname) if not atc_id then return end local nouw = rwt.to_secs(rwt.now()) if not S.timetake[ttname] then S.timetake[ttname] = {} end S.timetake[ttname][atc_id] = nouw end --L100 function F.timetake_end(ttname) if not atc_id then return end if not S.timetake[ttname] or not S.timetake[ttname][atc_id] then digiline_send("timetake", "No start time for "..atc_id) return end local first = S.timetake[ttname][atc_id] local nouw = rwt.to_secs(rwt.now()) local tdiff = nouw - first local cavg = S.timetake[ttname].avg local cmax = S.timetake[ttname].max local cmin = S.timetake[ttname].min if cavg and cmax and cmin then S.timetake[ttname].avg = tdiff*0.1 + cavg*0.9 S.timetake[ttname].min = math.min(tdiff, cmin) S.timetake[ttname].max = math.max(tdiff, cmax) else S.timetake[ttname].avg = tdiff S.timetake[ttname].min = tdiff S.timetake[ttname].max = tdiff end digiline_send("timetake", ttname.. " this:"..tdiff.. " min:"..math.floor(S.timetake[ttname].min).. " avg:"..math.floor(S.timetake[ttname].avg).. " max:"..math.floor(S.timetake[ttname].max) ) end --== Timetable prototype (TTP) === --[[ table structures: F.ttp - static timetable data - see below S.ttp[tt_name] = { - dynamic tt data recording_train = <id of the train that is recording travel times, or nil> travel_times = { <station name> = <time in seconds from initial departure at first station of the line to departure at this station> } station_order = { <station 1>, <station 2>...} } S.ttt[train_id] = { - trains timetable = <timetable ID that the train is currently using>, initial_dep = <departure at first station of the line>, location = <Station where the train was last seen>, desired_dep = <Departure time as in timetable>, planned_dep = <real departure time calculated as the train reaches station>, actual_dep = <actual departure time at last station. is nil while train is stopped>, last_delay = <last known delay of the train - calculated every departure>, } } ]] local STOP_TIME = 10 local DEPCMD="A1OCD1SM" local RDEPCMD="RA1OCD1SM" if not S.ttp then S.ttp = {} end if not S.ttt then S.ttt = {} end F.ttp={ CFE_N = { outside_text = "[CFE] Warmoneaye\nvia Ehlodex, Personhood West, Crystal Farms", inside_line_desc = "CFE to Warmoneaye", stn_display = "CFE Warmoneaye ", }, CFE_S = { outside_text = "[CFE] Origin\nvia Crystal Farms, Personhood West, Ehlodex", inside_line_desc = "CFE to Origin", stn_display = "CFE Origin ", }, NRG_E = { outside_text = "[NRG] Azena Transirejo", inside_line_desc = "NRG to Azena Transirejo", stn_display = "NRG Azena Trans.", }, NRG_W = { outside_text = "[NRG] New Roses Gardens", inside_line_desc = "NRG to New Roses Gardens", stn_display = "NRG N.Roses Gdns", }, NX_S = { outside_text = "[NX] Trisiston\nvia Personhood West, Ehlodex, South Forest, Melinka", inside_line_desc = "NX to Trisiston", stn_display = "NX Trisiston ", }, E1_S = { outside_text = "[E1] Melinka\nvia The Cube, Ehlodex, Spawn Main, Mom Junction", inside_line_desc = "E1 to Melinka", stn_display = "E1 Melinka ", }, S12_S = { outside_text = "[S12] Ehlodex\nvia Gardon St., Schwarzschild St., Anju Crossing, Lesnoi", inside_line_desc = "S12 to Spawn Main", stn_display = "S12 Spawn Main ", }, } --[[ Timetable entry point. The train finalizes its last timetable and registers itself on the given timetable instance. It departs at the next time slot (given by interval and offset). F.ttp_begin({ stn = "Warmoneaye", -- station name tt = "CFE_S", -- timetable ID depint = "05;00", --departure slot interval depoff = "00;00", --departure slot offset doorside = "L", reverse = true, only_lines = nil, --if given a table, only trains where only_lines[get_line()] is true are considered force_tt_reset = false, -- force reset of travel times for this timetable }) ]] -- Make train depart at the next time slot, and save its start time function F.ttp_begin(p) __approach_callback_mode = 1 if not F.ttp[p.tt] then error("No TT instance "..p.tt) end if not atc_id or not atc_arrow then return end if p.only_lines and not p.only_lines[get_line()] then return end if not S.ttp[p.tt] then S.ttp[p.tt] = {} end local tti = S.ttp[p.tt] --L150 if event.approach and not event.has_entered then -- make the train stop atc_set_ars_disable(true) atc_set_lzb_tsr(2) atc_set_text_inside("Next stop: "..p.stn.."\nTerminal Station.\nThis train continues as "..F.ttp[p.tt].inside_line_desc) end if event.train then -- train arrived, planning departure atc_send("B0 W O"..p.doorside) local time_now = rwt.now() -- Train might have had another TT before, do the cleanup from ttp_end here. local trno = S.ttt[atc_id] if trno then local ttio = S.ttp[trno.timetable] if ttio.recording_train == atc_id then ttio.travel_times[p.stn] = rwt.diff(trno.initial_dep, time_now) ttio.station_order[#ttio.station_order+1] = p.stn end end local next_dep_time = rwt.next_rpt(rwt.add(time_now, 10), p.depint, p.depoff) schedule(next_dep_time, "departure") S.ttt[atc_id] = { timetable = p.tt, initial_dep = next_dep_time, location = p.stn, desired_dep = next_dep_time, planned_dep = next_dep_time, last_delay = 0, } -- if no travel times are available yet, set this train as recording if not tti.travel_times or p.force_tt_reset then tti.travel_times = {} tti.station_order = {p.stn} tti.recording_train = atc_id elseif tti.recording_train == atc_id then tti.recording_train = nil end atc_set_text_outside(F.ttp[p.tt].outside_text) atc_set_text_inside(p.stn.."\nAa: " ..rwt.to_string(time_now, true).." Da: " ..rwt.to_string(next_dep_time, true)) end if event.schedule then -- departure. save actual departure time in tt if S.ttt[atc_id] then -- failsafe: if entry is deleted externally somehow, train just departs and is not tracked by tt (makes resetting S.ttt possible) S.ttt[atc_id].actual_dep = rwt.now() local delay = rwt.diff(S.ttt[atc_id].desired_dep, S.ttt[atc_id].actual_dep) atc_set_text_inside(F.ttp[p.tt].inside_line_desc .."\nDelay:"..rwt.to_string(delay, true)) S.ttt[atc_id].last_delay = delay end if p.reverse then atc_send(RDEPCMD) else atc_send(DEPCMD) end end end --[[ Generic stop on timetable. Any train that has a TT instance registered stops here, waits STOP_TIME and continues. Behavior can be altered by options: F.ttp_stop({ stn = "Personhood West", -- station name doorside = "L", only_lines = nil, --if given a table, only trains where only_lines[get_line()] is true are considered end_of_tt = { TT_ID = true }, -- if present and key is true for a TT identifier, this is the last station on this timetable. Trains will stop recording timetable and be deregistered. departure = { TT_ID = RWT relative to initial departure }, -- If present, override desired departure time. Defaults to travel time + STOP_TIME if not provided no_disable_ars = nil, -- if true, does not disable ARS on approach (used for example at INTERCAL) }) ]]function F.ttp_stop(p) -- set my approach callback mode __approach_callback_mode = 1 if not atc_id or not atc_arrow then return end if not S.ttt[atc_id] then return end if p.only_lines and not p.only_lines[get_line()] then return end local trn = S.ttt[atc_id] local tt = trn.timetable if not F.ttp[tt] then S.ttt[atc_id] = nil end local tti = S.ttp[tt] if event.approach and not event.has_entered then -- make the train stop if not p.no_disable_ars then atc_set_ars_disable(true) end atc_set_lzb_tsr(2) atc_set_text_inside("Next stop: "..p.stn) end if event.train then -- train arrived, planning departure atc_send("B0 W O"..p.doorside) local time_now = rwt.now() -- update our location and determine desired and planned departure --L200 local next_dep_time = rwt.add(time_now, STOP_TIME) trn.location = p.stn trn.desired_dep = nil trn.actual_dep = nil -- calculate desired departure nouw if p.departure and p.departure[tt] then trn.desired_dep = rwt.add(trn.initial_dep or 0, p.departure[tt]) elseif tti.travel_times[p.stn] then trn.desired_dep = rwt.add(trn.initial_dep or 0, tti.travel_times[p.stn] + STOP_TIME) end if trn.desired_dep then -- if we had a source for desired departure, update planned daparture time if rwt.to_secs(next_dep_time) < rwt.to_secs(trn.desired_dep) then -- don't depart before the planned departure time next_dep_time = trn.desired_dep end atc_set_text_inside(p.stn.."\nAa " ..rwt.to_string(time_now, true).." Dd" ..rwt.to_string(trn.desired_dep, true).." Da" ..rwt.to_string(next_dep_time, true)) local delay = rwt.diff(trn.desired_dep, next_dep_time) trn.last_delay = delay else atc_set_text_inside(p.stn.."\nAa " ..rwt.to_string(time_now, true).." Dd ? Da" ..rwt.to_string(next_dep_time, true)) end if tti.recording_train == atc_id then -- we are recording. save travel time tti.travel_times[p.stn] = rwt.diff(trn.initial_dep or 0, time_now) tti.station_order[#tti.station_order+1] = p.stn atc_set_text_inside(p.stn.."\nRec TT " ..rwt.to_string(tti.travel_times[p.stn], true).." Da" ..rwt.to_string(next_dep_time, true)) end trn.planned_dep = next_dep_time schedule(next_dep_time, "departure") end if event.schedule then -- departure. save actual departure time in tt trn.actual_dep = rwt.now() local delay = rwt.diff(trn.desired_dep or trn.actual_dep, trn.actual_dep) atc_set_text_inside(F.ttp[tt].inside_line_desc .."\nDelay:"..rwt.to_string(delay, true)) S.ttt[atc_id].last_delay = delay atc_send(DEPCMD) if p.end_of_tt and p.end_of_tt[tt] then -- end of timetable. Deregister train if tti.recording_train == atc_id then tti.recording_train = nil end S.ttt[atc_id] = nil end end end function F.ttp_info_times(tt, starttime) --L307 local ttf = F.ttp[tt] local tti = S.ttp[tt] local p = {} if tti.recording_train then p[#p+1] = ("recording "..tti.recording_train) end p[#p+1] = ("Di "..rwt.to_string(starttime, true).." "..tti.station_order[1]) for i=2,#tti.station_order do local ap = rwt.add(starttime, tti.travel_times[tti.station_order[i]]) p[#p+1] = ("Ap "..rwt.to_string(ap, true).. " Dp "..rwt.to_string(rwt.add(ap, STOP_TIME), true).. " "..tti.station_order[i]) end return p end function F.ttp_info_trains(tt, starttime) --L307 local ttf = F.ttp[tt] local tti = S.ttp[tt] local p = {} for tid,trn in pairs(S.ttt) do if trn.timetable==tt then if trn.actual_dep then p[#p+1] = ("Trn "..tid.. " after "..trn.location.. " Dd "..rwt.to_string(trn.desired_dep, true).. " Da "..rwt.to_string(trn.actual_dep, true).. " Delay "..rwt.to_string(trn.last_delay)) else p[#p+1] = ("Trn "..tid.. " at "..trn.location.. " Dd "..rwt.to_string(trn.desired_dep, true).. " Delay "..rwt.to_string(trn.last_delay)) end end end return p end --[[F.ttp_station_display({ lines = {"CFE_S", "NX_S", "E1_S"}, departure = {}, station = "The Cube", title = "The Cube (Track 2)", interval = 30, display1 = "display1", display2 = "display2", }]] function F.ttp_station_display(p) --L425 -- { dep, text } local next_trains = {} local function is_past_station(tstn, stnorder) for _,s in ipairs(stnorder) do if s==p.station then return true end if s==tstn then return false end end return true end local function add_train(deptime, line, train, tid) local tent = {dep = deptime, text = rwt.to_string(deptime,true).." "..F.ttp[line].stn_display .." +"..train.last_delay} for i,ntrn in ipairs(next_trains) do if rwt.diff(ntrn.dep, deptime)<0 then table.insert(next_trains, i, tent) return end end table.insert(next_trains, tent) end for _,line in ipairs(p.lines) do local fttp = F.ttp[line] local sttp = S.ttp[line] -- find all trains on this line for id, train in pairs(S.ttt) do if train.timetable == line then if train.location == p.station and not train.actual_dep then -- the train is currently standing at this station add_train(train.planned_dep, line, train, id) elseif not is_past_station(train.location, sttp.station_order) then -- train is still approaching, calculate arrival time local trav_dep = rwt.add(train.initial_dep, (sttp.travel_times[p.station] or 0) + STOP_TIME) local act_dep = rwt.add(trav_dep, train.last_delay) if p.departure and p.departure[line] then local plan_dep = rwt.add(train.initial_dep, p.departure[line]) if rwt.to_secs(act_dep) < rwt.to_secs(plan_dep) then act_dep = plan_dep end end add_train(act_dep, line, train, id) end end end end -- make output local i local text1 = p.title .. " * "..rwt.to_string(rwt.now(), true).." * " for i=1,3 do if next_trains[i] then text1 = text1 .. "\n".. next_trains[i].text end end digiline_send(p.display1, text1) if p.display2 then local text2 = "" for i=4,7 do if next_trains[i] then text2 = text2 .. next_trains[i].text .. "\n" end end digiline_send(p.display2, text2) end --if not p.notimer then -- schedule_in(p.interval or 30,"foo") --end end