apemanzilla

ic2 reactor control

Jun 5th, 2018
175
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[
  2.     IC2 fluid reactor control
  3.         Automatically run an IC2 fluid reactor, shutting down when operation is unsafe or components depleted. Can even
  4.         replace depleted components automatically using an attached inventory!
  5.  
  6.     Basic settings
  7.         reactor.signal_out - side to emit redstone to activate reactor, defaults to "back"
  8.         reactor.signal_in - side to detect redstone to manually en/disable, defaults to "front"
  9.         reactor.interval - interval (in seconds) to run safety checks, defaults to 0.5
  10.         reactor.heat_threshold - heat percent to automatically shut down reactor, defaults to 60
  11.  
  12.     Advanced settings
  13.         reactor.layout - optional layout code to ensure slots are appropriately filled
  14.         reactor.items_in - optional peripheral name for inventory to automatically resupply reactor from (also needs reactor.layout)
  15.         reactor.items_out - optional peripheral name for inventory to automatically remove depleted
  16.                 components, defaults to same as reactor.items_in (also needs reactor.layout)
  17. ]]
  18.  
  19. --[[ Config ]]
  20. local signalOut = settings.get("reactor.signal_out", "back")
  21. local signalIn = settings.get("reactor.signal_in", "front")
  22. local interval = settings.get("reactor.interval", 0.5)
  23. local heatThreshold = settings.get("reactor.heat_threshold", 60) / 100
  24. local layoutCode = settings.get("reactor.layout")
  25. local itemsInName = settings.get("reactor.items_in")
  26. local itemsOutName = settings.get("reactor.items_out", itemsInName)
  27.  
  28. --[[ Utility functions/constants ]]
  29. local function contains(tbl, value)
  30.     for k, v in pairs(tbl) do
  31.         if v == value then return true end
  32.     end
  33.  
  34.     return false
  35. end
  36.  
  37. -- lookup table for simulator IDs to part names, GT parts not included
  38. local simulatorLookup = {
  39.     "ic2:uranium_fuel_rod",
  40.     "ic2:dual_uranium_fuel_rod",
  41.     "ic2:quad_uranium_fuel_rod",
  42.     "ic2:mox_fuel_rod",
  43.     "ic2:dual_mox_fuel_rod",
  44.     "ic2:quad_mox_fuel_rod",
  45.     "ic2:neutron_reflector",
  46.     "ic2:thick_neutron_reflector",
  47.     "ic2:heat_vent",
  48.     "ic2:advanced_heat_vent",
  49.     "ic2:reactor_heat_vent",
  50.     "ic2:component_heat_vent",
  51.     "ic2:overclocked_heat_vent",
  52.     "ic2:heat_storage",
  53.     "ic2:tri_heat_storage",
  54.     "ic2:hex_heat_storage",
  55.     "ic2:heat_exchanger",
  56.     "ic2:advanced_heat_exchanger",
  57.     "ic2:reactor_heat_exchanger",
  58.     "ic2:component_heat_exchanger",
  59.     "ic2:plating",
  60.     "ic2:heat_plating",
  61.     "ic2:containment:plating",
  62.     "ic2:rsh_condensator",
  63.     "ic2:lzh_condensator",
  64.     [35] = "ic2:iridium_reflector"
  65. }
  66.  
  67. -- fuel times - item name pattern -> fuel time in seconds
  68. local fuelTimes = {
  69.     ["uranium_fuel_rod"] = 20000,
  70.     ["mox_fuel_rod"] = 10000
  71. }
  72.  
  73. local function formatTime(s)
  74.     local h = math.floor(s / 3600)
  75.     s = s - h * 3600
  76.  
  77.     local m = math.floor(s / 60)
  78.     s = s - m * 60
  79.  
  80.     return ("%02d:%02d:%02d"):format(h, m, s)
  81. end
  82.  
  83. --[[ Initialization ]]
  84. redstone.setOutput(signalOut, false) -- ensure reactor is shut down
  85.  
  86. -- allow tile entities to initialize properly on world load
  87. if os.clock() < 10 then
  88.     sleep(5)
  89. end
  90.  
  91. -- open all wireless modems
  92. peripheral.find("modem", function(name, wrapped)
  93.     if wrapped.isWireless() then
  94.         rednet.open(name)
  95.     end
  96. end)
  97.  
  98. local hatch = assert(peripheral.find("ic2:reactor_access_hatch"), "Access hatch not found")
  99. local core = assert(hatch.getReactorCore(), "Reactor core inaccessible")
  100. local fluid = assert(peripheral.find("ic2:reactor_fluid_port"), "Fluid port not found")
  101. local monitor = peripheral.find("monitor")
  102.  
  103. if monitor then
  104.     monitor.setCursorBlink(false)
  105.     monitor.setTextScale(2)
  106. else
  107.     term.setCursorBlink(false)
  108. end
  109.  
  110. local itemsIn = peripheral.wrap(itemsInName or "")
  111. local itemsOut = peripheral.wrap(itemsOutName or "")
  112.  
  113. assert(itemsIn or not itemsInName, "Item input not found")
  114. assert(itemsOut or not itemsOutName, "Item output not found")
  115.  
  116. if not layoutCode then
  117.     printError("Warning - layout code not specified, inventory check and automation disabled")
  118. elseif not (itemsIn and itemsOut) then
  119.     printError("Warning - item input/output not specified, automation disabled")
  120. else
  121.     -- ensure reactor can transfer to/from item input/output
  122.     local locations = hatch.getTransferLocations()
  123.     assert(contains(locations, itemsInName), "No connection between reactor and " .. itemsInName)
  124.     assert(contains(locations, itemsOutName), "No connection between reactor and " .. itemsOutName)
  125. end
  126.  
  127. local function assertEqual(a, b, message)
  128.     assert(a == b, (message or "Expected %d, got %d"):format(a, b))
  129. end
  130.  
  131. -- parse a layout code into a table of slot -> expected item name
  132. local function parseLayout(code)
  133.     assertEqual(code:len(), 54 * 2, "Invalid layout code length - got %d, expected %d")
  134.  
  135.     local out = {}
  136.  
  137.     for slot = 1, 54 do
  138.         local chunk = code:sub((2 * slot) - 1, 2 * slot)
  139.         local id = assert(tonumber(chunk, 16), ("Invalid chunk in layout code: %s in slot %d"):format(chunk, slot))
  140.  
  141.         if id ~= 0 then
  142.             out[slot] = assert(simulatorLookup[id], "Unrecognized component ID: " .. id)
  143.         else
  144.             out[slot] = false
  145.         end
  146.     end
  147.  
  148.     return out
  149. end
  150.  
  151. local layout = parseLayout(layoutCode)
  152.  
  153. --[[ Main functions ]]
  154.  
  155. -- verify the temperature is safe
  156. local function checkTemperature(coreState)
  157.     return coreState.heat / coreState.maxHeat <= heatThreshold
  158. end
  159.  
  160. -- verify that cold coolant > 40% and hot coolant < 40%
  161. local function checkCoolant(tanks)
  162.     local hot, cold = 0, 0
  163.  
  164.     for i, tank in ipairs(tanks) do
  165.         if tank.id == "ic2:ic2hot_coolant" then
  166.             hot = tank.amount / tank.capacity
  167.         elseif tank.id == "ic2:ic2coolant" then
  168.             cold = tank.amount / tank.capacity
  169.         end
  170.     end
  171.  
  172.     return hot < 0.4 and cold > 0.4
  173. end
  174.  
  175. -- get fuel time in seconds
  176. local function getFuelTime(inv)
  177.     local time, rods = math.huge, 0
  178.  
  179.     for slot, data in pairs(inv) do
  180.         for pattern, duration in pairs(fuelTimes) do
  181.             if data.name:match(pattern) then
  182.                 time = math.min(time, 1 + duration * (1 - hatch.getItemMeta(slot).durability))
  183.                 rods = rods + 1
  184.             end
  185.         end
  186.     end
  187.  
  188.     return rods == 0 and 0 or time
  189. end
  190.  
  191. -- verify reactor layout is correct
  192. local function checkLayout(inv, fix)
  193.     if not layout then return true end
  194.     if fix and not (itemsIn and itemsOut) then return true end
  195.  
  196.     for slot, expected in pairs(layout) do
  197.         if expected then
  198.             if inv[slot] and inv[slot].name ~= expected then
  199.                 -- wrong item present
  200.                 if fix then
  201.                     local moved = hatch.pushItems(itemsOutName, slot)
  202.  
  203.                     if moved ~= inv[slot].count then
  204.                         -- push failed
  205.                         return false, slot
  206.                     else
  207.                         inv[slot] = nil
  208.                     end
  209.                 else
  210.                     return false, slot
  211.                 end
  212.             end
  213.  
  214.             if not inv[slot] then
  215.                 -- item missing
  216.                 if fix then
  217.                     -- find appropriate item in input
  218.                     local fromSlot
  219.  
  220.                     for s, v in pairs(itemsIn.list()) do
  221.                         if v.name == expected then
  222.                             fromSlot = s
  223.                             break
  224.                         end
  225.                     end
  226.  
  227.                     if fromSlot then
  228.                         -- found appropriate item
  229.                         local moved = hatch.pullItems(itemsInName, fromSlot, 1, slot)
  230.  
  231.                         if moved < 1 then
  232.                             -- pull failed
  233.                             return false, slot
  234.                         else
  235.                             -- full rescan
  236.                             inv = hatch.list()
  237.                         end
  238.                     else
  239.                         return false, slot
  240.                     end
  241.                 else
  242.                     return false, slot
  243.                 end
  244.             end
  245.         else
  246.             if inv[slot] then
  247.                 -- item present, but shouldn't be
  248.                 if fix then
  249.                     local moved = hatch.pushItems(itemsOutName, slot)
  250.  
  251.                     if moved ~= inv[slot].count then
  252.                         -- push failed
  253.                         return false, slot
  254.                     else
  255.                         inv[slot] = nil
  256.                     end
  257.                 else
  258.                     return false, slot
  259.                 end
  260.             end
  261.         end
  262.     end
  263.  
  264.     return true
  265. end
  266.  
  267. local oldTerm = term.redirect(monitor or term.current())
  268.  
  269. local ok, err = pcall(function()
  270.     while true do
  271.         sleep(interval)
  272.  
  273.         local coreState = core.getMetadata().reactor
  274.         local tanks = fluid.getTanks("north")
  275.         local inv = hatch.list()
  276.  
  277.         local shouldRun = redstone.getInput(signalIn)
  278.         local fuelTime = getFuelTime(inv)
  279.         local tempOK = checkTemperature(coreState)
  280.         local coolantOK = checkCoolant(tanks)
  281.         local layoutOK, badSlot = checkLayout(inv)
  282.  
  283.         local active = false
  284.         local message
  285.  
  286.         if not shouldRun then
  287.             message = "Offline"
  288.         elseif not tempOK then
  289.             message = "Overheat"
  290.         elseif not coolantOK then
  291.             message = "Coolant"
  292.         elseif not layoutOK then
  293.             message = "Slot #" .. badSlot
  294.         elseif fuelTime <= 0 then
  295.             message = "No fuel"
  296.         else
  297.             active = true
  298.             message = "Online"
  299.         end
  300.  
  301.         redstone.setOutput(signalOut, active)
  302.  
  303.         term.setCursorPos(1, 1)
  304.         term.clear()
  305.         term.write(message)
  306.  
  307.         if active then
  308.             term.setCursorPos(1, 2)
  309.             term.write(formatTime(fuelTime))
  310.         end
  311.  
  312.         rednet.broadcast({ active = active, seconds = fuelTime, message = message }, "reactor")
  313.  
  314.         if shouldRun and itemsIn and itemsOut and not layoutOK then
  315.             -- try to fix inv
  316.             checkLayout(inv, true)
  317.         end
  318.     end
  319. end)
  320.  
  321. redstone.setOutput(signalOut, false)
  322.  
  323. term.setCursorPos(1, 1)
  324. term.clear()
  325. term.redirect(oldTerm)
  326.  
  327. if not ok then printError(err) end
RAW Paste Data