--[[
---------------------------------------------------------------------------
Remote Train Control Program for Windows

© Copyright 2025 by Mark DiVecchio

	This file is part of Remote Train Control.

	Remote Train Control is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	Remote Train Control is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Remote Train Control.  If not, see <http://www.gnu.org/licenses/>.

 Home Page : http://www.silogic.com/trains/RTC_Running.html
---------------------------------------------------------------------------]]
-- Semicolons are not required in Lua code
local title = "Layout4 APB Master"
require([[defines]]);
require([[functions]]);

--[[
When using this module, all variables (including functions!) must be declared through 
a regular assignment (even assigning nil will do) in a strict scope before being used 
anywhere or assigned to inside a nested scope.
https://github.com/lua-stdlib/strict
]]
local _ENV = require 'std.strict' (_G)	-- strict.lua will detect undeclared global variables
local _;
--
--[[
---------------------------------------------------------------------------
Based Program for APB example defined in August 2016 RMC by Bruce Chubb
Version Revision 07/17/2016

Clear all flags at start - only place that flags are changed (see also
manual F02, F03 and F04 buttons.
Layout4BlockOcc[] array used to remember Block Occupancy events.
Does not set any flags except on manual request or during setup()
Uses Block Occupancy event from EOB to control signals
Expects other scripts to set flags for their own use
Gets tags using tag() to update Layout4BlockOcc[] array.
Requests Events from Flags in layout window by calling ReqBlockEvents(true)

The APB system does not have separate occupancy information for passing
sidings. If either the thru or passing track is occupied, the information
is stored with the thru track.

Any reference to a passing/station track is translated to the thru track
for use by this script.

When running with Mike Hewett's 3 LED signals, use HdMLight for Permissive,
Absolute and Head Block signals
---------------------------------------------------------------------------]]

--[[
For testing, accepts events from "Layout4 Block XX Occupancy.lua" to simulate a train running across the layout
--]]

local Count = 0;

local Tags = {};		-- table to hold all of the tags detected in sequence
local TagCount = 0;

local FSR = {};			-- table to hold all of the traffic sticks
local Sig = {};			-- table to hold all of the Next Signal Aspects
local SigA = {};		-- table to hold all of the Next Signal Aspects modified for Approach Lighting
local ApproachLighting = false;	-- when true, use Approach Lighting
local SavedSig = {};	-- table to hold the last known Signal Aspects
local Layout4BlockOcc = {};			-- Table to hold the APB Master Block Occupancy Status.
									-- Values in this table can be:
									--  0	UNOCCUPIED
									-- >0	OCCUPIED
									--		1	Occupied or a thru/passing or yard with 1 branch occupied
									--		2	Thru/passing or yard with both branches occupied
Title(title);									
local TUMCNT = {};		-- table to hold all of the Tumbledown Counters
local TUMMAX = 15;  	-- maximum value for tumbledown delay counter
--
-- state machine states
local IDLE = 0;
local SM = IDLE;
local ErrCount = 0;
local BeepOn = false;
--
-- Instrumented Layout 4
local Layout = nil;
-- clear all tag handling functions
local Func30 = nil;
local Func51 = nil;
local Func10 = nil;
local Func41 = nil;
local Func21 = nil;
local Func20 = nil;
local Func90 = nil;
local Func40 = nil;
local Func50 = nil;
local Func60 = nil;
local Func61 = nil;
local Func100 = nil;
local Func101 = nil;
local Func110 = nil;
local Func150 = nil;
--
local MyTIUNo, Enum;
local OccupiedBlocks = {};		-- list of occupied blocks read from settings file
local BlockNameList = {};	-- table to hold all of the block numbers and names
--
local SetupComplete = false;	-- if true, permits settings to be saved.
local LEDTestOff = false;		-- set true to exit from the LED Test

-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "Control";
Tab02Label = "Support";
Tab03Label = "Debug";
--[[---------------------------------------------------------------------------------------]]
-- settings are saved in this file
local settingsfile = "APBMaster-PC" .. tostring(PCNo()) .. ".ini";

local function readSettings()
	OccupiedBlocks = {};		-- clear the occupied blocks list
    local f = io.open(settingsfile, "r");
    if f then
		print("Loading settings from " .. settingsfile);
		-- read occupied blocks from settings file if the file exists
		for iBlk = 1, BLOCK_COUNT do	-- up to BLOCK_COUNT blocks
			local BlockNo = f:read("*l");
			if (BlockNo == nil) then
				break;
				end;
			local iBlockNo = tonumber(string.match(BlockNo, "%d+"));
			if (iBlockNo == nil) then
				break;
				end;
			if (iBlockNo <= LAST_BLOCK) then
				print("Previously Occupied Block = " .. iBlockNo .. " " .. BlockName[iBlockNo]);
				OccupiedBlocks[iBlk] = iBlockNo;
				end;
			end;
        f:close()
		return true;
    end
	return false;
end

--[[---------------------------------------------------------------------------------------]]

local function writeSettings(pflag)
	-- setup must be complete before settings can be saved
	-- pflag == false for less printed output
	if (not SetupComplete) then
		return false;
		end;
    local f = io.open(settingsfile, "w");
    if f then
		print("Saving settings in " .. settingsfile);
		-- write occupied blocks to settings file
		for iBlk = 1, BLOCK_COUNT do	-- up to BLOCK_COUNT blocks
			if ((Layout4BlockOcc[iBlk] ~= nil) and (Layout4BlockOcc[iBlk] > 0)) then
				for iflagcount = 1, Layout4BlockOcc[iBlk] do
					-- Passing/station blocks can have a Layout4BlockOcc[] count of 0, 1, or 2,
					-- which, if 2, can generate multiple lines in the settings file.
					-- Blocks whose flag is (<0) are not written
					if (pflag) then print("Currently Occupied Block = " .. iBlk .. " " .. BlockName[iBlk]); end;
					f:write(tostring(iBlk) .. " " .. BlockName[iBlk] .. "\n");
					end;
				end;
			end;
        f:close()
		return true;
    end
	return false;
end
--[[---------------------------------------------------------------------------------------]]
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
	print("setup() Running");
	SetupComplete = false;
	-- Instrumented Layout 4
	Layout = require([[Layout4]]);		-- All of the details about the Layout
	--
	-- APB Master will work with either the Looping or the Point-to-Point Layout Configuration
	--	
	-- APB Master does not distinguish between "thru" and "passing/station" tracks they
	-- are considered as one block
	--
	-- The TIU which controls layout 4 is defined in the Layout4.lua file	
	MyTIUNo = Layout.MASTER_TIU;
	--
	-- Fix up passing/station blocks names
	BlockName[STRUTHERS_THRU_TRACK] = "Struthers Block";
	BlockName[STRUTHERS_STATION_TRACK] = "Struthers Block";
	BlockName[COLLEGE_THRU_TRACK] = "College Block";
	BlockName[COLLEGE_STATION_TRACK] = "College Block";
	BlockName[J_L_PASSING_TRACK] = "J&L Block";
	BlockName[J_L_THRU_TRACK] = "J&L Block";
	BlockName[GATEWAY_YARD1_TRACK] = "Gateway Yard Block";
	BlockName[GATEWAY_YARD2_TRACK] = "Gateway Yard Block";
	BlockName[MCKEES_ROCKS_REPAIR_SHOPS_YARD1_TRACK] = "McKees Rocks Repair Shops Block";
	BlockName[MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK] = "McKees Rocks Repair Shops Block";
	-- Possible better way to write it
	--BlockName[MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK] = string.gsub(BlockName[MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK], " 2 ", " ");
	--
	-- read in the occupied blocks from previous run
	--readSettings();
	--
	-- Requires 8 detectors and 13 tag readers
	local NumDetectors, NumReaders;
	NumDetectors, NumReaders = GetTagReaders();
	print("Number of Tag Detectors = " .. NumDetectors .. " Number of Tag Readers =  " .. NumReaders);
	if (Debug() == 0) then
		if (not CheckReader(1 , 0)) then
			return Stop ("Detector 1 missing");
			elseif (not CheckReader(2, 1)) then
				return Stop ("Detector 2 missing");
				elseif (not CheckReader(3, 0)) then
					return Stop ("Detector 3 missing");
					elseif (not CheckReader(4, 1)) then
						return Stop ("Detector 4 missing");
						elseif (not CheckReader(5, 1)) then
							return Stop ("Detector 5 missing");
							elseif (not CheckReader(6, 1)) then
								return Stop ("Detector 6 missing");
								elseif (not CheckReader(9, 0)) then
									return Stop ("Detector 9 missing");
									elseif (not CheckReader(10, 1)) then
										return Stop ("Detector 10 missing");
										else	
										print("All required tag detectors found");
										end;
		end;

	if ((Debug() == 0) and (NumDetectors < 8 or (NumReaders < 13 and NumReaders ~= 0))) then
		return Stop("Requires 8 Tag Detectors and 13 Tag Readers. Cycle power on layout and try again.");
		end;
	--	
	print("Showing the Layout Window");
	ShowLayout();		-- show the Layout Window
	--
	-- Next line not needed for normal operation - This script should run independently from Engine control scripts
	-- It is used for testing with "Layout4 APB XX Occupancy.lua" scripts only
	ReqBlockEvents(true);	-- request block occupancy flag events from the Layout to keep our data up to date	
	--
	DeleteAllSignals();	-- clear the window of any old signals
	print("Creating Signals");
	-- Signals in each direction
	-- Most signals are Permissive
	-- Signals controlling entrance into head blocks from sidings are Absolute
	-- Route runs EAST <-> WEST
-----------------------------------------------------------------------------------------------
--[[	
	Initialize signals to red to cover possibile single track
    occupancies at startup resulting in false setting of
    traffic sticks, i.e. sticks set for both directions
    when there should be no traffic sticks set
]]   
-----------------------------------------------------------------------------------------------
--[[
	Remember that switches set to REVERSE appear as an occupied block
	It may be nessary to set switches to NORMAL to allow trains
	to move into that block.
]]
-----------------------------------------------------------------------------------------------
	-- Balloon Switch is set in Layout4.lua
	--
	print("Setting all siding switches to NORMAL - Please Wait");
	Switch(NORMAL, Switches[ALIQUIPPA_WEST][CHAN_NUMBER], Switches[ALIQUIPPA_WEST][AIU_NUMBER], Switches[ALIQUIPPA_WEST][TIU_NUMBER], 0)
	--print("Aliquippa West Switch to NORMAL"); Sleep(2);
	Switch(NORMAL, Switches[ALIQUIPPA_EAST][CHAN_NUMBER], Switches[ALIQUIPPA_EAST][AIU_NUMBER], Switches[ALIQUIPPA_EAST][TIU_NUMBER], 0)
	--print("Aliquippa East Switch to NORMAL"); Sleep(2);
	Switch(NORMAL, Switches[COLLEGE_WEST][CHAN_NUMBER], Switches[COLLEGE_WEST][AIU_NUMBER], Switches[COLLEGE_WEST][TIU_NUMBER], 0)
	--print("College West Switch to NORMAL"); Sleep(2);
	Switch(NORMAL, Switches[COLLEGE_EAST][CHAN_NUMBER], Switches[COLLEGE_EAST][AIU_NUMBER], Switches[COLLEGE_EAST][TIU_NUMBER], 0)
	--print("College East Switch to NORMAL"); Sleep(2);
	Switch(NORMAL, Switches[STRUTHERS_WEST][CHAN_NUMBER], Switches[STRUTHERS_WEST][AIU_NUMBER], Switches[STRUTHERS_WEST][TIU_NUMBER], 0)
	--print("Struthers West Switch to NORMAL"); Sleep(2);
	Switch(NORMAL, Switches[STRUTHERS_EAST][CHAN_NUMBER], Switches[STRUTHERS_EAST][AIU_NUMBER], Switches[STRUTHERS_EAST][TIU_NUMBER], 0)
	--print("Struthers East Switch to NORMAL"); Sleep(2);
	Switch(NORMAL, Switches[WOODLAWN][CHAN_NUMBER], Switches[WOODLAWN][AIU_NUMBER], Switches[WOODLAWN][TIU_NUMBER], 0)
	--print("WOODLAWN Switch to NORMAL"); Sleep(2);
	Switch(NORMAL, Switches[CAMPBELL][CHAN_NUMBER], Switches[CAMPBELL][AIU_NUMBER], Switches[CAMPBELL][TIU_NUMBER], 0)
	--print("Campbell Switch to NORMAL"); Sleep(2);
	Sleep(3);	-- time for all of the switches to align
	print("All siding switches set to NORMAL");
	
	-- Here consider setting other switches to Normal
	-- J_L_SLAG_TRACK
	-- J_L_MILL_TRACK
	-- ALIQUIPPA_TEAM
	-- BYPASS_WEST
	-- BYPASS_EAST
	-- PM_MOORE
	
	-- Note: BALLOON is set in this script
-----------------------------------------------------------------------------------------------
--	
--[[ Real World
	local Permissive = Hd2Light;
	local Absolute = Hd1Light;
	local HeadBlock = Hd2Light;
	--]]
-- Layout Signals - use this when running with 3 LED signals developed by Mike Hewett
	local Permissive = HdMLight;
	local Absolute = HdMLight;
	local HeadBlock = HdMLight;
	
--	
-- Westbound
--
	CreateSignal( 1, WB, PERMISSIVE_IN_OPEN_COUNTRY, 370,280, SignalNames[1], AsClear, Permissive, 1);		-- Permissive signal in open country
	FSR[1] = NDT;
	Sig[1] = AsClear;
	TUMCNT[1] = 0;
--	
	CreateSignal( 5, WB, ABSOLUTE_HEAD_BLOCK, 780,280, SignalNames[5], AsClear, Absolute, 0);		-- Absolute Head Block Signal
    FSR[5] = NDT;	-- not used
	Sig[5] = AsClear;
	TUMCNT[5] = 0;
--
	CreateSignal( 9, WB, PERMISSIVE_HEAD_BLOCK, 1190,240, SignalNames[9], AsClear, HeadBlock, 9);	-- Permissive head block signal
	FSR[9] = NDT;
	Sig[9] = AsClear;
	TUMCNT[9] = 0;
--
	CreateSignal(11, WB, APPROACH_TO_HEAD_BLOCK, 1270,240, SignalNames[11], AsClear, Permissive, 11);	-- Signal in approach to head block signal
	FSR[11] = NDT;
	Sig[11] = AsClear;
	TUMCNT[11] = 0;
--	
	CreateSignal(13, WB, ABSOLUTE_HEAD_BLOCK, 1450,240, SignalNames[13], AsClear, Absolute, 0);		-- Absolute Head Block Signal
	FSR[13] = NDT;	-- not used
	Sig[13] = AsClear;
	TUMCNT[13] = 0;
--
	CreateSignal(17, WB, PERMISSIVE_HEAD_BLOCK, 1750,240, SignalNames[17], AsClear, HeadBlock, 17);	-- Permissive head block signal
	FSR[17] = NDT;	-- not used
	Sig[17] = AsClear;
	TUMCNT[17] = 0;
--
	CreateSignal(19, WB, APPROACH_TO_HEAD_BLOCK, 1833,240, SignalNames[19], AsClear, Permissive, 19);	-- Signal in approach to head block signal
	FSR[19] = NDT;
	Sig[19] = AsClear;
	TUMCNT[19] = 0;
--
	CreateSignal(21, WB, PERMISSIVE_IN_OPEN_COUNTRY, 2000,240, SignalNames[21], AsClear, Permissive, 21);	-- Permissive signal in open country
	FSR[21] = NDT;
	Sig[21] = AsClear;
	TUMCNT[21] = 0;
--
	CreateSignal(25, WB, PERMISSIVE_IN_OPEN_COUNTRY, 2100,240, SignalNames[25], AsClear, Permissive, 25);	-- Permissive signal in open country
	FSR[25] = NDT;
	Sig[25] = AsClear;
	TUMCNT[25] = 0;
--
	CreateSignal(27, WB, PERMISSIVE_IN_OPEN_COUNTRY, 2300,240, SignalNames[27], AsClear, Permissive, 27);	-- Permissive signal in open country
	FSR[27] = NDT;
	Sig[27] = AsClear;
	TUMCNT[27] = 0;
--
	CreateSignal(29, WB, ABSOLUTE_HEAD_BLOCK, 2675,240, SignalNames[29], AsClear, Absolute, 0);		-- Absolute Head Block Signal
	FSR[29] = NDT;	-- not used
	Sig[29] = AsClear;
	TUMCNT[29] = 0;
--
	CreateSignal(33, WB, PERMISSIVE_HEAD_BLOCK, 3280,240, SignalNames[33], AsClear, HeadBlock, 33);	-- Permissive head block signal
	FSR[33] = NDT;	-- not used
	Sig[33] = AsClear;
	TUMCNT[33] = 0;
--
	if (Layout.LLLoop) then
		-- in Loop Configuration
		CreateSignal(37, WB, APPROACH_TO_HEAD_BLOCK, 3430,240, SignalNames[37], AsClear, Permissive, 37);	-- Signal in approach to head block signal
		FSR[37] = NDT;
		Sig[37] = AsClear;
		TUMCNT[37] = 0;
		else
		-- in Point-to-Point Configuration
		CreateSignal(37, WB, ABSOLUTE_HEAD_BLOCK, 3430,240, SignalNames[37], AsClear, Absolute, 0);	-- Absolute Head Block Signal
		FSR[37] = NDT;
		Sig[37] = AsClear;
		TUMCNT[37] = 0;
		end;
--
	CreateSignal(43, WB, PERMISSIVE_IN_OPEN_COUNTRY, 2410,160, SignalNames[43], AsClear, Permissive, 43);	-- Permissive signal in open country
	FSR[43] = NDT;
	Sig[43] = AsClear;
	TUMCNT[43] = 0;
--
	Sig[47] = AsClear;
	CreateSignal(47, WB, PERMISSIVE_IN_OPEN_COUNTRY, 630,140, SignalNames[47], Sig[47], Permissive, 47);	-- Permissive signal in open country
	FSR[47] = NDT;
	TUMCNT[47] = 0;
-----------------------------------------------------------------------------------------------
--
-- Eastbound
--	
-- TBD: change Y coord of most even signals to move them down
-- change Y coord of signals 48 and 47 to move them up
--
	if (Layout.LLLoop) then
		-- in Loop Configuration
		CreateSignal(2, EB, APPROACH_TO_HEAD_BLOCK, 370,470, SignalNames[2], AsClear, Permissive, 2);		-- Signal in approach to head block signal
		FSR[2] = NDT;
		Sig[2] = AsClear;
		TUMCNT[2] = 0;
		else
		-- in Point-to-Point Configuration
		CreateSignal(2, EB, ABSOLUTE_HEAD_BLOCK, 370,470, SignalNames[2], AsClear, Absolute, 0);		-- Absolute Head Block Signal
		FSR[2] = NDT;
		Sig[2] = AsClear;
		TUMCNT[2] = 0;
		end;
--
	CreateSignal(6, EB, PERMISSIVE_HEAD_BLOCK, 780,470, SignalNames[6], AsClear, HeadBlock, 6);		-- Permissive head block signal
	FSR[6] = NDT;	-- not used
	Sig[6] = AsClear;
	TUMCNT[6] = 0;
--
	CreateSignal(10, EB, ABSOLUTE_HEAD_BLOCK, 1190,470, SignalNames[10], AsClear, Absolute, 0);		-- Absolute Head Block Signal
	FSR[10] = NDT;	-- not used
	Sig[10] = AsClear;
	TUMCNT[10] = 0;
--	
	CreateSignal(12, EB, APPROACH_TO_HEAD_BLOCK, 1270,470, SignalNames[12], AsClear, Permissive, 12);	-- Signal in approach to head block signal
	FSR[12] = NDT;
	Sig[12] = AsClear;
	TUMCNT[12] = 0;	
--
	CreateSignal(14, EB, PERMISSIVE_HEAD_BLOCK, 1450,470, SignalNames[14], AsClear, HeadBlock, 14);	-- Permissive head block signal
	FSR[14] = NDT;	-- not used
	Sig[14] = AsClear;
	TUMCNT[14] = 0;
--
	CreateSignal(18, EB, ABSOLUTE_HEAD_BLOCK, 1750,470, SignalNames[18], AsClear, Absolute, 0);		-- Absolute Head Block Signal
	FSR[18] = NDT;	-- not used
	Sig[18] = AsClear;
	TUMCNT[18] = 0;
--
	CreateSignal(20, EB, PERMISSIVE_IN_OPEN_COUNTRY, 1833,470, SignalNames[20], AsClear, Permissive, 20);	-- Permissive signal in open country
	FSR[20] = NDT;
	Sig[20] = AsClear;
	TUMCNT[20] = 0;
--
	CreateSignal(22, EB, PERMISSIVE_IN_OPEN_COUNTRY, 2000,470, SignalNames[22], AsClear, Permissive, 22);	-- Permissive signal in open country
	FSR[22] = NDT;
	Sig[22] = AsClear;
	TUMCNT[22] = 0;
--
	CreateSignal(26, EB, PERMISSIVE_IN_OPEN_COUNTRY, 2100,470, SignalNames[26], AsClear, Permissive, 26);	-- Permissive signal in open country
	FSR[26] = NDT;
	Sig[26] = AsClear;
	TUMCNT[26] = 0;
--
	CreateSignal(28, EB, APPROACH_TO_HEAD_BLOCK, 2300,470, SignalNames[28], AsClear, Permissive, 28);	-- Signal in approach to head block signal
	FSR[28] = NDT;
	Sig[28] = AsClear;
	TUMCNT[28] = 0;
--	
	CreateSignal(30, EB, PERMISSIVE_HEAD_BLOCK, 2675,470, SignalNames[30], AsClear, HeadBlock, 30);	-- Permissive head block signal
	FSR[30] = NDT;	-- not used
	Sig[30] = AsClear;
	TUMCNT[30] = 0;
--
	CreateSignal(34, EB, ABSOLUTE_HEAD_BLOCK, 3280,470, SignalNames[34], AsClear, Absolute, 0);		-- Absolute Head Block Signal
	FSR[34] = NDT;	-- not used
	Sig[34] = AsClear;
	TUMCNT[34] = 0;
--
	CreateSignal(38, EB, PERMISSIVE_IN_OPEN_COUNTRY, 3430,470, SignalNames[38], AsClear, Permissive, 38);	-- Permissive signal in open country
	FSR[38] = NDT;
	Sig[38] = AsClear;
	TUMCNT[38] = 0;
--
	CreateSignal(44, EB, PERMISSIVE_IN_OPEN_COUNTRY, 2480,160, SignalNames[44], AsClear, Permissive, 44);	-- Permissive signal in open country
	FSR[44] = NDT;
	Sig[44] = AsClear;
	TUMCNT[44] = 0;
--	
	Sig[48] = AsClear;
	CreateSignal(48, EB, PERMISSIVE_IN_OPEN_COUNTRY, 560,140, SignalNames[48], Sig[48], Permissive, 48);	-- Permissive signal in open country
	FSR[48] = NDT;
	TUMCNT[48] = 0;
--
	print("Signals Created");

-- save the current values
local orig_key, orig_value;
for orig_key, orig_value in pairs(Sig) do
	--print("Signal copy [", orig_key, "] = " , AspectNames[math.abs(orig_value)]);
    SavedSig[orig_key] = orig_value;	
	end;

-- clear all tag handling functions
Func30 = nil;
Func51 = nil;
Func10 = nil;
Func41 = nil;
Func21 = nil;
Func20 = nil;
Func90 = nil;
Func40 = nil;
Func50 = nil;
Func60 = nil;
Func61 = nil;
Func100 = nil;
Func101 = nil;
Func110 = nil;
Func150 = nil;

-- Unoccupy all Blocks
print("Set all Blocks as Unoccupied");
for iBlock = FIRST_BLOCK, LAST_BLOCK do
	Layout4BlockOcc[iBlock] = UNOCCUPIED;											-- Layout4BlockOcc[] is used by this APB Master thread
	-- this script clears the occupancy flags as we startup a run
	::nextBlockUnoccupied::
	end;


--[[--------------------------------------------]]
--
--BlockNameList[1] = "0 None";	-- use index 1 for "None"
--
-- Create list of block names in BlockNameList
--
BlockNameList[1] = NO_BLOCK .. " " .. BlockName[NO_BLOCK];	-- use index 1 for "None"
local iBlk = 2;
for iBlock = 1, BLOCK_COUNT do
	if (iBlock <= LAST_BLOCK) then
		-- in Point-to-Point mode, don't allow selection of Balloon Track
		if (Layout.LLLoop == false and iBlock == BALLOON_BLOCK) then
			goto nextBlock
			end;
			-- in Loop Configuration, don't allow selection of any Gateway or McKees Rocks yard track
		if ((Layout.LLLoop == true) and ((iBlock == MCKEES_ROCKS_REPAIR_SHOPS_YARD1_TRACK) or (iBlock == MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK) or 
			(iBlock == GATEWAY_YARD1_TRACK) or (iBlock == GATEWAY_YARD2_TRACK))) then
			goto nextBlock
			end			
		if ((iBlock == J_L_PASSING_TRACK) or (iBlock == COLLEGE_STATION_TRACK) or (iBlock == STRUTHERS_STATION_TRACK) or
			(iBlock == MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK) or (iBlock == GATEWAY_YARD2_TRACK)) then		
			-- APB system treats THRU and STATION/PASSING as the same track
			-- APB system treats YARD1 and YARD2 as the same track
			goto nextBlock;
			end;			
		--
		--print(Name .. "(" .. iBlock .. ")");
		BlockNameList[iBlk] = iBlock .. " " .. BlockName[iBlock];
		iBlk = iBlk + 1;
		::nextBlock::
		end;
	end;
--
--print("BlockNameList -------------------------------");
--PrettyPrint(BlockNameList);
--print("BlockNameList -------------------------------");
print("Approach Lighting set to " .. tostring(ApproachLighting));
--
-- spot for future call to AskBlocks(title, "Select Occupied Block ('None' for no more)")
--
-- indicate that setup is complete and the settings can be saved
-- and occupied blocks can be selected or cleared
SetupComplete = true;
--
-- Stop the Garbage collector since this is a real-time program
collectgarbage("stop")
print("Setup : Garbage Collection stopped");
--collectgarbage("collect")
--collectgarbage("restart")
--
-- Now as soon as the first engine passes over a reader, it will change the Layout4BlockOcc[] Occupancy flags and cause signals to change
--
return true;		-- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
-- SET TRAFFIC STICK UPON SEQUENTIAL TRAIN MOVEMENT	
-- use (western side block, eastern side block, westbound signal #, eastbound signal #)
function SetTrafficStick(WestSideBlock, EastSideBlock, WestSideSig, EastSideSig)
--print("SetTrafficStick() " .. Layout4BlockOcc[WestSideBlock] .. " " .. Layout4BlockOcc[EastSideBlock] .. " " .. FSR[WestSideSig] .. " " .. FSR[EastSideSig]);
if (((Layout4BlockOcc[WestSideBlock] ~= UNOCCUPIED) and (Layout4BlockOcc[EastSideBlock] ~= UNOCCUPIED)) and ((FSR[WestSideSig] == NDT) and (FSR[EastSideSig] == NDT))) then
	if (Sig[WestSideSig] ~= AsStopSignal and Sig[WestSideSig] ~= AsStopandProceed) then
		printc(clRed,"Traffic Stick Relay at signal #", WestSideSig, " set to WEST");
		FSR[WestSideSig] = WEST;
		end;
	if (Sig[EastSideSig] ~= AsStopSignal and Sig[EastSideSig] ~= AsStopandProceed) then
		printc(clRed, "Traffic Stick Relay at signal #", EastSideSig, " set to EAST");
		FSR[EastSideSig] = EAST;
		end;
	end;	
end;
--[[---------------------------------------------------------------------------------------]]
function ClearTrafficStick(SigNo)
-- CLEAR TRAFFIC STICK FOLLOWING SIGNAL UPGRADES
if (Sig[SigNo] ~= AsStopSignal and Sig[SigNo] ~= AsStopandProceed and TUMCNT[SigNo] == 0) then 
	FSR[SigNo] = NDT; 
	end;
end;
--
--
--[=[
APB_Program_for_August_Part_8_RMC
by Bruce Chubb

Rem**CALCULATE EASTBOUND SIGNAL ASPECTS**
.
Rem**Calculate Signal 124 Permissive in open country
S124: If FS123 = WEST Then GoTo S124RED
      If BK15 = OCC Then GoTo S124RED
      If SIG136 <> RED Then SIG124 = GRN: TUMCNT124 = 0: GoTo S114
      If FS136 = EAST Then SIG124 = YEL: TUMCNT124 = 0: GoTo S114
S124RED: If SIG124 = RED Then GoTo S114
      If TUMCNT124 = TUMMAX Then SIG124 = RED: TUMCNT124 = 0: GoTo S142
      TUMCNT124 = TUMCNT124 + 1

Rem**Calculate Signal 114 Permissive in open country
S114: If FS113 = WEST Then GoTo S114RED
      If BK14 = OCC Then GoTo S114RED
      If SIG124 <> RED Then SIG114 = GRN: TUMCNT114 = 0: GoTo S106
      If FS124 = EAST Then SIG114 = YEL: TUMCNT114 = 0: GoTo S106
S114RED: If SIG114 = RED Then GoTo S106
      If TUMCNT114 = TUMMAX Then SIG114 = RED: TUMCNT114 = 0: GoTo S106
      TUMCNT114 = TUMCNT114 + 1

Rem**Calculate Signal 106 Head Block Absolute
S106: If BK13 = OCC Then GoTo S106RED
      If SIG114 <> RED Then SIG106 = GRN: TUMCNT106 = 0: GoTo S94
      If FS114 = EAST Then SIG106 = YEL: TUMCNT106 = 0: GoTo S94
S106RED: If SIG106 = RED Then GoTo S94
      If TUMCNT106 = TUMMAX Then SIG106 = RED: TUMCNT106 = 0: GoTo S94
      TUMCNT106 = TUMCNT106 + 1

Rem**Calculate Signal 94 Head Block Permissive
S94:  If BK12 = OCC Then GoTo S94RED
      If (TU1 = TUR Or TU2 = TUR) Then GoTo S94RED
      If SIG106 <> RED Then SIG94 = GRN Else SIG94 = YEL
      TUMCNT94 = 0: GoTo S88
S94RED: If SIG94 = RED Then GoTo S88
      If TUMCNT94 = TUMMAX Then SIG94 = RED: TUMCNT94 = 0: GoTo S88
      TUMCNT94 = TUMCNT94 + 1

Rem**Calculate Signal 88 Approach to Head Block
S88:  If FS87 = WEST Then GoTo S88RED
      If BK11 = OCC Then GoTo S88RED
      If SIG94 <> RED Then SIG88 = GRN Else SIG88 = YEL
      TUMCNT88 = 0: GoTo S76
S88RED: If SIG88 = RED Then GoTo S76
      If TUMCNT88 = TUMMAX Then SIG88 = RED: TUMCNT88 = 0: GoTo S76
      TUMCNT88 = TUMCNT88 + 1

Rem**Calculate Signal 76 Permissive in open country
S76:  If FS179 = WEST Then GoTo S76RED
      If BK10 = OCC Then GoTo S76RED
      If SIG88 <> RED Then SIG76 = GRN: TUMCNT76 = 0: GoTo S87
      If FS88 = EAST Then SIG76 = YEL: TUMCNT76 = 0: GoTo S87
S76RED: If SIG76 = RED Then GoTo S87
      If TUMCNT76 = TUMMAX Then SIG76 = RED: TUMCNT76 = 0: GoTo S87
      TUMCNT76 = TUMCNT76 + 1

Rem**CALCULATE WESTBOUND SIGNAL ASPECTS**
Rem**Calculate Signal 87 Permissive in open country
S87:  	  
--]=]
--
-- In the next 4 functions:
-- Only ThisSig is changed
-- OppositeSig and NextSig are never changed
--
function PermissiveinOpenCountry(NextBlock, ThisSig, OppositeSig, NextSig, MyDir, OppositeDir)	-- Uses Traffic Sticks
	-- This function never changes OppositeSig
	-- this function never changes NextSig, NextSig can be zero
	--print("PermissiveinOpenCountry(" .. NextBlock .. "," .. ThisSig .. "," .. OppositeSig .. "," .. NextSig .. "," .. (MyDir or "nil") .. "," .. OppositeDir .. ")");

	if (FSR[OppositeSig] == nil) then Stop("Traffic Stick Relay nil in PermissiveinOpenCountry - Opposite Side #" .. OppositeSig); end;
	if (NextSig ~= 0 and FSR[NextSig] == nil) then Stop("Traffic Stick Relay nil in PermissiveinOpenCountry  - Next Signal This Side # " .. NextSig); end;
	----------------------------------------------------------------------------
	-- Calculate Signal Permissive in open country using Traffic stick on opposite and next signal    
	if (FSR[OppositeSig] == OppositeDir) then goto POCAsStopandProceed; end;
    if (Layout4BlockOcc[NextBlock] ~= UNOCCUPIED) then goto POCAsStopandProceed; end;
	-- Check for both kinds of Stop Aspects, if there is no NextSig (== 0) then set ThisSig to AsClear
    if (Sig[NextSig] ~= AsStopSignal and Sig[NextSig] ~= AsStopandProceed) then Sig[ThisSig] = AsClear; TUMCNT[ThisSig] = 0; goto POCDone; end;
    if (FSR[NextSig] == MyDir) then Sig[ThisSig] = AsApproach; TUMCNT[ThisSig] = 0; goto POCDone; end;	-- if NextSig == 0, never gets to this line
::POCAsStopandProceed::
    if (Sig[ThisSig] == AsStopandProceed) then goto POCDone; end;
    if (TUMCNT[ThisSig] == TUMMAX) then Sig[ThisSig] = AsStopandProceed; TUMCNT[ThisSig] = 0; goto POCDone; end;
    TUMCNT[ThisSig] = TUMCNT[ThisSig] + 1;
::POCDone::	  
	  return;
end;

function HeadBlockPermissive(NextBlock, ThisSig, OppositeSig, NextSig, Switch1, Switch2, MyDir, OppositeDir)
	-- This function never accesses or changes OppositeSig
	-- this function never changes NextSig	
	--print("HeadBlockPermissive(" .. NextBlock .. "," .. ThisSig .. "," .. (OppositeSig or "nil") .. "," .. NextSig .. "," .. (MyDir or "nil") .. "," .. (OppositeDir or "nil") .. ")");
	--Calculate Signal Head Block Permissive - no Traffic Stick involved
	--print("HBP Next Block ", BlockName[NextBlock], " Layout4BlockOcc[NextBlock] ", Layout4BlockOcc[NextBlock]);
	----------------------------------------------------------------------------
    if (Layout4BlockOcc[NextBlock] ~= UNOCCUPIED) then goto HBPAsStopandProceed; end;
	-- per Bruce Chubb's book, both switches on a siding must be checked
    if (GetSwitchStatus(Switches[Switch1][CHAN_NUMBER], Switches[Switch1][AIU_NUMBER], Switches[Switch1][TIU_NUMBER]) == REVERSE or 
		GetSwitchStatus(Switches[Switch2][CHAN_NUMBER], Switches[Switch2][AIU_NUMBER], Switches[Switch2][TIU_NUMBER]) == REVERSE) then
		goto HBPAsStopandProceed;
	  end;
	-- Check for both kinds of Stop Aspects		
    if (Sig[NextSig] ~= AsStopSignal and Sig[NextSig] ~= AsStopandProceed) then Sig[ThisSig] = AsClear;
		else Sig[ThisSig] = AsApproach; end
    TUMCNT[ThisSig] = 0;
	goto HBPDone;	  
::HBPAsStopandProceed::
    if (Sig[ThisSig] == AsStopandProceed) then goto HBPDone; end;
    if (TUMCNT[ThisSig] == TUMMAX) then Sig[ThisSig] = AsStopandProceed; TUMCNT[ThisSig] = 0; goto HBPDone; end;
    TUMCNT[ThisSig] = TUMCNT[ThisSig] + 1;
::HBPDone::
	return;
end;

function HeadBlockAbsolute(NextBlock, ThisSig, OppositeSig, NextSig, MyDir, OppositeDir)	-- Uses Traffic Sticks
	-- This function never accesses or changes OppositeSig
	-- this function never changes NextSig
	--print("HeadBlockAbsolute(" .. NextBlock .. "," .. ThisSig .. "," .. (OppositeSig or "nil") .. "," .. NextSig .. "," .. (MyDir or "nil") .. "," .. (OppositeDir or "nil") .. ")");
	-- THIS FUNCTION FAILS if less than 2 blocks between sidings
	if (FSR[NextSig] == nil) then
		Stop("Traffic Stick Relay nil in HeadBlockAbsolute - Next Signal This Side #" .. NextSig);
		end;
	----------------------------------------------------------------------------
	--Calculate Signal Head Block Absolute using Traffic Stick on next signal
    if (Layout4BlockOcc[NextBlock] ~= UNOCCUPIED) then goto HBAAsStop; end; -- Stop - next block is occupied by virtue of a train being in the block
	-- Otherwise the next block is unoccupied
	--
	-- if next signal (signal in advance of ThisSig) is not Stop, then set signal to clear indicating we are clear two blocks ahead with no opposing traffic
	-- Check for both kinds of Stop Aspects
    if (Sig[NextSig] ~= AsStopSignal and Sig[NextSig] ~= AsStopandProceed) then Sig[ThisSig] = AsClear; TUMCNT[ThisSig] = 0; goto HBADone; end;
	-- if traffic stick ahead indicates I'm following traffic then set signal to Approach indicating we are clear one block ahead and must prepare to stop.
    if (FSR[NextSig] == MyDir) then Sig[ThisSig] = AsApproach; TUMCNT[ThisSig] = 0; goto HBADone; end;
	-- if next signal is Stop and traffic stick indicates opposing traffic, then set signal to Stop by falling through to next code
::HBAAsStop::
    if (Sig[ThisSig] == AsStopSignal) then goto HBADone; end;
    if (TUMCNT[ThisSig] == TUMMAX) then Sig[ThisSig] = AsStopSignal; TUMCNT[ThisSig] = 0; goto HBADone; end;
    TUMCNT[ThisSig] = TUMCNT[ThisSig] + 1;
::HBADone::
	return;
end;

-- Special case of HeadBlockAbsolute() for the case were there
-- only 1 block between head block signals.
-- In this case, the stick relay information is not available.
-- The absolute signal will not indicate Stop but will
-- indicate Approach. The engineer must know which track
-- he will enter.
--[[
Communication with Bruce Chubb:
------------------------------
>
> Bruce,
>
> In your book on APB signaling, you say that the merits of APB
> only show up when you have two or more blocks between passing
> sidings.
>
> All of the examples that I can find in the book and in the RMC
> article series show the case where there are
> three or more blocks between passing sidings.
>
> Are there any examples of APB where there are 0, 1, or 2 blocks
> between sidings? Specifically I'm trying to understand if APB
> will still work and what the code would look like for
> those cases.
>
> Thanks for your help.
>
> Mark DiVecchio
------------------------------
>
>
> Very, very simple with 0, 1 or 2 blocks between passing siding. Don't have to be concerned about APB and no concern about handleing traffic sticks, etc...For example, if have
> only 0blocks between sidings, then: If turnout at opposite end of passing siding is occupied then keep signal at red If opposing signal is cleared or is running
> time, for the route that is aligned by the opposite end turnout, then keep signal at red. Mainly, just use "common sense" as what are all the things that need to be checkedred
> for it being safe to clear a signal. If have block between signing, then just add more more check:  If bock between passing siding is occupied, then keep signal at
> red. If turnout at opposite end of passing siding is occupied then keep signal at red If opposing signal is cleared or is running time, for the route that is aligned
> by the opposite end turnout, then keep signal at red. If have two blocks between siding, then need to add one more check If immediate block is occupied, then keep signal redIf
> second block is occupied and traffic is moving toward your signa that trying to clear, then keep signal redIf opposing signal is cleared or is running time, for the route that
> is aligned by the opposite end turnout, then keep signal at red.
>
> To determine traffic direction you can use the same technique used for APB. Once traffic direction stays set until the block becomes clear.
> Bruce
>
>
------------------------------
Bruce,

Have you ever written the BASIC code for these three cases?

Mark
------------------------------
The code should be in the Model Railroader 4-part series "Signaling Made Easier" that should be on the CD that comes with the User's Manual.
Attaching Parts 1 and 2 in case you do not have the CD handy.
Bruce


--]]
function HeadBlockAbsolute0(NextBlock, ThisSig, OppositeSig, NextSig, MyDir, OppositeDir)	-- Uses Traffic Sticks
	-- This function never accesses or changes OppositeSig
	-- this function never changes NextSig
	--print("HeadBlockAbsolute(" .. NextBlock .. "," .. ThisSig .. "," .. (OppositeSig or "nil") .. "," .. NextSig .. "," .. (MyDir or "nil") .. "," .. (OppositeDir or "nil") .. ")");
	----------------------------------------------------------------------------
	--Calculate Signal Head Block Absolute using Traffic Stick on next signal
    if (Layout4BlockOcc[NextBlock] ~= UNOCCUPIED) then goto HBAAsStop; end; -- Stop - next block is occupied by virtue of a train being in the block
	-- Otherwise the next block is unoccupied
	--
	-- if next signal (signal in advance of ThisSig) is not Stop, then set signal to clear indicating we are clear two blocks ahead with no opposing traffic
	-- Check for both kinds of Stop Aspects
    if (Sig[NextSig] ~= AsStopSignal and Sig[NextSig] ~= AsStopandProceed) then Sig[ThisSig] = AsClear; TUMCNT[ThisSig] = 0; goto HBADone; end;
	-- set signal to Approach indicating we are clear one block ahead and must prepare to stop.
	Sig[ThisSig] = AsApproach;
	TUMCNT[ThisSig] = 0;
	goto HBADone;
::HBAAsStop::
    if (Sig[ThisSig] == AsStopSignal) then goto HBADone; end;
    if (TUMCNT[ThisSig] == TUMMAX) then Sig[ThisSig] = AsStopSignal; TUMCNT[ThisSig] = 0; goto HBADone; end;
    TUMCNT[ThisSig] = TUMCNT[ThisSig] + 1;
::HBADone::
	return;
end;

function ApproachtoHeadBlock(NextBlock, ThisSig, OppositeSig, NextSig, MyDir, OppositeDir)	-- Uses Traffic Sticks
	-- This function never changes OppositeSig
	-- this function never changes NextSig, NextSig can be zero
	--print("ApproachtoHeadBlock(" .. NextBlock .. "," .. ThisSig .. "," .. OppositeSig .. "," .. NextSig .. "," .. (MyDir or "nil") .. "," .. OppositeDir .. ")");
	if (FSR[OppositeSig] == nil) then Stop("Traffic Stick Relay nil in ApproachtoHeadBlock - Opposite Side #" .. OppositeSig); end;
	----------------------------------------------------------------------------
	--Calculate Signal Approach to Head Block using Traffic Stick on opposite signal
    if (FSR[OppositeSig] == OppositeDir) then goto AHBAsStopandProceed; end;
    if (Layout4BlockOcc[NextBlock] ~= UNOCCUPIED) then goto AHBAsStopandProceed; end;
	-- Check for both kinds of Stop Aspects
    if (NextSig == 0 or (Sig[NextSig] ~= AsStopSignal and Sig[NextSig] ~= AsStopandProceed)) then Sig[ThisSig] = AsClear; else Sig[ThisSig] = AsApproach; end
    TUMCNT[ThisSig] = 0; 
	goto AHBDone;
::AHBAsStopandProceed::
    if (Sig[ThisSig] == AsStopandProceed) then goto AHBDone; end;
    if (TUMCNT[ThisSig] == TUMMAX) then Sig[ThisSig] = AsStopandProceed; TUMCNT[ThisSig] = 0; goto AHBDone; end;
    TUMCNT[ThisSig] = TUMCNT[ThisSig] + 1;
::AHBDone::	  
	return;
end;

--[[---------------------------------------------------------------------------------------]]

function loop()
-- Always call Sleep(X) at least once in this function if it returns true;
--print("loop() Running ", RunTime(), " seconds");

-- Read Occupancy flags
-- This is all done in the tag() function starting from initial occupancy conditions

-- SET TRAFFIC STICK UPON SEQUENTIAL TRAIN MOVEMENT
-- parameters :  SetTrafficStick(west side block, east side block, WB signal #, EB signal #)
-- From Chubb: An APB system by its very nature needs to know directional as well as occupancy infomation.
-- APB employs a "traffic stick" relay for each and every intermediate signal within APB territory with
-- the exception of those at head block locations.
-- For layout4, the head block signals are 5/6, 9/10, 13/14, 17/18, 29/30 and 33/34
--
--
if (Layout.LLLoop == true) then
	-- in Loop Configuration
	SetTrafficStick(BALLOON_BLOCK, YOUNGSTOWN_BLOCK, 1, 2);	-- Loop Configuration
	else
	-- in Point-to-Point Configuration, no traffic stick
	--SetTrafficStick(GATEWAY_YARD_BLOCK, YOUNGSTOWN_BLOCK, 1, 2);	-- Point-to-Point Configuration
	end
-- 2 or more blocks between sidings
SetTrafficStick(LOWELLVILLE_BLOCK, NEW_CASTLE_JUNCTION_BLOCK, 11, 12);
SetTrafficStick(BEAVER_FALLS_BLOCK, FALLSTON_BLOCK, 19, 20);
SetTrafficStick(FALLSTON_BLOCK, MONACA_BLOCK, 21, 22);
SetTrafficStick(MONACA_BLOCK, WEST_ALIQUIPPA_BLOCK, 25, 26);
SetTrafficStick(WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, 27, 28);
--add 43/44 and 47/48 stick relays
SetTrafficStick(ALIQUIPPA_BLOCK, ALIQUIPPA_CUTOFF, 43, 44);
SetTrafficStick(ALIQUIPPA_CUTOFF, STRUTHERS_BLOCK, 47, 48);

if (Layout.LLLoop == true) then
	-- in Loop Configuration
	SetTrafficStick(MCKEES_ROCKS_BLOCK, BALLOON_BLOCK, 37, 38);	-- Loop Configuration
	else
	-- in Point-to-Point Configuration, no traffic stick
	--SetTrafficStick(MCKEES_ROCKS_BLOCK, MCKEES_ROCKS_REPAIR_SHOPS_YARD_BLOCK, 37, 38);	-- Point-to-Point Configuration
	end

-- CALCULATE WESTBOUND SIGNAL ASPECTS
--add 43/44 and 47/48 westbound PermissiveinOpenCountry
PermissiveinOpenCountry(ALIQUIPPA_CUTOFF, 47, 48, 43, WEST, EAST);
PermissiveinOpenCountry(ALIQUIPPA_BLOCK, 43, 44, 27, WEST, EAST);

if (Layout.LLLoop == true) then
	-- in Loop Configuration
	ApproachtoHeadBlock(MCKEES_ROCKS_BLOCK, 37, 38, 33, nil, EAST);
	else
	-- in Point-to-Point Configuration 
	HeadBlockAbsolute0(MCKEES_ROCKS_BLOCK, 37, nil, 33, WEST, nil);
	end;
HeadBlockPermissive(J_L_BLOCK, 33, nil, 29, ALIQUIPPA_WEST, ALIQUIPPA_EAST, nil, nil);
HeadBlockAbsolute(ALIQUIPPA_BLOCK, 29, nil, 27, WEST, nil);
PermissiveinOpenCountry(WEST_ALIQUIPPA_BLOCK, 27, 28, 25, WEST, EAST);
PermissiveinOpenCountry(MONACA_BLOCK, 25, 26, 21, WEST, EAST); 
PermissiveinOpenCountry(FALLSTON_BLOCK, 21, 22, 19, WEST, EAST);
ApproachtoHeadBlock(BEAVER_FALLS_BLOCK, 19, 20, 17, nil, EAST);
HeadBlockPermissive(COLLEGE_BLOCK, 17, nil, 13, COLLEGE_WEST, COLLEGE_EAST, nil, nil);
HeadBlockAbsolute(NEW_CASTLE_JUNCTION_BLOCK, 13, nil, 11, WEST, nil);
ApproachtoHeadBlock(LOWELLVILLE_BLOCK, 11, 12, 9, nil, EAST);
HeadBlockPermissive(STRUTHERS_BLOCK, 9, nil, 5, STRUTHERS_WEST, STRUTHERS_EAST, nil, nil);
-- does not change NextSig #1
if (Layout.LLLoop) then
	HeadBlockAbsolute(YOUNGSTOWN_BLOCK, 5, nil, 1, WEST, nil);
	else
	HeadBlockAbsolute0(YOUNGSTOWN_BLOCK, 5, nil, 1, WEST, nil);
	end;
if (Layout.LLLoop) then
	-- in Loop Configuration
	PermissiveinOpenCountry(BALLOON_BLOCK, 1, 2, 37, WEST, EAST);
	else
	-- in Point-to-Point Configuration
	HeadBlockPermissive(GATEWAY_YARD_BLOCK, 1, nil, nil, GATEWAY_YARD, GATEWAY_YARD, nil, nil);
	end

-- CALCULATE EASTBOUND SIGNAL ASPECTS

if (Layout.LLLoop) then
	-- in Loop Configuration
	-- does not change OppositeSig #1
	ApproachtoHeadBlock(YOUNGSTOWN_BLOCK, 2, 1, 6, nil, WEST);
	else
	-- in Point-to-Point Configuration
	HeadBlockAbsolute0(YOUNGSTOWN_BLOCK, 2, nil, 6, EAST, nil);
	end;
HeadBlockPermissive(STRUTHERS_BLOCK, 6, nil, 10, STRUTHERS_EAST, STRUTHERS_WEST, nil, nil);
HeadBlockAbsolute(LOWELLVILLE_BLOCK, 10, nil, 12, EAST, nil);
ApproachtoHeadBlock(NEW_CASTLE_JUNCTION_BLOCK, 12, 11, 14, nil, WEST);
HeadBlockPermissive(COLLEGE_BLOCK, 14, nil, 18, COLLEGE_EAST, COLLEGE_WEST,	nil, nil);
HeadBlockAbsolute(BEAVER_FALLS_BLOCK, 18, nil, 20, EAST, nil);
PermissiveinOpenCountry(FALLSTON_BLOCK, 20, 19, 22, EAST, WEST);
PermissiveinOpenCountry(MONACA_BLOCK, 22, 21, 26, EAST, WEST);
PermissiveinOpenCountry(WEST_ALIQUIPPA_BLOCK, 26, 25, 28, EAST, WEST);
ApproachtoHeadBlock(ALIQUIPPA_BLOCK, 28, 27, 30, nil, WEST);
HeadBlockPermissive(J_L_BLOCK, 30, nil, 34, ALIQUIPPA_EAST, ALIQUIPPA_WEST, nil, nil);
if (Layout.LLLoop) then
	HeadBlockAbsolute(MCKEES_ROCKS_BLOCK, 34, nil, 38, EAST, nil);
	else
	HeadBlockAbsolute0(MCKEES_ROCKS_BLOCK, 34, nil, 38, EAST, nil);
	end;
if (Layout.LLLoop) then
	PermissiveinOpenCountry(BALLOON_BLOCK, 38, 37, 2, EAST, WEST);
	else
	HeadBlockPermissive(MCKEES_ROCKS_REPAIR_SHOPS_YARD_BLOCK, 38, nil, nil, MCKEES_ROCKS_REPAIR_SHOPS_YARD, MCKEES_ROCKS_REPAIR_SHOPS_YARD, nil, nil);
	end
-- add 43/44 and 47/48 eastbound PermissiveinOpenCountry
PermissiveinOpenCountry(ALIQUIPPA_CUTOFF, 44, 43, 48, EAST, WEST);
PermissiveinOpenCountry(STRUTHERS_BLOCK, 48, 47, 10, EAST, WEST);

-- CLEAR TRAFFIC STICK FOLLOWING SIGNAL UPGRADES
if (Layout.LLLoop) then
	-- in Loop Configuration
	ClearTrafficStick(1);
	ClearTrafficStick(2);
	end;

ClearTrafficStick(11);
ClearTrafficStick(12);

ClearTrafficStick(19);
ClearTrafficStick(20);

ClearTrafficStick(21);
ClearTrafficStick(22);
	
ClearTrafficStick(25);
ClearTrafficStick(26);
	
ClearTrafficStick(27);
ClearTrafficStick(28);

if (Layout.LLLoop) then
	-- in Loop Configuration
	ClearTrafficStick(37);
	ClearTrafficStick(38);
	end;

ClearTrafficStick(43);
ClearTrafficStick(44);

ClearTrafficStick(47);
ClearTrafficStick(48);
--
-- APPROACH LIGHTING
--
if (ApproachLighting) then
	--print("Approach Lighting");
	if (Layout.LLLoop == true) then
		-- Balloon block
		SetApproachLighting(BALLOON_BLOCK, 37, 2); 	-- Loop Configuration
		else
		-- Exit signal from yards is an absolute signal
		-- Should always be lit
		SigA[2] = Sig[2];	-- GATEWAY_YARD block exit signals always lit
		SigA[37] = Sig[37];	-- MCKEES_ROCKS_YARD block exit signals always lit
		end;
	SetApproachLighting(YOUNGSTOWN_BLOCK, 1, 6);
	SigA[5] = Sig[5];	-- Struthers block exit signals always lit
	SigA[10] = Sig[10];
	SetApproachLighting(LOWELLVILLE_BLOCK, 9, 12);
	SetApproachLighting(NEW_CASTLE_JUNCTION_BLOCK, 11, 14);
	SigA[13] = Sig[13];	-- College block exit signals always lit
	SigA[18] = Sig[18];
	SetApproachLighting(BEAVER_FALLS_BLOCK, 17, 20);
	SetApproachLighting(FALLSTON_BLOCK, 19, 22);
	SetApproachLighting(MONACA_BLOCK, 21, 26);
	SetApproachLighting(WEST_ALIQUIPPA_BLOCK, 25, 28);
	SetApproachLighting(ALIQUIPPA_BLOCK, 27, 30);
	SigA[29] = Sig[29];	-- J&L Block exit signals always lit
	SigA[34] = Sig[34];
	SetApproachLighting(MCKEES_ROCKS_BLOCK, 33, 38);
	SetApproachLighting(ALIQUIPPA_BLOCK, 44, 44);
	SetApproachLighting(ALIQUIPPA_CUTOFF, 43, 48);
	SetApproachLighting(STRUTHERS_BLOCK, 47, 47);
		
	-- Display signal aspects
	for Signal = 1, Layout.SIGNALMAX do
		--print("Signal = " .. Signal);
		if (SigA[Signal] ~= nil) then	-- nil means no change
			if (SavedSig[Signal] ~= SigA[Signal]) then	-- if no change, don't need to SetAspect
				local traffic;
				SetAspect(Signal, SigA[Signal]);
				if (FSR[Signal] == NDT) then
					traffic = "NDT";
					elseif (FSR[Signal] == EAST) then
						traffic = "East";
						else
						traffic = "West";
						end;
				--print(SigA[Signal] ..  " " .. SavedSig[Signal]);
				print("(", RunTime(),") SetAspect on signal # ", Signal, " to ", (SigA[Signal] < 0) and "-" or "", AspectNames[math.abs(SigA[Signal])], " was ", (SavedSig[Signal] < 0) and "-" or "", AspectNames[math.abs(SavedSig[Signal])], " : traffic stick relay : ", traffic);
				SavedSig[Signal] = SigA[Signal];	-- save current aspect
				end;
			end;
		-- TODO consider saving signals to a disk file here
		end;
	Sleep(0.1);
	return true;
end;
--
-- Always on Lighting
-- 
-- Display signal aspects
for Signal = 1, Layout.SIGNALMAX do
	--print("Signal = " .. Signal);
	if (Sig[Signal] ~= nil) then	-- nil means no change
		if (SavedSig[Signal] ~= Sig[Signal]) then	-- if no change, don't need to SetAspect
			local traffic;
			SetAspect(Signal, Sig[Signal]);
			if (FSR[Signal] == NDT) then
				traffic = "NDT";
				elseif (FSR[Signal] == EAST) then
					traffic = "East";
					else
					traffic = "West";
					end;
			print("(", RunTime(),") SetAspect on signal # ", Signal, " to ", (Sig[Signal] < 0) and "-" or "", AspectNames[math.abs(Sig[Signal])], " was ", (SavedSig[Signal] < 0) and "-" or "", AspectNames[math.abs(SavedSig[Signal])], " : traffic stick relay : ", traffic);
			SavedSig[Signal] = Sig[Signal];	-- save current aspect
			end;
		end;
	-- TODO consider saving signals to a disk file here
	end;
	
Sleep(0.1);
return true;
end
--[[---------------------------------------------------------------------------------------]]

function SetApproachLighting(block, signal1, signal2)
-- if the block is unoccupied, set the two exit signal to Dark
if (Layout4BlockOcc[block] ~= UNOCCUPIED) then
	-- lit
	SigA[signal1] = Sig[signal1];
	SigA[signal2] = Sig[signal2];
	else
	-- dark
	SigA[signal1] = -Sig[signal1];	-- < 0 means signal is dark 
	SigA[signal2] = -Sig[signal2];
	end;
end
--[[---------------------------------------------------------------------------------------]]

function tag(Detector, Reader, EngineNo, TagLocation, CarModel, Railroad, CarNumber, TagPacket)
-- This function is called each time a tag programmed with 32 bytes of data is detected (TAGDATABLOCK4)
-- TagPacket contains 8 characters (4 hex digits) of UID and 32 characters
-- of block 4 data from the programmed tag.

BumpCounter(COUNTER01);	
local PacketLen = #TagPacket;

if (Debug() >= 8) then print("Packet Length = ", PacketLen); end;
if (Debug() >= 7) then print(TagPacket); end;

	-- index the table Tags[] by tag ID, value is the tag packet itself
	Tags[string.sub(TagPacket, 1, 8)] = string.sub(TagPacket, 9, 40);	-- remove checksum and EOP
	TagCount = TagCount + 1;	-- count the number of tags
	if (EngineNo > 0) then	-- its an engine
		if (BeepOn) then Beep(ASTERISK); end;
		-- next line shows a different way to quote a string using brackets
		printc(clGreen, string.format([[tag(%.2f) : Detector %d Reader %d Eng#%d %s %s #%s %s]],
			RunTime(), Detector, Reader, EngineNo, GetRailroadName(Railroad), GetEngineName(CarModel), CarNumber, GetTagLocation(TagLocation)));
		-- continue on to process the detected engine
		else	-- it's a car or a caboose or observation (EOT)
		printc(clBlue, string.format([[tag(%.2f) : Detector %d Reader %d Car %s %s #%s]],
			RunTime(), Detector, Reader, GetRailroadName(Railroad), GetCarName(CarModel), CarNumber));
		-- continue on to process non-engine tags
		if (CarModel == CABOOSE or CarModel == OBSERVATION) then
			if (BeepOn) then Beep(EXCLAMATION); end;
			-- continue on to process Caboose and Observation (EOT) tags
			else
			-- no processing needed for other tags
			return true;
			end;
		end

	-- The complete packet received is in TagPacket 
	-- (all of the fields have already been extracted into the first parameters to tag()
	--   First 8 digits are the 4 byte Tag UID in hexidecimal
	--   Next 32 digits are the 16 bytes of Block 4 Information read from the Tag in hexidecimal
	if (Debug() >= 6) then print(string.format("Packet  %s  %s", string.sub(TagPacket, 1, 8),
		string.sub(TagPacket,9,40) )); end;

-- Tag 3-0
if (Detector == MCKEES_ROCKS_REPAIR_SHOPS_YARD_DETECTOR and Reader == MCKEES_ROCKS_REPAIR_SHOPS_YARD_READER) then
	if (Func30 == nil) then
		Func30 = createMainLine(false);
		end;
	if (Layout.LLLoop == true) then
		-- Loop Configuration
		Func30("@ MCKEES_ROCKS_REPAIR_SHOPS_YARD_TAG ", EngineNo, TagLocation, CarModel, BALLOON_BLOCK, MCKEES_ROCKS_BLOCK);
		else
		-- Point-to-Point Configuration
		Func30("@ MCKEES_ROCKS_REPAIR_SHOPS_YARD_TAG ", EngineNo, TagLocation, CarModel, MCKEES_ROCKS_REPAIR_SHOPS_YARD_BLOCK, MCKEES_ROCKS_BLOCK);
		end

-- Tag 5-1
elseif (Detector == ALIQUIPPA_EAST_DETECTOR and Reader == ALIQUIPPA_EAST_READER) then
	if (Func51 == nil) then
		Func51 = createMainLine(false);
		end;
	Func51("@ ALIQUIPPA_EAST_TAG ", EngineNo, TagLocation, CarModel, MCKEES_ROCKS_BLOCK, J_L_BLOCK);

-- Tag 1-0
elseif (Detector == ALIQUIPPA_WEST_DETECTOR and Reader == ALIQUIPPA_WEST_READER) then
	if (Func10 == nil) then
		Func10 = createMainLine(false); 
		end;	
	Func10("@ ALIQUIPPA_WEST_TAG ", EngineNo, TagLocation, CarModel, J_L_BLOCK, ALIQUIPPA_BLOCK); 

-- Tag 10-0
elseif (Detector == WEST_ALIQUIPPA_DETECTOR and Reader == WEST_ALIQUIPPA_READER) then
	if (Func100 == nil) then
		Func100 = createMainLine(false);
		end;
	Func100("@ WEST_ALIQUIPPA_TAG ", EngineNo, TagLocation, CarModel, ALIQUIPPA_BLOCK, WEST_ALIQUIPPA_BLOCK);

-- Tag 6-0
elseif (Detector == MONACA_EAST_DETECTOR and Reader == MONACA_EAST_READER) then
	if (Func60 == nil) then
		Func60 = createMainLine(false);
		end;
	Func60("@ MONACA_EAST_TAG ", EngineNo, TagLocation, CarModel, WEST_ALIQUIPPA_BLOCK, MONACA_BLOCK);
	
-- Tag 6-1
elseif (Detector == MONACA_WEST_DETECTOR and Reader == MONACA_WEST_READER) then
	if (Func61 == nil) then
		Func61 = createMainLine(false);	
		end;
	Func61("@ MONACA_WEST_TAG ", EngineNo, TagLocation, CarModel, MONACA_BLOCK, FALLSTON_BLOCK);

-- Tag 10-1
elseif  (Detector == FALLSTON_DETECTOR and Reader == FALLSTON_READER) then
	if (Func101 == nil) then
		Func101 = createMainLine(false);
		end;
	Func101("@ FALLSTON_TAG ", EngineNo, TagLocation, CarModel, FALLSTON_BLOCK, BEAVER_FALLS_BLOCK);
	
-- Tag 4-1
elseif (Detector == COLLEGE_EAST_DETECTOR and Reader == COLLEGE_EAST_READER) then
	if (Func41 == nil) then
		Func41 = createMainLine(false);
		end;
	Func41("@ COLLEGE_EAST_TAG ", EngineNo, TagLocation, CarModel, BEAVER_FALLS_BLOCK, COLLEGE_BLOCK);

-- Tag 2-1
elseif (Detector == COLLEGE_WEST_DETECTOR and Reader == COLLEGE_WEST_READER) then
	if (Func21 == nil) then
		Func21 = createMainLine(false);
		end;	
	Func21("@ COLLEGE_WEST_TAG ", EngineNo, TagLocation, CarModel, COLLEGE_BLOCK, NEW_CASTLE_JUNCTION_BLOCK);

-- Tag 9-0
elseif (Detector == LOWELLVILLE_DETECTOR and Reader == LOWELLVILLE_READER) then
	if (Func90 == nil) then
		Func90 = createMainLine(false);
		end;
	Func90("@ LOWELLVILLE_TAG ", EngineNo, TagLocation, CarModel, NEW_CASTLE_JUNCTION_BLOCK, LOWELLVILLE_BLOCK);

-- Tag 2-0
elseif (Detector == STRUTHERS_EAST_DETECTOR and Reader == STRUTHERS_EAST_READER) then
	if (Func20 == nil) then
		Func20 = createMainLine(false);
		end;
	Func20("@ STRUTHERS_EAST_TAG ", EngineNo, TagLocation, CarModel, LOWELLVILLE_BLOCK, STRUTHERS_BLOCK);

-- Tag 4-0
elseif (Detector == STRUTHERS_WEST_DETECTOR and Reader == STRUTHERS_WEST_READER) then
	if (Func40 == nil) then
		Func40 = createMainLine(false);
		end;
	Func40("@ STRUTHERS_WEST_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_BLOCK, YOUNGSTOWN_BLOCK);

-- Tag 15-0
elseif (Detector == CAMPBELL_DETECTOR and Reader == CAMPBELL_READER) then
	if (Func150 == nil) then
		Func150 = createMainLine(false);
		end;
	Func150("@ CAMPBELL_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_BLOCK, ALIQUIPPA_CUTOFF);

-- Tag 11-0
elseif (Detector == WOODLAWN_DETECTOR and Reader == WOODLAWN_READER) then
	if (Func110 == nil) then
		Func110 = createMainLine(false);
		end;
	Func110("@ WOODLAWN_TAG ", EngineNo, TagLocation, CarModel, ALIQUIPPA_CUTOFF, ALIQUIPPA_BLOCK);

-- Tag 8-1
elseif (Detector == GATEWAY_YARD_DETECTOR and Reader == GATEWAY_YARD_READER) then
	if (Func50 == nil) then
		Func50 = createMainLine(false);
		end;
	if (Layout.LLLoop == true) then
		-- Loop Configuration
		Func50("@ GATEWAY_YARD_TAG ", EngineNo, TagLocation, CarModel, YOUNGSTOWN_BLOCK, BALLOON_BLOCK);
		else
		-- Point-to-Point Configuration
		--[[
		TBD: Signal 5 shows Stop Signal with engine in STRUTHERS_THRU_TRACK and GATEWAY_YARD1_TRACK
		occupied. End block is GATEWAY_YARD2_TRACK
		which is empty. Signal 5 is using GATEWAY_YARD1_TRACK (AKA GATEWAY_YARD_BLOCK) set Signal 5 as
		absolute stop when the engine is trying to get into GATEWAY_YARD2_TRACK.
		The engine in STRUTHERS_THRU_TRACK waits forever for GATEWAY_YARD1_TRACK which it never
		does.
		I am trying to make the operation here different than the station/passing siding. With those,
		WB trains only go into the thru track and EB trains only go into the station tracks. Here I am
		trying to let WB trains go into either/both Gateway yard tracks.
		This same scenairo happens at MCKEES_ROCKS_REPAIR_YARD.
		--]]
		Func50("@ GATEWAY_YARD_TAG ", EngineNo, TagLocation, CarModel, YOUNGSTOWN_BLOCK, GATEWAY_YARD_BLOCK);
		end
--	
	end;

return true;	-- true=continue to process tags, false=stop processing tags
end			
--[[---------------------------------------------------------------------------------------]]

function createMainLine(flag)
	local WaitforEOT = flag;
	local MyLastBlockNo;
	local function MainLine(DetectorID, EngineNo, TagLocation, CarModel, EastSideBlock, WestSideBlock)
		-- EastSideBlock is the western block-if this is unoccupied, direction is WB
		-- WestSideBlock is the eastern block-if this is unoccupied, direction is EB
		---- uses globals: Layout4BlockOcc[]
		print("(", RunTime(),") MainLine " .. BlockName[EastSideBlock] .. " (" .. EastSideBlock .. ") " .. Layout4BlockOcc[EastSideBlock] ..
			" - " .. BlockName[WestSideBlock] .. " (" .. WestSideBlock .. ") " .. Layout4BlockOcc[WestSideBlock] ..
			"  Detector " .. DetectorID .. " Engine " .. EngineNo);
		if (isEngine(EngineNo) and ((TagLocation == TAG_ON_FRONT_TRUCK) or
			(not WaitforEOT and (TagLocation == TAG_ON_REAR_TRUCK)))) then		-- if its an engine
			
--[[--- Engine Detected -----]]
			if (WaitforEOT) then
				OccButton();
				return EmergencyStop("MainLine : Expected an EOT but got an engine");
				end;				
--[[--- 1 Initialization -----]]				
			--print("MainLine : Detector " .. DetectorID .. " Engine " .. EngineNo);
			MyLastBlockNo = nil;	-- reset last block number
			if (Layout4BlockOcc[EastSideBlock] == UNOCCUPIED) then	-- Direction is EB
				Layout4BlockOcc[EastSideBlock] = math.abs(Layout4BlockOcc[EastSideBlock]) + 1;				

				print("(", RunTime(),") 1 MainLine : Eastbound Occupy " .. BlockName[EastSideBlock] .. " (" .. EastSideBlock .. ") " .. Layout4BlockOcc[EastSideBlock]);
				MyLastBlockNo = WestSideBlock;
				WaitforEOT = true;	-- Save current state for the end of train detection call of this function
				return true;
				else
				if (math.abs(Layout4BlockOcc[EastSideBlock]) > 2) then	-- At this point, occupancy can only be 1 or 2
					OccButton();
					return EmergencyStop("1 MainLine : Eastbound Occupancy Count is " .. Layout4BlockOcc[EastSideBlock] .. " in " .. BlockName[EastSideBlock] .. "(" .. EastSideBlock .. ")");
					end;			
				end;
			if (Layout4BlockOcc[WestSideBlock] == UNOCCUPIED) then	-- Direction is WB
				Layout4BlockOcc[WestSideBlock] = math.abs(Layout4BlockOcc[WestSideBlock]) + 1;				

				print("(", RunTime(),") 2 MainLine : Westbound Occupy " .. BlockName[WestSideBlock] .. " (" .. WestSideBlock .. ") " .. Layout4BlockOcc[WestSideBlock]);
				MyLastBlockNo = EastSideBlock;
				WaitforEOT = true;	-- Save current state for the end of train detection call of this function
				return true;
				else
				if (math.abs(Layout4BlockOcc[WestSideBlock]) > 2) then	-- At this point, occupancy can only be 1 or 2
					OccButton();
					return EmergencyStop("2 MainLine : Westbound Occupancy Count is " .. Layout4BlockOcc[WestSideBlock] .. " in " .. BlockName[WestSideBlock] .. "(" .. WestSideBlock .. ")");
					end;			
				end;
			---[[
			if (Layout4BlockOcc[EastSideBlock] ~= UNOCCUPIED and Layout4BlockOcc[WestSideBlock] ~= UNOCCUPIED) then 
				-- Special case of engine entering a passing/siding/yard block with the other track occupied by another train
				-- Occupy the siding and clear the head block when a end of train comes along
				-- Direction is unknown
				if (EastSideBlock == ALIQUIPPA_BLOCK or
					EastSideBlock == MCKEES_ROCKS_BLOCK or
					EastSideBlock == BEAVER_FALLS_BLOCK or
					EastSideBlock == NEW_CASTLE_JUNCTION_BLOCK or
					EastSideBlock == LOWELLVILLE_BLOCK or
					EastSideBlock == YOUNGSTOWN_BLOCK) then
					if (Layout4BlockOcc[WestSideBlock] > 2) then	-- At this point, occupancy can only be 2 or less
						OccButton();
						return EmergencyStop("3 Siding : Occupancy Count is " .. Layout4BlockOcc[WestSideBlock] .. " in " .. BlockName[WestSideBlock] .. "(" .. WestSideBlock .. ")");
						end;					
					Layout4BlockOcc[WestSideBlock] = math.abs(Layout4BlockOcc[WestSideBlock]) + 1;	-- bump the siding occupancy count											

					print("(", RunTime(),") (Siding) : 3 Occupy " .. BlockName[WestSideBlock] .. " (" .. WestSideBlock .. ") " .. Layout4BlockOcc[WestSideBlock]);
					MyLastBlockNo = EastSideBlock;			-- Setup to decrement the head block occupancy count
					WaitforEOT = true;			-- Save current state for the end of train detection call of this function
					return true;
					end;
				if (WestSideBlock == ALIQUIPPA_BLOCK or
					WestSideBlock == MCKEES_ROCKS_BLOCK or
					WestSideBlock == BEAVER_FALLS_BLOCK or
					WestSideBlock == NEW_CASTLE_JUNCTION_BLOCK or
					WestSideBlock == LOWELLVILLE_BLOCK or
					WestSideBlock == YOUNGSTOWN_BLOCK) then
					if (Layout4BlockOcc[EastSideBlock] > 2) then	-- At this point, occupancy can only be 2 or less
						OccButton();
						return EmergencyStop("4 Siding : Occupancy Count is " .. Layout4BlockOcc[EastSideBlock] .. " in " .. BlockName[EastSideBlock] .. "(" .. EastSideBlock .. ")");
						end;					
					Layout4BlockOcc[EastSideBlock] = math.abs(Layout4BlockOcc[EastSideBlock]) + 1;	-- bump the siding occupancy count		
					
					print("(", RunTime(),") (Siding) : 4 Occupy " .. BlockName[EastSideBlock] .. " (" .. EastSideBlock .. ") " .. Layout4BlockOcc[EastSideBlock]);
					MyLastBlockNo = WestSideBlock;			-- setup to decrement the head block occupancy count
					WaitforEOT = true;			-- save current state for the end of train detection call of this function
					return true;
					end;
				end;
			--]]
			return EmergencyStop("MainLine : Illegal Block Status");

--[[--- EOT Detected -----]]

		elseif (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION) and WaitforEOT) then
			print("(", RunTime(),") 5 MainLine : Detector " .. DetectorID .. "End of Train detected");
			if ((math.abs(Layout4BlockOcc[MyLastBlockNo]) ~= OCCUPIED) and (math.abs(Layout4BlockOcc[MyLastBlockNo]) ~= (OCCUPIED + 1))) then	-- At this point, occupancy can only be 1 or 2
			-- I tried the following line to replace the previous line but it did not work
			--if ((Layout4BlockOcc[MyLastBlockNo] ~= UNOCCUPIED) and (math.abs(Layout4BlockOcc[MyLastBlockNo]) ~= 1)) then	-- At this point, occupancy can only be 0 or 1/-1
				OccButton();
				return EmergencyStop("5 MainLine : Occupancy Count is " .. Layout4BlockOcc[MyLastBlockNo] .. " in " .. BlockName[MyLastBlockNo] .. "(" .. MyLastBlockNo .. ")");
				end;
			Layout4BlockOcc[MyLastBlockNo] = math.abs(Layout4BlockOcc[MyLastBlockNo]) - 1;	-- decrement the occupancy count to 0 or 1
			print("(", RunTime(),") 5 MainLine : De-occupy " .. BlockName[MyLastBlockNo] .. " (" .. MyLastBlockNo .. ") ".. Layout4BlockOcc[MyLastBlockNo]);

			MyLastBlockNo = nil;	-- once MyLastBlockNo is used, set it to nil to catch possible future errors
			WaitforEOT = false;
			--writeSettings(false);	-- save state without comment
			end;
	-- other combinations of engines/cabooses/other cars are ignored
	return true;
	end;
-- return the MainLine() function when createMainLine() is called
return MainLine;
end;
--[[---------------------------------------------------------------------------------------]]

function cleanup()
	-- this function is called once when the user presses the [STOP] button or the Stop() function is called
	print("cleanup() Running");
	-- Next line not needed - This script should run independantly from Engine control scripts
	-- The line is here as it is needed when running with the test scripts Layout4 Block XX Occupancy.lua 
	ReqBlockEvents(false);	-- cancel request block occupancy flag events from the Layout
	-- save occupied block numbers
	--writeSettings(true);			-- write the .ini file and save the occupied blocks
	if (ErrCount == 0) then
		print("No errors");
		else
		print(ErrCount .. " Error(s)");
		end;	
	return true;		-- false=cleanup failed, true=cleanup succeeded
	end
--[[---------------------------------------------------------------------------------------]]

function ReloadButton()
	print("Reloading layout4.lua");
	package.loaded["layout"] = nil;	-- unload
	Layout = require([[layout4]]);	-- All of the details about the Layout
	return true;
end
Function11Name, Function11Label = ReloadButton, "Reload";
--[[---------------------------------------------------------------------------------------]]

function BeepOffButton()
	print("Beep Off");
	BeepOn = false;
	return true;
end
Function14Name, Function14Label = BeepOffButton, "Beep Off";
--[[---------------------------------------------------------------------------------------]]

function BeepOnButton()
	print("Beep On");
	BeepOn = true;
	return true;
end
Function15Name, Function15Label = BeepOnButton, "Beep On";
--[[---------------------------------------------------------------------------------------]]

function OccButton()
printc(clGreen, "Block Occupancy Status -------------------------");
local iBlock, FColor;
for iBlock = FIRST_BLOCK, LAST_BLOCK do
	-- passing, station and yard2 tracks
	if (iBlock == STRUTHERS_STATION_TRACK) then goto continue; end;	-- passing and station tracks don't have separate occupancy status
	if (iBlock == COLLEGE_STATION_TRACK) then goto continue; end;
	if (iBlock == J_L_PASSING_TRACK) then goto continue; end;
	if (iBlock == GATEWAY_YARD2_TRACK) then goto continue; end;
	if (iBlock == MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK) then goto continue; end;
	local flag = Layout4BlockOcc[iBlock];
	if (flag == UNOCCUPIED) then
		FColor = clBlack;
		else
		FColor = clRed;
		end;
	if (iBlock == STRUTHERS_THRU_TRACK) then 
		printc(FColor, "Struthers Thru & Station Tracks" .. " (" .. iBlock .. ") = ", flag);;
		goto continue;
		end;
	if (iBlock == COLLEGE_THRU_TRACK) then 
		printc(FColor, "College Thru & Station Tracks" .. " (" .. iBlock .. ") = ", flag);;
		goto continue;
		end;
	if (iBlock == J_L_THRU_TRACK) then 
		printc(FColor, "J&L Thru & Passing Tracks" .. " (" .. iBlock .. ") = ", flag);;
		goto continue;
		end;
	if (iBlock == GATEWAY_YARD1_TRACK) then 
		printc(FColor, "Gateway Yard" .. " (" .. iBlock .. ") = ", flag);;
		goto continue;
		end;
	if (iBlock == MCKEES_ROCKS_REPAIR_SHOPS_YARD1_TRACK) then 
		printc(FColor, "McKees Rocks Repair Shops Yard" .. " (" .. iBlock .. ") = ", flag);;
		goto continue;
		end;		
	printc(FColor, BlockName[iBlock] .. " (" .. iBlock .. ") = ", flag);;
	::continue::
	end;	
return true;
end
Function01Name, Function01Label = OccButton, "Block Occupancy";
--[[---------------------------------------------------------------------------------------]]
-- Ask the dispatcher for one block number
--
function AskBlock(title, query)
print("Select Block Number");
--
-- read up one block number
--
local OccupiedBlock = InputDropDown(title, query, 1, BlockNameList);
--print("OccupiedBlock ", OccupiedBlock);
local typ = type(OccupiedBlock);
--print("Type ", typ);

if (typ == "boolean" and OccupiedBlock == false) then
	print("Block selection canceled");
	return 0;  --InputDropDown Canceled
	else
	if (OccupiedBlock ~= nil and OccupiedBlock ~= "") then	-- if the result is a string, then use first number in the string, otherwise, ignore it
		-- pull first integer found in the string
		local BlockNo = string.match(OccupiedBlock, "%d+");
		if (BlockNo ~= nil) then
			OccupiedBlock = tonumber(BlockNo);
			if (OccupiedBlock == 0) then
				print("No Block selected");
				return 0;	-- done
				end;
			-- fixup for passing/station/yard tracks - algorithm considers the passing/station/yard tracks
			-- as occupied together with the thru tracks
			if (OccupiedBlock == STRUTHERS_STATION_TRACK) then OccupiedBlock = STRUTHERS_BLOCK; end;
			if (OccupiedBlock == COLLEGE_STATION_TRACK) then OccupiedBlock = COLLEGE_BLOCK; end;
			if (OccupiedBlock == J_L_PASSING_TRACK) then OccupiedBlock = J_L_BLOCK; end;
			if (OccupiedBlock == GATEWAY_YARD2_TRACK) then OccupiedBlock = GATEWAY_YARD_BLOCK; end;				
			if (OccupiedBlock == MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK) then OccupiedBlock = MCKEES_ROCKS_REPAIR_SHOPS_YARD_BLOCK; end;				
			-- indicate that the block is occupied.
			else
			return Stop("Invalid Block Number");
			end;
		else
		if (typ == "nil") then
			-- nil returned only if parameters are invalid
			return Stop("Invalid Parameters to InputDropDown");
			end
		end
	end
print("Block selected = " .. BlockName[OccupiedBlock] .. " (" .. OccupiedBlock .. ") - Layout4BlockOcc = " .. Layout4BlockOcc[OccupiedBlock]);
return OccupiedBlock;
end;
--[[---------------------------------------------------------------------------------------]]
-- Ask the dispatcher for block number(s)
--
function AskBlocks(title, query)
--[===[
-- Ask the dispatcher for occupied block number(s)
-- This is no longer necessary as the EOB script asks for this information
--
-- read Occupied Block Number(s)
print("Select Occupied Block Number(s)");
--
-- read up to BLOCK_COUNT occupied blocks
--
for iBlk = 1, BLOCK_COUNT do
	local OccupiedBlockNo;
	-- read next block - note that there may be duplicates in this list in the case of passing sidings
	OccupiedBlockNo = OccupiedBlocks[iBlk];
	-- if no more blocks, use "None" index setting of 1
	if (OccupiedBlockNo == nil) then
		OccupiedBlockNo	= 0;
		end;
	local BlockNameIndex = 1;
	local BlockNameCount = 1;	
	
	for iBlock = 1, BLOCK_COUNT do
		local Name = BlockNameList[iBlock];
		if (Name ~= nil and iBlock <= LAST_BLOCK) then
			--print(Name .. " --> (" .. iBlock .. ")");
			--------------------------------------------
			local BlockNo = tonumber(string.match(Name, "%d+"));
			if (OccupiedBlockNo == BlockNo) then
				BlockNameIndex = BlockNameCount;
				--print("BlockNameIndex = " .. BlockNameIndex);
				end;
			BlockNameCount = BlockNameCount + 1;		
			--------------------------------------------		
			end;
		end;

	local OccupiedBlock = InputDropDown(title, query, BlockNameIndex, BlockNameList);
	--print("OccupiedBlock ", OccupiedBlock);
	local typ = type(OccupiedBlock);
	--print("Type ", typ);

	if (typ == "boolean" and OccupiedBlock == false) then
		return Stop("InputDropDown Canceled");
		else
		if (OccupiedBlock ~= nil and OccupiedBlock ~= "") then	-- if the result is a string, then use first number in the string, otherwise, ignore it
			-- pull first integer found in the string
			local BlockNo = string.match(OccupiedBlock, "%d+");
			if (BlockNo ~= nil) then
				OccupiedBlock = tonumber(BlockNo);
				if (OccupiedBlock == 0) then
					break;	-- done
					end;
				-- fixup for passing/station/yard tracks - algorithm considers the passing/station/yard tracks
				-- as occupied together with the thru tracks
				if (OccupiedBlock == STRUTHERS_STATION_TRACK) then OccupiedBlock = STRUTHERS_BLOCK; end;
				if (OccupiedBlock == COLLEGE_STATION_TRACK) then OccupiedBlock = COLLEGE_BLOCK; end;
				if (OccupiedBlock == J_L_PASSING_TRACK) then OccupiedBlock = J_L_BLOCK; end;
				if (OccupiedBlock == GATEWAY_YARD2_TRACK) then OccupiedBlock = GATEWAY_YARD_BLOCK; end;				
				if (OccupiedBlock == MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK) then OccupiedBlock = MCKEES_ROCKS_REPAIR_SHOPS_YARD_BLOCK; end;				
				-- indicate that the block is occupied.
				Layout4BlockOcc[OccupiedBlock] = Layout4BlockOcc[OccupiedBlock] + 1;	-- this flag is used by this APB Master thread			
				else
				return Stop("Invalid Block Number");
				end;
			else
			if (typ == "nil") then
				-- nil returned only if parameters are invalid
				return Stop("Invalid Parameters to InputDropDown");
				end
			end
		end
	print("Selected Block = " .. BlockName[OccupiedBlock] .. " (" .. OccupiedBlock .. ") - Layout4BlockOcc = " .. Layout4BlockOcc[OccupiedBlock]);
	end;
--]===]
end;
--[[---------------------------------------------------------------------------------------]]

function F02()
if (not SetupComplete) then
	Beep();
	return true;
	end;
local OccupiedBlock = AskBlock(title, "Occupy Block Number?");
print("Occupy " .. BlockName[OccupiedBlock] .. " (" .. OccupiedBlock .. ")");
if (OccupiedBlock > 0) then
	Layout4BlockOcc[OccupiedBlock] = Layout4BlockOcc[OccupiedBlock] + 1;	-- this flag is used by this APB Master thread
	end;
return true;
end
Function02Name, Function02Label = F02, "Occupy Block";
--[[---------------------------------------------------------------------------------------]]

function F03()
if (not SetupComplete) then
	Beep();
	return true;
	end;
local OccupiedBlock = AskBlock(title, "De-occupy Block Number?")
if (Enum ~= nil and Enum ~= 0) then	-- if the result is a number, then use it, otherwise, ignore it
	print("De-occupy " .. BlockName[OccupiedBlock] .. " (" .. OccupiedBlock .. ")");
	if (Layout4BlockOcc[OccupiedBlock] == UNOCCUPIED) then
		-- block already unoccupied
		return true;
		end;
	if (Layout4BlockOcc[OccupiedBlock] > 1) then
		Layout4BlockOcc[OccupiedBlock] = Layout4BlockOcc[OccupiedBlock] - 1;
		-- block remains occupied with occupancy count reduced by one
		return true;
		end;
	if (Layout4BlockOcc[OccupiedBlock] == 1 or Layout4BlockOcc[OccupiedBlock] < 0) then
		Layout4BlockOcc[OccupiedBlock] = UNOCCUPIED;	-- This flag is used by this APB Master thread
		-- block becomes unoccupied
		return true;
		end;
	end;
return true;	
end;

Function03Name, Function03Label = F03, "De-occupy Block";
--[[---------------------------------------------------------------------------------------]]

function F04()
if (not SetupComplete) then
	Beep();
	return true;
	end;
for Block = FIRST_BLOCK, LAST_BLOCK do
	Layout4BlockOcc[Block] = UNOCCUPIED;			-- This flag is used by this APB Master thread
	end;
print("All blocks manually De-occupied");
return true;
end
Function04Name, Function04Label = F04, "De-occupy All Blocks";
--[[---------------------------------------------------------------------------------------]]
function LLLButton()
	if (Layout.LLLoop == true) then
		print("Balloon Loop");
		Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);	
		else
		print("Point-to-Point");
		Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);	
		end;
	return true;
end
Function06Name, Function06Label = LLLButton, "Balloon/Pt-to-Pt";
--[[---------------------------------------------------------------------------------------]]
function RunTimeButton()
-- this function does not do anything except print the run time
print(string.format("RunTime = %.2f seconds", RunTime()));
return true;
end
Function07Name, Function07Label = RunTimeButton, "Run Time";
--[[---------------------------------------------------------------------------------------]]

function AppLightingButton()
print("Toggle Approach Lighting");
-- toggle Approach Lighting flag
ApproachLighting =  not ApproachLighting;
print("Approach Lighting set to " .. tostring(ApproachLighting));
return true;
end
Function08Name, Function08Label = AppLightingButton, "Approach Lighting";
--[[---------------------------------------------------------------------------------------]]

function F09()
if (not SetupComplete) then
	Beep();
	return true;
	end;
printc(clGreen,"Traffic Stick Relay Status -------------------------");
for iSignal, iSetting in pairs(FSR) do
	if (iSetting ~= 0) then
		printc(clRed, SignalNames[iSignal] .. " (" .. iSignal .. ") = ", DirectionNames[iSetting]);;
		end;
	end
return true;
end
Function09Name, Function09Label = F09, "Traffic Stick Status";
--[[---------------------------------------------------------------------------------------]]
--
-- Used for three operations
-- 1. the EOB script send initial block occupancy information
-- 2. Allow "Layout4 Block XX Occupancy.lua" to simulate operation
--		of the layout without actually having a layout. That script
--		sends tags and occupancy information just as if an engine
--		were runnning on a layout.
-- 3. Allow a user to interact with operation on the layout by setting
--		and resetting occupancy flags
--
-- More on operation #2:
--
-- When used for testing this function receives events from scripts when simulating
-- an engine travelling over the layout.
--
-- More on operation #3
--
-- When operational, this function receives events from the layout window (Layout.cpp) when
-- the user manually sets flags
--
-- Routine
--	1. to accept TAG_EVENT from "Layout4 Block XX Occupancy.lua"
--	2. to accept BLOCK_OCCUPANCY_EVENT from "Layout4 Block XX Occupancy.lua"
--  3. to accept BLOCK_OCCUPANCY_EVENT from manual setting of occupancy in Layout Window (Layout.cpp)
-- examples:
--	res = SetEvent(1, BLOCK_OCCUPANCY_EVENT, ALIQUIPPA_BLOCK, OCCUPIED, MyDirection);	-- set block occupancy flag
--	res = SetEvent(1, TAG_EVENT, ALIQUIPPA_WEST_DETECTOR, ALIQUIPPA_WEST_READER, ENGINE_TAG_EVENT);	-- simulate a tag
--
function event(FromPC, Type, P1, P2, P3)

if (Type == BLOCK_OCCUPANCY_EVENT) then
	-- Block Number must be non zero and be a valid flag number
	-- first get direction of travel
	if (P3 == nil) then
		Stop("Block Occupancy Event requires direction of travel");
		end;
	local traffic;
	traffic = "NDT";	-- possible from Layout Window
	if (P3 == EB) then
		traffic = "East";
		elseif (P3 == WB) then
		traffic = "West";
		end;	
	if (P1 < 1 or P1 > FLAGLIMIT or P1 == nil) then
		return false;
		end;
	if (FromPC > PCMAX) then
	
		-- FromPC == 99 for layout window
		print("Block Occupancy event() From Layout Window - ", BlockName[P1], " Value = ", P2, " Direction = ", traffic);
		else
		-- FromPC 1 to PCMAX for PC Window
		print("Block Occupancy event() From PC# ", FromPC, " - ", BlockName[P1], " Value = ", P2, " Direction = ", traffic);
		end
	-- Block Number = P1, value can be 1 to (FLAGLIMIT - 1)
	-- Flag Value = P2, value can be 0 or 1. math.abs() is taken incase it is negative
	-- P3 Direction of Traffic EB, WB or NDT
	-- Layout4BlockOcc				-- Table to hold the APB Master Block Occupancy Status.
									-- Values in this table can be:
									--  0	UNOCCUPIED
									-- >0	OCCUPIED
									--		1	Occupied or a thru/passing or yard with 1 branch occupied
									--		2	Thru/passing or yard with both branches occupied
	-- Update the block number for passing, station and yard2 tracks
	if (P1 == STRUTHERS_STATION_TRACK) then 
		P1 = STRUTHERS_BLOCK; 
		elseif (P1 == COLLEGE_STATION_TRACK) then 
			P1 = COLLEGE_BLOCK; 
			elseif (P1 == J_L_PASSING_TRACK) then 
				P1 = J_L_BLOCK; 
				elseif (P1 == MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK) then 
					P1 = MCKEES_ROCKS_REPAIR_SHOPS_YARD_BLOCK; 
					elseif (P1 == GATEWAY_YARD2_TRACK) then 
						P1 = GATEWAY_YARD_BLOCK; 
						end;

	-- An event can set any occupancy flag to any P2 and if P2 > 0 then increment block occupancy count by P2
	local OppDir, x;
	if (P3 == WB) then
		x = -1;
		OppDir = EB;
		elseif (P3 == EB) then
			x = 1;
			OppDir = WB;
			end;
		
	if (P2 ~= UNOCCUPIED) then
		print("P2 = " .. tostring(P2) .. " P1 = " .. tostring(P1) .. " Layout4BlockOcc[P1] = " .. tostring(Layout4BlockOcc[P1]));
		Layout4BlockOcc[P1] = Layout4BlockOcc[P1] + math.abs(P2);
		--print("OppDir = " .. tostring(OppDir) .. " P1 = " .. tostring(P1));
		if (OppDir ~= nil) then
			-- Set direction of travel into FSR[]
			print("FSR[" .. NextSignal[OppDir][P1][HOMESIGNAL] + x .. "]=" .. traffic); 
			FSR[NextSignal[OppDir][P1][HOMESIGNAL] + x] = P3;
			end;
		end;
	if (P2 == UNOCCUPIED) then
		if (Layout4BlockOcc[P1] ~= UNOCCUPIED) then		-- This flag is used by this APB Master thread
			if (Layout4BlockOcc[P1] > 0) then  
				Layout4BlockOcc[P1] = Layout4BlockOcc[P1] - 1;
				end;
			end;			
		end;

	-- It is the responsibility of the sketch or program sending the event to call SetFlag()
	-- in order to set the flag to the correct color and to set the hint correctly.
	return true;
	end;
--[[
	oldway

if (Type == BLOCK_OCCUPANCY_EVENT) then
	-- Block Number must be non zero and be a valid flag number
	if (P1 < 1 or P1 > FLAGLIMIT or P1 == nil) then
		return false;
		end;
	if (FromPC > PCMAX) then
		-- FromPC == 99 for layout window
		print("Block Occupancy event() From Layout Window - ", BlockName[P1], " Value = ", P2);
		else
		-- FromPC 1 to PCMAX for PC Window
		print("Block Occupancy event() From PC# ", FromPC, " - ", BlockName[P1], " Value = ", P2);
		end
	-- Block Number = P1, value can be 1 to (FLAGLIMIT - 1)
	-- Flag Value = P2, value can be -2, -1, 0 , 1, 2
	-- Flag values of -2 or -1 are changed to a 1
	-- P3 not used
	-- update the block number for passing and station tracks
	if (P1 == STRUTHERS_STATION_TRACK) then 
		P1 = STRUTHERS_BLOCK; 
		elseif (P1 == COLLEGE_STATION_TRACK) then 
			P1 = COLLEGE_BLOCK; 
			elseif (P1 == J_L_PASSING_TRACK) then 
				P1 = J_L_BLOCK; 
				end;

	-- An event can set any occupancy flag to any P2 and if P2 > 0 then increment block occupancy count by P2
	if (P2 <= 0) then
		Layout4BlockOcc[P1] = P2;
		if (P2 == 0) then
			Layout4BlockOcc[P1] = UNOCCUPIED;				-- This flag is used by this APB Master thread
			end;
		else
		Layout4BlockOcc[P1] = Layout4BlockOcc[P1] + P2;
		end
	-- It is the responsibility of the sketch or program sending the event to call SetFlag()
	-- in order to set the flag to the correct color and to set the hint correctly.
	return true;
	end;	
	
--]]

if (Type == TAG_EVENT) then
	-- Detector = P1
	-- Reader	= P2
	--            P3 ~= 0 means Engine
	--            P3 == 0 means Caboose --- NOTE that this supports only an Engine tag and a Caboose tag
	-- Engine Number	1561				NOT_AN_ENGINE
	-- CarModel			UNKNOWN_CARMODEL	CABOOSE
	-- Car Number		1561				123
	if (P3 ~= NOT_AN_ENGINE) then	-- engine
		print("Engine Tag Event");
		tag(P1, P2, 1561, TAG_ON_FRONT_TRUCK, UNKNOWN_CARMODEL, P_AND_LE, "1561", "NONE")	;
		else			-- caboose
		print("Caboose Tag Event");
		tag(P1, P2, NOT_AN_ENGINE, TAG_ON_FRONT_TRUCK, CABOOSE, P_AND_LE, "123", "NONE")	;
		end;
	return true;
	end;
return true;		-- false=event failed, true=event succeeded	
end

--[[---------------------------------------------------------------------------------------]]
function TestButton()
	print("LED Signal Test");
	LEDTestOff = false
	-- Loop 100 times
	for iCnt = 1, 100 do
		-- turn all LED off
		for Signal = 1, Layout.SIGNALMAX do
			if (SignalNames[Signal] ~= nil) then	-- good signal number
				--print("Signal = " .. Signal);
				SetAspect(Signal, AsDark);
				SavedSig[Signal] = AsDark;	-- save current aspect
			end;
		end;
		if (LEDTestOff) then return true; end;
		Sleep(3);
		if (LEDTestOff) then return true; end;
		-- Green	
		for Signal = 1, Layout.SIGNALMAX do
			if (SignalNames[Signal] ~= nil) then	-- good signal number
				--print("Signal = " .. Signal);
				SetAspect(Signal, AsClear);
				SavedSig[Signal] = AsClear;	-- save current aspect
			end;
		end;
		if (LEDTestOff) then return true; end;		
		Sleep(3);
		if (LEDTestOff) then return true; end;
		-- Yellow		
		for Signal = 1, Layout.SIGNALMAX do
			if (SignalNames[Signal] ~= nil) then	-- good signal number
				--print("Signal = " .. Signal);
				SetAspect(Signal, AsApproach);
				SavedSig[Signal] = AsApproach;	-- save current aspect
			end;
		end;
		if (LEDTestOff) then return true; end;		
		Sleep(3);
		if (LEDTestOff) then return true; end;
		-- Red		
		for Signal = 1, Layout.SIGNALMAX do
			if (SignalNames[Signal] ~= nil) then	-- good signal number
				--print("Signal = " .. Signal);
				SetAspect(Signal, AsStopandProceed);
				SavedSig[Signal] = AsStopandProceed;	-- save current aspect
			end;
		end;
		if (LEDTestOff) then return true; end;		
		Sleep(3);
		if (LEDTestOff) then return true; end;
		-- turn all LED on		
		for Signal = 1, Layout.SIGNALMAX do
			if (SignalNames[Signal] ~= nil) then	-- good signal number
				--print("Signal = " .. Signal);
				SetAspect(Signal, AsTest);
				SavedSig[Signal] = AsTest;	-- save current aspect
			end;
		end;
		if (LEDTestOff) then return true; end;		
		Sleep(3);
		if (LEDTestOff) then return true; end;		
	end;
	return true;
end
Function21Name, Function21Label = TestButton, "LED Test";
--[[---------------------------------------------------------------------------------------]]
function TestOffButton()
	print("LED Signal Test Off");
	LEDTestOff = true;
	return true;
end
Function22Name, Function22Label = TestOffButton, "LED Test Off";
--[[---------------------------------------------------------------------------------------]]	
