2023-09-07 18:33:16 +00:00
--[[
This program should be installed on a portable computer with a wireless
modem , to act as a routing beacon in conjunction with managed switches .
2023-09-13 17:49:53 +00:00
It also serves as the GUI that users of the system interact with .
2023-09-07 18:33:16 +00:00
] ] --
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 ( )
2023-09-07 18:33:16 +00:00
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-07 18:33:16 +00:00
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
2023-09-07 18:33:16 +00:00
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
2023-09-07 18:33:16 +00:00
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