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