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

© Copyright 2022 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

require("defines");
require("functions");

title = "Stop in Block Test"
--
--[[
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 _;
--
--[[---------------------------------------------------------------------------------------]]

--[[
This test starts an engine. When that engine passes over a tag reader, a calculation
is done to stop that train in the block just entered.

It sits there for a few seconds and then repeats at the next tag reader.

The calculated Stop in Block distance should safely get the engine stopped before
it passes over the next tag reader.
]]
	
local TagCount = 0;
-- Counter Labels - all globals
Counter01Label = "tags()";
C1 = COUNTER01;
Counter03Label = "Block Speed Limit";
C3 = COUNTER03;
Counter04Label = "Target Distance";
C4 = COUNTER04;
Counter05Label = "Stopping Distance";
C5 = COUNTER05;
Counter06Label = "Stop Delay";
C6 = COUNTER06;
	
-- state machine states
local IDLE = 0;
local STOPATNEXTSTATION = 1;
local SM = IDLE;

local MyEngineNo, MyTIUNo;
--local AC, IAC;
local MyDirection = WB;
local OppDirection = EB;
Layout4 = nil;	-- global
local StartBlockNo, CurrentBlockNo, LastBlockNo;
local ExpectedReader = 0;	-- check that we get the next expected tag reader
local WaitforCaboose = false;

local RunningSpeed = 20;
local LastSentSpeed = 0;
local WaitingTime = 10;
local BeepOn = false;

local TIUSerialPortConnected, TIUConnected, RFIDPortConnected;
--
local SetupComplete = false;	-- if true, permits settings to be saved.
--[[---------------------------------------------------------------------------------------]]
-- settings are saved in this file
local settingsfile = "StopinBlock-PC" .. tostring(PCNo()) .. ".ini";

-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "Control";
Tab02Label = "Support";
Tab03Label = "Debug";

local function readSettings()
	local f = io.open(settingsfile, "r");
	if f then
		print("Loading settings from " .. settingsfile);
		-- Engine Number
		MyEngineNo   = tonumber(string.match(f:read("*l"), "%d+")) or 1;
		-- Running Speed
		RunningSpeed = tonumber(string.match(f:read("*l"), "%d+")) or 20;
		-- Direction
		MyDirection  = tonumber(string.match(f:read("*l"), "%d+")) or WB;
		-- Starting Block
		StartBlockNo = tonumber(string.match(f:read("*l"), "%d+")) or YOUNGSTOWN_BLOCK;
		CurrentBlockNo = StartBlockNo;
		f:close()
		print("Engine = " .. MyEngineNo .. " " .. GetName(MyEngineNo));
		print("Running Speed = " .. RunningSpeed .. " Smph");
		print("Direction = " .. MyDirection .. " " .. DirectionNames[MyDirection]);
		print("Start Block = " .. CurrentBlockNo .. " " .. BlockName[CurrentBlockNo]);		
		return true;
	end;
	return false;
end

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

local function writeSettings()
	-- setup must be complete before settings can be saved
	if (not SetupComplete) then
		return false;
		end;
	local f = io.open(settingsfile, "w");
	if f then
		print("Saving settings in " .. settingsfile);
		-- Engine Number
        f:write(tostring(MyEngineNo).. " " .. GetName(MyEngineNo) .. "\n");		
		-- Running Speed
		f:write(tostring(RunningSpeed).. " Smph\n");	
		-- Direction
		f:write(tostring(MyDirection).. " " .. DirectionNames[MyDirection] .. "\n");
		-- Starting Block
		f:write(tostring(CurrentBlockNo).. " " .. BlockName[CurrentBlockNo] .. "\n");	-- save current block for restart
		f:close();
		print("Engine = " .. MyEngineNo .. " " .. GetName(MyEngineNo));
		print("Running Speed = " .. RunningSpeed .. " Smph");
		print("Direction = " .. MyDirection .. " " .. DirectionNames[MyDirection]);		
		print("Current Block = " .. CurrentBlockNo .. " " .. BlockName[CurrentBlockNo]);
		return true;
	end;	
	return false;
end;

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
	-- this function is called once when the [Run Script] button is pushed
	-- Engine : Engine number selected on the RTC Main window
	-- TIU : TIU number selected on the RTC Main window
	print("setup()");
	-- Instrumented Layout 4
	Layout4 = require([[Layout4]]);		-- All of the details about the Layout
	--	
	MyEngineNo = Engine;	-- this engine number may be changed by the query below
	-- The TIU which controls layout 4 is defined in the Layout4.lua file
	MyTIUNo = Layout4.MASTER_TIU;
	--
	if (Layout4.LLLoop == false) then
		return Stop("Requires a Looping Layout Configuration");
		end;
	--
	readSettings();		-- read the .ini file and load MyEngineNo, MyDirection, StartBlockNo and RunningSpeed
	--
	SetCounter(C3, RunningSpeed);	
	-- make sure that RFID is connected
	TIUSerialPortConnected, TIUConnected, RFIDPortConnected = CommStatus();	
	--print("TIUPortConnected : " .. tostring(TIUPortConnected) .. " - TIUConnected : " .. tostring(TIUConnected) .. " - RFIDSerialPortConnected : " .. tostring(RFIDSerialPortConnected));
	if (not RFIDPortConnected) then
		print("RFID Port must be enabled for this script");
		return false;	-- false=setup failed
		end
	--
	-- Requires at least 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) and (NumDetectors < 8 or (NumReaders < 13 and NumReaders ~= 0))) then
		return Stop("Requires at least 8 Tag Detectors and 13 Tag Readers. Cycle power on layout and try again.")
		end;
	--	
	
	--[[--------------------------------------------]]
	local EngineNameList = {};	-- table to hold all of the Engine numbers and Names
	local EngineNameIndex = 1;
	local EngineNameCount = 1;

	for iEng = 1, 99 do	-- check all possible engines on one TIU
		if (Exists(iEng)) then
			local Name, PS;
			Name, PS = GetName(iEng);
			--print(iEng .. " " .. Name .. " PS" .. PS);
			EngineNameList[iEng] = iEng .. " " .. Name .. " PS" .. PS;
			if (MyEngineNo == iEng) then
				EngineNameIndex = EngineNameCount;
				--print("EngineNameIndex = " .. EngineNameIndex);
				end;
			EngineNameCount = EngineNameCount + 1;		
			end
		end
	--[[--------------------------------------------]]		
	--
	-- Ask the dispatcher for the engine number
	--
	--[[--------------------------------------------]]	
	print("Select Engine");
	local Enum = InputDropDown(title, "Select Engine", EngineNameIndex, EngineNameList);
	--print("Enum ", Enum);
	local typ = type(Enum);
	--print("Type ", typ);

	if (typ == "boolean" and Enum == false) then
		return Stop("Canceled");
		end;
			
	if (Enum ~= nil and Enum ~= "") 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 EngNo = string.match(Enum, "%d+");
		if (EngNo ~= nil) then
			MyEngineNo = tonumber(EngNo);
			else
			return Stop("Invalid Engine Number");
			end;
		else
		if (typ == "nil") then
			-- nil returned only if parameters are invalid
			return Stop("Invalid Parameters to InputDropDown");
			else
			MyEngineNo = 1;
			end
		end	
	--[[--------------------------------------------]]
	print("Setup : Engine Number = " .. MyEngineNo);
	Rate(Layout4.AccDecRate, Layout4.AccDecRate, 0, MyEngineNo, MyTIUNo);
	print("setup : Set accel/decel rates to " .. Layout4.AccDecRate);	
	--[[--------------------------------------------]]		
	-- Show new title with Engine # and Name and put Engine # into Engine Number Spinner
	local Name = GetName(MyEngineNo);
	Title(title .. " #" .. MyEngineNo .. " " .. Name, MyEngineNo);
	--[[--------------------------------------------]]
	--
	-- Ask the dispatcher for direction
	--	
	--[[--------------------------------------------]]
	print("Select Direction");
	local DirValue = InputDropDown(title, "Select Direction", MyDirection, {"1 Westbound", "2 Eastbound"});
	--print("DirValue ", DirValue);
	typ = type(DirValue);
	--print("Type ", typ);

	if (typ == "boolean" and DirValue == false) then
		return Stop("Canceled");
		end;
			
	if (DirValue ~= nil and DirValue ~= "") 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 DirNo = string.match(DirValue, "%d+");
		if (DirNo ~= nil) then
			MyDirection = tonumber(DirNo);
			else
			return Stop("Invalid Direction");
			end;
		else
		if (typ == "nil") then
			-- nil returned only if parameters are invalid
			return Stop("Invalid Parameters to InputDropDown");
			else
			--MyDirection is not changed 
			end
		end
	if (MyDirection == WB) then
		title = "StopBlock WB"; -- Westbound
		OppDirection = EB;
		end;
	if (MyDirection == EB) then
		title = "StopBlock EB"; -- Eastbound
		OppDirection = WB;
		end;
	--[[--------------------------------------------]]	
	Title(title .. " : Engine #" .. MyEngineNo .. " " .. Name, MyEngineNo);
	print("Setup : Direction = " .. DirectionNames[MyDirection]);
	--[[--------------------------------------------]]	
	--
	-- Ask the dispatcher for starting block number
	--
	--[[--------------------------------------------]]	
	-- For westbound trains, starting block can be any Thru block or any mainline block
	-- For eastbound trains, starting block can be any Station/Passing block or any mainline block
	--
	local BlockNameList = {};	-- table to hold all of the block numbers and names
	local BlockNameIndex = 1;
	local BlockNameCount = 1;
	
	for iBlock = 1, LAST_BLOCK do
		local Name = BlockName[iBlock];
		if (Name ~= nil and iBlock <= LAST_BLOCK) then
			--print(Name .. "(" .. iBlock .. ")");
			if (MyDirection == EB) then
				-- don't allow selection of thru blocks or starting in McKees Rocks Repair Shops Yard
				if ((iBlock ~= J_L_THRU_TRACK) and (iBlock ~= COLLEGE_THRU_TRACK) and (iBlock ~= STRUTHERS_THRU_TRACK) and
					(iBlock ~= MCKEES_ROCKS_REPAIR_SHOPS_YARD1_TRACK) and (iBlock ~= MCKEES_ROCKS_REPAIR_SHOPS_YARD2_TRACK)) then 
					BlockNameList[BlockNameCount] = iBlock .. " " .. Name; 
					--print("BlockNameList[" .. BlockNameCount .. "] = " .. BlockNameList[BlockNameCount]);
					--------------------------------------------
					if (StartBlockNo == iBlock) then
						BlockNameIndex = BlockNameCount;
						--print("BlockNameIndex = " .. BlockNameIndex);
						end;
					BlockNameCount = BlockNameCount + 1;		
					--------------------------------------------					
					end;
				end;
			if (MyDirection == WB) then
				-- don't allow selection of station/passing blocks or starting in Gateway Yard
				if ((iBlock ~= J_L_PASSING_TRACK) and (iBlock ~= COLLEGE_STATION_TRACK) and (iBlock ~= STRUTHERS_STATION_TRACK) and
					(iBlock ~= GATEWAY_YARD1_TRACK) and (iBlock ~= GATEWAY_YARD2_TRACK)) then
					BlockNameList[BlockNameCount] = iBlock .. " " .. Name; 
					--print("BlockNameList[" .. BlockNameCount .. "] = " .. BlockNameList[BlockNameCount]);
					--------------------------------------------
					if (StartBlockNo == iBlock) then
						BlockNameIndex = BlockNameCount;
						--print("BlockNameIndex = " .. BlockNameIndex);
						end;
					BlockNameCount = BlockNameCount + 1;		
					--------------------------------------------					
					end;
				end;
			end;
		end;
	--[[--------------------------------------------]]		
	--
	-- read Starting Block Number
	--
	--[[--------------------------------------------]]
	print("Select Starting Block");
	--
	CurrentBlockNo = StartBlockNo;
	local StartingBlock = InputDropDown(title, "Select Starting Block", BlockNameIndex, BlockNameList);
	--print("StartingBlock ", StartingBlock);
	typ = type(StartingBlock);
	--print("Type ", typ);

	if (typ == "boolean" and StartingBlock == false) then
		return Stop("Canceled");
		end;
			
	if (StartingBlock ~= nil and StartingBlock ~= "") 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(StartingBlock, "%d+");
		if (BlockNo ~= nil) then
			StartBlockNo = tonumber(BlockNo);
			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");
			else
			StartBlockNo = 1;
			end
		end	
	CurrentBlockNo = StartBlockNo;
	--[[--------------------------------------------]]		
	print("Setup : Starting Block = " .. BlockName[StartBlockNo]);
	--[[--------------------------------------------]]	
	--
	-- Ask the Engineer for Speed
	--	
	--[[--------------------------------------------]]	
	local RunningSpeedList = {};	-- table to hold all of the Engine speeds
	local RunningSpeedIndex = 1;
	local RunningSpeedCount = 1;

	for iBlockSpeed = 5, 60, 5 do
		--print(iBlockSpeed .. " Smph");
		RunningSpeedList[RunningSpeedCount] = iBlockSpeed .. " Smph"; 
		--------------------------------------------
		if (RunningSpeed == iBlockSpeed) then
			RunningSpeedIndex = RunningSpeedCount;
			--print("RunningSpeedIndex = " .. RunningSpeedIndex);
			end;
		RunningSpeedCount = RunningSpeedCount + 1;		
		--------------------------------------------					
		end;


	print("Select Running Speed");
	local SpdValue = InputDropDown(title, "Select Running Speed", RunningSpeedIndex, RunningSpeedList);
	--print("SpdValue ", SpdValue);
	typ = type(SpdValue);
	--print("Type ", typ);


	if (typ == "boolean" and SpdValue == false) then
		return Stop("Canceled");
		end;
			
	if (SpdValue ~= nil and SpdValue ~= "") 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 Spd = string.match(SpdValue, "%d+");
		if (Spd ~= nil) then
			RunningSpeed = tonumber(Spd);
			else
			return Stop("Invalid Running Speed");
			end;
		else
		if (typ == "nil") then
			-- nil returned only if parameters are invalid
			return Stop("Invalid Parameters to InputDropDown");
			else
			--RunningSpeed is not changed
			end
		end	
	--[[--------------------------------------------]]			
	print("Setup : Running Speed = " .. RunningSpeed);	
	--RunningSpeed =  BlockSpeed[MyDirection][StartBlockNo];	-- alternate method for setting the speed
	SetCounter(C3, RunningSpeed);
	--[[----------------------------------------------------------------------------------------------------]]		
	-- All options selected, proceed with setup
	-- Set first expected reader
	--print("5 OverrideOccupancy Flag " .. CurrentBlockNo .. " " .. GetFlag(CurrentBlockNo) .. " " .. (MyEngineNo | (MyDirection << 8)));
	OverrideOccupancy(CurrentBlockNo, MyEngineNo | (MyDirection << 8));	-- force set occupancy flag on the starting block
	--print("6 OverrideOccupancy Flag " .. CurrentBlockNo .. " " .. GetFlag(CurrentBlockNo));
	ExpectedReader = Layout4.STOPBlock(CurrentBlockNo, OppDirection, DETECTOR) * 10 + Layout4.STOPBlock(CurrentBlockNo, OppDirection, READER)
	print("Expected Reader = " .. ExpectedReader);	
	--
	-- Set Loop Switches to looping
	Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0)	

	if (MyDirection == EB) then
		print("Setup : Eastbound");
		print("All passing/station track switches to REVERSE to stop at the station");
		Switch(REVERSE, Switches[ALIQUIPPA_WEST][CHAN_NUMBER], Switches[ALIQUIPPA_WEST][AIU_NUMBER], Switches[ALIQUIPPA_WEST][TIU_NUMBER], 0)	
		Sleep(1);		
		Switch(REVERSE, Switches[ALIQUIPPA_EAST][CHAN_NUMBER], Switches[ALIQUIPPA_EAST][AIU_NUMBER], Switches[ALIQUIPPA_EAST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(REVERSE, Switches[COLLEGE_WEST][CHAN_NUMBER], Switches[COLLEGE_WEST][AIU_NUMBER], Switches[COLLEGE_WEST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(REVERSE, Switches[COLLEGE_EAST][CHAN_NUMBER], Switches[COLLEGE_EAST][AIU_NUMBER], Switches[COLLEGE_EAST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(REVERSE, Switches[STRUTHERS_WEST][CHAN_NUMBER], Switches[STRUTHERS_WEST][AIU_NUMBER], Switches[STRUTHERS_WEST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(REVERSE, Switches[STRUTHERS_EAST][CHAN_NUMBER], Switches[STRUTHERS_EAST][AIU_NUMBER], Switches[STRUTHERS_EAST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(NORMAL, Switches[WOODLAWN][CHAN_NUMBER], Switches[WOODLAWN][AIU_NUMBER], Switches[WOODLAWN][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(NORMAL, Switches[CAMPBELL][CHAN_NUMBER], Switches[CAMPBELL][AIU_NUMBER], Switches[CAMPBELL][TIU_NUMBER], 0)
		Sleep(3);	-- time for all of the switches to align
		else
		print("Setup : Westbound");
		print("All passing/station track switches to NORMAL");
		Switch(NORMAL, Switches[ALIQUIPPA_WEST][CHAN_NUMBER], Switches[ALIQUIPPA_WEST][AIU_NUMBER], Switches[ALIQUIPPA_WEST][TIU_NUMBER], 0)	
		Sleep(1);		
		Switch(NORMAL, Switches[ALIQUIPPA_EAST][CHAN_NUMBER], Switches[ALIQUIPPA_EAST][AIU_NUMBER], Switches[ALIQUIPPA_EAST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(NORMAL, Switches[COLLEGE_WEST][CHAN_NUMBER], Switches[COLLEGE_WEST][AIU_NUMBER], Switches[COLLEGE_WEST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(NORMAL, Switches[COLLEGE_EAST][CHAN_NUMBER], Switches[COLLEGE_EAST][AIU_NUMBER], Switches[COLLEGE_EAST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(NORMAL, Switches[STRUTHERS_WEST][CHAN_NUMBER], Switches[STRUTHERS_WEST][AIU_NUMBER], Switches[STRUTHERS_WEST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(NORMAL, Switches[STRUTHERS_EAST][CHAN_NUMBER], Switches[STRUTHERS_EAST][AIU_NUMBER], Switches[STRUTHERS_EAST][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(NORMAL, Switches[WOODLAWN][CHAN_NUMBER], Switches[WOODLAWN][AIU_NUMBER], Switches[WOODLAWN][TIU_NUMBER], 0)
		Sleep(1);		
		Switch(NORMAL, Switches[CAMPBELL][CHAN_NUMBER], Switches[CAMPBELL][AIU_NUMBER], Switches[CAMPBELL][TIU_NUMBER], 0)
		Sleep(3);	-- time for all of the switches to align
		end
	--[[-------------------------]]	
	-- debug level as selected on the Debug window, values 0 (off) to 9 (maximum)
	if (Debug() > 0) then 
		print(string.format("setup(): Debug level %d", Debug())); 
		print("setup(): Engine Number " .. MyEngineNo .. " TIU Number " .. MyTIUNo);
		end
	print("MyTIUNo : " .. MyTIUNo);
	assert((MyTIUNo >= 1 and MyTIUNo <= 5),"TIU Number out of range");
	-- initialize the layout as needed
	--AC, IAC = Counts();	-- Counts() returns two values
	--print("TIU" .. MyTIUNo .. ": " .. AC .. " Active Engines - " .. IAC .. " Inactive Engines");
	--
	CabChat(OFF, 0, MyEngineNo, MyTIUNo);
	Sleep(5);
	EngineStartUp(MyEngineNo, MyTIUNo);
	Sleep(20);
	Whistle(ON, 2, MyEngineNo, MyTIUNo);
	Whistle(OFF, 4, MyEngineNo, MyTIUNo);
	print("Trip Odometer " .. DTO(MyEngineNo, MyTIUNo)); 	
	Sleep(5);
	SetSpeed(RunningSpeed, 0, MyEngineNo, MyTIUNo);
	SetCounter(C3, RunningSpeed);
	--print(string.format("Speed %d Smph", GetSpeed(MyEngineNo, MyTIUNo)));
	--
	-- indicate that setup is complete and the settings can be saved
	SetupComplete = true;	
	return true;		-- false=setup failed, true=setup succeeded
	end
--[[---------------------------------------------------------------------------------------]]
function tag(Detector, Reader, EngineNo, TagLocation, CarModel, Railroad, CarNumber, TagPacket)
	-- this function is called each time a tag is detected

--[=[
	-- Line below shows a different way to quote a string using brackets
	if (EngineNo > 0) then	-- its an engine		
		-- 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)));

		else	-- it's a car
		printc(clBlue, string.format([[tag(%.2f) : Detector %d Reader %d Car %s %s #%s]],
			RunTime(), Detector, Reader, GetRailroadName(Railroad), GetCarName(CarModel), CarNumber));
		-- ignore non engine tags
		return true;	-- true=continue to process tags, false=stop processing tags
		end
]=]	
	BumpCounter(C1);	
		
	if (EngineNo > 0) then	-- it's an engine
		if (EngineNo == MyEngineNo) then
			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)));
			else
			-- ignore engines that are not me
			return true;	-- true=continue to process tags, false=stop processing tags
			end;
		-- Continue on to process the detected engine
		else	-- it's a car
		if (CarModel == CABOOSE or CarModel == OBSERVATION) then
			if (BeepOn) then Beep(EXCLAMATION); end;
			printc(clBlue, string.format([[tag(%.2f) : Detector %d Reader %d Car %s %s #%s]],
				RunTime(), Detector, Reader, GetRailroadName(Railroad), GetCarName(CarModel), CarNumber));
			else
			-- ignore all non-end of train cars
			return true;	-- true=continue to process tags, false=stop processing tags
			end;		
		-- Continue on to process end of train tags
		end
	
	
	-- The complete packet received is in TagPacket 
	-- (some of the most used fields have already been extracted into the first parameters to tag()
	--   First 8 digits are the 4 byte Tag UID in hexidecimal
	--   Final 32 digits are the 16 bytes of Block 4 Information read from the Tag in hexidecimal
	if (Debug() > 4) then print(string.format("Packet  %s", TagPacket)); end
	TagCount = TagCount + 1;	-- count the number of tags
	
	local MyReader = (Detector * 10) + Reader;
	if (MyReader ~= ExpectedReader) then
		return Stop("Got Reader = " .. MyReader .. " Expected Reader = " .. ExpectedReader);
		end
	
	if (isEngine(EngineNo) and isTagFront(TagLocation)) then
			
--[[--- Engine Detected -----]]
	
		-- Engine front truck
		--print(string.format("Speed %d Smph",GetSpeed(EngineNo, MyTIUNo)));
		--print("Trip Odometer " .. DTO(EngineNo, MyTIUNo));
		LastBlockNo = CurrentBlockNo;
		local StoppingBlockNo = Layout4.NEXTBlock(MyDirection, MyReader);
		local Spd = GetSpeed(EngineNo, MyTIUNo);	-- get speed as engine covers tag
		---[[
		-- this handles the case where the engine could not get up to speed
		-- because it stopped too close to the next tag.
		if (Spd ~= nil) then
			-- set speed to speed over the tag (Spd)
			SetSpeed(Spd, 0, EngineNo, MyTIUNo);
			-- then continue on using that speed to calculate the stop;
			local StopDelay;		-- stop in every block
			-- set a StopBlockAfterDelay in the block then a resume speed (RunningSpeed) after WaitingTime seconds
			-- use speed read up from the engine (in Spd)
			StopDelay, _ = StopBlockAfterDelay(Layout4, true, StoppingBlockNo, EngineNo, MyDirection, Spd, Spd);
			--print(string.format("B %d Tag %d StoppingBlock = %s", MyDirection, MyReader, BlockName[StoppingBlockNo]));
			-- wait a few seconds and start the engine running
			print(string.format("Set speed to %d Smph after %.3f seconds (@ %.3f Elapsed Time)", RunningSpeed, StopDelay + WaitingTime,
				GetCounter(ELAPSEDTIME) + StopDelay + WaitingTime))
			-- Resume using RunningSpeed
			SetSpeed(RunningSpeed, StopDelay + WaitingTime, EngineNo, MyTIUNo);
			else
			printc(clRed, "GetSpeed failed - Not Stopping but continuing on");
			end;
		--
		-- set occupancy flags as usual
		--print("1 SingleSetOccupancy Flag " .. StoppingBlockNo .. " " ..GetFlag(StoppingBlockNo));
		SingleSetOccupancy({StoppingBlockNo}, MyEngineNo | (MyDirection << 8));
		CurrentBlockNo = StoppingBlockNo;
		--print("2 SingleSetOccupancy Flag " .. StoppingBlockNo .. " " ..GetFlag(StoppingBlockNo));
		WaitforCaboose = true;

--[[--- Caboose Detected -----]]
		
		elseif (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION) and WaitforCaboose) then
			if (LastBlockNo ~= 0) then
				--print("3 ClearOccupancy Flag " .. LastBlockNo .. " " ..GetFlag(LastBlockNo));
				ClearOccupancy(LastBlockNo, MyEngineNo | (MyDirection << 8));	-- clear LastBlockNo occupancy (must use MyEngineNo here)
				--print("4 ClearOccupancy Flag " .. LastBlockNo .. " " ..GetFlag(LastBlockNo));
				end;
			if ((GetCounter(C1)) % 5 ~= 0) then
				return Stop("Tag counter not multiple of 5");
				end;				
			ExpectedReader = Layout4.STOPBlock(CurrentBlockNo, OppDirection, DETECTOR) * 10 + Layout4.STOPBlock(CurrentBlockNo, OppDirection, READER);
			print("Expected Reader Changed to " .. ExpectedReader);				
			WaitforCaboose = false;
		end
	-- its a end of train car
--[[---------------------------------------------------------------------------------------]]

--[[---------------------------------------------------------------------------------------]]
	return true;	-- true=continue to process tags, false=stop processing tags
	end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Eng, TIU)
	-- this function is called once when the user presses the [STOP] button
	print("cleanup()");
	ClearDispatchList();
	SetSpeed(0, 0, MyEngineNo, MyTIUNo);
	Rate(Layout4.AccDecRate, Layout4.AccDecRate, 0, MyEngineNo, MyTIUNo);
	--
	writeSettings();		-- write the .ini file and save MyEngineNo, MyDirection, StartBlockNo and Running Speed
	--	
	print("Shutdown in Progress.....");
	Sleep(10);	
	EngineShutdown(MyEngineNo, MyTIUNo);
	print(string.format("cleanup() Complete at %.2f", RunTime()));
	--
	return true;		-- false=cleanup failed, true=cleanup succeeded
	end
--[[---------------------------------------------------------------------------------------]]

function PlayBell(Eng, TIU)
	print("PlayBell(): Engine " .. MyEngineNo .. " TIU " .. TIU .. " Debug() " .. Debug());
	Bell(ON, 0, MyEngineNo, MyTIUNo);
	Bell(OFF, 4, MyEngineNo, MyTIUNo);
	return true;
end
Function02Name, Function02Label = PlayBell, "Sound Bell";
--[[---------------------------------------------------------------------------------------]]
function ClearButton(Eng, TIU)
print("Clear Occupancy Flags");
-- clear occupancy flags on request but not at startup (cleared in "Layout4 APB Master")
local x;
for x = FIRST_BLOCK, LAST_BLOCK do
	OverrideOccupancy(x, UNOCCUPIED);
	end;
print("Occupancy Flags cleared");
return true;
end
Function03Name, Function03Label = ClearButton, "Clear Flags";
--[[---------------------------------------------------------------------------------------]]
function Plus5Button(Eng, TIU)
RunningSpeed = RunningSpeed + 5;
SetSpeed(RunningSpeed, 0, MyEngineNo, MyTIUNo);
SetCounter(C3, RunningSpeed)
return true;
end
Function04Name, Function04Label = Plus5Button, "Speed +5";
--[[---------------------------------------------------------------------------------------]]
function Minus5Button(Eng, TIU)
RunningSpeed = RunningSpeed - 5;
SetSpeed(RunningSpeed, 0, MyEngineNo, MyTIUNo);
SetCounter(C3, RunningSpeed)
return true;
end
Function05Name, Function05Label = Minus5Button, "Speed -5";
--[[---------------------------------------------------------------------------------------]]
function ShowDTO(Eng, TIU)
	print("ShowDTO(): Engine " .. MyEngineNo .. " TIU " .. TIU .. " Debug() " .. Debug());
	print("Trip Odometer " .. DTO(MyEngineNo, TIU));
	return true;
end
Function06Name, Function06Label = ShowDTO, "Trip Odometer";
--[[---------------------------------------------------------------------------------------]]
function ResOn(Eng, TIU)
	print(string.format("Resume Speed - Engine %d TIU %d Debug() %d", MyEngineNo, TIU, Debug()));
	SetSpeed(RunningSpeed, 0, MyEngineNo, MyTIUNo);
	SetCounter(C3, RunningSpeed);
	return true;
end
Function07Name, Function07Label = ResOn, "Resume Speed";
--[[---------------------------------------------------------------------------------------]]
function ResOff(Eng, TIU)
	ClearDispatchList();
	print(string.format("Stop - Engine %d TIU %d Debug() %d", MyEngineNo, TIU, Debug()));
	SetSpeed(0, 0, MyEngineNo, MyTIUNo);
	SetCounter(C3, 0);
	return true;
end
Function08Name, Function08Label = ResOff, "Stop";
--[[---------------------------------------------------------------------------------------]]
function OccButton(Eng, TIU)
printc(clGreen, "Occupancy Flags -------------------------");
local x, FColor;
for x = FIRST_BLOCK, LAST_BLOCK do
	local flag = GetFlag(x);
	if (flag == 0) then
		FColor = clBlack;
		else
		FColor = clRed;
		end;
	printc(FColor, BlockName[x] .. "(" .. x .. ") = ", flag)
	end;	
return true;
end
Function09Name, Function09Label = OccButton, "Occupancy Flags";
--[[---------------------------------------------------------------------------------------]]
function F10()
for Block = FIRST_BLOCK, LAST_BLOCK do
	OverrideOccupancy(Block, UNOCCUPIED);	-- This flag is used by the Engine PC threads
	end;
print("All blocks manually cleared");
return true;
end
Function10Name, Function10Label = F10, "Clear Flags";
--[[---------------------------------------------------------------------------------------]]
function ReloadButton()
	print("Reloading layout4.lua");
	package.loaded["layout4"] = nil;	-- unload
	Layout4 = 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";
--[[---------------------------------------------------------------------------------------]]