HandieAndy

RailSignal Signal Script

Nov 22nd, 2021 (edited)
2,619
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 7.79 KB | None | 0 0
  1. local VERSION = "1.0.2"
  2.  
  3. -- Global signal-indexed state maps.
  4. local lastTrainOverheadStates = {}
  5. local lastTrainOverheadDataObjs = {}
  6. local trainScanTimers = {}
  7.  
  8. -- Determines if a train is currently traversing the given signal.
  9. local function trainOverhead(signal)
  10.     return redstone.getInput(signal.redstoneInputSide)
  11. end
  12.  
  13. -- Determines if a train was over the given signal the last time we checked.
  14. local function wasTrainOverheadPreviously(signal)
  15.     return lastTrainOverheadStates[signal.id] == true
  16. end
  17.  
  18. local function getTrainData(signal)
  19.     local det = peripheral.wrap(signal.detector)
  20.     if det == nil then
  21.         print("Error: Signal " .. signal.id .. "'s detector could not be connected. Please fix the config and restart.")
  22.     else
  23.         return det.consist()
  24.     end
  25. end
  26.  
  27. -- Returns the from, to branches for a signal when a train travels in the given direction.
  28. local function getBranches(dir, signal)
  29.     local branches = signal.branches
  30.     if string.upper(branches[1].direction) == string.upper(dir) then
  31.         return branches[2].id, branches[1].id
  32.     elseif string.upper(branches[2].direction) == string.upper(dir) then
  33.         return branches[1].id, branches[2].id
  34.     end
  35. end
  36.  
  37. -- Sends an update to the RailSignal API.
  38. local function sendSignalUpdate(ws, signal, data, msgType)
  39.     local from, to = getBranches(string.upper(data.direction), signal)
  40.     local msg = textutils.serializeJSON({
  41.         signalId = signal.id,
  42.         fromBranchId = from,
  43.         toBranchId = to,
  44.         type = msgType
  45.     })
  46.     print("-> S: " .. signal.id .. ", from: " .. from .. ", to: " .. to .. ", T: " .. msgType)
  47.     ws.send(msg)
  48. end
  49.  
  50. local function updateBranchStatusIndicator(branch, status, config)
  51.     if branch.monitor ~= nil then
  52.         local mon = peripheral.wrap(branch.monitor)
  53.         if mon ~= nil then
  54.             local c = config.statusColors[status]
  55.             if c == nil then c = config.statusColors.ERROR end
  56.             mon.setBackgroundColor(c)
  57.             mon.clear()
  58.         else
  59.             print("Error! Could not connect to monitor " .. branch.monitor .. " for branch " .. branch.id .. ". Check and fix config.")
  60.         end
  61.     end
  62. end
  63.  
  64. local function updateBranchStatus(signal, branchId, status, config)
  65.     for _, branch in pairs(signal.branches) do
  66.         if branch.id == branchId then
  67.             updateBranchStatusIndicator(branch, status, config)
  68.             return
  69.         end
  70.     end
  71. end
  72.  
  73. -- Manually set the status indicator for all branch monitors.
  74. local function setAllBranchStatus(config, status)
  75.     for _, signal in pairs(config.signals) do
  76.         for _, branch in pairs(signal.branches) do
  77.             updateBranchStatusIndicator(branch, status, config)
  78.         end
  79.     end
  80. end
  81.  
  82. local function initMonitorColors(config)
  83.     for _, signal in pairs(config.signals) do
  84.         for _, branch in pairs(signal.branches) do
  85.             if branch.monitor ~= nil then
  86.                 local mon = peripheral.wrap(branch.monitor)
  87.                 if mon ~= nil then
  88.                     for c, v in pairs(config.paletteColors) do
  89.                         mon.setPaletteColor(c, v)
  90.                     end
  91.                 else
  92.                     print("Error! Could not connect to monitor " .. branch.monitor .. " for branch " .. branch.id .. ". Check and fix config.")
  93.                 end
  94.             end
  95.         end
  96.     end
  97. end
  98.  
  99. -- Checks if all signals managed by this controller are reporting an "online" status.
  100. local function checkSignalOnlineStatus(config)
  101.     for _, signal in pairs(config.signals) do
  102.         local resp = http.get(config.apiUrl .. "/railSystems/" .. config.rsId .. "/signals/" .. signal.id)
  103.         if resp == nil or resp.getResponseCode() ~= 200 then
  104.             return false
  105.         else
  106.             local signalData = textutils.unserializeJSON(resp.readAll())
  107.             if not signalData.online then
  108.                 return false
  109.             end
  110.         end
  111.     end
  112.     return true
  113. end
  114.  
  115. local function handleRedstoneEvent(ws, config)
  116.     for _, signal in pairs(config.signals) do
  117.         if trainOverhead(signal) and not wasTrainOverheadPreviously(signal) then
  118.             local data = getTrainData(signal)
  119.             if data == nil then
  120.                 print("Got redstone event but could not obtain train data on signal " .. signal.id)
  121.             else
  122.                 lastTrainOverheadDataObjs[signal.id] = data
  123.                 lastTrainOverheadStates[signal.id] = true
  124.                 sendSignalUpdate(ws, signal, data, "BEGIN")
  125.                 trainScanTimers[signal.id] = os.startTimer(config.trainScanInterval)
  126.             end
  127.         end
  128.     end
  129. end
  130.  
  131. local function handleTrainScanTimerEvent(ws, config, timerId)
  132.     for k, signal in pairs(config.signals) do
  133.         if trainScanTimers[signal.id] == timerId then
  134.             if trainOverhead(signal) then -- The train is still overhead.
  135.                 local data = getTrainData(signal)
  136.                 if data ~= nil then
  137.                     lastTrainOverheadDataObjs[signal.id] = data
  138.                 end
  139.                 trainScanTimers[signal.id] = os.startTimer(config.trainScanInterval)
  140.             else -- The train has left the signal so send an update.
  141.                 sendSignalUpdate(ws, signal, lastTrainOverheadDataObjs[signal.id], "END")
  142.                 lastTrainOverheadDataObjs[signal.id] = nil
  143.                 lastTrainOverheadStates[signal.id] = nil
  144.                 trainScanTimers[signal.id] = nil
  145.             end
  146.             return
  147.         end
  148.     end
  149.     print("Warn: Train scan timer was ignored.")
  150. end
  151.  
  152. local function handleWebSocketMessage(msg, config)
  153.     local data = textutils.unserializeJSON(msg)
  154.     local branchId = data["branchId"]
  155.     local status = data["status"]
  156.     print("<- B: " .. branchId .. ", Status: " .. status)
  157.     for _, signal in pairs(config.signals) do
  158.         updateBranchStatus(signal, branchId, status, config)
  159.     end
  160. end
  161.  
  162. local function loadConfig(file)
  163.     local f = io.open(file, "r")
  164.     if f == nil then return createConfig(file) end
  165.     local text = f:read("*a")
  166.     f:close()
  167.     return textutils.unserialize(text)
  168. end
  169.  
  170. -- Connects to the RailSignal API websocket. Will block indefinitely until a connection can be obtained.
  171. local function connectToWebSocket(config)
  172.     local signalIds = {}
  173.     for _, signal in pairs(config.signals) do
  174.         table.insert(signalIds, tostring(signal.id))
  175.     end
  176.     local signalIdsStr = table.concat(signalIds, ",")
  177.     while true do
  178.         local ws, err = http.websocket(config.wsUrl, {[config.wsHeader] = signalIdsStr})
  179.         if ws == false then
  180.             print("Error connecting to RailSignal websocket:\n\tError: " .. err .. "\n\tTrying again in 3 seconds.")
  181.             os.sleep(3)
  182.         else
  183.             print("Successfully connected to RailSignal websocket at " .. config.wsUrl .. " for managing signals: " .. signalIdsStr)
  184.             return ws
  185.         end
  186.     end
  187. end
  188.  
  189. -- Main Script
  190. term.clear()
  191. print("RailSignal Signal Controller V" .. VERSION)
  192. print("  By Andrew Lalis. For more info, check here: https://github.com/andrewlalis/RailSignalAPI")
  193. print("  To update, execute \"sig update\" in the command line.")
  194. local w, h = term.getSize()
  195. print(string.rep("-", w))
  196. if arg[1] ~= nil and string.lower(arg[1]) == "update" then
  197.     print("Updating to the latest version of RailSignal signal program.")
  198.     fs.delete("sig.lua")
  199.     shell.execute("pastebin", "get", "erA3mSfd", "sig.lua")
  200.     print("Download complete. Restarting...")
  201.     os.sleep(1)
  202.     os.reboot()
  203. end
  204. local config = loadConfig("sig_config.tbl")
  205. initMonitorColors(config)
  206. setAllBranchStatus(config, "NONE")
  207. local ws = connectToWebSocket(config)
  208. local refreshWebSocketAlarm = os.setAlarm((os.time() + math.random(1, 23)) % 24)
  209. while true do
  210.     local eventData = {os.pullEvent()}
  211.     local event = eventData[1]
  212.     if event == "redstone" then
  213.         handleRedstoneEvent(ws, config)
  214.     elseif event == "timer" then
  215.         handleTrainScanTimerEvent(ws, config, eventData[2])
  216.     elseif event == "websocket_message" then
  217.         handleWebSocketMessage(eventData[3], config)
  218.     elseif event == "websocket_closed" then
  219.         setAllBranchStatus(config, "ERROR")
  220.         print("! RailSignal websocket closed. Attempting to reconnect.")
  221.         os.sleep(0.5)
  222.         ws = connectToWebSocket(config)
  223.     elseif event == "alarm" and eventData[2] == refreshWebSocketAlarm then
  224.         print("! Checking signal online status.")
  225.         if not checkSignalOnlineStatus(config) then
  226.             print("Not all signals are reporting an online status. Reconnecting to the websocket.")
  227.             ws.close()
  228.             ws = connectToWebSocket(config)
  229.         end
  230.     end
  231. end
  232. ws.close()
Add Comment
Please, Sign In to add comment