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