celevator-cd2025/controllerfw.lua

1342 lines
47 KiB
Lua

local pos,event,mem = ...
local changedinterrupts = {}
mem.messages = {}
local function send(carid,channel,message)
table.insert(mem.messages,{
carid = carid,
channel = channel,
message = message,
})
end
local function fault(ftype,fatal)
if fatal then mem.fatalfault = true end
if not mem.activefaults then mem.activefaults = {} end
if not mem.faultlog then mem.faultlog = {} end
if mem.activefaults[ftype] then return end
mem.activefaults[ftype] = true
table.insert(mem.faultlog,{ftype = ftype,timestamp = os.time()})
end
if not mem.drive.status then
fault("drivecomm",true)
mem.drive.status = {
apos = 0,
dpos = 0,
vel = 0,
maxvel = 0,
}
elseif mem.drive.status.fault then
fault("drive"..mem.drive.status.fault,true)
end
if mem.drive.state == "uninit" then
fault("driveuninit",true)
end
local juststarted = false
local modenames = {
normal = "Normal Operation",
uninit = "Uninitialized",
resync = "Position Sync - Floor",
bfdemand = "Position Sync - Terminal",
fault = "Fault",
stop = "Emergency Stop",
mrinspect = "Machine Room Inspection",
carinspect = "Car Top Inspection",
inspconflict = "Inspection Conflict",
fs1 = "Fire Service - Phase 1",
fs2 = "Fire Service - Phase 2",
fs2hold = "Fire Service - Phase 2 Hold",
indep = "Independent Service",
capture = "Captured",
test = "Test Mode",
}
local doorstates = {
open = "Open",
opening = "Opening",
closing = "Closing",
closed = "Closed",
testtiming = "Closed",
}
local faultnames = {
opentimeout = "Door Open Timeout",
closetimeout = "Door Close Timeout",
drivecomm = "Lost Communication With Drive",
driveuninit = "Drive Not Configured",
drivemetaload = "Drive Metadata Load Failure",
drivebadorigin = "Drive Origin Invalid",
drivedoorinterlock = "Attempted to Move Doors With Car in Motion",
driveoutofbounds = "Target Position Out of Bounds",
}
local function drivecmd(command)
table.insert(mem.drive.commands,command)
end
local function interrupt(time,iid)
mem.interrupts[iid] = time
changedinterrupts[iid] = true
end
local function getpos(pioffset)
local ret = 0
local searchpos = mem.drive.status.apos
if pioffset then searchpos = math.max(0,searchpos+0.5) end
for k,v in ipairs(mem.params.floorheights) do
ret = ret+v
if ret > searchpos then return k end
end
return mem.params.floorheights[#mem.params.floorheights]
end
local function gettarget(floor)
local target = 0
if floor == 1 then return 0 end
for i=1,floor-1,1 do
target = target+mem.params.floorheights[i]
end
return target
end
local function gotofloor(floor)
mem.carmotion = true
drivecmd({
command = "setmaxvel",
maxvel = mem.params.contractspeed
})
if gettarget(floor) ~= mem.drive.status.apos then
drivecmd({
command = "moveto",
pos = gettarget(floor)
})
end
interrupt(0,"checkdrive")
juststarted = true
end
local function getnextcallabove(dir,excludecurrent)
local start = (excludecurrent and getpos()+1 or getpos())
for i=start,#mem.params.floorheights,1 do
if not dir then
if mem.carcalls[i] then
return i,"car"
elseif mem.upcalls[i] then
return i,"up"
elseif mem.dncalls[i] then
return i,"down"
end
elseif dir == "up" then
if mem.carcalls[i] then
return i,"car"
elseif mem.upcalls[i] then
return i,"up"
end
elseif dir == "down" then
if mem.carcalls[i] then
return i,"car"
elseif mem.dncalls[i] then
return i,"down"
end
end
end
end
local function getnextcallbelow(dir,excludecurrent)
local start = (excludecurrent and getpos()-1 or getpos())
for i=start,1,-1 do
if not dir then
if mem.carcalls[i] then
return i,"car"
elseif mem.upcalls[i] then
return i,"up"
elseif mem.dncalls[i] then
return i,"down"
end
elseif dir == "up" then
if mem.carcalls[i] then
return i,"car"
elseif mem.upcalls[i] then
return i,"up"
end
elseif dir == "down" then
if mem.carcalls[i] then
return i,"car"
elseif mem.dncalls[i] then
return i,"down"
end
end
end
end
local function getlowestupcall()
for i=1,#mem.params.floornames,1 do
if mem.upcalls[i] then return i end
end
end
local function gethighestdowncall()
for i=#mem.params.floornames,1,-1 do
if mem.dncalls[i] then return i end
end
end
local function open()
mem.doorstate = "opening"
drivecmd({command = "open"})
interrupt(0.2,"checkopen")
interrupt(10,"opentimeout")
interrupt(nil,"closetimeout")
end
local function close()
mem.doorstate = "closing"
drivecmd({command = "close"})
interrupt(0.2,"checkclosed")
interrupt(10,"closetimeout")
interrupt(nil,"opentimeout")
end
mem.formspec = ""
local function fs(element)
mem.formspec = mem.formspec..element
end
if type(mem.groupupcalls) ~= "table" then mem.groupupcalls = {} end
if type(mem.groupdncalls) ~= "table" then mem.groupdncalls = {} end
if type(mem.swingupcalls) ~= "table" then mem.swingupcalls = {} end
if type(mem.swingdncalls) ~= "table" then mem.swingdncalls = {} end
if event.type == "program" then
mem.carstate = "uninit"
mem.editingfloor = 1
mem.doorstate = "closed"
mem.carmotion = false
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.screenpage = 1
mem.scrollfollowscar = true
mem.controllerstopsw = false
mem.controllerinspectsw = false
mem.cartopinspectsw = false
mem.capturesw = false
mem.testsw = false
mem.activefaults = {}
mem.faultlog = {}
mem.fatalfault = false
mem.fs1switch = false
mem.fs1led = false
mem.fs2sw = "off"
mem.indsw = false
mem.lightsw = true
mem.fansw = true
if not mem.params then
mem.state = "unconfigured"
mem.screenstate = "oobe_welcome"
mem.params = {
contractspeed = 1,
floorheights = {5,5,5},
floornames = {"1","2","3"},
doortimer = 5,
groupmode = "simplex",
mainlanding = 1,
}
end
elseif event.type == "ui" then
if mem.screenstate == "oobe_welcome" then
if event.fields.license then
mem.screenstate = "oobe_license"
elseif event.fields.next then
mem.screenstate = "oobe_groupmode"
end
elseif mem.screenstate == "oobe_license" then
if event.fields.back then
mem.screenstate = "oobe_welcome"
end
elseif mem.screenstate == "oobe_groupmode" then
if event.fields.back then
mem.screenstate = "oobe_welcome"
elseif event.fields.simplex then
mem.screenstate = "oobe_floortable"
mem.params.groupmode = "simplex"
elseif event.fields.group then
mem.screenstate = "oobe_dispatcherconnect"
mem.params.groupmode = "group"
end
elseif mem.screenstate == "oobe_dispatcherconnect" then
if event.fields.back then
mem.screenstate = "oobe_groupmode"
end
elseif mem.screenstate == "oobe_floortable" or mem.screenstate == "floortable" then
local exp = event.fields.floor and minetest.explode_textlist_event(event.fields.floor) or {}
if event.fields.back then
mem.screenstate = "oobe_groupmode"
elseif event.fields.next then
if mem.screenstate == "oobe_floortable" then
mem.activefaults = {}
mem.faultlog = {}
mem.fatalfault = false
end
mem.state = "configured"
mem.screenstate = (mem.screenstate == "oobe_floortable" and "status" or "parameters")
mem.screenpage = 1
mem.carstate = "bfdemand"
if mem.doorstate == "closed" then
drivecmd({
command = "setmaxvel",
maxvel = mem.params.contractspeed,
})
drivecmd({command = "resetpos"})
interrupt(0.1,"checkdrive")
mem.carmotion = true
juststarted = true
else
close()
end
elseif exp.type == "CHG" then
mem.editingfloor = #mem.params.floornames-exp.index+1
elseif exp.type == "DCL" then
mem.editingfloor = #mem.params.floornames-exp.index+1
mem.screenstate = (mem.screenstate == "oobe_floortable" and "oobe_floortable_edit" or "floortable_edit")
elseif event.fields.edit then
mem.screenstate = (mem.screenstate == "oobe_floortable" and "oobe_floortable_edit" or "floortable_edit")
elseif event.fields.add then
table.insert(mem.params.floorheights,5)
table.insert(mem.params.floornames,tostring(#mem.params.floornames+1))
elseif event.fields.remove then
table.remove(mem.params.floorheights,mem.editingfloor)
table.remove(mem.params.floornames,mem.editingfloor)
mem.editingfloor = math.max(1,mem.editingfloor-1)
elseif event.fields.moveup then
local height = mem.params.floorheights[mem.editingfloor]
local name = mem.params.floornames[mem.editingfloor]
table.remove(mem.params.floorheights,mem.editingfloor)
table.remove(mem.params.floornames,mem.editingfloor)
table.insert(mem.params.floorheights,mem.editingfloor+1,height)
table.insert(mem.params.floornames,mem.editingfloor+1,name)
mem.editingfloor = mem.editingfloor + 1
elseif event.fields.movedown then
local height = mem.params.floorheights[mem.editingfloor]
local name = mem.params.floornames[mem.editingfloor]
table.remove(mem.params.floorheights,mem.editingfloor)
table.remove(mem.params.floornames,mem.editingfloor)
table.insert(mem.params.floorheights,mem.editingfloor-1,height)
table.insert(mem.params.floornames,mem.editingfloor-1,name)
mem.editingfloor = mem.editingfloor - 1
end
elseif mem.screenstate == "oobe_floortable_edit" or mem.screenstate == "floortable_edit" then
if event.fields.back or event.fields.save then
mem.screenstate = (mem.screenstate == "oobe_floortable_edit" and "oobe_floortable" or "floortable")
local height = tonumber(event.fields.height)
if height then
height = math.floor(height+0.5)
mem.params.floorheights[mem.editingfloor] = math.max(0,height)
end
mem.params.floornames[mem.editingfloor] = string.sub(event.fields.name,1,256)
end
elseif mem.screenstate == "parameters" then
if event.fields.save then
mem.screenstate = "status"
local doortimer = tonumber(event.fields.doortimer)
if doortimer and doortimer > 0 and doortimer <= 30 then
mem.params.doortimer = doortimer
end
local contractspeed = tonumber(event.fields.contractspeed)
if contractspeed and contractspeed >= 0.1 and contractspeed <= 20 then
mem.params.contractspeed = contractspeed
end
local mainlanding = tonumber(event.fields.mainlanding)
if mainlanding and mainlanding >= 1 and mainlanding <= #mem.params.floorheights then
mem.params.mainlanding = math.floor(mainlanding)
end
elseif event.fields.floortable then
mem.screenstate = "floortable"
elseif event.fields.cancel then
mem.screenstate = "status"
elseif event.fields.resetdoors then
mem.screenstate = "status"
if mem.doorstate ~= "closed" then close() end
elseif event.fields.resetcontroller then
mem.screenstate = "status"
mem.carstate = "bfdemand"
if mem.doorstate == "closed" then
drivecmd({
command = "setmaxvel",
maxvel = mem.params.contractspeed,
})
drivecmd({command = "resetpos"})
interrupt(0.1,"checkdrive")
mem.carmotion = true
juststarted = true
else
close()
end
end
elseif mem.screenstate == "status" then
for i=1,#mem.params.floornames,1 do
if event.fields[string.format("carcall%d",i)] and (mem.carstate == "normal" or mem.carstate == "test" or mem.carstate == "capture" or mem.carstate == "indep") then
mem.carcalls[i] = true
elseif event.fields[string.format("upcall%d",i)] and mem.carstate == "normal" and not mem.capturesw then
if mem.params.groupmode == "group" then
mem.swingupcalls[i] = true
else
mem.upcalls[i] = true
end
elseif event.fields[string.format("downcall%d",i)] and mem.carstate == "normal" and not mem.capturesw then
if mem.params.groupmode == "group" then
mem.swingdncalls[i] = true
else
mem.dncalls[i] = true
end
end
end
if event.fields.scrollup then
mem.screenpage = mem.screenpage + 1
mem.scrollfollowscar = false
elseif event.fields.scrolldown then
mem.screenpage = mem.screenpage - 1
mem.scrollfollowscar = false
elseif event.fields.scrollfollowscar then
mem.scrollfollowscar = (event.fields.scrollfollowscar == "true")
elseif event.fields.stopsw then
mem.controllerstopsw = not mem.controllerstopsw
elseif event.fields.inspectsw then
mem.controllerinspectsw = not mem.controllerinspectsw
elseif event.fields.capturesw then
mem.capturesw = not mem.capturesw
elseif event.fields.testsw then
mem.testsw = not mem.testsw
elseif event.fields.inspectup and mem.carstate == "mrinspect" and mem.doorstate == "closed" and getpos() < #mem.params.floornames then
mem.carmotion = true
juststarted = true
drivecmd({
command = "setmaxvel",
maxvel = 0.2,
})
drivecmd({
command = "moveto",
pos = math.floor(mem.drive.status.apos)+1
})
elseif event.fields.inspectdown and mem.carstate == "mrinspect" and mem.doorstate == "closed" and mem.drive.status.apos-1 >= 0 then
mem.carmotion = true
juststarted = true
drivecmd({
command = "setmaxvel",
maxvel = 0.2,
})
drivecmd({
command = "moveto",
pos = math.floor(mem.drive.status.apos)-1
})
elseif event.fields.parameters then
mem.screenstate = "parameters"
elseif event.fields.faults then
mem.screenstate = "faults"
end
elseif mem.screenstate == "faults" then
if event.fields.back then
mem.screenstate = "status"
elseif event.fields.clear then
mem.faultlog = {}
mem.activefaults = {}
mem.fatalfault = false
drivecmd({command = "resetfault"})
end
end
elseif event.iid == "opened" and mem.doorstate == "opening" then
mem.doorstate = "open"
if mem.carstate == "normal" then
interrupt(mem.params.doortimer,"close")
end
elseif event.iid == "close" and mem.doorstate == "open" then
close()
elseif event.iid == "closed" and (mem.doorstate == "closing" or mem.doorstate == "testtiming") then
mem.doorstate = "closed"
if mem.carstate == "bfdemand" then
drivecmd({
command = "setmaxvel",
maxvel = mem.params.contractspeed,
})
drivecmd({command = "resetpos"})
interrupt(0.1,"checkdrive")
mem.carmotion = true
juststarted = true
elseif mem.carstate == "resync" then
gotofloor(getpos())
interrupt(0.1,"checkdrive")
mem.carmotion = true
juststarted = true
end
elseif event.type == "callbutton" and mem.carstate == "normal" then
if mem.doorstate == "closed" or mem.direction ~= event.dir or getpos() ~= event.landing then
if mem.params.groupmode == "group" then
if event.dir == "up" and event.landing >= 1 and event.landing < #mem.params.floornames then
mem.swingupcalls[event.landing] = true
elseif event.dir == "down" and event.landing > 1 and event.landing <= #mem.params.floornames then
mem.swingdncalls[event.landing] = true
end
else
if event.dir == "up" and event.landing >= 1 and event.landing < #mem.params.floornames then
mem.upcalls[event.landing] = true
elseif event.dir == "down" and event.landing > 1 and event.landing <= #mem.params.floornames then
mem.dncalls[event.landing] = true
end
end
elseif mem.direction == event.dir and mem.doorstate == "open" then
interrupt(mem.params.doortimer,"close")
elseif mem.direction == event.dir and mem.doorstate == "closing" then
open()
end
elseif event.iid == "checkopen" then
if mem.drive.status.doorstate == "open" then
interrupt(0,"opened")
if mem.carstate == "normal" or mem.carstate == "indep" or mem.carstate == "fs1" or mem.carstate == "fs2" or mem.carstate == "fs2hold" then
interrupt(nil,"opentimeout")
end
else
interrupt(0.2,"checkopen")
end
elseif event.iid == "checkclosed" then
if mem.drive.status.doorstate == "closed" then
interrupt(0,"closed")
interrupt(nil,"closetimeout")
else
interrupt(0.2,"checkclosed")
end
elseif event.iid == "opentimeout" then
fault("opentimeout",true)
elseif event.iid == "closetimeout" then
fault("closetimeout",true)
elseif event.type == "cop" then
local fields = event.fields
if mem.carstate == "normal" or mem.carstate == "indep" or mem.carstate == "fs2" then
for k,v in pairs(fields) do
if string.sub(k,1,7) == "carcall" then
local landing = tonumber(string.sub(k,8,-1))
if v and landing and landing >= 1 and landing <= #mem.params.floorheights then
if getpos() == landing then
if mem.carstate == "normal" or mem.carstate == "indep" then
if mem.doorstate == "closing" then
open()
elseif mem.doorstate == "open" then
interrupt(mem.params.doortimer,"close")
elseif mem.doorstate == "closed" then
mem.carcalls[landing] = true
end
end
else
mem.carcalls[landing] = true
end
end
end
end
if fields.close and mem.doorstate == "open" then
interrupt(0,"close")
elseif fields.open and (mem.doorstate == "closed" or mem.doorstate == "closing") and not (mem.carmotion or juststarted) then
open()
elseif fields.callcancel and (mem.carstate == "indep" or mem.carstate == "fs2") then
mem.carcalls = {}
end
end
elseif event.type == "copswitches" then
local fields = event.fields
if fields.fanon then
mem.fansw = true
elseif fields.fanoff then
mem.fansw = false
elseif fields.lighton then
mem.lightsw = true
elseif fields.lightoff then
mem.lightsw = false
elseif fields.indon then
mem.indsw = true
elseif fields.indoff then
mem.indsw = false
elseif fields.fs2on then
mem.fs2sw = "on"
elseif fields.fs2hold then
mem.fs2sw = "hold"
elseif fields.fs2off then
mem.fs2sw = "off"
end
elseif event.type == "fs1switch" then
mem.fs1switch = event.state
mem.fs1led = event.state
elseif event.type == "cartopbox" then
if event.control == "inspectswitch" then
mem.cartopinspectsw = not mem.cartopinspectsw
elseif event.control == "up" and mem.carstate == "carinspect" and mem.doorstate == "closed" and getpos() < #mem.params.floornames then
mem.carmotion = true
juststarted = true
drivecmd({
command = "setmaxvel",
maxvel = 0.2,
})
drivecmd({
command = "moveto",
pos = math.floor(mem.drive.status.apos)+1
})
elseif event.control == "down" and mem.carstate == "carinspect" and mem.doorstate == "closed" and mem.drive.status.apos-1 >= 0 then
mem.carmotion = true
juststarted = true
drivecmd({
command = "setmaxvel",
maxvel = 0.2,
})
drivecmd({
command = "moveto",
pos = math.floor(mem.drive.status.apos)-1
})
end
elseif event.type == "dispatchermsg" then
if event.channel == "pairrequest" and mem.screenstate == "oobe_dispatcherconnect" then
mem.params.floornames = event.msg.floornames
mem.params.floorheights = event.msg.floorheights
mem.activefaults = {}
mem.faultlog = {}
mem.fatalfault = false
mem.state = "configured"
mem.screenstate = "status"
mem.screenpage = 1
mem.carstate = "bfdemand"
if mem.doorstate == "closed" then
drivecmd({
command = "setmaxvel",
maxvel = mem.params.contractspeed,
})
drivecmd({command = "resetpos"})
interrupt(0.1,"checkdrive")
mem.carmotion = true
juststarted = true
else
close()
end
send(event.source,"pairok",mem)
elseif event.channel == "newfloortable" then
mem.params.floornames = event.msg.floornames
mem.params.floorheights = event.msg.floorheights
mem.carstate = "bfdemand"
if mem.doorstate == "closed" then
drivecmd({
command = "setmaxvel",
maxvel = mem.params.contractspeed,
})
drivecmd({command = "resetpos"})
interrupt(0.1,"checkdrive")
mem.carmotion = true
juststarted = true
else
close()
end
elseif event.channel == "getstatus" then
send(event.source,"status",mem)
elseif event.channel == "groupupcall" and mem.carstate == "normal" then
mem.groupupcalls[event.msg] = true
elseif event.channel == "groupdncall" and mem.carstate == "normal" then
mem.groupdncalls[event.msg] = true
elseif event.channel == "groupupcancel" then
mem.groupupcalls[event.msg] = nil
elseif event.channel == "groupdncancel" then
mem.groupdncalls[event.msg] = nil
elseif event.channel == "carcall" and mem.carstate == "normal" then
mem.carcalls[event.msg] = true
send(event.source,"status",mem)
elseif event.channel == "fs1switch" then
mem.fs1switch = event.msg
mem.fs1led = event.msg
end
elseif event.type == "remotemsg" then
if event.channel == "groupupcall" and mem.carstate == "normal" then
mem.groupupcalls[event.msg] = true
elseif event.channel == "groupdncall" and mem.carstate == "normal" then
mem.groupdncalls[event.msg] = true
elseif event.channel == "swingupcall" and mem.carstate == "normal" then
mem.swingupcalls[event.msg] = true
elseif event.channel == "swingdncall" and mem.carstate == "normal" then
mem.swingdncalls[event.msg] = true
elseif event.channel == "upcall" and mem.carstate == "normal" then
mem.upcalls[event.msg] = true
elseif event.channel == "dncall" and mem.carstate == "normal" then
mem.dncalls[event.msg] = true
elseif event.channel == "groupupcancel" then
mem.groupupcalls[event.msg] = nil
elseif event.channel == "groupdncancel" then
mem.groupdncalls[event.msg] = nil
elseif event.channel == "carcall" and mem.carstate == "normal" then
mem.carcalls[event.msg] = true
end
elseif event.type == "lightcurtain" then
if mem.carstate == "normal" or mem.carstate == "indep" then
if mem.doorstate == "closing" then
open()
elseif mem.doorstate == "open" and mem.carstate == "normal" then
interrupt(mem.params.doortimer,"close")
end
end
end
local oldstate = mem.carstate
if mem.fatalfault then
mem.carstate = "fault"
drivecmd({command="estop"})
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
mem.direction = nil
interrupt(nil,"opentimeout")
interrupt(nil,"closetimeout")
elseif mem.controllerstopsw or mem.screenstate == "floortable" or mem.screenstate == "floortable_edit" then
mem.carstate = "stop"
drivecmd({command="estop"})
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
mem.direction = nil
interrupt(nil,"opentimeout")
interrupt(nil,"closetimeout")
elseif mem.controllerinspectsw and mem.cartopinspectsw then
mem.carstate = "inspconflict"
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
mem.direction = nil
drivecmd({command="estop"})
interrupt(nil,"opentimeout")
interrupt(nil,"closetimeout")
elseif mem.controllerinspectsw and not mem.cartopinspectsw then
mem.carstate = "mrinspect"
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
mem.direction = nil
interrupt(nil,"opentimeout")
interrupt(nil,"closetimeout")
if oldstate ~= "mrinspect" then drivecmd({command="estop"}) end
elseif mem.cartopinspectsw and not mem.controllerinspectsw then
mem.carstate = "carinspect"
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
mem.direction = nil
interrupt(nil,"opentimeout")
interrupt(nil,"closetimeout")
if oldstate ~= "carinspect" then drivecmd({command="estop"}) end
elseif mem.fs2sw == "on" then
mem.carstate = "fs2"
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
if oldstate ~= "fs2" then mem.carcalls = {} end
if mem.doorstate == "open" and oldstate ~= "fs2" then interrupt(nil,"close") end
elseif mem.fs2sw == "hold" then
mem.carstate = "fs2hold"
mem.upcalls = {}
mem.dncalls = {}
mem.carcalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
if mem.doorstate == "open" then interrupt(nil,"close") end
elseif mem.fs1switch then
mem.carstate = "fs1"
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
interrupt(nil,"close")
if mem.drive.status.vel > 0 and mem.drive.status.apos > gettarget(mem.params.mainlanding or 1) then
for i=getpos(),#mem.params.floornames,1 do
if mem.drive.status.neareststop < gettarget(i) and mem.drive.status.dpos > gettarget(i) then
gotofloor(i)
break
end
end
elseif mem.drive.status.vel < 0 and mem.drive.status.apos < gettarget(mem.params.mainlanding or 1) then
for i=1,getpos(),1 do
if mem.drive.status.neareststop > gettarget(i) and mem.drive.status.dpos < gettarget(i) then
gotofloor(i)
break
end
end
elseif getpos() ~= (mem.params.mainlanding or 1) then
if not (mem.carmotion or juststarted) then
if mem.doorstate == "closed" then
gotofloor(mem.params.mainlanding or 1)
elseif mem.doorstate == "open" then
close()
end
end
elseif mem.doorstate == "closed" then
if not (mem.carmotion or juststarted) then
open()
end
end
elseif mem.indsw then
if mem.carstate ~= "resync" then mem.carstate = "indep" end
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
if oldstate == "stop" or oldstate == "mrinspect" or oldstate == "fault" then
mem.carstate = "resync"
gotofloor(getpos())
elseif oldstate == "normal" and (mem.doorstate == "closed" or mem.doorstate == "closing") and not (mem.carmotion or juststarted) then
open()
elseif oldstate == "normal" and mem.doorstate == "open" then
interrupt(nil,"close")
end
elseif mem.testsw then
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
if mem.carstate ~= "resync" then mem.carstate = "test" end
if oldstate == "stop" or oldstate == "mrinspect" or oldstate == "carinspect" or oldstate == "fault" then
mem.carstate = "resync"
gotofloor(getpos())
end
elseif mem.capturesw then
mem.upcalls = {}
mem.dncalls = {}
mem.swingupcalls = {}
mem.swingdncalls = {}
mem.groupupcalls = {}
mem.groupdncalls = {}
if oldstate == "stop" or oldstate == "mrinspect" or oldstate == "carinspect" or oldstate == "fault" then
mem.carstate = "resync"
gotofloor(getpos())
elseif mem.carstate ~= "resync" and not mem.direction then
mem.carstate = "capture"
end
else
if oldstate == "stop" or oldstate == "mrinspect" or oldstate == "carinspect" or oldstate == "fault" then
mem.carstate = "resync"
gotofloor(getpos())
elseif oldstate == "test" or oldstate == "capture" or oldstate == "fs1" or oldstate == "fs2" or oldstate == "fs2hold" then
if (oldstate == "fs1" or oldstate == "fs2" or oldstate == "fs2hold") and mem.doorstate == "open" then
interrupt(mem.params.doortimer,"close")
end
mem.carstate = "normal"
elseif oldstate == "indep" then
mem.carstate = "normal"
if mem.doorstate == "open" then interrupt(mem.params.doortimer,"close") end
end
end
if mem.carmotion then
mem.carmotion = (mem.drive.status.vel ~= 0) or juststarted
if mem.carmotion then
interrupt(0.1,"checkdrive")
else
local hallcall = mem.upcalls[getpos()] or mem.dncalls[getpos()]
if mem.carstate == "normal" or mem.carstate == "indep" or mem.carstate == "fs1" or mem.carstate == "fs2" then
mem.carcalls[getpos()] = nil
if mem.direction == "up" then
mem.upcalls[getpos()] = nil
mem.swingupcalls[getpos()] = nil
mem.groupupcalls[getpos()] = nil
if (not hallcall) and (not getnextcallabove()) and getnextcallbelow() then
mem.direction = "down"
elseif mem.dncalls[getpos()] and (not mem.upcalls[getpos()]) and (not getnextcallabove(nil,true)) then
mem.direction = "down"
mem.dncalls[getpos()] = nil
mem.swingdncalls[getpos()] = nil
mem.groupdncalls[getpos()] = nil
end
elseif mem.direction == "down" then
mem.dncalls[getpos()] = nil
mem.swingdncalls[getpos()] = nil
mem.groupdncalls[getpos()] = nil
if (not hallcall) and (not getnextcallbelow()) and getnextcallabove() then
mem.direction = "up"
elseif mem.upcalls[getpos()] and (not mem.dncalls[getpos()]) and (not getnextcallbelow(nil,true)) then
mem.direction = "up"
mem.upcalls[getpos()] = nil
mem.swingupcalls[getpos()] = nil
mem.groupupcalls[getpos()] = nil
end
end
if getpos() >= #mem.params.floornames then
mem.direction = "down"
mem.dncalls[#mem.params.floornames] = nil
mem.swingdncalls[#mem.params.floornames] = nil
mem.groupdncalls[#mem.params.floornames] = nil
elseif getpos() <= 1 then
mem.direction = "up"
mem.upcalls[1] = nil
mem.swingupcalls[1] = nil
mem.groupupcalls[1] = nil
end
if (mem.carstate ~= "fs1" or getpos() == (mem.params.mainlanding or 1)) and mem.carstate ~= "fs2" and mem.carstate ~= "fs2hold" then open() end
if mem.carstate == "fs1" and getpos() ~= (mem.params.mainlanding or 1) then
gotofloor(mem.params.mainlanding or 1)
end
elseif mem.carstate == "test" then
mem.carcalls[getpos()] = nil
mem.doorstate = "testtiming"
interrupt(5,"closed")
if getpos() >= #mem.params.floornames then
mem.direction = "down"
elseif getpos() <= 1 then
mem.direction = "up"
end
elseif mem.carstate == "bfdemand" or mem.carstate == "resync" then
if mem.indsw then
mem.carstate = "indep"
elseif mem.testsw then
mem.carstate = "test"
elseif mem.capturesw then
mem.carstate = "capture"
else
mem.carstate = "normal"
end
end
end
end
if mem.params.groupmode == "group" then
mem.upcalls = table.copy(mem.groupupcalls)
mem.dncalls = table.copy(mem.groupdncalls)
for i in pairs(mem.swingupcalls) do
mem.upcalls[i] = true
end
for i in pairs(mem.swingdncalls) do
mem.dncalls[i] = true
end
end
if (mem.carstate == "normal" or mem.carstate == "capture" or mem.carstate == "test" or mem.carstate == "indep" or mem.carstate == "fs2") and mem.doorstate == "closed" then
if not mem.carmotion then
if mem.direction == "up" then
if getnextcallabove("up") then
mem.direction = "up"
gotofloor(getnextcallabove("up"))
elseif gethighestdowncall() then
mem.direction = "down"
gotofloor(gethighestdowncall())
elseif getlowestupcall() then
gotofloor(getlowestupcall())
elseif getnextcallbelow("down") then
mem.direction = "down"
gotofloor(getnextcallbelow("down"))
else
mem.direction = nil
end
elseif mem.direction == "down" then
if getnextcallbelow("down") then
gotofloor(getnextcallbelow("down"))
elseif getlowestupcall() then
mem.direction = "up"
gotofloor(getlowestupcall())
elseif gethighestdowncall() then
gotofloor(gethighestdowncall())
elseif getnextcallabove("up") then
mem.direction = "up"
gotofloor(getnextcallabove())
else
mem.direction = nil
end
else
if getnextcallabove("up") then
mem.direction = "up"
gotofloor(getnextcallabove())
elseif getnextcallbelow("down") then
mem.direction = "down"
gotofloor(getnextcallbelow("down"))
elseif getlowestupcall() then
mem.direction = "up"
gotofloor(getlowestupcall())
elseif gethighestdowncall() then
mem.direction = "down"
gotofloor(gethighestdowncall())
end
end
if mem.carstate == "normal" and mem.capturesw and not mem.direction then
mem.upcalls = {}
mem.dncalls = {}
mem.carstate = "capture"
elseif mem.carstate == "capture" and mem.direction then
mem.carstate = "normal"
end
elseif (mem.carstate == "normal" or mem.carstate == "capture" or mem.carstate == "test") and mem.carmotion then
if mem.drive.status.vel > 0 then
local nextup = getnextcallabove("up")
if nextup then
mem.direction = "up"
local target = gettarget(nextup)
if target < mem.drive.status.dpos and target > mem.drive.status.neareststop then
gotofloor(nextup)
end
end
elseif mem.drive.status.vel < 0 then
local nextdown = getnextcallbelow("down")
if nextdown then
mem.direction = "down"
local target = gettarget(nextdown)
if target > mem.drive.status.dpos and target < mem.drive.status.neareststop then
gotofloor(nextdown)
end
end
end
end
end
if mem.scrollfollowscar and mem.screenstate == "status" then
mem.screenpage = math.floor((getpos()-1)/10)+1
end
fs("formspec_version[6]")
fs("size[16,12]")
fs("background9[0,0;16,12;celevator_fs_bg.png;true;3]")
if mem.screenstate == "oobe_welcome" then
fs("image[6,1;4,2;celevator_logo.png]")
fs("label[1,4;Welcome to your new MTronic XT elevator controller!]")
fs("label[1,4.5;This setup wizard is designed to get your elevator up and running as quickly as possible.]")
fs("label[1,5.5;Press Next to begin.]")
fs("button[1,10;2,1;license;License Info]")
fs("button[13,10;2,1;next;Next >]")
elseif mem.screenstate == "oobe_license" then
local licensefile = io.open(minetest.get_modpath("celevator")..DIR_DELIM.."COPYING")
local license = minetest.formspec_escape(licensefile:read("*all"))
licensefile:close()
fs("textarea[1,1;14,8;license;This applies to the whole celevator mod\\, not just this controller:;"..license.."]")
fs("button[7,10.5;2,1;back;OK]")
elseif mem.screenstate == "oobe_groupmode" then
fs("button[1,10;2,1;back;< Back]")
fs("label[1,1;Select a group operation mode:]")
fs("button[1,3;2,1;simplex;Simplex]")
fs("label[1,4.5;This will be the only elevator in the group. Hall calls will be handled by this controller.]")
fs("button[1,6;2,1;group;Group]")
fs("label[1,7.5;This elevator will participate in a group with others. Hall calls will be handled by a dispatcher.]")
elseif mem.screenstate == "oobe_dispatcherconnect" then
fs("button[1,10;2,1;back;< Cancel]")
fs("label[1,1;Waiting for connection from dispatcher...]")
fs(string.format("label[1,1.5;This controller's car ID is: %d]",mem.carid))
elseif mem.screenstate == "oobe_floortable" or mem.screenstate == "floortable" then
if mem.screenstate == "oobe_floortable" then
fs("label[1,1;Enter details of all floors this elevator will serve, then press Done.]")
fs("button[1,10;2,1;back;< Back]")
fs("button[13,10;2,1;next;Done >]")
else
fs("label[1,1;EDIT FLOOR TABLE]")
fs("button[1,10;2,1;next;Done]")
end
fs("textlist[1,2;6,7;floor;")
for i=#mem.params.floornames,1,-1 do
fs(minetest.formspec_escape(string.format("%d - Height: %d - PI: %s",i,mem.params.floorheights[i],mem.params.floornames[i]))..(i==1 and "" or ","))
end
fs(";"..tostring(#mem.params.floornames-mem.editingfloor+1)..";false]")
if #mem.params.floornames < 100 then fs("button[8,2;2,1;add;New Floor]") end
fs("button[8,3.5;2,1;edit;Edit Floor]")
if #mem.params.floornames > 2 then fs("button[8,5;2,1;remove;Remove Floor]") end
if mem.editingfloor < #mem.params.floornames then fs("button[8,6.5;2,1;moveup;Move Up]") end
if mem.editingfloor > 1 then fs("button[8,8;2,1;movedown;Move Down") end
elseif mem.screenstate == "oobe_floortable_edit" or mem.screenstate == "floortable_edit" then
if mem.screenstate == "oobe_floortable_edit" then
fs("button[7,10.5;2,1;back;OK]")
fs("label[1,5;The Floor Height is the distance (in meters/nodes) from the floor level of this floor to the floor level of the next floor.]")
fs("label[1,5.5;(not used at the highest floor)]")
fs("label[1,6.5;The Floor Name is how the floor will be displayed on the position indicators.]")
else
fs("button[7,10.5;2,1;save;Save]")
end
fs("label[1,1;Editing floor "..tostring(mem.editingfloor).."]")
fs("field[1,3;3,1;height;Floor Height;"..tostring(mem.params.floorheights[mem.editingfloor]).."]")
fs("field[5,3;3,1;name;Floor Name;"..minetest.formspec_escape(mem.params.floornames[mem.editingfloor]).."]")
elseif mem.screenstate == "status" then
fs("style_type[image_button;font=mono;font_size=*0.75]")
fs("box[12,2.5;0.1,9;#AAAAAAFF]")
fs("box[13.12,2.5;0.05,9;#AAAAAAFF]")
fs("box[14.12,2.5;0.05,9;#AAAAAAFF]")
fs("box[15.25,2.5;0.1,9;#AAAAAAFF]")
fs("label[12.5,2;UP]")
fs("label[13.38,2;CAR]")
fs("label[14.25,2;DOWN]")
local maxfloor = #mem.params.floornames
local bottom = (mem.screenpage-1)*10+1
for i=0,9,1 do
local ypos = 11-(i*0.9)
local floornum = bottom+i
if floornum > maxfloor then break end
fs(string.format("label[11.25,%f;%s]",ypos,minetest.formspec_escape(mem.params.floornames[floornum])))
local ccdot = mem.carcalls[floornum] and "*" or ""
if getpos() == floornum then
local cargraphics = {
open = "\\[ \\]",
opening = "\\[< >\\]",
closing = "\\[> <\\]",
closed = "\\[ | \\]",
testtiming = "\\[ | \\]",
}
ccdot = cargraphics[mem.doorstate]
if mem.direction == "up" then
ccdot = minetest.colorize("#55FF55",ccdot)
elseif mem.direction == "down" then
ccdot = minetest.colorize("#FF5555",ccdot)
end
end
fs(string.format("image_button[13.25,%f;0.75,0.75;celevator_fs_bg.png;carcall%d;%s]",ypos-0.25,floornum,ccdot))
if floornum < maxfloor then
local arrow = mem.upcalls[floornum] and minetest.colorize("#55FF55","^") or ""
if mem.params.groupmode == "group" then
arrow = mem.groupupcalls[floornum] and minetest.colorize("#55FF55","^") or ""
arrow = (mem.swingupcalls[floornum] and minetest.colorize("#FFFF55","^") or "")..arrow
end
fs(string.format("image_button[12.25,%f;0.75,0.75;celevator_fs_bg.png;upcall%d;%s]",ypos-0.25,floornum,arrow))
end
if floornum > 1 then
local arrow = mem.dncalls[floornum] and minetest.colorize("#FF5555","v") or ""
if mem.params.groupmode == "group" then
arrow = mem.swingdncalls[floornum] and minetest.colorize("#FFFF55","v") or ""
arrow = (mem.groupdncalls[floornum] and minetest.colorize("#FF5555","v") or "")..arrow
end
fs(string.format("image_button[14.25,%f;0.75,0.75;celevator_fs_bg.png;downcall%d;%s]",ypos-0.25,floornum,arrow))
end
end
if maxfloor > 10 then
fs(string.format("checkbox[13,1.25;scrollfollowscar;Follow Car;%s]",tostring(mem.scrollfollowscar)))
if bottom+9 < maxfloor then
fs("image_button[12.75,0.25;0.75,0.75;celevator_menu_arrow.png;scrollup;;false;false;celevator_menu_arrow.png]")
end
if bottom > 1 then
fs("image_button[13.87,0.25;0.75,0.75;celevator_menu_arrow.png^\\[transformFY;scrolldown;;false;false;celevator_menu_arrow.png^\\[transformFY]")
end
end
fs("label[1,1;CAR STATUS]")
fs(string.format("label[1,2;%s]",modenames[mem.carstate]))
fs(string.format("label[1,2.5;Doors %s]",doorstates[mem.doorstate]))
fs(string.format("label[1,3;Position: %0.02fm Speed: %+0.02fm/s PI: %s]",mem.drive.status.apos,mem.drive.status.vel,minetest.formspec_escape(mem.params.floornames[getpos()])))
if #mem.faultlog > 0 then
fs("label[1,3.5;Fault(s) Active]")
else
fs("label[1,3.5;No Current Faults]")
end
fs("button[1,10;3,1;faults;Fault History]")
fs("button[4.5,10;3,1;parameters;Edit Parameters]")
local redon = "celevator_led_red_on.png"
local redoff = "celevator_led_red_off.png"
local yellowon = "celevator_led_yellow_on.png"
local yellowoff = "celevator_led_yellow_off.png"
local greenon = "celevator_led_green_on.png"
local greenoff = "celevator_led_green_off.png"
fs(string.format("image[7,1;0.7,0.7;%s]",mem.carstate == "fault" and redon or redoff))
fs("label[8,1.35;FAULT]")
fs(string.format("image[7,1.9;0.7,0.7;%s]",(mem.carstate == "mrinspect" or mem.carstate == "carinspect" or mem.carstate == "inspconflict") and yellowon or yellowoff))
fs("label[8,2.25;INSP/ACCESS]")
fs(string.format("image[7,2.8;0.7,0.7;%s]",mem.carstate == "normal" and greenon or greenoff))
fs("label[8,3.15;NORMAL OPERATION]")
fs(string.format("image[7,3.7;0.7,0.7;%s]",mem.drive.status.vel > 0.01 and yellowon or yellowoff))
fs("label[8,4.05;UP]")
fs(string.format("image[7,4.6;0.7,0.7;%s]",math.abs(mem.drive.status.vel) > 0.01 and yellowon or yellowoff))
fs("label[8,4.95;DRIVE CMD]")
fs(string.format("image[7,5.5;0.7,0.7;%s]",mem.drive.status.vel < -0.01 and yellowon or yellowoff))
fs("label[8,5.85;DOWN]")
fs(string.format("image[7,6.4;0.7,0.7;%s]",math.abs(mem.drive.status.vel) > math.min(0.4,mem.params.contractspeed/2) and yellowon or yellowoff))
fs("label[8,6.75;HIGH SPEED]")
fs(string.format("image[7,7.3;0.7,0.7;%s]",math.abs(gettarget(getpos(true))-mem.drive.status.apos) < 0.5 and greenon or greenoff))
fs("label[8,7.65;DOOR ZONE]")
fs(string.format("image[7,8.2;0.7,0.7;%s]",mem.doorstate == "closed" and greenon or greenoff))
fs("label[8,8.55;DOORS LOCKED]")
fs("style[*;font=mono]")
local stopswimg = "celevator_toggle_switch.png"..(mem.controllerstopsw and "^\\[transformFY" or "")
fs(string.format("image_button[1,5;1,1.33;%s;stopsw;;false;false;%s]",stopswimg,stopswimg))
fs("label[1.3,4.75;RUN]")
fs("label[1.2,6.6;STOP]")
local captureswimg = "celevator_toggle_switch.png"..(mem.capturesw and "" or "^\\[transformFY")
fs(string.format("image_button[3,5;1,1.33;%s;capturesw;;false;false;%s]",captureswimg,captureswimg))
fs("label[3,4.75;CAPTURE]")
local testswimg = "celevator_toggle_switch.png"..(mem.testsw and "" or "^\\[transformFY")
fs(string.format("image_button[5,5;1,1.33;%s;testsw;;false;false;%s]",testswimg,testswimg))
fs("label[5.23,4.75;TEST]")
local inspectswimg = "celevator_toggle_switch.png"..(mem.controllerinspectsw and "" or "^\\[transformFY")
fs(string.format("image_button[1,8;1,1.33;%s;inspectsw;;false;false;%s]",inspectswimg,inspectswimg))
fs("label[1.05,7.75;INSPECT]")
fs("label[1.1,9.6;NORMAL]")
fs(string.format("image_button[3,8.25;1,1;%s;inspectup;;false;false;%s]","celevator_button_black.png","celevator_button_black.png"))
fs("label[3.4,7.75;UP]")
fs(string.format("image_button[5,8.25;1,1;%s;inspectdown;;false;false;%s]","celevator_button_black.png","celevator_button_black.png"))
fs("label[5.25,7.75;DOWN]")
elseif mem.screenstate == "parameters" then
fs("label[1,1;EDIT PARAMETERS]")
fs("button[1,10;3,1;save;Save]")
fs("button[4.5,10;3,1;cancel;Cancel]")
if mem.params.groupmode == "simplex" then fs("button[8,10;3,1;floortable;Edit Floor Table]") end
fs(string.format("field[1,3;3,1;doortimer;Door Dwell Timer;%0.1f]",mem.params.doortimer))
fs(string.format("field[1,5;3,1;contractspeed;Contract Speed (m/s);%0.1f]",mem.params.contractspeed))
fs(string.format("field[1,7;3,1;mainlanding;Main Landing;%d]",mem.params.mainlanding or 1))
fs("style[resetdoors,resetcontroller;bgcolor=#DD3333]")
fs("button[12,1;3,1;resetdoors;Reset Doors]")
fs("button[12,2.5;3,1;resetcontroller;Reset Controller]")
elseif mem.screenstate == "faults" then
fs("label[1,1;FAULT HISTORY]")
if #mem.faultlog > 0 then
for i=0,9,1 do
if #mem.faultlog-i >= 1 then
local currfault = mem.faultlog[#mem.faultlog-i]
local date = os.date("*t",currfault.timestamp)
fs(string.format("label[1,%0.1f;%04d-%02d-%02d %02d:%02d:%02d - %s]",2+i,date.year,date.month,date.day,date.hour,date.min,date.sec,faultnames[currfault.ftype]))
end
end
else
fs("label[1,2;No Current Faults]")
end
fs("button[1,10;3,1;back;Back]")
fs("button[4.5,10;3,1;clear;Clear]")
end
local arrow = " "
if mem.drive.status.dpos > mem.drive.status.apos then
arrow = "^"
elseif mem.drive.status.dpos < mem.drive.status.apos then
arrow = "v"
end
mem.infotext = string.format("ID %d: Floor %s %s - %s - Doors %s",mem.carid,mem.params.floornames[getpos()],arrow,modenames[mem.carstate],doorstates[mem.doorstate])
if mem.drive.type then
mem.showrunning = mem.drive.status.vel ~= 0
else
mem.showrunning = false
end
local oldpifloor = mem.pifloor
mem.pifloor = mem.params.floornames[getpos(true)]
local hidepi = {
bfdemand = true,
uninit = true,
stop = true,
fault = true,
mrinspect = true,
carinspect = true,
inspconflict = true,
}
if hidepi[mem.carstate] then mem.pifloor = "--" end
if mem.pifloor ~= oldpifloor and mem.carstate == "normal" then
drivecmd({command="pibeep"})
end
local arrowenabled = {
normal = true,
fs1 = true,
fs2 = true,
indep = true,
capture = true,
test = true,
}
mem.piuparrow = mem.drive.status.vel > 0 and arrowenabled[mem.carstate]
mem.pidownarrow = mem.drive.status.vel < 0 and arrowenabled[mem.carstate]
mem.flash_fs = (mem.carstate == "fs1" or mem.carstate == "fs2" or mem.carstate == "fs2hold")
mem.flash_is = mem.carstate == "indep"
mem.lanterns = {}
if mem.carstate == "normal" and (mem.doorstate == "open" or mem.doorstate == "opening") then
mem.lanterns[getpos()] = mem.direction
end
mem.copformspec = "formspec_version[7]"
local floorcount = #mem.params.floornames
local copcols = math.floor((floorcount-1)/10)+1
local coprows = math.floor((floorcount-1)/copcols)+1
local litimg = "celevator_copbutton_lit.png"
local unlitimg = "celevator_copbutton_unlit.png"
mem.copformspec = mem.copformspec..string.format("size[%f,%f]",copcols*1.25+2.5,coprows*1.25+5)
for i=1,floorcount,1 do
local row = math.floor((i-1)/copcols)+1
local col = ((i-1)%copcols)+1
local yp = (coprows-row+1)*1.25+1
local xp = col*1.25
local tex = mem.carcalls[i] and litimg or unlitimg
mem.copformspec = mem.copformspec..string.format("image_button[%f,%f;1.2,1.2;%s;carcall%d;%s;false;false;%s]",xp,yp,tex,i,minetest.formspec_escape(mem.params.floornames[i]),litimg)
end
local doxp = (copcols == 1) and 0.5 or 1.25
mem.copformspec = mem.copformspec..string.format("image_button[%f,%f;1.2,1.2;%s;open;%s;false;false;%s]",doxp,coprows*1.25+2.5,unlitimg,minetest.formspec_escape("<|>"),litimg)
local dcxp = 3.75
if copcols == 1 then
dcxp = 2
elseif copcols == 2 then
dcxp = 2.5
end
mem.copformspec = mem.copformspec..string.format("image_button[%f,%f;1.2,1.2;%s;close;%s;false;false;%s]",dcxp,coprows*1.25+2.5,unlitimg,minetest.formspec_escape(">|<"),litimg)
mem.copformspec = mem.copformspec..string.format("image_button[0.4,0.5;1.4,1.4;%s;callcancel;Call\nCancel;false;false;%s]",unlitimg,litimg)
local firehat = mem.flash_fs and "celevator_fire_hat_lit.png" or "celevator_fire_hat_unlit.png"
mem.copformspec = mem.copformspec..string.format("image[2.2,0.5;1.4,1.4;%s]",firehat)
mem.switchformspec = "formspec_version[7]size[8,10]"
local fs2ontex = (mem.fs2sw == "on") and "celevator_button_rect_active.png" or "celevator_button_rect.png"
local fs2holdtex = (mem.fs2sw == "hold") and "celevator_button_rect_active.png" or "celevator_button_rect.png"
local fs2offtex = (mem.fs2sw == "off" or not mem.fs2sw) and "celevator_button_rect_active.png" or "celevator_button_rect.png"
mem.switchformspec = mem.switchformspec..string.format("image_button[1.5,1.5;1.5,1;%s;fs2on;ON;false;false;celevator_button_rect_active.png]",fs2ontex)
mem.switchformspec = mem.switchformspec..string.format("image_button[1.5,2.5;1.5,1;%s;fs2hold;HOLD;false;false;celevator_button_rect_active.png]",fs2holdtex)
mem.switchformspec = mem.switchformspec..string.format("image_button[1.5,3.5;1.5,1;%s;fs2off;OFF;false;false;celevator_button_rect_active.png]",fs2offtex)
mem.switchformspec = mem.switchformspec.."label[1.6,4.75;FIRE SVC]"
local indontex = mem.indsw and "celevator_button_rect_active.png" or "celevator_button_rect.png"
local indofftex = (not mem.indsw) and "celevator_button_rect_active.png" or "celevator_button_rect.png"
mem.switchformspec = mem.switchformspec..string.format("image_button[4.5,1.5;1.5,1;%s;indon;ON;false;false;celevator_button_rect_active.png]",indontex)
mem.switchformspec = mem.switchformspec..string.format("image_button[4.5,3.5;1.5,1;%s;indoff;OFF;false;false;celevator_button_rect_active.png]",indofftex)
mem.switchformspec = mem.switchformspec.."label[4.6,4.75;IND SVC]"
local lightontex = mem.lightsw and "celevator_button_rect_active.png" or "celevator_button_rect.png"
local lightofftex = (not mem.lightsw) and "celevator_button_rect_active.png" or "celevator_button_rect.png"
mem.switchformspec = mem.switchformspec..string.format("image_button[1.5,5.5;1.5,1;%s;lighton;ON;false;false;celevator_button_rect_active.png]",lightontex)
mem.switchformspec = mem.switchformspec..string.format("image_button[1.5,7.5;1.5,1;%s;lightoff;OFF;false;false;celevator_button_rect_active.png]",lightofftex)
mem.switchformspec = mem.switchformspec.."label[1.6,8.75;CAR LIGHT]"
local fanontex = mem.fansw and "celevator_button_rect_active.png" or "celevator_button_rect.png"
local fanofftex = (not mem.fansw) and "celevator_button_rect_active.png" or "celevator_button_rect.png"
mem.switchformspec = mem.switchformspec..string.format("image_button[4.5,5.5;1.5,1;%s;fanon;ON;false;false;celevator_button_rect_active.png]",fanontex)
mem.switchformspec = mem.switchformspec..string.format("image_button[4.5,7.5;1.5,1;%s;fanoff;OFF;false;false;celevator_button_rect_active.png]",fanofftex)
mem.switchformspec = mem.switchformspec.."label[4.6,8.75;CAR FAN]"
return pos,mem,changedinterrupts