laptop.register_app("celevator",{ app_name = "mView", app_info = "Remote interface for MTronic XT elevator controllers", app_icon = "celevator_laptop_icon.png", formspec_func = function(_,mtos) local ram = mtos.bdev:get_app_storage("ram","celevator") local mem = mtos.bdev:get_app_storage("hdd","celevator") if not mem.connections then mem.connections = {} end if not ram.screenstate then ram.screenstate = (#mem.connections > 0 and "connections" or "welcome") end if not mem.selectedconnection then mem.selectedconnection = 1 end if not mem.screenpage then mem.screenpage = 1 end if not mem.newconnection then mem.newconnection = {} end if not mem.scrollfollowscar then mem.scrollfollowscar = false end local fs = "" if ram.screenstate == "welcome" then fs = fs.."background9[5.5,1;4,2;celevator_fs_bg.png;false;3]" fs = fs.."image[5.75,1;4,2;celevator_logo.png]" fs = fs..mtos.theme:get_label("2,4","Welcome to the mView remote interface for MTronic XT elevator controllers!") fs = fs..mtos.theme:get_label("2,6","Add a connection to get started.") fs = fs..mtos.theme:get_button("5.5,7;4,1","major","connections","Add/Edit Connections") elseif ram.screenstate == "connections" then fs = fs..mtos.theme:get_label("0.5,0.5","MANAGE CONNECTIONS") if #mem.connections > 0 then fs = fs.."textlist[1,2;6,7;connection;" for i=#mem.connections,1,-1 do local text = string.format("ID %d - %s",mem.connections[i].carid,mem.connections[i].name) fs = fs..minetest.formspec_escape(text) fs = fs..(i==1 and "" or ",") end fs = fs..";"..tostring(#mem.connections-mem.selectedconnection+1)..";false]" else fs = fs..mtos.theme:get_label("1,2","No Connections") end fs = fs..mtos.theme:get_button("8,2;3,1","major","new","New Connection") if mem.connections[mem.selectedconnection] then fs = fs..mtos.theme:get_button("8,3;3,1","major","edit","Edit Connection") fs = fs..mtos.theme:get_button("8,4;3,1","major","delete","Delete Connection") fs = fs..mtos.theme:get_button("8,7;3,1","major","connect","Connect >") if #mem.connections > mem.selectedconnection then fs = fs..mtos.theme:get_button("8,5;3,1","major","moveup","Move Up") end if mem.selectedconnection > 1 then fs = fs..mtos.theme:get_button("8,6;3,1","major","movedown","Move Down") end end elseif ram.screenstate == "newconnection" then fs = fs..mtos.theme:get_label("0.5,0.5","NEW CONNECTION") fs = fs..mtos.theme:get_label("0.5,1","Please enter the ID you would like to connect to and a name for the connection.") fs = fs..mtos.theme:get_label("0.7,1.8","ID") fs = fs..mtos.theme:get_label("3.7,1.8","Name") fs = fs..string.format("field[1,2.5;2,1;carid;;%s]",minetest.formspec_escape(mem.newconnection.carid)) fs = fs..string.format("field[4,2.5;4,1;name;;%s]",minetest.formspec_escape(mem.newconnection.name)) fs = fs..mtos.theme:get_button("3,4;3,1","major","save","Save") fs = fs..mtos.theme:get_button("3,5.5;3,1","major","cancel","Cancel") elseif ram.screenstate == "editconnection" then fs = fs..mtos.theme:get_label("0.5,0.5","EDIT CONNECTION") fs = fs..mtos.theme:get_label("0.7,1.8","ID: "..mem.connections[mem.selectedconnection].carid) fs = fs..mtos.theme:get_label("3.7,1.8","Name") fs = fs..string.format("field[4,2.5;4,1;name;;%s]",minetest.formspec_escape(mem.connections[mem.selectedconnection].name)) fs = fs..mtos.theme:get_button("3,4;3,1","major","save","Save") fs = fs..mtos.theme:get_button("3,5.5;3,1","major","cancel","Cancel") elseif ram.screenstate == "notfound" then fs = fs..mtos.theme:get_label("0.5,0.5","Error") fs = fs..mtos.theme:get_label("0.5,1","Could not find a controller or dispatcher with the given ID.") fs = fs..mtos.theme:get_label("0.5,1.3","Please check the ID number and try again.") fs = fs..mtos.theme:get_button("0.5,3;2,1","major","ok","OK") elseif ram.screenstate == "controllerstatus" then local connection = mem.connections[mem.selectedconnection] local pos = connection.pos if celevator.controller.iscontroller(pos) then local meta = minetest.get_meta(pos) local cmem = minetest.deserialize(meta:get_string("mem")) if not cmem then return end 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 carpos = 0 local carfloor = 0 local searchpos = cmem.drive.status.apos for k,v in ipairs(cmem.params.floorheights) do carpos = carpos+v if carpos > searchpos then carfloor = k break end end fs = fs..mtos.theme:get_label("1,1",string.format("Connected to %s (ID %d)",connection.name,connection.carid)) fs = fs..mtos.theme:get_label("1,2",modenames[cmem.carstate]) fs = fs..mtos.theme:get_label("1,2.5",string.format("Doors %s",doorstates[cmem.doorstate])) local pi = minetest.formspec_escape(cmem.params.floornames[carfloor]) fs = fs..mtos.theme:get_label("1,3",string.format("Position: %0.02fm Speed: %+0.02fm/s PI: %s",cmem.drive.status.apos,cmem.drive.status.vel,pi)) if #cmem.faultlog > 0 then fs = fs..mtos.theme:get_label("1,3.5","Fault(s) Active") else fs = fs..mtos.theme:get_label("1,3.5","No Current Faults") end fs = fs.."background9[8,0.3;6.2,10;celevator_fs_bg.png;false;3]" fs = fs.."style_type[image_button;font=mono;font_size=*0.75]" fs = fs.."box[10.8,0.75;0.1,9;#AAAAAAFF]" fs = fs.."box[11.808,0.75;0.05,9;#AAAAAAFF]" fs = fs.."box[12.708,0.75;0.05,9;#AAAAAAFF]" fs = fs.."box[13.725,0.75;0.1,9;#AAAAAAFF]" fs = fs.."label[11.25,0.3;UP]" fs = fs.."label[12.042,0.3;CAR]" fs = fs.."label[12.825,0.3;DOWN]" if mem.scrollfollowscar then mem.screenpage = math.floor((carfloor-1)/10)+1 end local maxfloor = #cmem.params.floornames local bottom = (mem.screenpage-1)*10+1 if maxfloor > 10 then fs = fs..string.format("checkbox[8.4,1.5;scrollfollowscar;Follow Car;%s]",tostring(mem.scrollfollowscar)) if bottom+9 < maxfloor then fs = fs.."image_button[8.5,1;0.75,0.75;celevator_menu_arrow.png;scrollup;;false;false;celevator_menu_arrow.png]" end if bottom > 1 then fs = fs.."image_button[8.5,2.25;0.75,0.75;celevator_menu_arrow.png^\\[transformFY;scrolldown;;false;false;celevator_menu_arrow.png^\\[transformFY]" end end for i=0,9,1 do local ypos = (11-(i*0.9))*0.9-0.75 local floornum = bottom+i if floornum > maxfloor then break end fs = fs..string.format("label[10.125,%f;%s]",ypos-0.2,minetest.formspec_escape(cmem.params.floornames[floornum])) local ccdot = cmem.carcalls[floornum] and "*" or "" if carfloor == floornum then local cargraphics = { open = "\\[ \\]", opening = "\\[< >\\]", closing = "\\[> <\\]", closed = "\\[ | \\]", testtiming = "\\[ | \\]", } ccdot = cargraphics[cmem.doorstate] if cmem.direction == "up" then ccdot = minetest.colorize("#55FF55",ccdot) elseif cmem.direction == "down" then ccdot = minetest.colorize("#FF5555",ccdot) end end fs = fs..string.format("image_button[11.925,%f;0.75,0.75;celevator_fs_bg.png;carcall%d;%s]",ypos-0.25,floornum,ccdot) if floornum < maxfloor then local arrow = cmem.upcalls[floornum] and minetest.colorize("#55FF55","^") or "" if cmem.params.groupmode == "group" then arrow = cmem.groupupcalls[floornum] and minetest.colorize("#55FF55","^") or "" arrow = (cmem.swingupcalls[floornum] and minetest.colorize("#FFFF55","^") or "")..arrow end fs = fs..string.format("image_button[11.025,%f;0.75,0.75;celevator_fs_bg.png;upcall%d;%s]",ypos-0.25,floornum,arrow) end if floornum > 1 then local arrow = cmem.dncalls[floornum] and minetest.colorize("#FF5555","v") or "" if cmem.params.groupmode == "group" then arrow = cmem.swingdncalls[floornum] and minetest.colorize("#FFFF55","v") or "" arrow = (cmem.groupdncalls[floornum] and minetest.colorize("#FF5555","v") or "")..arrow end fs = fs..string.format("image_button[12.825,%f;0.75,0.75;celevator_fs_bg.png;downcall%d;%s]",ypos-0.25,floornum,arrow) end end else ram.screenstate = "notfound" end fs = fs..mtos.theme:get_button("1,8;3,1","major","disconnect","Disconnect") end return fs end, receive_fields_func = function(app,mtos,_,fields) local ram = mtos.bdev:get_app_storage("ram","celevator") local mem = mtos.bdev:get_app_storage("hdd","celevator") if ram.screenstate == "welcome" then if fields.connections then ram.screenstate = "connections" end elseif ram.screenstate == "connections" then local exp = fields.connection and minetest.explode_textlist_event(fields.connection) or {} if fields.new then ram.screenstate = "newconnection" mem.newconnection.name = "Untitled" mem.newconnection.carid = "" elseif fields.edit then ram.screenstate = "editconnection" elseif fields.delete then table.remove(mem.connections,mem.selectedconnection) mem.selectedconnection = math.max(1,#mem.connections) elseif fields.moveup then local connection = mem.connections[mem.selectedconnection] table.remove(mem.connections,mem.selectedconnection) table.insert(mem.connections,mem.selectedconnection+1,connection) mem.selectedconnection = mem.selectedconnection+1 elseif fields.movedown then local connection = mem.connections[mem.selectedconnection] table.remove(mem.connections,mem.selectedconnection) table.insert(mem.connections,mem.selectedconnection-1,connection) mem.selectedconnection = mem.selectedconnection-1 elseif fields.connection and exp.type == "CHG" then mem.selectedconnection = #mem.connections-exp.index+1 elseif fields.connect or exp.type == "DCL" then if exp.type == "DCL" then mem.selectedconnection = #mem.connections-exp.index+1 end local connection = mem.connections[mem.selectedconnection] if connection.itemtype == "controller" and celevator.controller.iscontroller(connection.pos) then ram.screenstate = "controllerstatus" app:get_timer():start(0.2) elseif connection.itemtype == "dispatcher" and celevator.dispatcher.isdispatcher(connection.pos) then ram.screenstate = "dispatcherstatus" app:get_timer():start(0.2) else ram.screenstate = "notfound" end end elseif ram.screenstate == "editconnection" then if fields.save then mem.connections[mem.selectedconnection].name = fields.name ram.screenstate = "connections" elseif fields.cancel then ram.screenstate = "connections" end elseif ram.screenstate == "newconnection" then if fields.save then local carid = tonumber(fields.carid) if not (carid and math.floor(carid) == carid) then return end local carinfo = minetest.deserialize(celevator.storage:get_string(string.format("car%d",carid))) if not carinfo then return end local pos = carinfo.controllerpos local itemtype if pos and celevator.controller.iscontroller(pos) then itemtype = "controller" elseif carinfo.dispatcherpos and celevator.dispatcher.isdispatcher(carinfo.dispatcherpos) then pos = carinfo.dispatcherpos itemtype = "dispatcher" else ram.screenstate = "notfound" return end local connection = { name = fields.name, carid = carid, itemtype = itemtype, pos = pos, } table.insert(mem.connections,connection) mem.selectedconnection = #mem.connections ram.screenstate = "connections" elseif fields.cancel then ram.screenstate = "connections" end elseif ram.screenstate == "notfound" then if fields.ok then ram.screenstate = "newconnection" end elseif ram.screenstate == "controllerstatus" then if fields.disconnect then ram.screenstate = "connections" return end local pos = mem.connections[mem.selectedconnection].pos if celevator.controller.iscontroller(pos) then local meta = minetest.get_meta(pos) local cmem = minetest.deserialize(meta:get_string("mem")) if not cmem then return end for i=1,#cmem.params.floornames,1 do if fields[string.format("carcall%d",i)] and (cmem.carstate == "normal" or cmem.carstate == "test" or cmem.carstate == "capture" or cmem.carstate == "indep") then celevator.controller.run(pos,{ type = "remotemsg", source = 0, channel = "carcall", msg = i, }) elseif fields[string.format("upcall%d",i)] and cmem.carstate == "normal" and not cmem.capturesw then if cmem.params.groupmode == "group" then celevator.controller.run(pos,{ type = "remotemsg", channel = "swingupcall", msg = i, }) else celevator.controller.run(pos,{ type = "remotemsg", channel = "upcall", msg = i, }) end elseif fields[string.format("downcall%d",i)] and cmem.carstate == "normal" and not cmem.capturesw then if cmem.params.groupmode == "group" then celevator.controller.run(pos,{ type = "remotemsg", channel = "swingdncall", msg = i, }) else celevator.controller.run(pos,{ type = "remotemsg", channel = "dncall", msg = i, }) end end end if fields.scrolldown then mem.screenpage = math.max(1,mem.screenpage-1) mem.scrollfollowscar = false elseif fields.scrollup then mem.screenpage = math.min(mem.screenpage+1,math.floor((#cmem.params.floornames-1)/10)+1) mem.scrollfollowscar = false elseif fields.scrollfollowscar then mem.scrollfollowscar = (fields.scrollfollowscar == "true") end end end end, on_timer = function(_,mtos) local ram = mtos.bdev:get_app_storage("ram","celevator") local loop = { ["controllerstatus"] = true, ["dispatcherstatus"] = true, } return loop[ram.screenstate] end, })