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