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