Advertisement
HandieAndy

router.lua

Sep 8th, 2023 (edited)
952
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 10.14 KB | None | 0 0
  1. --[[
  2. This program should be installed on a portable computer with a wireless
  3. modem, to act as a routing beacon in conjunction with managed switches.
  4. It also serves as the GUI that users of the system interact with.
  5. ]]--
  6. local SWITCH_CHANNEL = 45450
  7. local STATION_BROADCAST_CHANNEL = 45451
  8. local SERVER_CHANNEL = 45452
  9. local MY_CHANNEL = 45460
  10.  
  11. local g = require("simple-graphics")
  12. local W, H = term.getSize()
  13.  
  14. local modem = peripheral.wrap("back") or error("Missing modem.")
  15. modem.open(MY_CHANNEL) -- Listen for messages directed to this device.
  16. modem.open(STATION_BROADCAST_CHANNEL) -- Listen for station broadcasts.
  17.  
  18. local function serializeRoutePath(path)
  19.     local str = ""
  20.     for i, segment in pairs(path) do
  21.         str = str .. segment
  22.         if i < #path then str = str .. "," end
  23.     end
  24.     return str
  25. end
  26.  
  27. local function broadcastRoute(route)
  28.     while true do
  29.         modem.transmit(SWITCH_CHANNEL, MY_CHANNEL, route)
  30.         os.sleep(0.5)
  31.     end
  32. end
  33.  
  34. local function isValidStationInfo(msg)
  35.     return msg ~= nil and
  36.         msg.name ~= nil and type(msg.name) == "string" and
  37.         msg.range ~= nil and type(msg.range) == "number" and
  38.         msg.displayName ~= nil and type(msg.displayName) == "string"
  39. end
  40.  
  41. -- Repeats until we are within range of a station that's sending out its info.
  42. local function waitForStation(stationName)
  43.     while true do
  44.         local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message")
  45.         if type(channel) == "number" and channel == STATION_BROADCAST_CHANNEL and isValidStationInfo(msg) and msg.name == stationName and dist ~= nil and msg.range >= dist then
  46.             return
  47.         end
  48.     end
  49. end
  50.  
  51. local function listenForAnyStation()
  52.     while true do
  53.         local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message")
  54.         if type(channel) == "number" and channel == STATION_BROADCAST_CHANNEL and isValidStationInfo(msg) and dist ~= nil and msg.range >= dist then
  55.             os.queueEvent("rail_station_nearby", msg, dist)
  56.         end
  57.     end
  58. end
  59.  
  60. local function waitForNoStation(targetName)
  61.     local lastPing = os.epoch()
  62.     while os.epoch() - lastPing < 5000 do
  63.         parallel.waitForAny(
  64.             function ()
  65.                 local event, data, dist = os.pullEvent("rail_station_nearby")
  66.                 if not targetName or targetName == data.name then
  67.                     stationPresent = true
  68.                     lastPing = os.epoch()
  69.                 end
  70.             end,
  71.             function () os.sleep(3) end
  72.         )
  73.     end
  74. end
  75.  
  76. local function waitForModemMessage(expectedReplyChannel, timeout)
  77.     local data = nil
  78.     parallel.waitForAny(
  79.         function ()
  80.             while true do
  81.                 local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message")
  82.                 if type(replyChannel) == "number" and replyChannel == expectedReplyChannel then
  83.                     data = {}
  84.                     data.channel = channel
  85.                     data.replyChannel = replyChannel
  86.                     data.msg = msg
  87.                     data.dist = dist
  88.                     return
  89.                 end
  90.             end
  91.         end,
  92.         function () os.sleep(timeout) end
  93.     )
  94.     return data
  95. end
  96.  
  97. local function drawLookingForStationScreen()
  98.     g.clear(term, colors.white)
  99.     g.drawText(term, 1, 1, "Looking for nearby station", colors.black, colors.yellow)
  100.     g.drawText(term, 1, 2, "Walk near a station to", colors.gray, colors.white)
  101.     g.drawText(term, 1, 3, "see available routes.", colors.gray, colors.white)
  102. end
  103.  
  104. local function drawStationFoundScreen(stationName)
  105.     g.clear(term, colors.white)
  106.     g.drawXLine(term, 1, W, 1, colors.lightBlue)
  107.     g.drawText(term, 1, 1, "Found a station!", colors.black, colors.lightBlue)
  108.     g.drawText(term, 1, 3, stationName, colors.blue, colors.white)
  109.     g.drawText(term, 1, 5, "Fetching routes...", colors.gray, colors.white)
  110. end
  111.  
  112. local function drawDestinationsChoiceScreen(choices)
  113.     g.clear(term, colors.white)
  114.     g.drawXLine(term, 1, W, 1, colors.blue)
  115.     g.drawText(term, 1, 1, "Destinations", colors.white, colors.blue)
  116.     g.drawText(term, W-3, 1, "Quit", colors.white, colors.red)
  117.     for i, choice in pairs(choices) do
  118.         local y = i + 1
  119.         local bg = colors.white
  120.         if i % 2 == 0 then bg = colors.lightGray end
  121.         g.drawXLine(term, 1, W, y, bg)
  122.         g.drawText(term, 1, y, i..". "..choice, colors.black, bg)
  123.     end
  124. end
  125.  
  126. local function drawErrorPage(errorMsg)
  127.     g.clear(term, colors.white)
  128.     g.drawXLine(term, 1, W, 1, colors.red)
  129.     g.drawText(term, 1, 1, "Error", colors.white, colors.red)
  130.     term.setCursorPos(1, 2)
  131.     term.setTextColor(colors.black)
  132.     term.setBackgroundColor(colors.white)
  133.     print(errorMsg)
  134.     local x, y = term.getCursorPos()
  135.     term.setCursorPos(1, y + 1)
  136.     print("Click to dismiss")
  137.     parallel.waitForAny(
  138.         function () os.sleep(5) end,
  139.         function () os.pullEvent("mouse_click") end
  140.     )
  141. end
  142.  
  143. local function drawGoingToSleepScreen()
  144.     g.clear(term, colors.white)
  145.     term.setTextColor(colors.gray)
  146.     term.setCursorPos(1, 1)
  147.     print("Going to sleep. Click again to wake me.")
  148.     os.sleep(2)
  149.     os.shutdown()
  150. end
  151.  
  152. local function handleNearbyStation()
  153.     while true do
  154.         drawLookingForStationScreen()
  155.         local event, stationData, dist = nil
  156.         parallel.waitForAny(
  157.             function () event, stationData, dist = os.pullEvent("rail_station_nearby") end,
  158.             function ()
  159.                 os.sleep(10)
  160.                 drawGoingToSleepScreen()
  161.             end
  162.         )
  163.         if not stationData then return end
  164.         drawStationFoundScreen(stationData.displayName)
  165.         os.sleep(0.5)
  166.  
  167.         modem.transmit(SERVER_CHANNEL, MY_CHANNEL, {command = "GET_ROUTES", startNode = stationData.name})
  168.         local response = waitForModemMessage(SERVER_CHANNEL, 3)
  169.         if response and response.msg.success then
  170.             local stations = response.msg.stations
  171.             local stationNames = {}
  172.             local stationIds = {}
  173.             for _, station in pairs(stations) do
  174.                 table.insert(stationNames, station.displayName)
  175.                 table.insert(stationIds, station.id)
  176.             end
  177.             drawDestinationsChoiceScreen(stationNames)
  178.             local destination = nil
  179.             -- Wait for user to choose destination, quit, or go away from station.
  180.             parallel.waitForAny(
  181.                 function ()
  182.                     while true do
  183.                         local event, button, x, y = os.pullEvent("mouse_click")
  184.                         if button == 1 then
  185.                             if x >= W-3 and y == 1 then
  186.                                 return
  187.                             elseif y > 1 and y - 1 <= #stationIds then
  188.                                 destination = stationIds[y-1]
  189.                                 return
  190.                             end
  191.                         end
  192.                     end
  193.                 end,
  194.                 function () waitForNoStation(stationData.name) end
  195.             )
  196.             if destination ~= nil then
  197.                 -- Fetch the whole route.
  198.                 modem.transmit(SERVER_CHANNEL, MY_CHANNEL, {command = "ROUTE", startNode = stationData.name, endNode = destination})
  199.                 local routeResponse = waitForModemMessage(SERVER_CHANNEL, 3)
  200.                 if routeResponse and routeResponse.msg.success then
  201.                     local routeEdgeIds = {}
  202.                     for _, segment in pairs(routeResponse.msg.route) do
  203.                         if segment.via then
  204.                             table.insert(routeEdgeIds, segment.via)
  205.                         end
  206.                     end
  207.                     os.queueEvent("rail_route_selected", {path = routeEdgeIds, destination = destination})
  208.                     return
  209.                 elseif routeResponse and routeResponse.msg.error then
  210.                     drawErrorPage("Failed to get route: "..routeResponse.msg.error)
  211.                 else
  212.                     drawErrorPage("Failed to get route. Please contact an administrator if the issue persists.")
  213.                 end
  214.             end
  215.         elseif response and response.msg.error then
  216.             drawErrorPage(response.msg.error)
  217.         else
  218.             drawErrorPage("Could not get a list of stations. Please contact an administrator if the issue persists.\n"..textutils.serialize(response, {compact=true}))
  219.         end
  220.     end
  221. end
  222.  
  223. local function waitForRouteSelection()
  224.     while true do
  225.         parallel.waitForAny(
  226.             listenForAnyStation,
  227.             handleNearbyStation
  228.         )
  229.         local event, route = os.pullEvent("rail_route_selected")
  230.         if event and route then
  231.             return route
  232.         end
  233.     end
  234. end
  235.  
  236. local args = {...}
  237.  
  238. if #args > 1 then
  239.     local route = args
  240.     print("Routing via command-line args:")
  241.     for _, branch in pairs(route) do
  242.         print("  "..branch)
  243.     end
  244.     broadcastRoute(route)
  245.     return
  246. end
  247.  
  248. g.clear(term, colors.white)
  249. g.drawTextCenter(term, W/2, H/2, "Rail Router", colors.black, colors.white)
  250. g.drawTextCenter(term, W/2, H/2 + 2, "By Andrew", colors.gray, colors.white)
  251. os.sleep(0.5)
  252.  
  253. while true do
  254.     local route = waitForRouteSelection()
  255.     g.clear(term, colors.white)
  256.     g.drawTextCenter(term, W/2, 2, "Broadcasting route...", colors.black, colors.white)
  257.     g.drawText(term, 1, 4, "  Path:", colors.gray, colors.white)
  258.     for i, segment in pairs(route.path) do
  259.         local y = i + 4
  260.         g.drawText(term, 4, y, segment, colors.gray, colors.white)
  261.     end
  262.     g.drawText(term, W-3, 1, "Quit", colors.white, colors.red)
  263.  
  264.     parallel.waitForAny(
  265.         function() broadcastRoute(route.path) end,
  266.         function() waitForStation(route.destination) end,
  267.         function() -- Listen for user clicks on the "Quit" button.
  268.             while true do
  269.                 local event, button, x, y = os.pullEvent("mouse_click")
  270.                 if button == 1 and x >= W-3 and y == 1 then
  271.                     return
  272.                 end
  273.             end
  274.         end
  275.     )
  276. end
  277.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement