PonyKuu

master API

Mar 17th, 2013
539
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.59 KB | None | 0 0
  1. ----------------------------------------------------------------------
  2. --                              Master API                          --
  3. ----------------------------------------------------------------------
  4.  
  5. -- Version 0.1
  6.  
  7. -- Master is an API that controls modules that use my module API.
  8. -- Master should have:
  9. --  1) fuel chest in the first slot.
  10. --  2) stuff chest in the second slot
  11. --  3) wireless mining turtle in the third slot
  12. --  4) Any number of fuel/stuff chests and wireless mining turtles in its inventory
  13. --  5) Note, that Master keeps at least one of each of those items in it's inventory.
  14. -- Master's job is to listen to requests and send responses.
  15. -- Usage:
  16. --  1) For each type of the module you use, make a function which returns a response which will determine what module will do.
  17. --     This function recieves the ID of that module as an argument
  18. --     Of course, your module should know about that response and have that in its task table.
  19. --     You can use master.makeTask(taskName, coordinates, additionalData) function to generate a task response.
  20. --     additionalData parameter is optional, and if you don't want to send any coordinates, pass an empty table as coordinates.
  21. --     There is a master.makeReturnTask() function added for convinience - it just makes a task that returns module to the Master
  22. --  2) Use master.setType(Type, taskFunction) function to associate that function with the type of the module
  23. --     you might want to change the state of the module in your taskFunction. Use master.getState(ID) and master.setState(ID, state)
  24. --     functions to do that. Initial state of each module is "Waiting"
  25. --  4) Set navigation zones for modules using the master.setNavigation(x, z, height) function
  26. --     Please notice that x, z are 2D coordinates and height is the height of the plane where all the navigation is done
  27. --  5) Initialize the master by running master.init(filename, ID, mainChannel, moduleCount) function
  28. --     Master should have module API on its disk and a script for modules (that's the filename argument)
  29. --     mainChannel is the channel on which master listens, and the moduleCount is the required amount of modules
  30. --     ID is a new ID of the Master
  31. --  6) make a stateFunction() that determines whether master should stop the operation. It should return false if it should stop
  32. --     and return true if it shouldn't. State function can do other things, such as reinitialization to a new module script and so on
  33. --     but it shouldn't take too much time to execute. Use master.reinit(filename, moduleCount) function to change the module script, if you want to.
  34. --  7) Run master.operate(stateFunction) to start.
  35.  
  36. -- Some basic variables:
  37. -- MasterID is the unique identifier of master. Default is 100
  38. local MasterID = 100
  39.  
  40. -- Channel is the channel Master listens.
  41. local channel
  42. -- Position is just the position of the Master.
  43. local Position = {x = 0, y = 0, z = 0, f = 0}
  44. -- New placed modules will request "Position" to know where they are.
  45. local modPosition = {x = 1, y = 0, z = 0, f = 0}
  46.  
  47. -- naviZones is used by modules to navigate and not interlock themselves
  48. local naviZones = {x = 0, z = 0, height = 0}
  49. function setNavigation (x, z, height)
  50.     naviZones = {x = x, z = z, height = height}
  51. end
  52.  
  53. --[[
  54. *********************************************************************************************
  55. *                                    Communication Part                                     *
  56. *********************************************************************************************
  57. ]]--
  58.  
  59. -- We need a modem to communicate.
  60. local modem = peripheral.wrap ("right")
  61.  
  62. -- The first function is used to parse a message received on modem
  63. -- It determines whether the message is valid or not
  64. -- Message should be a table with fields
  65. --  1) Protocol - must be equal to "KuuNet"
  66. --  2) ID - that's a sender ID
  67. --  3) Master - that must be equal to MasterID variable.
  68. --  4) Type - the type of the module. Used to know how to handle its task requests
  69. --  Some other fields
  70. function isValid (message)
  71.     if message ~= nil then
  72.         if message.Protocol ~= "KuuNet" then
  73.             return false
  74.         end
  75.         if message.ID == nil then
  76.             return false
  77.         end
  78.         if message.Master ~= MasterID then
  79.             return false
  80.         end
  81.         return true
  82.     else
  83.         return false
  84.     end
  85. end
  86.  
  87. -- The function that listens for a valid message and returns it
  88. function listen ()
  89.     while true do
  90.         local event, modemSide, sndChan, rplyChan, text_msg, senderDistance = os.pullEvent("modem_message")
  91.         local msg = textutils.unserialize (text_msg)
  92.         if isValid(msg) then
  93.             return msg
  94.         end
  95.     end
  96. end
  97.  
  98. -- And a function to send a response
  99. function response (ID, chan, message)
  100.     message.Protocol = "KuuNet"
  101.     message.ID = ID
  102.     message.Master = MasterID
  103.     modem.transmit (chan+1, chan, textutils.serialize(message))
  104. end
  105.  
  106. --[[
  107. *********************************************************************************************
  108. *                                   Module Placement Part                                   *
  109. *********************************************************************************************
  110. ]]--
  111. -- moduleCount is the number of active modules
  112. -- needModules is the number of modules required
  113. -- modulesAvailable is the number of available modules
  114. local moduleCount = 0
  115. local needModules = 0
  116. local modAvailable = nil
  117. -- This function searches the turtle inventory for same item
  118. -- as in the given slot, or selects that slot if there are more than one item
  119. function searchItem (slot)
  120.     turtle.select (slot)
  121.     local found = 0
  122.     for i=1,16 do
  123.         if i ~= slot then
  124.             if turtle.getItemCount (i) > 0 then
  125.                 if turtle.compareTo (i) then
  126.                     found = i
  127.                 end
  128.             end
  129.         end
  130.     end
  131.     if found == 0 then
  132.         if turtle.getItemCount (slot) > 1 then
  133.             found = slot
  134.         else
  135.             turtle.select(1)
  136.             return false
  137.         end
  138.     end
  139.     turtle.select (found)
  140.     return true
  141. end
  142. -- This is the function to search the whole inventory for the same items and return how many items are there
  143. local function countBySample (slot)
  144.     turtle.select (slot)
  145.     local count = 0
  146.     for i = 1,16 do
  147.         if turtle.compareTo(i) then
  148.             count = count + turtle.getItemCount(i)
  149.         end
  150.     end
  151.     return count
  152. end
  153. -- A function to know how many modules are available.
  154. -- A module is available if there is at least two turtles and at least two of each Ender chests left
  155. -- The counting takes some time, so there is a variable to speed up the process
  156. function availableModules ()
  157.     if modAvailable == nil then
  158.         local minimum = 1024
  159.         for i = 1, 3 do
  160.             local quantity = countBySample(i) - 1
  161.             if minimum > quantity then
  162.                 minimum = quantity
  163.             end
  164.         end
  165.         modAvailable = minimum
  166.     end
  167.     return modAvailable
  168. end
  169. -- Function to place a new module
  170. function addModule ()
  171.     searchItem (3)   -- search for a module
  172.     if turtle.place () then  -- put it down
  173.         searchItem (1)
  174.         turtle.drop (1)  -- Add a fuel chest
  175.         searchItem (2)
  176.         turtle.drop (1)  -- And a stuff chest
  177.         turtle.select (1)-- select first slot to make things look good :)
  178.         peripheral.call ("front", "turnOn")    -- Turn on the module    
  179.         modAvailable = modAvailable - 1
  180.     end
  181. end
  182.  
  183. --[[
  184. *********************************************************************************************
  185. *                                       Operation Part                                      *
  186. *********************************************************************************************
  187. ]]--
  188.  
  189. -- This is a table that contains all the states of the modules.
  190. -- Indexes of that table are ID's and values are tables with following fields
  191. --  1) Type - type of the module. Each Type has it's own task table
  192. --  2) State - the state of the module. List of states is unique for each module type, but there are some common states: "Waiting" and "Returning"
  193. tStates = {}
  194.  
  195. -- There is a function that searches for a free ID. Let's limit the maximun number of IDs to 1024
  196. function freeID ()
  197.     for i = 1, 1024 do
  198.         if tStates [i] == nil then
  199.             return i
  200.         end
  201.     end
  202. end
  203.  
  204. -- Tasks table. I'll explain it a bit later
  205. tTasks = {}
  206.  
  207. -- Here is the table used to handle the requests.
  208. tRequests = {
  209.     -- "Master" is the request sent by new modules. We should assign a new ID to it and remember that ID in tStates table
  210.     Master = function (request)
  211.         local ID = freeID ()
  212.         response (request.ID, channel, {NewID = ID})
  213.         tStates[ID] = {State = "Waiting", Type = request.Type}
  214.         moduleCount = moduleCount + 1
  215.     end,
  216.     -- Task is the request used to reques the next thing module should do
  217.     -- It uses the tTasks table. There is a function for each module type which returns a response
  218.     -- Response may contain coordinate of the next place and id should contain "Task" field
  219.     -- This function may do something else, change the state of module and so on.
  220.     -- To associate it with the module, sender ID is passed to that function
  221.     -- tTasks table should be filled by user of this API
  222.     Task = function (request)
  223.         response (request.ID, channel, tTasks[request.Type](request.ID))
  224.     end,
  225.     -- "Returned" is the request sent by module that has returned to Master and waiting there until Master collects it
  226.     Returned = function (request)
  227.         while turtle.suck () do end -- Get all the items that module had
  228.         turtle.dig ()               -- And get the module back
  229.         tStates[request.ID] = nil -- delete that module from our state table
  230.         moduleCount = moduleCount - 1 -- and decrease the counter
  231.         modAvailable = modAvailable + 1
  232.     end
  233. }
  234.  
  235. -- This is the variable used to determine whether the master should place modules
  236. local isPlacing = false
  237.  
  238. -- This is the function used to place modules if there are any available and state is
  239. -- It automatically stops placing modules when there is enough modules
  240. function moduleManager ()
  241.     while true do
  242.         if isPlacing then
  243.             if moduleCount == needModules or availableModules() == 0 then
  244.                 isPlacing = false
  245.             else
  246.                 addModule ()
  247.             end
  248.         end
  249.         sleep (6)
  250.     end
  251. end
  252.  
  253. -- The main operation function
  254. -- stateFunction is the function used to determine when master should stop.
  255. -- Master stops when there are to active modules and stateFunction returns false.
  256. -- State function can do other things, such as reinitialization to a new module script and so on, but it shouldn't take too much time to execute
  257. function operate (stateFunction)
  258.     local server = function ()
  259.         while stateFunction() or moduleCount > 0 do
  260.             local request = listen ()
  261.             tRequests[request.Request](request) -- Just execute the request handler.
  262.         end
  263.     end
  264.     parallel.waitForAny (server, moduleManager)
  265.     modem.closeAll ()
  266. end
  267.  
  268. --[[
  269. *********************************************************************************************
  270. *                                    Initialization Part                                    *
  271. *********************************************************************************************
  272. ]]--
  273.  
  274. -- Some basic movement and refueling
  275. -- Refuel assumes that Master has a bunch of fuel chests in slot 1 and slot 16 is empty
  276. function refuel (amount)
  277.     if turtle.getFuelLevel () > amount then
  278.         return true
  279.     else
  280.         turtle.select (1)
  281.         turtle.placeUp ()
  282.         turtle.select (16)
  283.             turtle.suckUp ()
  284.             while turtle.refuel (1) do
  285.                 if turtle.getFuelLevel() >= amount then
  286.                     turtle.dropUp ()
  287.                     turtle.select (1)
  288.                     turtle.digUp ()
  289.                     return true
  290.                 end
  291.                 if turtle.getItemCount (1) == 0 then
  292.                     turtle.suckUp ()
  293.                 end
  294.             end
  295.         end
  296.     turtle.dropUp ()
  297.     turtle.select (1)
  298.     turtle.digUp ()
  299.     return false
  300. end
  301.  
  302. local tMoves = {
  303.     forward = turtle.forward,
  304.     back = turtle.back
  305. }
  306. function forceMove (direction)
  307.     refuel (1)
  308.     while not tMoves[direction]() do
  309.         print "Movement obstructed. Waiting"
  310.         sleep (1)
  311.     end
  312. end
  313.  
  314. -- The function to set the "task" function for the said module type
  315. function setType (Type, taskFunction)
  316.     tTasks[Type] = taskFunction
  317. end
  318.  
  319. -- The initialization function of the master.
  320. -- Filename is the name of module's script. It will be copied on the disk drive
  321. function init (filename, ID, mainChannel, moduleCount)
  322.     -- Set the ID of the Master
  323.     MasterID = ID
  324.     -- Set the main channel and listen on said channel
  325.     channel = mainChannel
  326.     modem.open (channel)
  327.    
  328.     -- Next, we need to know the position of the master.
  329.     -- If gps is not found, use relative coordinates
  330.     modem.open(gps.CHANNEL_GPS)
  331.     local gpsPresent = true
  332.     local x, y, z = gps.locate(5)
  333.     if x == nil then
  334.         x, y, z = 0, 0, 0
  335.         gpsPresent = false
  336.     end
  337.    
  338.     -- Now we need to move forward to copy files on disk and determine our f
  339.     forceMove ("forward")
  340.     local newX, newZ = 1, 0 -- if there is no gps, assume that master is facing positive x
  341.     if gpsPresent then
  342.         newX, __, newZ = gps.locate (5)
  343.     end
  344.     modem.close(gps.CHANNEL_GPS)
  345.     -- Determine f by the difference of coordinates.
  346.     local xDiff = newX - x
  347.     local zDiff = newZ - z
  348.     if xDiff ~= 0 then
  349.         if xDiff > 0 then
  350.             f = 0     -- Positive x
  351.         else
  352.             f = 2     -- Negative x
  353.         end
  354.     else
  355.         if zDiff > 0 then
  356.             f = 1     -- Positive z
  357.         else
  358.             f = 3     -- Negative z
  359.         end
  360.     end
  361.     -- And set the position and modPosition variables.
  362.     Position = {x = x, y = y, z = z, f = f}
  363.     modPosition = {x = newX, y = y, z = newZ, f = f}
  364.    
  365.     -- Copy all the required files on the disk
  366.     if fs.exists ("/disk/module") then
  367.         fs.delete ("/disk/module")
  368.     end
  369.     fs.copy ("module", "/disk/module")
  370.     if fs.exists ("/disk/"..filename) then
  371.         fs.delete ("/disk/"..filename)
  372.     end
  373.     fs.copy (filename, "/disk/"..filename)
  374.     -- Make a startup file for modules
  375.     local file = fs.open ("/disk/startup", "w")
  376.     file.writeLine ("shell.run(\"copy\", \"/disk/module\", \"/module\")")
  377.     file.writeLine ("shell.run(\"copy\", \"/disk/"..filename.."\", \"/startup\")")
  378.     file.writeLine ("shell.run(\"startup\")")
  379.     file.close()
  380.    
  381.     -- Now, make a file with the data modules need
  382.     file = fs.open ("/disk/initdata", "w")
  383.     -- Communication data: master ID and communication channel
  384.     file.writeLine (textutils.serialize ({MasterID = MasterID, channel = channel}) )
  385.     -- Location data:
  386.     file.writeLine (textutils.serialize (modPosition))
  387.     -- Navigation data:
  388.     file.writeLine (textutils.serialize (naviZones))
  389.     file.close()
  390.    
  391.     -- And go back to initial location
  392.     forceMove ("back")
  393.    
  394.     -- Set the amount of modules needed. I use "+" because one can run init again to add a different type of module to the operation
  395.     needModules = needModules + moduleCount
  396.     -- And start placing them
  397.     isPlacing = true
  398. end
  399.  
  400.  
  401. --[[
  402. *********************************************************************************************
  403. *                                       Utility Part                                        *
  404. *********************************************************************************************
  405. ]]--
  406.  
  407. -- This is just a return response added for convinience, because every task function should return the module eventually.
  408. function makeReturnTask ()
  409.     -- return position is two block away of Master. So, let's calculate it.
  410.     local tShifts = {
  411.         [0] = { 1,  0},
  412.         [1] = { 0,  1},
  413.         [2] = {-1,  0},
  414.         [3] = { 0, -1},
  415.     }
  416.     local xShift, zShift = unpack (tShifts[modPosition.f])
  417.     returnX = modPosition.x + xShift
  418.     returnZ = modPosition.z + zShift
  419.     return {   Task = "Return",
  420.         x = returnX,
  421.         y = modPosition.y,
  422.         z = returnZ,
  423.         f = (modPosition.f+2)%4, -- basically, reverse direction
  424.     }
  425. end
  426.  
  427. -- And a function to make any other task
  428. -- additionalData is optional. coordinates can be an empty table
  429. function makeTask (taskName, coordinates, additionalData)
  430.     local newTask = {Task = taskName}
  431.     for key, value in pairs (coordinates) do  
  432.         newTask[key] = value
  433.     end
  434.     if additionalData ~= nil then
  435.         for key, value in pairs (additionalData) do  
  436.             newTask[key] = value
  437.         end
  438.     end
  439.     return newTask
  440. end
  441.  
  442. function getState (ID)
  443.     return tStates[ID].State
  444. end
  445.  
  446. function setState (ID, newState)
  447.     tStates[ID].State = newState
  448. end
  449.  
  450. -- reinit function is just a simplified init, that doesn't update channel, MasterID and Master's position. Use it to change the modules script
  451.  function reinit (filename, moduleCount)
  452.     forceMove ("forward")
  453.     -- Copy all the required files on the disk
  454.     if fs.exists ("/disk/module") then
  455.         fs.delete ("/disk/module")
  456.     end
  457.     fs.copy ("module", "/disk/module")
  458.     if fs.exists ("/disk/"..filename) then
  459.         fs.delete ("/disk/"..filename)
  460.     end
  461.     fs.copy (filename, "/disk/"..filename)
  462.     -- Make a startup file for modules
  463.     local file = fs.open ("/disk/startup", "w")
  464.     file.writeLine ("shell.run(\"copy\", \"/disk/module\", \"/module\")")
  465.     file.writeLine ("shell.run(\"copy\", \"/disk/"..filename.."\", \"/startup\")")
  466.     file.writeLine ("shell.run(\"startup\")")
  467.     file.close()
  468.    
  469.     -- Now, make a file with the data modules need
  470.     file = fs.open ("/disk/initdata", "w")
  471.     -- Communication data: master ID and communication channel
  472.     file.writeLine (textutils.serialize ({MasterID = MasterID, channel = channel}) )
  473.     -- Location data:
  474.     file.writeLine (textutils.serialize (modPosition))
  475.     -- Navigation data:
  476.     file.writeLine (textutils.serialize (naviZones))
  477.     file.close()
  478.    
  479.     -- And go back to initial location
  480.     forceMove ("back")
  481.    
  482.     -- Set the amount of modules needed. I use "+" because one can run init again to add a different type of module to the operation
  483.     needModules = needModules + moduleCount
  484.     -- And start placing them
  485.     isPlacing = true
  486. end
Advertisement
Add Comment
Please, Sign In to add comment