--[[ Program name: Lolmer's EZ-NUKE reactor control system Version: v0.3.18 Programmer: Lolmer With great assistance from @mechaet and @thetaphi Last update: 2015-05-11 Pastebin: http://pastebin.com/fguScPBQ GitHub: https://github.com/sandalle/minecraft_bigreactor_control Description: 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. These scripts are known to run on: - Minecraft 1.6.4 - NST Diet (https://www.technicpack.net/modpack/never-stop-toasting-diet.254882) - NST Maxx (https://www.technicpack.net/modpack/nstmaxx.398172) - Minecraft 1.7.10 - FTB Infinity (http://www.feed-the-beast.com/modpacks/FTBInfinity) - Modderation: Permabanned (https://www.technicpack.net/modpack/modderation-permabanned-edition.449941) - Modderation: FYAD (https://www.technicpack.net/modpack/modderation-fyad-edition.696257) - Minecraft 1.12: - All the Mods 3 (https://minecraft.curseforge.com/projects/all-the-mods-3) 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: 1) One Advanced Monitor for overall status display plus one or more Reactors plus none or more Turbines. 2) One Advanced Monitor for overall status display plus (furthest monitor from computer by cable length) one Advanced Monitor for each connected Reactor plus (subsequent found monitors) one Advanced Monitor for each connected Turbine (last group of monitors found). If you enable debug mode, add one additional Advanced Monitor for #1 or #2. Notes ---------------------------- - Only one reactor and one, two, and three turbines have been tested with the above, but IN THEORY any number is supported. - Devices are found in the reverse order they are plugged in, so monitor_10 will be found before monitor_9. When using actively cooled reactors with turbines, keep the following in mind: - 1 mB steam carries up to 10RF of potential energy to extract in a turbine. - Actively cooled reactors produce steam, not power. - You will need about 10 mB of water for each 1 mB of steam that you want to create in a 7^3 reactor. - Two 15x15x14 Turbines can output 260K RF/t by just one 7^3 (four rods) reactor putting out 4k mB steam. Features ---------------------------- - Configurable min/max energy buffer and min/max temperature via ReactorOptions file. - Disengages coils and minimizes flow for turbines over max energy buffer. - ReactorOptions is read on start and then current values are saved every program cycle. - 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. - Auto-adjusts control rods per reactor to maintain temperature. - Will display reactor data to all attached monitors of correct dimensions. - For multiple monitors, the first monitor (often last plugged in) is the overall status monitor. - For multiple monitors, the first monitor (often last plugged in) is the overall status monitor. - A new cruise mode from mechaet, ONLINE will be "blue" when active, to keep your actively cooled reactors running smoothly. GUI Usage ---------------------------- - Right-clicking between "< * >" of the last row of a monitor alternates the device selection between Reactor, Turbine, and Status output. - Right-clicking "<" and ">" switches between connected devices, starting with the currently selected type, but not limited to them. - The other "<" and ">" buttons, when right-clicked with the mouse, will decrease and increase, respectively, the values assigned to the monitor: - "Rod (%)" will lower/raise the Reactor Control Rods for that Reactor - "mB/t" will lower/raise the Turbine Flow Rate maximum for that Turbine - "RPM" will lower/raise the target Turbine RPM for that Turbine - Right-clicking between the "<" and ">" (not on them) will disable auto-adjust of that value for attached device. - Right-clicking on the "Enabled" or "Disabled" text for auto-adjust will do the same. - Right-clicking on "ONLINE" or "OFFLINE" at the top-right will toggle the state of attached device. Default values ---------------------------- - Rod Control: 90% (Let's start off safe and then power up as we can) - Minimum Energy Buffer: 15% (will power on below this value) - Maximum Energy Buffer: 85% (will power off above this value) - Minimum Passive Cooling Temperature: 950^C (will raise control rods below this value) - Maximum Passive Cooling Temperature: 1,400^C (will lower control rods above this value) - Minimum Active Cooling Temperature: 300^C (will raise the control rods below this value) - Maximum Active Cooling Temperature: 420^C (will lower control rods above this value) - Optimal Turbine RPM: 900, 1,800, or 2,700 (divisible by 900) - New user-controlled option for target speed of turbines, defaults to 2726RPM, which is high-optimal. Requirements ---------------------------- - Advanced Monitor size is X: 29, Y: 12 with a 3x2 size - Computer or Advanced Computer - Modems (not wireless) connecting each of the Computer to both the Advanced Monitor and Reactor Computer Port. - Big Reactors (http://www.big-reactors.com/) 0.3.2A+ or Extreme Reactors (https://minecraft.curseforge.com/projects/extreme-reactors) - Computercraft (http://computercraft.info/) 1.58, 1.63+, 1.73+, or 1.80pr1+ - Reset the computer any time number of connected devices change. Resources ---------------------------- - This script is available from: - http://pastebin.com/fguScPBQ - https://github.com/sandalle/minecraft_bigreactor_control - Start-up script is available from: - http://pastebin.com/ZTMzRLez - https://github.com/sandalle/minecraft_bigreactor_control - Other reactor control program which I based my program on: - http://pastebin.com/aMAu4X5J (ScatmanJohn) - http://pastebin.com/HjUVNDau (version ScatmanJohn based his on) - A simpler Big Reactor control program is available from: - http://pastebin.com/7S5xCvgL (IronClaymore only for passively cooled reactors) - Reactor Computer Port API: http://wiki.technicpack.net/Reactor_Computer_Port - Computercraft API: http://computercraft.info/wiki/Category:APIs - Big Reactors Efficiency, Speculation and Questions! http://www.reddit.com/r/feedthebeast/comments/1vzds0/big_reactors_efficiency_speculation_and_questions/ - Big Reactors API code: https://github.com/erogenousbeef/BigReactors/blob/master/erogenousbeef/bigreactors/common/multiblock/tileentity/TileEntityReactorComputerPort.java - Big Reactors API: http://big-reactors.com/cc_api.html - Big Reactor Simulator from http://reddit.com/r/feedthebeast : http://br.sidoh.org/ - 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 ChangeLog ============================ - 0.3.18 Fix Issue #61 (Reactor Online/Offline input non-responsive when reactor is converted from passive to active cooled). Prior ChangeLogs are posted at https://github.com/sandalle/minecraft_bigreactor_control/releases TODO ============================ See https://github.com/sandalle/minecraft_bigreactor_control/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement :) ]]-- -- Some global variables local progVer = "0.3.18" local progName = "EZ-NUKE" local sideClick, xClick, yClick = nil, 0, 0 local loopTime = 2 local controlRodAdjustAmount = 1 -- Default Reactor Rod Control % adjustment amount local flowRateAdjustAmount = 25 -- Default Turbine Flow Rate in mB adjustment amount local debugMode = false -- End multi-reactor cleanup section local minStoredEnergyPercent = nil -- Max energy % to store before activate local maxStoredEnergyPercent = nil -- Max energy % to store before shutdown local monitorList = {} -- Empty monitor array local monitorNames = {} -- Empty array of monitor names local reactorList = {} -- Empty reactor array local reactorNames = {} -- Empty array of reactor names local turbineList = {} -- Empty turbine array local turbineNames = {} -- Empty array of turbine names local monitorAssignments = {} -- Empty array of monitor - "what to display" assignments local monitorOptionFileName = "monitors.options" -- File for saving the monitor assignments local knowlinglyOverride = false -- Issue #39 Allow the user to override safe values, currently only enabled for actively cooled reactor min/max temperature local steamRequested = 0 -- Sum of Turbine Flow Rate in mB local steamDelivered = 0 -- Sum of Active Reactor steam output in mB -- Log levels local FATAL = 16 local ERROR = 8 local WARN = 4 local INFO = 2 local DEBUG = 1 term.clear() term.setCursorPos(2,1) write("Initializing program...\n") -- File needs to exist for append "a" later and zero it out if it already exists -- Always initalize this file to avoid confusion with old files and the latest run local logFile = fs.open("reactorcontrol.log", "w") if logFile then logFile.writeLine("Minecraft time: Day "..os.day().." at "..textutils.formatTime(os.time(),true)) logFile.close() else error("Could not open file reactorcontrol.log for writing.") end -- Helper functions local function termRestore() local ccVersion = nil ccVersion = os.version() if ccVersion == "CraftOS 1.6" or "CraftOS 1.7" then term.redirect(term.native()) elseif ccVersion == "CraftOS 1.5" then term.restore() else -- Default to older term.restore printLog("Unsupported CraftOS found. Reported version is \""..ccVersion.."\".") term.restore() end -- if ccVersion end -- function termRestore() local function printLog(printStr, logLevel) logLevel = logLevel or INFO -- 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. -- Since you're already looking at it, you can adjust your preferred log level right here. if debugMode and (logLevel >= WARN) then -- If multiple monitors, print to all of them for monitorName, deviceData in pairs(monitorAssignments) do if deviceData.type == "Debug" then debugMonitor = monitorList[deviceData.index] if(not debugMonitor) or (not debugMonitor.getSize()) then term.write("printLog(): debug monitor "..monitorName.." failed") else term.redirect(debugMonitor) -- Redirect to selected monitor debugMonitor.setTextScale(0.5) -- Fit more logs on screen local color = colors.lightGray if (logLevel == WARN) then color = colors.white elseif (logLevel == ERROR) then color = colors.red elseif (logLevel == FATAL) then color = colors.black debugMonitor.setBackgroundColor(colors.red) end debugMonitor.setTextColor(color) write(printStr.."\n") -- May need to use term.scroll(x) if we output too much, not sure debugMonitor.setBackgroundColor(colors.black) termRestore() end end end -- for local logFile = fs.open("reactorcontrol.log", "a") -- See http://computercraft.info/wiki/Fs.open if logFile then logFile.writeLine(printStr) logFile.close() else error("Cannot open file reactorcontrol.log for appending!") end -- if logFile then end -- if debugMode then end -- function printLog(printStr) -- Trim a string function stringTrim(s) assert(s ~= nil, "String can't be nil") return(string.gsub(s, "^%s*(.-)%s*$", "%1")) end -- Format number with [k,M,G,T,P,E] postfix or exponent, depending on how large it is local function formatReadableSIUnit(num) printLog("formatReadableSIUnit("..num..")", DEBUG) num = tonumber(num) if(num < 1000) then return tostring(num) end local sizes = {"", "k", "M", "G", "T", "P", "E"} local exponent = math.floor(math.log(num,10)) local group = math.floor(exponent / 3) if group > #sizes then return string.format("%e", num) else local divisor = math.pow(10, (group - 1) * 3) return string.format("%i%s", num / divisor, sizes[group]) end end -- local function formatReadableSIUnit(num) -- pretty printLog() a table local function tprint (tbl, loglevel, indent) if not loglevel then loglevel = DEBUG end if not indent then indent = 0 end for k, v in pairs(tbl) do local formatting = string.rep(" ", indent) .. k .. ": " if type(v) == "table" then printLog(formatting, loglevel) tprint(v, loglevel, indent+1) elseif type(v) == 'boolean' or type(v) == "function" then printLog(formatting .. tostring(v), loglevel) else printLog(formatting .. v, loglevel) end end end -- function tprint() config = {} -- Save a table into a config file -- path: path of the file to write -- tab: table to save config.save = function(path, tab) printLog("Save function called for config for "..path.." EOL") assert(path ~= nil, "Path can't be nil") assert(type(tab) == "table", "Second parameter must be a table") local f = io.open(path, "w") local i = 0 for key, value in pairs(tab) do if i ~= 0 then f:write("\n") end f:write("["..key.."]".."\n") for key2, value2 in pairs(tab[key]) do key2 = stringTrim(key2) --doesn't like boolean values if (type(value2) ~= "boolean") then value2 = stringTrim(value2) else value2 = tostring(value2) end key2 = key2:gsub(";", "\\;") key2 = key2:gsub("=", "\\=") value2 = value2:gsub(";", "\\;") value2 = value2:gsub("=", "\\=") f:write(key2.."="..value2.."\n") end i = i + 1 end f:close() end --config.save = function(path, tab) -- Load a config file -- path: path of the file to read config.load = function(path) printLog("Load function called for config for "..path.." EOL") assert(path ~= nil, "Path can't be nil") local f = fs.open(path, "r") if f ~= nil then printLog("Successfully opened "..path.." for reading EOL") local tab = {} local line = "" local newLine local i local currentTag = nil local found = false local pos = 0 while line ~= nil do found = false line = line:gsub("\\;", "#_!36!_#") -- to keep \; line = line:gsub("\\=", "#_!71!_#") -- to keep \= if line ~= "" then -- Delete comments newLine = line line = "" for i=1, string.len(newLine) do if string.sub(newLine, i, i) ~= ";" then line = line..newLine:sub(i, i) else break end end line = stringTrim(line) -- Find tag if line:sub(1, 1) == "[" and line:sub(line:len(), line:len()) == "]" then currentTag = stringTrim(line:sub(2, line:len()-1)) tab[currentTag] = {} found = true end -- Find key and values if not found and line ~= "" then pos = line:find("=") if pos == nil then error("Bad INI file structure") end line = line:gsub("#_!36!_#", ";") line = line:gsub("#_!71!_#", "=") tab[currentTag][stringTrim(line:sub(1, pos-1))] = stringTrim(line:sub(pos+1, line:len())) found = true end end line = f.readLine() end f:close() return tab else printLog("Could NOT opened "..path.." for reading! EOL") return nil end end --config.load = function(path) -- round() function from mechaet local function round(num, places) local mult = 10^places local addon = nil if ((num * mult) < 0) then addon = -.5 else addon = .5 end local integer, decimal = math.modf(num*mult+addon) local newNum = integer/mult printLog("Called round(num="..num..",places="..places..") returns \""..newNum.."\".") return newNum end -- function round(num, places) local function print(printParams) -- Default to xPos=1, yPos=1, and first monitor setmetatable(printParams,{__index={xPos=1, yPos=1, monitorIndex=1}}) local printString, xPos, yPos, monitorIndex = printParams[1], -- Required parameter printParams[2] or printParams.xPos, printParams[3] or printParams.yPos, printParams[4] or printParams.monitorIndex local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in print() is NOT a valid monitor.") return -- Invalid monitorIndex end monitor.setCursorPos(xPos, yPos) monitor.write(printString) end -- function print(printParams) -- Replaces the one from FC_API (http://pastebin.com/A9hcbZWe) and adding multi-monitor support local function printCentered(printString, yPos, monitorIndex) local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in printCentered() is NOT a valid monitor.", ERROR) return -- Invalid monitorIndex end local width, height = monitor.getSize() local monitorNameLength = 0 -- Special changes for title bar if yPos == 1 then -- Add monitor name to first line monitorNameLength = monitorNames[monitorIndex]:len() width = width - monitorNameLength -- add a space -- Leave room for "offline" and "online" on the right except for overall status display if monitorAssignments[monitorNames[monitorIndex]].type ~= "Status" then width = width - 7 end end monitor.setCursorPos(monitorNameLength + math.ceil((1 + width - printString:len())/2), yPos) monitor.write(printString) end -- function printCentered(printString, yPos, monitorIndex) -- Print text padded from the left side -- Clear the left side of the screen local function printLeft(printString, yPos, monitorIndex) local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in printLeft() is NOT a valid monitor.", ERROR) return -- Invalid monitorIndex end local gap = 1 local width = monitor.getSize() -- Clear left-half of the monitor for curXPos = 1, (width / 2) do monitor.setCursorPos(curXPos, yPos) monitor.write(" ") end -- Write our string left-aligned monitor.setCursorPos(1+gap, yPos) monitor.write(printString) end -- Print text padded from the right side -- Clear the right side of the screen local function printRight(printString, yPos, monitorIndex) local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in printRight() is NOT a valid monitor.", ERROR) return -- Invalid monitorIndex end -- Make sure printString is a string printString = tostring(printString) local gap = 1 local width = monitor.getSize() -- Clear right-half of the monitor for curXPos = (width/2), width do monitor.setCursorPos(curXPos, yPos) monitor.write(" ") end -- Write our string right-aligned monitor.setCursorPos(math.floor(width) - math.ceil(printString:len()+gap), yPos) monitor.write(printString) end -- Replaces the one from FC_API (http://pastebin.com/A9hcbZWe) and adding multi-monitor support local function clearMonitor(printString, monitorIndex) local monitor = nil monitor = monitorList[monitorIndex] printLog("Called as clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..").") if not monitor then printLog("monitor["..monitorIndex.."] in clearMonitor(printString="..printString..",monitorIndex="..monitorIndex..") is NOT a valid monitor.", ERROR) return -- Invalid monitorIndex end local gap = 2 monitor.clear() local width, height = monitor.getSize() printCentered(printString, 1, monitorIndex) monitor.setTextColor(colors.blue) print{monitorNames[monitorIndex], 1, 1, monitorIndex} monitor.setTextColor(colors.white) for i=1, width do monitor.setCursorPos(i, gap) monitor.write("-") end monitor.setCursorPos(1, gap+1) end -- function clearMonitor(printString, monitorIndex) -- Return a list of all connected (including via wired modems) devices of "deviceType" local function getDevices(deviceType) printLog("Called as getDevices(deviceType="..deviceType..")") local deviceName = nil local deviceIndex = 1 local deviceList, deviceNames = {}, {} -- Empty array, which grows as we need local peripheralList = peripheral.getNames() -- Get table of connected peripherals deviceType = deviceType:lower() -- Make sure we're matching case here for peripheralIndex = 1, #peripheralList do -- Log every device found -- printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] attached as \""..peripheralList[peripheralIndex].."\".") if (string.lower(peripheral.getType(peripheralList[peripheralIndex])) == deviceType) then -- Log devices found which match deviceType and which device index we give them printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".") write("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".\n") deviceNames[deviceIndex] = peripheralList[peripheralIndex] deviceList[deviceIndex] = peripheral.wrap(peripheralList[peripheralIndex]) deviceIndex = deviceIndex + 1 end end -- for peripheralIndex = 1, #peripheralList do return deviceList, deviceNames end -- function getDevices(deviceType) -- Draw a line across the entire x-axis local function drawLine(yPos, monitorIndex) local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in drawLine() is NOT a valid monitor.") return -- Invalid monitorIndex end local width, height = monitor.getSize() for i=1, width do monitor.setCursorPos(i, yPos) monitor.write("-") end end -- function drawLine(yPos,monitorIndex) -- Display a solid bar of specified color local function drawBar(startXPos, startYPos, endXPos, endYPos, color, monitorIndex) local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in drawBar() is NOT a valid monitor.") return -- Invalid monitorIndex end -- PaintUtils only outputs to term., not monitor. -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/ term.redirect(monitor) paintutils.drawLine(startXPos, startYPos, endXPos, endYPos, color) monitor.setBackgroundColor(colors.black) -- PaintUtils doesn't restore the color termRestore() end -- function drawBar(startXPos, startYPos,endXPos,endYPos,color,monitorIndex) -- Display single pixel color local function drawPixel(xPos, yPos, color, monitorIndex) local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in drawPixel() is NOT a valid monitor.") return -- Invalid monitorIndex end -- PaintUtils only outputs to term., not monitor. -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/ term.redirect(monitor) paintutils.drawPixel(xPos, yPos, color) monitor.setBackgroundColor(colors.black) -- PaintUtils doesn't restore the color termRestore() end -- function drawPixel(xPos, yPos, color, monitorIndex) local function saveMonitorAssignments() local assignments = {} for monitor, data in pairs(monitorAssignments) do local name = nil if (data.type == "Reactor") then name = data.reactorName elseif (data.type == "Turbine") then name = data.turbineName else name = data.type end assignments[monitor] = name end config.save(monitorOptionFileName, {Monitors = assignments}) end UI = { monitorIndex = 1, reactorIndex = 1, turbineIndex = 1 } UI.handlePossibleClick = function(self) local monitorData = monitorAssignments[sideClick] if monitorData == nil then printLog("UI.handlePossibleClick(): "..sideClick.." is unassigned, can't handle click", WARN) return end self.monitorIndex = monitorData.index local width, height = monitorList[self.monitorIndex].getSize() -- All the last line are belong to us if (yClick == height) then if (monitorData.type == "Reactor") then if (xClick == 1) then self:selectPrevReactor() elseif (xClick == width) then self:selectNextReactor() elseif (3 <= xClick and xClick <= width - 2) then if (#turbineList > 0) then self:selectTurbine() else self:selectStatus() end end elseif (monitorData.type == "Turbine") then if (xClick == 1) then self:selectPrevTurbine() elseif (xClick == width) then self:selectNextTurbine() elseif (3 <= xClick and xClick <= width - 2) then self:selectStatus() end elseif (monitorData.type == "Status") then if (xClick == 1) then if (#turbineList > 0) then self.turbineIndex = #turbineList self:selectTurbine() else self.reactorIndex = 1 self:selectReactor() end elseif (xClick == width) then self.reactorIndex = 1 self:selectReactor() elseif (3 <= xClick and xClick <= width - 2) then self:selectReactor() end else self:selectStatus() end -- Yes, that means we're skipping Debug. I figure everyone who wants that is -- bound to use the console key commands anyway, and that way we don't have -- it interfere with regular use. sideClick, xClick, yClick = 0, 0, 0 else if (monitorData.type == "Turbine") then self:handleTurbineMonitorClick(monitorData.turbineIndex, monitorData.index) elseif (monitorData.type == "Reactor") then self:handleReactorMonitorClick(monitorData.reactorIndex, monitorData.index) end end end -- UI.handlePossibleClick() UI.logChange = function(self, messageText) printLog("UI: "..messageText) termRestore() write(messageText.."\n") end UI.selectNextMonitor = function(self) self.monitorIndex = self.monitorIndex + 1 if self.monitorIndex > #monitorList then self.monitorIndex = 1 end local messageText = "Selected monitor "..monitorNames[self.monitorIndex] self:logChange(messageText) end -- UI.selectNextMonitor() UI.selectReactor = function(self) monitorAssignments[monitorNames[self.monitorIndex]] = {type="Reactor", index=self.monitorIndex, reactorName=reactorNames[self.reactorIndex], reactorIndex=self.reactorIndex} saveMonitorAssignments() local messageText = "Selected reactor "..reactorNames[self.reactorIndex].." for display on "..monitorNames[self.monitorIndex] self:logChange(messageText) end -- UI.selectReactor() UI.selectPrevReactor = function(self) if self.reactorIndex <= 1 then self.reactorIndex = #reactorList self:selectStatus() else self.reactorIndex = self.reactorIndex - 1 self:selectReactor() end end -- UI.selectPrevReactor() UI.selectNextReactor = function(self) if self.reactorIndex >= #reactorList then self.reactorIndex = 1 if #turbineList > 0 then self.turbineIndex = 1 self:selectTurbine() else self:selectStatus() end else self.reactorIndex = self.reactorIndex + 1 self:selectReactor() end end -- UI.selectNextReactor() UI.selectTurbine = function(self) monitorAssignments[monitorNames[self.monitorIndex]] = {type="Turbine", index=self.monitorIndex, turbineName=turbineNames[self.turbineIndex], turbineIndex=self.turbineIndex} saveMonitorAssignments() local messageText = "Selected turbine "..turbineNames[self.turbineIndex].." for display on "..monitorNames[self.monitorIndex] self:logChange(messageText) end -- UI.selectTurbine() UI.selectPrevTurbine = function(self) if self.turbineIndex <= 1 then self.turbineIndex = #turbineList self.reactorIndex = #reactorList self:selectReactor() else self.turbineIndex = self.turbineIndex - 1 self:selectTurbine() end end -- UI.selectPrevTurbine() UI.selectNextTurbine = function(self) if self.turbineIndex >= #turbineList then self.turbineIndex = 1 self:selectStatus() else self.turbineIndex = self.turbineIndex + 1 self:selectTurbine() end end -- UI.selectNextTurbine() UI.selectStatus = function(self) monitorAssignments[monitorNames[self.monitorIndex]] = {type="Status", index=self.monitorIndex} saveMonitorAssignments() local messageText = "Selected status summary for display on "..monitorNames[self.monitorIndex] self:logChange(messageText) end -- UI.selectStatus() UI.selectDebug = function(self) monitorAssignments[monitorNames[self.monitorIndex]] = {type="Debug", index=self.monitorIndex} saveMonitorAssignments() monitorList[self.monitorIndex].clear() local messageText = "Selected debug output for display on "..monitorNames[self.monitorIndex] self:logChange(messageText) end -- UI.selectDebug() -- Allow controlling Reactor Control Rod Level from GUI UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex) -- Decrease rod button: 23X, 4Y -- Increase rod button: 28X, 4Y -- Grab current monitor local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.") return -- Invalid monitorIndex end -- Grab current reactor local reactor = nil reactor = reactorList[reactorIndex] if not reactor then printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.") return -- Invalid reactorIndex else printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.") if reactor.mbIsConnected() then printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.") else printLog("reactor["..reactorIndex.."] in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.") return -- Disconnected reactor end -- if reactor.mbIsConnected() then end -- if not reactor then local reactorStatus = _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"] local width, height = monitor.getSize() if xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1) and (sideClick == monitorNames[monitorIndex]) then if yClick == 1 then reactor.setActive(not reactor.getActive()) -- Toggle reactor status _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = reactor.getActive() config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it -- If someone offlines the reactor (offline after a status click was detected), then disable autoStart if not reactor.getActive() then _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false end end -- if yClick == 1 then end -- if (xClick >= (width - string.len(reactorStatus) - 1) and xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then -- Allow disabling rod level auto-adjust and only manual rod level control if ((xClick > 20 and xClick < 27 and yClick == 9)) and (sideClick == monitorNames[monitorIndex]) then _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it end -- if (xClick > 23) and (xClick < 28) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then local targetTemp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] local newTargetTemp = targetTemp -- temporary: local targetTempAdjustAmount = 50 if (xClick == 21) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then printLog("Decreasing Target Temperature in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") --Decrease target temp by amount newTargetTemp = targetTemp - targetTempAdjustAmount if newTargetTemp < 0 then newTargetTemp = 0 end sideClick, xClick, yClick = 0, 0, 0 printLog("Setting reactor["..reactorIndex.."] Target Temperature to "..newTargetTemp.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = newTargetTemp -- Save updated target temp config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) targetTemp = newTargetTemp elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then printLog("Increasing Target Temperature in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") --Increase Target Temperature by amount newTargetTemp = targetTemp + targetTempAdjustAmount sideClick, xClick, yClick = 0, 0, 0 printLog("Setting reactor["..reactorIndex.."] Target Temperature to "..newTargetTemp.."% in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = newTargetTemp -- Save updated target temp config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) targetTemp = newTargetTemp else printLog("No change to Target Temp requested by "..progName.." GUI in handleReactorMonitorClick(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then end -- UI.handleReactorMonitorClick = function(self, reactorIndex, monitorIndex) -- Allow controlling Turbine Flow Rate from GUI UI.handleTurbineMonitorClick = function(self, turbineIndex, monitorIndex) -- Decrease flow rate button: 22X, 4Y -- Increase flow rate button: 28X, 4Y -- Grab current monitor local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.") return -- Invalid monitorIndex end -- Grab current turbine local turbine = nil turbine = turbineList[turbineIndex] if not turbine then printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.") return -- Invalid turbineIndex else printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.") if turbine.mbIsConnected() then printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.") else printLog("turbine["..turbineIndex.."] in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.") return -- Disconnected turbine end -- if turbine.mbIsConnected() then end local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"]) local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"]) local turbineStatus = _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"] local width, height = monitor.getSize() if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then if yClick == 1 then turbine.setActive(not turbine.getActive()) -- Toggle turbine status _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = turbine.getActive() config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) end -- if yClick == 1 then end -- if (xClick >= (width - string.len(turbineStatus) - 1)) and (xClick <= (width-1)) and (sideClick == monitorNames[monitorIndex]) then -- Allow disabling/enabling flow rate auto-adjust if (xClick > 23 and xClick < 28 and yClick == 4) and (sideClick == monitorNames[monitorIndex]) then _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it elseif (xClick > 20 and xClick < 27 and yClick == 10) and (sideClick == monitorNames[monitorIndex]) then if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "true")) then _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false else _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = true end sideClick, xClick, yClick = 0, 0, 0 -- Reset click after we register it config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) end if (xClick == 22) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then printLog("Decrease to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").") --Decrease rod level by amount local newTurbineFlowRate = turbineFlowRate - flowRateAdjustAmount if newTurbineFlowRate < 0 then newTurbineFlowRate = 0 end sideClick, xClick, yClick = 0, 0, 0 -- Check bounds [0,2000] if newTurbineFlowRate > 2000 then newTurbineFlowRate = 2000 elseif newTurbineFlowRate < 0 then newTurbineFlowRate = 0 end turbine.setFluidFlowRateMax(newTurbineFlowRate) _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newTurbineFlowRate -- Save updated Turbine Flow Rate turbineFlowRate = newTurbineFlowRate config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) elseif (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then printLog("Increase to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").") --Increase rod level by amount local newTurbineFlowRate = turbineFlowRate + flowRateAdjustAmount if newTurbineFlowRate > 2000 then newTurbineFlowRate = 2000 end sideClick, xClick, yClick = 0, 0, 0 -- Check bounds [0,2000] if newTurbineFlowRate > 2000 then newTurbineFlowRate = 2000 elseif newTurbineFlowRate < 0 then newTurbineFlowRate = 0 end turbine.setFluidFlowRateMax(newTurbineFlowRate) -- Save updated Turbine Flow Rate turbineFlowRate = math.ceil(newTurbineFlowRate) _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = turbineFlowRate config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) else printLog("No change to Flow Rate requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").") end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then if (xClick == 22) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then printLog("Decrease to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").") local rpmRateAdjustment = 909 local newTurbineBaseSpeed = turbineBaseSpeed - rpmRateAdjustment if newTurbineBaseSpeed < 908 then newTurbineBaseSpeed = 908 end sideClick, xClick, yClick = 0, 0, 0 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) elseif (xClick == 29) and (yClick == 6) and (sideClick == monitorNames[monitorIndex]) then printLog("Increase to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").") local rpmRateAdjustment = 909 local newTurbineBaseSpeed = turbineBaseSpeed + rpmRateAdjustment if newTurbineBaseSpeed > 2726 then newTurbineBaseSpeed = 2726 end sideClick, xClick, yClick = 0, 0, 0 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = newTurbineBaseSpeed config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) else printLog("No change to Turbine RPM requested by "..progName.." GUI in handleTurbineMonitorClick(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").") end -- if (xClick == 29) and (yClick == 4) and (sideClick == monitorNames[monitorIndex]) then end -- function handleTurbineMonitorClick(turbineIndex, monitorIndex) -- End helper functions -- Then initialize the monitors local function findMonitors() -- Empty out old list of monitors monitorList = {} printLog("Finding monitors...") monitorList, monitorNames = getDevices("monitor") if #monitorList == 0 then printLog("No monitors found, continuing headless") else for monitorIndex = 1, #monitorList do local monitor, monitorX, monitorY = nil, nil, nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitorList["..monitorIndex.."] in findMonitors() is NOT a valid monitor.") table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list if monitorIndex ~= #monitorList then -- If we're not at the end, clean up monitorIndex = monitorIndex - 1 -- We just removed an element end -- if monitorIndex == #monitorList then break -- Invalid monitorIndex else -- valid monitor monitor.setTextScale(2) -- Reset scale, see Issue #68 monitor.setTextScale(1.0) -- Make sure scale is correct monitorX, monitorY = monitor.getSize() if (monitorX == nil) or (monitorY == nil) then -- somehow a valid monitor, but non-existent sizes? Maybe fixes Issue #3 printLog("monitorList["..monitorIndex.."] in findMonitors() is NOT a valid sized monitor.") table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list if monitorIndex ~= #monitorList then -- If we're not at the end, clean up monitorIndex = monitorIndex - 1 -- We just removed an element end -- if monitorIndex == #monitorList then break -- Invalid monitorIndex -- Check for minimum size to allow for monitor.setTextScale(0.5) to work for 3x2 debugging monitor, changes getSize() elseif monitorX < 29 or monitorY < 12 then term.redirect(monitor) monitor.clear() printLog("Removing monitor "..monitorIndex.." for being too small.") monitor.setCursorPos(1,2) write("Monitor is the wrong size!\n") write("Needs to be at least 3x2.") termRestore() table.remove(monitorList, monitorIndex) -- Remove invalid monitor from list if monitorIndex == #monitorList then -- If we're at the end already, break from loop break else monitorIndex = monitorIndex - 1 -- We just removed an element end -- if monitorIndex == #monitorList then end -- if monitorX < 29 or monitorY < 12 then end -- if not monitor then printLog("Monitor["..monitorIndex.."] named \""..monitorNames[monitorIndex].."\" is a valid monitor of size x:"..monitorX.." by y:"..monitorY..".") end -- for monitorIndex = 1, #monitorList do end -- if #monitorList == 0 then printLog("Found "..#monitorList.." monitor(s) in findMonitors().") end -- local function findMonitors() -- Initialize all Big Reactors - Reactors local function findReactors() -- Empty out old list of reactors local newReactorList = {} printLog("Finding reactors...") newReactorList, reactorNames = getDevices("BigReactors-Reactor") if #newReactorList == 0 then printLog("No reactors found!") error("Can't find any reactors!") else -- Placeholder for reactorIndex = 1, #newReactorList do local reactor = nil reactor = newReactorList[reactorIndex] if not reactor then printLog("reactorList["..reactorIndex.."] in findReactors() is NOT a valid Big Reactor.") table.remove(newReactorList, reactorIndex) -- Remove invalid reactor from list if reactorIndex ~= #newReactorList then -- If we're not at the end, clean up reactorIndex = reactorIndex - 1 -- We just removed an element end -- reactorIndex ~= #newReactorList then return -- Invalid reactorIndex else printLog("reactor["..reactorIndex.."] in findReactors() is a valid Big Reactor.") --initialize the default table _G[reactorNames[reactorIndex]] = {} _G[reactorNames[reactorIndex]]["ReactorOptions"] = {} _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = 80 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = 0 _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"] = 0 _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = reactor.isActivelyCooled() _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = false _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = controlRodAdjustAmount _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"] = reactorNames[reactorIndex] _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] = 0 _G[reactorNames[reactorIndex]]["ReactorOptions"]["proportionalGain"] = 0.05 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralGain"] = 0.00 _G[reactorNames[reactorIndex]]["ReactorOptions"]["derivativeGain"] = 0.05 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"] = 200 _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"] = -200 _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] = 200 _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = false if reactor.mbIsConnected() then printLog("reactor["..reactorIndex.."] in findReactors() is connected.") else printLog("reactor["..reactorIndex.."] in findReactors() is NOT connected.") return -- Disconnected reactor end end --failsafe local tempTable = _G[reactorNames[reactorIndex]] --check to make sure we get a valid config if (config.load(reactorNames[reactorIndex]..".options")) ~= nil then tempTable = config.load(reactorNames[reactorIndex]..".options") else --if we don't have a valid config from disk, make a valid config config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) end --load values from tempTable, checking for nil values along the way for k, v in pairs(_G[reactorNames[reactorIndex]]["ReactorOptions"]) do if tempTable["ReactorOptions"][k] ~= nil then _G[reactorNames[reactorIndex]]["ReactorOptions"][k] = tempTable["ReactorOptions"][k] end end --stricter typing, let's set these puppies up with the right type of value. _G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["baseControlRodLevel"]) _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"]) if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"]) == "true") then _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = true else _G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] = false end if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"]) == "true") then _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = true else _G[reactorNames[reactorIndex]]["ReactorOptions"]["activeCooled"] = false end if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) == "true") then printLog("Setting Rod Override for "..reactorNames[reactorIndex].." to true because value was "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL") _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = true else printLog("Setting Rod Override for "..reactorNames[reactorIndex].." to false because value was "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL") _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] = false end _G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"]["controlRodAdjustAmount"]) if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"]) == "true") then _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = true else _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] = false end if (tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"]) == "true") then _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = true else _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] = false end local number_configs = {"integratedError", "proportionalGain", "integralGain", "derivativeGain", "integralMax", "integralMin", "reactorTargetTemp"} for k, v in pairs(number_configs) do _G[reactorNames[reactorIndex]]["ReactorOptions"][v] = tonumber(_G[reactorNames[reactorIndex]]["ReactorOptions"][v]) end --save one more time, in case we didn't have a complete config file before config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) end -- for reactorIndex = 1, #newReactorList do end -- if #newReactorList == 0 then -- Overwrite old reactor list with the now updated list reactorList = newReactorList printLog("Found "..#reactorList.." reactor(s) in findReactors().") end -- function findReactors() -- Initialize all Big Reactors - Turbines local function findTurbines() -- Empty out old list of turbines local newTurbineList = {} printLog("Finding turbines...") newTurbineList, turbineNames = getDevices("BigReactors-Turbine") if #newTurbineList == 0 then printLog("No turbines found") -- Not an error else for turbineIndex = 1, #newTurbineList do local turbine = nil turbine = newTurbineList[turbineIndex] if not turbine then printLog("turbineList["..turbineIndex.."] in findTurbines() is NOT a valid Big Reactors Turbine.") table.remove(newTurbineList, turbineIndex) -- Remove invalid turbine from list if turbineIndex ~= #newTurbineList then -- If we're not at the end, clean up turbineIndex = turbineIndex - 1 -- We just removed an element end -- turbineIndex ~= #newTurbineList then return -- Invalid turbineIndex else _G[turbineNames[turbineIndex]] = {} _G[turbineNames[turbineIndex]]["TurbineOptions"] = {} _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = 0 _G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"] = 1817 _G[turbineNames[turbineIndex]]["TurbineOptions"]["autoStart"] = true _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"] = 2000 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = 2000 _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"] = 2000 _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] = false _G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"] = turbineNames[turbineIndex] _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] = 0 _G[turbineNames[turbineIndex]]["TurbineOptions"]["proportionalGain"] = 10 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralGain"] = 0.00 _G[turbineNames[turbineIndex]]["TurbineOptions"]["derivativeGain"] = 5 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"] = 200 _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"] = -200 printLog("turbineList["..turbineIndex.."] in findTurbines() is a valid Big Reactors Turbine.") if turbine.mbIsConnected() then printLog("turbine["..turbineIndex.."] in findTurbines() is connected.") else printLog("turbine["..turbineIndex.."] in findTurbines() is NOT connected.") return -- Disconnected turbine end end --failsafe local tempTable = _G[turbineNames[turbineIndex]] --check to make sure we get a valid config if (config.load(turbineNames[turbineIndex]..".options")) ~= nil then tempTable = config.load(turbineNames[turbineIndex]..".options") else --if we don't have a valid config from disk, make a valid config config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) end for k, v in pairs(_G[turbineNames[turbineIndex]]["TurbineOptions"]) do if tempTable["TurbineOptions"][k] ~= nil then _G[turbineNames[turbineIndex]]["TurbineOptions"][k] = tempTable["TurbineOptions"][k] end end local number_configs = {"integratedError", "proportionalGain", "integralGain", "derivativeGain", "integralMax", "integralMin"} for k, v in pairs(number_configs) do _G[turbineNames[turbineIndex]]["TurbineOptions"][v] = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"][v]) end --save once more just to make sure we got it config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) end -- for turbineIndex = 1, #newTurbineList do -- Overwrite old turbine list with the now updated list turbineList = newTurbineList end -- if #newTurbineList == 0 then printLog("Found "..#turbineList.." turbine(s) in findTurbines().") end -- function findTurbines() -- Assign status, reactors, turbines and debug output to the monitors that shall display them -- Depends on the [monitor,reactor,turbine]Lists being populated already local function assignMonitors() local monitors = {} monitorAssignments = {} printLog("Assigning monitors...") local m = config.load(monitorOptionFileName) if (m ~= nil) then -- first, merge the detected and the configured monitor lists -- this is to ensure we pick up new additions to the network for monitorIndex, monitorName in ipairs(monitorNames) do monitors[monitorName] = m.Monitors[monitorName] or "" end -- then, go through all of it again to build our runtime data structure for monitorName, assignedName in pairs(monitors) do printLog("Looking for monitor and device named "..monitorName.." and "..assignedName) for monitorIndex = 1, #monitorNames do printLog("if "..monitorName.." == "..monitorNames[monitorIndex].." then", DEBUG) if monitorName == monitorNames[monitorIndex] then printLog("Found "..monitorName.." at index "..monitorIndex, DEBUG) if assignedName == "Status" then monitorAssignments[monitorName] = {type="Status", index=monitorIndex} elseif assignedName == "Debug" then monitorAssignments[monitorName] = {type="Debug", index=monitorIndex} else local maxListLen = math.max(#reactorNames, #turbineNames) for i = 1, maxListLen do if assignedName == reactorNames[i] then monitorAssignments[monitorName] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[i], reactorIndex=i} break elseif assignedName == turbineNames[i] then monitorAssignments[monitorName] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[i], turbineIndex=i} break elseif i == maxListLen then printLog("assignMonitors(): Monitor "..monitorName.." was configured to display nonexistant device "..assignedName..". Setting inactive.", WARN) monitorAssignments[monitorName] = {type="Inactive", index=monitorIndex} end end end break elseif monitorIndex == #monitorNames then printLog("assignMonitors(): Monitor "..monitorName.." not found. It was configured to display device "..assignedName..". Discarding.", WARN) end end end else printLog("No valid monitor configuration found, generating...") --print(tostring(monitorNames)) -- create assignments that reflect the setup before 0.3.17 local monitorIndex = 1 monitorAssignments[monitorNames[1]] = {type="Status", index=1} monitorIndex = monitorIndex + 1 for reactorIndex = 1, #reactorList do if monitorIndex > #monitorList then break end monitorAssignments[monitorNames[monitorIndex]] = {type="Reactor", index=monitorIndex, reactorName=reactorNames[reactorIndex], reactorIndex=reactorIndex} printLog(monitorNames[monitorIndex].." -> "..reactorNames[reactorIndex]) monitorIndex = monitorIndex + 1 end for turbineIndex = 1, #turbineList do if monitorIndex > #monitorList then break end monitorAssignments[monitorNames[monitorIndex]] = {type="Turbine", index=monitorIndex, turbineName=turbineNames[turbineIndex], turbineIndex=turbineIndex} printLog(monitorNames[monitorIndex].." -> "..turbineNames[turbineIndex]) monitorIndex = monitorIndex + 1 end if monitorIndex <= #monitorList then monitorAssignments[monitorNames[#monitorList]] = {type="Debug", index=#monitorList} end end tprint(monitorAssignments) saveMonitorAssignments() end -- function assignMonitors() local eventHandler -- Replacement for sleep, which passes on events instead of dropping themo -- Straight from http://computercraft.info/wiki/Os.sleep local function wait(time) local timer = os.startTimer(time) while true do local event = {os.pullEvent()} if (event[1] == "timer" and event[2] == timer) then break else eventHandler(event[1], event[2], event[3], event[4]) end end end -- Return current energy buffer in a specific reactor by % local function getReactorStoredEnergyBufferPercent(reactor) printLog("Called as getReactorStoredEnergyBufferPercent(reactor).") if not reactor then printLog("getReactorStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Reactor.") return -- Invalid reactorIndex else printLog("getReactorStoredEnergyBufferPercent() did receive a valid Big Reactor Reactor.") end return round(100*reactor.getEnergyStored()/reactor.getEnergyCapacity(), 1) -- (buffer/capacity RF)*100% end -- function getReactorStoredEnergyBufferPercent(reactor) -- Return current energy buffer in a specific Turbine by % local function getTurbineStoredEnergyBufferPercent(turbine) printLog("Called as getTurbineStoredEnergyBufferPercent(turbine)") if not turbine then printLog("getTurbineStoredEnergyBufferPercent() did NOT receive a valid Big Reactor Turbine.") return -- Invalid reactorIndex else printLog("getTurbineStoredEnergyBufferPercent() did receive a valid Big Reactor Turbine.") end return round(100*turbine.getEnergyStored()/turbine.getEnergyCapacity(), 1) -- (buffer/capacity RF)*100% end -- function getTurbineStoredEnergyBufferPercent(turbine) -- Modify reactor control rod levels to keep temperature with defined parameters local function temperatureControl(reactorIndex) printLog("Called as temperatureControl(reactorIndex="..reactorIndex..")") local reactor = nil reactor = reactorList[reactorIndex] if not reactor then printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT a valid Big Reactor.") return -- Invalid reactorIndex else printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is a valid Big Reactor.") if reactor.mbIsConnected() then printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is connected.") else printLog("reactor["..reactorIndex.."] in temperatureControl(reactorIndex="..reactorIndex..") is NOT connected.") return -- Disconnected reactor end -- if reactor.mbIsConnected() then end local reactorNum = reactorIndex local rodPercentage = math.ceil(reactor.getControlRodLevel(0)) local reactorTemp = math.ceil(reactor.getFuelTemperature()) --bypass if the reactor itself is set to not be auto-controlled if ((not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]) or (_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] == "false")) then -- No point modifying control rod levels for temperature if the reactor is offline if reactor.getActive() then -- Actively cooled reactors should range between 0^C-300^C -- Actually, active-cooled reactors should range between 300 and 420C (Mechaet) -- Accordingly I changed the below lines -- if reactor.isActivelyCooled() and not knowlinglyOverride then -- -- below was 0 -- localMinReactorTemp = 300 -- -- below was 300 -- localMaxReactorTemp = 420 -- end -- and I (matthew) got rid of them because of the new control algorithm local lastTempPoll = _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] local target local Error local derivedError if _G[reactorNames[reactorIndex]]["ReactorOptions"]["targetForSteam"] then printLog("Targeting for steam", WARN) target = steamRequested Error = steamDelivered - target derivedError = reactor.getHotFluidProducedLastTick() - _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"] else target = _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorTargetTemp"] -- target is the setpoint Error = reactorTemp - target derivedError = reactorTemp - lastTempPoll end local integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] + Error if integratedError > _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"] then integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMax"] end if integratedError < _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"] then integratedError = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralMin"] end _G[reactorNames[reactorIndex]]["ReactorOptions"]["integratedError"] = integratedError -- Coefficients (gains) local Kp = _G[reactorNames[reactorIndex]]["ReactorOptions"]["proportionalGain"] local Ki = _G[reactorNames[reactorIndex]]["ReactorOptions"]["integralGain"] local Kd = _G[reactorNames[reactorIndex]]["ReactorOptions"]["derivativeGain"] local adjustAmount = round(Kp * Error + Ki * integratedError + Kd * derivedError, 0) -- for the control rods local coefficientsString = "Kp:" .. tostring(Kp) .. " Ki:" .. tostring(Ki) .. " Kd:" .. tostring(Kd) local errorsString = "Ep:" .. tostring(Error) .. " Ei:" .. tostring(integratedError) .. " Ed:" .. tostring(derivedError) .. " AA:" .. tostring(adjustAmount) printLog(coefficientsString, INFO) printLog(errorsString, INFO) local setLevel = rodPercentage + adjustAmount if setLevel > 100 then setLevel = 100 end if setLevel < 0 then setLevel = 0 end -- Prevent Runaway printLog("running temperatureControl Reactor Temp: "..reactorTemp.." Target Temp: "..target, WARN) if reactorTemp > 2 * target then printLog("preventing runaway", WARN) setLevel = 100 end reactor.setAllControlRodLevels(setLevel) --always set this number _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastTempPoll"] = reactorTemp _G[reactorNames[reactorIndex]]["ReactorOptions"]["lastSteamPoll"] = reactor.getHotFluidProducedLastTick() config.save(reactorNames[reactorIndex]..".options", _G[reactorNames[reactorIndex]]) end -- if reactor.getActive() then else printLog("Bypassed temperature control due to rodOverride being "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL") end -- if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then end -- function temperatureControl(reactorIndex) -- Load saved reactor parameters if ReactorOptions file exists local function loadReactorOptions() local reactorOptions = fs.open("ReactorOptions", "r") -- See http://computercraft.info/wiki/Fs.open if reactorOptions then -- The following values were added by Lolmer minStoredEnergyPercent = tonumber(reactorOptions.readLine()) maxStoredEnergyPercent = tonumber(reactorOptions.readLine()) --added by Mechaet -- If we succeeded in reading a string, convert it to a number reactorOptions.close() end -- if reactorOptions then -- Set default values if we failed to read any of the above if minStoredEnergyPercent == nil then minStoredEnergyPercent = 15 end if maxStoredEnergyPercent == nil then maxStoredEnergyPercent = 85 end end -- function loadReactorOptions() -- Save our reactor parameters local function saveReactorOptions() local reactorOptions = fs.open("ReactorOptions", "w") -- See http://computercraft.info/wiki/Fs.open -- If we can save the files, save them if reactorOptions then local reactorIndex = 1 -- The following values were added by Lolmer reactorOptions.writeLine(minStoredEnergyPercent) reactorOptions.writeLine(maxStoredEnergyPercent) reactorOptions.close() else printLog("Failed to open file ReactorOptions for writing!") end -- if reactorOptions then end -- function saveReactorOptions() local function displayReactorBars(barParams) -- Default to first reactor and first monitor setmetatable(barParams,{__index={reactorIndex=1, monitorIndex=1}}) local reactorIndex, monitorIndex = barParams[1] or barParams.reactorIndex, barParams[2] or barParams.monitorIndex printLog("Called as displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") -- Grab current monitor local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.") return -- Invalid monitorIndex end -- Grab current reactor local reactor = nil reactor = reactorList[reactorIndex] local reactorInfo = _G[reactorNames[reactorIndex]] if not reactor then printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.") return -- Invalid reactorIndex else printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.") if reactor.mbIsConnected() then printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.") else printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.") return -- Disconnected reactor end -- if reactor.mbIsConnected() then end -- if not reactor then -- Draw border lines local width, height = monitor.getSize() printLog("Size of monitor is "..width.."w x"..height.."h in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")") for i=3, 5 do monitor.setCursorPos(20, i) monitor.write("|") end drawLine(6, monitorIndex) monitor.setCursorPos(1, height) monitor.write("< ") monitor.setCursorPos(width-1, height) monitor.write(" >") -- Draw some text local fuelString = "Fuel: " local tempString = "Temp: " local energyBufferString = "" if reactor.isActivelyCooled() then energyBufferString = "Steam: " else energyBufferString = "Energy: " end local padding = math.max(string.len(fuelString), string.len(tempString), string.len(energyBufferString)) local fuelPercentage = round(reactor.getFuelAmount()/reactor.getFuelAmountMax()*100,1) print{fuelString,2,3,monitorIndex} print{fuelPercentage.." %",padding+2,3,monitorIndex} local reactorTemp = math.ceil(reactor.getFuelTemperature()) print{tempString,2,5,monitorIndex} print{reactorTemp.." C",padding+2,5,monitorIndex} local rodPercentage = math.ceil(reactor.getControlRodLevel(0)) printLog("Current Rod Percentage for reactor["..reactorIndex.."] is "..rodPercentage.."% in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") print{"Target °C",21,3,monitorIndex} print{"< >",21,4,monitorIndex} --print{stringTrim(rodPercentage),23,4,monitorIndex} local target = tostring(reactorInfo["ReactorOptions"]["reactorTargetTemp"]) print{stringTrim(target),23,4,monitorIndex} -- Extreme Reactors is now using two separate functions for RF/t (passively cooled) and mB/t (actively cooled) local energyBuffer if reactor.isActivelyCooled() then energyBuffer = reactor.getHotFluidProducedLastTick() else energyBuffer = reactor.getEnergyProducedLastTick() end if reactor.isActivelyCooled() then printLog("reactor["..reactorIndex.."] produced "..energyBuffer.." mB last tick in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") else printLog("reactor["..reactorIndex.."] produced "..energyBuffer.." RF last tick in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") end print{energyBufferString,2,4,monitorIndex} -- Actively cooled reactors do not produce energy, only hot fluid mB/t to be used in a turbine -- still uses getEnergyProducedLastTick for mB/t of hot fluid generated if not reactor.isActivelyCooled() then printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT an actively cooled reactor.") -- Draw stored energy buffer bar drawBar(2,8,28,8,colors.gray,monitorIndex) local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor) if curStoredEnergyPercent > 4 then drawBar(2, 8, math.floor(26*curStoredEnergyPercent/100)+2, 8, colors.yellow, monitorIndex) elseif curStoredEnergyPercent > 0 then drawPixel(2, 8, colors.yellow, monitorIndex) end -- if curStoredEnergyPercent > 4 then print{"Energy Buffer",2,7,monitorIndex} print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),7,monitorIndex} print{"%",28,7,monitorIndex} print{math.ceil(energyBuffer).." RF/t",padding+2,4,monitorIndex} else printLog("reactor["..reactorIndex.."] in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is an actively cooled reactor.") print{math.ceil(energyBuffer).." mB/t",padding+2,4,monitorIndex} end -- if not reactor.isActivelyCooled() then -- Print rod override status local reactorRodOverrideStatus = "" print{"Rod Auto-adjust:",2,9,monitorIndex} if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL") reactorRodOverrideStatus = "Enabled" monitor.setTextColor(colors.green) else printLog("Reactor Rod Override status is: "..tostring(_G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"]).." EOL") reactorRodOverrideStatus = "Disabled" monitor.setTextColor(colors.red) end -- if not reactorRodOverride then printLog("reactorRodOverrideStatus is \""..reactorRodOverrideStatus.."\" in displayReactorBars(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..").") print{reactorRodOverrideStatus, width - string.len(reactorRodOverrideStatus) - 1, 9, monitorIndex} monitor.setTextColor(colors.white) print{"Reactivity: "..math.ceil(reactor.getFuelReactivity()).." %", 2, 10, monitorIndex} print{"Fuel: "..round(reactor.getFuelConsumedLastTick(),3).." mB/t", 2, 11, monitorIndex} print{"Waste: "..reactor.getWasteAmount().." mB", width-(string.len(reactor.getWasteAmount())+10), 11, monitorIndex} monitor.setTextColor(colors.blue) printCentered(_G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorName"],12,monitorIndex) monitor.setTextColor(colors.white) -- monitor switch controls monitor.setCursorPos(1, height) monitor.write("<") monitor.setCursorPos(width, height) monitor.write(">") end -- function displayReactorBars(barParams) local function reactorStatus(statusParams) -- Default to first reactor and first monitor setmetatable(statusParams,{__index={reactorIndex=1, monitorIndex=1}}) local reactorIndex, monitorIndex = statusParams[1] or statusParams.reactorIndex, statusParams[2] or statusParams.monitorIndex printLog("Called as reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..")") -- Grab current monitor local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.") return -- Invalid monitorIndex end -- Grab current reactor local reactor = nil reactor = reactorList[reactorIndex] if not reactor then printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Reactor.") return -- Invalid reactorIndex else printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is a valid Big Reactor.") end local width, height = monitor.getSize() local reactorStatus = "" if reactor.mbIsConnected() then printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is connected.") if reactor.getActive() then reactorStatus = "ONLINE" -- Set "ONLINE" to blue if the actively cooled reactor is both in cruise mode and online if _G[reactorNames[reactorIndex]]["ReactorOptions"]["reactorCruising"] and reactor.isActivelyCooled() then monitor.setTextColor(colors.blue) else monitor.setTextColor(colors.green) end -- if reactorCruising and reactor.isActivelyCooled() then else reactorStatus = "OFFLINE" monitor.setTextColor(colors.red) end -- if reactor.getActive() then else printLog("reactor["..reactorIndex.."] in reactorStatus(reactorIndex="..reactorIndex..",monitorIndex="..monitorIndex..") is NOT connected.") reactorStatus = "DISCONNECTED" monitor.setTextColor(colors.red) end -- if reactor.mbIsConnected() then _G[reactorNames[reactorIndex]]["ReactorOptions"]["Status"] = reactorStatus print{reactorStatus, width - string.len(reactorStatus) - 1, 1, monitorIndex} monitor.setTextColor(colors.white) end -- function reactorStatus(statusParams) -- Display all found reactors' status to selected monitor -- This is only called if multiple reactors and/or a reactor plus at least one turbine are found local function displayAllStatus(monitorIndex) local reactor, turbine = nil, nil local onlineReactor, onlineTurbine = 0, 0 local totalReactorRF, totalReactorSteam, totalTurbineRF = 0, 0, 0 local totalReactorFuelConsumed = 0 local totalCoolantStored, totalSteamStored, totalEnergy, totalMaxEnergyStored = 0, 0, 0, 0 -- Total turbine and reactor energy buffer and overall capacity local maxSteamStored, maxCoolantStored = 0, 0 if #reactorList > 0 then maxCoolantStored = maxCoolantStored + reactorList[1].getCoolantAmountMax()*#reactorList maxSteamStored = maxSteamStored + reactorList[1].getHotFluidAmountMax()*#reactorList end if #turbineList > 0 then maxCoolantStored = maxCoolantStored + turbineList[1].getFluidAmountMax()*#turbineList maxSteamStored = maxSteamStored + turbineList[1].getFluidAmountMax()*#turbineList end local monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in displayAllStatus() is NOT a valid monitor.") return -- Invalid monitorIndex end for reactorIndex = 1, #reactorList do reactor = reactorList[reactorIndex] if not reactor then printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT a valid Big Reactor.") break -- Invalid reactorIndex else printLog("reactor["..reactorIndex.."] in displayAllStatus() is a valid Big Reactor.") end -- if not reactor then if reactor.mbIsConnected() then printLog("reactor["..reactorIndex.."] in displayAllStatus() is connected.") if reactor.getActive() then onlineReactor = onlineReactor + 1 totalReactorFuelConsumed = totalReactorFuelConsumed + reactor.getFuelConsumedLastTick() end -- reactor.getActive() then -- Actively cooled reactors do not produce or store energy if not reactor.isActivelyCooled() then totalMaxEnergyStored = totalMaxEnergyStored + reactor.getEnergyCapacity() -- Reactors store 3.9M RF totalEnergy = totalEnergy + reactor.getEnergyStored() totalReactorRF = totalReactorRF + reactor.getEnergyProducedLastTick() else totalReactorSteam = totalReactorSteam + reactor.getHotFluidProducedLastTick() totalSteamStored = totalSteamStored + reactor.getHotFluidAmount() totalCoolantStored = totalCoolantStored + reactor.getCoolantAmount() end -- if not reactor.isActivelyCooled() then else printLog("reactor["..reactorIndex.."] in displayAllStatus() is NOT connected.") end -- if reactor.mbIsConnected() then end -- for reactorIndex = 1, #reactorList do for turbineIndex = 1, #turbineList do turbine = turbineList[turbineIndex] if not turbine then printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT a valid Turbine.") break -- Invalid turbineIndex else printLog("turbine["..turbineIndex.."] in displayAllStatus() is a valid Turbine.") end -- if not turbine then if turbine.mbIsConnected() then printLog("turbine["..turbineIndex.."] in displayAllStatus() is connected.") if turbine.getActive() then onlineTurbine = onlineTurbine + 1 end totalMaxEnergyStored = totalMaxEnergyStored + turbine.getEnergyCapacity() totalEnergy = totalEnergy + turbine.getEnergyStored() totalTurbineRF = totalTurbineRF + turbine.getEnergyProducedLastTick() totalSteamStored = totalSteamStored + turbine.getInputAmount() totalCoolantStored = totalCoolantStored + turbine.getOutputAmount() else printLog("turbine["..turbineIndex.."] in displayAllStatus() is NOT connected.") end -- if turbine.mbIsConnected() then end -- for turbineIndex = 1, #turbineList do print{"Reactors online/found: "..onlineReactor.."/"..#reactorList, 2, 3, monitorIndex} print{"Turbines online/found: "..onlineTurbine.."/"..#turbineList, 2, 4, monitorIndex} if totalReactorRF ~= 0 then monitor.setTextColor(colors.blue) printRight("Reactor", 9, monitorIndex) monitor.setTextColor(colors.white) printRight(math.ceil(totalReactorRF).." (RF/t)", 10, monitorIndex) end if #turbineList then -- Display liquids monitor.setTextColor(colors.blue) printLeft("Steam (mB)", 6, monitorIndex) monitor.setTextColor(colors.white) printLeft(math.ceil(totalSteamStored).."/"..maxSteamStored, 7, monitorIndex) printLeft(math.ceil(totalReactorSteam).." mB/t", 8, monitorIndex) monitor.setTextColor(colors.blue) printRight("Coolant (mB)", 6, monitorIndex) monitor.setTextColor(colors.white) printRight(math.ceil(totalCoolantStored).."/"..maxCoolantStored, 7, monitorIndex) monitor.setTextColor(colors.blue) printLeft("Turbine", 9, monitorIndex) monitor.setTextColor(colors.white) printLeft(math.ceil(totalTurbineRF).." RF/t", 10, monitorIndex) end -- if #turbineList then printCentered("Fuel: "..round(totalReactorFuelConsumed,3).." mB/t", 11, monitorIndex) printCentered("Buffer: "..formatReadableSIUnit(math.ceil(totalEnergy)).."/"..formatReadableSIUnit(totalMaxEnergyStored).." RF", 12, monitorIndex) -- monitor switch controls local width, height = monitor.getSize() monitor.setCursorPos(1, height) monitor.write("<") monitor.setCursorPos(width, height) monitor.write(">") end -- function displayAllStatus() -- Get turbine status local function displayTurbineBars(turbineIndex, monitorIndex) printLog("Called as displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").") -- Grab current monitor local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.") return -- Invalid monitorIndex end -- Grab current turbine local turbine = nil turbine = turbineList[turbineIndex] if not turbine then printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.") return -- Invalid turbineIndex else printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.") if turbine.mbIsConnected() then printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.") else printLog("turbine["..turbineIndex.."] in displayTurbineBars(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.") return -- Disconnected turbine end -- if turbine.mbIsConnected() then end -- if not turbine then --local variable to match the view on the monitor local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"]) -- Draw border lines local width, height = monitor.getSize() for i=3, 6 do monitor.setCursorPos(21, i) monitor.write("|") end drawLine(7,monitorIndex) monitor.setCursorPos(1, height) monitor.write("< ") monitor.setCursorPos(width-1, height) monitor.write(" >") local turbineFlowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"]) print{" mB/t",22,3,monitorIndex} print{"< >",22,4,monitorIndex} print{stringTrim(turbineFlowRate),24,4,monitorIndex} print{" RPM",22,5,monitorIndex} print{"< >",22,6,monitorIndex} print{stringTrim(tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"])),24,6,monitorIndex} local rotorSpeedString = "Speed: " local energyBufferString = "Energy: " local steamBufferString = "Steam: " local padding = math.max(string.len(rotorSpeedString), string.len(energyBufferString), string.len(steamBufferString)) local energyBuffer = turbine.getEnergyProducedLastTick() print{energyBufferString,1,4,monitorIndex} print{math.ceil(energyBuffer).." RF/t",padding+1,4,monitorIndex} local rotorSpeed = math.ceil(turbine.getRotorSpeed()) print{rotorSpeedString,1,5,monitorIndex} print{rotorSpeed.." RPM",padding+1,5,monitorIndex} local steamBuffer = turbine.getFluidFlowRate() print{steamBufferString,1,6,monitorIndex} print{steamBuffer.." mB/t",padding+1,6,monitorIndex} -- PaintUtils only outputs to term., not monitor. -- See http://www.computercraft.info/forums2/index.php?/topic/15540-paintutils-on-a-monitor/ -- Draw stored energy buffer bar drawBar(1,9,28,9,colors.gray,monitorIndex) local curStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine) if curStoredEnergyPercent > 4 then drawBar(1, 9, math.floor(26*curStoredEnergyPercent/100)+2, 9, colors.yellow,monitorIndex) elseif curStoredEnergyPercent > 0 then drawPixel(1, 9, colors.yellow, monitorIndex) end -- if curStoredEnergyPercent > 4 then print{"Energy Buffer",1,8,monitorIndex} print{curStoredEnergyPercent, width-(string.len(curStoredEnergyPercent)+2),8,monitorIndex} print{"%",28,8,monitorIndex} -- Print rod override status local turbineFlowRateOverrideStatus = "" print{"Flow Auto-adjust:",2,10,monitorIndex} if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then turbineFlowRateOverrideStatus = "Enabled" monitor.setTextColor(colors.green) else turbineFlowRateOverrideStatus = "Disabled" monitor.setTextColor(colors.red) end -- if not reactorRodOverride then print{turbineFlowRateOverrideStatus, width - string.len(turbineFlowRateOverrideStatus) - 1, 10, monitorIndex} monitor.setTextColor(colors.white) -- Print coil status local turbineCoilStatus = "" print{"Turbine coils:",2,11,monitorIndex} if ((_G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] == "true")) then turbineCoilStatus = "Engaged" monitor.setTextColor(colors.green) else turbineCoilStatus = "Disengaged" monitor.setTextColor(colors.red) end print{turbineCoilStatus, width - string.len(turbineCoilStatus) - 1, 11, monitorIndex} monitor.setTextColor(colors.white) monitor.setTextColor(colors.blue) printCentered(_G[turbineNames[turbineIndex]]["TurbineOptions"]["turbineName"],12,monitorIndex) monitor.setTextColor(colors.white) -- monitor switch controls monitor.setCursorPos(1, height) monitor.write("<") monitor.setCursorPos(width, height) monitor.write(">") -- Need equation to figure out rotor efficiency and display end -- function displayTurbineBars(statusParams) -- Display turbine status local function turbineStatus(turbineIndex, monitorIndex) printLog("Called as turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..").") -- Grab current monitor local monitor = nil monitor = monitorList[monitorIndex] if not monitor then printLog("monitor["..monitorIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid monitor.") return -- Invalid monitorIndex end -- Grab current turbine local turbine = nil turbine = turbineList[turbineIndex] if not turbine then printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT a valid Big Turbine.") return -- Invalid turbineIndex else printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is a valid Big Turbine.") end local width, height = monitor.getSize() local turbineStatus = "" if turbine.mbIsConnected() then printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is connected.") if turbine.getActive() then turbineStatus = "ONLINE" monitor.setTextColor(colors.green) else turbineStatus = "OFFLINE" monitor.setTextColor(colors.red) end -- if turbine.getActive() then _G[turbineNames[turbineIndex]]["TurbineOptions"]["Status"] = turbineStatus else printLog("turbine["..turbineIndex.."] in turbineStatus(turbineIndex="..turbineIndex..",monitorIndex="..monitorIndex..") is NOT connected.") turbineStatus = "DISCONNECTED" monitor.setTextColor(colors.red) end -- if turbine.mbIsConnected() then print{turbineStatus, width - string.len(turbineStatus) - 1, 1, monitorIndex} monitor.setTextColor(colors.white) end -- function function turbineStatus(turbineIndex, monitorIndex) -- Adjust Turbine flow rate to maintain 900 or 1,800 RPM, and disengage coils when buffer full local function flowRateControl(turbineIndex) if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then printLog("Called as flowRateControl(turbineIndex="..turbineIndex..").") -- Grab current turbine local turbine = nil turbine = turbineList[turbineIndex] -- assign for the duration of this run local lastTurbineSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"]) local turbineBaseSpeed = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["BaseSpeed"]) local coilsEngaged = _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] or _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] == "true" if not turbine then printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT a valid Big Turbine.") return -- Invalid turbineIndex else printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is a valid Big Turbine.") if turbine.mbIsConnected() then printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is connected.") else printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT connected.") end -- if turbine.mbIsConnected() then end -- if not turbine then -- No point modifying control rod levels for temperature if the turbine is offline if turbine.getActive() then printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is active.") local flowRate local flowRateUserMax = math.ceil(turbine.getFluidFlowRateMax()) local rotorSpeed = math.ceil(turbine.getRotorSpeed()) local newFlowRate -- Flips on and off the coils. Binary and stupid local currentStoredEnergyPercent = getTurbineStoredEnergyBufferPercent(turbine) if currentStoredEnergyPercent < 15 and turbineBaseSpeed - rotorSpeed <= 50 then coilsEngaged = true end if currentStoredEnergyPercent > 85 or turbineBaseSpeed - rotorSpeed > 50 then coilsEngaged = false end -- Uses two stored steam values. One for coils engaged, one for disengaged. if coilsEngaged then flowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"]) else flowRate = tonumber(_G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"]) end -- PID Controller local target = turbineBaseSpeed local Error = target - rotorSpeed local derivedError = lastTurbineSpeed - rotorSpeed local integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] + Error if integratedError > _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"] then integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMax"] end if integratedError > _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"] then integratedError = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralMin"] end _G[turbineNames[turbineIndex]]["TurbineOptions"]["integratedError"] = integratedError local Kp = _G[turbineNames[turbineIndex]]["TurbineOptions"]["proportionalGain"] local Ki = _G[turbineNames[turbineIndex]]["TurbineOptions"]["integralGain"] local Kd = _G[turbineNames[turbineIndex]]["TurbineOptions"]["derivativeGain"] local adjustAmount = round(Kp * Error + Ki * integratedError + Kd * derivedError, 0) -- for the turbine flow rate newFlowRate = flowRate + adjustAmount -- Failsafe to prevent explosions if rotorSpeed >= 2000 then coilsEngaged = true newFlowRate = 0 end --boundary check if newFlowRate > 2000 then newFlowRate = 2000 elseif newFlowRate < 0 then newFlowRate = 0 end -- if newFlowRate > 2000 then --no sense running an adjustment if it's not necessary printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is being commanded to "..newFlowRate.." mB/t flow") newFlowRate = round(newFlowRate, 0) turbine.setFluidFlowRateMax(newFlowRate) _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlow"] = newFlowRate -- For display purposes -- For the PID if coilsEngaged then _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsEngaged"] = newFlowRate else _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastFlowCoilsDisengaged"] = newFlowRate end config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) turbine.setInductorEngaged(coilsEngaged) --always set this _G[turbineNames[turbineIndex]]["TurbineOptions"]["CoilsEngaged"] = coilsEngaged _G[turbineNames[turbineIndex]]["TurbineOptions"]["LastSpeed"] = rotorSpeed config.save(turbineNames[turbineIndex]..".options", _G[turbineNames[turbineIndex]]) else printLog("turbine["..turbineIndex.."] in flowRateControl(turbineIndex="..turbineIndex..") is NOT active.") end -- if turbine.getActive() then else printLog("turbine["..turbineIndex.."] has flow override set to "..tostring(_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"])..", bypassing flow control.") end -- if not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] then end -- function flowRateControl(turbineIndex) local function helpText() -- these keys are actually defined in eventHandler(), check there return [[Keyboard commands: m Select next monitor s Make selected monitor display global status x Make selected monitor display debug information d Toggle debug mode q Quit r Quit and reboot h Print this help ]] end -- function helpText() local function initializePeripherals() monitorAssignments = {} -- Get our list of connected monitors and reactors findMonitors() findReactors() findTurbines() assignMonitors() end local function updateMonitors() -- Display overall status on selected monitors for monitorName, deviceData in pairs(monitorAssignments) do local monitor = nil local monitorIndex = deviceData.index local monitorType = deviceData.type monitor = monitorList[monitorIndex] printLog("main(): Trying to display "..monitorType.." on "..monitorNames[monitorIndex].."["..monitorIndex.."]", DEBUG) if #monitorList < (#reactorList + #turbineList + 1) then printLog("You may want "..(#reactorList + #turbineList + 1).." monitors for your "..#reactorList.." connected reactors and "..#turbineList.." connected turbines.") end if (not monitor) or (not monitor.getSize()) then printLog("monitor["..monitorIndex.."] in main() is NOT a valid monitor, discarding", ERROR) monitorAssignments[monitorName] = nil -- 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 break elseif monitorType == "Status" then -- General status display clearMonitor(progName.." "..progVer, monitorIndex) -- Clear monitor and draw borders printCentered(progName.." "..progVer, 1, monitorIndex) displayAllStatus(monitorIndex) elseif monitorType == "Reactor" then -- Reactor display local reactorMonitorIndex = monitorIndex for reactorIndex = 1, #reactorList do if deviceData.reactorName == reactorNames[reactorIndex] then printLog("Attempting to display reactor["..reactorIndex.."] on monitor["..monitorIndex.."]...", DEBUG) -- Only attempt to assign a monitor if we have a monitor for this reactor if (reactorMonitorIndex <= #monitorList) then printLog("Displaying reactor["..reactorIndex.."] on monitor["..reactorMonitorIndex.."].") clearMonitor(progName, reactorMonitorIndex) -- Clear monitor and draw borders printCentered(progName, 1, reactorMonitorIndex) -- Display reactor status, includes "Disconnected" but found reactors reactorStatus{reactorIndex, reactorMonitorIndex} -- Draw the borders and bars for the current reactor on the current monitor displayReactorBars{reactorIndex, reactorMonitorIndex} end end -- if deviceData.reactorName == reactorNames[reactorIndex] then end -- for reactorIndex = 1, #reactorList do elseif monitorType == "Turbine" then -- Turbine display local turbineMonitorIndex = monitorIndex for turbineIndex = 1, #turbineList do if deviceData.turbineName == turbineNames[turbineIndex] then printLog("Attempting to display turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."]...", DEBUG) -- Only attempt to assign a monitor if we have a monitor for this turbine --printLog("debug: "..turbineMonitorIndex) if (turbineMonitorIndex <= #monitorList) then printLog("Displaying turbine["..turbineIndex.."] on monitor["..turbineMonitorIndex.."].") clearMonitor(progName, turbineMonitorIndex) -- Clear monitor and draw borders printCentered(progName, 1, turbineMonitorIndex) -- Display turbine status, includes "Disconnected" but found turbines turbineStatus(turbineIndex, turbineMonitorIndex) -- Draw the borders and bars for the current turbine on the current monitor displayTurbineBars(turbineIndex, turbineMonitorIndex) end end end elseif monitorType == "Debug" then -- do nothing, printLog() outputs to here else clearMonitor(progName, monitorIndex) print{"Monitor inactive", 7, 7, monitorIndex} end -- if monitorType == [...] end end function main() -- Load reactor parameters and initialize systems loadReactorOptions() initializePeripherals() write(helpText()) --TODO: this is a global variable set by the event handler! while not finished do updateMonitors() local reactor = nil local sd = 0 -- Iterate through reactors for reactorIndex = 1, #reactorList do local monitor = nil reactor = reactorList[reactorIndex] if not reactor then printLog("reactor["..reactorIndex.."] in main() is NOT a valid Big Reactor.") break -- Invalid reactorIndex else printLog("reactor["..reactorIndex.."] in main() is a valid Big Reactor.") end -- if not reactor then if reactor.mbIsConnected() then printLog("reactor["..reactorIndex.."] is connected.") -- Collect steam production data if reactor.isActivelyCooled() then sd = sd + reactor.getHotFluidProducedLastTick() else -- Not actively cooled local curStoredEnergyPercent = getReactorStoredEnergyBufferPercent(reactor) -- Shutdown reactor if current stored energy % is >= desired level, otherwise activate -- First pass will have curStoredEnergyPercent=0 until displayBars() is run once if curStoredEnergyPercent >= maxStoredEnergyPercent then reactor.setActive(false) -- Do not auto-start the reactor if it was manually powered off (autoStart=false) elseif (curStoredEnergyPercent <= minStoredEnergyPercent) and (_G[reactorNames[reactorIndex]]["ReactorOptions"]["autoStart"] == true) then reactor.setActive(true) end -- if curStoredEnergyPercent >= maxStoredEnergyPercent then end -- if reactor.isActivelyCooled() then -- Don't try to auto-adjust control rods if manual control is requested if not _G[reactorNames[reactorIndex]]["ReactorOptions"]["rodOverride"] then temperatureControl(reactorIndex) end -- if not reactorRodOverride then else printLog("reactor["..reactorIndex.."] is NOT connected.") end -- if reactor.mbIsConnected() then end -- for reactorIndex = 1, #reactorList do -- Now that temperatureControl() had a chance to use it, reset/calculate steam data for next iteration printLog("Steam requested: "..steamRequested.." mB") printLog("Steam delivered: "..steamDelivered.." mB") steamDelivered = sd steamRequested = 0 -- Turbine control for turbineIndex = 1, #turbineList do local turbine = turbineList[turbineIndex] if not turbine then printLog("turbine["..turbineIndex.."] in main() is NOT a valid Big Turbine.") break -- Invalid turbineIndex else printLog("turbine["..turbineIndex.."] in main() is a valid Big Turbine.") end -- if not turbine then if turbine.mbIsConnected() then printLog("turbine["..turbineIndex.."] is connected.") if ((not _G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"]) or (_G[turbineNames[turbineIndex]]["TurbineOptions"]["flowOverride"] == "false")) then flowRateControl(turbineIndex) end -- if not turbineFlowRateOverride[turbineIndex] then -- Collect steam consumption data if turbine.getActive() then steamRequested = steamRequested + turbine.getFluidFlowRateMax() end else printLog("turbine["..turbineIndex.."] is NOT connected.") end -- if turbine.mbIsConnected() then end -- for reactorIndex = 1, #reactorList do wait(loopTime) -- Sleep. No, wait... saveReactorOptions() end -- while not finished do end -- main() -- handle all the user interaction events eventHandler = function(event, arg1, arg2, arg3) printLog(string.format("handleEvent(%s, %s, %s, %s)", tostring(event), tostring(arg1), tostring(arg2), tostring(arg3)), DEBUG) if event == "monitor_touch" then sideClick, xClick, yClick = arg1, math.floor(arg2), math.floor(arg3) UI:handlePossibleClick() elseif (event == "peripheral") or (event == "peripheral_detach") then printLog("Change in network detected. Reinitializing peripherals. We will be back shortly.", WARN) initializePeripherals() elseif event == "char" and not inManualMode then local ch = string.lower(arg1) -- remember to update helpText() when you edit these if ch == "q" then finished = true elseif ch == "d" then debugMode = not debugMode local modeText if debugMode then modeText = "on" else modeText = "off" end termRestore() write("debugMode "..modeText.."\n") elseif ch == "m" then UI:selectNextMonitor() elseif ch == "s" then UI:selectStatus() elseif ch == "x" then UI:selectDebug() elseif ch == "r" then finished = true os.reboot() elseif ch == "h" then write(helpText()) end -- if ch == "q" then end -- if event == "monitor_touch" then updateMonitors() end -- function eventHandler() main() -- Clear up after an exit term.clear() term.setCursorPos(1,1)