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