Advertisement
Derek1017

Big Reactors Monitor Control

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