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