cc-rail-router/router.lua

277 lines
9.9 KiB
Lua
Raw Permalink Normal View History

--[[
This program should be installed on a portable computer with a wireless
modem, to act as a routing beacon in conjunction with managed switches.
It also serves as the GUI that users of the system interact with.
]]--
2023-09-08 14:49:17 +00:00
local SWITCH_CHANNEL = 45450
local STATION_BROADCAST_CHANNEL = 45451
2023-09-14 15:09:26 +00:00
local SERVER_CHANNEL = 45452
2023-09-08 14:49:17 +00:00
local MY_CHANNEL = 45460
local g = require("simple-graphics")
local W, H = term.getSize()
local modem = peripheral.wrap("back") or error("Missing modem.")
2023-09-08 14:49:17 +00:00
modem.open(MY_CHANNEL) -- Listen for messages directed to this device.
modem.open(STATION_BROADCAST_CHANNEL) -- Listen for station broadcasts.
2023-09-08 14:49:17 +00:00
local function serializeRoutePath(path)
local str = ""
for i, segment in pairs(path) do
str = str .. segment
if i < #path then str = str .. "," end
end
return str
end
2023-09-07 19:38:39 +00:00
local function broadcastRoute(route)
while true do
2023-09-08 14:49:17 +00:00
modem.transmit(SWITCH_CHANNEL, MY_CHANNEL, route)
2023-09-07 19:38:39 +00:00
os.sleep(0.5)
end
end
2023-09-14 23:06:16 +00:00
local function isValidStationInfo(msg)
return msg ~= nil and
msg.name ~= nil and type(msg.name) == "string" and
msg.range ~= nil and type(msg.range) == "number" and
msg.displayName ~= nil and type(msg.displayName) == "string"
end
2023-09-07 19:38:39 +00:00
-- Repeats until we are within range of a station that's sending out its info.
local function waitForStation(stationName)
while true do
local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message")
2023-09-17 11:44:47 +00:00
if type(channel) == "number" and channel == STATION_BROADCAST_CHANNEL and isValidStationInfo(msg) and msg.name == stationName and dist ~= nil and msg.range >= dist then
2023-09-07 19:38:39 +00:00
return
end
end
end
2023-09-08 14:49:17 +00:00
local function listenForAnyStation()
while true do
local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message")
2023-09-17 11:44:47 +00:00
if type(channel) == "number" and channel == STATION_BROADCAST_CHANNEL and isValidStationInfo(msg) and dist ~= nil and msg.range >= dist then
2023-09-08 14:49:17 +00:00
os.queueEvent("rail_station_nearby", msg, dist)
end
end
end
local function waitForNoStation(targetName)
local lastPing = os.epoch()
while os.epoch() - lastPing < 5000 do
parallel.waitForAny(
function ()
2023-09-14 15:09:26 +00:00
local event, data, dist = os.pullEvent("rail_station_nearby")
if not targetName or targetName == data.name then
2023-09-08 14:49:17 +00:00
stationPresent = true
lastPing = os.epoch()
end
end,
function () os.sleep(3) end
)
end
end
local function waitForModemMessage(expectedReplyChannel, timeout)
local data = nil
parallel.waitForAny(
function ()
while true do
local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message")
2023-09-15 13:23:36 +00:00
if type(replyChannel) == "number" and replyChannel == expectedReplyChannel then
2023-09-08 14:49:17 +00:00
data = {}
data.channel = channel
data.replyChannel = replyChannel
data.msg = msg
data.dist = dist
return
end
end
end,
function () os.sleep(timeout) end
)
return data
end
2023-09-14 23:06:16 +00:00
local function drawLookingForStationScreen()
g.clear(term, colors.white)
g.drawText(term, 1, 1, "Looking for nearby station", colors.black, colors.yellow)
g.drawText(term, 1, 2, "Walk near a station to", colors.gray, colors.white)
g.drawText(term, 1, 3, "see available routes.", colors.gray, colors.white)
end
local function drawStationFoundScreen(stationName)
g.clear(term, colors.white)
g.drawXLine(term, 1, W, 1, colors.lightBlue)
g.drawText(term, 1, 1, "Found a station!", colors.black, colors.lightBlue)
g.drawText(term, 1, 3, stationName, colors.blue, colors.white)
g.drawText(term, 1, 5, "Fetching routes...", colors.gray, colors.white)
end
local function drawDestinationsChoiceScreen(choices)
g.clear(term, colors.white)
g.drawXLine(term, 1, W, 1, colors.blue)
g.drawText(term, 1, 1, "Destinations", colors.white, colors.blue)
g.drawText(term, W-3, 1, "Quit", colors.white, colors.red)
for i, choice in pairs(choices) do
local y = i + 1
local bg = colors.white
if i % 2 == 0 then bg = colors.lightGray end
g.drawXLine(term, 1, W, y, bg)
g.drawText(term, 1, y, i..". "..choice, colors.black, bg)
end
end
local function drawErrorPage(errorMsg)
g.clear(term, colors.white)
g.drawXLine(term, 1, W, 1, colors.red)
g.drawText(term, 1, 1, "Error", colors.white, colors.red)
term.setCursorPos(1, 2)
term.setTextColor(colors.black)
term.setBackgroundColor(colors.white)
print(errorMsg)
local x, y = term.getCursorPos()
term.setCursorPos(1, y + 1)
print("Click to dismiss")
parallel.waitForAny(
function () os.sleep(5) end,
function () os.pullEvent("mouse_click") end
)
end
2023-09-17 11:44:47 +00:00
local function drawGoingToSleepScreen()
g.clear(term, colors.white)
term.setTextColor(colors.gray)
term.setCursorPos(1, 1)
print("Going to sleep. Click again to wake me.")
os.sleep(2)
os.shutdown()
end
2023-09-08 14:49:17 +00:00
local function handleNearbyStation()
while true do
2023-09-14 23:06:16 +00:00
drawLookingForStationScreen()
2023-09-17 11:44:47 +00:00
local event, stationData, dist = nil
parallel.waitForAny(
function () event, stationData, dist = os.pullEvent("rail_station_nearby") end,
function ()
os.sleep(10)
drawGoingToSleepScreen()
end
)
if not stationData then return end
2023-09-14 23:06:16 +00:00
drawStationFoundScreen(stationData.displayName)
os.sleep(0.5)
modem.transmit(SERVER_CHANNEL, MY_CHANNEL, {command = "GET_ROUTES", startNode = stationData.name})
local response = waitForModemMessage(SERVER_CHANNEL, 3)
if response and response.msg.success then
local stations = response.msg.stations
local stationNames = {}
local stationIds = {}
for _, station in pairs(stations) do
table.insert(stationNames, station.displayName)
table.insert(stationIds, station.id)
2023-09-08 14:49:17 +00:00
end
2023-09-14 23:06:16 +00:00
drawDestinationsChoiceScreen(stationNames)
local destination = nil
-- Wait for user to choose destination, quit, or go away from station.
2023-09-08 14:49:17 +00:00
parallel.waitForAny(
function ()
while true do
local event, button, x, y = os.pullEvent("mouse_click")
if button == 1 then
if x >= W-3 and y == 1 then
2023-09-14 23:06:16 +00:00
return
elseif y > 1 and y - 1 <= #stationIds then
destination = stationIds[y-1]
2023-09-08 14:49:17 +00:00
return
end
end
end
end,
2023-09-14 15:09:26 +00:00
function () waitForNoStation(stationData.name) end
2023-09-08 14:49:17 +00:00
)
2023-09-14 23:06:16 +00:00
if destination ~= nil then
-- Fetch the whole route.
modem.transmit(SERVER_CHANNEL, MY_CHANNEL, {command = "ROUTE", startNode = stationData.name, endNode = destination})
local routeResponse = waitForModemMessage(SERVER_CHANNEL, 3)
if routeResponse and routeResponse.msg.success then
local routeEdgeIds = {}
for _, segment in pairs(routeResponse.msg.route) do
if segment.via then
table.insert(routeEdgeIds, segment.via)
end
end
os.queueEvent("rail_route_selected", {path = routeEdgeIds, destination = destination})
return
elseif routeResponse and routeResponse.msg.error then
drawErrorPage("Failed to get route: "..routeResponse.msg.error)
else
drawErrorPage("Failed to get route. Please contact an administrator if the issue persists.")
end
end
elseif response and response.msg.error then
drawErrorPage(response.msg.error)
else
drawErrorPage("Could not get a list of stations. Please contact an administrator if the issue persists.\n"..textutils.serialize(response, {compact=true}))
2023-09-08 14:49:17 +00:00
end
end
end
local function waitForRouteSelection()
while true do
parallel.waitForAny(
listenForAnyStation,
handleNearbyStation
)
local event, route = os.pullEvent("rail_route_selected")
2023-09-14 23:06:16 +00:00
if event and route then
2023-09-08 14:49:17 +00:00
return route
end
end
end
local args = {...}
2023-09-08 14:49:17 +00:00
if #args > 1 then
local route = args
print("Routing via command-line args:")
for _, branch in pairs(route) do
print(" "..branch)
end
2023-09-14 23:06:16 +00:00
broadcastRoute(route)
2023-09-08 14:49:17 +00:00
return
end
2023-09-08 14:49:17 +00:00
g.clear(term, colors.white)
g.drawTextCenter(term, W/2, H/2, "Rail Router", colors.black, colors.white)
g.drawTextCenter(term, W/2, H/2 + 2, "By Andrew", colors.gray, colors.white)
2023-09-17 11:44:47 +00:00
os.sleep(0.5)
2023-09-08 14:49:17 +00:00
while true do
local route = waitForRouteSelection()
g.clear(term, colors.white)
g.drawTextCenter(term, W/2, 2, "Broadcasting route...", colors.black, colors.white)
g.drawText(term, 1, 4, " Path:", colors.gray, colors.white)
for i, segment in pairs(route.path) do
local y = i + 4
g.drawText(term, 4, y, segment, colors.gray, colors.white)
end
g.drawText(term, W-3, 1, "Quit", colors.white, colors.red)
parallel.waitForAny(
function() broadcastRoute(route.path) end,
2023-09-14 23:06:16 +00:00
function() waitForStation(route.destination) end,
2023-09-08 14:49:17 +00:00
function() -- Listen for user clicks on the "Quit" button.
while true do
local event, button, x, y = os.pullEvent("mouse_click")
if button == 1 and x >= W-3 and y == 1 then
return
end
end
end
)
end