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