Advertisement
PonyKuu

Master 0.2a

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