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