· 8 years ago · Dec 01, 2017, 06:36 PM
1
2--[[
3Program name: Lolmer's EZ-NUKE reactor control system
4Version: v0.3.18
5Programmer: Lolmer
6With great assistance from @mechaet and @thetaphi
7Last update: 2015-05-11
8Pastebin: http://pastebin.com/fguScPBQ
9GitHub: https://github.com/sandalle/minecraft_bigreactor_control
10Description:
11This program controls a Big Reactors nuclear reactor in Minecraft with a Computercraft computer, using Computercraft's own wired modem connected to the reactors computer control port.
12These scripts are known to run on:
13- Minecraft 1.6.4
14 - NST Diet (https://www.technicpack.net/modpack/never-stop-toasting-diet.254882)
15 - NST Maxx (https://www.technicpack.net/modpack/nstmaxx.398172)
16- Minecraft 1.7.10
17 - FTB Infinity (http://www.feed-the-beast.com/modpacks/FTBInfinity)
18 - Modderation: Permabanned (https://www.technicpack.net/modpack/modderation-permabanned-edition.449941)
19 - Modderation: FYAD (https://www.technicpack.net/modpack/modderation-fyad-edition.696257)
20- Minecraft 1.12:
21 - All the Mods 3 (https://minecraft.curseforge.com/projects/all-the-mods-3)
22To simplify the code and guesswork, I assume the following monitor layout, where each "monitor" listed below is a collection of three wide by two high Advanced Monitors:
231) One Advanced Monitor for overall status display plus
24 one or more Reactors plus
25 none or more Turbines.
262) One Advanced Monitor for overall status display plus (furthest monitor from computer by cable length)
27 one Advanced Monitor for each connected Reactor plus (subsequent found monitors)
28 one Advanced Monitor for each connected Turbine (last group of monitors found).
29If you enable debug mode, add one additional Advanced Monitor for #1 or #2.
30Notes
31----------------------------
32- Only one reactor and one, two, and three turbines have been tested with the above, but IN THEORY any number is supported.
33- Devices are found in the reverse order they are plugged in, so monitor_10 will be found before monitor_9.
34When using actively cooled reactors with turbines, keep the following in mind:
35- 1 mB steam carries up to 10RF of potential energy to extract in a turbine.
36- Actively cooled reactors produce steam, not power.
37- You will need about 10 mB of water for each 1 mB of steam that you want to create in a 7^3 reactor.
38- Two 15x15x14 Turbines can output 260K RF/t by just one 7^3 (four rods) reactor putting out 4k mB steam.
39Features
40----------------------------
41- Configurable min/max energy buffer and min/max temperature via ReactorOptions file.
42- Disengages coils and minimizes flow for turbines over max energy buffer.
43- ReactorOptions is read on start and then current values are saved every program cycle.
44- Rod Control value in ReactorOptions is only useful for initial start, after that the program saves the current Rod Control average over all Fuel Rods for next boot.
45- Auto-adjusts control rods per reactor to maintain temperature.
46- Will display reactor data to all attached monitors of correct dimensions.
47 - For multiple monitors, the first monitor (often last plugged in) is the overall status monitor.
48- For multiple monitors, the first monitor (often last plugged in) is the overall status monitor.
49- A new cruise mode from mechaet, ONLINE will be "blue" when active, to keep your actively cooled reactors running smoothly.
50GUI Usage
51----------------------------
52- Right-clicking between "< * >" of the last row of a monitor alternates the device selection between Reactor, Turbine, and Status output.
53 - Right-clicking "<" and ">" switches between connected devices, starting with the currently selected type, but not limited to them.
54- The other "<" and ">" buttons, when right-clicked with the mouse, will decrease and increase, respectively, the values assigned to the monitor:
55 - "Rod (%)" will lower/raise the Reactor Control Rods for that Reactor
56 - "mB/t" will lower/raise the Turbine Flow Rate maximum for that Turbine
57 - "RPM" will lower/raise the target Turbine RPM for that Turbine
58- Right-clicking between the "<" and ">" (not on them) will disable auto-adjust of that value for attached device.
59 - Right-clicking on the "Enabled" or "Disabled" text for auto-adjust will do the same.
60- Right-clicking on "ONLINE" or "OFFLINE" at the top-right will toggle the state of attached device.
61Default values
62----------------------------
63- Rod Control: 90% (Let's start off safe and then power up as we can)
64- Minimum Energy Buffer: 15% (will power on below this value)
65- Maximum Energy Buffer: 85% (will power off above this value)
66- Minimum Passive Cooling Temperature: 950^C (will raise control rods below this value)
67- Maximum Passive Cooling Temperature: 1,400^C (will lower control rods above this value)
68- Minimum Active Cooling Temperature: 300^C (will raise the control rods below this value)
69- Maximum Active Cooling Temperature: 420^C (will lower control rods above this value)
70- Optimal Turbine RPM: 900, 1,800, or 2,700 (divisible by 900)
71 - New user-controlled option for target speed of turbines, defaults to 2726RPM, which is high-optimal.
72Requirements
73----------------------------
74- Advanced Monitor size is X: 29, Y: 12 with a 3x2 size
75- Computer or Advanced Computer
76- Modems (not wireless) connecting each of the Computer to both the Advanced Monitor and Reactor Computer Port.
77- Big Reactors (http://www.big-reactors.com/) 0.3.2A+ or Extreme Reactors (https://minecraft.curseforge.com/projects/extreme-reactors)
78- Computercraft (http://computercraft.info/) 1.58, 1.63+, 1.73+, or 1.80pr1+
79- Reset the computer any time number of connected devices change.
80Resources
81----------------------------
82- This script is available from:
83 - http://pastebin.com/fguScPBQ
84 - https://github.com/sandalle/minecraft_bigreactor_control
85- Start-up script is available from:
86 - http://pastebin.com/ZTMzRLez
87 - https://github.com/sandalle/minecraft_bigreactor_control
88- Other reactor control program which I based my program on:
89 - http://pastebin.com/aMAu4X5J (ScatmanJohn)
90 - http://pastebin.com/HjUVNDau (version ScatmanJohn based his on)
91- A simpler Big Reactor control program is available from:
92 - http://pastebin.com/7S5xCvgL (IronClaymore only for passively cooled reactors)
93- Reactor Computer Port API: http://wiki.technicpack.net/Reactor_Computer_Port
94- Computercraft API: http://computercraft.info/wiki/Category:APIs
95- Big Reactors Efficiency, Speculation and Questions! http://www.reddit.com/r/feedthebeast/comments/1vzds0/big_reactors_efficiency_speculation_and_questions/
96- Big Reactors API code: https://github.com/erogenousbeef/BigReactors/blob/master/erogenousbeef/bigreactors/common/multiblock/tileentity/TileEntityReactorComputerPort.java
97- Big Reactors API: http://big-reactors.com/cc_api.html
98- Big Reactor Simulator from http://reddit.com/r/feedthebeast : http://br.sidoh.org/
99- A tutorial from FTB's rhn : http://forum.feed-the-beast.com/threads/rhns-continued-adventures-a-build-journal-guide-collection-etc.42664/page-10#post-657819
100ChangeLog
101============================
102- 0.3.18
103 Fix Issue #61 (Reactor Online/Offline input non-responsive when reactor is converted from passive to active cooled).
104Prior ChangeLogs are posted at https://github.com/sandalle/minecraft_bigreactor_control/releases
105TODO
106============================
107See https://github.com/sandalle/minecraft_bigreactor_control/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement :)
108]]--
109
110
111-- Some global variables
112local progVer = "0.3.18"
113local progName = "EZ-NUKE"
114local sideClick, xClick, yClick = nil, 0, 0
115local loopTime = 2
116local controlRodAdjustAmount = 1 -- Default Reactor Rod Control % adjustment amount
117local flowRateAdjustAmount = 25 -- Default Turbine Flow Rate in mB adjustment amount
118local debugMode = false
119-- End multi-reactor cleanup section
120local minStoredEnergyPercent = nil -- Max energy % to store before activate
121local maxStoredEnergyPercent = nil -- Max energy % to store before shutdown
122local monitorList = {} -- Empty monitor array
123local monitorNames = {} -- Empty array of monitor names
124local reactorList = {} -- Empty reactor array
125local reactorNames = {} -- Empty array of reactor names
126local turbineList = {} -- Empty turbine array
127local turbineNames = {} -- Empty array of turbine names
128local monitorAssignments = {} -- Empty array of monitor - "what to display" assignments
129local monitorOptionFileName = "monitors.options" -- File for saving the monitor assignments
130local knowlinglyOverride = false -- Issue #39 Allow the user to override safe values, currently only enabled for actively cooled reactor min/max temperature
131local steamRequested = 0 -- Sum of Turbine Flow Rate in mB
132local steamDelivered = 0 -- Sum of Active Reactor steam output in mB
133
134-- Log levels
135local FATAL = 16
136local ERROR = 8
137local WARN = 4
138local INFO = 2
139local DEBUG = 1
140
141term.clear()
142term.setCursorPos(2,1)
143write("Initializing program...\n")
144
145
146-- File needs to exist for append "a" later and zero it out if it already exists
147-- Always initalize this file to avoid confusion with old files and the latest run
148local logFile = fs.open("reactorcontrol.log", "w")
149if logFile then
150 logFile.writeLine("Minecraft time: Day "..os.day().." at "..textutils.formatTime(os.time(),true))
151 logFile.close()
152else
153 error("Could not open file reactorcontrol.log for writing.")
154end
155
156
157-- Helper functions
158
159local function termRestore()
160 local ccVersion = nil
161 ccVersion = os.version()
162
163 if ccVersion == "CraftOS 1.6" or "CraftOS 1.7" then
164 term.redirect(term.native())
165 elseif ccVersion == "CraftOS 1.5" then
166 term.restore()
167 else -- Default to older term.restore
168 printLog("Unsupported CraftOS found. Reported version is \""..ccVersion.."\".")
169 term.restore()
170 end -- if ccVersion
171end -- function termRestore()
172
173local function printLog(printStr, logLevel)
174 logLevel = logLevel or INFO
175 -- No, I'm not going to write full syslog style levels. But this makes it a little easier filtering and finding stuff in the logfile.
176 -- Since you're already looking at it, you can adjust your preferred log level right here.
177 if debugMode and (logLevel >= WARN) then
178 -- If multiple monitors, print to all of them
179 for monitorName, deviceData in pairs(monitorAssignments) do
180 if deviceData.type == "Debug" then
181 debugMonitor = monitorList[deviceData.index]
182 if(not debugMonitor) or (not debugMonitor.getSize()) then
183 term.write("printLog(): debug monitor "..monitorName.." failed")
184 else
185 term.redirect(debugMonitor) -- Redirect to selected monitor
186 debugMonitor.setTextScale(0.5) -- Fit more logs on screen
187 local color = colors.lightGray
188 if (logLevel == WARN) then
189 color = colors.white
190 elseif (logLevel == ERROR) then
191 color = colors.red
192 elseif (logLevel == FATAL) then
193 color = colors.black
194 debugMonitor.setBackgroundColor(colors.red)
195 end
196 debugMonitor.setTextColor(color)
197 write(printStr.."\n") -- May need to use term.scroll(x) if we output too much, not sure
198 debugMonitor.setBackgroundColor(colors.black)
199 termRestore()
200 end
201 end
202 end -- for
203
204 local logFile = fs.open("reactorcontrol.log", "a") -- See http://computercraft.info/wiki/Fs.open
205 if logFile then
206 logFile.writeLine(printStr)
207 logFile.close()
208 else
209 error("Cannot open file reactorcontrol.log for appending!")
210 end -- if logFile then
211 end -- if debugMode then
212end -- function printLog(printStr)
213
214
215
216-- Trim a string
217function stringTrim(s)
218 assert(s ~= nil, "String can't be nil")
219 return(string.gsub(s, "^%s*(.-)%s*$", "%1"))
220end
221
222
223
224-- Format number with [k,M,G,T,P,E] postfix or exponent, depending on how large it is
225local function formatReadableSIUnit(num)
226 printLog("formatReadableSIUnit("..num..")", DEBUG)
227 num = tonumber(num)
228 if(num < 1000) then return tostring(num) end
229 local sizes = {"", "k", "M", "G", "T", "P", "E"}
230 local exponent = math.floor(math.log10(num))
231 local group = math.floor(exponent / 3)
232 if group > #sizes then
233 return string.format("%e", num)
234 else
235 local divisor = math.pow(10, (group - 1) * 3)
236 return string.format("%i%s", num / divisor, sizes[group])
237 end
238end -- local function formatReadableSIUnit(num)
239
240-- pretty printLog() a table
241local function tprint (tbl, loglevel, indent)
242 if not loglevel then loglevel = DEBUG end
243 if not indent then indent = 0 end
244 for k, v in pairs(tbl) do
245 local formatting = string.rep(" ", indent) .. k .. ": "
246 if type(v) == "table" then
247 printLog(formatting, loglevel)
248 tprint(v, loglevel, indent+1)
249 elseif type(v) == 'boolean' or type(v) == "function" then
250 printLog(formatting .. tostring(v), loglevel)
251 else
252 printLog(formatting .. v, loglevel)
253 end
254 end
255end -- function tprint()
256
257config = {}
258
259-- Save a table into a config file
260-- path: path of the file to write
261-- tab: table to save
262config.save = function(path, tab)
263 printLog("Save function called for config for "..path.." EOL")
264 assert(path ~= nil, "Path can't be nil")
265 assert(type(tab) == "table", "Second parameter must be a table")
266 local f = io.open(path, "w")
267 local i = 0
268 for key, value in pairs(tab) do
269 if i ~= 0 then
270 f:write("\n")
271 end
272 f:write("["..key.."]".."\n")
273 for key2, value2 in pairs(tab[key]) do
274 key2 = stringTrim(key2)
275 --doesn't like boolean values
276 if (type(value2) ~= "boolean") then
277 value2 = stringTrim(value2)
278 else
279 value2 = tostring(value2)
280 end
281 key2 = key2:gsub(";", "\\;")
282 key2 = key2:gsub("=", "\\=")
283 value2 = value2:gsub(";", "\\;")
284 value2 = value2:gsub("=", "\\=")
285 f:write(key2.."="..value2.."\n")
286 end
287 i = i + 1
288 end
289 f:close()
290end --config.save = function(path, tab)
291
292-- Load a config file
293-- path: path of the file to read
294config.load = function(path)
295 printLog("Load function called for config for "..path.." EOL")
296 assert(path ~= nil, "Path can't be nil")
297 local f = fs.open(path, "r")
298 if f ~= nil then
299 printLog("Successfully opened "..path.." for reading EOL")
300 local tab = {}
301 local line = ""
302 local newLine
303 local i
304 local currentTag = nil
305 local found = false
306 local pos = 0
307 while line ~= nil do
308 found = false
309 line = line:gsub("\\;", "#_!36!_#") -- to keep \;
310 line = line:gsub("\\=", "#_!71!_#") -- to keep \=
311 if line ~= "" then
312 -- Delete comments
313 newLine = line
314 line = ""
315 for i=1, string.len(newLine) do
316 if string.sub(newLine, i, i) ~= ";" then
317 line = line..newLine:sub(i, i)
318 else
319 break
320 end
321 end
322 line = stringTrim(line)
323 -- Find tag
324 if line:sub(1, 1) == "[" and line:sub(line:len(), line:len()) == "]" then
325 currentTag = stringTrim(line:sub(2, line:len()-1))
326 tab[currentTag] = {}
327 found = true
328 end
329 -- Find key and values
330 if not found and line ~= "" then
331 pos = line:find("=")
332 if pos == nil then
333 error("Bad INI file structure")
334 end
335 line = line:gsub("#_!36!_#", ";")
336 line = line:gsub("#_!71!_#", "=")
337 tab[currentTag][stringTrim(line:sub(1, pos-1))] = stringTrim(line:sub(pos+1, line:len()))
338 found = true
339 end
340 end
341 line = f.readLine()
342 end
343
344 f:close()
345
346 return tab
347 else
348 printLog("Could NOT opened "..path.." for reading! EOL")
349 return nil
350 end
351end --config.load = function(path)
352
353
354
355-- round() function from mechaet
356local function round(num, places)
357 local mult = 10^places
358 local addon = nil
359 if ((num * mult) < 0) then
360 addon = -.5
361 else
362 addon = .5
363 end
364
365 local integer, decimal = math.modf(num*mult+addon)
366 local newNum = integer/mult
367 printLog("Called round(num="..num..",places="..places..") returns \""..newNum.."\".")
368 return newNum
369end -- function round(num, places)
370
371
372local function print(printParams)
373 -- Default to xPos=1, yPos=1, and first monitor
374 setmetatable(printParams,{__index={xPos=1, yPos=1, monitorIndex=1}})
375 local printString, xPos, yPos, monitorIndex =
376 printParams[1], -- Required parameter
377 printParams[2] or printParams.xPos,
378 printParams[3] or printParams.yPos,
379 printParams[4] or printParams.monitorIndex
380
381 local monitor = nil
382 monitor = monitorList[monitorIndex]
383
384 if not monitor then
385 printLog("monitor["..monitorIndex.."] in print() is NOT a valid monitor.")
386 return -- Invalid monitorIndex
387 end
388
389 monitor.setCursorPos(xPos, yPos)
390 monitor.write(printString)
391end -- function print(printParams)
392
393
394-- Replaces the one from FC_API (http://pastebin.com/A9hcbZWe) and adding multi-monitor support
395local function printCentered(printString, yPos, monitorIndex)
396 local monitor = nil
397 monitor = monitorList[monitorIndex]
398
399 if not monitor then
400 printLog("monitor["..monitorIndex.."] in printCentered() is NOT a valid monitor.", ERROR)
401 return -- Invalid monitorIndex
402 end
403
404 local width, height = monitor.getSize()
405 local monitorNameLength = 0
406
407 -- Special changes for title bar
408 if yPos == 1 then
409 -- Add monitor name to first line
410 monitorNameLength = monitorNames[monitorIndex]:len()
411 width = width - monitorNameLength -- add a space
412
413 -- Leave room for "offline" and "online" on the right except for overall status display
414 if monitorAssignments[monitorNames[monitorIndex]].type ~= "Status" then
415 width = width - 7
416 end
417 end
418
419 monitor.setCursorPos(monitorNameLength + math.ceil((1 + width - printString:len())/2), yPos)
420 monitor.write(printString)
421end -- function printCentered(printString, yPos, monitorIndex)
422
423
424-- Print text padded from the left side
425-- Clear the left side of the screen
426local function printLeft(printString, yPos, monitorIndex)
427 local monitor = nil
428 monitor = monitorList[monitorIndex]
429
430 if not monitor then
431 printLog("monitor["..monitorIndex.."] in printLeft() is NOT a valid monitor.", ERROR)
432 return -- Invalid monitorIndex
433 end
434
435 local gap = 1
436 local width = monitor.getSize()
437
438 -- Clear left-half of the monitor
439
440 for curXPos = 1, (width / 2) do
441 monitor.setCursorPos(curXPos, yPos)
442 monitor.write(" ")
443 end
444
445 -- Write our string left-aligned
446 monitor.setCursorPos(1+gap, yPos)
447 monitor.write(printString)
448end
449
450
451-- Print text padded from the right side
452-- Clear the right side of the screen
453local function printRight(printString, yPos, monitorIndex)
454 local monitor = nil
455 monitor = monitorList[monitorIndex]
456
457 if not monitor then
458 printLog("monitor["..monitorIndex.."] in printRight() is NOT a valid monitor.", ERROR)
459 return -- Invalid monitorIndex
460 end
461
462 -- Make sure printString is a string
463 printString = tostring(printString)
464
465 local gap = 1
466 local width = monitor.getSize()
467
468 -- Clear right-half of the monitor
469 for curXPos = (width/2), width do
470 monitor.setCursorPos(curXPos, yPos)
471 monitor.write(" ")
472 end
473
474 -- Write our string right-aligned
475 monitor.setCursorPos(math.floor(width) - math.ceil(printString:len()+gap), yPos)
476 monitor.write(printString)
477end
478
479
480-- Replaces the one from FC_API (http://pastebin.com/A9hcbZWe) and adding multi-monitor support
481local function clearMonitor(printString, monitorIndex)
482 local monitor = nil
483 monitor = monitorList[monitorIndex]
484
485 printLog("Called as clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..").")
486
487 if not monitor then
488 printLog("monitor["..monitorIndex.."] in clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..") is NOT a valid monitor.", ERROR)
489 return -- Invalid monitorIndex
490 end
491
492 local gap = 2
493 monitor.clear()
494 local width, height = monitor.getSize()
495
496 printCentered(printString, 1, monitorIndex)
497 monitor.setTextColor(colors.blue)
498 print{monitorNames[monitorIndex], 1, 1, monitorIndex}
499 monitor.setTextColor(colors.white)
500
501 for i=1, width do
502 monitor.setCursorPos(i, gap)
503 monitor.write("-")
504 end
505
506 monitor.setCursorPos(1, gap+1)
507end -- function clearMonitor(printString, monitorIndex)
508
509
510-- Return a list of all connected (including via wired modems) devices of "deviceType"
511local function getDevices(deviceType)
512 printLog("Called as getDevices(deviceType="..deviceType..")")
513
514 local deviceName = nil
515 local deviceIndex = 1
516 local deviceList, deviceNames = {}, {} -- Empty array, which grows as we need
517 local peripheralList = peripheral.getNames() -- Get table of connected peripherals
518
519 deviceType = deviceType:lower() -- Make sure we're matching case here
520
521 for peripheralIndex = 1, #peripheralList do
522 -- Log every device found
523 -- printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] attached as \""..peripheralList[peripheralIndex].."\".")
524 if (string.lower(peripheral.getType(peripheralList[peripheralIndex])) == deviceType) then
525 -- Log devices found which match deviceType and which device index we give them
526 printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".")
527 write("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".\n")
528 deviceNames[deviceIndex] = peripheralList[peripheralIndex]
529 deviceList[deviceIndex] = peripheral.wrap(peripheralList[peripheralIndex])
530 deviceIndex = deviceIndex + 1
531 end
532 end -- for peripheralIndex = 1, #peripheralList do
533
534 return deviceList, deviceNames
535end -- function getDevices(deviceType)
536
537-- Draw a line across the entire x-axis
538local function drawLine(yPos, monitorIndex)
539 local monitor = nil
540 monitor = monitorList[monitorIndex]
541
542 if not monitor then
543 printLog("monitor["..monitorIndex.."] in drawLine() is NOT a valid monitor.")
544 return -- Invalid monitorIndex
545 end
546
547 local width, height = monitor.getSize()
548
549 for i=1, width do
550 monitor.setCursorPos(i, yPos)
551 monitor.write("-")
552 end
553end -- function drawLine(yPos,monitorIndex)
554
555
556-- Display a solid bar of specified color
557local function drawBar(startXPos, startYPos, endXPos, endYPos, color, monitorIndex)
558 local monitor = nil
559 monitor = monitorList[monitorIndex]
560
561 if not monitor then
562 printLog("monitor["..monitorIndex.."] in drawBar() is NOT a valid monitor.")
563 return -- Invalid monitorIndex
564 end
565
566 -- PaintUtils only outputs to term., not monitor.
567 -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/
568 term.redirect(monitor)
569 paintutils.drawLine(startXPos, startYPos, endXPos, endYPos, color)
570 monitor.setBackgroundColor(colors.black) -- PaintUtils doesn't restore the color
571 termRestore()
572end -- function drawBar(startXPos, startYPos,endXPos,endYPos,color,monitorIndex)
573
574
575-- Display single pixel color
576local function drawPixel(xPos, yPos, color, monitorIndex)
577 local monitor = nil
578 monitor = monitorList[monitorIndex]
579
580 if not monitor then
581 printLog("monitor["..monitorIndex.."] in drawPixel() is NOT a valid monitor.")
582 return -- Invalid monitorIndex
583 end
584
585 -- PaintUtils only outputs to term., not monitor.
586 -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/
587 term.redirect(monitor)
588 paintutils.drawPixel(xPos, yPos, color)
589 monitor.setBackgroundColor(colors.black) -- PaintUtils doesn't restore the color
590 termRestore()
591end -- function drawPixel(xPos, yPos, color, monitorIndex)
592
593local function saveMonitorAssignments()
594 local assignments = {}
595 for monitor, data in pairs(monitorAssignments) do
596 local name = nil
597 if (data.type == "Reactor") then
598 name = data.reactorName
599 elseif (data.type == "Turbine") then
600 name = data.turbineName
601 else
602 name = data.type
603 end
604 assignments[monitor] = name
605 end
606 config.save(monitorOptionFileName, {Monitors = assignments})
607end
608
609UI = {
610 monitorIndex = 1,
611 reactorIndex = 1,
612 turbineIndex = 1
613}
614
615UI.handlePossibleClick = function(self)
616 local monitorData = monitorAssignments[sideClick]
617 if monitorData == nil then
618 printLog("UI.handlePossibleClick(): "..sideClick.." is unassigned, can't handle click", WARN)
619 return
620 end
621
622 self.monitorIndex = monitorData.index
623 local width, height = monitorList[self.monitorIndex].getSize()
624 -- All the last line are belong to us
625 if (yClick == height) then
626 if (monitorData.type == "Reactor") then
627 if (xClick == 1) then
628 self:selectPrevReactor()
629 elseif (xClick == width) then
630 self:selectNextReactor()
631 elseif (3 <= xClick and xClick <= width - 2) then
632 if (#turbineList > 0) then
633 self:selectTurbine()
634 else
635 self:selectStatus()
636 end
637 end
638 elseif (monitorData.type == "Turbine") then
639 if (xClick == 1) then
640 self:selectPrevTurbine()
641 elseif (xClick == width) then
642 self:selectNextTurbine()
643 elseif (3 <= xClick and xClick <= width - 2) then
644 self:selectStatus()
645 end
646 elseif (monitorData.type == "Status") then
647 if (xClick == 1) then
648 if (#turbineList > 0) then
649 self.turbineIndex = #turbineList
650 self:selectTurbine()
651 else
652 self.reactorIndex = 1
653 self:selectReactor()
654 end
655 elseif (xClick == width) then
656 self.reactorIndex = 1
657 self:selectReactor()
658 elseif (3 <= xClick and xClick <= width - 2) then
659 self:selectReactor()
660 end
661 else
662 self:selectStatus()
663 end
664 -- Yes, that means we're skipping Debug. I figure everyone who wants that is
665 -- bound to use the console key commands anyway, and that way we don't have
666 -- it interfere with regular use.
667
668 sideClick, xClick, yClick = 0, 0, 0
669 else
670 if (monitorData.type == "Turbine") then
671 self:handleTurbineMonitorClick(monitorData.turbineIndex, monitorData.index)
672 elseif (monitorData.type == "Reactor") then
673 self:handleReactorMonitorClick(monitorData.reactorIndex, monitorData.index)
674 end
675 end
676end -- UI.handlePossibleClick()
677
678UI.logChange = function(self, messageText)
679 printLog("UI: "..messageText)
680 termRestore()
681 write(messageText.."\n")
682end
683
684UI.selectNextMonitor = function(self)
685 self.monitorIndex = self.monitorIndex + 1
686 if self.monitorIndex > #monitorList then
687 self.monitorIndex = 1
688 end
689 local messageText = "Selected monitor "..monitorNames[self.monitorIndex]
690 self:logChange(messageText)
691end -- UI.selectNextMonitor()
692
693
694UI.selectReactor = function(self)
695 monitorAssignments[monitorNames[self.monitorIndex]] = {type="Reactor", index=self.monitorIndex, reactorName=reactorNames[self.reactorIndex], reactorIndex=self.reactorIndex}
696 saveMonitorAssignments()
697 local messageText = "Selected reactor "..reactorNames[self.reactorIndex].." for display on "..monitorNames[self.monitorIndex]
698 self:logChange(messageText)
699end -- UI.selectReactor()
700
701UI.selectPrevReactor = function(self)
702 if self.reactorIndex <= 1 then
703 self.reactorIndex = #reactorList
704 self:selectStatus()
705 else
706 self.reactorIndex = self.reactorIndex - 1
707 self:selectReactor()
708 end
709end -- UI.selectPrevReactor()
710
711UI.selectNextReactor = function(self)
712 if self.reactorIndex >= #reactorList then
713 self.reactorIndex = 1
714 self.turbineIndex = 1
715 self:selectTurbine()
716 else
717 self.reactorIndex = self.reactorIndex + 1
718 self:selectReactor()
719 end
720end -- UI.selectNextReactor()
721
722
723UI.selectTurbine = function(self)
724 monitorAssignments[monitorNames[self.monitorIndex]] = {type="Turbine", index=self.monitorIndex, turbineName=turbineNames[self.turbineIndex], turbineIndex=self.turbineIndex}
725 saveMonitorAssignments()
726 local messageText = "Selected turbine "..turbineNames[self.turbineIndex].." for display on "..monitorNames[self.monitorIndex]
727 self:logChange(messageText)
728end -- UI.selectTurbine()
729
730UI.selectPrevTurbine = function(self)
731 if self.turbineIndex <= 1 then
732 self.turbineIndex = #turbineList
733 self.reactorIndex = #reactorList
734 self:selectReactor()
735 else
736 self.turbineIndex = self.turbineIndex - 1
737 self:selectTurbine()
738 end
739end -- UI.selectPrevTurbine()
740
741UI.selectNextTurbine = function(self)
742 if self.turbineIndex >= #turbineList then
743 self.turbineIndex = 1
744 self:selectStatus()
745 else
746 self.turbineIndex = self.turbineIndex + 1
747 self:selectTurbine()
748 end
749end -- UI.selectNextTurbine()
750
751
752UI.selectStatus = function(self)
753 monitorAssignments[monitorNames[self.monitorIndex]] = {type="Status", index=self.monitorIndex}
754 saveMonitorAssignments()
755 local messageText = "Selected status summary for display on "..monitorNames[self.monitorIndex]
756 self:logChange(messageText)
757end -- UI.selectStatus()
758
759UI.selectDebug = function(self)
760 monitorAssignments[monitorNames[self.monitorIndex]] = {type="Debug", index=self.monitorIndex}
761 saveMonitorAssignments()
762 monitorList[self.monitorIndex].clear()
763 local messageText = "Selected debug output for display on "..monitorNames[self.monitorIndex]
764 self:logChange(messageText)
765end -- UI.selectDebug()
766
767-- Allow controlling Reactor Control Rod Level from GUI
768UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex)
769
770 -- Decrease rod button: 23X, 4Y
771 -- Increase rod button: 28X, 4Y
772
773 -- Grab current monitor
774 local monitor = nil
775 monitor = monitorList[monitorIndex]
776 if not monitor then
777 printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
778 return -- Invalid monitorIndex
779 end
780
781 -- Grab current reactor
782 local reactor = nil
783 reactor = reactorList[reactorIndex]
784 if not reactor then
785 printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
786 return -- Invalid reactorIndex
787 else
788 printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
789 if reactor.getConnected() then
790 printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
791 else
792 printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
793 return -- Disconnected reactor
794 end -- if reactor.getConnected() then
795 end -- if not reactor then
796
797 local reactorStatus = _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"]
798
799 local width, height = monitor.getSize()
800 if xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1) and (sideClick == monitorNames[monitorIndex]) then
801 if yClick == 1 then
802 reactor.setActive(not reactor.getActive()) -- Toggle reactor status
803 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = reactor.getActive()
804 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
805 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
806
807 -- If someone offlines the reactor (offline after a status click was detected), then disable autoStart
808 if not reactor.getActive() then
809 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false
810 end
811 end -- if yClick == 1 then
812 end -- if (xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
813
814 -- Allow disabling rod level auto-adjust and only manual rod level control
815 if ((xClick > 20 and xClick < 27 and yClick == 9))
816 and (sideClick == monitorNames[monitorIndex]) then
817 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]
818 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
819 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
820 end -- if (xClick > 23) and (xClick < 28) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
821
822
823 local targetTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"]
824 local newTargetTemp = targetTemp
825 -- temporary:
826 local targetTempAdjustAmount = 50
827 if (xClick == 21) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
828 printLog("Decreasing Target Temperature in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
829 --Decrease target temp by amount
830 newTargetTemp = targetTemp - targetTempAdjustAmount
831 if newTargetTemp < 0 then
832 newTargetTemp = 0
833 end
834 sideClick, xClick, yClick = 0, 0, 0
835
836 printLog("Setting reactor["..reactorIndex.."] Target Temperature to "..newTargetTemp.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
837 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = newTargetTemp
838
839 -- Save updated target temp
840 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
841 targetTemp = newTargetTemp
842 elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
843 printLog("Increasing Target Temperature in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
844 --Increase Target Temperature by amount
845 newTargetTemp = targetTemp + targetTempAdjustAmount
846
847 sideClick, xClick, yClick = 0, 0, 0
848
849 printLog("Setting reactor["..reactorIndex.."] Target Temperature to "..newTargetTemp.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
850 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = newTargetTemp
851
852 -- Save updated target temp
853 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
854 targetTemp = newTargetTemp
855 else
856 printLog("No change to Target Temp requested by "..progName.." GUI in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
857 end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
858end -- UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex)
859
860-- Allow controlling Turbine Flow Rate from GUI
861UI.handleTurbineMonitorClick = function(self, turbineIndex, monitorIndex)
862
863 -- Decrease flow rate button: 22X, 4Y
864 -- Increase flow rate button: 28X, 4Y
865
866 -- Grab current monitor
867 local monitor = nil
868 monitor = monitorList[monitorIndex]
869 if not monitor then
870 printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
871 return -- Invalid monitorIndex
872 end
873
874 -- Grab current turbine
875 local turbine = nil
876 turbine = turbineList[turbineIndex]
877 if not turbine then
878 printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
879 return -- Invalid turbineIndex
880 else
881 printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
882 if turbine.getConnected() then
883 printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
884 else
885 printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
886 return -- Disconnected turbine
887 end -- if turbine.getConnected() then
888 end
889
890 local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
891 local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
892 local turbineStatus = _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"]
893 local width, height = monitor.getSize()
894
895 if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
896 if yClick == 1 then
897 turbine.setActive(not turbine.getActive()) -- Toggle turbine status
898 _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = turbine.getActive()
899 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
900 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
901 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
902 end -- if yClick == 1 then
903 end -- if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
904
905 -- Allow disabling/enabling flow rate auto-adjust
906 if (xClick > 23 and xClick < 28 and yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
907 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
908 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
909 elseif (xClick > 20 and xClick < 27 and yClick == 10) and (sideClick == monitorNames[monitorIndex]) then
910
911 if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "true")) then
912 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
913 else
914 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
915 end
916 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
917 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
918 end
919
920 if (xClick == 22) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
921 printLog("Decrease to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
922 --Decrease rod level by amount
923 local newTurbineFlowRate = turbineFlowRate - flowRateAdjustAmount
924 if newTurbineFlowRate < 0 then
925 newTurbineFlowRate = 0
926 end
927 sideClick, xClick, yClick = 0, 0, 0
928
929 -- Check bounds [0,2000]
930 if newTurbineFlowRate > 2000 then
931 newTurbineFlowRate = 2000
932 elseif newTurbineFlowRate < 0 then
933 newTurbineFlowRate = 0
934 end
935
936 turbine.setFluidFlowRateMax(newTurbineFlowRate)
937 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newTurbineFlowRate
938 -- Save updated Turbine Flow Rate
939 turbineFlowRate = newTurbineFlowRate
940 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
941 elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
942 printLog("Increase to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
943 --Increase rod level by amount
944 local newTurbineFlowRate = turbineFlowRate + flowRateAdjustAmount
945 if newTurbineFlowRate > 2000 then
946 newTurbineFlowRate = 2000
947 end
948 sideClick, xClick, yClick = 0, 0, 0
949
950 -- Check bounds [0,2000]
951 if newTurbineFlowRate > 2000 then
952 newTurbineFlowRate = 2000
953 elseif newTurbineFlowRate < 0 then
954 newTurbineFlowRate = 0
955 end
956
957 turbine.setFluidFlowRateMax(newTurbineFlowRate)
958
959 -- Save updated Turbine Flow Rate
960 turbineFlowRate = math.ceil(newTurbineFlowRate)
961 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = turbineFlowRate
962 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
963 else
964 printLog("No change to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
965 end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
966
967 if (xClick == 22) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then
968 printLog("Decrease to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
969 local rpmRateAdjustment = 909
970 local newTurbineBaseSpeed = turbineBaseSpeed - rpmRateAdjustment
971 if newTurbineBaseSpeed < 908 then
972 newTurbineBaseSpeed = 908
973 end
974 sideClick, xClick, yClick = 0, 0, 0
975 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed
976 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
977 elseif (xClick == 29) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then
978 printLog("Increase to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
979 local rpmRateAdjustment = 909
980 local newTurbineBaseSpeed = turbineBaseSpeed + rpmRateAdjustment
981 if newTurbineBaseSpeed > 2726 then
982 newTurbineBaseSpeed = 2726
983 end
984 sideClick, xClick, yClick = 0, 0, 0
985 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed
986 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
987 else
988 printLog("No change to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
989 end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
990end -- function handleTurbineMonitorClick(turbineIndex, monitorIndex)
991
992
993-- End helper functions
994
995
996-- Then initialize the monitors
997local function findMonitors()
998 -- Empty out old list of monitors
999 monitorList = {}
1000
1001 printLog("Finding monitors...")
1002 monitorList, monitorNames = getDevices("monitor")
1003
1004 if #monitorList == 0 then
1005 printLog("No monitors found, continuing headless")
1006 else
1007 for monitorIndex = 1, #monitorList do
1008 local monitor, monitorX, monitorY = nil, nil, nil
1009 monitor = monitorList[monitorIndex]
1010
1011 if not monitor then
1012 printLog("monitorList["..monitorIndex.."] in findMonitors() is NOT a valid monitor.")
1013
1014 table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
1015 if monitorIndex ~= #monitorList then -- If we're not at the end, clean up
1016 monitorIndex = monitorIndex - 1 -- We just removed an element
1017 end -- if monitorIndex == #monitorList then
1018 break -- Invalid monitorIndex
1019 else -- valid monitor
1020 monitor.setTextScale(2) -- Reset scale, see Issue #68
1021 monitor.setTextScale(1.0) -- Make sure scale is correct
1022 monitorX, monitorY = monitor.getSize()
1023
1024 if (monitorX == nil) or (monitorY == nil) then -- somehow a valid monitor, but non-existent sizes? Maybe fixes Issue #3
1025 printLog("monitorList["..monitorIndex.."] in findMonitors() is NOT a valid sized monitor.")
1026
1027 table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
1028 if monitorIndex ~= #monitorList then -- If we're not at the end, clean up
1029 monitorIndex = monitorIndex - 1 -- We just removed an element
1030 end -- if monitorIndex == #monitorList then
1031 break -- Invalid monitorIndex
1032
1033 -- Check for minimum size to allow for monitor.setTextScale(0.5) to work for 3x2 debugging monitor, changes getSize()
1034 elseif monitorX < 29 or monitorY < 12 then
1035 term.redirect(monitor)
1036 monitor.clear()
1037 printLog("Removing monitor "..monitorIndex.." for being too small.")
1038 monitor.setCursorPos(1,2)
1039 write("Monitor is the wrong size!\n")
1040 write("Needs to be at least 3x2.")
1041 termRestore()
1042
1043 table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
1044 if monitorIndex == #monitorList then -- If we're at the end already, break from loop
1045 break
1046 else
1047 monitorIndex = monitorIndex - 1 -- We just removed an element
1048 end -- if monitorIndex == #monitorList then
1049
1050 end -- if monitorX < 29 or monitorY < 12 then
1051 end -- if not monitor then
1052
1053 printLog("Monitor["..monitorIndex.."] named \""..monitorNames[monitorIndex].."\" is a valid monitor of size x:"..monitorX.." by y:"..monitorY..".")
1054 end -- for monitorIndex = 1, #monitorList do
1055 end -- if #monitorList == 0 then
1056
1057 printLog("Found "..#monitorList.." monitor(s) in findMonitors().")
1058end -- local function findMonitors()
1059
1060
1061-- Initialize all Big Reactors - Reactors
1062local function findReactors()
1063 -- Empty out old list of reactors
1064 local newReactorList = {}
1065 printLog("Finding reactors...")
1066 newReactorList, reactorNames = getDevices("BigReactors-Reactor")
1067
1068 if #newReactorList == 0 then
1069 printLog("No reactors found!")
1070 error("Can't find any reactors!")
1071 else -- Placeholder
1072 for reactorIndex = 1, #newReactorList do
1073 local reactor = nil
1074 reactor = newReactorList[reactorIndex]
1075
1076 if not reactor then
1077 printLog("reactorList["..reactorIndex.."] in findReactors() is NOT a valid Big Reactor.")
1078
1079 table.remove(newReactorList, reactorIndex) -- Remove invalid reactor from list
1080 if reactorIndex ~= #newReactorList then -- If we're not at the end, clean up
1081 reactorIndex = reactorIndex - 1 -- We just removed an element
1082 end -- reactorIndex ~= #newReactorList then
1083 return -- Invalid reactorIndex
1084 else
1085 printLog("reactor["..reactorIndex.."] in findReactors() is a valid Big Reactor.")
1086 --initialize the default table
1087 _G[reactorNames[reactorIndex]] = {}
1088 _G[reactorNames[reactorIndex]]["ReactorOptions"] = {}
1089 _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = 80
1090 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = 0
1091 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"] = 0
1092 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true
1093 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
1094 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = false
1095 _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = controlRodAdjustAmount
1096 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"] = reactorNames[reactorIndex]
1097 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
1098 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] = 0
1099 _G[reactorNames[reactorIndex]]["ReactorOptions"]["proportionalGain"] = 0.05
1100 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralGain"] = 0.00
1101 _G[reactorNames[reactorIndex]]["ReactorOptions"]["derivativeGain"] = 0.05
1102 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"] = 200
1103 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"] = -200
1104 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = 200
1105 _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = false
1106
1107 if reactor.getConnected() then
1108 printLog("reactor["..reactorIndex.."] in findReactors() is connected.")
1109 else
1110 printLog("reactor["..reactorIndex.."] in findReactors() is NOT connected.")
1111 return -- Disconnected reactor
1112 end
1113 end
1114
1115 --failsafe
1116 local tempTable = _G[reactorNames[reactorIndex]]
1117
1118 --check to make sure we get a valid config
1119 if (config.load(reactorNames[reactorIndex]..".options")) ~= nil then
1120 tempTable = config.load(reactorNames[reactorIndex]..".options")
1121 else
1122 --if we don't have a valid config from disk, make a valid config
1123 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) end
1124
1125 --load values from tempTable, checking for nil values along the way
1126
1127 for k, v in pairs(_G[reactorNames[reactorIndex]]["ReactorOptions"]) do
1128 if tempTable["ReactorOptions"][k] ~= nil then
1129 _G[reactorNames[reactorIndex]]["ReactorOptions"][k] = tempTable["ReactorOptions"][k]
1130 end
1131 end
1132
1133
1134
1135
1136 --stricter typing, let's set these puppies up with the right type of value.
1137 _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"])
1138
1139 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"])
1140
1141 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"]) == "true") then
1142 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true
1143 else
1144 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false
1145 end
1146
1147 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"]) == "true") then
1148 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
1149 else
1150 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = false
1151 end
1152
1153 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) == "true") then
1154 printLog("Setting Rod Override for "..reactorNames[reactorIndex].." to true because value was "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1155 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = true
1156 else
1157 printLog("Setting Rod Override for "..reactorNames[reactorIndex].." to false because value was "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1158 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = false
1159 end
1160
1161 _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"])
1162
1163 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"]) == "true") then
1164 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = true
1165 else
1166 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
1167 end
1168 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"]) == "true") then
1169 _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = true
1170 else
1171 _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = false
1172 end
1173
1174 local number_configs = {"integratedError", "proportionalGain", "integralGain", "derivativeGain", "integralMax", "integralMin", "reactorTargetTemp"}
1175 for k, v in pairs(number_configs) do
1176 _G[reactorNames[reactorIndex]]["ReactorOptions"][v] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"][v])
1177 end
1178
1179 --save one more time, in case we didn't have a complete config file before
1180 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1181 end -- for reactorIndex = 1, #newReactorList do
1182 end -- if #newReactorList == 0 then
1183
1184 -- Overwrite old reactor list with the now updated list
1185 reactorList = newReactorList
1186
1187 printLog("Found "..#reactorList.." reactor(s) in findReactors().")
1188end -- function findReactors()
1189
1190
1191-- Initialize all Big Reactors - Turbines
1192local function findTurbines()
1193 -- Empty out old list of turbines
1194 local newTurbineList = {}
1195
1196 printLog("Finding turbines...")
1197 newTurbineList, turbineNames = getDevices("BigReactors-Turbine")
1198
1199 if #newTurbineList == 0 then
1200 printLog("No turbines found") -- Not an error
1201 else
1202 for turbineIndex = 1, #newTurbineList do
1203 local turbine = nil
1204 turbine = newTurbineList[turbineIndex]
1205
1206 if not turbine then
1207 printLog("turbineList["..turbineIndex.."] in findTurbines() is NOT a valid Big Reactors Turbine.")
1208
1209 table.remove(newTurbineList, turbineIndex) -- Remove invalid turbine from list
1210 if turbineIndex ~= #newTurbineList then -- If we're not at the end, clean up
1211 turbineIndex = turbineIndex - 1 -- We just removed an element
1212 end -- turbineIndex ~= #newTurbineList then
1213
1214 return -- Invalid turbineIndex
1215 else
1216
1217 _G[turbineNames[turbineIndex]] = {}
1218 _G[turbineNames[turbineIndex]]["TurbineOptions"] = {}
1219 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = 0
1220 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = 1817
1221 _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = true
1222 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"] = 2000
1223 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = 2000
1224 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"] = 2000
1225 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
1226 _G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"] = turbineNames[turbineIndex]
1227 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] = 0
1228 _G[turbineNames[turbineIndex]]["TurbineOptions"]["proportionalGain"] = 10
1229 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralGain"] = 0.00
1230 _G[turbineNames[turbineIndex]]["TurbineOptions"]["derivativeGain"] = 5
1231 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"] = 200
1232 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"] = -200
1233
1234
1235 printLog("turbineList["..turbineIndex.."] in findTurbines() is a valid Big Reactors Turbine.")
1236 if turbine.getConnected() then
1237 printLog("turbine["..turbineIndex.."] in findTurbines() is connected.")
1238 else
1239 printLog("turbine["..turbineIndex.."] in findTurbines() is NOT connected.")
1240 return -- Disconnected turbine
1241 end
1242 end
1243
1244 --failsafe
1245 local tempTable = _G[turbineNames[turbineIndex]]
1246
1247 --check to make sure we get a valid config
1248 if (config.load(turbineNames[turbineIndex]..".options")) ~= nil then
1249 tempTable = config.load(turbineNames[turbineIndex]..".options")
1250 else
1251 --if we don't have a valid config from disk, make a valid config
1252 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1253 end
1254
1255
1256 for k, v in pairs(_G[turbineNames[turbineIndex]]["TurbineOptions"]) do
1257 if tempTable["TurbineOptions"][k] ~= nil then
1258 _G[turbineNames[turbineIndex]]["TurbineOptions"][k] = tempTable["TurbineOptions"][k]
1259 end
1260 end
1261
1262
1263
1264
1265
1266 local number_configs = {"integratedError", "proportionalGain", "integralGain", "derivativeGain", "integralMax", "integralMin"}
1267 for k, v in pairs(number_configs) do
1268 _G[turbineNames[turbineIndex]]["TurbineOptions"][v] = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"][v])
1269 end
1270
1271
1272 --save once more just to make sure we got it
1273 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1274 end -- for turbineIndex = 1, #newTurbineList do
1275
1276 -- Overwrite old turbine list with the now updated list
1277 turbineList = newTurbineList
1278 end -- if #newTurbineList == 0 then
1279
1280 printLog("Found "..#turbineList.." turbine(s) in findTurbines().")
1281end -- function findTurbines()
1282
1283-- Assign status, reactors, turbines and debug output to the monitors that shall display them
1284-- Depends on the [monitor,reactor,turbine]Lists being populated already
1285local function assignMonitors()
1286
1287 local monitors = {}
1288 monitorAssignments = {}
1289
1290 printLog("Assigning monitors...")
1291
1292 local m = config.load(monitorOptionFileName)
1293 if (m ~= nil) then
1294 -- first, merge the detected and the configured monitor lists
1295 -- this is to ensure we pick up new additions to the network
1296 for monitorIndex, monitorName in ipairs(monitorNames) do
1297 monitors[monitorName] = m.Monitors[monitorName] or ""
1298 end
1299 -- then, go through all of it again to build our runtime data structure
1300 for monitorName, assignedName in pairs(monitors) do
1301 printLog("Looking for monitor and device named "..monitorName.." and "..assignedName)
1302 for monitorIndex = 1, #monitorNames do
1303 printLog("if "..monitorName.." == "..monitorNames[monitorIndex].." then", DEBUG)
1304
1305 if monitorName == monitorNames[monitorIndex] then
1306 printLog("Found "..monitorName.." at index "..monitorIndex, DEBUG)
1307 if assignedName == "Status" then
1308 monitorAssignments[monitorName] = {type="Status", index=monitorIndex}
1309 elseif assignedName == "Debug" then
1310 monitorAssignments[monitorName] = {type="Debug", index=monitorIndex}
1311 else
1312 local maxListLen = math.max(#reactorNames, #turbineNames)
1313 for i = 1, maxListLen do
1314 if assignedName == reactorNames[i] then
1315 monitorAssignments[monitorName] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[i], reactorIndex=i}
1316 break
1317 elseif assignedName == turbineNames[i] then
1318 monitorAssignments[monitorName] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[i], turbineIndex=i}
1319 break
1320 elseif i == maxListLen then
1321 printLog("assignMonitors(): Monitor "..monitorName.." was configured to display nonexistant device "..assignedName..". Setting inactive.", WARN)
1322 monitorAssignments[monitorName] = {type="Inactive", index=monitorIndex}
1323 end
1324 end
1325 end
1326 break
1327 elseif monitorIndex == #monitorNames then
1328 printLog("assignMonitors(): Monitor "..monitorName.." not found. It was configured to display device "..assignedName..". Discarding.", WARN)
1329 end
1330 end
1331 end
1332 else
1333 printLog("No valid monitor configuration found, generating...")
1334 --print(tostring(monitorNames))
1335 -- create assignments that reflect the setup before 0.3.17
1336 local monitorIndex = 1
1337 monitorAssignments[monitorNames[1]] = {type="Status", index=1}
1338 monitorIndex = monitorIndex + 1
1339 for reactorIndex = 1, #reactorList do
1340 if monitorIndex > #monitorList then
1341 break
1342 end
1343 monitorAssignments[monitorNames[monitorIndex]] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[reactorIndex], reactorIndex=reactorIndex}
1344 printLog(monitorNames[monitorIndex].." -> "..reactorNames[reactorIndex])
1345
1346 monitorIndex = monitorIndex + 1
1347 end
1348 for turbineIndex = 1, #turbineList do
1349 if monitorIndex > #monitorList then
1350 break
1351 end
1352 monitorAssignments[monitorNames[monitorIndex]] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[turbineIndex], turbineIndex=turbineIndex}
1353 printLog(monitorNames[monitorIndex].." -> "..turbineNames[turbineIndex])
1354
1355 monitorIndex = monitorIndex + 1
1356 end
1357 if monitorIndex <= #monitorList then
1358 monitorAssignments[monitorNames[#monitorList]] = {type="Debug", index=#monitorList}
1359 end
1360 end
1361
1362 tprint(monitorAssignments)
1363
1364 saveMonitorAssignments()
1365
1366end -- function assignMonitors()
1367
1368local eventHandler
1369-- Replacement for sleep, which passes on events instead of dropping themo
1370-- Straight from http://computercraft.info/wiki/Os.sleep
1371local function wait(time)
1372 local timer = os.startTimer(time)
1373
1374 while true do
1375 local event = {os.pullEvent()}
1376
1377 if (event[1] == "timer" and event[2] == timer) then
1378 break
1379 else
1380 eventHandler(event[1], event[2], event[3], event[4])
1381 end
1382 end
1383end
1384
1385
1386-- Return current energy buffer in a specific reactor by %
1387local function getReactorStoredEnergyBufferPercent(reactor)
1388 printLog("Called as getReactorStoredEnergyBufferPercent(reactor).")
1389
1390 if not reactor then
1391 printLog("getReactorStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Reactor.")
1392 return -- Invalid reactorIndex
1393 else
1394 printLog("getReactorStoredEnergyBufferPercent() did receive a valid Big Reactor Reactor.")
1395 end
1396
1397 local energyBufferStorage = reactor.getEnergyStored()
1398 return round(energyBufferStorage/100000, 1) -- (buffer/10000000 RF)*100%
1399end -- function getReactorStoredEnergyBufferPercent(reactor)
1400
1401
1402-- Return current energy buffer in a specific Turbine by %
1403local function getTurbineStoredEnergyBufferPercent(turbine)
1404 printLog("Called as getTurbineStoredEnergyBufferPercent(turbine)")
1405
1406 if not turbine then
1407 printLog("getTurbineStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Turbine.")
1408 return -- Invalid reactorIndex
1409 else
1410 printLog("getTurbineStoredEnergyBufferPercent() did receive a valid Big Reactor Turbine.")
1411 end
1412
1413 local energyBufferStorage = turbine.getEnergyStored()
1414 return round(energyBufferStorage/10000, 1) -- (buffer/1000000 RF)*100%
1415end -- function getTurbineStoredEnergyBufferPercent(turbine)
1416
1417
1418-- Modify reactor control rod levels to keep temperature with defined parameters
1419local function temperatureControl(reactorIndex)
1420 printLog("Called as temperatureControl(reactorIndex="..reactorIndex..")")
1421
1422 local reactor = nil
1423 reactor = reactorList[reactorIndex]
1424 if not reactor then
1425 printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT a valid Big Reactor.")
1426 return -- Invalid reactorIndex
1427 else
1428 printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is a valid Big Reactor.")
1429
1430 if reactor.getConnected() then
1431 printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is connected.")
1432 else
1433 printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT connected.")
1434 return -- Disconnected reactor
1435 end -- if reactor.getConnected() then
1436 end
1437
1438 local reactorNum = reactorIndex
1439 local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
1440 local reactorTemp = math.ceil(reactor.getFuelTemperature())
1441
1442 --bypass if the reactor itself is set to not be auto-controlled
1443 if ((not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) or (_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] == "false")) then
1444 -- No point modifying control rod levels for temperature if the reactor is offline
1445 if reactor.getActive() then
1446 -- Actively cooled reactors should range between 0^C-300^C
1447 -- Actually, active-cooled reactors should range between 300 and 420C (Mechaet)
1448 -- Accordingly I changed the below lines
1449-- if reactor.isActivelyCooled() and not knowlinglyOverride then
1450-- -- below was 0
1451-- localMinReactorTemp = 300
1452-- -- below was 300
1453-- localMaxReactorTemp = 420
1454-- end
1455 -- and I (matthew) got rid of them because of the new control algorithm
1456 local lastTempPoll = _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"]
1457
1458 local target
1459 local Error
1460 local derivedError
1461
1462 if _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] then
1463 printLog("Targeting for steam", WARN)
1464 target = steamRequested
1465 Error = steamDelivered - target
1466 derivedError = reactor.getHotFluidProducedLastTick() - _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"]
1467
1468 else
1469 target = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] -- target is the setpoint
1470 Error = reactorTemp - target
1471 derivedError = reactorTemp - lastTempPoll
1472 end
1473
1474 local integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] + Error
1475
1476 if integratedError > _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"] then
1477 integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"]
1478 end
1479
1480 if integratedError < _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"] then
1481 integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"]
1482 end
1483
1484 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] = integratedError
1485
1486
1487 -- Coefficients (gains)
1488 local Kp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["proportionalGain"]
1489 local Ki = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralGain"]
1490 local Kd = _G[reactorNames[reactorIndex]]["ReactorOptions"]["derivativeGain"]
1491
1492
1493 local adjustAmount = round(Kp * Error + Ki * integratedError + Kd * derivedError, 0) -- for the control rods
1494 local coefficientsString = "Kp:" .. tostring(Kp) .. " Ki:" .. tostring(Ki) .. " Kd:" .. tostring(Kd)
1495 local errorsString = "Ep:" .. tostring(Error) .. " Ei:" .. tostring(integratedError) .. " Ed:" .. tostring(derivedError) .. " AA:" .. tostring(adjustAmount)
1496
1497 printLog(coefficientsString, INFO)
1498 printLog(errorsString, INFO)
1499
1500 local setLevel = rodPercentage + adjustAmount
1501
1502 if setLevel > 100 then
1503 setLevel = 100
1504 end
1505 if setLevel < 0 then
1506 setLevel = 0
1507 end
1508
1509 -- Prevent Runaway
1510 printLog("running temperatureControl Reactor Temp: "..reactorTemp.." Target Temp: "..target, WARN)
1511 if reactorTemp > 2 * target then
1512 printLog("preventing runaway", WARN)
1513 setLevel = 100
1514 end
1515
1516 reactor.setAllControlRodLevels(setLevel)
1517
1518
1519 --always set this number
1520 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = reactorTemp
1521 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"] = reactor.getHotFluidProducedLastTick()
1522
1523 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1524 end -- if reactor.getActive() then
1525 else
1526 printLog("Bypassed temperature control due to rodOverride being "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1527 end -- if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
1528end -- function temperatureControl(reactorIndex)
1529
1530-- Load saved reactor parameters if ReactorOptions file exists
1531local function loadReactorOptions()
1532 local reactorOptions = fs.open("ReactorOptions", "r") -- See http://computercraft.info/wiki/Fs.open
1533
1534 if reactorOptions then
1535 -- The following values were added by Lolmer
1536 minStoredEnergyPercent = tonumber(reactorOptions.readLine())
1537 maxStoredEnergyPercent = tonumber(reactorOptions.readLine())
1538 --added by Mechaet
1539 -- If we succeeded in reading a string, convert it to a number
1540
1541 reactorOptions.close()
1542 end -- if reactorOptions then
1543
1544 -- Set default values if we failed to read any of the above
1545 if minStoredEnergyPercent == nil then
1546 minStoredEnergyPercent = 15
1547 end
1548
1549 if maxStoredEnergyPercent == nil then
1550 maxStoredEnergyPercent = 85
1551 end
1552
1553end -- function loadReactorOptions()
1554
1555
1556-- Save our reactor parameters
1557local function saveReactorOptions()
1558 local reactorOptions = fs.open("ReactorOptions", "w") -- See http://computercraft.info/wiki/Fs.open
1559
1560 -- If we can save the files, save them
1561 if reactorOptions then
1562 local reactorIndex = 1
1563 -- The following values were added by Lolmer
1564 reactorOptions.writeLine(minStoredEnergyPercent)
1565 reactorOptions.writeLine(maxStoredEnergyPercent)
1566 reactorOptions.close()
1567 else
1568 printLog("Failed to open file ReactorOptions for writing!")
1569 end -- if reactorOptions then
1570end -- function saveReactorOptions()
1571
1572
1573local function displayReactorBars(barParams)
1574 -- Default to first reactor and first monitor
1575 setmetatable(barParams,{__index={reactorIndex=1, monitorIndex=1}})
1576 local reactorIndex, monitorIndex =
1577 barParams[1] or barParams.reactorIndex,
1578 barParams[2] or barParams.monitorIndex
1579
1580 printLog("Called as displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1581
1582 -- Grab current monitor
1583 local monitor = nil
1584 monitor = monitorList[monitorIndex]
1585 if not monitor then
1586 printLog("monitor["..monitorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1587 return -- Invalid monitorIndex
1588 end
1589
1590 -- Grab current reactor
1591 local reactor = nil
1592 reactor = reactorList[reactorIndex]
1593 local reactorInfo = _G[reactorNames[reactorIndex]]
1594 if not reactor then
1595 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
1596 return -- Invalid reactorIndex
1597 else
1598 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
1599 if reactor.getConnected() then
1600 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
1601 else
1602 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1603 return -- Disconnected reactor
1604 end -- if reactor.getConnected() then
1605 end -- if not reactor then
1606
1607 -- Draw border lines
1608 local width, height = monitor.getSize()
1609 printLog("Size of monitor is "..width.."w x"..height.."h in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")")
1610
1611 for i=3, 5 do
1612 monitor.setCursorPos(20, i)
1613 monitor.write("|")
1614 end
1615
1616 drawLine(6, monitorIndex)
1617 monitor.setCursorPos(1, height)
1618 monitor.write("< ")
1619 monitor.setCursorPos(width-1, height)
1620 monitor.write(" >")
1621
1622 -- Draw some text
1623 local fuelString = "Fuel: "
1624 local tempString = "Temp: "
1625 local energyBufferString = ""
1626
1627 if reactor.isActivelyCooled() then
1628 energyBufferString = "Steam: "
1629 else
1630 energyBufferString = "Energy: "
1631 end
1632
1633 local padding = math.max(string.len(fuelString), string.len(tempString), string.len(energyBufferString))
1634
1635 local fuelPercentage = round(reactor.getFuelAmount()/reactor.getFuelAmountMax()*100,1)
1636 print{fuelString,2,3,monitorIndex}
1637 print{fuelPercentage.." %",padding+2,3,monitorIndex}
1638
1639 local reactorTemp = math.ceil(reactor.getFuelTemperature())
1640 print{tempString,2,5,monitorIndex}
1641 print{reactorTemp.." C",padding+2,5,monitorIndex}
1642
1643 local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
1644 printLog("Current Rod Percentage for reactor["..reactorIndex.."] is "..rodPercentage.."% in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1645 print{"Target °C",21,3,monitorIndex}
1646 print{"< >",21,4,monitorIndex}
1647 --print{stringTrim(rodPercentage),23,4,monitorIndex}
1648 local target = tostring(reactorInfo["ReactorOptions"]["reactorTargetTemp"])
1649 print{stringTrim(target),23,4,monitorIndex}
1650
1651
1652 -- getEnergyProducedLastTick() is used for both RF/t (passively cooled) and mB/t (actively cooled)
1653 local energyBuffer = reactor.getEnergyProducedLastTick()
1654 if reactor.isActivelyCooled() then
1655 printLog("reactor["..reactorIndex.."] produced "..energyBuffer.." mB last tick in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1656 else
1657 printLog("reactor["..reactorIndex.."] produced "..energyBuffer.." RF last tick in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1658 end
1659
1660 print{energyBufferString,2,4,monitorIndex}
1661
1662 -- Actively cooled reactors do not produce energy, only hot fluid mB/t to be used in a turbine
1663 -- still uses getEnergyProducedLastTick for mB/t of hot fluid generated
1664 if not reactor.isActivelyCooled() then
1665 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT an actively cooled reactor.")
1666
1667 -- Draw stored energy buffer bar
1668 drawBar(2,8,28,8,colors.gray,monitorIndex)
1669
1670 local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor)
1671 if curStoredEnergyPercent > 4 then
1672 drawBar(2, 8, math.floor(26*curStoredEnergyPercent/100)+2, 8, colors.yellow, monitorIndex)
1673 elseif curStoredEnergyPercent > 0 then
1674 drawPixel(2, 8, colors.yellow, monitorIndex)
1675 end -- if curStoredEnergyPercent > 4 then
1676
1677 print{"Energy Buffer",2,7,monitorIndex}
1678 print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),7,monitorIndex}
1679 print{"%",28,7,monitorIndex}
1680
1681 print{math.ceil(energyBuffer).." RF/t",padding+2,4,monitorIndex}
1682 else
1683 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is an actively cooled reactor.")
1684 print{math.ceil(energyBuffer).." mB/t",padding+2,4,monitorIndex}
1685 end -- if not reactor.isActivelyCooled() then
1686
1687 -- Print rod override status
1688 local reactorRodOverrideStatus = ""
1689
1690 print{"Rod Auto-adjust:",2,9,monitorIndex}
1691
1692 if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
1693 printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1694 reactorRodOverrideStatus = "Enabled"
1695 monitor.setTextColor(colors.green)
1696 else
1697 printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1698 reactorRodOverrideStatus = "Disabled"
1699 monitor.setTextColor(colors.red)
1700 end -- if not reactorRodOverride then
1701 printLog("reactorRodOverrideStatus is \""..reactorRodOverrideStatus.."\" in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1702
1703 print{reactorRodOverrideStatus, width - string.len(reactorRodOverrideStatus) - 1, 9, monitorIndex}
1704 monitor.setTextColor(colors.white)
1705
1706 print{"Reactivity: "..math.ceil(reactor.getFuelReactivity()).." %", 2, 10, monitorIndex}
1707 print{"Fuel: "..round(reactor.getFuelConsumedLastTick(),3).." mB/t", 2, 11, monitorIndex}
1708 print{"Waste: "..reactor.getWasteAmount().." mB", width-(string.len(reactor.getWasteAmount())+10), 11, monitorIndex}
1709
1710 monitor.setTextColor(colors.blue)
1711 printCentered(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"],12,monitorIndex)
1712 monitor.setTextColor(colors.white)
1713
1714 -- monitor switch controls
1715 monitor.setCursorPos(1, height)
1716 monitor.write("<")
1717 monitor.setCursorPos(width, height)
1718 monitor.write(">")
1719
1720end -- function displayReactorBars(barParams)
1721
1722
1723local function reactorStatus(statusParams)
1724 -- Default to first reactor and first monitor
1725 setmetatable(statusParams,{__index={reactorIndex=1, monitorIndex=1}})
1726 local reactorIndex, monitorIndex =
1727 statusParams[1] or statusParams.reactorIndex,
1728 statusParams[2] or statusParams.monitorIndex
1729 printLog("Called as reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")")
1730
1731 -- Grab current monitor
1732 local monitor = nil
1733 monitor = monitorList[monitorIndex]
1734 if not monitor then
1735 printLog("monitor["..monitorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1736 return -- Invalid monitorIndex
1737 end
1738
1739 -- Grab current reactor
1740 local reactor = nil
1741 reactor = reactorList[reactorIndex]
1742 if not reactor then
1743 printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
1744 return -- Invalid reactorIndex
1745 else
1746 printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
1747 end
1748
1749 local width, height = monitor.getSize()
1750 local reactorStatus = ""
1751
1752 if reactor.getConnected() then
1753 printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
1754
1755 if reactor.getActive() then
1756 reactorStatus = "ONLINE"
1757
1758 -- Set "ONLINE" to blue if the actively cooled reactor is both in cruise mode and online
1759 if _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] and reactor.isActivelyCooled() then
1760 monitor.setTextColor(colors.blue)
1761 else
1762 monitor.setTextColor(colors.green)
1763 end -- if reactorCruising and reactor.isActivelyCooled() then
1764 else
1765 reactorStatus = "OFFLINE"
1766 monitor.setTextColor(colors.red)
1767 end -- if reactor.getActive() then
1768
1769 else
1770 printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1771 reactorStatus = "DISCONNECTED"
1772 monitor.setTextColor(colors.red)
1773 end -- if reactor.getConnected() then
1774 _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"] = reactorStatus
1775 print{reactorStatus, width - string.len(reactorStatus) - 1, 1, monitorIndex}
1776 monitor.setTextColor(colors.white)
1777end -- function reactorStatus(statusParams)
1778
1779
1780-- Display all found reactors' status to selected monitor
1781-- This is only called if multiple reactors and/or a reactor plus at least one turbine are found
1782local function displayAllStatus(monitorIndex)
1783 local reactor, turbine = nil, nil
1784 local onlineReactor, onlineTurbine = 0, 0
1785 local totalReactorRF, totalReactorSteam, totalTurbineRF = 0, 0, 0
1786 local totalReactorFuelConsumed = 0
1787 local totalCoolantStored, totalSteamStored, totalEnergy, totalMaxEnergyStored = 0, 0, 0, 0 -- Total turbine and reactor energy buffer and overall capacity
1788 local maxSteamStored = (2000*#turbineList)+(5000*#reactorList)
1789 local maxCoolantStored = (2000*#turbineList)+(5000*#reactorList)
1790
1791 local monitor = monitorList[monitorIndex]
1792 if not monitor then
1793 printLog("monitor["..monitorIndex.."] in displayAllStatus() is NOT a valid monitor.")
1794 return -- Invalid monitorIndex
1795 end
1796
1797 for reactorIndex = 1, #reactorList do
1798 reactor = reactorList[reactorIndex]
1799 if not reactor then
1800 printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT a valid Big Reactor.")
1801 break -- Invalid reactorIndex
1802 else
1803 printLog("reactor["..reactorIndex.."] in displayAllStatus() is a valid Big Reactor.")
1804 end -- if not reactor then
1805
1806 if reactor.getConnected() then
1807 printLog("reactor["..reactorIndex.."] in displayAllStatus() is connected.")
1808 if reactor.getActive() then
1809 onlineReactor = onlineReactor + 1
1810 totalReactorFuelConsumed = totalReactorFuelConsumed + reactor.getFuelConsumedLastTick()
1811 end -- reactor.getActive() then
1812
1813 -- Actively cooled reactors do not produce or store energy
1814 if not reactor.isActivelyCooled() then
1815 totalMaxEnergyStored = totalMaxEnergyStored + 10000000 -- Reactors store 10M RF
1816 totalEnergy = totalEnergy + reactor.getEnergyStored()
1817 totalReactorRF = totalReactorRF + reactor.getEnergyProducedLastTick()
1818 else
1819 totalReactorSteam = totalReactorSteam + reactor.getEnergyProducedLastTick()
1820 totalSteamStored = totalSteamStored + reactor.getHotFluidAmount()
1821 totalCoolantStored = totalCoolantStored + reactor.getCoolantAmount()
1822 end -- if not reactor.isActivelyCooled() then
1823 else
1824 printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT connected.")
1825 end -- if reactor.getConnected() then
1826 end -- for reactorIndex = 1, #reactorList do
1827
1828 for turbineIndex = 1, #turbineList do
1829 turbine = turbineList[turbineIndex]
1830 if not turbine then
1831 printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT a valid Turbine.")
1832 break -- Invalid turbineIndex
1833 else
1834 printLog("turbine["..turbineIndex.."] in displayAllStatus() is a valid Turbine.")
1835 end -- if not turbine then
1836
1837 if turbine.getConnected() then
1838 printLog("turbine["..turbineIndex.."] in displayAllStatus() is connected.")
1839 if turbine.getActive() then
1840 onlineTurbine = onlineTurbine + 1
1841 end
1842
1843 totalMaxEnergyStored = totalMaxEnergyStored + 1000000 -- Turbines store 1M RF
1844 totalEnergy = totalEnergy + turbine.getEnergyStored()
1845 totalTurbineRF = totalTurbineRF + turbine.getEnergyProducedLastTick()
1846 totalSteamStored = totalSteamStored + turbine.getInputAmount()
1847 totalCoolantStored = totalCoolantStored + turbine.getOutputAmount()
1848 else
1849 printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT connected.")
1850 end -- if turbine.getConnected() then
1851 end -- for turbineIndex = 1, #turbineList do
1852
1853 print{"Reactors online/found: "..onlineReactor.."/"..#reactorList, 2, 3, monitorIndex}
1854 print{"Turbines online/found: "..onlineTurbine.."/"..#turbineList, 2, 4, monitorIndex}
1855
1856 if totalReactorRF ~= 0 then
1857 monitor.setTextColor(colors.blue)
1858 printRight("Reactor", 9, monitorIndex)
1859 monitor.setTextColor(colors.white)
1860 printRight(math.ceil(totalReactorRF).." (RF/t)", 10, monitorIndex)
1861 end
1862
1863 if #turbineList then
1864 -- Display liquids
1865 monitor.setTextColor(colors.blue)
1866 printLeft("Steam (mB)", 6, monitorIndex)
1867 monitor.setTextColor(colors.white)
1868 printLeft(math.ceil(totalSteamStored).."/"..maxSteamStored, 7, monitorIndex)
1869 printLeft(math.ceil(totalReactorSteam).." mB/t", 8, monitorIndex)
1870 monitor.setTextColor(colors.blue)
1871 printRight("Coolant (mB)", 6, monitorIndex)
1872 monitor.setTextColor(colors.white)
1873 printRight(math.ceil(totalCoolantStored).."/"..maxCoolantStored, 7, monitorIndex)
1874
1875 monitor.setTextColor(colors.blue)
1876 printLeft("Turbine", 9, monitorIndex)
1877 monitor.setTextColor(colors.white)
1878 printLeft(math.ceil(totalTurbineRF).." RF/t", 10, monitorIndex)
1879 end -- if #turbineList then
1880
1881 printCentered("Fuel: "..round(totalReactorFuelConsumed,3).." mB/t", 11, monitorIndex)
1882 printCentered("Buffer: "..formatReadableSIUnit(math.ceil(totalEnergy)).."/"..formatReadableSIUnit(totalMaxEnergyStored).." RF", 12, monitorIndex)
1883
1884 -- monitor switch controls
1885 local width, height = monitor.getSize()
1886 monitor.setCursorPos(1, height)
1887 monitor.write("<")
1888 monitor.setCursorPos(width, height)
1889 monitor.write(">")
1890
1891end -- function displayAllStatus()
1892
1893
1894-- Get turbine status
1895local function displayTurbineBars(turbineIndex, monitorIndex)
1896 printLog("Called as displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1897
1898 -- Grab current monitor
1899 local monitor = nil
1900 monitor = monitorList[monitorIndex]
1901 if not monitor then
1902 printLog("monitor["..monitorIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1903 return -- Invalid monitorIndex
1904 end
1905
1906 -- Grab current turbine
1907 local turbine = nil
1908 turbine = turbineList[turbineIndex]
1909 if not turbine then
1910 printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
1911 return -- Invalid turbineIndex
1912 else
1913 printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
1914 if turbine.getConnected() then
1915 printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
1916 else
1917 printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1918 return -- Disconnected turbine
1919 end -- if turbine.getConnected() then
1920 end -- if not turbine then
1921
1922 --local variable to match the view on the monitor
1923 local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
1924
1925 -- Draw border lines
1926 local width, height = monitor.getSize()
1927
1928 for i=3, 6 do
1929 monitor.setCursorPos(21, i)
1930 monitor.write("|")
1931 end
1932
1933 drawLine(7,monitorIndex)
1934 monitor.setCursorPos(1, height)
1935 monitor.write("< ")
1936 monitor.setCursorPos(width-1, height)
1937 monitor.write(" >")
1938
1939 local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
1940 print{" mB/t",22,3,monitorIndex}
1941 print{"< >",22,4,monitorIndex}
1942 print{stringTrim(turbineFlowRate),24,4,monitorIndex}
1943 print{" RPM",22,5,monitorIndex}
1944 print{"< >",22,6,monitorIndex}
1945 print{stringTrim(tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])),24,6,monitorIndex}
1946 local rotorSpeedString = "Speed: "
1947 local energyBufferString = "Energy: "
1948 local steamBufferString = "Steam: "
1949 local padding = math.max(string.len(rotorSpeedString), string.len(energyBufferString), string.len(steamBufferString))
1950
1951 local energyBuffer = turbine.getEnergyProducedLastTick()
1952 print{energyBufferString,1,4,monitorIndex}
1953 print{math.ceil(energyBuffer).." RF/t",padding+1,4,monitorIndex}
1954
1955 local rotorSpeed = math.ceil(turbine.getRotorSpeed())
1956 print{rotorSpeedString,1,5,monitorIndex}
1957 print{rotorSpeed.." RPM",padding+1,5,monitorIndex}
1958
1959 local steamBuffer = turbine.getFluidFlowRate()
1960 print{steamBufferString,1,6,monitorIndex}
1961 print{steamBuffer.." mB/t",padding+1,6,monitorIndex}
1962
1963 -- PaintUtils only outputs to term., not monitor.
1964 -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/
1965
1966 -- Draw stored energy buffer bar
1967 drawBar(1,9,28,9,colors.gray,monitorIndex)
1968
1969 local curStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine)
1970 if curStoredEnergyPercent > 4 then
1971 drawBar(1, 9, math.floor(26*curStoredEnergyPercent/100)+2, 9, colors.yellow,monitorIndex)
1972 elseif curStoredEnergyPercent > 0 then
1973 drawPixel(1, 9, colors.yellow, monitorIndex)
1974 end -- if curStoredEnergyPercent > 4 then
1975
1976 print{"Energy Buffer",1,8,monitorIndex}
1977 print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),8,monitorIndex}
1978 print{"%",28,8,monitorIndex}
1979
1980 -- Print rod override status
1981 local turbineFlowRateOverrideStatus = ""
1982
1983 print{"Flow Auto-adjust:",2,10,monitorIndex}
1984
1985 if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
1986 turbineFlowRateOverrideStatus = "Enabled"
1987 monitor.setTextColor(colors.green)
1988 else
1989 turbineFlowRateOverrideStatus = "Disabled"
1990 monitor.setTextColor(colors.red)
1991 end -- if not reactorRodOverride then
1992
1993 print{turbineFlowRateOverrideStatus, width - string.len(turbineFlowRateOverrideStatus) - 1, 10, monitorIndex}
1994 monitor.setTextColor(colors.white)
1995
1996 -- Print coil status
1997 local turbineCoilStatus = ""
1998
1999 print{"Turbine coils:",2,11,monitorIndex}
2000
2001 if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] == "true")) then
2002 turbineCoilStatus = "Engaged"
2003 monitor.setTextColor(colors.green)
2004 else
2005 turbineCoilStatus = "Disengaged"
2006 monitor.setTextColor(colors.red)
2007 end
2008
2009 print{turbineCoilStatus, width - string.len(turbineCoilStatus) - 1, 11, monitorIndex}
2010 monitor.setTextColor(colors.white)
2011
2012 monitor.setTextColor(colors.blue)
2013 printCentered(_G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"],12,monitorIndex)
2014 monitor.setTextColor(colors.white)
2015
2016 -- monitor switch controls
2017 monitor.setCursorPos(1, height)
2018 monitor.write("<")
2019 monitor.setCursorPos(width, height)
2020 monitor.write(">")
2021
2022 -- Need equation to figure out rotor efficiency and display
2023end -- function displayTurbineBars(statusParams)
2024
2025
2026-- Display turbine status
2027local function turbineStatus(turbineIndex, monitorIndex)
2028 printLog("Called as turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
2029
2030 -- Grab current monitor
2031 local monitor = nil
2032 monitor = monitorList[monitorIndex]
2033 if not monitor then
2034 printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
2035 return -- Invalid monitorIndex
2036 end
2037
2038 -- Grab current turbine
2039 local turbine = nil
2040 turbine = turbineList[turbineIndex]
2041 if not turbine then
2042 printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
2043 return -- Invalid turbineIndex
2044 else
2045 printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
2046 end
2047
2048 local width, height = monitor.getSize()
2049 local turbineStatus = ""
2050
2051 if turbine.getConnected() then
2052 printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
2053 if turbine.getActive() then
2054 turbineStatus = "ONLINE"
2055 monitor.setTextColor(colors.green)
2056 else
2057 turbineStatus = "OFFLINE"
2058 monitor.setTextColor(colors.red)
2059 end -- if turbine.getActive() then
2060 _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"] = turbineStatus
2061 else
2062 printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
2063 turbineStatus = "DISCONNECTED"
2064 monitor.setTextColor(colors.red)
2065 end -- if turbine.getConnected() then
2066
2067 print{turbineStatus, width - string.len(turbineStatus) - 1, 1, monitorIndex}
2068 monitor.setTextColor(colors.white)
2069end -- function function turbineStatus(turbineIndex, monitorIndex)
2070
2071
2072-- Adjust Turbine flow rate to maintain 900 or 1,800 RPM, and disengage coils when buffer full
2073local function flowRateControl(turbineIndex)
2074 if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
2075
2076 printLog("Called as flowRateControl(turbineIndex="..turbineIndex..").")
2077
2078 -- Grab current turbine
2079 local turbine = nil
2080 turbine = turbineList[turbineIndex]
2081
2082 -- assign for the duration of this run
2083 local lastTurbineSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"])
2084 local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
2085 local coilsEngaged = _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] or _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] == "true"
2086
2087 if not turbine then
2088 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT a valid Big Turbine.")
2089 return -- Invalid turbineIndex
2090 else
2091 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is a valid Big Turbine.")
2092
2093 if turbine.getConnected() then
2094 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is connected.")
2095 else
2096 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT connected.")
2097 end -- if turbine.getConnected() then
2098 end -- if not turbine then
2099
2100 -- No point modifying control rod levels for temperature if the turbine is offline
2101 if turbine.getActive() then
2102 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is active.")
2103
2104 local flowRate
2105 local flowRateUserMax = math.ceil(turbine.getFluidFlowRateMax())
2106 local rotorSpeed = math.ceil(turbine.getRotorSpeed())
2107 local newFlowRate
2108
2109
2110 -- Flips on and off the coils. Binary and stupid
2111 local currentStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine)
2112 if currentStoredEnergyPercent < 15 and turbineBaseSpeed - rotorSpeed <= 50 then
2113 coilsEngaged = true
2114 end
2115 if currentStoredEnergyPercent > 85 or turbineBaseSpeed - rotorSpeed > 50 then
2116 coilsEngaged = false
2117
2118 end
2119
2120
2121 -- Uses two stored steam values. One for coils engaged, one for disengaged.
2122 if coilsEngaged then
2123 flowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"])
2124 else
2125 flowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"])
2126 end
2127
2128 -- PID Controller
2129 local target = turbineBaseSpeed
2130 local Error = target - rotorSpeed
2131 local derivedError = lastTurbineSpeed - rotorSpeed
2132 local integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] + Error
2133
2134 if integratedError > _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"] then
2135 integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"]
2136 end
2137 if integratedError > _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"] then
2138 integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"]
2139 end
2140
2141 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] = integratedError
2142
2143
2144
2145
2146 local Kp = _G[turbineNames[turbineIndex]]["TurbineOptions"]["proportionalGain"]
2147 local Ki = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralGain"]
2148 local Kd = _G[turbineNames[turbineIndex]]["TurbineOptions"]["derivativeGain"]
2149
2150 local adjustAmount = round(Kp * Error + Ki * integratedError + Kd * derivedError, 0) -- for the turbine flow rate
2151
2152 newFlowRate = flowRate + adjustAmount
2153
2154
2155
2156
2157 -- Failsafe to prevent explosions
2158 if rotorSpeed >= 2000 then
2159 coilsEngaged = true
2160 newFlowRate = 0
2161 end
2162
2163
2164
2165 --boundary check
2166 if newFlowRate > 2000 then
2167 newFlowRate = 2000
2168 elseif newFlowRate < 0 then
2169 newFlowRate = 0
2170 end -- if newFlowRate > 2000 then
2171 --no sense running an adjustment if it's not necessary
2172
2173 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is being commanded to "..newFlowRate.." mB/t flow")
2174 newFlowRate = round(newFlowRate, 0)
2175 turbine.setFluidFlowRateMax(newFlowRate)
2176 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newFlowRate -- For display purposes
2177
2178 -- For the PID
2179 if coilsEngaged then
2180 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"] = newFlowRate
2181 else
2182 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"] = newFlowRate
2183 end
2184
2185 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
2186
2187
2188 turbine.setInductorEngaged(coilsEngaged)
2189
2190 --always set this
2191 _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] = coilsEngaged
2192 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = rotorSpeed
2193 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
2194 else
2195 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT active.")
2196 end -- if turbine.getActive() then
2197 else
2198 printLog("turbine["..turbineIndex.."] has flow override set to "..tostring(_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"])..", bypassing flow control.")
2199 end -- if not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] then
2200end -- function flowRateControl(turbineIndex)
2201
2202
2203local function helpText()
2204
2205 -- these keys are actually defined in eventHandler(), check there
2206 return [[Keyboard commands:
2207 m Select next monitor
2208 s Make selected monitor display global status
2209 x Make selected monitor display debug information
2210 d Toggle debug mode
2211 q Quit
2212 r Quit and reboot
2213 h Print this help
2214]]
2215
2216end -- function helpText()
2217
2218local function initializePeripherals()
2219 monitorAssignments = {}
2220 -- Get our list of connected monitors and reactors
2221 findMonitors()
2222 findReactors()
2223 findTurbines()
2224 assignMonitors()
2225end
2226
2227
2228local function updateMonitors()
2229
2230 -- Display overall status on selected monitors
2231 for monitorName, deviceData in pairs(monitorAssignments) do
2232 local monitor = nil
2233 local monitorIndex = deviceData.index
2234 local monitorType = deviceData.type
2235 monitor = monitorList[monitorIndex]
2236
2237 printLog("main(): Trying to display "..monitorType.." on "..monitorNames[monitorIndex].."["..monitorIndex.."]", DEBUG)
2238
2239 if #monitorList < (#reactorList + #turbineList + 1) then
2240 printLog("You may want "..(#reactorList + #turbineList + 1).." monitors for your "..#reactorList.." connected reactors and "..#turbineList.." connected turbines.")
2241 end
2242
2243 if (not monitor) or (not monitor.getSize()) then
2244
2245 printLog("monitor["..monitorIndex.."] in main() is NOT a valid monitor, discarding", ERROR)
2246 monitorAssignments[monitorName] = nil
2247 -- we need to get out of the for loop now, or it will dereference x.next (where x is the element we just killed) and crash
2248 break
2249
2250 elseif monitorType == "Status" then
2251
2252 -- General status display
2253 clearMonitor(progName.." "..progVer, monitorIndex) -- Clear monitor and draw borders
2254 printCentered(progName.." "..progVer, 1, monitorIndex)
2255 displayAllStatus(monitorIndex)
2256
2257 elseif monitorType == "Reactor" then
2258
2259 -- Reactor display
2260 local reactorMonitorIndex = monitorIndex
2261 for reactorIndex = 1, #reactorList do
2262
2263 if deviceData.reactorName == reactorNames[reactorIndex] then
2264
2265 printLog("Attempting to display reactor["..reactorIndex.."] on monitor["..monitorIndex.."]...", DEBUG)
2266 -- Only attempt to assign a monitor if we have a monitor for this reactor
2267 if (reactorMonitorIndex <= #monitorList) then
2268 printLog("Displaying reactor["..reactorIndex.."] on monitor["..reactorMonitorIndex.."].")
2269
2270 clearMonitor(progName, reactorMonitorIndex) -- Clear monitor and draw borders
2271 printCentered(progName, 1, reactorMonitorIndex)
2272
2273 -- Display reactor status, includes "Disconnected" but found reactors
2274 reactorStatus{reactorIndex, reactorMonitorIndex}
2275
2276 -- Draw the borders and bars for the current reactor on the current monitor
2277 displayReactorBars{reactorIndex, reactorMonitorIndex}
2278 end
2279
2280 end -- if deviceData.reactorName == reactorNames[reactorIndex] then
2281
2282 end -- for reactorIndex = 1, #reactorList do
2283
2284 elseif monitorType == "Turbine" then
2285
2286 -- Turbine display
2287 local turbineMonitorIndex = monitorIndex
2288 for turbineIndex = 1, #turbineList do
2289
2290 if deviceData.turbineName == turbineNames[turbineIndex] then
2291 printLog("Attempting to display turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."]...", DEBUG)
2292 -- Only attempt to assign a monitor if we have a monitor for this turbine
2293 --printLog("debug: "..turbineMonitorIndex)
2294 if (turbineMonitorIndex <= #monitorList) then
2295 printLog("Displaying turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."].")
2296 clearMonitor(progName, turbineMonitorIndex) -- Clear monitor and draw borders
2297 printCentered(progName, 1, turbineMonitorIndex)
2298
2299 -- Display turbine status, includes "Disconnected" but found turbines
2300 turbineStatus(turbineIndex, turbineMonitorIndex)
2301
2302 -- Draw the borders and bars for the current turbine on the current monitor
2303 displayTurbineBars(turbineIndex, turbineMonitorIndex)
2304 end
2305 end
2306 end
2307
2308 elseif monitorType == "Debug" then
2309
2310 -- do nothing, printLog() outputs to here
2311
2312 else
2313
2314 clearMonitor(progName, monitorIndex)
2315 print{"Monitor inactive", 7, 7, monitorIndex}
2316
2317 end -- if monitorType == [...]
2318 end
2319end
2320
2321function main()
2322 -- Load reactor parameters and initialize systems
2323 loadReactorOptions()
2324 initializePeripherals()
2325
2326 write(helpText())
2327
2328 --TODO: this is a global variable set by the event handler!
2329 while not finished do
2330
2331 updateMonitors()
2332
2333 local reactor = nil
2334 local sd = 0
2335
2336 -- Iterate through reactors
2337 for reactorIndex = 1, #reactorList do
2338 local monitor = nil
2339
2340 reactor = reactorList[reactorIndex]
2341 if not reactor then
2342 printLog("reactor["..reactorIndex.."] in main() is NOT a valid Big Reactor.")
2343 break -- Invalid reactorIndex
2344 else
2345 printLog("reactor["..reactorIndex.."] in main() is a valid Big Reactor.")
2346 end -- if not reactor then
2347
2348 if reactor.getConnected() then
2349 printLog("reactor["..reactorIndex.."] is connected.")
2350
2351 -- Collect steam production data
2352 if reactor.isActivelyCooled() then
2353 sd = sd + reactor.getHotFluidProducedLastTick()
2354 else -- Not actively cooled
2355 local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor)
2356
2357 -- Shutdown reactor if current stored energy % is >= desired level, otherwise activate
2358 -- First pass will have curStoredEnergyPercent=0 until displayBars() is run once
2359 if curStoredEnergyPercent >= maxStoredEnergyPercent then
2360 reactor.setActive(false)
2361 -- Do not auto-start the reactor if it was manually powered off (autoStart=false)
2362 elseif (curStoredEnergyPercent <= minStoredEnergyPercent) and (_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] == true) then
2363 reactor.setActive(true)
2364 end -- if curStoredEnergyPercent >= maxStoredEnergyPercent then
2365 end -- if reactor.isActivelyCooled() then
2366
2367 -- Don't try to auto-adjust control rods if manual control is requested
2368 if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
2369 temperatureControl(reactorIndex)
2370 end -- if not reactorRodOverride then
2371
2372 else
2373 printLog("reactor["..reactorIndex.."] is NOT connected.")
2374 end -- if reactor.getConnected() then
2375 end -- for reactorIndex = 1, #reactorList do
2376
2377 -- Now that temperatureControl() had a chance to use it, reset/calculate steam data for next iteration
2378 printLog("Steam requested: "..steamRequested.." mB")
2379 printLog("Steam delivered: "..steamDelivered.." mB")
2380 steamDelivered = sd
2381 steamRequested = 0
2382
2383 -- Turbine control
2384 for turbineIndex = 1, #turbineList do
2385
2386 local turbine = turbineList[turbineIndex]
2387 if not turbine then
2388 printLog("turbine["..turbineIndex.."] in main() is NOT a valid Big Turbine.")
2389 break -- Invalid turbineIndex
2390 else
2391 printLog("turbine["..turbineIndex.."] in main() is a valid Big Turbine.")
2392 end -- if not turbine then
2393
2394 if turbine.getConnected() then
2395 printLog("turbine["..turbineIndex.."] is connected.")
2396
2397 if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
2398 flowRateControl(turbineIndex)
2399 end -- if not turbineFlowRateOverride[turbineIndex] then
2400
2401 -- Collect steam consumption data
2402 if turbine.getActive() then
2403 steamRequested = steamRequested + turbine.getFluidFlowRateMax()
2404 end
2405 else
2406 printLog("turbine["..turbineIndex.."] is NOT connected.")
2407 end -- if turbine.getConnected() then
2408 end -- for reactorIndex = 1, #reactorList do
2409
2410 wait(loopTime) -- Sleep. No, wait...
2411 saveReactorOptions()
2412 end -- while not finished do
2413end -- main()
2414
2415-- handle all the user interaction events
2416eventHandler = function(event, arg1, arg2, arg3)
2417
2418 printLog(string.format("handleEvent(%s, %s, %s, %s)", tostring(event), tostring(arg1), tostring(arg2), tostring(arg3)), DEBUG)
2419
2420 if event == "monitor_touch" then
2421 sideClick, xClick, yClick = arg1, math.floor(arg2), math.floor(arg3)
2422 UI:handlePossibleClick()
2423 elseif (event == "peripheral") or (event == "peripheral_detach") then
2424 printLog("Change in network detected. Reinitializing peripherals. We will be back shortly.", WARN)
2425 initializePeripherals()
2426 elseif event == "char" and not inManualMode then
2427 local ch = string.lower(arg1)
2428 -- remember to update helpText() when you edit these
2429 if ch == "q" then
2430 finished = true
2431 elseif ch == "d" then
2432 debugMode = not debugMode
2433 local modeText
2434 if debugMode then
2435 modeText = "on"
2436 else
2437 modeText = "off"
2438 end
2439 termRestore()
2440 write("debugMode "..modeText.."\n")
2441 elseif ch == "m" then
2442 UI:selectNextMonitor()
2443 elseif ch == "s" then
2444 UI:selectStatus()
2445 elseif ch == "x" then
2446 UI:selectDebug()
2447 elseif ch == "r" then
2448 finished = true
2449 os.reboot()
2450 elseif ch == "h" then
2451 write(helpText())
2452 end -- if ch == "q" then
2453 end -- if event == "monitor_touch" then
2454
2455 updateMonitors()
2456
2457end -- function eventHandler()
2458
2459main()
2460
2461-- Clear up after an exit
2462term.clear()
2463term.setCursorPos(1,1)