Advertisement
Rihlsul

ElevatorControl Motor 1.01

Mar 7th, 2013
814
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.79 KB | None | 0 0
  1. -- Todo
  2.     -- Detect floor collisions
  3.  
  4. local version = 1.01
  5.  
  6. local MotorConfig = {}
  7. local MotorScreen = {}
  8. local motorSettingsFile = "Motor.Settings"
  9.  
  10. local floorCount = 0
  11. local floorMap = { }
  12. local floorDesc = { }
  13. local floorPass = { }
  14. local currentFloor = nil
  15. local callQueue = { }
  16.  
  17. local movingUp = false
  18. local movingDown = false
  19. local doorOpen = false
  20. local KeepWaiting = true
  21.  
  22. -- ######################################################
  23. -- ##  Load the API                                    ##
  24. -- ######################################################
  25.  
  26. if fs.exists("/rom/apis/ECAPI") then --Check to make sure that the entered API exists
  27.   os.loadAPI("/rom/apis/ECAPI")
  28.   print("ECAPI loaded.")
  29. else
  30.   print("That API does not exist!")
  31. end
  32.  
  33. -- ######################################################
  34. -- ##  Setup Configuration                             ##
  35. -- ######################################################
  36.  
  37. local DefaultConfig = {}
  38. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Version",version,"Please do not change this.")
  39. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Network","","Elevator network name:")
  40. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Modem","","Which side is the modem?",'side')
  41. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Up","","Which side controls Up?",'side')
  42. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Down","","Which side controls Down?",'side')
  43. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Debug","error","What level of debug output?")
  44. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"ConfigPW","","Config. Password:",'pass')   -- note, this is per machine
  45.  
  46.  
  47. local function networkName()
  48.     return ECAPI.getConfigOption(MotorConfig,"Network")
  49. end
  50.  
  51. local function modemSide()
  52.     return ECAPI.getConfigOption(MotorConfig,"Modem")
  53. end
  54.  
  55. local function upControlSide()
  56.     return ECAPI.getConfigOption(MotorConfig,"Up")
  57. end
  58.  
  59. local function downControlSide()
  60.     return ECAPI.getConfigOption(MotorConfig,"Down")
  61. end
  62.  
  63. -- ######################################################
  64. -- ##  Terminal Functions                              ##
  65. -- ######################################################
  66.  
  67. local function termClear()
  68.     ECAPI.printMotorTemplate(MotorConfig)
  69.     MotorScreen = ECAPI.setupScreen(MotorScreen)
  70.     term.setCursorPos(3,18)
  71.     write("Command: ")
  72. end
  73.  
  74. local function justPrintInfo(MotorScreen,moduleID,sourceID,floorID,newLine)
  75.     local t = "["..ECAPI.padLeft(moduleID,6," ").."]"
  76.     t = t .. "["..ECAPI.padLeft(sourceID,4," ")..":"
  77.     t = t .. ECAPI.padLeft(floorID,2," ").."]: " .. ECAPI.padLeft(newLine,24," ")
  78.    
  79.     if (ECAPI.getConfigOption(MotorConfig,"Debug") == "info") then
  80.         MotorScreen = ECAPI.justPrintLine(MotorScreen,t)
  81.     end
  82.     return MotorScreen
  83. end
  84.  
  85. local function justPrintLine(newLine)
  86.     MotorScreen = ECAPI.justPrintLine(MotorScreen,newLine)
  87. end
  88.  
  89. -- ######################################################
  90. -- ##  Settings Functions                              ##
  91. -- ######################################################
  92.  
  93. local function createNewSettings()
  94.     MotorConfig = DefaultConfig
  95.     termClear()
  96.     --MotorScreen = ECAPI.printConfig(MotorConfig,MotorScreen)
  97.  
  98.     -- Confirm settings with the user, then save to file
  99.     MotorScreen = ECAPI.addPrintLine(MotorScreen,"No settings file found. Confirming settings:")
  100.     --MotorScreen = ECAPI.printConfig(MotorConfig,MotorScreen)
  101.  
  102.     -- To ensure this code plays nice with multiple elevators, Unique network names are required (as best as can)
  103.     MotorScreen, MotorConfig = ECAPI.getConfigFromUser(MotorScreen,MotorConfig,"Network")
  104.     MotorScreen = ECAPI.printConfig(MotorConfig,MotorScreen,"Network")
  105.    
  106.     -- Confirm modem side
  107.     MotorScreen, MotorConfig = ECAPI.getConfigFromUser(MotorScreen,MotorConfig,"Modem")
  108.     MotorScreen = ECAPI.printConfig(MotorConfig,MotorScreen,"Modem")
  109.    
  110.     -- Confirm up control side
  111.     MotorScreen, MotorConfig = ECAPI.getConfigFromUser(MotorScreen,MotorConfig,"Up")
  112.     MotorScreen = ECAPI.printConfig(MotorConfig,MotorScreen,"Up")
  113.    
  114.     -- Confirm down control side
  115.     MotorScreen, MotorConfig = ECAPI.getConfigFromUser(MotorScreen,MotorConfig,"Down")
  116.     MotorScreen = ECAPI.printConfig(MotorConfig,MotorScreen,"Down")
  117.    
  118.     MotorScreen, MotorConfig = ECAPI.getConfigFromUser(MotorScreen,MotorConfig,"Debug")
  119.     MotorScreen = ECAPI.printConfig(MotorConfig,MotorScreen,"Debug")
  120.        
  121.     -- Write to file
  122.     ECAPI.table_save(MotorConfig,motorSettingsFile)
  123.     termClear()
  124. end
  125.  
  126. local function startupConfirmation()
  127.     -- Check if there's a settings file
  128.     if fs.exists(motorSettingsFile) then
  129.         termClear()
  130.         MotorConfig = ECAPI.table_load(motorSettingsFile)
  131.         ECAPI.verifyConfig(DefaultConfig,MotorConfig)
  132.         termClear()
  133.         MotorScreen = ECAPI.justPrintLine(MotorScreen,"Settings loaded.")
  134.         --MotorScreen = ECAPI.printConfig(MotorConfig,MotorScreen)
  135.         os.sleep(0.5)
  136.     else
  137.         createNewSettings()
  138.     end
  139.     ECAPI.openModem(modemSide())
  140.     -- see if there's any running Doors
  141.     ECAPI.broadcast(networkName(),'controlreset')
  142.     --MotorScreen = ECAPI.justPrintLine(MotorScreen,"Control Reset starting... Please wait.")
  143. end
  144.  
  145.  
  146. -- ######################################################
  147. -- ##  Control Functions                               ##
  148. -- ######################################################
  149.  
  150.  
  151. local function getIDFromFloor(whichFloor)
  152.     reverseMap = ECAPI.table_invert(floorMap)
  153.     MotorScreen = justPrintInfo(MotorScreen,"Which: "..ECAPI.padLeft(whichFloor,4," ").."  : "..ECAPI.padLeft(reverseMap[tonumber(whichFloor)],4," "))
  154.     return reverseMap[whichFloor]
  155. end
  156.  
  157. local function testUp()
  158.     MotorScreen = justPrintInfo(MotorScreen,"MOTOR",nil,nil,"In 3, ["..upControlSide().."] will blink 3.")
  159.     os.sleep(3)
  160.     for i = 1,3 do
  161.         redstone.setOutput(upControlSide(),true)
  162.         os.sleep(0.2)
  163.         redstone.setOutput(upControlSide(),false)
  164.         os.sleep(0.2)
  165.     end
  166. end
  167.  
  168. local function testDown()
  169.     MotorScreen = justPrintInfo(MotorScreen,"MOTOR",nil,nil,"In 3, ["..downControlSide().."] will blink 3.")
  170.     os.sleep(3)
  171.     for i = 1,3 do
  172.         redstone.setOutput(downControlSide(),true)
  173.         os.sleep(0.2)
  174.         redstone.setOutput(downControlSide(),false)
  175.         os.sleep(0.2)
  176.     end
  177. end
  178.  
  179. local function addFloor(computerID,whichFloor,description,floorPW)
  180.     floorMap[computerID] = tonumber(whichFloor)
  181.     floorDesc[tonumber(whichFloor)] = description
  182.     if (floorPW ~= nil) and (floorPW ~= "") then
  183.         floorPass[tonumber(whichFloor)] = floorPW
  184.     end
  185.     floorCount = floorCount + 1
  186. end
  187.  
  188. local function printFloors()
  189.     -- first, move the floor map to be keyed on the floor ID, rather than computer ID
  190.     local floorListing = {}
  191.     for i,v in pairs(floorMap) do
  192.         floorListing[tonumber(v)] = tonumber(i)
  193.     end
  194.     table.sort(floorListing,reverseSort)
  195.  
  196.     justPrintLine("Printing floorlist")
  197.     justPrintLine("Floor  SysID   Description")
  198.     justPrintLine("--------------------------")
  199.     if (floorCount > 0) then
  200.         for fl,comp in ECAPI.ripairs(floorListing) do
  201.             local indicator = ""
  202.             if (currentFloor == fl) then indicator = "*" else indicator = "" end
  203.             justPrintLine(ECAPI.padLeft(fl..indicator,7,' ')..ECAPI.padLeft(comp,8,' ')..ECAPI.padLeft(floorDesc[fl],35,' '))
  204.         end
  205.     else
  206.         justPrintLine("* No Floor computers registered. *")
  207.     end
  208. end
  209.  
  210. local function startMotion()
  211.     -- determine next direction of motion based on next queue entry
  212.     local queueCount = # callQueue
  213.     if (queueCount > 0) then
  214.         local callFloor = floorMap[callQueue[1]]
  215.         if (currentFloor == nil) then
  216.             -- the elevator got lost
  217.            
  218.         elseif (callFloor > currentFloor) then
  219.             movingUp = true
  220.             ECAPI.broadcast(networkName(),'displayup|/\\|'..currentFloor)
  221.         elseif (callFloor < currentFloor) then
  222.             movingDown = true
  223.             ECAPI.broadcast(networkName(),'displaydown|\\/|'..currentFloor)
  224.         else
  225.             -- open the door, then remove the request
  226.             ECAPI.broadcast(networkName(),'opendoor|'..callQueue[1])
  227.             table.remove(callQueue,1)
  228.         end
  229.     end
  230. end
  231.  
  232. local function detectArrival(sendID)
  233.     currentFloor = floorMap[sendID]
  234.    
  235.     -- If the first floor to 'call out' is the currentFloor, the map might not be populated yet
  236.     if (currentFloor ~= nil) then
  237.        
  238.         -- detect if the floor is one of the ones in the queue.  if so, pause motion for a bit.
  239.         if ECAPI.tableContainsValue(callQueue,sendID) then
  240.             --MotorScreen = justPrintInfo(MotorScreen,"On the queued floor. Pausing, opening door")
  241.             movingUp = false
  242.             ECAPI.broadcast(networkName(),'displayup|  ')
  243.             movingDown = false
  244.             ECAPI.broadcast(networkName(),'displaydown|  ')
  245.             ECAPI.broadcast(networkName(),'opendoor|'..sendID)
  246.             os.sleep(5)
  247.             ECAPI.broadcast(networkName(),'opendoor|invalid')
  248.            
  249.             table.remove(callQueue,1)
  250.         end
  251.         startMotion()
  252.     end
  253. end
  254.  
  255. local function addCallQueue(sendID)
  256.     local callFloor = floorMap[sendID]
  257.    
  258.     -- add the call to the queue after checking to make sure it's not a duplicate
  259.     local found = false
  260.     if (callQueue ~= nil) then
  261.         for i,v in ipairs(callQueue) do
  262.             if v == sendID then found = true end
  263.         end
  264.     end
  265.     if not found then
  266.         table.insert(callQueue,sendID)
  267.     end
  268.    
  269.     if (not movingUp) and (not movingDown) then
  270.         startMotion()
  271.     end
  272. end
  273.  
  274. local function rebootAll()
  275.     ECAPI.broadcast(networkName(),'reboot')
  276.     os.reboot()
  277. end
  278.  
  279. local function sendFloors(toID)
  280.     local floors = { }
  281.     for floorID, desc in pairs(floorDesc) do
  282.         local pass = ""
  283.         if (floorPass[floorID] ~= nil) then
  284.             pass = floorPass[floorID]
  285.         end
  286.         floors[floorID] = { ['Description']= desc , ['Password'] = pass}
  287.     end
  288.    
  289.     local t = textutils.serialize(floors)
  290.     ECAPI.send(networkName(),toID,"floordata|"..t)
  291. end
  292.  
  293. local function reconfig(args)
  294.     if (ECAPI.checkPassword(MotorScreen,MotorConfig,"ConfigPW")) then
  295.         local what = args[1]
  296.         if (what == nil) or (what == "") then
  297.             MotorConfig = DefaultConfig
  298.             createNewSettings()
  299.         else
  300.             what = ECAPI.caseCheck(MotorConfig,what)
  301.             if (args[2] == nil) then
  302.                 MotorScreen, MotorConfig = ECAPI.getConfigFromUser(MotorScreen,MotorConfig,what)
  303.             else
  304.                 MotorConfig = ECAPI.setConfigOption(MotorConfig,what,table.concat(args," ",2))
  305.             end
  306.             -- Make sure to SAVE changes
  307.             ECAPI.table_save(MotorConfig,motorSettingsFile)
  308.            
  309.             -- if the modem changed, this makes sure we reopen it
  310.             ECAPI.openModem(modemSide())
  311.             -- see if there's any running Doors
  312.             ECAPI.broadcast(networkName(),'controlreset')
  313.         end
  314.     else
  315.         justPrintLine("Invalid password.")
  316.     end
  317. end
  318.  
  319. -- ######################################################
  320. -- ##  Input Actions                                   ##
  321. -- ######################################################
  322.  
  323. -- MAX Topic details: 45 characters per table entry
  324. --                      |---------------------------------------------|
  325. local helpTopics = {
  326.     ["reboot"]      = { "Restart this terminal." ,},
  327.     ["clear"]       = { "Refresh the terminal screen." ,},
  328.     ["config"]      = { "Prints the current settings. Will not print",
  329.                         "any set passwords." ,},
  330.     ["reconfig"]    = { "With no parameters, re-run the whole setup.",
  331.                         "RECONFIG [option] to change a single setting",
  332.                         "Note: Reconfig can be password protected via",
  333.                         "the ConfigPW setting. If this password is",
  334.                         "lost, you will need server admin help or be",
  335.                         "forced to recreate the terminal." ,},
  336.     ["broadcast"]   = { "Manually broadcast <message>",},
  337.     ["send"]        = { "Manually send <id> <message>",},
  338.  
  339.     ["testup"]      = { "Blinks the Up control side." },
  340.     ["testdown"]    = { "Blinks the Down control side.", },
  341.     ["floors"]      = { "Print list of floors with Door IDs"},
  342.     ["rebootall"]   = { "Sends a restart command to the whole network.",},
  343. }
  344.  
  345. local function printHelp(topic)
  346.     justPrintLine("Help on '"..string.upper(topic).."':")
  347.     local t = helpTopics[topic]
  348.     for _, text in ipairs(t) do
  349.         justPrintLine("  "..text)
  350.     end
  351. end
  352.  
  353. local commands = {
  354.     ["help"] = function (x)
  355.             if (x[1] ~= nil) then
  356.                 printHelp(x[1])
  357.             else
  358.                 justPrintLine("Please use HELP [COMMAND]. ? for Command List")
  359.             end
  360.         end,
  361.     ["reboot"] = function (x) os.reboot() end,
  362.     ["clear"] = function (x) termClear() end,
  363.     ["broadcast"] = function (x)
  364.             MotorScreen = justPrintInfo(MotorScreen,"REDNET",nil,nil,"Broadcast: "..table.concat(x," ",2))
  365.             ECAPI.broadcast(networkName(),table.concat(x," "))
  366.         end,
  367.     ["send"] = function (x)
  368.             MotorScreen = justPrintInfo(MotorScreen,"REDNET",nil,nil,"Send("..tonumber(x[1]).."): "..table.concat(x," ",2))
  369.             ECAPI.send(networkName(),tonumber(x[1]),table.concat(x," ",2))
  370.         end,
  371.    
  372.     ["config"] = function (x)
  373.             justPrintLine("Current Configuration:")
  374.             justPrintLine("-------------------------------")
  375.             ECAPI.printConfig(MotorConfig,MotorScreen)
  376.         end,
  377.     ["reconfig"] = function (x) reconfig(x) end,
  378.  
  379.  
  380.     ["floors"] = function (x) printFloors() end,
  381.     ["rebootall"] = function (x) rebootAll() end,
  382.    
  383.     ["testup"] = function (x) testUp(x) end,
  384.     ["testdown"] = function (x) testDown(x) end,
  385. }
  386.  
  387. local function printCommands()
  388.     local col = 1
  389.     local row = {}
  390.     for named, _ in pairs(commands) do
  391.         if (col < 5) then row[col] = string.upper(named) end
  392.         col = col + 1
  393.         if (col == 5) then
  394.             justPrintLine(ECAPI.padLeft(row[1],12," ")..ECAPI.padLeft(row[2],12," ")..ECAPI.padLeft(row[3],12," ")..ECAPI.padLeft(row[4],11," "))
  395.             row = {}
  396.             col = 1
  397.         end
  398.     end
  399.     if (col > 1) then
  400.         -- handles 'remainders'
  401.         justPrintLine(ECAPI.padLeft(row[1],12," ")..ECAPI.padLeft(row[2],12," ")..ECAPI.padLeft(row[3],12," ")..ECAPI.padLeft(row[4],11," "))
  402.     end
  403. end
  404.  
  405.  
  406. local function processCommands(input)
  407.     local arg = ECAPI.explode(" ",input)
  408.     local command = arg[1]
  409.     table.remove(arg,1)
  410.     if ECAPI.tableContainsKey(commands, command) then
  411.         commands[command](arg)
  412.     else
  413.         justPrintLine("Unknown command '"..command.."'. Commands are:")
  414.         printCommands()
  415.     end
  416. end
  417.  
  418.  
  419.  
  420. -- ######################################################
  421. -- ##  Handler Functions                               ##
  422. -- ######################################################
  423.  
  424. -- User input listener
  425. local function userConsole()
  426.     while KeepWaiting do
  427.         processCommands(ECAPI.getInput("Command:"))
  428.     end
  429. end
  430.  
  431. -- Rednet listener
  432. local function rednetListener()
  433.     while KeepWaiting do       
  434.         local sendID, rawmessage, distance = rednet.receive()
  435.         local packet = ECAPI.explode("|",rawmessage)
  436.         if packet[1] == networkName() then
  437.             MotorScreen = justPrintInfo(MotorScreen,"REDNET",sendID,floorMap[sendID],table.concat(packet," ",2))
  438.             if string.lower(packet[2]) ==     'addfloor'       then
  439.                 addFloor(tonumber(sendID),packet[3],packet[4],packet[5])
  440.             elseif string.lower(packet[2]) == 'call'           then
  441.                 addCallQueue(tonumber(sendID))
  442.             elseif string.lower(packet[2]) == 'arrival'           then
  443.                 detectArrival(tonumber(sendID))
  444.             elseif string.lower(packet[2]) == 'getfloors'           then
  445.                 sendFloors(tonumber(sendID))
  446.             elseif string.lower(packet[2]) == 'openfloor'           then
  447.                 if (not movingUp) and (not movingDown) then
  448.                     --MotorScreen = ECAPI.justPrintLine(MotorScreen,'Opening Door Floor[id]: '..currentFloor..'['..getIDFromFloor(currentFloor)..']')
  449.                     ECAPI.broadcast(networkName(),'opendoor|'..tostring(getIDFromFloor(currentFloor)))
  450.                     doorOpen = true
  451.                 end
  452.             elseif string.lower(packet[2]) == 'request'          then
  453.                 addCallQueue(getIDFromFloor(tonumber(packet[3])))
  454.             end
  455.         end
  456.     end
  457. end
  458.  
  459. local function cabinFinder()
  460.     -- always wait for possible bootup
  461.     os.sleep(6)
  462.     local maxMove = 50    
  463.     local currMove = 0
  464.     -- try to move 50 up to find a station, stop if we find it
  465.     while (currentFloor == nil) and KeepWaiting and (currMove < maxMove) do
  466.         redstone.setOutput(upControlSide(),true)
  467.         os.sleep(0.2)
  468.         redstone.setOutput(upControlSide(),false)
  469.         os.sleep(2)
  470.         currMove = currMove + 1
  471.     end
  472.     -- try to move 100 down to find a station, stop if we find it
  473.     while (currentFloor == nil) and KeepWaiting and (currMove > (-maxMove*2)) do
  474.         redstone.setOutput(upControlSide(),true)
  475.         os.sleep(0.2)
  476.         redstone.setOutput(upControlSide(),false)
  477.         os.sleep(2)
  478.         currMove = currMove - 1
  479.     end
  480. end
  481.  
  482. local function mover()
  483.     while KeepWaiting do
  484.         if (movingUp) then
  485.             redstone.setOutput(upControlSide(),true)
  486.             os.sleep(0.2)
  487.             redstone.setOutput(upControlSide(),false)
  488.             ECAPI.broadcast(networkName(),'displayup|/\\|'..currentFloor)
  489.         elseif (movingDown) then
  490.             redstone.setOutput(downControlSide(),true)
  491.             os.sleep(0.2)
  492.             redstone.setOutput(downControlSide(),false)
  493.             ECAPI.broadcast(networkName(),'displaydown|\\/|'..currentFloor)
  494.         end
  495.         os.sleep(.8)
  496.     end
  497. end
  498.  
  499. local function doorCloser()
  500.     -- if a door is manually opened, close the door. Check every 10 seconds
  501.     while KeepWaiting do
  502.         if doorOpen then
  503.             ECAPI.broadcast(networkName(),'closedoor')
  504.             doorOpen = false
  505.         end
  506.         os.sleep(10)
  507.     end
  508. end
  509.  
  510.  
  511. -- ######################################################
  512. -- ##  Actual Run Program                              ##
  513. -- ######################################################
  514. startupConfirmation()
  515. parallel.waitForAll(userConsole,rednetListener,cabinFinder,mover,doorCloser)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement