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