From eba7b01888cf6383c4e986bc6d5f5d511c6566f7 Mon Sep 17 00:00:00 2001 From: cheapie Date: Fri, 20 Jan 2017 01:26:42 -0600 Subject: Convert interrupts to use node timers --- fw.lua | 66 +++++++++++++++++++++++++++++++++------ init.lua | 107 ++++++++++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 128 insertions(+), 45 deletions(-) diff --git a/fw.lua b/fw.lua index 31cdd45..f15d082 100644 --- a/fw.lua +++ b/fw.lua @@ -157,7 +157,7 @@ elseif was_timed then end --Detector signal handling -if (not mem.phaselocked and not mem.preempt) and event.type == "digiline" and string.sub(event.channel,1,9) == "detector_" then +if (not mem.phaselocked and not mem.preempt) and event.type == "digiline" and string.sub(event.channel,1,9) == "detector_" and not mem.stoptime then local detname = string.sub(event.channel,10) if mem.stats[detname] then mem.stats[detname] = mem.stats[detname] + 1 @@ -191,7 +191,7 @@ if (not mem.phaselocked and not mem.preempt) and event.type == "digiline" and st end end -if event.type == "digiline" and mem.pedbuttontype == 2 and event.channel == "pedbutton" then +if event.type == "digiline" and mem.pedbuttontype == 2 and event.channel == "pedbutton" and not mem.stoptime then if event.msg == "main" then log("Emulating detector_ap/cp for TrafficNeXt compatibility",true) mem.det.ap = true @@ -214,6 +214,8 @@ if event.type == "interrupt" and (event.iid == "gapout" or event.iid == "maxgree log("Reached maximum green",true) end --Either gapped out or reached max green, go to next step + interrupt(nil,"gapout") + interrupt(nil,"maxgreen") interrupt(0,"tick") end end @@ -224,7 +226,7 @@ if (event.channel == "detector_b" or event.channel == "detector_d") and (mem.cyc end --Preemption logic -if event.type == "digiline" and string.sub(event.channel,1,8) == "preempt_" then +if event.type == "digiline" and string.sub(event.channel,1,8) == "preempt_" and not mem.stoptime then log("Preemption detector activated",true) mem.preempt = string.sub(event.channel,9,10) log("Entering preemption on approach "..mem.preempt.." from state "..(mem.cycle or "idle"),true) @@ -251,7 +253,7 @@ if event.type == "digiline" and string.sub(event.channel,1,8) == "preempt_" then end --Phase logic for already-running cycles -if mem.busy and event.type == "interrupt" and event.iid == "tick" and not mem.phaselocked then +if mem.busy and event.type == "interrupt" and (event.iid == "tick" or event.iid == "manualtick") and not mem.phaselocked and (event.iid == "manualtick" or not mem.stoptime) then log("Continuing existing cycle at phase "..mem.cycle,true) if mem.cycle == "preempt_yellow" then for k,v in pairs(mem.currentphase) do @@ -580,7 +582,7 @@ end --Phase logic for starting new cycles detactive = false for _,_ in pairs(mem.det) do detactive = true end -if (not mem.busy) and detactive then +if (not mem.busy) and detactive and (not mem.stoptime) then if mem.phaselocked then log("Not starting cycle due to phase lock",true) elseif mem.preempt then @@ -656,6 +658,8 @@ if event.type == "digiline" and event.channel == "touchscreen" then mem.menu = "stats" elseif fields.mode then mem.menu = "mode" + elseif fields.diag then + mem.menu = "diag" end elseif mem.menu == "run" then if fields.menu then @@ -712,7 +716,7 @@ if event.type == "digiline" and event.channel == "touchscreen" then for _,v in pairs({"a","b","c","d","at","bt","ct","dt","ap","bp","cp","dp"}) do if fields[v] then mem.det[v] = true - interrupt(0) --Forces the program to be re-run since the cycle logic is up there ^^ + interrupt(0,"tick") --Forces the program to be re-run since the cycle logic is up there ^^ end end end @@ -770,7 +774,33 @@ if event.type == "digiline" and event.channel == "touchscreen" then mem.normalmode = pivot(modes)[fields.normalmode] mem.schedmode = pivot(modes)[fields.schedmode] mem.menu = "main" - interrupt(0) -- Some of these need to take immediate effect + interrupt(0,"rerun") -- Some of these need to take immediate effect + end + elseif mem.menu == "diag" then + if fields.cancel then + mem.menu = "main" + elseif fields.startstop then + if mem.stoptime then + mem.stoptime = false + interrupt(1,"tick") + else + mem.stoptime = true + interrupt(nil,"tick") + end + elseif fields.stepnow then + interrupt(0,"manualtick") + elseif fields.reboot then + mem.phaselocked = true + mem.cycle = nil + mem.menu = "reboot" + mem.busy = false + mem.preempt = nil + mem.stoptime = true + interrupt(10,"reboot") + interrupt(nil,"step") + interrupt(nil,"rerun") + interrupt(nil,"gapout") + interrupt(nil,"maxgreen") end else logfault("Unrecognized menu "..mem.menu,false) @@ -778,6 +808,14 @@ if event.type == "digiline" and event.channel == "touchscreen" then end end +if event.iid == "reboot" then + mem.phaselocked = false + mem.menu = "main" + mem.stoptime = false + mem.cycle = nil + interrupt(0,"tick") +end + --Light control signal sending if mem.phaselocked then mem.busy = false @@ -815,6 +853,7 @@ if mem.menu == "main" then table.insert(disp,{command="addbutton",X=7,Y=5,W=2,H=1,name="mancyc",label="Manual Call Entry"}) table.insert(disp,{command="addbutton",X=1,Y=7,W=2,H=1,name="stats",label="Statistics"}) table.insert(disp,{command="addbutton",X=4,Y=7,W=2,H=1,name="mode",label="Mode/Schedule"}) + table.insert(disp,{command="addbutton",X=7,Y=7,W=2,H=1,name="diag",label="Diagnostics"}) elseif mem.menu == "run" then table.insert(disp,{command="addlabel",X=0,Y=0,label=mem.name}) table.insert(disp,{command="addlabel",X=0,Y=1,label="Advanced Mesecons Devices LTC-4000E"}) @@ -901,8 +940,7 @@ elseif mem.menu == "log" then table.insert(disp,{command="addlabel",X=0,Y=1.5,label="No Faults"}) end elseif mem.menu == "monitoring" then - --interrupt(0.6,"monflash") - mem.monflash = true + interrupt(1,"monflash") local monitor_textures = {} monitor_textures.O = "streets_tl_off.png" @@ -1130,6 +1168,16 @@ elseif mem.menu == "mode" then table.insert(disp,{command="addfield",X=0.5,Y=5.5,W=2,H=1,name="schedend",label="Schedule End Hour",default=tostring(mem.schedend)}) table.insert(disp,{command="addbutton",X=3,Y=7,W=2,H=1,name="save",label="Save"}) table.insert(disp,{command="addbutton",X=6,Y=7,W=2,H=1,name="cancel",label="Cancel"}) +elseif mem.menu == "diag" then + table.insert(disp,{command="addlabel",X=0,Y=0,label="Diagnostics"}) + table.insert(disp,{command="addlabel",X=0,Y=1,label="State: "..(mem.cycle or "Idle")}) + table.insert(disp,{command="addbutton",X=0,Y=2,W=2,H=1,name="startstop",label=(mem.stoptime and "Start Time" or "Stop Time")}) + table.insert(disp,{command="addbutton",X=0,Y=3,W=2,H=1,name="stepnow",label="Next Step"}) + table.insert(disp,{command="addbutton",X=0,Y=5,W=2,H=1,name="reboot",label="Reboot"}) + table.insert(disp,{command="addbutton",X=0,Y=7,W=2,H=1,name="cancel",label="Back"}) +elseif mem.menu == "reboot" then + table.insert(disp,{command="addlabel",X=0,Y=0,label="Rebooting, please wait..."}) + table.insert(disp,{command="addlabel",X=0,Y=0.5,label="This will take about 10 seconds."}) else logfault("Unrecognized menu "..mem.menu,false) mem.menu = "run" diff --git a/init.lua b/init.lua index c45740e..e95a537 100644 --- a/init.lua +++ b/init.lua @@ -230,6 +230,43 @@ local function ts_on_digiline_receive(pos,msg) update_ts_formspec(pos,data) end +--Interrupt node timer stuff +local function getnextinterrupt(interrupts) + local nextint = 0 + for k,v in pairs(interrupts) do + if nextint == 0 or v < nextint then + nextint = v + end + end + if nextint ~= 0 then return(nextint) end +end + +local function getcurrentinterrupts(interrupts) + local current = {} + for k,v in pairs(interrupts) do + if v <= os.time() then + table.insert(current,k) + end + end + return(current) +end + +local function setinterrupt(pos,time,iid) + local meta = minetest.get_meta(pos) + local timer = minetest.get_node_timer(pos) + local interrupts = minetest.deserialize(meta:get_string("interrupts")) or {} + if time == nil then + interrupts[iid] = nil + else + interrupts[iid] = os.time()+time + end + local nextint = getnextinterrupt(interrupts) + if nextint then + timer:start(nextint-os.time()) + end + meta:set_string("interrupts",minetest.serialize(interrupts)) +end + --Load the (mostly unmodified) firmware local fw = loadfile(minetest.get_modpath("ltc4000e")..DIR_DELIM.."fw.lua") @@ -284,22 +321,9 @@ local function run(pos,event) end function context.interrupt(time,iid) - --Enforce a minimum interrupt time of half a second - time = math.max(time,0.5) - if iid == "gapout" then - --This one can have the time changed on-the-fly, so it has to be done with node timers - local timer = minetest.get_node_timer(pos) - if time then - timer:start(time) - else - timer:stop() - end - else - local event = {} - event.type = "interrupt" - event.iid = iid - minetest.after(time,run,pos,event) - end + --Enforce a minimum interrupt time of one second + if time ~= nil then time = math.max(time,1) end + setinterrupt(pos,time,iid) end --This is where the magic happens... @@ -316,6 +340,33 @@ local function run(pos,event) meta:set_string("mem",minetest.serialize(context.mem)) end +local function oninterrupt(pos) + local meta = minetest.get_meta(pos) + local timer = minetest.get_node_timer(pos) + local interrupts = minetest.deserialize(meta:get_string("interrupts")) or {} + local current = getcurrentinterrupts(interrupts) + for _,i in ipairs(current) do + interrupts[i] = nil + local event = {} + event.type = "interrupt" + event.iid = i + run(pos,event) + end + local interrupts = minetest.deserialize(meta:get_string("interrupts")) or {} --Reload as it may have changed + for _,i in ipairs(current) do + if interrupts[i] and interrupts[i] <= os.time() then + interrupts[i] = nil + end + end + local nextint = getnextinterrupt(interrupts) + if nextint then + timer:start(nextint-os.time()) + else + timer:stop() + end + meta:set_string("interrupts",minetest.serialize(interrupts)) +end + local function ts_on_receive_fields(pos,formname,fields,sender) local meta = minetest.get_meta(pos) local playername = sender:get_player_name() @@ -358,12 +409,7 @@ minetest.register_node("ltc4000e:polemount", { local event = {type="program"} run(pos,event) end, - on_timer = function(pos) - local event = {} - event.type = "interrupt" - event.iid = "gapout" - run(pos,event) - end, + on_timer = oninterrupt, node_box = { type = "fixed", fixed = polemount_nodebox @@ -466,12 +512,7 @@ minetest.register_node("ltc4000e:nema_bottom", { end,pos) return ret end, - on_timer = function(pos) - local event = {} - event.type = "interrupt" - event.iid = "gapout" - run(pos,event) - end, + on_timer = oninterrupt, on_punch = function(pos,node,puncher) if not puncher:is_player() then return @@ -615,12 +656,7 @@ minetest.register_node("ltc4000e:nema_bottom_open", { minetest.set_node(fronttoppos,{name="air"}) end, on_rotate = false, - on_timer = function(pos) - local event = {} - event.type = "interrupt" - event.iid = "gapout" - run(pos,event) - end, + on_timer = oninterrupt, on_punch = function(pos,node,puncher) if not puncher:is_player() then return @@ -749,12 +785,11 @@ minetest.register_node("ltc4000e:door_top", { sounds = default.node_sound_metal_defaults() }) ---Make sure lights don't "stall" if unloaded +--Make sure lights don't "stall" if unloaded and not yet converted to node timers minetest.register_lbm({ label = "Restart LTC-4000E timers", name = "ltc4000e:restart_timers", nodenames = {"ltc4000e:polemount","ltc4000e:nema_bottom","ltc4000e:nema_bottom_open"}, - run_at_every_load = true, action = function(pos) local meta = minetest.get_meta(pos) local mem = minetest.deserialize(meta:get_string("mem")) -- cgit v1.2.3