Updated switch service and driver.lua
This commit is contained in:
		
							parent
							
								
									3b2797f259
								
							
						
					
					
						commit
						d9a4ec31c4
					
				| 
						 | 
					@ -3,14 +3,16 @@ package nl.andrewl.railsignalapi.service;
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import nl.andrewl.railsignalapi.dao.ComponentRepository;
 | 
					import nl.andrewl.railsignalapi.dao.ComponentRepository;
 | 
				
			||||||
import nl.andrewl.railsignalapi.live.ComponentDownlinkService;
 | 
					import nl.andrewl.railsignalapi.live.ComponentDownlinkService;
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.live.dto.ComponentDataMessage;
 | 
				
			||||||
import nl.andrewl.railsignalapi.live.dto.ErrorMessage;
 | 
					import nl.andrewl.railsignalapi.live.dto.ErrorMessage;
 | 
				
			||||||
import nl.andrewl.railsignalapi.live.dto.SwitchUpdateMessage;
 | 
					import nl.andrewl.railsignalapi.live.dto.SwitchUpdateMessage;
 | 
				
			||||||
import nl.andrewl.railsignalapi.live.websocket.AppUpdateService;
 | 
					import nl.andrewl.railsignalapi.live.websocket.AppUpdateService;
 | 
				
			||||||
import nl.andrewl.railsignalapi.model.component.Switch;
 | 
					import nl.andrewl.railsignalapi.model.component.Switch;
 | 
				
			||||||
import nl.andrewl.railsignalapi.model.component.SwitchConfiguration;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
import org.springframework.transaction.annotation.Transactional;
 | 
					import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Service for managing switches.
 | 
					 * Service for managing switches.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -24,17 +26,20 @@ public class SwitchService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Transactional
 | 
						@Transactional
 | 
				
			||||||
	public void onSwitchUpdate(SwitchUpdateMessage msg) {
 | 
						public void onSwitchUpdate(SwitchUpdateMessage msg) {
 | 
				
			||||||
		switchRepository.findById(msg.cId).ifPresent(sw -> {
 | 
							Optional<Switch> optionalSwitch = switchRepository.findById(msg.cId);
 | 
				
			||||||
			sw.getPossibleConfigurations().stream()
 | 
							if (optionalSwitch.isPresent()) {
 | 
				
			||||||
				.filter(c -> c.getId().equals(msg.activeConfigId))
 | 
								Switch sw = optionalSwitch.get();
 | 
				
			||||||
				.findFirst()
 | 
								for (var config : sw.getPossibleConfigurations()) {
 | 
				
			||||||
				.ifPresentOrElse(config -> {
 | 
									if (config.getId().equals(msg.activeConfigId)) {
 | 
				
			||||||
					sw.setActiveConfiguration(config);
 | 
										sw.setActiveConfiguration(config);
 | 
				
			||||||
					switchRepository.save(sw);
 | 
										switchRepository.save(sw);
 | 
				
			||||||
					appUpdateService.sendComponentUpdate(sw.getRailSystem().getId(), sw.getId());
 | 
										appUpdateService.sendUpdate(sw.getRailSystem().getId(), new ComponentDataMessage(sw));
 | 
				
			||||||
				}, () -> {
 | 
										return;
 | 
				
			||||||
					downlinkService.sendMessage(new ErrorMessage(sw.getId(), "Invalid active config id."));
 | 
									}
 | 
				
			||||||
				});
 | 
								}
 | 
				
			||||||
		});
 | 
								downlinkService.sendMessage(new ErrorMessage(msg.cId, "Invalid config id."));
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								downlinkService.sendMessage(new ErrorMessage(msg.cId, "Unknown switch."));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,235 +1,268 @@
 | 
				
			||||||
local VERSION = "1.0.2"
 | 
					-- Rail Signal CC:Tweaked Driver
 | 
				
			||||||
 | 
					local VERSION = "1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- Global component states
 | 
					-- Global config. Will be loaded at start of script.
 | 
				
			||||||
local components = {}
 | 
					local config = {}
 | 
				
			||||||
 | 
					-- Global websocket reference
 | 
				
			||||||
 | 
					local ws = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- Global signal-indexed state maps.
 | 
					local function loadConfig()
 | 
				
			||||||
local lastTrainOverheadStates = {}
 | 
					  local configFile = io.open("rs_config.tbl", "r")
 | 
				
			||||||
local lastTrainOverheadDataObjs = {}
 | 
					  if not configFile then
 | 
				
			||||||
local trainScanTimers = {}
 | 
					    return nil
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
-- Determines if a train is currently traversing the given signal.
 | 
					  local txt = configFile:read("*a")
 | 
				
			||||||
local function trainOverhead(signal)
 | 
					  local cfg = textutils.unserialize(txt)
 | 
				
			||||||
    return redstone.getInput(signal.redstoneInputSide)
 | 
					  configFile:close()
 | 
				
			||||||
 | 
					  return cfg
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- Determines if a train was over the given signal the last time we checked.
 | 
					local function fetchJson(endpoint)
 | 
				
			||||||
local function wasTrainOverheadPreviously(signal)
 | 
					  local response, msg, r = http.get({
 | 
				
			||||||
    return lastTrainOverheadStates[signal.id] == true
 | 
					    url = config.apiUrl .. endpoint,
 | 
				
			||||||
end
 | 
					    method = "GET"
 | 
				
			||||||
 | 
					 | 
				
			||||||
local function getTrainData(signal)
 | 
					 | 
				
			||||||
    local det = peripheral.wrap(signal.detector)
 | 
					 | 
				
			||||||
    if det == nil then
 | 
					 | 
				
			||||||
        print("Error: Signal " .. signal.id .. "'s detector could not be connected. Please fix the config and restart.")
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
        return det.consist()
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- Returns the from, to branches for a signal when a train travels in the given direction.
 | 
					 | 
				
			||||||
local function getBranches(dir, signal)
 | 
					 | 
				
			||||||
    local branches = signal.branches
 | 
					 | 
				
			||||||
    if string.upper(branches[1].direction) == string.upper(dir) then
 | 
					 | 
				
			||||||
        return branches[2].id, branches[1].id
 | 
					 | 
				
			||||||
    elseif string.upper(branches[2].direction) == string.upper(dir) then
 | 
					 | 
				
			||||||
        return branches[1].id, branches[2].id
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- Sends an update to the RailSignal API.
 | 
					 | 
				
			||||||
local function sendSignalUpdate(ws, signal, data, msgType)
 | 
					 | 
				
			||||||
    local from, to = getBranches(string.upper(data.direction), signal)
 | 
					 | 
				
			||||||
    local msg = textutils.serializeJSON({
 | 
					 | 
				
			||||||
        signalId = signal.id,
 | 
					 | 
				
			||||||
        fromBranchId = from,
 | 
					 | 
				
			||||||
        toBranchId = to,
 | 
					 | 
				
			||||||
        type = msgType
 | 
					 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
    print("-> S: " .. signal.id .. ", from: " .. from .. ", to: " .. to .. ", T: " .. msgType)
 | 
					  if response then
 | 
				
			||||||
    ws.send(msg)
 | 
					    return textutils.unserialiseJSON(response.readAll())
 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
local function updateBranchStatusIndicator(branch, status, config)
 | 
					 | 
				
			||||||
    if branch.monitor ~= nil then
 | 
					 | 
				
			||||||
        local mon = peripheral.wrap(branch.monitor)
 | 
					 | 
				
			||||||
        if mon ~= nil then
 | 
					 | 
				
			||||||
            local c = config.statusColors[status]
 | 
					 | 
				
			||||||
            if c == nil then c = config.statusColors.ERROR end
 | 
					 | 
				
			||||||
            mon.setBackgroundColor(c)
 | 
					 | 
				
			||||||
            mon.clear()
 | 
					 | 
				
			||||||
  else
 | 
					  else
 | 
				
			||||||
            print("Error! Could not connect to monitor " .. branch.monitor .. " for branch " .. branch.id .. ". Check and fix config.")
 | 
					    print("Error: " .. r.getResponseCode())
 | 
				
			||||||
        end
 | 
					    return nil
 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
local function updateBranchStatus(signal, branchId, status, config)
 | 
					 | 
				
			||||||
    for _, branch in pairs(signal.branches) do
 | 
					 | 
				
			||||||
        if branch.id == branchId then
 | 
					 | 
				
			||||||
            updateBranchStatusIndicator(branch, status, config)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- Manually set the status indicator for all branch monitors.
 | 
					 | 
				
			||||||
local function setAllBranchStatus(config, status)
 | 
					 | 
				
			||||||
    for _, signal in pairs(config.signals) do
 | 
					 | 
				
			||||||
        for _, branch in pairs(signal.branches) do
 | 
					 | 
				
			||||||
            updateBranchStatusIndicator(branch, status, config)
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
local function initMonitorColors(config)
 | 
					 | 
				
			||||||
    for _, signal in pairs(config.signals) do
 | 
					 | 
				
			||||||
        for _, branch in pairs(signal.branches) do
 | 
					 | 
				
			||||||
            if branch.monitor ~= nil then
 | 
					 | 
				
			||||||
                local mon = peripheral.wrap(branch.monitor)
 | 
					 | 
				
			||||||
                if mon ~= nil then
 | 
					 | 
				
			||||||
                    for c, v in pairs(config.paletteColors) do
 | 
					 | 
				
			||||||
                        mon.setPaletteColor(c, v)
 | 
					 | 
				
			||||||
                    end
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                    print("Error! Could not connect to monitor " .. branch.monitor .. " for branch " .. branch.id .. ". Check and fix config.")
 | 
					 | 
				
			||||||
                end
 | 
					 | 
				
			||||||
            end
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- Checks if all signals managed by this controller are reporting an "online" status.
 | 
					 | 
				
			||||||
local function checkSignalOnlineStatus(config)
 | 
					 | 
				
			||||||
    for _, signal in pairs(config.signals) do
 | 
					 | 
				
			||||||
        local resp = http.get(config.apiUrl .. "/railSystems/" .. config.rsId .. "/signals/" .. signal.id)
 | 
					 | 
				
			||||||
        if resp == nil or resp.getResponseCode() ~= 200 then
 | 
					 | 
				
			||||||
            return false
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            local signalData = textutils.unserializeJSON(resp.readAll())
 | 
					 | 
				
			||||||
            if not signalData.online then
 | 
					 | 
				
			||||||
                return false
 | 
					 | 
				
			||||||
            end
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
    return true
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
local function handleRedstoneEvent(ws, config)
 | 
					 | 
				
			||||||
    for _, signal in pairs(config.signals) do
 | 
					 | 
				
			||||||
        if trainOverhead(signal) and not wasTrainOverheadPreviously(signal) then
 | 
					 | 
				
			||||||
            local data = getTrainData(signal)
 | 
					 | 
				
			||||||
            if data == nil then
 | 
					 | 
				
			||||||
                print("Got redstone event but could not obtain train data on signal " .. signal.id)
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
                lastTrainOverheadDataObjs[signal.id] = data
 | 
					 | 
				
			||||||
                lastTrainOverheadStates[signal.id] = true
 | 
					 | 
				
			||||||
                sendSignalUpdate(ws, signal, data, "BEGIN")
 | 
					 | 
				
			||||||
                trainScanTimers[signal.id] = os.startTimer(config.trainScanInterval)
 | 
					 | 
				
			||||||
            end
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function handleTrainScanTimerEvent(ws, config, timerId)
 | 
					-- Blocking function to obtain a websocket connection to Rail Signal.
 | 
				
			||||||
    for k, signal in pairs(config.signals) do
 | 
					local function connectToWebsocket()
 | 
				
			||||||
        if trainScanTimers[signal.id] == timerId then
 | 
					  local url = config.wsUrl .. "?token=" .. config.linkToken
 | 
				
			||||||
            if trainOverhead(signal) then -- The train is still overhead.
 | 
					  print("Connecting to Rail Signal websocket...")
 | 
				
			||||||
                local data = getTrainData(signal)
 | 
					 | 
				
			||||||
                if data ~= nil then
 | 
					 | 
				
			||||||
                    lastTrainOverheadDataObjs[signal.id] = data
 | 
					 | 
				
			||||||
                end
 | 
					 | 
				
			||||||
                trainScanTimers[signal.id] = os.startTimer(config.trainScanInterval)
 | 
					 | 
				
			||||||
            else -- The train has left the signal so send an update.
 | 
					 | 
				
			||||||
                sendSignalUpdate(ws, signal, lastTrainOverheadDataObjs[signal.id], "END")
 | 
					 | 
				
			||||||
                lastTrainOverheadDataObjs[signal.id] = nil
 | 
					 | 
				
			||||||
                lastTrainOverheadStates[signal.id] = nil
 | 
					 | 
				
			||||||
                trainScanTimers[signal.id] = nil
 | 
					 | 
				
			||||||
            end
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
    print("Warn: Train scan timer was ignored.")
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
local function handleWebSocketMessage(msg, config)
 | 
					 | 
				
			||||||
    local data = textutils.unserializeJSON(msg)
 | 
					 | 
				
			||||||
    local branchId = data["branchId"]
 | 
					 | 
				
			||||||
    local status = data["status"]
 | 
					 | 
				
			||||||
    print("<- B: " .. branchId .. ", Status: " .. status)
 | 
					 | 
				
			||||||
    for _, signal in pairs(config.signals) do
 | 
					 | 
				
			||||||
        updateBranchStatus(signal, branchId, status, config)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
local function loadConfig(file)
 | 
					 | 
				
			||||||
    local f = io.open(file, "r")
 | 
					 | 
				
			||||||
    if f == nil then return createConfig(file) end
 | 
					 | 
				
			||||||
    local text = f:read("*a")
 | 
					 | 
				
			||||||
    f:close()
 | 
					 | 
				
			||||||
    return textutils.unserialize(text)
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- Connects to the RailSignal API websocket. Will block indefinitely until a connection can be obtained.
 | 
					 | 
				
			||||||
local function connectToWebSocket(config)
 | 
					 | 
				
			||||||
    local signalIds = {}
 | 
					 | 
				
			||||||
    for _, signal in pairs(config.signals) do
 | 
					 | 
				
			||||||
        table.insert(signalIds, tostring(signal.id))
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
    local signalIdsStr = table.concat(signalIds, ",")
 | 
					 | 
				
			||||||
  while true do
 | 
					  while true do
 | 
				
			||||||
        local ws, err = http.websocket(config.wsUrl, {[config.wsHeader] = signalIdsStr})
 | 
					    local ws, msg = http.websocket(url)
 | 
				
			||||||
    if ws == false then
 | 
					    if ws == false then
 | 
				
			||||||
            print("Error connecting to RailSignal websocket:\n\tError: " .. err .. "\n\tTrying again in 3 seconds.")
 | 
					      print("Error connecting to the Rail Signal API websocket:\n\tError: " .. msg .. "\n\tTrying again in 3 seconds.")
 | 
				
			||||||
      os.sleep(3)
 | 
					      os.sleep(3)
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
            print("Successfully connected to RailSignal websocket at " .. config.wsUrl .. " for managing signals: " .. signalIdsStr)
 | 
					      print("Successfully connected to Rail Signal API websocket.")
 | 
				
			||||||
      return ws
 | 
					      return ws
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- Main Script
 | 
					local function getComponentById(id)
 | 
				
			||||||
term.clear()
 | 
					  for _, c in pairs(config.components) do
 | 
				
			||||||
print("RailSignal Signal Controller V" .. VERSION)
 | 
					    if c.id == id then
 | 
				
			||||||
print("  By Andrew Lalis. For more info, check here: https://github.com/andrewlalis/RailSignalAPI")
 | 
					      return c
 | 
				
			||||||
print("  To update, execute \"sig update\" in the command line.")
 | 
					 | 
				
			||||||
local w, h = term.getSize()
 | 
					 | 
				
			||||||
print(string.rep("-", w))
 | 
					 | 
				
			||||||
if arg[1] ~= nil and string.lower(arg[1]) == "update" then
 | 
					 | 
				
			||||||
    print("Updating to the latest version of RailSignal signal program.")
 | 
					 | 
				
			||||||
    fs.delete("sig.lua")
 | 
					 | 
				
			||||||
    shell.execute("pastebin", "get", "erA3mSfd", "sig.lua")
 | 
					 | 
				
			||||||
    print("Download complete. Restarting...")
 | 
					 | 
				
			||||||
    os.sleep(1)
 | 
					 | 
				
			||||||
    os.reboot()
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
local config = loadConfig("sig_config.tbl")
 | 
					  end
 | 
				
			||||||
initMonitorColors(config)
 | 
					  return nil
 | 
				
			||||||
setAllBranchStatus(config, "NONE")
 | 
					end
 | 
				
			||||||
local ws = connectToWebSocket(config)
 | 
					
 | 
				
			||||||
local refreshWebSocketAlarm = os.setAlarm((os.time() + math.random(1, 23)) % 24)
 | 
					local function displaySignal(signal)
 | 
				
			||||||
 | 
					  local color = colors.white
 | 
				
			||||||
 | 
					  if signal.segment.occupied ~= nil then
 | 
				
			||||||
 | 
					    if signal.segment.occupied then
 | 
				
			||||||
 | 
					      color = colors.red
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      color = colors.lime
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  signal.segment.monitor.setBackgroundColor(color)
 | 
				
			||||||
 | 
					  signal.segment.monitor.clear()
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function setSwitchActiveConfig(switch, configId)
 | 
				
			||||||
 | 
					  for _, cfg in pairs(switch.possibleConfigurations) do
 | 
				
			||||||
 | 
					    if cfg.id == configId then
 | 
				
			||||||
 | 
					      print("Setting active config id: " .. configId)
 | 
				
			||||||
 | 
					      rs.setOutput(switch.rsSide, cfg.rsOutput)
 | 
				
			||||||
 | 
					      ws.send(textutils.serializeJSON({
 | 
				
			||||||
 | 
					        cId=switch.id,
 | 
				
			||||||
 | 
					        type="SWITCH_UPDATE",
 | 
				
			||||||
 | 
					        activeConfigId=configId
 | 
				
			||||||
 | 
					      }))
 | 
				
			||||||
 | 
					      return true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  return false
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function trainOverhead(sb)
 | 
				
			||||||
 | 
					  return rs.getInput(sb.rsSide)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function wasTrainOverhead(sb)
 | 
				
			||||||
 | 
					  return sb.trainOverhead == true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function getTrainData(sb)
 | 
				
			||||||
 | 
					  return sb.augment.consist()
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Sends an update to the API regarding a segment boundary event. The event
 | 
				
			||||||
 | 
					-- should be either "ENTERING" if a train has just entered a segment, or
 | 
				
			||||||
 | 
					-- "ENTERED" if a train has just completely entered a segment and left its
 | 
				
			||||||
 | 
					-- previous segment.
 | 
				
			||||||
 | 
					local function sendSegmentBoundaryUpdate(sb, eventType)
 | 
				
			||||||
 | 
					  local dir = string.upper(sb.trainLastData.direction)
 | 
				
			||||||
 | 
					  local toSegmentId = nil
 | 
				
			||||||
 | 
					  for _, segment in pairs(sb.segments) do
 | 
				
			||||||
 | 
					    if string.upper(segment.direction) == dir then
 | 
				
			||||||
 | 
					      toSegmentId = segment.id
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  print("Sending sb update: " .. toSegmentId .. ", " .. eventType)
 | 
				
			||||||
 | 
					  ws.send(textutils.serializeJSON({
 | 
				
			||||||
 | 
					    cId = sb.id,
 | 
				
			||||||
 | 
					    type = "SEGMENT_BOUNDARY_UPDATE",
 | 
				
			||||||
 | 
					    toSegmentId = toSegmentId,
 | 
				
			||||||
 | 
					    eventType = eventType
 | 
				
			||||||
 | 
					  }))
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function handleRedstoneEvent()
 | 
				
			||||||
 | 
					  for _, component in pairs(config.components) do
 | 
				
			||||||
 | 
					    if component.type == "SEGMENT_BOUNDARY" then
 | 
				
			||||||
 | 
					      local sb = component
 | 
				
			||||||
 | 
					      if trainOverhead(sb) and not wasTrainOverhead(sb) then
 | 
				
			||||||
 | 
					        local data = getTrainData(sb)
 | 
				
			||||||
 | 
					        if data ~= nil then
 | 
				
			||||||
 | 
					          sb.trainOverhead = true
 | 
				
			||||||
 | 
					          sb.trainLastData = data
 | 
				
			||||||
 | 
					          sendSegmentBoundaryUpdate(sb, "ENTERING")
 | 
				
			||||||
 | 
					          sb.trainScanTimer = os.startTimer(config.trainScanInterval)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Handles timer events that happen when we periodically check if a train that
 | 
				
			||||||
 | 
					-- was overhead is still there.
 | 
				
			||||||
 | 
					local function handleTrainScanTimerEvent(timerId)
 | 
				
			||||||
 | 
					  for _, component in pairs(config.components) do
 | 
				
			||||||
 | 
					    if component.trainScanTimer == timerId then
 | 
				
			||||||
 | 
					      local sb = component
 | 
				
			||||||
 | 
					      if trainOverhead(sb) then -- train is still overhead.
 | 
				
			||||||
 | 
					        local data = getTrainData(sb)
 | 
				
			||||||
 | 
					        if data ~= nil then sb.trainLastData = data end
 | 
				
			||||||
 | 
					        -- Schedule another timer to check again later.
 | 
				
			||||||
 | 
					        sb.trainOverhead = true
 | 
				
			||||||
 | 
					        sb.trainScanTimer = os.startTimer(config.trainScanInterval)
 | 
				
			||||||
 | 
					      else -- The train has left the signal, so send an update.
 | 
				
			||||||
 | 
					        sendSegmentBoundaryUpdate(sb, "ENTERED")
 | 
				
			||||||
 | 
					        sb.trainOverhead = false
 | 
				
			||||||
 | 
					        sb.trainLastData = nil
 | 
				
			||||||
 | 
					        sb.trainScanTimer = nil
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Process status updates for segments that we receive from the API.
 | 
				
			||||||
 | 
					local function handleSegmentStatusUpdate(msg)
 | 
				
			||||||
 | 
					  local signal = getComponentById(msg.cId)
 | 
				
			||||||
 | 
					  if signal ~= nil and signal.segment.id == msg.segmentId then
 | 
				
			||||||
 | 
					    signal.segment.occupied = msg.occupied
 | 
				
			||||||
 | 
					    displaySignal(signal)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function handleSwitchUpdate(msg)
 | 
				
			||||||
 | 
					  local switch = getComponentById(msg.cId)
 | 
				
			||||||
 | 
					  if switch ~= nil then
 | 
				
			||||||
 | 
					    setSwitchActiveConfig(switch, msg.activeConfigId)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function handleWebsocketMessage(msg)
 | 
				
			||||||
 | 
					  local data = textutils.unserializeJSON(msg)
 | 
				
			||||||
 | 
					  if data.type == "SEGMENT_STATUS" then
 | 
				
			||||||
 | 
					    handleSegmentStatusUpdate(data)
 | 
				
			||||||
 | 
					  elseif data.type == "SWITCH_UPDATE" then
 | 
				
			||||||
 | 
					    handleSwitchUpdate(data)
 | 
				
			||||||
 | 
					  elseif data.type == "ERROR" then
 | 
				
			||||||
 | 
					    print("Rail Signal sent an error: " .. data.message)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Augments the configuration data with extra information from the API, such
 | 
				
			||||||
 | 
					-- as rail system information and component names.
 | 
				
			||||||
 | 
					local function initApiData()
 | 
				
			||||||
 | 
					  local tokenData = fetchJson("/lt/" .. config.linkToken)
 | 
				
			||||||
 | 
					  if tokenData == nil then
 | 
				
			||||||
 | 
					    print("Error: Could not fetch device component data from the Rail Signal API.")
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  for _, component in pairs(tokenData.components) do
 | 
				
			||||||
 | 
					    local c = getComponentById(component.id)
 | 
				
			||||||
 | 
					    if c == nil then
 | 
				
			||||||
 | 
					      print("Error: This device's link token is linked to component with id " .. component.id .. " but no component with that id is configured.")
 | 
				
			||||||
 | 
					      return false
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      c.name = component.name
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  config.rsName = tokenData.rsName
 | 
				
			||||||
 | 
					  config.rsId = tokenData.rsId
 | 
				
			||||||
 | 
					  return true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- MAIN SCRIPT
 | 
				
			||||||
 | 
					print("Rail Signal Device Driver " .. VERSION .. " for CC:Tweaked computers")
 | 
				
			||||||
 | 
					print("  By Andrew Lalis <andrewlalisofficial@gmail.com>")
 | 
				
			||||||
 | 
					config = loadConfig()
 | 
				
			||||||
 | 
					print("Loaded config.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if initApiData() then
 | 
				
			||||||
 | 
					  print("Data fetched from Rail Signal API successfully.")
 | 
				
			||||||
 | 
					  print("Initialized for rail system \"" .. config.rsName .. "\", ID: " .. config.rsId)
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					  print("Could not fetch data from Rail Signal API.")
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ws = connectToWebsocket()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Initialize all components.
 | 
				
			||||||
 | 
					for _, c in pairs(config.components) do
 | 
				
			||||||
 | 
					  if c.type == "SIGNAL" then
 | 
				
			||||||
 | 
					    c.segment.monitor = peripheral.wrap(c.segment.monitorId)
 | 
				
			||||||
 | 
					    local m = c.segment.monitor
 | 
				
			||||||
 | 
					    m.setPaletteColor(colors.white, 0xFFFFFF)
 | 
				
			||||||
 | 
					    m.setPaletteColor(colors.red, 0xFF0000)
 | 
				
			||||||
 | 
					    m.setPaletteColor(colors.lime, 0x00FF00)
 | 
				
			||||||
 | 
					    m.setPaletteColor(colors.blue, 0x0000FF)
 | 
				
			||||||
 | 
					    displaySignal(c)
 | 
				
			||||||
 | 
					  elseif c.type == "SEGMENT_BOUNDARY" then
 | 
				
			||||||
 | 
					    c.augment = peripheral.wrap(c.augmentId)
 | 
				
			||||||
 | 
					  elseif c.type == "SWITCH" then
 | 
				
			||||||
 | 
					    local state = rs.getOutput(c.rsSide)
 | 
				
			||||||
 | 
					    local activeConfigId = nil
 | 
				
			||||||
 | 
					    for _, cfg in pairs(c.possibleConfigurations) do
 | 
				
			||||||
 | 
					      if cfg.rsOutput == state then
 | 
				
			||||||
 | 
					        activeConfigId = cfg.id
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    ws.send(textutils.serializeJSON({cid=c.id, type="SWITCH_UPDATE", activeConfigId=activeConfigId}))
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
while true do
 | 
					while true do
 | 
				
			||||||
  local eventData = {os.pullEvent()}
 | 
					  local eventData = {os.pullEvent()}
 | 
				
			||||||
  local event = eventData[1]
 | 
					  local event = eventData[1]
 | 
				
			||||||
  if event == "redstone" then
 | 
					  if event == "redstone" then
 | 
				
			||||||
        handleRedstoneEvent(ws, config)
 | 
					    handleRedstoneEvent()
 | 
				
			||||||
    elseif event == "timer" then
 | 
					 | 
				
			||||||
        handleTrainScanTimerEvent(ws, config, eventData[2])
 | 
					 | 
				
			||||||
  elseif event == "websocket_message" then
 | 
					  elseif event == "websocket_message" then
 | 
				
			||||||
        handleWebSocketMessage(eventData[3], config)
 | 
					    handleWebsocketMessage(eventData[3])
 | 
				
			||||||
  elseif event == "websocket_closed" then
 | 
					  elseif event == "websocket_closed" then
 | 
				
			||||||
        setAllBranchStatus(config, "ERROR")
 | 
					    for _, component in pairs(config.components) do
 | 
				
			||||||
        print("! RailSignal websocket closed. Attempting to reconnect.")
 | 
					      if component.type == "SIGNAL" then
 | 
				
			||||||
 | 
					        component.segment.occupied = nil
 | 
				
			||||||
 | 
					        displaySignal(component)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    print("Rail Signal websocket closed. Attempting to reconnect.")
 | 
				
			||||||
    os.sleep(0.5)
 | 
					    os.sleep(0.5)
 | 
				
			||||||
        ws = connectToWebSocket(config)
 | 
					    ws = connectToWebsocket()
 | 
				
			||||||
    elseif event == "alarm" and eventData[2] == refreshWebSocketAlarm then
 | 
					  elseif event == "timer" then
 | 
				
			||||||
        print("! Checking signal online status.")
 | 
					    handleTrainScanTimerEvent(eventData[2])
 | 
				
			||||||
        if not checkSignalOnlineStatus(config) then
 | 
					 | 
				
			||||||
            print("Not all signals are reporting an online status. Reconnecting to the websocket.")
 | 
					 | 
				
			||||||
            ws.close()
 | 
					 | 
				
			||||||
            ws = connectToWebSocket(config)
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
end
 | 
					 | 
				
			||||||
ws.close()
 | 
					 | 
				
			||||||
		Loading…
	
		Reference in New Issue