Skip to content
GT New HorizonsGTNH 2.9.0 · Community server

GT New Horizons · OpenComputers

OpenComputers Space Pumping


These OpenComputers (OC) scripts automatically switch Space Elevator (SE) pumping module settings based on which fluids are needed at the time, similar to a level maintainer. This saves from having to manually change the settings all the time, or creating dedicated modules for each fluid. This guide walks through every step of the process, from building the setup to debugging and troubleshooting. No prior knowledge of OC is necessary.

The general idea is that the script queries an AE2 subnetwork to compare the current fluid levels against the configured target levels. If any are below the minimum threshold, then the script finds an available pumping module to change the gas, planet, and batch size parameters. It calculates the exact length of time needed to reach the target level based on the tier of the module and the fluid itself. Once all target levels are reached, the script enters a sleep mode until it is needed again. The player only ever needs to change the configuration settings at the top of the script.

Building the Setup

STEP 1
Build out the fluid subnetwork (blue) with an ME controller and ME drive. Connect as many ME output haches from as many pumping modules as preferred, although 1-3 is usually enough.

Insert fluid storage cells into the ME drive to hold the full capacity of the SE fluids for the main network, since they are stored locally. Only 40 types are needed for all the different fluids, but there could be additional ME drives with even more cells.

Optionally, add a fluid terminal to view the current fluid levels in the subnetwork.

STEP 2
Use adapters to connect to the pumping modules. They can either touch the controllers directly, or use an MFU to wirelessly connect. Link the adapters together with OpenComputers cable.

To use an MFU, sneak right-click one of the controllers and then insert the upgrade into one of the adapters. It should highlight the block that is selected when holding the MFU in hand.

The maximum range of the MFU is 16 blocks so the adapters do not have to in these exact locations, but it is important that one also touches the ME controller of the fluid subnetwork (blue). Adapters can connect to multiple components at the same time.

STEP 3
Connect a screen (Tier 3), computer case (Tier 3), and keyboard to the adapters in the previous step. The components just need to be touching to work.

Insert the bare minimum components into the computer case. These could be even lower tier but there is no reason not to use the best components.

  • APU (Tier 3)
  • Memory (Tier 3.5)
  • Hard Disk Drive (Tier 3)
  • EEPROM (Lua BIOS)
  • OpenOS Floppy Disk

Power the computer with an AE2 cable or neutronium energy cell.

STEP 4
Place an ME dual interface on the fluid subnetwork (blue) with default settings facing into an ME fluid storage bus on the main network (green).

This lets the main network use all the fluids on the fluid subnetwork as if they were stored on the main network itself.

If the direction of the ME fluid storage bus is EXTRACT ONLY then the main network may randomly use extra types for the same fluids.

If the direction of the ME fluid storage bus is BIDIRECTIONAL then the main network may store random fluids on the fluid subnetwork and clog it. In that case, partition the storage cells to the SE fluids.

STEP 5
After booting up the computer, follow the commands on screen to install OpenOS.

install → Y → Y

Create a new script by typing edit autoPump.lua. The name is not important. Copy one of the code blocks below into the script. Use middle-click to paste.

Edit the config as necessary. Press CTRL+S to save and CTRL+W to exit. Use the same edit command to change the code at anytime.

Launch the script by typing autoPump. It automatically detects when fluids are low and changes the parameters of the pumping modules. Press CTRL+ALT+C to stop the script at any time.

Option 1: Target Levels

This version of the script uses specified target levels for each of the fluids. This allows less desirable fluids to be capped at a lower amount or even blacklisted to maximize time and energy for more desirable fluids. Priority is based on the percentage to target level and the priority setting, as seen in the following equation. This means empty fluids are prioritized first and those with a lower priority setting can still take priority if their percentage to target level is low enough. The target level can be set really high and hardly ever needs to change since all fluid levels rise together.

Priority = 1 − % Target Priority Setting {\displaystyle {\displaystyle {\text{Priority}}=1-\%{\text{Target}}^{\text{Priority Setting}}}}

The dashboard updates every few iterations to visually depict the current fluid levels with regards to their target levels.
-- GTNH 2.9 Space Pumping (Fox, samsonsin, San32)
local component = require('component')
local thread = require('thread')
local event = require('event')
local term = require('term')
local me = component.me_controller
local pumps = {}

-- CTRL+C to stop the script at any time, or restart the computer.

-- ===================== CONFIG ======================
-- Only change the PRIORITY and TARGET values. Do not change the SETTING or RATE values.
-- Quantum = 270e9 || Digital Singularity = 4e18 || Artificial Universe = 9e18

local master = {
  -- Planet 2 -----------------------------------------------------------------------------
  ['Chlorobenzene'] =     {target=10e9,  priority=1,  setting={2,1},  rate=896000}, -- Gas 1
  -- Planet 3 -----------------------------------------------------------------------------
  ['Ender Goo'] =         {target=10e9,  priority=1,  setting={3,1},  rate=32000}, -- Gas 1
  ['Very Heavy Oil'] =    {target=0,     priority=1,  setting={3,2},  rate=1400000}, -- Gas 2
  ['Lava'] =              {target=10e9,  priority=1,  setting={3,3},  rate=1800000}, -- Gas 3
  ['Natural Gas'] =       {target=0,     priority=1,  setting={3,4},  rate=1400000}, -- Gas 4
  -- Planet 4 -----------------------------------------------------------------------------
  ['Sulfuric Acid'] =     {target=10e9,  priority=1,  setting={4,1},  rate=784000}, -- Gas 1
  ['Molten Iron'] =       {target=10e9,  priority=2,  setting={4,2},  rate=896000}, -- Gas 2
  ['Oil'] =               {target=10e9,  priority=1,  setting={4,3},  rate=1400000}, -- Gas 3
  ['Heavy Oil'] =         {target=0,     priority=1,  setting={4,4},  rate=1792000}, -- Gas 4
  ['Molten Lead'] =       {target=10e9,  priority=1,  setting={4,5},  rate=896000}, -- Gas 5
  ['Raw Oil'] =           {target=0,     priority=1,  setting={4,6},  rate=1400000}, -- Gas 6
  ['Light Oil'] =         {target=0,     priority=1,  setting={4,7},  rate=780000}, -- Gas 7
  ['Carbon Dioxide'] =    {target=1e9,   priority=1,  setting={4,8},  rate=1680000}, -- Gas 8
  -- Planet 5 -----------------------------------------------------------------------------
  ['Carbon Monoxide'] =   {target=10e9,  priority=1,  setting={5,1},  rate=4480000}, -- Gas 1
  ['Helium-3'] =          {target=10e9,  priority=1,  setting={5,2},  rate=2800000}, -- Gas 2
  ['Salt Water'] =        {target=10e9,  priority=1,  setting={5,3},  rate=2800000}, -- Gas 3
  ['Helium'] =            {target=10e9,  priority=3,  setting={5,4},  rate=1400000}, -- Gas 4
  ['Liquid Oxygen'] =     {target=0,     priority=1,  setting={5,5},  rate=896000}, -- Gas 5
  ['Neon'] =              {target=1e9,   priority=1,  setting={5,6},  rate=32000}, -- Gas 6
  ['Argon'] =             {target=1e9,   priority=1,  setting={5,7},  rate=32000}, -- Gas 7
  ['Krypton'] =           {target=1e9,   priority=1,  setting={5,8},  rate=8000}, -- Gas 8
  ['Methane'] =           {target=1e9,   priority=1,  setting={5,9},  rate=1792000}, -- Gas 9
  ['Hydrogen Sulfide'] =  {target=0,     priority=1,  setting={5,10},  rate=392000}, -- Gas 10
  ['Ethane'] =            {target=0,     priority=1,  setting={5,11},  rate=1194000}, -- Gas 11
  -- Planet 6 -----------------------------------------------------------------------------
  ['Deuterium'] =         {target=10e9,  priority=1,  setting={6,1},  rate=1568000}, -- Gas 1
  ['Tritium'] =           {target=10e9,  priority=1,  setting={6,2},  rate=240000}, -- Gas 2
  ['Ammonia'] =           {target=10e9,  priority=2,  setting={6,3},  rate=240000}, -- Gas 3
  ['Xenon'] =             {target=10e9,  priority=2,  setting={6,4},  rate=16000}, -- Gas 4
  ['Ethylene'] =          {target=10e9,  priority=1,  setting={6,5},  rate=1792000}, -- Gas 5
  -- Planet 7 -----------------------------------------------------------------------------
  ['Hydrofluoric Acid'] = {target=10e9,  priority=1,  setting={7,1},  rate=672000}, -- Gas 1
  ['Fluorine'] =          {target=10e9,  priority=1,  setting={7,2},  rate=1792000}, -- Gas 2
  ['Nitrogen'] =          {target=10e9,  priority=3,  setting={7,3},  rate=1792000}, -- Gas 3
  ['Oxygen'] =            {target=10e9,  priority=3,  setting={7,4},  rate=1729000}, -- Gas 4
  -- Planet 8 -----------------------------------------------------------------------------
  ['Hydrogen'] =          {target=10e9,  priority=3,  setting={8,1},  rate=1568000}, -- Gas 1
  ['Liquid Air'] =        {target=0,     priority=1,  setting={8,2},  rate=875000}, -- Gas 2
  ['Molten Copper'] =     {target=10e9,  priority=2,  setting={8,3},  rate=672000}, -- Gas 3
  ['Unknown Liquid'] =    {target=10e9,  priority=1,  setting={8,4},  rate=672000}, -- Gas 4
  ['Distilled Water'] =   {target=10e9,  priority=1,  setting={8,5},  rate=17920000}, -- Gas 5
  ['Radon'] =             {target=1e9,   priority=1,  setting={8,6},  rate=64000}, -- Gas 6
  ['Molten Tin'] =        {target=10e9,  priority=1,  setting={8,7},  rate=672000}} -- Gas 7

-- The % of the Target when Considered Complete (Default: 95%)
local threshold = 0.95
-- The Upper Limit on the Duration of an Iteration (Default: 30s)
local maxBatchSize = 30
-- The Text Color (Default: '\27[1;36m')
local color = '\27[1;36m'

-- (https://github.com/torch/sys/blob/master/colors.lua)
-- Cyan = '\27[1;36m' || Green = '\27[1;32m' || Red = '\27[0;31m' || Magenta = '\27[0;35m'

-- =================== END CONFIG ====================

local function findPumps()
  for address in component.list('gt_machine') do
    local module = component.proxy(address)
    local name = module.getName()

    if name == "projectmodulepumpt1" then
      table.insert(pumps, {module=module, threads=1, mult=4, priority=1, fluid=nil, amount=0})
    elseif name == "projectmodulepumpt2" then
      table.insert(pumps, {module=module, threads=4, mult=16, priority=2, fluid=nil, amount=0})
    elseif name == "projectmodulepumpt3" then
      table.insert(pumps, {module=module, threads=4, mult=256, priority=3, fluid=nil, amount=0})
    end
  end

  -- Sort Based on Priority
  table.sort(pumps, function(a, b) return a.priority > b.priority end)
end

local function updateFluids()

  -- Reset Everything to Zero
  local lowFluids = {}
  for _, fluid in pairs(master) do fluid.amount = 0 end

  -- Update Fluids from ME Network
  for _, fluid in ipairs(me.getFluidsInNetwork()) do
    if master[fluid.label] ~= nil then master[fluid.label].amount = fluid.size end
  end

  -- Update Fluids from Pumps
  for _, pump in ipairs(pumps) do
    if pump.fluid ~= nil then master[pump.fluid].amount = master[pump.fluid].amount + pump.amount end
  end

  -- Identify Low Fluids
  for _, fluid in pairs(master) do
    if fluid.amount < threshold * fluid.target then table.insert(lowFluids, fluid) end
  end
  return lowFluids
end

local function updatePumps(lowFluids)
  for _, pump in ipairs(pumps) do

    -- Ensure Pump is Disabled
    pump.module.setWorkAllowed(false)
    while pump.module.isMachineActive() do os.sleep(2) end
    pump.fluid, pump.amount = nil, 0

    -- Sort Low Fluids based on Priority and % of Target
    table.sort(lowFluids, function(a, b)
      return (1 - (a.amount / a.target) ^ a.priority) < (1 - (b.amount / b.target) ^ b.priority)
    end)

    local fluid = lowFluids[#lowFluids]
    if fluid ~= nil then

      -- Change Planet and Gas for all Threads
      for i=0, pump.threads-1 do
        pump.module.setParameter('recipe' .. i .. '.planetType', fluid.setting[1])
        pump.module.setParameter('recipe' .. i .. '.gasType', fluid.setting[2])
      end

      -- Change Batch Size based on Distance from Target
      local batchSize = math.min(maxBatchSize, math.ceil((fluid.target - fluid.amount) / (fluid.rate * pump.mult)))
      pump.module.setParameter('batch', batchSize)
      print(string.format('autoPump: Running %s for %d Seconds', fluid.label, batchSize))

      -- Preemptively Update Fluid Amount
      pump.fluid = fluid.label
      pump.amount = batchSize * fluid.rate * pump.mult
      fluid.amount = fluid.amount + pump.amount

      -- Remove Fluid if above Target Threshold
      if fluid.amount >= fluid.target * threshold then table.remove(lowFluids) end

      -- Run Once
      pump.module.setWorkAllowed(true)
      os.sleep(0.1)
      pump.module.setWorkAllowed(false)
    else
      return
    end
  end
end

local function parse(label)
  local fluid = master[label]
  if fluid.target == 0 then
    return string.format('[----------] %-20s', fluid.label)
  else
    local percent = math.min(10, math.ceil(10 * (fluid.amount / fluid.target)))
    return string.format('[%s%s] %-20s', color .. string.rep('■', percent) .. '\27[0m', string.rep('□', 10-percent), fluid.label)
  end
end

local function printDashboard()
  term.clear()
  print('\n┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐')
  print('│' .. color .. ' Space Elevator Fluid Levels (% of Target)' .. '\27[0m' .. string.rep(' ', 95) .. '│')
  print('├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤')
  print(string.format('│ %s %s %s %s │', parse('Hydrogen'),      parse('Argon'),            parse('Oil'),             parse('Hydrogen Sulfide')))
  print(string.format('│ %s %s %s %s │', parse('Helium'),        parse('Radon'),            parse('Raw Oil'),         parse('Sulfuric Acid')))
  print(string.format('│ %s %s %s %s │', parse('Nitrogen'),      parse('Neon'),             parse('Light Oil'),       parse('Hydrofluoric Acid')))
  print(string.format('│ %s %s %s %s │', parse('Oxygen'),        parse('Krypton'),          parse('Heavy Oil'),       string.rep(' ', 33)))
  print(string.format('│ %s %s %s %s │', parse('Fluorine'),      parse('Xenon'),            parse('Natural Gas'),     string.rep(' ', 33)))
  print(string.format('│ %s %s %s %s │', string.rep(' ', 33),    string.rep(' ', 33),       string.rep(' ', 33),      string.rep(' ', 33)))
  print(string.format('│ %s %s %s %s │', parse('Molten Iron'),   parse('Ammonia'),          parse('Distilled Water'), parse('Helium-3')))
  print(string.format('│ %s %s %s %s │', parse('Molten Copper'), parse('Ethylene'),         parse('Salt Water'),      parse('Deuterium')))
  print(string.format('│ %s %s %s %s │', parse('Molten Tin'),    parse('Ethane'),           parse('Chlorobenzene'),   parse('Tritium')))
  print(string.format('│ %s %s %s %s │', parse('Molten Lead'),   parse('Methane'),          parse('Unknown Liquid'),  string.rep(' ', 33)))
  print(string.format('│ %s %s %s %s │', string.rep(' ', 33),    string.rep(' ', 33),       string.rep(' ', 33),      string.rep(' ', 33)))
  print(string.format('│ %s %s %s %s │', parse('Liquid Air'),    parse('Carbon Monoxide'),  parse('Lava'),            string.rep(' ', 33)))
  print(string.format('│ %s %s %s %s │', parse('Liquid Oxygen'), parse('Carbon Dioxide'),   parse('Ender Goo'),       string.rep(' ', 33)))
  print('└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\n')
end

local keyHandler = thread.create(function()
  local keyboard = require('keyboard')
  while true do
    _, _, _, code, _ = event.pull('key_down')
    if code == keyboard.keys.p then printDashboard()
    elseif code == keyboard.keys.u then
      pumps = {}
      findPumps()
    end
  end
end)

-- ====================== MAIN =======================

local main = thread.create(function()
  local n = 1
  findPumps()
  for k, fluid in pairs(master) do fluid.label = k end

  -- THE LOOP
  while true do

    -- Update Fluid Amounts
    local lowFluids = updateFluids()
    if next(lowFluids) ~= nil then

      if n % 5 == 1 then printDashboard() end

      -- Update Pump Settings
      updatePumps(lowFluids)
      n = n+1

    elseif n > 0 then

      -- Reset Pump Settings
      for _, pump in ipairs(pumps) do
        pump.fluid, pump.amount = nil, 0
      end

      -- Nothing to Update, Sleep 3 Minutes
      printDashboard()
      print('autoPump: Sleeping...\n')
      os.sleep(180)
      n=0
    end
  end
end)

local cleanUp = thread.create(function()
    event.pull('interrupted')
    term.clear()
    print('Received Exit Command!')
    for _, pump in ipairs(pumps) do
      for _=1, pump.threads do pump.module.setWorkAllowed(false) end
    end
    main.kill()
    keyHandler.kill()
    os.exit(0)
end)

thread.waitForAny({main, keyHandler, cleanUp})
os.exit(0)

Option 2: Median and Offset

This version of the script takes the median value of the current fluid levels and adds an offset to determine the target level for all fluids, as seen in the following equation. This means the target level never has to be manually set and dynamically changes with the current fluid levels. However, there is no capping undesirable fluids at a lower amount or blacklisting them. Priority is based first on the priority setting and then the percentage to the target level. All fluid levels rise together until they reach the max storage size (configurable) which by default is 99% of a fluid digital singularity storage cell.

Target = Median + Offset {\displaystyle {\displaystyle {\text{Target}}={\text{Median}}+{\text{Offset}}}}

The dashboard updates immediately to numerically depict the current fluid levels with regards to their previous fluid levels. The default offset is 10 GL.
-- GTNH 2.9 Space Pumping (Fox, samsonsin)
local component = require('component')
local me = component.me_controller
local pumps = {}
local n = 0

-- CTRL+ALT+C to stop the script at any time.

-- ===================== CONFIG ======================
-- Only change the PRIORITY value. Do not change the SETTING or RATE values.

local master = {
  -- Planet 2 -----------------------------------------------------------------------------
  ['Chlorobenzene'] =     {priority=0,  amount=0,  setting={2,1},  rate=896000}, -- Gas 1
  -- Planet 3 -----------------------------------------------------------------------------
  ['Ender Goo'] =         {priority=0,  amount=0,  setting={3,1},  rate=32000}, -- Gas 1
  ['Very Heavy Oil'] =    {priority=0,  amount=0,  setting={3,2},  rate=1400000}, -- Gas 2
  ['Lava'] =              {priority=0,  amount=0,  setting={3,3},  rate=1800000}, -- Gas 3
  ['Natural Gas'] =       {priority=0,  amount=0,  setting={3,4},  rate=1400000}, -- Gas 4
  -- Planet 4 -----------------------------------------------------------------------------
  ['Sulfuric Acid'] =     {priority=0,  amount=0,  setting={4,1},  rate=784000}, -- Gas 1
  ['Molten Iron'] =       {priority=0,  amount=0,  setting={4,2},  rate=896000}, -- Gas 2
  ['Oil'] =               {priority=0,  amount=0,  setting={4,3},  rate=1400000}, -- Gas 3
  ['Heavy Oil'] =         {priority=0,  amount=0,  setting={4,4},  rate=1792000}, -- Gas 4
  ['Molten Lead'] =       {priority=0,  amount=0,  setting={4,5},  rate=896000}, -- Gas 5
  ['Raw Oil'] =           {priority=0,  amount=0,  setting={4,6},  rate=1400000}, -- Gas 6
  ['Light Oil'] =         {priority=0,  amount=0,  setting={4,7},  rate=780000}, -- Gas 7
  ['Carbon Dioxide'] =    {priority=0,  amount=0,  setting={4,8},  rate=1680000}, -- Gas 8
  -- Planet 5 -----------------------------------------------------------------------------
  ['Carbon Monoxide'] =   {priority=0,  amount=0,  setting={5,1},  rate=4480000}, -- Gas 1
  ['Helium-3'] =          {priority=0,  amount=0,  setting={5,2},  rate=2800000}, -- Gas 2
  ['Salt Water'] =        {priority=0,  amount=0,  setting={5,3},  rate=2800000}, -- Gas 3
  ['Helium'] =            {priority=0,  amount=0,  setting={5,4},  rate=1400000}, -- Gas 4
  ['Liquid Oxygen'] =     {priority=0,  amount=0,  setting={5,5},  rate=896000}, -- Gas 5
  ['Neon'] =              {priority=0,  amount=0,  setting={5,6},  rate=32000}, -- Gas 6
  ['Argon'] =             {priority=0,  amount=0,  setting={5,7},  rate=32000}, -- Gas 7
  ['Krypton'] =           {priority=0,  amount=0,  setting={5,8},  rate=8000}, -- Gas 8
  ['Methane'] =           {priority=0,  amount=0,  setting={5,9},  rate=1792000}, -- Gas 9
  ['Hydrogen Sulfide'] =  {priority=0,  amount=0,  setting={5,10},  rate=392000}, -- Gas 10
  ['Ethane'] =            {priority=0,  amount=0,  setting={5,11},  rate=1194000}, -- Gas 11
  -- Planet 6 -----------------------------------------------------------------------------
  ['Deuterium'] =         {priority=0,  amount=0,  setting={6,1},  rate=1568000}, -- Gas 1
  ['Tritium'] =           {priority=0,  amount=0,  setting={6,2},  rate=240000}, -- Gas 2
  ['Ammonia'] =           {priority=0,  amount=0,  setting={6,3},  rate=240000}, -- Gas 3
  ['Xenon'] =             {priority=0,  amount=0,  setting={6,4},  rate=16000}, -- Gas 4
  ['Ethylene'] =          {priority=0,  amount=0,  setting={6,5},  rate=1792000}, -- Gas 5
  -- Planet 7 -----------------------------------------------------------------------------
  ['Hydrofluoric Acid'] = {priority=0,  amount=0,  setting={7,1},  rate=672000}, -- Gas 1
  ['Fluorine'] =          {priority=0,  amount=0,  setting={7,2},  rate=1792000}, -- Gas 2
  ['Nitrogen'] =          {priority=0,  amount=0,  setting={7,3},  rate=1792000}, -- Gas 3
  ['Oxygen'] =            {priority=0,  amount=0,  setting={7,4},  rate=1729000}, -- Gas 4
  -- Planet 8 -----------------------------------------------------------------------------
  ['Hydrogen'] =          {priority=0,  amount=0,  setting={8,1},  rate=1568000}, -- Gas 1
  ['Liquid Air'] =        {priority=0,  amount=0,  setting={8,2},  rate=875000}, -- Gas 2
  ['Molten Copper'] =     {priority=0,  amount=0,  setting={8,3},  rate=672000}, -- Gas 3
  ['Unknown Liquid'] =    {priority=0,  amount=0,  setting={8,4},  rate=672000}, -- Gas 4
  ['Distilled Water'] =   {priority=0,  amount=0,  setting={8,5},  rate=17920000}, -- Gas 5
  ['Radon'] =             {priority=0,  amount=0,  setting={8,6},  rate=64000}, -- Gas 6
  ['Molten Tin'] =        {priority=0,  amount=0,  setting={8,7},  rate=672000}} -- Gas 7

local target = 1
local dynamicTargetOffset = 10e9 -- adds to the median fluid amount to set as the target
local singularityCellSize = 4.61e18
local maxStorageAmount = singularityCellSize*0.99

-- The Upper Limit on the Duration of an Iteration (Default: 30s)
local maxBatchSize = 30

-- =================== END CONFIG ====================

local function sortFluidsByPriorityThenFillRatio(lowFluids)
  table.sort(lowFluids, function(a, b)
    if a.priority - b.priority ~= 0 then
        return a.priority > b.priority
    else
        return (a.amount / target) < (b.amount / target)
    end
  end)
end

local function sortFluidsByAmount(lowFluids)
  table.sort(lowFluids, function(a, b)
    return a.amount  < b.amount 
  end)
end

local function formatFluid(amount)
        local suffixes = {' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}
        local index = 1
        local value = amount
        
        while value >= 1000 and index < #suffixes do
          value = value / 1000
          index = index + 1
        end
        
        return string.format('%.3f %sL', value, suffixes[index])
      end

local function findPumps()
  for address in component.list('gt_machine') do
    local module = component.proxy(component.get(address))
    local name = module.getName()

    if name == "projectmodulepumpt1" then
      table.insert(pumps, {module=module, threads=1, mult=4, priority=1})
    elseif name == "projectmodulepumpt2" then
      table.insert(pumps, {module=module, threads=4, mult=16, priority=2})
    elseif name == "projectmodulepumpt3" then
      table.insert(pumps, {module=module, threads=4, mult=256, priority=3})
    end
  end

  -- Sort Based on Priority
  table.sort(pumps, function(a, b) return a.priority > b.priority end)
end

local function updateFluids()

  -- Reset Everything to Zero
  local lowFluids = {}
  for _, fluid in pairs(master) do
    fluid.amount = 0
  end

  -- Update the Fluids Available
  for _, fluid in ipairs(me.getFluidsInNetwork()) do
    if master[fluid.label] ~= nil then
      master[fluid.label].amount = fluid.size
    end
  end

  -- Convert master dictionary to array for sorting
  local fluidArray = {}
  for _, fluid in pairs(master) do
    table.insert(fluidArray, fluid)
  end
  
  -- Find Median to set dynamic target
  sortFluidsByAmount(fluidArray)
  local medianFluid = #fluidArray > 0 and fluidArray[math.ceil(#fluidArray / 2)] or nil
  target = medianFluid ~= nil and math.min(medianFluid.amount + dynamicTargetOffset, maxStorageAmount) or maxStorageAmount

  print('┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐')
  print(string.format('│ Target set according to Median: │ %-20s │ %10s + %10s = %10s %51s', medianFluid.label, formatFluid(medianFluid.amount), formatFluid(dynamicTargetOffset), formatFluid(target), '│'))
  print('├──────────────────────┬───────┬────────────────────────────────────────────────────────────────┬───────────────────────────────┬───────────────┤')
  print('│ Name                 │ Dur   │        Old +      Added =        New :  Target +% =   Target % │                            +% │           L/s │')
  print('├──────────────────────┼───────┼────────────────────────────────────────────────────────────────┼───────────────────────────────┼───────────────┤')
 
  -- Identify Low Fluids
  for _, fluid in pairs(master) do
    if fluid.amount < target then
      table.insert(lowFluids, fluid)
    end
  end

  return lowFluids
end

local function updatePumps(lowFluids)
  for _, pump in ipairs(pumps) do
      
    -- Ensure Pump is Disabled
    pump.module.setWorkAllowed(false)
    while pump.module.isMachineActive() do os.sleep(2) end

    sortFluidsByPriorityThenFillRatio(lowFluids)
    local fluid = lowFluids[1]
    if fluid ~= nil then

      -- Ensure pump is disabled
      while pump.module.isMachineActive() do os.sleep(1) end

      -- Remove fluid if already at target. next fluid will be at position 1 after removal
      if lowFluids[1].amount >= target then
        table.remove(lowFluids, 1)
      end

      -- Change planet and gas for ALL threads
      for i=0, pump.threads-1 do
        pump.module.setParameter('recipe' .. i .. '.planetType', fluid.setting[1])
        pump.module.setParameter('recipe' .. i .. '.gasType', fluid.setting[2])
      end

      -- Calculate varous amounts, update fluid amount preemptively
      local batchSize = math.min(maxBatchSize, math.ceil((maxStorageAmount - fluid.amount) / (fluid.rate * pump.mult)))
      local oldAmount = fluid.amount
      fluid.amount = fluid.amount + (batchSize * fluid.rate * pump.mult)
      local percentageGain = oldAmount > 0 and ((fluid.amount - oldAmount) / oldAmount) * 100 or 0
      local targetPercentageGain = target > 0 and ((fluid.amount - oldAmount) / target) * 100 or 0
      local targetFillPercentage = (fluid.amount / target) * 100
      
      print(string.format('│ %-20s │ %3d s │ %10s + %10s = %10s : %+8.3f %% = %8.3f %% │ %+27.3f %% │ %8.3f ML/s │', 
      fluid.label, 
      batchSize, 
      formatFluid(oldAmount), 
      formatFluid((fluid.amount - oldAmount)), 
      formatFluid(fluid.amount), 
      targetPercentageGain,
      targetFillPercentage,
      percentageGain, 
      (fluid.amount - oldAmount)/1e6/batchSize))
      
      pump.module.setParameter('batch', batchSize)
      
      -- Run once
      pump.module.setWorkAllowed(true)
      os.sleep(0.1)
      pump.module.setWorkAllowed(false)

    else
      return
    end
  end
  print('└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘')
end

-- ====================== MAIN =======================

local function main()
  print('autoPump: Reading Config... Scanning Pumps...')
  findPumps()

  for k, fluid in pairs(master) do
    fluid.label = k
  end

  -- THE LOOP
  while true do

    -- Update Fluid Amounts
    local lowFluids = updateFluids()

    -- Update Pump Settings
    if next(lowFluids) ~= nil then
      updatePumps(lowFluids)
      n=0

    -- Nothing to Update, Sleep 3 Minutes
    elseif n==0 then
      print('autoPump: Sleeping...\n')
      os.sleep(180)
      n=1
    end
  end
end

main()

Running the Scripts

In both versions, the script autoPump first identifies all connected pumps. The tier of the pumps do not all have to be the same, and higher tiers are automatically prioritized over lower ones. Next, the script identifies any low fluids by querying the fluid subnetwork and comparing the current fluid levels against the target level, whether set manually or by using the median and offset. A fluid subnetwork is used to reduce the query size and save TPS. If any fluids are below the target value, then the script finds an available pumping module to change the gas, planet, and batch size parameters.

The batch size is calculated based on the length of time needed to reach the target level which depends on the tier of the module and the fluid itself. The maximum batch size is 30 (configurable) to prevent fluid pumps from running for a very long time without producing anything. Every "recipe" is exactly 1 second long so the maximum batch size is equivalent to the maximum duration in seconds.

Once all target levels are reached, or exceed the maximum storage size, the computer enters a sleep mode until it is needed again. By default, the script queries the fluid subnetwork once every 3 minutes to see if anything has changed. To run autoPump, simply enter:

autoPump

Note that the pumps can only change settings while idle. Ensure that they are not actively running prior to launching the script, or manually enabled during the script. To stop the script, simply restart the computer or press CTRL+ALT+C at any time. Changing anything in the config does NOT require a restart since it is all within a single file.

The target level version of the script has two additional keybinds. Pressing "u" updates the registered pumps which allows more to be added without ever stopping the program. Pressing "p" updates the dashboard and re-prints it immediately.

Pumping Modules & Fluids

There are three tiers of Pumping Modules available. Higher tiers offer more parallels and the ability to run multiple recipes (threads) simultaneously. Threads do not need to run the same recipe, but the script guarantees that they do. All three tiers have access to every possible fluid and gas. The modules do not overclock, but they only require a minimum EU/t to run--the voltage is irrelevant. Parallels process the same fluid and multiply the power requirement.

Module Requires Threads Parallels per Thread Max Power
Space Pumping MK-I Motor MK-II 1 4 7,864,320 EU/t
Space Pumping MK-II Motor MK-III 4 4 31,457,280 EU/t
Space Pumping MK-III Motor MK-IV 4 64 503,316,480 EU/t

The following table is the full list of planet types, gas types, fluids, and pumping speeds for various amounts of parallels. The script already includes these setting and rate values in the code.

Planet Type Gas Type Fluid Base (L/s) 4 Parallels (L/s) 64 Parallels (L/s)
2 1 Chlorobenzene 896,000 3,584,000 57,344,000
3 1 Ender Goo 32,000 128,000 2,048,000
2 Very Heavy Oil 1,400,000 5,600,000 89,600,000
3 Lava 1,800,000 7,200,000 115,200,000
4 Natural Gas 1,400,000 5,600,000 89,600,000
4 1 Sulfuric Acid 784,000 3,136,000 50,176,000
2 Molten Iron 896,000 3,584,000 57,344,000
3 Oil 1,400,000 5,600,000 89,600,000
4 Heavy Oil 1,792,000 7,168,000 114,688,000
5 Molten Lead 896,000 3,584,000 57,344,000
6 Raw Oil 1,400,000 5,600,000 89,600,000
7 Light Oil 780,000 3,120,000 49,920,000
8 CO2 Gas 1,680,000 6,720,000 107,520,000
5 1 Carbon Monoxide 4,480,000 17,920,000 286,720,000
2 Helium-3 2,800,000 11,200,000 179,200,000
3 Salt Water 2,800,000 11,200,000 179,200,000
4 Helium 1,400,000 5,600,000 89,600,000
5 Liquid Oxygen 896,000 3,584,000 57,344,000
6 Neon 32,000 128,000 2,048,000
7 Argon 32,000 128,000 2,048,000
8 Krypton 8,000 32,000 512,000
9 Methane 1,792,000 7,168,000 114,688,000
10 Hydrogen Sulfide 392,000 1,568,000 25,088,000
11 Ethane 1,194,000 4,776,000 76,416,000
6 1 Deuterium 1,568,000 6,272,000 100,352,000
2 Tritium 240,000 960,000 15,360,000
3 Ammonia 240,000 960,000 15,360,000
4 Xenon 16,000 64,000 1,024,000
5 Ethylene 1,792,000 7,168,000 114,688,000
7 1 Hydrofluoric Acid 672,000 2,688,000 32,008,000
2 Fluorine 1,792,000 7,168,000 114,688,000
3 Nitrogen 1,792,000 7,168,000 114,688,000
4 Oxygen 1,792,000 7,168,000 114,688,000
8 1 Hydrogen 1,568,000 6,272,000 100,352,000
2 Liquid Air 875,000 3,500,000 56,000,000
3 Molten Copper 672,000 2,688,000 32,008,000
4 Unknown Liquid 672,000 2,688,000 32,008,000
5 Distilled Water 17,920,000 71,680,000 1,146,880,000
6 Radon 64,000 256,000 4,096,000
7 Molten Tin 672,000 2,688,000 32,008,000

Other Helpful Commands

To list all of the files installed on the computer, enter

ls

To edit (or create) a new file, enter

edit <filename>.lua

To remove any one file installed on the computer, enter

rm <filename>

To view an entire message regardless of how long it may be, enter

<program> 2>/errors.log
edit /errors.log

Thanks

Huge thanks to Recursive Pineapple for their initial implementation and inspiration! Also big thanks to samsonsin for helping improve the script and designing the median + offset version!