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