· 6 years ago · Aug 15, 2019, 09:56 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 if self.reactorIndex <= #reactorList then
712 monitorAssignments[monitorNames[self.monitorIndex]] = {type="Reactor", index=self.monitorIndex, reactorName=reactorNames[self.reactorIndex], reactorIndex=self.reactorIndex}
713 saveMonitorAssignments()
714 local messageText = "Selected reactor "..reactorNames[self.reactorIndex].." for display on "..monitorNames[self.monitorIndex]
715 self:logChange(messageText)
716 end
717end -- UI.selectReactor()
718
719UI.selectPrevReactor = function(self)
720 if self.reactorIndex <= 1 then
721 self.reactorIndex = #reactorList
722 self:selectStatus()
723 else
724 self.reactorIndex = self.reactorIndex - 1
725 self:selectReactor()
726 end
727end -- UI.selectPrevReactor()
728
729UI.selectNextReactor = function(self)
730 if self.reactorIndex >= #reactorList then
731 if #turbineList > 0 then
732 self.reactorIndex = 1
733 self.turbineIndex = 1
734 self:selectTurbine()
735 else
736 self.reactorIndex = 1
737 self:selectStatus()
738 end
739 else
740 self.reactorIndex = self.reactorIndex + 1
741 self:selectReactor()
742 end
743end -- UI.selectNextReactor()
744
745
746UI.selectTurbine = function(self)
747 if self.turbineIndex <= #turbineList then
748 monitorAssignments[monitorNames[self.monitorIndex]] = {type="Turbine", index=self.monitorIndex, turbineName=turbineNames[self.turbineIndex], turbineIndex=self.turbineIndex}
749 saveMonitorAssignments()
750 local messageText = "Selected turbine "..turbineNames[self.turbineIndex].." for display on "..monitorNames[self.monitorIndex]
751 self:logChange(messageText)
752 end
753end -- UI.selectTurbine()
754
755UI.selectPrevTurbine = function(self)
756 if self.turbineIndex <= 1 then
757 self.turbineIndex = #turbineList
758 self.reactorIndex = #reactorList
759 self:selectReactor()
760 else
761 self.turbineIndex = self.turbineIndex - 1
762 self:selectTurbine()
763 end
764end -- UI.selectPrevTurbine()
765
766UI.selectNextTurbine = function(self)
767 if self.turbineIndex >= #turbineList then
768 self.turbineIndex = 1
769 self:selectStatus()
770 else
771 self.turbineIndex = self.turbineIndex + 1
772 self:selectTurbine()
773 end
774end -- UI.selectNextTurbine()
775
776
777UI.selectStatus = function(self)
778 monitorAssignments[monitorNames[self.monitorIndex]] = {type="Status", index=self.monitorIndex}
779 saveMonitorAssignments()
780 local messageText = "Selected status summary for display on "..monitorNames[self.monitorIndex]
781 self:logChange(messageText)
782end -- UI.selectStatus()
783
784UI.selectDebug = function(self)
785 monitorAssignments[monitorNames[self.monitorIndex]] = {type="Debug", index=self.monitorIndex}
786 saveMonitorAssignments()
787 monitorList[self.monitorIndex].clear()
788 local messageText = "Selected debug output for display on "..monitorNames[self.monitorIndex]
789 self:logChange(messageText)
790end -- UI.selectDebug()
791
792-- Allow controlling Reactor Control Rod Level from GUI
793UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex)
794
795 -- Decrease rod button: 23X, 4Y
796 -- Increase rod button: 28X, 4Y
797
798 -- Grab current monitor
799 local monitor = nil
800 monitor = monitorList[monitorIndex]
801 if not monitor then
802 printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
803 return -- Invalid monitorIndex
804 end
805
806 -- Grab current reactor
807 local reactor = nil
808 reactor = reactorList[reactorIndex]
809 if not reactor then
810 printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
811 return -- Invalid reactorIndex
812 else
813 printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
814 if reactor.getConnected() then
815 printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
816 else
817 printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
818 return -- Disconnected reactor
819 end -- if reactor.getConnected() then
820 end -- if not reactor then
821
822 local reactorStatus = _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"]
823
824 local width, height = monitor.getSize()
825 if xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1) and (sideClick == monitorNames[monitorIndex]) then
826 if yClick == 1 then
827 reactor.setActive(not reactor.getActive()) -- Toggle reactor status
828 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = reactor.getActive()
829 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
830 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
831
832 -- If someone offlines the reactor (offline after a status click was detected), then disable autoStart
833 if not reactor.getActive() then
834 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false
835 end
836 end -- if yClick == 1 then
837 end -- if (xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
838
839 -- Allow disabling rod level auto-adjust and only manual rod level control
840 if ((xClick > 20 and xClick < 27 and yClick == 9))
841 and (sideClick == monitorNames[monitorIndex]) then
842 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]
843 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
844 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
845 end -- if (xClick > 23) and (xClick < 28) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
846
847
848 local targetTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"]
849 local newTargetTemp = targetTemp
850 -- temporary:
851 local targetTempAdjustAmount = 50
852 if (xClick == 21) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
853 printLog("Decreasing Target Temperature in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
854 --Decrease target temp by amount
855 newTargetTemp = targetTemp - targetTempAdjustAmount
856 if newTargetTemp < 0 then
857 newTargetTemp = 0
858 end
859 sideClick, xClick, yClick = 0, 0, 0
860
861 printLog("Setting reactor["..reactorIndex.."] Target Temperature to "..newTargetTemp.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
862 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = newTargetTemp
863
864 -- Save updated target temp
865 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
866 targetTemp = newTargetTemp
867 elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
868 printLog("Increasing Target Temperature in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
869 --Increase Target Temperature by amount
870 newTargetTemp = targetTemp + targetTempAdjustAmount
871
872 sideClick, xClick, yClick = 0, 0, 0
873
874 printLog("Setting reactor["..reactorIndex.."] Target Temperature to "..newTargetTemp.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
875 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = newTargetTemp
876
877 -- Save updated target temp
878 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
879 targetTemp = newTargetTemp
880 else
881 printLog("No change to Target Temp requested by "..progName.." GUI in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
882 end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
883end -- UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex)
884
885-- Allow controlling Turbine Flow Rate from GUI
886UI.handleTurbineMonitorClick = function(self, turbineIndex, monitorIndex)
887
888 -- Decrease flow rate button: 22X, 4Y
889 -- Increase flow rate button: 28X, 4Y
890
891 -- Grab current monitor
892 local monitor = nil
893 monitor = monitorList[monitorIndex]
894 if not monitor then
895 printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
896 return -- Invalid monitorIndex
897 end
898
899 -- Grab current turbine
900 local turbine = nil
901 turbine = turbineList[turbineIndex]
902 if not turbine then
903 printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
904 return -- Invalid turbineIndex
905 else
906 printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
907 if turbine.getConnected() then
908 printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
909 else
910 printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
911 return -- Disconnected turbine
912 end -- if turbine.getConnected() then
913 end
914
915 local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
916 local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
917 local turbineStatus = _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"]
918 local width, height = monitor.getSize()
919
920 if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
921 if yClick == 1 then
922 turbine.setActive(not turbine.getActive()) -- Toggle turbine status
923 _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = turbine.getActive()
924 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
925 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
926 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
927 end -- if yClick == 1 then
928 end -- if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then
929
930 -- Allow disabling/enabling flow rate auto-adjust
931 if (xClick > 23 and xClick < 28 and yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
932 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
933 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
934 elseif (xClick > 20 and xClick < 27 and yClick == 10) and (sideClick == monitorNames[monitorIndex]) then
935
936 if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "true")) then
937 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
938 else
939 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true
940 end
941 sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it
942 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
943 end
944
945 if (xClick == 22) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
946 printLog("Decrease to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
947 --Decrease rod level by amount
948 local newTurbineFlowRate = turbineFlowRate - flowRateAdjustAmount
949 if newTurbineFlowRate < 0 then
950 newTurbineFlowRate = 0
951 end
952 sideClick, xClick, yClick = 0, 0, 0
953
954 -- Check bounds [0,2000]
955 if newTurbineFlowRate > 2000 then
956 newTurbineFlowRate = 2000
957 elseif newTurbineFlowRate < 0 then
958 newTurbineFlowRate = 0
959 end
960
961 turbine.setFluidFlowRateMax(newTurbineFlowRate)
962 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newTurbineFlowRate
963 -- Save updated Turbine Flow Rate
964 turbineFlowRate = newTurbineFlowRate
965 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
966 elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
967 printLog("Increase to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
968 --Increase rod level by amount
969 local newTurbineFlowRate = turbineFlowRate + flowRateAdjustAmount
970 if newTurbineFlowRate > 2000 then
971 newTurbineFlowRate = 2000
972 end
973 sideClick, xClick, yClick = 0, 0, 0
974
975 -- Check bounds [0,2000]
976 if newTurbineFlowRate > 2000 then
977 newTurbineFlowRate = 2000
978 elseif newTurbineFlowRate < 0 then
979 newTurbineFlowRate = 0
980 end
981
982 turbine.setFluidFlowRateMax(newTurbineFlowRate)
983
984 -- Save updated Turbine Flow Rate
985 turbineFlowRate = math.ceil(newTurbineFlowRate)
986 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = turbineFlowRate
987 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
988 else
989 printLog("No change to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
990 end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
991
992 if (xClick == 22) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then
993 printLog("Decrease to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
994 local rpmRateAdjustment = 909
995 local newTurbineBaseSpeed = turbineBaseSpeed - rpmRateAdjustment
996 if newTurbineBaseSpeed < 908 then
997 newTurbineBaseSpeed = 908
998 end
999 sideClick, xClick, yClick = 0, 0, 0
1000 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed
1001 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1002 elseif (xClick == 29) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then
1003 printLog("Increase to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1004 local rpmRateAdjustment = 909
1005 local newTurbineBaseSpeed = turbineBaseSpeed + rpmRateAdjustment
1006 if newTurbineBaseSpeed > 2726 then
1007 newTurbineBaseSpeed = 2726
1008 end
1009 sideClick, xClick, yClick = 0, 0, 0
1010 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed
1011 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1012 else
1013 printLog("No change to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1014 end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then
1015end -- function handleTurbineMonitorClick(turbineIndex, monitorIndex)
1016
1017
1018-- End helper functions
1019
1020
1021-- Then initialize the monitors
1022local function findMonitors()
1023 -- Empty out old list of monitors
1024 monitorList = {}
1025
1026 printLog("Finding monitors...")
1027 monitorList, monitorNames = getDevices("monitor")
1028
1029 if #monitorList == 0 then
1030 printLog("No monitors found, continuing headless")
1031 else
1032 for monitorIndex = 1, #monitorList do
1033 local monitor, monitorX, monitorY = nil, nil, nil
1034 monitor = monitorList[monitorIndex]
1035
1036 if not monitor then
1037 printLog("monitorList["..monitorIndex.."] in findMonitors() is NOT a valid monitor.")
1038
1039 table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
1040 if monitorIndex ~= #monitorList then -- If we're not at the end, clean up
1041 monitorIndex = monitorIndex - 1 -- We just removed an element
1042 end -- if monitorIndex == #monitorList then
1043 break -- Invalid monitorIndex
1044 else -- valid monitor
1045 monitor.setTextScale(2) -- Reset scale, see Issue #68
1046 monitor.setTextScale(1.0) -- Make sure scale is correct
1047 monitorX, monitorY = monitor.getSize()
1048
1049 if (monitorX == nil) or (monitorY == nil) then -- somehow a valid monitor, but non-existent sizes? Maybe fixes Issue #3
1050 printLog("monitorList["..monitorIndex.."] in findMonitors() is NOT a valid sized monitor.")
1051
1052 table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
1053 if monitorIndex ~= #monitorList then -- If we're not at the end, clean up
1054 monitorIndex = monitorIndex - 1 -- We just removed an element
1055 end -- if monitorIndex == #monitorList then
1056 break -- Invalid monitorIndex
1057
1058 -- Check for minimum size to allow for monitor.setTextScale(0.5) to work for 3x2 debugging monitor, changes getSize()
1059 elseif monitorX < 29 or monitorY < 12 then
1060 term.redirect(monitor)
1061 monitor.clear()
1062 printLog("Removing monitor "..monitorIndex.." for being too small.")
1063 monitor.setCursorPos(1,2)
1064 write("Monitor is the wrong size!\n")
1065 write("Needs to be at least 3x2.")
1066 termRestore()
1067
1068 table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list
1069 if monitorIndex == #monitorList then -- If we're at the end already, break from loop
1070 break
1071 else
1072 monitorIndex = monitorIndex - 1 -- We just removed an element
1073 end -- if monitorIndex == #monitorList then
1074
1075 end -- if monitorX < 29 or monitorY < 12 then
1076 end -- if not monitor then
1077
1078 printLog("Monitor["..monitorIndex.."] named \""..monitorNames[monitorIndex].."\" is a valid monitor of size x:"..monitorX.." by y:"..monitorY..".")
1079 end -- for monitorIndex = 1, #monitorList do
1080 end -- if #monitorList == 0 then
1081
1082 printLog("Found "..#monitorList.." monitor(s) in findMonitors().")
1083end -- local function findMonitors()
1084
1085
1086-- Initialize all Big Reactors - Reactors
1087local function findReactors()
1088 -- Empty out old list of reactors
1089 local newReactorList = {}
1090 printLog("Finding reactors...")
1091 newReactorList, reactorNames = getDevices("BigReactors-Reactor")
1092
1093 if #newReactorList == 0 then
1094 printLog("No reactors found!")
1095 error("Can't find any reactors!")
1096 else -- Placeholder
1097 for reactorIndex = 1, #newReactorList do
1098 local reactor = nil
1099 reactor = newReactorList[reactorIndex]
1100
1101 if not reactor then
1102 printLog("reactorList["..reactorIndex.."] in findReactors() is NOT a valid Big Reactor.")
1103
1104 table.remove(newReactorList, reactorIndex) -- Remove invalid reactor from list
1105 if reactorIndex ~= #newReactorList then -- If we're not at the end, clean up
1106 reactorIndex = reactorIndex - 1 -- We just removed an element
1107 end -- reactorIndex ~= #newReactorList then
1108 return -- Invalid reactorIndex
1109 else
1110 printLog("reactor["..reactorIndex.."] in findReactors() is a valid Big Reactor.")
1111 --initialize the default table
1112 _G[reactorNames[reactorIndex]] = {}
1113 _G[reactorNames[reactorIndex]]["ReactorOptions"] = {}
1114 _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = 80
1115 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = 0
1116 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"] = 0
1117 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true
1118 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
1119 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = false
1120 _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = controlRodAdjustAmount
1121 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"] = reactorNames[reactorIndex]
1122 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
1123 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] = 0
1124 _G[reactorNames[reactorIndex]]["ReactorOptions"]["proportionalGain"] = 0.05
1125 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralGain"] = 0.00
1126 _G[reactorNames[reactorIndex]]["ReactorOptions"]["derivativeGain"] = 0.05
1127 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"] = 200
1128 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"] = -200
1129 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = 200
1130 _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = false
1131
1132 if reactor.getConnected() then
1133 printLog("reactor["..reactorIndex.."] in findReactors() is connected.")
1134 else
1135 printLog("reactor["..reactorIndex.."] in findReactors() is NOT connected.")
1136 return -- Disconnected reactor
1137 end
1138 end
1139
1140 --failsafe
1141 local tempTable = _G[reactorNames[reactorIndex]]
1142
1143 --check to make sure we get a valid config
1144 if (config.load(reactorNames[reactorIndex]..".options")) ~= nil then
1145 tempTable = config.load(reactorNames[reactorIndex]..".options")
1146 else
1147 --if we don't have a valid config from disk, make a valid config
1148 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) end
1149
1150 --load values from tempTable, checking for nil values along the way
1151
1152 for k, v in pairs(_G[reactorNames[reactorIndex]]["ReactorOptions"]) do
1153 if tempTable["ReactorOptions"][k] ~= nil then
1154 _G[reactorNames[reactorIndex]]["ReactorOptions"][k] = tempTable["ReactorOptions"][k]
1155 end
1156 end
1157
1158
1159
1160
1161 --stricter typing, let's set these puppies up with the right type of value.
1162 _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"])
1163
1164 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"])
1165
1166 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"]) == "true") then
1167 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true
1168 else
1169 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false
1170 end
1171
1172 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"]) == "true") then
1173 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true
1174 else
1175 _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = false
1176 end
1177
1178 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) == "true") then
1179 printLog("Setting Rod Override for "..reactorNames[reactorIndex].." to true because value was "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1180 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = true
1181 else
1182 printLog("Setting Rod Override for "..reactorNames[reactorIndex].." to false because value was "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1183 _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = false
1184 end
1185
1186 _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"])
1187
1188 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"]) == "true") then
1189 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = true
1190 else
1191 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false
1192 end
1193 if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"]) == "true") then
1194 _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = true
1195 else
1196 _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = false
1197 end
1198
1199 local number_configs = {"integratedError", "proportionalGain", "integralGain", "derivativeGain", "integralMax", "integralMin", "reactorTargetTemp"}
1200 for k, v in pairs(number_configs) do
1201 _G[reactorNames[reactorIndex]]["ReactorOptions"][v] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"][v])
1202 end
1203
1204 --save one more time, in case we didn't have a complete config file before
1205 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1206 end -- for reactorIndex = 1, #newReactorList do
1207 end -- if #newReactorList == 0 then
1208
1209 -- Overwrite old reactor list with the now updated list
1210 reactorList = newReactorList
1211
1212 printLog("Found "..#reactorList.." reactor(s) in findReactors().")
1213end -- function findReactors()
1214
1215
1216-- Initialize all Big Reactors - Turbines
1217local function findTurbines()
1218 -- Empty out old list of turbines
1219 local newTurbineList = {}
1220
1221 printLog("Finding turbines...")
1222 newTurbineList, turbineNames = getDevices("BigReactors-Turbine")
1223
1224 if #newTurbineList == 0 then
1225 printLog("No turbines found") -- Not an error
1226 else
1227 for turbineIndex = 1, #newTurbineList do
1228 local turbine = nil
1229 turbine = newTurbineList[turbineIndex]
1230
1231 if not turbine then
1232 printLog("turbineList["..turbineIndex.."] in findTurbines() is NOT a valid Big Reactors Turbine.")
1233
1234 table.remove(newTurbineList, turbineIndex) -- Remove invalid turbine from list
1235 if turbineIndex ~= #newTurbineList then -- If we're not at the end, clean up
1236 turbineIndex = turbineIndex - 1 -- We just removed an element
1237 end -- turbineIndex ~= #newTurbineList then
1238
1239 return -- Invalid turbineIndex
1240 else
1241
1242 _G[turbineNames[turbineIndex]] = {}
1243 _G[turbineNames[turbineIndex]]["TurbineOptions"] = {}
1244 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = 0
1245 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = 1817
1246 _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = true
1247 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"] = 2000
1248 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = 2000
1249 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"] = 2000
1250 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false
1251 _G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"] = turbineNames[turbineIndex]
1252 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] = 0
1253 _G[turbineNames[turbineIndex]]["TurbineOptions"]["proportionalGain"] = 10
1254 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralGain"] = 0.00
1255 _G[turbineNames[turbineIndex]]["TurbineOptions"]["derivativeGain"] = 5
1256 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"] = 200
1257 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"] = -200
1258
1259
1260 printLog("turbineList["..turbineIndex.."] in findTurbines() is a valid Big Reactors Turbine.")
1261 if turbine.getConnected() then
1262 printLog("turbine["..turbineIndex.."] in findTurbines() is connected.")
1263 else
1264 printLog("turbine["..turbineIndex.."] in findTurbines() is NOT connected.")
1265 return -- Disconnected turbine
1266 end
1267 end
1268
1269 --failsafe
1270 local tempTable = _G[turbineNames[turbineIndex]]
1271
1272 --check to make sure we get a valid config
1273 if (config.load(turbineNames[turbineIndex]..".options")) ~= nil then
1274 tempTable = config.load(turbineNames[turbineIndex]..".options")
1275 else
1276 --if we don't have a valid config from disk, make a valid config
1277 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1278 end
1279
1280
1281 for k, v in pairs(_G[turbineNames[turbineIndex]]["TurbineOptions"]) do
1282 if tempTable["TurbineOptions"][k] ~= nil then
1283 _G[turbineNames[turbineIndex]]["TurbineOptions"][k] = tempTable["TurbineOptions"][k]
1284 end
1285 end
1286
1287
1288
1289
1290
1291 local number_configs = {"integratedError", "proportionalGain", "integralGain", "derivativeGain", "integralMax", "integralMin"}
1292 for k, v in pairs(number_configs) do
1293 _G[turbineNames[turbineIndex]]["TurbineOptions"][v] = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"][v])
1294 end
1295
1296
1297 --save once more just to make sure we got it
1298 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
1299 end -- for turbineIndex = 1, #newTurbineList do
1300
1301 -- Overwrite old turbine list with the now updated list
1302 turbineList = newTurbineList
1303 end -- if #newTurbineList == 0 then
1304
1305 printLog("Found "..#turbineList.." turbine(s) in findTurbines().")
1306end -- function findTurbines()
1307
1308-- Assign status, reactors, turbines and debug output to the monitors that shall display them
1309-- Depends on the [monitor,reactor,turbine]Lists being populated already
1310local function assignMonitors()
1311
1312 local monitors = {}
1313 monitorAssignments = {}
1314
1315 printLog("Assigning monitors...")
1316
1317 local m = config.load(monitorOptionFileName)
1318 if (m ~= nil) then
1319 -- first, merge the detected and the configured monitor lists
1320 -- this is to ensure we pick up new additions to the network
1321 for monitorIndex, monitorName in ipairs(monitorNames) do
1322 monitors[monitorName] = m.Monitors[monitorName] or ""
1323 end
1324 -- then, go through all of it again to build our runtime data structure
1325 for monitorName, assignedName in pairs(monitors) do
1326 printLog("Looking for monitor and device named "..monitorName.." and "..assignedName)
1327 for monitorIndex = 1, #monitorNames do
1328 printLog("if "..monitorName.." == "..monitorNames[monitorIndex].." then", DEBUG)
1329
1330 if monitorName == monitorNames[monitorIndex] then
1331 printLog("Found "..monitorName.." at index "..monitorIndex, DEBUG)
1332 if assignedName == "Status" then
1333 monitorAssignments[monitorName] = {type="Status", index=monitorIndex}
1334 elseif assignedName == "Debug" then
1335 monitorAssignments[monitorName] = {type="Debug", index=monitorIndex}
1336 else
1337 local maxListLen = math.max(#reactorNames, #turbineNames)
1338 for i = 1, maxListLen do
1339 if assignedName == reactorNames[i] then
1340 monitorAssignments[monitorName] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[i], reactorIndex=i}
1341 break
1342 elseif assignedName == turbineNames[i] then
1343 monitorAssignments[monitorName] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[i], turbineIndex=i}
1344 break
1345 elseif i == maxListLen then
1346 printLog("assignMonitors(): Monitor "..monitorName.." was configured to display nonexistant device "..assignedName..". Setting inactive.", WARN)
1347 monitorAssignments[monitorName] = {type="Inactive", index=monitorIndex}
1348 end
1349 end
1350 end
1351 break
1352 elseif monitorIndex == #monitorNames then
1353 printLog("assignMonitors(): Monitor "..monitorName.." not found. It was configured to display device "..assignedName..". Discarding.", WARN)
1354 end
1355 end
1356 end
1357 else
1358 printLog("No valid monitor configuration found, generating...")
1359 --print(tostring(monitorNames))
1360 -- create assignments that reflect the setup before 0.3.17
1361 local monitorIndex = 1
1362 monitorAssignments[monitorNames[1]] = {type="Status", index=1}
1363 monitorIndex = monitorIndex + 1
1364 for reactorIndex = 1, #reactorList do
1365 if monitorIndex > #monitorList then
1366 break
1367 end
1368 monitorAssignments[monitorNames[monitorIndex]] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[reactorIndex], reactorIndex=reactorIndex}
1369 printLog(monitorNames[monitorIndex].." -> "..reactorNames[reactorIndex])
1370
1371 monitorIndex = monitorIndex + 1
1372 end
1373 for turbineIndex = 1, #turbineList do
1374 if monitorIndex > #monitorList then
1375 break
1376 end
1377 monitorAssignments[monitorNames[monitorIndex]] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[turbineIndex], turbineIndex=turbineIndex}
1378 printLog(monitorNames[monitorIndex].." -> "..turbineNames[turbineIndex])
1379
1380 monitorIndex = monitorIndex + 1
1381 end
1382 if monitorIndex <= #monitorList then
1383 monitorAssignments[monitorNames[#monitorList]] = {type="Debug", index=#monitorList}
1384 end
1385 end
1386
1387 tprint(monitorAssignments)
1388
1389 saveMonitorAssignments()
1390
1391end -- function assignMonitors()
1392
1393local eventHandler
1394-- Replacement for sleep, which passes on events instead of dropping themo
1395-- Straight from http://computercraft.info/wiki/Os.sleep
1396local function wait(time)
1397 local timer = os.startTimer(time)
1398
1399 while true do
1400 local event = {os.pullEvent()}
1401
1402 if (event[1] == "timer" and event[2] == timer) then
1403 break
1404 else
1405 eventHandler(event[1], event[2], event[3], event[4])
1406 end
1407 end
1408end
1409
1410
1411-- Return current energy buffer in a specific reactor by %
1412local function getReactorStoredEnergyBufferPercent(reactor)
1413 printLog("Called as getReactorStoredEnergyBufferPercent(reactor).")
1414
1415 if not reactor then
1416 printLog("getReactorStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Reactor.")
1417 return -- Invalid reactorIndex
1418 else
1419 printLog("getReactorStoredEnergyBufferPercent() did receive a valid Big Reactor Reactor.")
1420 end
1421
1422 local energyBufferStorage = reactor.getEnergyStored()
1423 return round(energyBufferStorage/100000, 1) -- (buffer/10000000 RF)*100%
1424end -- function getReactorStoredEnergyBufferPercent(reactor)
1425
1426
1427-- Return current energy buffer in a specific Turbine by %
1428local function getTurbineStoredEnergyBufferPercent(turbine)
1429 printLog("Called as getTurbineStoredEnergyBufferPercent(turbine)")
1430
1431 if not turbine then
1432 printLog("getTurbineStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Turbine.")
1433 return -- Invalid reactorIndex
1434 else
1435 printLog("getTurbineStoredEnergyBufferPercent() did receive a valid Big Reactor Turbine.")
1436 end
1437
1438 local energyBufferStorage = turbine.getEnergyStored()
1439 return round(energyBufferStorage/10000, 1) -- (buffer/1000000 RF)*100%
1440end -- function getTurbineStoredEnergyBufferPercent(turbine)
1441
1442
1443-- Modify reactor control rod levels to keep temperature with defined parameters
1444local function temperatureControl(reactorIndex)
1445 printLog("Called as temperatureControl(reactorIndex="..reactorIndex..")")
1446
1447 local reactor = nil
1448 reactor = reactorList[reactorIndex]
1449 if not reactor then
1450 printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT a valid Big Reactor.")
1451 return -- Invalid reactorIndex
1452 else
1453 printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is a valid Big Reactor.")
1454
1455 if reactor.getConnected() then
1456 printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is connected.")
1457 else
1458 printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT connected.")
1459 return -- Disconnected reactor
1460 end -- if reactor.getConnected() then
1461 end
1462
1463 local reactorNum = reactorIndex
1464 local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
1465 local reactorTemp = math.ceil(reactor.getFuelTemperature())
1466
1467 --bypass if the reactor itself is set to not be auto-controlled
1468 if ((not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) or (_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] == "false")) then
1469 -- No point modifying control rod levels for temperature if the reactor is offline
1470 if reactor.getActive() then
1471 -- Actively cooled reactors should range between 0^C-300^C
1472 -- Actually, active-cooled reactors should range between 300 and 420C (Mechaet)
1473 -- Accordingly I changed the below lines
1474-- if reactor.isActivelyCooled() and not knowlinglyOverride then
1475-- -- below was 0
1476-- localMinReactorTemp = 300
1477-- -- below was 300
1478-- localMaxReactorTemp = 420
1479-- end
1480 -- and I (matthew) got rid of them because of the new control algorithm
1481 local lastTempPoll = _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"]
1482
1483 local target
1484 local Error
1485 local derivedError
1486
1487 if _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] then
1488 printLog("Targeting for steam", WARN)
1489 target = steamRequested
1490 Error = steamDelivered - target
1491 derivedError = reactor.getHotFluidProducedLastTick() - _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"]
1492
1493 else
1494 target = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] -- target is the setpoint
1495 Error = reactorTemp - target
1496 derivedError = reactorTemp - lastTempPoll
1497 end
1498
1499 local integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] + Error
1500
1501 if integratedError > _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"] then
1502 integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"]
1503 end
1504
1505 if integratedError < _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"] then
1506 integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"]
1507 end
1508
1509 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] = integratedError
1510
1511
1512 -- Coefficients (gains)
1513 local Kp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["proportionalGain"]
1514 local Ki = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralGain"]
1515 local Kd = _G[reactorNames[reactorIndex]]["ReactorOptions"]["derivativeGain"]
1516
1517
1518 local adjustAmount = round(Kp * Error + Ki * integratedError + Kd * derivedError, 0) -- for the control rods
1519 local coefficientsString = "Kp:" .. tostring(Kp) .. " Ki:" .. tostring(Ki) .. " Kd:" .. tostring(Kd)
1520 local errorsString = "Ep:" .. tostring(Error) .. " Ei:" .. tostring(integratedError) .. " Ed:" .. tostring(derivedError) .. " AA:" .. tostring(adjustAmount)
1521
1522 printLog(coefficientsString, INFO)
1523 printLog(errorsString, INFO)
1524
1525 local setLevel = rodPercentage + adjustAmount
1526
1527 if setLevel > 100 then
1528 setLevel = 100
1529 end
1530 if setLevel < 0 then
1531 setLevel = 0
1532 end
1533
1534 -- Prevent Runaway
1535 printLog("running temperatureControl Reactor Temp: "..reactorTemp.." Target Temp: "..target, WARN)
1536 if reactorTemp > 2 * target then
1537 printLog("preventing runaway", WARN)
1538 setLevel = 100
1539 end
1540
1541 reactor.setAllControlRodLevels(setLevel)
1542
1543
1544 --always set this number
1545 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = reactorTemp
1546 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"] = reactor.getHotFluidProducedLastTick()
1547
1548 config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]])
1549 end -- if reactor.getActive() then
1550 else
1551 printLog("Bypassed temperature control due to rodOverride being "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1552 end -- if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
1553end -- function temperatureControl(reactorIndex)
1554
1555-- Load saved reactor parameters if ReactorOptions file exists
1556local function loadReactorOptions()
1557 local reactorOptions = fs.open("ReactorOptions", "r") -- See http://computercraft.info/wiki/Fs.open
1558
1559 if reactorOptions then
1560 -- The following values were added by Lolmer
1561 minStoredEnergyPercent = tonumber(reactorOptions.readLine())
1562 maxStoredEnergyPercent = tonumber(reactorOptions.readLine())
1563 --added by Mechaet
1564 -- If we succeeded in reading a string, convert it to a number
1565
1566 reactorOptions.close()
1567 end -- if reactorOptions then
1568
1569 -- Set default values if we failed to read any of the above
1570 if minStoredEnergyPercent == nil then
1571 minStoredEnergyPercent = 15
1572 end
1573
1574 if maxStoredEnergyPercent == nil then
1575 maxStoredEnergyPercent = 85
1576 end
1577
1578end -- function loadReactorOptions()
1579
1580
1581-- Save our reactor parameters
1582local function saveReactorOptions()
1583 local reactorOptions = fs.open("ReactorOptions", "w") -- See http://computercraft.info/wiki/Fs.open
1584
1585 -- If we can save the files, save them
1586 if reactorOptions then
1587 local reactorIndex = 1
1588 -- The following values were added by Lolmer
1589 reactorOptions.writeLine(minStoredEnergyPercent)
1590 reactorOptions.writeLine(maxStoredEnergyPercent)
1591 reactorOptions.close()
1592 else
1593 printLog("Failed to open file ReactorOptions for writing!")
1594 end -- if reactorOptions then
1595end -- function saveReactorOptions()
1596
1597
1598local function displayReactorBars(barParams)
1599 -- Default to first reactor and first monitor
1600 setmetatable(barParams,{__index={reactorIndex=1, monitorIndex=1}})
1601 local reactorIndex, monitorIndex =
1602 barParams[1] or barParams.reactorIndex,
1603 barParams[2] or barParams.monitorIndex
1604
1605 printLog("Called as displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1606
1607 -- Grab current monitor
1608 local monitor = nil
1609 monitor = monitorList[monitorIndex]
1610 if not monitor then
1611 printLog("monitor["..monitorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1612 return -- Invalid monitorIndex
1613 end
1614
1615 -- Grab current reactor
1616 local reactor = nil
1617 reactor = reactorList[reactorIndex]
1618 local reactorInfo = _G[reactorNames[reactorIndex]]
1619 if not reactor then
1620 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
1621 return -- Invalid reactorIndex
1622 else
1623 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
1624 if reactor.getConnected() then
1625 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
1626 else
1627 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1628 return -- Disconnected reactor
1629 end -- if reactor.getConnected() then
1630 end -- if not reactor then
1631
1632 -- Draw border lines
1633 local width, height = monitor.getSize()
1634 printLog("Size of monitor is "..width.."w x"..height.."h in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")")
1635
1636 for i=3, 5 do
1637 monitor.setCursorPos(20, i)
1638 monitor.write("|")
1639 end
1640
1641 drawLine(6, monitorIndex)
1642 monitor.setCursorPos(1, height)
1643 monitor.write("< ")
1644 monitor.setCursorPos(width-1, height)
1645 monitor.write(" >")
1646
1647 -- Draw some text
1648 local fuelString = "Fuel: "
1649 local tempString = "Temp: "
1650 local energyBufferString = ""
1651
1652 if reactor.isActivelyCooled() then
1653 energyBufferString = "Steam: "
1654 else
1655 energyBufferString = "Energy: "
1656 end
1657
1658 local padding = math.max(string.len(fuelString), string.len(tempString), string.len(energyBufferString))
1659
1660 local fuelPercentage = round(reactor.getFuelAmount()/reactor.getFuelAmountMax()*100,1)
1661 print{fuelString,2,3,monitorIndex}
1662 print{fuelPercentage.." %",padding+2,3,monitorIndex}
1663
1664 local reactorTemp = math.ceil(reactor.getFuelTemperature())
1665 print{tempString,2,5,monitorIndex}
1666 print{reactorTemp.." C",padding+2,5,monitorIndex}
1667
1668 local rodPercentage = math.ceil(reactor.getControlRodLevel(0))
1669 printLog("Current Rod Percentage for reactor["..reactorIndex.."] is "..rodPercentage.."% in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1670 print{"Target °C",21,3,monitorIndex}
1671 print{"< >",21,4,monitorIndex}
1672 --print{stringTrim(rodPercentage),23,4,monitorIndex}
1673 local target = tostring(reactorInfo["ReactorOptions"]["reactorTargetTemp"])
1674 print{stringTrim(target),23,4,monitorIndex}
1675
1676
1677 -- getEnergyProducedLastTick() is used for both RF/t (passively cooled) and mB/t (actively cooled)
1678 local energyBuffer = reactor.getEnergyProducedLastTick()
1679 if reactor.isActivelyCooled() then
1680 printLog("reactor["..reactorIndex.."] produced "..energyBuffer.." mB last tick in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1681 else
1682 printLog("reactor["..reactorIndex.."] produced "..energyBuffer.." RF last tick in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1683 end
1684
1685 print{energyBufferString,2,4,monitorIndex}
1686
1687 -- Actively cooled reactors do not produce energy, only hot fluid mB/t to be used in a turbine
1688 -- still uses getEnergyProducedLastTick for mB/t of hot fluid generated
1689 if not reactor.isActivelyCooled() then
1690 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT an actively cooled reactor.")
1691
1692 -- Draw stored energy buffer bar
1693 drawBar(2,8,28,8,colors.gray,monitorIndex)
1694
1695 local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor)
1696 if curStoredEnergyPercent > 4 then
1697 drawBar(2, 8, math.floor(26*curStoredEnergyPercent/100)+2, 8, colors.yellow, monitorIndex)
1698 elseif curStoredEnergyPercent > 0 then
1699 drawPixel(2, 8, colors.yellow, monitorIndex)
1700 end -- if curStoredEnergyPercent > 4 then
1701
1702 print{"Energy Buffer",2,7,monitorIndex}
1703 print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),7,monitorIndex}
1704 print{"%",28,7,monitorIndex}
1705
1706 print{math.ceil(energyBuffer).." RF/t",padding+2,4,monitorIndex}
1707 else
1708 printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is an actively cooled reactor.")
1709 print{math.ceil(energyBuffer).." mB/t",padding+2,4,monitorIndex}
1710 end -- if not reactor.isActivelyCooled() then
1711
1712 -- Print rod override status
1713 local reactorRodOverrideStatus = ""
1714
1715 print{"Rod Auto-adjust:",2,9,monitorIndex}
1716
1717 if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
1718 printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1719 reactorRodOverrideStatus = "Enabled"
1720 monitor.setTextColor(colors.green)
1721 else
1722 printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL")
1723 reactorRodOverrideStatus = "Disabled"
1724 monitor.setTextColor(colors.red)
1725 end -- if not reactorRodOverride then
1726 printLog("reactorRodOverrideStatus is \""..reactorRodOverrideStatus.."\" in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").")
1727
1728 print{reactorRodOverrideStatus, width - string.len(reactorRodOverrideStatus) - 1, 9, monitorIndex}
1729 monitor.setTextColor(colors.white)
1730
1731 print{"Reactivity: "..math.ceil(reactor.getFuelReactivity()).." %", 2, 10, monitorIndex}
1732 print{"Fuel: "..round(reactor.getFuelConsumedLastTick(),3).." mB/t", 2, 11, monitorIndex}
1733 print{"Waste: "..reactor.getWasteAmount().." mB", width-(string.len(reactor.getWasteAmount())+10), 11, monitorIndex}
1734
1735 monitor.setTextColor(colors.blue)
1736 printCentered(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"],12,monitorIndex)
1737 monitor.setTextColor(colors.white)
1738
1739 -- monitor switch controls
1740 monitor.setCursorPos(1, height)
1741 monitor.write("<")
1742 monitor.setCursorPos(width, height)
1743 monitor.write(">")
1744
1745end -- function displayReactorBars(barParams)
1746
1747
1748local function reactorStatus(statusParams)
1749 -- Default to first reactor and first monitor
1750 setmetatable(statusParams,{__index={reactorIndex=1, monitorIndex=1}})
1751 local reactorIndex, monitorIndex =
1752 statusParams[1] or statusParams.reactorIndex,
1753 statusParams[2] or statusParams.monitorIndex
1754 printLog("Called as reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")")
1755
1756 -- Grab current monitor
1757 local monitor = nil
1758 monitor = monitorList[monitorIndex]
1759 if not monitor then
1760 printLog("monitor["..monitorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1761 return -- Invalid monitorIndex
1762 end
1763
1764 -- Grab current reactor
1765 local reactor = nil
1766 reactor = reactorList[reactorIndex]
1767 if not reactor then
1768 printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.")
1769 return -- Invalid reactorIndex
1770 else
1771 printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.")
1772 end
1773
1774 local width, height = monitor.getSize()
1775 local reactorStatus = ""
1776
1777 if reactor.getConnected() then
1778 printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.")
1779
1780 if reactor.getActive() then
1781 reactorStatus = "ONLINE"
1782
1783 -- Set "ONLINE" to blue if the actively cooled reactor is both in cruise mode and online
1784 if _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] and reactor.isActivelyCooled() then
1785 monitor.setTextColor(colors.blue)
1786 else
1787 monitor.setTextColor(colors.green)
1788 end -- if reactorCruising and reactor.isActivelyCooled() then
1789 else
1790 reactorStatus = "OFFLINE"
1791 monitor.setTextColor(colors.red)
1792 end -- if reactor.getActive() then
1793
1794 else
1795 printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1796 reactorStatus = "DISCONNECTED"
1797 monitor.setTextColor(colors.red)
1798 end -- if reactor.getConnected() then
1799 _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"] = reactorStatus
1800 print{reactorStatus, width - string.len(reactorStatus) - 1, 1, monitorIndex}
1801 monitor.setTextColor(colors.white)
1802end -- function reactorStatus(statusParams)
1803
1804
1805-- Display all found reactors' status to selected monitor
1806-- This is only called if multiple reactors and/or a reactor plus at least one turbine are found
1807local function displayAllStatus(monitorIndex)
1808 local reactor, turbine = nil, nil
1809 local onlineReactor, onlineTurbine = 0, 0
1810 local totalReactorRF, totalReactorSteam, totalTurbineRF = 0, 0, 0
1811 local totalReactorFuelConsumed = 0
1812 local totalCoolantStored, totalSteamStored, totalEnergy, totalMaxEnergyStored = 0, 0, 0, 0 -- Total turbine and reactor energy buffer and overall capacity
1813 local maxSteamStored = (2000*#turbineList)+(5000*#reactorList)
1814 local maxCoolantStored = (2000*#turbineList)+(5000*#reactorList)
1815
1816 local monitor = monitorList[monitorIndex]
1817 if not monitor then
1818 printLog("monitor["..monitorIndex.."] in displayAllStatus() is NOT a valid monitor.")
1819 return -- Invalid monitorIndex
1820 end
1821
1822 for reactorIndex = 1, #reactorList do
1823 reactor = reactorList[reactorIndex]
1824 if not reactor then
1825 printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT a valid Big Reactor.")
1826 break -- Invalid reactorIndex
1827 else
1828 printLog("reactor["..reactorIndex.."] in displayAllStatus() is a valid Big Reactor.")
1829 end -- if not reactor then
1830
1831 if reactor.getConnected() then
1832 printLog("reactor["..reactorIndex.."] in displayAllStatus() is connected.")
1833 if reactor.getActive() then
1834 onlineReactor = onlineReactor + 1
1835 totalReactorFuelConsumed = totalReactorFuelConsumed + reactor.getFuelConsumedLastTick()
1836 end -- reactor.getActive() then
1837
1838 -- Actively cooled reactors do not produce or store energy
1839 if not reactor.isActivelyCooled() then
1840 totalMaxEnergyStored = totalMaxEnergyStored + 10000000 -- Reactors store 10M RF
1841 totalEnergy = totalEnergy + reactor.getEnergyStored()
1842 totalReactorRF = totalReactorRF + reactor.getEnergyProducedLastTick()
1843 else
1844 totalReactorSteam = totalReactorSteam + reactor.getEnergyProducedLastTick()
1845 totalSteamStored = totalSteamStored + reactor.getHotFluidAmount()
1846 totalCoolantStored = totalCoolantStored + reactor.getCoolantAmount()
1847 end -- if not reactor.isActivelyCooled() then
1848 else
1849 printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT connected.")
1850 end -- if reactor.getConnected() then
1851 end -- for reactorIndex = 1, #reactorList do
1852
1853 for turbineIndex = 1, #turbineList do
1854 turbine = turbineList[turbineIndex]
1855 if not turbine then
1856 printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT a valid Turbine.")
1857 break -- Invalid turbineIndex
1858 else
1859 printLog("turbine["..turbineIndex.."] in displayAllStatus() is a valid Turbine.")
1860 end -- if not turbine then
1861
1862 if turbine.getConnected() then
1863 printLog("turbine["..turbineIndex.."] in displayAllStatus() is connected.")
1864 if turbine.getActive() then
1865 onlineTurbine = onlineTurbine + 1
1866 end
1867
1868 totalMaxEnergyStored = totalMaxEnergyStored + 1000000 -- Turbines store 1M RF
1869 totalEnergy = totalEnergy + turbine.getEnergyStored()
1870 totalTurbineRF = totalTurbineRF + turbine.getEnergyProducedLastTick()
1871 totalSteamStored = totalSteamStored + turbine.getInputAmount()
1872 totalCoolantStored = totalCoolantStored + turbine.getOutputAmount()
1873 else
1874 printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT connected.")
1875 end -- if turbine.getConnected() then
1876 end -- for turbineIndex = 1, #turbineList do
1877
1878 print{"Reactors online/found: "..onlineReactor.."/"..#reactorList, 2, 3, monitorIndex}
1879 print{"Turbines online/found: "..onlineTurbine.."/"..#turbineList, 2, 4, monitorIndex}
1880
1881 if totalReactorRF ~= 0 then
1882 monitor.setTextColor(colors.blue)
1883 printRight("Reactor", 9, monitorIndex)
1884 monitor.setTextColor(colors.white)
1885 printRight(math.ceil(totalReactorRF).." (RF/t)", 10, monitorIndex)
1886 end
1887
1888 if #turbineList then
1889 -- Display liquids
1890 monitor.setTextColor(colors.blue)
1891 printLeft("Steam (mB)", 6, monitorIndex)
1892 monitor.setTextColor(colors.white)
1893 printLeft(math.ceil(totalSteamStored).."/"..maxSteamStored, 7, monitorIndex)
1894 printLeft(math.ceil(totalReactorSteam).." mB/t", 8, monitorIndex)
1895 monitor.setTextColor(colors.blue)
1896 printRight("Coolant (mB)", 6, monitorIndex)
1897 monitor.setTextColor(colors.white)
1898 printRight(math.ceil(totalCoolantStored).."/"..maxCoolantStored, 7, monitorIndex)
1899
1900 monitor.setTextColor(colors.blue)
1901 printLeft("Turbine", 9, monitorIndex)
1902 monitor.setTextColor(colors.white)
1903 printLeft(math.ceil(totalTurbineRF).." RF/t", 10, monitorIndex)
1904 end -- if #turbineList then
1905
1906 printCentered("Fuel: "..round(totalReactorFuelConsumed,3).." mB/t", 11, monitorIndex)
1907 printCentered("Buffer: "..formatReadableSIUnit(math.ceil(totalEnergy)).."/"..formatReadableSIUnit(totalMaxEnergyStored).." RF", 12, monitorIndex)
1908
1909 -- monitor switch controls
1910 local width, height = monitor.getSize()
1911 monitor.setCursorPos(1, height)
1912 monitor.write("<")
1913 monitor.setCursorPos(width, height)
1914 monitor.write(">")
1915
1916end -- function displayAllStatus()
1917
1918
1919-- Get turbine status
1920local function displayTurbineBars(turbineIndex, monitorIndex)
1921 printLog("Called as displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
1922
1923 -- Grab current monitor
1924 local monitor = nil
1925 monitor = monitorList[monitorIndex]
1926 if not monitor then
1927 printLog("monitor["..monitorIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
1928 return -- Invalid monitorIndex
1929 end
1930
1931 -- Grab current turbine
1932 local turbine = nil
1933 turbine = turbineList[turbineIndex]
1934 if not turbine then
1935 printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
1936 return -- Invalid turbineIndex
1937 else
1938 printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
1939 if turbine.getConnected() then
1940 printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
1941 else
1942 printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
1943 return -- Disconnected turbine
1944 end -- if turbine.getConnected() then
1945 end -- if not turbine then
1946
1947 --local variable to match the view on the monitor
1948 local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
1949
1950 -- Draw border lines
1951 local width, height = monitor.getSize()
1952
1953 for i=3, 6 do
1954 monitor.setCursorPos(21, i)
1955 monitor.write("|")
1956 end
1957
1958 drawLine(7,monitorIndex)
1959 monitor.setCursorPos(1, height)
1960 monitor.write("< ")
1961 monitor.setCursorPos(width-1, height)
1962 monitor.write(" >")
1963
1964 local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"])
1965 print{" mB/t",22,3,monitorIndex}
1966 print{"< >",22,4,monitorIndex}
1967 print{stringTrim(turbineFlowRate),24,4,monitorIndex}
1968 print{" RPM",22,5,monitorIndex}
1969 print{"< >",22,6,monitorIndex}
1970 print{stringTrim(tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])),24,6,monitorIndex}
1971 local rotorSpeedString = "Speed: "
1972 local energyBufferString = "Energy: "
1973 local steamBufferString = "Steam: "
1974 local padding = math.max(string.len(rotorSpeedString), string.len(energyBufferString), string.len(steamBufferString))
1975
1976 local energyBuffer = turbine.getEnergyProducedLastTick()
1977 print{energyBufferString,1,4,monitorIndex}
1978 print{math.ceil(energyBuffer).." RF/t",padding+1,4,monitorIndex}
1979
1980 local rotorSpeed = math.ceil(turbine.getRotorSpeed())
1981 print{rotorSpeedString,1,5,monitorIndex}
1982 print{rotorSpeed.." RPM",padding+1,5,monitorIndex}
1983
1984 local steamBuffer = turbine.getFluidFlowRate()
1985 print{steamBufferString,1,6,monitorIndex}
1986 print{steamBuffer.." mB/t",padding+1,6,monitorIndex}
1987
1988 -- PaintUtils only outputs to term., not monitor.
1989 -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/
1990
1991 -- Draw stored energy buffer bar
1992 drawBar(1,9,28,9,colors.gray,monitorIndex)
1993
1994 local curStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine)
1995 if curStoredEnergyPercent > 4 then
1996 drawBar(1, 9, math.floor(26*curStoredEnergyPercent/100)+2, 9, colors.yellow,monitorIndex)
1997 elseif curStoredEnergyPercent > 0 then
1998 drawPixel(1, 9, colors.yellow, monitorIndex)
1999 end -- if curStoredEnergyPercent > 4 then
2000
2001 print{"Energy Buffer",1,8,monitorIndex}
2002 print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),8,monitorIndex}
2003 print{"%",28,8,monitorIndex}
2004
2005 -- Print rod override status
2006 local turbineFlowRateOverrideStatus = ""
2007
2008 print{"Flow Auto-adjust:",2,10,monitorIndex}
2009
2010 if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
2011 turbineFlowRateOverrideStatus = "Enabled"
2012 monitor.setTextColor(colors.green)
2013 else
2014 turbineFlowRateOverrideStatus = "Disabled"
2015 monitor.setTextColor(colors.red)
2016 end -- if not reactorRodOverride then
2017
2018 print{turbineFlowRateOverrideStatus, width - string.len(turbineFlowRateOverrideStatus) - 1, 10, monitorIndex}
2019 monitor.setTextColor(colors.white)
2020
2021 -- Print coil status
2022 local turbineCoilStatus = ""
2023
2024 print{"Turbine coils:",2,11,monitorIndex}
2025
2026 if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] == "true")) then
2027 turbineCoilStatus = "Engaged"
2028 monitor.setTextColor(colors.green)
2029 else
2030 turbineCoilStatus = "Disengaged"
2031 monitor.setTextColor(colors.red)
2032 end
2033
2034 print{turbineCoilStatus, width - string.len(turbineCoilStatus) - 1, 11, monitorIndex}
2035 monitor.setTextColor(colors.white)
2036
2037 monitor.setTextColor(colors.blue)
2038 printCentered(_G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"],12,monitorIndex)
2039 monitor.setTextColor(colors.white)
2040
2041 -- monitor switch controls
2042 monitor.setCursorPos(1, height)
2043 monitor.write("<")
2044 monitor.setCursorPos(width, height)
2045 monitor.write(">")
2046
2047 -- Need equation to figure out rotor efficiency and display
2048end -- function displayTurbineBars(statusParams)
2049
2050
2051-- Display turbine status
2052local function turbineStatus(turbineIndex, monitorIndex)
2053 printLog("Called as turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").")
2054
2055 -- Grab current monitor
2056 local monitor = nil
2057 monitor = monitorList[monitorIndex]
2058 if not monitor then
2059 printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.")
2060 return -- Invalid monitorIndex
2061 end
2062
2063 -- Grab current turbine
2064 local turbine = nil
2065 turbine = turbineList[turbineIndex]
2066 if not turbine then
2067 printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.")
2068 return -- Invalid turbineIndex
2069 else
2070 printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.")
2071 end
2072
2073 local width, height = monitor.getSize()
2074 local turbineStatus = ""
2075
2076 if turbine.getConnected() then
2077 printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.")
2078 if turbine.getActive() then
2079 turbineStatus = "ONLINE"
2080 monitor.setTextColor(colors.green)
2081 else
2082 turbineStatus = "OFFLINE"
2083 monitor.setTextColor(colors.red)
2084 end -- if turbine.getActive() then
2085 _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"] = turbineStatus
2086 else
2087 printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.")
2088 turbineStatus = "DISCONNECTED"
2089 monitor.setTextColor(colors.red)
2090 end -- if turbine.getConnected() then
2091
2092 print{turbineStatus, width - string.len(turbineStatus) - 1, 1, monitorIndex}
2093 monitor.setTextColor(colors.white)
2094end -- function function turbineStatus(turbineIndex, monitorIndex)
2095
2096
2097-- Adjust Turbine flow rate to maintain 900 or 1,800 RPM, and disengage coils when buffer full
2098local function flowRateControl(turbineIndex)
2099 if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
2100
2101 printLog("Called as flowRateControl(turbineIndex="..turbineIndex..").")
2102
2103 -- Grab current turbine
2104 local turbine = nil
2105 turbine = turbineList[turbineIndex]
2106
2107 -- assign for the duration of this run
2108 local lastTurbineSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"])
2109 local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])
2110 local coilsEngaged = _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] or _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] == "true"
2111
2112 if not turbine then
2113 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT a valid Big Turbine.")
2114 return -- Invalid turbineIndex
2115 else
2116 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is a valid Big Turbine.")
2117
2118 if turbine.getConnected() then
2119 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is connected.")
2120 else
2121 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT connected.")
2122 end -- if turbine.getConnected() then
2123 end -- if not turbine then
2124
2125 -- No point modifying control rod levels for temperature if the turbine is offline
2126 if turbine.getActive() then
2127 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is active.")
2128
2129 local flowRate
2130 local flowRateUserMax = math.ceil(turbine.getFluidFlowRateMax())
2131 local rotorSpeed = math.ceil(turbine.getRotorSpeed())
2132 local newFlowRate
2133
2134
2135 -- Flips on and off the coils. Binary and stupid
2136 local currentStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine)
2137 if currentStoredEnergyPercent < 15 and turbineBaseSpeed - rotorSpeed <= 50 then
2138 coilsEngaged = true
2139 end
2140 if currentStoredEnergyPercent > 85 or turbineBaseSpeed - rotorSpeed > 50 then
2141 coilsEngaged = false
2142
2143 end
2144
2145
2146 -- Uses two stored steam values. One for coils engaged, one for disengaged.
2147 if coilsEngaged then
2148 flowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"])
2149 else
2150 flowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"])
2151 end
2152
2153 -- PID Controller
2154 local target = turbineBaseSpeed
2155 local Error = target - rotorSpeed
2156 local derivedError = lastTurbineSpeed - rotorSpeed
2157 local integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] + Error
2158
2159 if integratedError > _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"] then
2160 integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"]
2161 end
2162 if integratedError > _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"] then
2163 integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"]
2164 end
2165
2166 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] = integratedError
2167
2168
2169
2170
2171 local Kp = _G[turbineNames[turbineIndex]]["TurbineOptions"]["proportionalGain"]
2172 local Ki = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralGain"]
2173 local Kd = _G[turbineNames[turbineIndex]]["TurbineOptions"]["derivativeGain"]
2174
2175 local adjustAmount = round(Kp * Error + Ki * integratedError + Kd * derivedError, 0) -- for the turbine flow rate
2176
2177 newFlowRate = flowRate + adjustAmount
2178
2179
2180
2181
2182 -- Failsafe to prevent explosions
2183 if rotorSpeed >= 2000 then
2184 coilsEngaged = true
2185 newFlowRate = 0
2186 end
2187
2188
2189
2190 --boundary check
2191 if newFlowRate > 2000 then
2192 newFlowRate = 2000
2193 elseif newFlowRate < 0 then
2194 newFlowRate = 0
2195 end -- if newFlowRate > 2000 then
2196 --no sense running an adjustment if it's not necessary
2197
2198 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is being commanded to "..newFlowRate.." mB/t flow")
2199 newFlowRate = round(newFlowRate, 0)
2200 turbine.setFluidFlowRateMax(newFlowRate)
2201 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newFlowRate -- For display purposes
2202
2203 -- For the PID
2204 if coilsEngaged then
2205 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"] = newFlowRate
2206 else
2207 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"] = newFlowRate
2208 end
2209
2210 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
2211
2212
2213 turbine.setInductorEngaged(coilsEngaged)
2214
2215 --always set this
2216 _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] = coilsEngaged
2217 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = rotorSpeed
2218 config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]])
2219 else
2220 printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT active.")
2221 end -- if turbine.getActive() then
2222 else
2223 printLog("turbine["..turbineIndex.."] has flow override set to "..tostring(_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"])..", bypassing flow control.")
2224 end -- if not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] then
2225end -- function flowRateControl(turbineIndex)
2226
2227
2228local function helpText()
2229
2230 -- these keys are actually defined in eventHandler(), check there
2231 return [[Keyboard commands:
2232 m Select next monitor
2233 s Make selected monitor display global status
2234 x Make selected monitor display debug information
2235
2236 d Toggle debug mode
2237
2238 q Quit
2239 r Quit and reboot
2240 h Print this help
2241]]
2242
2243end -- function helpText()
2244
2245local function initializePeripherals()
2246 monitorAssignments = {}
2247 -- Get our list of connected monitors and reactors
2248 findMonitors()
2249 findReactors()
2250 findTurbines()
2251 assignMonitors()
2252end
2253
2254
2255local function updateMonitors()
2256
2257 -- Display overall status on selected monitors
2258 for monitorName, deviceData in pairs(monitorAssignments) do
2259 local monitor = nil
2260 local monitorIndex = deviceData.index
2261 local monitorType = deviceData.type
2262 monitor = monitorList[monitorIndex]
2263
2264 printLog("main(): Trying to display "..monitorType.." on "..monitorNames[monitorIndex].."["..monitorIndex.."]", DEBUG)
2265
2266 if #monitorList < (#reactorList + #turbineList + 1) then
2267 printLog("You may want "..(#reactorList + #turbineList + 1).." monitors for your "..#reactorList.." connected reactors and "..#turbineList.." connected turbines.")
2268 end
2269
2270 if (not monitor) or (not monitor.getSize()) then
2271
2272 printLog("monitor["..monitorIndex.."] in main() is NOT a valid monitor, discarding", ERROR)
2273 monitorAssignments[monitorName] = nil
2274 -- 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
2275 break
2276
2277 elseif monitorType == "Status" then
2278
2279 -- General status display
2280 clearMonitor(progName.." "..progVer, monitorIndex) -- Clear monitor and draw borders
2281 printCentered(progName.." "..progVer, 1, monitorIndex)
2282 displayAllStatus(monitorIndex)
2283
2284 elseif monitorType == "Reactor" then
2285
2286 -- Reactor display
2287 local reactorMonitorIndex = monitorIndex
2288 for reactorIndex = 1, #reactorList do
2289
2290 if deviceData.reactorName == reactorNames[reactorIndex] then
2291
2292 printLog("Attempting to display reactor["..reactorIndex.."] on monitor["..monitorIndex.."]...", DEBUG)
2293 -- Only attempt to assign a monitor if we have a monitor for this reactor
2294 if (reactorMonitorIndex <= #monitorList) then
2295 printLog("Displaying reactor["..reactorIndex.."] on monitor["..reactorMonitorIndex.."].")
2296
2297 clearMonitor(progName, reactorMonitorIndex) -- Clear monitor and draw borders
2298 printCentered(progName, 1, reactorMonitorIndex)
2299
2300 -- Display reactor status, includes "Disconnected" but found reactors
2301 reactorStatus{reactorIndex, reactorMonitorIndex}
2302
2303 -- Draw the borders and bars for the current reactor on the current monitor
2304 displayReactorBars{reactorIndex, reactorMonitorIndex}
2305 end
2306
2307 end -- if deviceData.reactorName == reactorNames[reactorIndex] then
2308
2309 end -- for reactorIndex = 1, #reactorList do
2310
2311 elseif monitorType == "Turbine" then
2312
2313 -- Turbine display
2314 local turbineMonitorIndex = monitorIndex
2315 for turbineIndex = 1, #turbineList do
2316
2317 if deviceData.turbineName == turbineNames[turbineIndex] then
2318 printLog("Attempting to display turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."]...", DEBUG)
2319 -- Only attempt to assign a monitor if we have a monitor for this turbine
2320 --printLog("debug: "..turbineMonitorIndex)
2321 if (turbineMonitorIndex <= #monitorList) then
2322 printLog("Displaying turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."].")
2323 clearMonitor(progName, turbineMonitorIndex) -- Clear monitor and draw borders
2324 printCentered(progName, 1, turbineMonitorIndex)
2325
2326 -- Display turbine status, includes "Disconnected" but found turbines
2327 turbineStatus(turbineIndex, turbineMonitorIndex)
2328
2329 -- Draw the borders and bars for the current turbine on the current monitor
2330 displayTurbineBars(turbineIndex, turbineMonitorIndex)
2331 end
2332 end
2333 end
2334
2335 elseif monitorType == "Debug" then
2336
2337 -- do nothing, printLog() outputs to here
2338
2339 else
2340
2341 clearMonitor(progName, monitorIndex)
2342 print{"Monitor inactive", 7, 7, monitorIndex}
2343
2344 end -- if monitorType == [...]
2345 end
2346end
2347
2348function main()
2349 -- Load reactor parameters and initialize systems
2350 loadReactorOptions()
2351 initializePeripherals()
2352
2353 write(helpText())
2354
2355 --TODO: this is a global variable set by the event handler!
2356 while not finished do
2357
2358 updateMonitors()
2359
2360 local reactor = nil
2361 local sd = 0
2362
2363 -- Iterate through reactors
2364 for reactorIndex = 1, #reactorList do
2365 local monitor = nil
2366
2367 reactor = reactorList[reactorIndex]
2368 if not reactor then
2369 printLog("reactor["..reactorIndex.."] in main() is NOT a valid Big Reactor.")
2370 break -- Invalid reactorIndex
2371 else
2372 printLog("reactor["..reactorIndex.."] in main() is a valid Big Reactor.")
2373 end -- if not reactor then
2374
2375 if reactor.getConnected() then
2376 printLog("reactor["..reactorIndex.."] is connected.")
2377
2378 -- Collect steam production data
2379 if reactor.isActivelyCooled() then
2380 sd = sd + reactor.getHotFluidProducedLastTick()
2381 else -- Not actively cooled
2382 local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor)
2383
2384 -- Shutdown reactor if current stored energy % is >= desired level, otherwise activate
2385 -- First pass will have curStoredEnergyPercent=0 until displayBars() is run once
2386 if curStoredEnergyPercent >= maxStoredEnergyPercent then
2387 reactor.setActive(false)
2388 -- Do not auto-start the reactor if it was manually powered off (autoStart=false)
2389 elseif (curStoredEnergyPercent <= minStoredEnergyPercent) and (_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] == true) then
2390 reactor.setActive(true)
2391 end -- if curStoredEnergyPercent >= maxStoredEnergyPercent then
2392 end -- if reactor.isActivelyCooled() then
2393
2394 -- Don't try to auto-adjust control rods if manual control is requested
2395 if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then
2396 temperatureControl(reactorIndex)
2397 end -- if not reactorRodOverride then
2398
2399 else
2400 printLog("reactor["..reactorIndex.."] is NOT connected.")
2401 end -- if reactor.getConnected() then
2402 end -- for reactorIndex = 1, #reactorList do
2403
2404 -- Now that temperatureControl() had a chance to use it, reset/calculate steam data for next iteration
2405 printLog("Steam requested: "..steamRequested.." mB")
2406 printLog("Steam delivered: "..steamDelivered.." mB")
2407 steamDelivered = sd
2408 steamRequested = 0
2409
2410 -- Turbine control
2411 for turbineIndex = 1, #turbineList do
2412
2413 local turbine = turbineList[turbineIndex]
2414 if not turbine then
2415 printLog("turbine["..turbineIndex.."] in main() is NOT a valid Big Turbine.")
2416 break -- Invalid turbineIndex
2417 else
2418 printLog("turbine["..turbineIndex.."] in main() is a valid Big Turbine.")
2419 end -- if not turbine then
2420
2421 if turbine.getConnected() then
2422 printLog("turbine["..turbineIndex.."] is connected.")
2423
2424 if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then
2425 flowRateControl(turbineIndex)
2426 end -- if not turbineFlowRateOverride[turbineIndex] then
2427
2428 -- Collect steam consumption data
2429 if turbine.getActive() then
2430 steamRequested = steamRequested + turbine.getFluidFlowRateMax()
2431 end
2432 else
2433 printLog("turbine["..turbineIndex.."] is NOT connected.")
2434 end -- if turbine.getConnected() then
2435 end -- for reactorIndex = 1, #reactorList do
2436
2437 wait(loopTime) -- Sleep. No, wait...
2438 saveReactorOptions()
2439 end -- while not finished do
2440end -- main()
2441
2442-- handle all the user interaction events
2443eventHandler = function(event, arg1, arg2, arg3)
2444
2445 printLog(string.format("handleEvent(%s, %s, %s, %s)", tostring(event), tostring(arg1), tostring(arg2), tostring(arg3)), DEBUG)
2446
2447 if event == "monitor_touch" then
2448 sideClick, xClick, yClick = arg1, math.floor(arg2), math.floor(arg3)
2449 UI:handlePossibleClick()
2450 elseif (event == "peripheral") or (event == "peripheral_detach") then
2451 printLog("Change in network detected. Reinitializing peripherals. We will be back shortly.", WARN)
2452 initializePeripherals()
2453 elseif event == "char" and not inManualMode then
2454 local ch = string.lower(arg1)
2455 -- remember to update helpText() when you edit these
2456 if ch == "q" then
2457 finished = true
2458 elseif ch == "d" then
2459 debugMode = not debugMode
2460 local modeText
2461 if debugMode then
2462 modeText = "on"
2463 else
2464 modeText = "off"
2465 end
2466 termRestore()
2467 write("debugMode "..modeText.."\n")
2468 elseif ch == "m" then
2469 UI:selectNextMonitor()
2470 elseif ch == "s" then
2471 UI:selectStatus()
2472 elseif ch == "x" then
2473 UI:selectDebug()
2474 elseif ch == "r" then
2475 finished = true
2476 os.reboot()
2477 elseif ch == "h" then
2478 write(helpText())
2479 end -- if ch == "q" then
2480 end -- if event == "monitor_touch" then
2481
2482 updateMonitors()
2483
2484end -- function eventHandler()
2485
2486main()
2487
2488-- Clear up after an exit
2489term.clear()
2490term.setCursorPos(1,1)