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