cc-rail-router/switcher.lua

191 lines
7.1 KiB
Lua

--[[
This program is to be installed on a computer that controls a single rail
junction. As a player with a portable computer approaches, that portable
computer will be sending out a signal containing their route, and this switch
will decode it and make any adjustments it needs to.
]]--
local CONFIG_FILE = "switch_config.tbl"
local CHANNEL = 0
local config = nil
local modem = peripheral.wrap("top") or error("Missing top modem")
if not modem.isWireless() then error("Wireless modem required") end
modem.open(CHANNEL)
term.clear()
term.setCursorPos(1, 1)
print("Receiving routing commands on channel 0")
-- Series of guided inputs for building a configuration file from user input.
local function configSetupWizard()
local cfg = {range = 32, switches = {}}
term.clear()
term.setCursorPos(1, 1)
print("Switch Controller Config Setup Wizard")
print("-------------------------------------")
print("What range (blocks) does this switch have? Defaults to 32.")
local rangeStr = io.read()
if rangeStr then
local rangeInt = tonumber(rangeStr)
if rangeInt == nil or rangeInt < 1 or rangeInt > 128 then
print("Invalid range value. Should be a positive integer number. Got "..rangeStr)
return nil
end
cfg.range = rangeInt
end
print("How many switch configurations are there? Usually 1 for each possible path of travel.")
local switchCountStr = io.read()
if not switchCountStr then
print("Invalid switch configuration count.")
return nil
end
local switchCount = tonumber(switchCountStr)
if switchCount == nil or switchCount < 2 or switchCount > 32 then
print("Invalid switch configuration count. Expected a positive integer between 2 and 32.")
return nil
end
for i = 1, switchCount do
local sw = {from = nil, to = nil, controls = {}}
print("Switch Configuration #"..i..":")
print("What is name of the branch traffic comes from?")
sw.from = io.read()
print("What is the name of the branch traffic goes to?")
sw.to = io.read()
print("How many control points are needed for this switch configuration?")
local controlCountStr = io.read()
local controlCount = 0
if controlCountStr then controlCount = tonumber(controlCountStr) end
if not controlCount or controlCount < 1 then
print("Invalid control point count. Expected a positive integer.")
return nil
end
for c = 1, controlCount do
local ctl = {type = "redstone", side = "top", state = true}
print(" Control Point #"..c..":")
print(" What type of control is this?")
print(" 1. redstone")
print(" 2. redstoneIntegrator")
local typeStr = io.read()
if not typeStr or typeStr == "1" or typeStr == "redstone" then
ctl.type = "redstone"
print(" What side is redstone output on?")
ctl.side = io.read()
print(" What state should the output be? [T/F]")
local stateStr = io.read()
ctl.state = stateStr == "T" or stateStr == "True" or stateStr == "t" or stateStr == "true"
elseif typeStr == "2" or typeStr == "redstoneIntegrator" then
ctl.type = "redstoneIntegrator"
print(" What is the peripheral name?")
ctl.name = io.read()
print(" What side is output on?")
ctl.side = io.read()
print(" What state should the output be? [T/F]")
local stateStr = io.read()
ctl.state = stateStr == "T" or stateStr == "True" or stateStr == "t" or stateStr == "true"
else
print(" Unsupported control type.")
return nil
end
table.insert(sw.controls, ctl)
end
table.insert(cfg.switches, sw)
end
local f = io.open(CONFIG_FILE, "w")
f:write(textutils.serialize(cfg))
f:close()
return cfg
end
local function loadConfig()
if fs.exists(CONFIG_FILE) then
local f = io.open(CONFIG_FILE, "r")
local cfg = textutils.unserialize(f:read("*a"))
f:close()
print("Loaded configuration from file:")
print(" "..tostring(#cfg.switches).." switch configurations.")
print(" "..tostring(cfg.range).." block range.")
return cfg
else
print("File "..CONFIG_FILE.." doesn't exist. Start setup wizard? [y/n]")
local response = io.read()
if response == "y" or response == "Y" or response == "yes" then
return configSetupWizard()
else
return nil
end
end
end
local function findSwitchConfiguration(cfg, from, to)
if from == nil or to == nil then return nil end
for _, sw in pairs(cfg.switches) do
if sw.from == from and sw.to == to then return sw end
end
return nil
end
local function isSwitchConfigurationActive(sw)
for _, control in pairs(sw.controls) do
if control.type == "redstone" then
local state = redstone.getOutput(control.side)
if state ~= control.state then return false end
elseif control.type == "redstoneIntegrator" then
local state = peripheral.call(control.name, "getOutput", control.side)
if state ~= control.state then return false end
else
error("Invalid control type: "..control.type)
end
end
return true
end
local function activateSwitchConfiguration(sw)
print("Activating switch: FROM="..sw.from..", TO="..sw.to)
for _, control in pairs(sw.controls) do
if control.type == "redstone" then
redstone.setOutput(control.side, control.state)
elseif control.type == "redstoneIntegrator" then
peripheral.call(control.name, "setOutput", control.side, control.state)
else
error("Invalid control type: "..control.type)
end
end
end
-- Handles incoming rail messages that consist of a list of branch names
-- that the user would like to traverse.
local function handleModemMsg(replyChannel, msg)
if type(msg) == "string" and msg == "PING" then
modem.transmit(replyChannel, CHANNEL, "PONG")
return
end
-- Ignore invalid messages.
if not msg or #msg < 2 then return end
-- Find the switch configuration(s) that pertain to this route.
for i = 1, #msg - 1 do
local sw = findSwitchConfiguration(config, msg[i], msg[i+1])
if sw and not isSwitchConfigurationActive(sw) then
activateSwitchConfiguration(sw)
end
end
end
config = loadConfig()
if not config then
print("Unable to load configuration. Exiting.")
return
end
while true do
local event, p1, p2, p3, p4, p5 = os.pullEvent()
if event == "modem_message" then
local channel = p2
local replyChannel = p3
local msg = p4
local dist = p5
if channel == CHANNEL and dist ~= nil and dist < config.range and msg ~= nil then
handleModemMsg(replyChannel, msg)
end
end
end