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