diff --git a/router.lua b/router.lua index c7701e1..f36f60a 100644 --- a/router.lua +++ b/router.lua @@ -2,13 +2,30 @@ This program should be installed on a portable computer with a wireless modem, to act as a routing beacon in conjunction with managed switches. ]]-- -local modem = peripheral.wrap("back") or error("Missing modem.") +local SWITCH_CHANNEL = 45450 +local STATION_BROADCAST_CHANNEL = 45451 +local STATION_REQUEST_CHANNEL = 45452 +local MY_CHANNEL = 45460 -local STATION_CHANNEL = 1 +local g = require("simple-graphics") +local W, H = term.getSize() + +local modem = peripheral.wrap("back") or error("Missing modem.") +modem.open(MY_CHANNEL) -- Listen for messages directed to this device. +modem.open(STATION_BROADCAST_CHANNEL) -- Listen for station broadcasts. + +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 local function broadcastRoute(route) while true do - modem.transmit(0, 42, route) + modem.transmit(SWITCH_CHANNEL, MY_CHANNEL, route) os.sleep(0.5) end end @@ -17,22 +34,178 @@ end local function waitForStation(stationName) while true do local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message") - if channel == STATION_CHANNEL and msg == stationName and dist <= 16 then - print("Arrived at station " .. stationName) + if channel == STATION_BROADCAST_CHANNEL and msg == stationName and dist <= 16 then return end end end +local function listenForAnyStation() + while true do + local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message") + if channel == STATION_BROADCAST_CHANNEL and type(msg) == "string" and dist <= 16 then + 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 () + local event, name, dist = os.pullEvent("rail_station_nearby") + if not targetName or targetName == name then + 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") + if replyChannel == expectedReplyChannel then + 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 + +local function handleNearbyStation() + while true do + 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) + os.sleep(1) + + local event, name, dist = os.pullEvent("rail_station_nearby") + 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, name, colors.blue, colors.white) + g.drawText(term, 1, 5, "Fetching routes...", colors.gray, colors.white) + os.sleep(1) + + modem.transmit(STATION_REQUEST_CHANNEL, MY_CHANNEL, "GET_ROUTES") + local response = waitForModemMessage(STATION_REQUEST_CHANNEL, 1) + if not response or not response.msg or type(response.msg) ~= "table" then + g.clear(term, colors.white) + g.drawXLine(term, 1, W, 1, colors.red) + g.drawText(term, 1, 1, "Error", colors.white, colors.red) + g.drawText(term, 1, 2, "Failed to get routes.", colors.gray, colors.white) + if response then + term.setCursorPos(1, 3) + term.setTextColor(colors.black) + term.setBackgroundColor(colors.lightGray) + print("Response:"..textutils.serialize(response, {compact=true})) + end + os.sleep(5) + else + local routes = response.msg + g.clear(term, colors.white) + g.drawXLine(term, 1, W, 1, colors.blue) + g.drawText(term, 1, 1, "Routes", colors.white, colors.blue) + g.drawText(term, W-3, 1, "Quit", colors.white, colors.red) + for i, route in pairs(routes) 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..". "..route.name, colors.black, bg) + end + -- Either wait for the user to choose a route, or go away from the + -- station transponder. + 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 + break + elseif y > 1 and y - 1 <= #routes then + local selectedRoute = routes[y-1] + os.queueEvent("rail_route_selected", selectedRoute) + return + end + end + end + end, + function () waitForNoStation(name) end + ) + end + end +end + +local function waitForRouteSelection() + while true do + parallel.waitForAny( + listenForAnyStation, + handleNearbyStation + ) + local event, route = os.pullEvent("rail_route_selected") + if event and type(route) == "table" then + return route + end + end +end + local args = {...} -local route = args -print("Routing via:") -for _, branch in pairs(route) do - print(" "..branch) +if #args > 1 then + local route = args + print("Routing via command-line args:") + for _, branch in pairs(route) do + print(" "..branch) + end + parallel.waitForAny( + function() broadcastRoute(route) end, + function() waitForStation(route[#route]) end + ) + return end -parallel.waitForAny( - function() broadcastRoute(route) end - function() waitForStation(route[#route]) end -) +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) +os.sleep(1) + +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, + function() waitForStation(route.path[#route.path]) end, + 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 diff --git a/station.lua b/station.lua index 4c16940..a1e2512 100644 --- a/station.lua +++ b/station.lua @@ -1,19 +1,66 @@ --[[ Stations are kiosks where users can configure their portable computer for a particular route to another station. + +You should add a "station_config.tbl" file containing: +{ + name = "Station name", + range = 8, + routes = { + {name = "First", path = {"A", "B", "C"}}, + {name = "Second", path = {"D", "A", "C"}} + } +} ]]-- local modem = peripheral.wrap("top") or error("Missing top modem") -local CHANNEL = 1 -local STATION_NAME = "Test Station" +local BROADCAST_CHANNEL = 45451 +local RECEIVE_CHANNEL = 45452 -local function broadcastName() +modem.open(RECEIVE_CHANNEL) + +local function readConfig() + local f = io.open("station_config.tbl", "r") + if not f then error("Missing station_config.tbl") end + local cfg = textutils.unserialize(f:read("*a")) + f:close() + return cfg +end + +local function broadcastName(config) while true do - modem.transmit(CHANNEL, CHANNEL, STATION_NAME) + modem.transmit(BROADCAST_CHANNEL, BROADCAST_CHANNEL, config.name) os.sleep(1) end end +local function handleRequests(config) + while true do + local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message") + if channel == RECEIVE_CHANNEL and dist <= config.range then + if msg == "GET_ROUTES" then + modem.transmit(replyChannel, RECEIVE_CHANNEL, config.routes) + print(textutils.formatTime(os.time()).." Sent routes to "..replyChannel) + end + end + end +end + +local config = readConfig() +term.clear() +term.setCursorPos(1, 1) +print("Running station transponder for \""..config.name.."\".") +print(" Range: "..config.range.." blocks") +print(" Routes:") +for i, route in pairs(config.routes) do + local pathStr = "" + for j, segment in pairs(route.path) do + pathStr = pathStr .. segment + if j < #route.path then pathStr = pathStr .. "," end + end + print(" "..i..". "..route.name..": "..pathStr) +end parallel.waitForAll( - broadcastName + function() broadcastName(config) end, + function() handleRequests(config) end ) diff --git a/switcher.lua b/switcher.lua index 219ae7c..44959f6 100644 --- a/switcher.lua +++ b/switcher.lua @@ -6,7 +6,7 @@ will decode it and make any adjustments it needs to. ]]-- local CONFIG_FILE = "switch_config.tbl" -local CHANNEL = 0 +local CHANNEL = 45450 local config = nil local modem = peripheral.wrap("top") or error("Missing top modem") @@ -15,7 +15,7 @@ modem.open(CHANNEL) term.clear() term.setCursorPos(1, 1) -print("Receiving routing commands on channel 0") +print("Receiving routing commands on channel " .. CHANNEL) -- Series of guided inputs for building a configuration file from user input. local function configSetupWizard() @@ -156,10 +156,6 @@ 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.