Some time ago I wrote some skeleton code designed to make some simple widgets work properly in LUA. I then turned it into a factory control system. Since then I’ve advanced that to a control system using wireless modems and remote terminals so I can have one multi-screen factory computer control remote computers that do things like monitor reactors or control spawners etc. Here is the LUA that does this in my currently simple factory in case someone finds it useful.
Main Control Computer
-- Modem Port Numbers
local version = "0.2";
local myPort = 101; -- main factory control computer
local reactPort = 102; -- reactor main control
local spawnPort = 103; -- spawn room
local turbine0Port = 104; -- turbine 0
local running = true;
local modem = peripheral.wrap("right");
local mon = "";
local HB = 5;
local lastPowerTot = 0;
local buttons = {}
local window = {}
local dirty = false;
local ReactorCycles = 0;
function round(n,dec)
local shift = 10 ^ dec;
local ret = math.floor( n * shift ) / shift;
return ret;
end
----- Button Maintainence -----
function updateButtonCalc( button )
button["tlen"] = string.len( button["text"] )
button["height"] = button["pady"] * 2 + 1
button["width"] = button["padx"] * 2 + button["tlen"]
button["xend"] = button["origx"] + button["width"] - 1
button["yend"] = button["origy"] + button["height"] - 1
button["tX"] = button["origx"] + (math.floor( button["width"] / 2 ) - math.floor( button["tlen"] / 2 )) - 1 + button["padx"]
button["tY"] = button["origy"] + math.floor( button["height"] / 2 )
end
function addButton( name, text, func, state, origx, origy, padx, pady, fore, bact, binact )
buttons[name] = {}
buttons[name]["text"] = text
buttons[name]["state"] = state
buttons[name]["defstate"] = state
buttons[name]["func"] = func
buttons[name]["origx"] = origx
buttons[name]["origy"] = origy
buttons[name]["padx"] = padx
buttons[name]["pady"] = pady
buttons[name]["foreground"] = fore
buttons[name]["bact"] = bact
buttons[name]["binact"] = binact
updateButtonCalc(buttons[name])
end
function addSimpleButton( name, text, func, origx, origy )
addButton( name, text, func, false, origx, origy, 1, 1, colors.white, colors.cyan, colors.red )
end
function addInfoButton( name, text, origx, origy )
addButton( name, text, noop, false, origx, origy, 1, 0, colors.white, colors.black, colors.black)
end
function drawButton( bData )
local tLen = bData["tlen"]
local h = bData["height"]
local w = bData["width"]
local xend = bData["xend"]
local yend = bData["yend"]
local tY = bData["tY"]
local tX = bData["tX"]
if bData["state"] == true then
mon.setBackgroundColor( bData["bact"] )
else
mon.setBackgroundColor( bData["binact"] )
end
mon.setTextColor( bData["foreground"] )
for i = bData["origy"], yend do
mon.setCursorPos( bData["origx"], i )
if i == tY then
for j = bData["origx"], (xend - tLen + 1) do
if j == tX then
mon.write(bData["text"])
else
mon.write(" ")
end
end
else
for i = bData["origx"], xend do
mon.write(" ")
end
end
end
mon.setBackgroundColor( colors.black )
end
function drawButtons()
for name,data in pairs(buttons) do
drawButton( data )
end
end
function heading( yoff, text )
w, h = mon.getSize()
mon.setCursorPos( (w-string.len(text))/2+1, yoff )
mon.write( text )
end
function mainDrawHook()
heading(1, "Factory Control v"..version)
handleReactorAuto()
end
local drawHook = mainDrawHook;
local nextClear = 0
function drawScreen( )
if nextClear < os.clock() or dirty == true then
mon.clear()
nextClear = os.clock() + 10
dirty = false;
end
drawHook()
drawButtons()
end
function toggleButton(name)
buttons[name]["state"] = not buttons[name]["state"]
end
--[[ for alignment, horiz and vert can have 3 states:
0 no change
1 left/top
2 right/bottom
3 center
]]--
function alignButtonInBox( button, box, horiz, vert )
local x = box["o"]["x"]
local y = box["o"]["y"]
if horiz > 0 then
if horiz == 1 then
button["origx"] = x
elseif horiz == 2 then
x = (x + box["w"] - 1) - button["width"]
button["origx"] = x
elseif horiz == 3 then
x = x + (box["w"] / 2)
button["origx"] = x
end
end
if vert > 0 then
if vert == 1 then
button["origy"] = y
elseif vert == 2 then
y = (y + box["h"] - 1) - button["height"]
button["origy"] = y
elseif vert == 3 then
y = y + (box["h"] / 2)
button["origy"] = y
end
end
updateButtonCalc( button )
end
function checkButtonHit( x, y )
for name, data in pairs(buttons) do
if y >= data["origy"] and y <= data["yend"] then
if x >= data["origx"] and x <= data["xend"] then
data["func"](name)
end
end
end
end
function shutdownButtons()
for name, data in pairs(buttons) do
if data["state"] ~= data["defstate"] then
data["func"](name)
end
end
end
--- INIT ---
function initMon(side)
print("Initialize monitor..");
mon = peripheral.wrap(side)
--mon.setTextScale(1)
mon.setBackgroundColor(colors.black)
mon.setTextColor(colors.white)
window["o"] = {}
window["o"]["x"] = 1
window["o"]["y"] = 1
window["w"], window["h"] = mon.getSize()
print(" size="..window["w"].."x"..window["h"]);
end
-- open our modem
function initModem()
modem.open( myPort );
end
function fetchValueFromRemote(remote, value)
modem.transmit( remote, myPort, value);
local timeout = os.startTimer(HB);
local retval = "i/o err";
local waiting = 1;
while waiting == 1 do
local event, modemSide, senderchannel,
replyPort, message, senderDistance = os.pullEvent();
if event == "modem_message" then
waiting = 0;
retval = message;
elseif event == "timer" then
waiting = 0;
end
end
os.cancelTimer(timeout);
return(retval);
end
function fetchValueFromReactor(value)
local retval = fetchValueFromRemote( reactPort, value);
return(retval);
end
function handleEvent()
local timeout = os.startTimer(HB);
local e, side, x, y = os.pullEvent();
if e == "monitor_touch" then
checkButtonHit( x, y )
end
os.cancelTimer(timeout)
end
function noop()
print("noop");
end
function toggleReactorAuto(name)
local button = buttons[name]
if button["state"] == false then
button["state"] = true
fetchValueFromReactor("keeppow");
else
button["state"] = false
fetchValueFromReactor("keeppow");
end
end
local nextRename = 0;
function toggleCows(name)
local button = buttons[name]
if button["state"] == false then
button["state"] = true
fetchValueFromRemote(spawnPort, "tcow");
else
button["state"] = false
fetchValueFromRemote(spawnPort, "tcow");
end
end
function toggleBlazes(name)
local button = buttons[name]
if button["state"] == false then
button["state"] = true
fetchValueFromRemote(spawnPort, "tblaze");
else
button["state"] = false
fetchValueFromRemote(spawnPort, "tblaze");
end
end
function toggleGrinder(name)
local button = buttons[name]
if button["state"] == false then
button["state"] = true
fetchValueFromRemote(spawnPort, "toverride");
else
button["state"] = false
fetchValueFromRemote(spawnPort, "toverride");
end
end
function waitForRemote()
mon.clear()
for i=1,5 do
heading(10, "Waiting for remote computers..."..tostring(5-i));
sleep(1);
end
end
function queryRemoteButtonStates()
if fetchValueFromRemote( spawnPort, "cows" ) ~= 0 then
buttons["spawnCows"]["state"] = true;
else
buttons["spawnCows"]["state"] = false;
end
if fetchValueFromRemote( spawnPort, "blazes" ) ~= 0 then
buttons["spawnBlazes"]["state"] = true;
else
buttons["spawnBlazes"]["state"] = false;
end
if fetchValueFromRemote( spawnPort, "override" ) ~= 0 then
buttons["overrideGrind"]["state"] = true;
else
buttons["overrideGrind"]["state"] = false;
end
end
function resetRemotes(name)
fetchValueFromReactor("reboot");
fetchValueFromRemote(spawnPort, "reboot");
waitForRemote()
queryRemoteButtonStates()
end
function reactorControl(name)
mon.clear();
initReactButtons()
drawHook = reactDrawHook;
end
function initMainButtons()
HB = 5;
buttons = {}
-- add the reactor auto button and set its state
addSimpleButton( "reactor", "Reactor Control", reactorControl, 1, 3 )
buttons["reactor"]["bact"] = colors.green;
buttons["reactor"]["binact"] = colors.green;
addSimpleButton( "spawnCows", "Spawn Cows", toggleCows, 1, 7 );
addSimpleButton( "spawnBlazes", "Spawn Blazes", toggleBlazes, 1, 11 );
addSimpleButton( "overrideGrind", "Grinder Ovrd ", toggleGrinder, 1, 15 );
-- second column
-- local rbl = buttons["reactor"]["xend"] + 1;
-- infoBox["o"] = {}
-- infoBox["o"]["x"] = rbl;
-- infoBox["o"]["y"] = 3;
-- infoBox["w"] = 18;
-- infoBox["h"] = 12;
-- addInfoButton( "rinfo", "-Reactor Info-", rbl, 3 )
-- alignButtonInBox(buttons["rinfo"], infoBox, 3, 0 )
-- addInfoButton( "powlvl", "Power: "..fetchValueFromReactor("power"), rbl, 4 )
-- alignButtonInBox(buttons["powlvl"], infoBox, 3, 0 )
-- power button
addSimpleButton( "power", "(|)", powerDown, 1, 3 )
buttons["power"]["binact"] = colors.purple
alignButtonInBox( buttons["power"], window, 2, 0 )
addSimpleButton( "resetremote", "(8)", resetRemotes, 1, 7 )
buttons["resetremote"]["binact"] = colors.purple
alignButtonInBox( buttons["resetremote"], window, 2, 0 )
queryRemoteButtonStates()
end
function backToMain()
mon.clear()
initMainButtons()
drawHook = mainDrawHook;
end
----------------
-- Reactor Control Screen
----------------
local turbineVitals = {}
local reactorVitals = {}
local rInfoBox = {}
local trautorun = true;
function drawRInfo()
if nextRename < os.clock() then
nextRename = os.clock() + 2;
local pcolor = colors.white;
if turbineVitals["power"] > 500000 then
pcolor = colors.green
elseif turbineVitals["power"] > 100000 then
pcolor = colors.yellow
else
pcolor = colors.red
end
local cX = rInfoBox["o"]["x"]
local cY = rInfoBox["o"]["y"]
-- heading --
mon.setTextColor(colors.cyan)
mon.setCursorPos(cX, cY)
mon.write("Turbine Info");
-- state
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Active: ");
if turbineVitals["active"] == true then
mon.setTextColor(colors.green);
mon.write("yes");
else
mon.setTextColor(colors.red);
mon.write("no");
end
-- coils (note - same line as state )
mon.setTextColor(colors.white);
mon.write(" Coils On: ");
if turbineVitals["coilson"] == true then
mon.setTextColor(colors.green);
mon.write("yes");
else
mon.setTextColor(colors.red);
mon.write("no");
end
-- power --
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Power Stored: ");
mon.setTextColor(pcolor);
mon.write(round(turbineVitals["power"], 2).." rf");
-- power --
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Generating: ");
mon.setTextColor(colors.pink);
mon.write(round(turbineVitals["elt"], 2).." rf/t");
-- Rotor --
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Rotor Speed: ");
local rcolor = colors.red;
if (turbineVitals["speed"] > 850 and turbineVitals["speed"] < 950) or (turbineVitals["speed"] > 1750 and turbineVitals["speed"] < 1850) then
rcolor = colors.green;
elseif (turbineVitals["speed"] > 750 and turbineVitals["speed"] < 1050) or (turbineVitals["speed"] > 1650 and turbineVitals["speed"] < 1950) then
rcolor = colors.yellow;
end
mon.setTextColor(rcolor);
mon.write(round(turbineVitals["speed"], 2).." rpm");
-- Reactor Info --
cY = cY + 2;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.cyan)
mon.setCursorPos(cX, cY)
mon.write("Reactor Info");
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Active: ");
if reactorVitals["active"] == true then
mon.setTextColor(colors.green);
mon.write("yes");
else
mon.setTextColor(colors.red);
mon.write("no");
end
-- Fuel
cY = cY + 1
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Fuel Eff: ");
local fcolor = colors.red;
if reactorVitals["fuelreact"] > 350 then
fcolor = colors.green;
elseif reactorVitals["fuelreact"] > 250 then
fcolor = colors.yellow;
elseif reactorVitals["fuelreact"] > 150 then
fcolor = colors.brown;
end
mon.setTextColor(fcolor);
mon.write(round(reactorVitals["fuelreact"], 2).."%");
mon.setTextColor(colors.white);
mon.write(" Burn: ");
mon.setTextColor(colors.yellow);
--local burn = math.floor(reactorVitals["fuellt"] * 1000) / 1000;
local burn = round(reactorVitals["fuellt"], 3);
mon.write(burn.." mb/t");
-- Rods
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setCursorPos(cX, cY);
mon.setTextColor(colors.white);
mon.write("Rod Depth: ");
mon.setTextColor(colors.magenta);
mon.write( reactorVitals["rodlevel"].."%");
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setCursorPos(cX, cY);
mon.setTextColor(colors.white);
mon.write("Runtime: ");
mon.setTextColor(colors.lightBlue);
mon.write(round(ReactorCycles/5,2).." s");
end
end
function handleReactorAuto()
local result = fetchValueFromRemote(turbine0Port, "vitals");
turbineVitals = textutils.unserialize(result);
result = fetchValueFromReactor("vitals");
reactorVitals = textutils.unserialize(result);
if trautorun == true then
if turbineVitals["power"] < 10000 then
ReactorCycles = ReactorCycles + 1;
print("turning on power system");
fetchValueFromReactor("activate");
fetchValueFromRemote(turbine0Port, "activate");
fetchValueFromRemote(turbine0Port, "coilon");
elseif turbineVitals["power"] > 950000 then
print("turning off power system");
fetchValueFromReactor("deactivate");
fetchValueFromRemote(turbine0Port, "deactivate");
fetchValueFromRemote(turbine0Port, "coiloff");
end
end
end
function reactDrawHook()
heading(1, "Reactor Control v"..version);
handleReactorAuto()
resetReactButtons()
drawRInfo()
end
function tCoils(name)
if turbineVitals["coilson"] == true then
buttons["toggleCoils"]["text"] = "Coils";
buttons["toggleCoils"]["state"] = false;
fetchValueFromRemote(turbine0Port, "coiloff");
else
buttons["toggleCoils"]["text"] = "Coils";
buttons["toggleCoils"]["state"] = true;
fetchValueFromRemote(turbine0Port, "coilon");
end
updateButtonCalc(buttons["toggleCoils"])
dirty = true
end
function tReact(name)
if reactorVitals["active"] == true then
buttons[name]["state"] = false;
fetchValueFromReactor("deactivate");
else
buttons[name]["state"] = true;
fetchValueFromReactor("activate");
end
updateButtonCalc(buttons[name]);
dirty = true
end
function tTurb(name)
if turbineVitals["active"] == true then
buttons[name]["state"] = false;
fetchValueFromRemote(turbine0Port, "deactivate");
else
buttons[name]["state"] = true;
fetchValueFromRemote(turbine0Port, "activate");
end
updateButtonCalc(buttons[name]);
dirty = true
end
function resetReactButtons()
if turbineVitals["coilson"] == true then
buttons["toggleCoils"]["state"] = true;
updateButtonCalc(buttons["toggleCoils"])
else
buttons["toggleCoils"]["state"] = false;
updateButtonCalc(buttons["toggleCoils"])
end
if turbineVitals["active"] == true then
buttons["tturb"]["state"] = true;
updateButtonCalc(buttons["tturb"])
else
buttons["tturb"]["state"] = false;
updateButtonCalc(buttons["tturb"])
end
if reactorVitals["active"] == true then
buttons["treact"]["state"] = true;
updateButtonCalc(buttons["treact"]);
else
buttons["treact"]["state"] = false;
updateButtonCalc(buttons["treact"]);
end
if trautorun == true then
buttons["trauto"]["state"] = true;
updateButtonCalc(buttons["trauto"]);
else
buttons["trauto"]["state"] = false;
updateButtonCalc(buttons["trauto"]);
end
dirty = true
end
function tRAuto(name)
if trautorun == false then
print("toggling autorun to true");
trautorun = true
else
truatorun = false;
print("toggling autorun to false");
end
end
function initReactButtons()
HB = 5;
buttons = {}
addSimpleButton("back", "Back", backToMain, 1, 3);
buttons["back"]["bact"] = colors.green;
buttons["back"]["binact"] = colors.green;
-- initialize the vitals for the reactor so we can use them
local result = fetchValueFromRemote(turbine0Port, "vitals")
turbineVitals = textutils.unserialize(result)
result = fetchValueFromReactor("vitals");
reactorVitals = textutils.unserialize(result);
local rbl = buttons["back"]["xend"] + 4;
rInfoBox["o"] = {}
rInfoBox["o"]["x"] = rbl;
rInfoBox["o"]["y"] = 3;
rInfoBox["w"] = 18;
rInfoBox["h"] = 12;
addSimpleButton("toggleCoils", "Coils", tCoils, rbl, 1);
alignButtonInBox(buttons["toggleCoils"], window, 0, 2);
if turbineVitals["coilson"] == true then
buttons["toggleCoils"]["text"] = "Coils";
buttons["toggleCoils"]["state"] = true;
updateButtonCalc(buttons["toggleCoils"])
end
addSimpleButton("treact", "Reactor", tReact, buttons["toggleCoils"]["origx"] + buttons["toggleCoils"]["width"] + 2,
buttons["toggleCoils"]["origy"]);
addSimpleButton("tturb", "Turbine", tTurb, buttons["treact"]["origx"] + buttons["treact"]["width"] + 2,
buttons["treact"]["origy"]);
addSimpleButton("trauto", "Auto", tRAuto, buttons["tturb"]["origx"] + buttons["tturb"]["width"] + 2,
buttons["tturb"]["origy"]);
end
----------------
-- End Reactor Control
----------------
function powerDown()
running = 0;
end
local reactorPower = 0;
local ppt = 0;
local burnRate = 0;
-------
-- Startup/Runtime
-------
initMon("top")
initModem()
waitForRemote()
mon.clear()
initMainButtons()
while running == true
do
drawScreen()
handleEvent()
end
mon.clear();
Reactor Control
local myPort = 102;
local running = true;
local modem = peripheral.wrap("left");
local reactor = peripheral.wrap("BigReactors-Reactor_0");
local HB = 5;
local doReboot = 0;
modem.open( myPort );
while running == true
do
local timeout = os.startTimer(HB);
local event, modemSide, senderchannel,
replyPort, message, senderDistance = os.pullEvent();
local result;
-- values
local energyStored = reactor.getEnergyStored()
local energyPLT = reactor.getEnergyProducedLastTick()
local vitals = {};
vitals["active"] = reactor.getActive();
vitals["nrods"] = reactor.getNumberOfControlRods();
vitals["splt"] = reactor.getHotFluidProducedLastTick();
vitals["coolantvol"] = reactor.getCoolantAmount();
vitals["steamvol"] = reactor.getHotFluidAmount();
vitals["fuelreact"] = reactor.getFuelReactivity();
vitals["fuellt"] = reactor.getFuelConsumedLastTick();
vitals["rodlevel"] = reactor.getControlRodLevel(0);
if event == "modem_message" then
if message == "power" then
print("sending: "..energyStored)
result = energyStored;
elseif message == "ppt" then
result = energyPLT;
print("energy/tick: "..energyPLT)
elseif message == "quit" then
result = 0;
running = false;
elseif message == "keeppow" then
keepPowered = not keepPowered;
result = keepPowered;
local st = "on"
if keepPowered then
st = "off"
end
print("reactor-auto: "..st)
elseif message == "qkeeppow" then
result = keepPowered;
elseif message == "vitals" then
result = textutils.serialize(vitals);
elseif message == "reboot" then
doReboot = 1;
result = 0;
elseif message == "activate" then
reactor.setActive(true);
result = 0;
elseif message == "deactivate" then
reactor.setActive(false);
result = 0;
else
result = -1;
end
modem.transmit(replyPort, myPort, result);
end
os.cancelTimer(timeout);
if doReboot == 1 then
reactor.setActive(false);
os.reboot();
end
end
Turbine Control
local myPort = 104;
local running = true;
local modem = peripheral.wrap("left");
local reactor = peripheral.wrap("BigReactors-Turbine_0");
local HB = 5;
local doReboot = 0;
modem.open( myPort );
while running == true
do
local timeout = os.startTimer(HB);
local event, modemSide, senderchannel,
replyPort, message, senderDistance = os.pullEvent();
local result;
-- values
local energyStored = reactor.getEnergyStored()
local energyPLT = reactor.getEnergyProducedLastTick()
local vitals = {}
vitals["active"] = reactor.getActive()
vitals["power"] = reactor.getEnergyStored()
vitals["speed"] = reactor.getRotorSpeed()
vitals["rate"] = reactor.getFluidFlowRate()
vitals["elt"] = reactor.getEnergyProducedLastTick()
vitals["coilson"] = reactor.getInductorEngaged()
if event == "modem_message" then
if message == "power" then
print("sending: "..energyStored)
result = energyStored;
elseif message == "ppt" then
result = energyPLT;
print("energy/tick: "..energyPLT)
elseif message == "quit" then
result = 0;
running = false;
elseif message == "keeppow" then
keepPowered = not keepPowered;
result = keepPowered;
local st = "on"
if keepPowered then
st = "off"
end
print("reactor-auto: "..st)
elseif message == "vitals" then
result = textutils.serialize(vitals);
elseif message == "reboot" then
doReboot = 1;
result = 0;
elseif message == "coilon" then
reactor.setInductorEngaged(true);
elseif message == "coiloff" then
reactor.setInductorEngaged(false);
elseif message == "activate" then
reactor.setActive(true);
elseif message == "deactivate" then
reactor.setActive(false);
else
result = -1;
end
modem.transmit(replyPort, myPort, result);
end
os.cancelTimer(timeout);
if doReboot == 1 then
reactor.setActive(false);
os.reboot();
end
end
Spawner Control
-- Ports
local myPort = 103;
local running = true;
local modem = peripheral.wrap("right");
local HB = 60;
local cowSide = "bottom";
local blazeSide = "left";
local overrideSide = "back";
local doReboot = 0;
modem.open(myPort);
while running == true
do
local timeout = os.startTimer(HB);
local e, s, sChan, rPort, msg, sDist = os.pullEvent();
local tb;
local result = 0;
if e == "modem_message" then
if msg == "tcow" then
tb = not redstone.getOutput(cowSide);
redstone.setOutput(cowSide, tb);
print("set cows: "..tostring(tb));
elseif msg == "tblaze" then
tb = not redstone.getOutput(blazeSide);
redstone.setOutput(blazeSide, tb);
print("set blazes: "..tostring(tb));
elseif msg == "toverride" then
tb = not redstone.getOutput(overrideSide);
redstone.setOutput(overrideSide, tb);
print("set toverride: "..tostring(tb));
elseif msg == "cows" then
if redstone.getOutput(cowSide) then
result = 1;
end
elseif msg == "blazes" then
if redstone.getOutput(blazeSide) then
result = 1;
end
elseif msg == "override" then
if redstone.getOutput(overrideSide) then
result = 1;
end
elseif msg == "reboot" then
doReboot = 1
end
modem.transmit(rPort, myPort, result);
end
os.cancelTimer(timeout);
if doReboot == 1 then
os.reboot();
end
end