Mark DiVecchio's O-Gauge Train Layouts

1992 Layout Page

2006 Layout Page

2009 Layout Page

P&LE Postcards by Howard Fogg 

Plasticville Buildings

Portable Layout Page

Train Clubs

Bing Track

Remote Train Control Program

YouTube Channel

OOK Radio Support

Technical Videos

3D Prints for my Layout

More RTC Videos

ADPCM - Playing clips from .mth sound files

P&LE McKees Rocks Locomotive Shop
3D Printer Project

White Tower Restaurant
3D Printer Project

RFID Train Detection

Engine and Car Operation
Hints and Tricks

RFID Tag Programmer using PN532

RTC Control Language - Scripting

RTC WiFi Support
Getting rid of (most) of the wires

More RFID Tag Videos


RTC Control Langauge - Signaling (Flags, Fixed Signals and Cab Heads Up Display)

This Page last updated on .

Background

Like the cartoon below shows, running trains is hard. Trying to write a computer script that runs trains while thinking like a human engineer is really hard.....

Of course the most important thing to know about on a layout is the location of each train. On a real railroad, several different methods were used. This wikipedia page describes Railway Signaling.

  Under construction
(Always email me (markdsilogic.com) if you are going to use this program to be sure that you have the latest version)



July 1954
by Joe Salame
Model Railroader Magazine


"Layout" and Flags Display in RTC v4.3.0

Version 4.3 of the RTC program added a feature I call the "Layout". I found that as I wrote more and more complicated scripts, I needed a way to visualize the location of trains as they ran over the layout.

I put together a minimal implentation of a graphical view of the layout status. The background is a bitmap image of the layout. Superimposed over the layout are flags which display the occupancy status of each block. Right now, my layout is broken up into 15 blocks. My blocks generally run from switch track to switch track but can also be defined to split up a particularly long mainline run into several blocks.

The flags permit several scripts, each controlling an engine, to run those engines on the layout at the same time. By using the block occupancy flags, each script can keep its engine out of the way of other
engines. The flag setting functions, described below, use C++ coding constructions (like Critical Sections) to guarantee that testing a flag value and then setting a new value is done without the possibility of being interrupted by other scripts. In programmer speak, this "test and set" is an atomic operation.

Creating a bitmap of your layout.

First you need a bitmap of your layout. Since I used the RR-Track program to design my layout, I was done! RR-Track can export your design as a bitmap file. You can see mine below. Otherwise, you need to create something using a program similar to Paint which I think is available on every version of Windows. You can make it as fancy or as simple as you want. It just has to be a *.bmp file.


When you first open the Layout Window, this is what you will see.

Press the [Bitmap] button and select the bitmap file of your layout.

Since I did this screen capture, I've removed the Minimize-Maximize-Restore buttons and replaced with buttons to copy of the window or the layout to the clipboard as a bitmap file.
Here is the bitmap file of my layout. As I described above, this file is generated by the RR-Track program. You can scroll around on the layout or you can enlarge the window so you can see the entire layout. The position and size of the window is remembered automagically and used again next time you run the program.
Below is the enlarged Layout window.
Now you need to create and position the flags on the layout.

Click the right mouse button. On the popup menu, select Create Flag. Then point to where you want to place a flag.

Type in the flag name and then click on one of the four position points for where you want the name to appear. As shown you can select "Above Flag", "Right of Flag", "Below Flag" and "Left of Flag". This is so that you can put the text where it won't obscure some layout feature.
Below, I've added "Youngstown Block" and selected "Below Flag". When I first clicked on the layout, I used the mouse to point to the track and that is where the flag itself is placed. If you didn't get the position quite right, you can use the [Direct Edit] button to make changes. I'll show you that just below.
Below is my layout showing all of the flags that I have defined so far. When flags are first added, they are Green. That means the block represented by the flag  is not occupied. Flags turn Red once the running script recognizes that they are occupied. Note the three flags (98, 99, and 100) which are floating in space. I use these not as block occupancy flags but rather to display status information from the running script.
Below you can see the result of clicking on [Direct Edit]. This shows the spreadsheet that stores information about each flag. You can click on any cell and edit the values. 'X' or "Left" and 'Y' or "Top" are the coordinates of the flag - (0, 0) being the upper left corner of the bitmap. 'O' is the ordinal position of the flag name around the flag (Right=1, Below=2, Left=3, Above=4). Click on the button again and the spreadsheet will disappear. You can position the spreadsheet on your layout bitmap so that it does not obscure any layout features. Click anywhere in the spreadsheet and use the Ctrl-Arrow buttons to move it around. The program will remember your final position for next time.
Look at the Youngstown Block flag. You can manually occupy a block by just clicking on it. The flag will turn Yellow. A script will recognize this as an occupied block. If you click it again, it will return to Green.
Look at the McKees Rocks Block flag. The flag has been set as occupied by a running script and has turned red.


Functions that access the Flags

Note: Flags are manually created and positioned using the Layout window. The flag number referenced in these functions must already exist. In the box just below, I describe other functions that I wrote which incorporate these functions.

ShowLayout(); -- If the layout window is not visible, this function will make it visible. You must call
    -- this function if you are using the flags otherwise, the flags will not be functional.

SetFlag(FlagNo, value);
GetFlag(FlagNo);
TrySet
Flag(FlagNo, value);
TryMultiSetFlag(table, value); -- There are 100 flags which any Program Control window
    -- can set, clear or test a flag status. FlagNo is 1-100, value is any integer but generally
    -- should be either 0 or the Engine number which occupies the block.
    -- Use SetFlag() to store a value and GetFlag() to retrieve a value.
    -- TrySetFlag() attempts to set a value into a flag and will only succeed if the
    -- flag is zero or equal to value. This test and set is done atomically. If it succeeds
    -- it will return true, if it can't get the occupancy lock, it will return false.
    -- TryMultiSetFlag() attemps to set a value into several flags and will only succeed
    -- if all of the flags are zero or equal to value.  This test and set is done atomically.
    -- It returns two values. The first is true if it gets a lock on all of the flags, false
    -- if it cannot. The second value returned is the flag number of the flag which would
    -- not lock otherwise returns zero. table is a lua table, for example : {1, 8, 23}

SetFlagName(FlagNo, string);
GetFlagName(FlagNo);  -- There are 100 flags which any Program Control window
    -- can store or retrieve a string.
    -- FlagNo is 1-100, string is a string. Use SetFlagName() to store a string
    -- and GetFlagName() to retrieve a string.




Flag support functions in functions.lua

Note: I wrote these functions in lua to make using flags a little easier. These functions use the Flags to represent Block Occupancy. You can actually use the Flags for anything your script requires. Your lua script can incorporate these functions by adding "require([[functions]])". These functions use the lower level functions described above. Of course, you don't have to use these, you can write your own versions using the the above functions.

You must call ShowLayout() if you are using the flags otherwise, the flags will not be functional.

function SetOccupancy(block, value)
    -- Set a value into the block occupancy flag given by block
    -- Waits forever for the block to show as unoccupied before it sets occupancy

function GetOccupancy(block)
    -- Fetch a block occupany flag

function ClearOccupancy(block, value)
    -- Clear a block occupancy flag to zero if we have ownership, that is,
    -- we are occupying the block.

function TrySetOccupancy(block, value)
    -- Tries to set a value into the block occupancy flag
    -- Only tries once. Returns true on success, false if the block is occupied.

function OverrideOccupancy(block, value)
    -- Set a block occupancy flag without waiting for lock or checking ownership.
    -- Can be very dangerous if your script makes a mistake.

function MultiSetOccupancy(blocks, value, Wait)
    -- Set a value into several block occupancy flags. All blocks must be unoccupied.
    -- Waits almost forever for the blocks to show as unoccupied before it sets occupancy.
    -- returns two values
    -- 1: true on success, false on failure
    -- 2: on failure, one of the block numbers that would not lock, otherwise 0
    -- Wait is the number of seconds to wait, default to 240 (8 minutes)
    -- blocks is a table of the form {2, 44, 21}

function TryMultiSetOccupancy(blocks, value)
    -- Set a value into several block occupancy flags. All blocks must be unoccupied.
    -- Only tries once
    -- returns two values
    -- 1: true on success, false on failure
    -- 2: on failure, the block number that would not lock, otherwise 0
    -- blocks is a table of the form {2, 44, 21}

function SingleSetOccupancy(blocks, value, Wait)
    -- Set a value into one of several block occupancy flags. One block must be unoccupied.
    -- Waits almost forever for one of the blocks to show as unoccupied before it sets occupancy.
    -- returns two values
    -- 1: true on success, false on failure
    -- 2: on success, the block number that locked, otherwise 0
    -- Wait is the number of seconds to wait, default to 240 (8 minutes)
    -- blocks is a table of the form {2, 44, 21}


Here is an example of a script which uses flags. This script (Thinking Engine Westbound.lua) runs a train over my layout from McKees Rocks to Youngtown. It uses the flags as Block Occupancy indication. It checks, sets and clears block occupancy flags as it goes along. There is a companion script in the download (Thinking Engine Eastbound.lua) which runs a train from Youngstown to McKees Rocks. Both of the scripts can run simultaneously and will successfully avoid each other by taking sidings and stopping when necessary.

In this script, the flags denote not only occupied block but reserved blocks.  A train cannot enter a block occupied by another train AND it cannot enter block that another train is about to enter (that is, a reserved block).

Here is a listing of the Thinking Engines Westbound.lua  script
(this may not be as up to date as the zip file linked to below):

title = "Thinking Engine Westbound"
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 TagCount = 0;
-- Counter Labels
Counter01Label = "tags()";
Counter03Label = "Block Speed Limit";
local C3 = COUNTER03;
Counter04Label = "Target Distance";
local C4 = COUNTER04;
Counter05Label = "Stopping Distance";
local C5 = COUNTER05;
Counter06Label = "Stop Delay";
local C6 = COUNTER06;

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

local MyDirection = 0;
local StartBlockNo, EndBlockNo;
local CurrentBlockNo, LastBlockNo, NextBlockNo;
local StartedFlag = false;
local RFIDFlag = false;
local ResetinProgress = false;
local LLLoop = false;
local Layout4;
local MyTIUNo, MyEngineNo, Enum;
local SoundOff = false;
local EngineSoundSave, S1;
local BeepOn = false;
local MyEngineSpeed; -- Smph
MASTER_TIU = nil; -- TIU number used by Layout4 (global)
-- clear all tag handling functions
local Func30 = nil;
local Func51 = nil;
local Func10 = nil;
local Func41 = nil;
local Func21 = nil;
local Func20 = nil;
local Func40 = nil;
local Func50 = nil;
--[[---------------------------------------------------------------------------------------]]
function CalculateBlockStopDelay(StoppingBlockNo, EngineNo, direction, EngSpeed)
--
-- Calculate the stop delay based on parameters
-- Returns the stop delay in seconds and the target distance in inches
local Target_Station;
local Target_Distance;
local StopDelay;
local StoppingDistance
Target_Station = BlockName[StoppingBlockNo];
--print("Block = " .. BlockName[StoppingBlockNo] .. ", Direction = " .. direction .. ", Type = " .. FREIGHT);
--Target_Detector = Layout4.STOPBlock(StoppingBlockNo, direction, DETECTOR);
--Target_Reader = Layout4.STOPBlock(StoppingBlockNo, direction, READER);
if (EngineNo == 7 or EngineNo == 13) then
Target_Distance = Layout4.STOPBlock(StoppingBlockNo, direction, PASSENGER); --Passenger StoppingDistance;
else
Target_Distance = Layout4.STOPBlock(StoppingBlockNo, direction, FREIGHT); --Freight StoppingDistance;
end
if (Target_Distance == nil) then
Target_Distance = 0;
StopDelay = 0;
printc(clRed, "CalculateBlockStopDelay() : Target_Distance missing in Layout description");
return StopDelay, Target_Distance;
end
SetCounter(C4, Target_Distance);
--print(BlockName[StoppingBlockNo] .. " " .. direction .. ", Target Distance = " .. Target_Distance);
-- equation for Stop Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
StoppingDistance = Layout4.Stopping(EngineNo, EngSpeed);
StopDelay = (Target_Distance - StoppingDistance) * (48 / (12 * 5280 * EngSpeed)) * 3600;
-- if the StopDelay value comes out negative, that means we have to stop
-- very quickly. If that happens, set the Decell rate very high and then
-- set the speed to zero.
SetCounter(C5, StoppingDistance);
SetCounter(C6, StopDelay);
if (StopDelay < 0) then
StopDelay = 0;
printc(clRed, "CalculateBlockStopDelay() : Engine cannot stop in time (Stopping Distance > Target_Distance)");
return StopDelay, Target_Distance;
end;
print(string.format("%s : Target Distance = %d in - Stopping Distance = %.3f in - Speed %d Smph = Stop Delay %.3f seconds",
BlockName[StoppingBlockNo], Target_Distance, StoppingDistance, EngSpeed, StopDelay));
print("CalculateBlockStopDelay() : Set speed to 0 after " .. StopDelay .. " seconds")
return StopDelay, Target_Distance;
end;
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");

MASTER_TIU = TIU;
-- Instrumented Layout 4
Layout4 = require([[Layout4]]); -- All of the details about the Layout
-- an undirected weighted double vertex graph
MyTIUNo = MASTER_TIU;
MyEngineNo = 1;
Title(title, MyEngineNo);
Enum = tonumber(InputBox(title, "Enter Engine Number", MyEngineNo));
if (Enum ~= nil) then -- if the result is a number, then use it, otherwise, ignore it
MyEngineNo = Enum;
end
print("Engine Number = " .. MyEngineNo);
-- Show new title with Engine # and put Engine # into Engine Number Spinner
Title(title .. "-Engine # " .. MyEngineNo, MyEngineNo);
if (Layout4.StoppingisEmpty(MyEngineNo,1) == nil) then
return Stop("No StoppingDistance Table for Engine " .. MyEngineNo);
end

--print("StationName = " .. Pretty(StationName));
--print("StationName = " .. Pretty(BlockName));
--print("Switches = " .. Pretty(Switches));
--print("NextBlock = " .. Pretty(NextBlock));
--
SetFlagName(98, "Last Block");
SetFlagName(99, "Current Block");
SetFlagName(100, "Next Block");
--
-- start of run
---------------
-- Westbound train
MyDirection = WB;
StartBlockNo = MCKEES_ROCKS_YARD2_BLOCK; -- can start in Block MCKEES_ROCKS_YARD2_BLOCK or MCKEES_ROCKS_YARD1_BLOCK
CurrentBlockNo = StartBlockNo;
EndBlockNo = YOUNGSTOWN_YARD1_BLOCK; -- can end in Block YOUNGSTOWN_YARD2_BLOCK or YOUNGSTOWN_YARD1_BLOCK
MyEngineSpeed = BlockSpeed[MyDirection][StartBlockNo];
SetCounter(C3, MyEngineSpeed);
--[[---------------------]]
if (MyDirection == EB) then
print("Eastbound");
else
print("Westbound");
end

return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function createMainLine2Passing(flag, SwDistance)
local WaitforCaboose = flag;
local SwitchClearingDistance = SwDistance or 24;
local EngineStopped = false;
local StopDelay = 0;
local GotBlock = false;
local StopTime = 0;
local Target_Distance = 0;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local function MainLine2Passing(DetectorID, EngineNo, TagLocation, CarModel, Normal_Track, Reverse_Track, OperatingSwitch)
-- uses globals: MyEngineNo, LastBlockNo, CurrentBlockNo, NextBlockNo
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK) then -- if MyEngineNo in tag
print("MainLine2Passing : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
--print("MainLine2Passing : " .. BlockName[LastBlockNo] .. " > " .. BlockName[CurrentBlockNo] .. " > " .. BlockName[NextBlockNo]);
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;

-- Adjust speed for the current block
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];
SetSpeed(MyEngineSpeed, 0, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("%s : Speed %d Smph",
BlockName[CurrentBlockNo], MyEngineSpeed));

-- check for occupancy clear Reverse_Track or Normal_Track - one or the other must be unoccupied otherwise fail
-- choose based on superiority or choose empty block. Put choice into NextBlockNo.
-- this code checks one track then the other track, if deadlock, it waits.
if (MyDirection == SuperiorDirection) then
if (TrySetOccupancy(Normal_Track, MyEngineNo)) then
NextBlockNo = Normal_Track; -- Westbound trains are superior, take the through track if possible
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
else
-- check inferior direction track
if (TrySetOccupancy(Reverse_Track, MyEngineNo)) then
NextBlockNo = Reverse_Track;
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
else
NextBlockNo = Normal_Track;
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = CalculateBlockStopDelay(CurrentBlockNo, MyEngineNo, MyDirection, MyEngineSpeed)
-- Stop delay any positive value including 0
if (StopDelay == 0) then
Rate(0, 8, 0, MyEngineNo, MyTIUNo); -- stop as quickly as possible
end;
SetSpeed(0, StopDelay, MyEngineNo, MyTIUNo); -- stop engine
if (StopDelay == 0) then
Rate(0, 1, 0, MyEngineNo, MyTIUNo);
end;
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end
end;
else -- MyDirection ~= SuperiorDirection
if (TrySetOccupancy(Reverse_Track, MyEngineNo)) then
NextBlockNo = Reverse_Track; -- Eastbound trains are inferior, take the passing track if possible
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
else
-- check superior direction track
if (TrySetOccupancy(Normal_Track, MyEngineNo)) then
NextBlockNo = Normal_Track;
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
else
NextBlockNo = Reverse_Track;
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = CalculateBlockStopDelay(CurrentBlockNo, MyEngineNo, MyDirection, MyEngineSpeed)
-- Stop delay
if (StopDelay == 0) then
Rate(0, 8, 0, MyEngineNo, MyTIUNo); -- stop as quickly as possible
end;
SetSpeed(0, StopDelay, MyEngineNo, MyTIUNo); -- stop engine
if (StopDelay == 0) then
Rate(0, 1, 0, MyEngineNo, MyTIUNo);
end;
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
end;
end
if (GotBlock) then
DisplayBlockNames("MainLine2Passing : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
end;

-- engine was stopped quickly
--if (not GotBlock and StopDelay == 0) then
if (not GotBlock and Target_Distance <= 50) then
-- Caboose may not have passed over detector yet and it may occupy two blocks
-- wait for one block to be locked (default wait 8 minutes)
-- wait for either Normal_Track or Reverse_Track to lock
local result;
if (MyDirection == SuperiorDirection) then
result, NextBlockNo = SingleSetOccupancy({Normal_Track, Reverse_Track}, MyEngineNo, 240);
else
result, NextBlockNo = SingleSetOccupancy({Reverse_Track, Normal_Track}, MyEngineNo, 240);
end;
if (not result) then
return Stop("MainLine2Passing : Timed out waiting for " .. BlockName[Normal_Track] .. " or " .. BlockName[Reverse_Track]);
end;
DisplayBlockNames("MainLine2Passing : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
GotBlock = true; -- indicate Block is locked
if (EngineStopped) then
-- start moving if stopped
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 0;
else
StartDelay = StopDelay - UsedUpTime + 5; --UsedUpTime always <= StopDelay
end;
SetSpeed(MyEngineSpeed, StartDelay, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("MainLine2Passing : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[CurrentBlockNo], MyEngineSpeed, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
end;
--[[
if (not GotBlock and Target_Distance > 50) then
-- defer wait to Caboose detection code
end;
--]]
if (GotBlock) then
--print("MainLine2Passing : " .. DetectorID .. "Next Block Selection = " .. BlockName[NextBlockNo]);
-- set OperatingSwitch switch
if (NextBlockNo == Normal_Track) then
Switch(NORMAL, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("MainLine2Passing : Switch " .. SwitchName[OperatingSwitch] .. " to Normal");
end;
if (NextBlockNo == Reverse_Track) then
Switch(REVERSE, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("MainLine2Passing : Switch " .. SwitchName[OperatingSwitch] .. " to Reverse");
end;
end;
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
-- in the elseif block, use the local copy of CurrentBlockNo and LastBlockNo
print("MainLine2Passing : " .. DetectorID .. "Caboose detected");
if (SwitchClearingDistance > 0) then
-- equation for Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
local SleepDelay = (SwitchClearingDistance) * (48 / (12 * 5280 * MyEngineSpeed)) * 3600;
print(string.format("MainLine2Passing : %s : Switch Clearing Distance = %.3f in - Speed %d Smph = Sleep Delay %.3f seconds",
BlockName[MyCurrentBlockNo], SwitchClearingDistance, MyEngineSpeed, SleepDelay));
print("MainLine2Passing : " .. "Switch cleared after " .. SleepDelay .. " seconds")
Sleep(SleepDelay); -- give the caboose time to clear the switch
end;
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (not GotBlock) then
-- wait for all blocks to be locked (default wait 8 minutes)
-- wait for NextBlockNo to be locked (either the thru or passing track)
local result;
-- set occupancy flag in NextBlockNo
-- TODO understand what it means to change NextBlockNo in the elseif Caboose detection code
result, NextBlockNo = SingleSetOccupancy({Normal_Track, Reverse_Track}, MyEngineNo, 240);
if (not result) then
return Stop("MainLine2Passing : Timed out waiting for " .. BlockName[Normal_Track] .. " or " .. BlockName[Reverse_Track]);
end;
DisplayBlockNames("MainLine2Passing : ", MyLastBlockNo, MyCurrentBlockNo, NextBlockNo);
GotBlock = true; -- indicate Block is locked
-- set OperatingSwitch switch
if (NextBlockNo == Normal_Track) then
Switch(NORMAL, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("MainLine2Passing : Switch " .. SwitchName[OperatingSwitch] .. " to Normal");
end;
if (NextBlockNo == Reverse_Track) then
Switch(REVERSE, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("MainLine2Passing : Switch " .. SwitchName[OperatingSwitch] .. " to Reverse");
end;
end;
if (EngineStopped) then
-- start moving if stopped
MyEngineSpeed = BlockSpeed[MyDirection][MyCurrentBlockNo];
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 0;
else
StartDelay = StopDelay - UsedUpTime + 5; --UsedUpTime always <= StopDelay
end;
SetSpeed(MyEngineSpeed, StartDelay, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
EngineStopped = false; -- indicate that we handled the stop here
print(string.format("MainLine2Passing : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[MyCurrentBlockNo], MyEngineSpeed, StartDelay));
WaitforCaboose = false;
end;
--print("MainLine2Passing : " .. DetectorID .. " " .. BlockName[MyLastBlockNo] .. " > " .. BlockName[MyCurrentBlockNo] .. " > " .. BlockName[MyNextBlockNo]);
end;
return true;
end;

return MainLine2Passing;
end;
--[[---------------------------------------------------------------------------------------]]
function createMainLine2MainLine(flag, SwDistance)
local WaitforCaboose = flag;
-- on any MainLine exit readers, there are no switches so
-- there SwitchCLearingDistance is zero
local SwitchClearingDistance = SwDistance or 0;
local EngineStopped = false;
local StopDelay = 0
local GotBlock = false;
local StopTime = 0;
local Target_Distance = 0;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local function MainLine2MainLine(DetectorID, EngineNo, TagLocation, CarModel, Normal_Track, Reverse_Track, OperatingSwitch, Block)
-- uses globals: MyEngineNo, LastBlockNo, CurrentBlockNo, NextBlockNo
if (type(Block) ~= "table") then return Stop("MainLine2MainLine Block not a table"); end
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK) then -- if MyEngineNo in tag
print("MainLine2MainLine : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
--print("MainLine2MainLine : " .. BlockName[LastBlockNo] .. " > " .. BlockName[CurrentBlockNo] .. " > " .. BlockName[NextBlockNo]);
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;

-- Adjust speed for the current block
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];
SetSpeed(MyEngineSpeed, 0, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("%s : Speed %d Smph",
BlockName[CurrentBlockNo], MyEngineSpeed));

NextBlockNo = Block[1]; -- set NextBlockNo to Block[1]
DisplayBlockNames("MainLine2MainLine : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
-- moving into a block requires the block to be empty (== 0) or
-- be already reserved for this engine (== EnginNo)
if (not TryMultiSetOccupancy(Block, MyEngineNo)) then
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = CalculateBlockStopDelay(CurrentBlockNo, MyEngineNo, MyDirection, MyEngineSpeed)
-- Stop delay may be zero for immediate stop
if (StopDelay == 0) then
Rate(0, 8, 0, MyEngineNo, MyTIUNo); -- stop as quickly as possible
end;
SetSpeed(0, StopDelay, MyEngineNo, MyTIUNo); -- stop engine
if (StopDelay == 0) then
Rate(0, 1, 0, MyEngineNo, MyTIUNo);
end;
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
else
-- got a lock on the needed block(s)
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
end
--if (not GotBlock and StopDelay == 0) then
if (not GotBlock and Target_Distance <= 50) then -- Engine was stopped
-- Caboose may not have passed over detector yet and it may occupy two blocks
-- wait for all blocks to be locked (default wait 8 minutes)
if (not MultiSetOccupancy(block, MyEngineNo)) then -- set occupancy flag in all blocks
return Stop("MainLine2MainLine : Timed out waiting for " .. BlockName[Block[1]]);
end
GotBlock = true; -- indicate Block is locked
-- start moving if stopped or adjust speed
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];

if (EngineStopped) then
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 0;
else
StartDelay = StopDelay - UsedUpTime + 5; --UsedUpTime always <= StopDelay
end;
SetSpeed(MyEngineSpeed, StartDelay, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("MainLine2MainLine : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[CurrentBlockNo], MyEngineSpeed, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
end;
--[[
if (not GotBlock and Target_Distance > 50) then
-- defer wait to Caboose detection code
end;
--]]
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
print("MainLine2MainLine : " .. DetectorID .. "Caboose detected");
-- on any MainLine exit readers, there is no switch so
-- the SwitchClearingDistance is zero
--[[
if (not EngineStopped and (SwitchClearingDistance > 0)) then
-- equation for Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
local SleepDelay = (SwitchClearingDistance) * (48 / (12 * 5280 * MyEngineSpeed)) * 3600;
print(string.format("MainLine2MainLine : %s : Switch Clearing Distance = %.3f in - Speed %d Smph = Sleep Delay %.3f seconds",
BlockName[MyMyCurrentBlockNo], SwitchClearingDistance, MyEngineSpeed, SleepDelay));
print("MainLine2MainLine : " .. "Switch cleared after " .. SleepDelay .. " seconds")
Sleep(SleepDelay); -- give the caboose time to clear the switch
end;
--]]
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (not GotBlock) then
-- wait for all blocks to be locked (default wait 8 minutes)
if (not MultiSetOccupancy(block, MyEngineNo)) then -- set occupancy flag in all blocks
return Stop("MainLine2MainLine : Timed out waiting for " .. BlockName[Block[1]]);
end;
end;
if (EngineStopped) then
-- Start moving if stopped
MyEngineSpeed = BlockSpeed[MyDirection][MyCurrentBlockNo];
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 0;
else
StartDelay = StopDelay - UsedUpTime + 5; --UsedUpTime always <= StopDelay
end;
SetSpeed(MyEngineSpeed, StartDelay, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("MainLine2MainLine : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[MyCurrentBlockNo], MyEngineSpeed, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
WaitforCaboose = false;
--print("MainLine2MainLine : " .. DetectorID .. " " .. BlockName[MyLastBlockNo] .. " > " .. BlockName[MyCurrentBlockNo] .. " > " .. BlockName[MyNextBlockNo]);
end;
return true;
end;

return MainLine2MainLine;
end;
--[[---------------------------------------------------------------------------------------]]
function createPassing2MainLine(flag, SwDistance)
local WaitforCaboose = flag;
-- on any MainLine exit readers, there is no switch so
-- there SwitchClearingDistance is zero
local SwitchClearingDistance = SwDistance or 0;
local EngineStopped = false;
local StopDelay = 0;
local GotBlock = false;
local StopTime = 0;
local Target_Distance = 0;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local function Passing2MainLine(DetectorID, EngineNo, TagLocation, CarModel, Normal_Track, Reverse_Track, OperatingSwitch, Block)
-- uses globals: MyEngineNo, LastBlockNo, CurrentBlockNo, NextBlockNo
if (type(Block) ~= "table") then return Stop("Passing2MainLine Block not a table"); end
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK) then -- if MyEngineNo in tag
print("Passing2MainLine : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
--print("Passing2MainLine : " .. BlockName[LastBlockNo] .. " > " .. BlockName[CurrentBlockNo] .. " > " .. BlockName[NextBlockNo]);
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;

-- Adjust speed for the current block
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];
SetSpeed(MyEngineSpeed, 0, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("%s : Speed %d Smph",
BlockName[CurrentBlockNo], MyEngineSpeed));

NextBlockNo = Block[1]; -- set NextBlockNo to Block[1]
DisplayBlockNames("Passing2MainLine : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
--moving into a block requires the block to be empty (== 0) or
-- be already reserved for this engine (== MyEngineNo)
if (not TryMultiSetOccupancy(Block, MyEngineNo)) then
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = CalculateBlockStopDelay(CurrentBlockNo, MyEngineNo, MyDirection, MyEngineSpeed)
-- Stop delay
if (StopDelay == 0) then
Rate(0, 8, 0, MyEngineNo, MyTIUNo); -- stop as quickly as possible
end;
SetSpeed(0, StopDelay, MyEngineNo, MyTIUNo);
if (StopDelay == 0) then
Rate(0, 1, 0, MyEngineNo, MyTIUNo);
end;
EngineStopped = true; -- engine was stopped
GotBlock = false;
StopTime = RunTime();
else
-- got lock, continue on
StopDelay = 0;
EngineStopped = false; -- engine was not stopped
GotBlock = true;
StopTime = 0;
end

--if (not GotBlock and StopDelay == 0) then
if (not GotBlock and Target_Distance <= 50) then
-- Engine was stopped
-- Caboose may not have passed over detector yet
-- it may occupy two blocks
-- wait for all blocks to be locked (default wait 8 minutes)
if (not MultiSetOccupancy(Block, MyEngineNo)) then -- set occupancy flag in all blocks
return Stop("Passing2MainLine : Timed out waiting for " .. BlockName[Block[1]]);
end
GotBlock = true; -- indicate Block is locked
if (EngineStopped) then
-- start moving if stopped
-- Cabooose may have not cleared the tag detector
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 0;
else
StartDelay = StopDelay - UsedUpTime + 5; --UsedUpTime always <= StopDelay
end;
SetSpeed(MyEngineSpeed, StartDelay, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("Passing2MainLine : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[CurrentBlockNo], MyEngineSpeed, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
end;
--[[
if (not GotBlock and Target_Distance > 50) then
-- defer wait to Caboose detection code
end;
--]]
if (GotBlock) then
-- set OperatingSwitch switch
if (CurrentBlockNo == Normal_Track) then
Switch(NORMAL, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("Passing2MainLine : Switch " .. SwitchName[OperatingSwitch] .. " to Normal");
end;
if (CurrentBlockNo == Reverse_Track) then
Switch(REVERSE, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("Passing2MainLine : Switch " .. SwitchName[OperatingSwitch] .. " to Reverse");
end;
end;

WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
print("Passing2MainLine : " .. DetectorID .. "Caboose detected");
if (SwitchClearingDistance > 0) then
-- equation for Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
local SleepDelay = (SwitchClearingDistance) * (48 / (12 * 5280 * MyEngineSpeed)) * 3600;
print(string.format("Passing2MainLine : %s : Switch Clearing Distance = %.3f in - Speed %d Smph = Sleep Delay %.3f seconds",
BlockName[MyCurrentBlockNo], SwitchClearingDistance, MyEngineSpeed, SleepDelay));
--print("Passing2MainLine : " .. "Switch cleared after " .. SleepDelay .. " seconds")
Sleep(SleepDelay); -- give the caboose time to clear the switch
end;
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (not GotBlock) then
-- wait for all blocks to be locked (default wait 8 minutes)
if (not MultiSetOccupancy(Block, MyEngineNo)) then -- set occupancy flag in all blocks
return Stop("Passing2MainLine : Timed out waiting for " .. BlockName[Block[1]]);
end;
GotBlock = true; -- indicate Block is locked
-- set OperatingSwitch switch
if (MyCurrentBlockNo == Normal_Track) then
Switch(NORMAL, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("Passing2MainLine : Switch " .. SwitchName[OperatingSwitch] .. " to Normal");
end;
if (MyCurrentBlockNo == Reverse_Track) then
Switch(REVERSE, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("Passing2MainLine : Switch " .. SwitchName[OperatingSwitch] .. " to Reverse");
end;
end;
if (EngineStopped) then
-- start moving if stopped
MyEngineSpeed = BlockSpeed[MyDirection][MyCurrentBlockNo];
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 0;
else
StartDelay = StopDelay - UsedUpTime + 5; --UsedUpTime always <= StopDelay
end;
SetSpeed(MyEngineSpeed, StartDelay, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("Passing2MainLine : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[MyCurrentBlockNo], MyEngineSpeed, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
WaitforCaboose = false;
--print("Passing2MainLine : " .. DetectorID .. " " .. BlockName[MyLastBlockNo] .. " > " .. BlockName[MyCurrentBlockNo] .. " > " .. BlockName[MyNextBlockNo]);
end;
return true;
end;

return Passing2MainLine;
end;
--[[---------------------------------------------------------------------------------------]]
function createMainLine2Yard(flag, SwDistance)
local WaitforCaboose = flag;
local SwitchClearingDistance = SwDistance or 24;
local EngineStopped = false;
local StopDelay = 0;
local GotBlock = false;
local StopTime = 0;
local Target_Distance;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local function MainLine2Yard(DetectorID, EngineNo, TagLocation, CarModel, Yard1, Yard2, OperatingSwitch, Block)
if (type(Block) ~= "table") then return Stop("MainLine2Yard Block not a table"); end
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK) then -- if MyEngineNo in tag
print("MainLine2Yard : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
--print("MainLine2Yard : " .. BlockName[LastBlockNo] .. " > " .. BlockName[CurrentBlockNo] .. " > " .. BlockName[NextBlockNo]);
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;

-- Adjust speed for the current block
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];
SetSpeed(MyEngineSpeed, 0, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("%s : Speed %d Smph",
BlockName[CurrentBlockNo], MyEngineSpeed));

NextBlockNo = Block[1]; -- ending block number
-- check for occupancy clear Yard2 or Yard1 - Ending block must be unoccupied otherwise stop engine
if (TrySetOccupancy(NextBlockNo, MyEngineNo)) then
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
else
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = CalculateBlockStopDelay(CurrentBlockNo, MyEngineNo, MyDirection, MyEngineSpeed)
-- Stop delay
if (StopDelay == 0) then
Rate(0, 8, 0, MyEngineNo, MyTIUNo); -- stop as quickly as possible
end;
SetSpeed(0, StopDelay, MyEngineNo, MyTIUNo); -- stop engine
if (StopDelay == 0) then
Rate(0, 1, 0, MyEngineNo, MyTIUNo);
end;
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end
if (GotBlock) then
DisplayBlockNames("MainLine2Yard : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
end;

-- engine was stopped quickly
--if (not GotBlock and StopDelay == 0) then
if (not GotBlock and Target_Distance <= 50) then
-- Caboose may not have passed over detector yet and it may occupy two blocks
-- wait for all blocks to be locked (default wait 8 minutes)
-- wait for either Normal_Track or Reverse_Track to clear
SetOccupancy(NextBlockNo, MyEngineNo); -- set occupancy flag in NextBlockNo
DisplayBlockNames("MainLine2Yard : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
GotBlock = true; -- indicate Block is locked
if (EngineStopped) then
-- start moving if stopped
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 0;
else
StartDelay = StopDelay - UsedUpTime + 5; --UsedUpTime always <= StopDelay
end;
SetSpeed(MyEngineSpeed, StartDelay, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
print(string.format("MainLine2Yard : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[CurrentBlockNo], MyEngineSpeed, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
end;

--[[
if (not GotBlock and Target_Distance > 50) then
-- defer wait to Caboose detection code
end;
--]]
if (GotBlock) then
-- set OperatingSwitch switch
if (CurrentBlockNo == Yard1) then
Switch(NORMAL, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("MainLine2Yard : Switch " .. SwitchName[OperatingSwitch] .. " to Normal");
end;
if (CurrentBlockNo == Yard2) then
Switch(REVERSE, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("MainLine2Yard : Switch " .. SwitchName[OperatingSwitch] .. " to Reverse");
end;
end;
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
print("MainLine2Yard : " .. DetectorID .. "Caboose detected");
--print("MainLine2Yard : " .. DetectorID .. "Caboose - Last Block = " .. BlockName[MyLastBlockNo]);
if (SwitchClearingDistance > 0) then
-- equation for Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
local SleepDelay = (SwitchClearingDistance) * (48 / (12 * 5280 * MyEngineSpeed)) * 3600;
print(string.format("MainLine2Yard : %s : Switch Clearing Distance = %.3f in - Speed %d Smph = Sleep Delay %.3f seconds",
BlockName[MyCurrentBlockNo], SwitchClearingDistance, MyEngineSpeed, SleepDelay));
print("MainLine2Yard : " .. "Switch cleared after " .. SleepDelay .. " seconds")
Sleep(SleepDelay); -- give the caboose time to clear the switch
end;
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (not GotBlock) then
-- wait for NextBlockNo to be locked (either Yard track)
local result;
-- set occupancy flag in NextBlockNo
SetOccupancy(NextBlockNo, MyEngineNo); -- set occupancy flag in NextBlockNo
DisplayBlockNames("MainLine2Yard : ", MyLastBlockNo, MyCurrentBlockNo, NextBlockNo);
GotBlock = true; -- indicate Block is locked
-- set OperatingSwitch switch
-- TODO understand what it means to change NextBlockNo in the elseif Caboose detection code
if (NextBlockNo == Yard1) then
Switch(NORMAL, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("MainLine2Yard : Switch " .. SwitchName[OperatingSwitch] .. " to Normal");
end;
if (NextBlockNo == Yard2) then
Switch(REVERSE, Switches[OperatingSwitch][CHAN_NUMBER], Switches[OperatingSwitch][AIU_NUMBER], Switches[OperatingSwitch][TIU_NUMBER], 0);
print("MainLine2Yard : Switch " .. SwitchName[OperatingSwitch] .. " to Reverse");
end;
end;
if (EngineStopped) then
-- start moving if stopped
MyEngineSpeed = BlockSpeed[MyDirection][MyCurrentBlockNo];
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 0;
else
StartDelay = StopDelay - UsedUpTime + 5; --UsedUpTime always <= StopDelay
end;
SetSpeed(MyEngineSpeed, StartDelay, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
EngineStopped = false; -- indicate that we handled the stop here
print(string.format("MainLine2Yard : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[MyCurrentBlockNo], MyEngineSpeed, StartDelay));
end;
WaitforCaboose = false;
--print("MainLine2Yard : " .. DetectorID .. " " .. BlockName[MyLastBlockNo] .. " > " .. BlockName[MyCurrentBlockNo] .. " > " .. BlockName[MyNextBlockNo]);
end;
return true;
end

return MainLine2Yard;
end;
--[[---------------------------------------------------------------------------------------]]
function createStopInYard(flag, SwDistance)
local WaitforCaboose = flag;
local SwitchClearingDistance = SwDistance or 24;
local EngineStopped = false;
local StopDelay = 0;
local StopTime = 0;
local Target_Distance;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local function StopInYard(DetectorID, EngineNo, TagLocation, CarModel, OffLayoutBlock)
-- uses globals: MyEngineNo, LastBlockNo, CurrentBlockNo, NextBlockNo
if (type(OffLayoutBlock) ~= "table") then return Stop("StopInYard OffLayoutBlock not a table"); end
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK) then -- if MyEngineNo in tag
print("StopInYard : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
--print("StopInYard : " .. BlockName[LastBlockNo] .. " > " .. BlockName[CurrentBlockNo] .. " > " .. BlockName[NextBlockNo]);
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
NextBlockNo = OffLayoutBlock[1];
DisplayBlockNames("StopInYard : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
-- schedule stop in CurrentBlockNo
StopDelay, Target_Distance = CalculateBlockStopDelay(CurrentBlockNo, MyEngineNo, MyDirection, MyEngineSpeed)
-- Stop delay
if (StopDelay == 0) then
Rate(0, 8, 0, MyEngineNo, MyTIUNo); -- stop as quickly as possible
end;
SetSpeed(0, StopDelay, MyEngineNo, MyTIUNo); -- stop engine
if (StopDelay == 0) then
Rate(0, 1, 0, MyEngineNo, MyTIUNo);
end;
EngineStopped = true;
StopTime = RunTime();
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
print("StopInYard : " .. DetectorID .. "Caboose detected");
if (SwitchClearingDistance > 0) then
-- equation for Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
local SleepDelay = (SwitchClearingDistance) * (48 / (12 * 5280 * MyEngineSpeed)) * 3600;
print(string.format("StopInYard : %s : Switch Clearing Distance = %.3f in - Speed %d Smph = Sleep Delay %.3f seconds",
BlockName[MyCurrentBlockNo], SwitchClearingDistance, MyEngineSpeed, SleepDelay));
print("StopInYard : " .. "Switch cleared after " .. SleepDelay .. " seconds")
Sleep(SleepDelay); -- give the caboose time to clear the switch
end;
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (EngineStopped) then
print("StopInYard : " .. DetectorID .. "Stopped!");
RFIDFlag = false;
end
WaitforCaboose = false;
--print("StopInYard : " .. DetectorID .. " " .. BlockName[MyLastBlockNo] .. " > " .. BlockName[MyCurrentBlockNo] .. " > " .. BlockName[MyNextBlockNo]);
end;
return true;
end;

return StopInYard;
end;
--[[---------------------------------------------------------------------------------------]]
function DisplayBlockNames(Title, LastBlockNo, CurrentBlockNo, NextBlockNo)
SetFlagName(98, BlockName[LastBlockNo]);
SetFlagName(99, BlockName[CurrentBlockNo]);
SetFlagName(100, BlockName[NextBlockNo]);
print(Title .. BlockName[LastBlockNo] .. " > " .. BlockName[CurrentBlockNo] .. " > " .. BlockName[NextBlockNo]);
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;

--SetFlagName(98, string.format([[Tag Detector %d Reader %d]], Detector, Reader));

-- 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)));
-- follow on to process the detected engine
else -- it's a car
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));
-- follow on to process non-engine tags
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
--[[--------------------------]]
-- Westbound trains are superior
-- if MyEngineNo is not in tag and Caboose number is not in tag, then ignore it

if (ResetinProgress) then
-- run back to Tag 3-0
if (Detector == 3 and Reader == 0) then
local DetectorID = "@ 3-0 ";
local StopDelay = 0;
if (EngineNo == 0 and CarModel == CABOOSE) then -- going in reverse, execute on caboose detection
print(DetectorID .. "Caboose detected");
-- schedule stop in StartBlockNo
StopDelay = CalculateBlockStopDelay(StartBlockNo, MyEngineNo, EB, MyEngineSpeed)
SetSpeed(0, StopDelay, MyEngineNo, MyTIUNo); -- Stop delay
SetDirection(FORWARD, StopDelay + 5, MyEngineNo, MyTIUNo);
ResetinProgress = false;
end;
end;
return true;

elseif (not RFIDFlag) then -- ignore tags unless engine is moving
return true;

-- Tag 3-0
elseif (Detector == 3 and Reader == 0) then
if (Func30 == nil) then
Func30 = createMainLine2Passing(false, SwitchClearingInches[30][MyDirection]);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func30("@ 3-0 ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, ALIQUIPPA_EAST);

-- Tag 5-1
elseif (Detector == 5 and Reader == 1) then
if (Func51 == nil) then
Func51 = createPassing2MainLine(false, SwitchClearingInches[51][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set
Func51("@ 5-1 ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, ALIQUIPPA_WEST, {ALIQUIPPA_BLOCK});

-- Tag 1-0
elseif (Detector == 1 and Reader == 0) then
if (Func10 == nil) then
Func10 = createMainLine2Passing(false, SwitchClearingInches[10][MyDirection]);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func10("@ 1-0 ", EngineNo, TagLocation, CarModel, COLLEGE_THRU_TRACK, COLLEGE_PASSING_TRACK, COLLEGE_EAST);

-- Tag 4-1
elseif (Detector == 4 and Reader == 1) then
if (Func41 == nil) then
Func41 = createPassing2MainLine(false, SwitchClearingInches[41][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set
Func41("@ 4-1 ", EngineNo, TagLocation, CarModel, COLLEGE_THRU_TRACK, COLLEGE_PASSING_TRACK, COLLEGE_WEST, {NEW_CASTLE_BLOCK});

-- Tag 2-1
elseif (Detector == 2 and Reader == 1) then
if (Func21 == nil) then
Func21 = createMainLine2Passing(false, SwitchClearingInches[21][MyDirection]);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func21("@ 2-1 ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_PASSING_TRACK, STRUTHERS_EAST);

-- Tag 2-0
elseif (Detector == 2 and Reader == 0) then
if (LLLoop == false) then
if (Func20 == nil) then
Func20 = createPassing2MainLine(false, SwitchClearingInches[20][MyDirection]);
end;
Func20("@ 2-0 ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_PASSING_TRACK, STRUTHERS_WEST, {YOUNGSTOWN_BLOCK});
else
if (Func20 == nil) then
Func20 = createPassing2MainLine(false, SwitchClearingInches[20][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set
Func20("@ 2-0 ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_PASSING_TRACK, STRUTHERS_WEST, {YOUNGSTOWN_BLOCK, LIONEL_IVES_BLOCK, MCKEES_ROCKS_BLOCK});
end;

-- Tag 4-0
elseif (Detector == 4 and Reader == 0) then
-- status: just entered NextBlockNo, occupancy already set
if (LLLoop == false) then
if (Func40 == nil) then
Func40 = createMainLine2Yard(false, SwitchClearingInches[40][MyDirection]);
end;
Func40("@ 4-0 ", EngineNo, TagLocation, CarModel, YOUNGSTOWN_YARD1_BLOCK, YOUNGSTOWN_YARD2_BLOCK, YOUNGSTOWN_YARD, {EndBlockNo});
else
if (Func40 == nil) then
Func40 = createMainLine2MainLine(false, 0);
end;
-- engine never stops in this block except for deadlock
Func40("@ 4-0 ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, ALIQUIPPA_EAST, {LIONEL_IVES_BLOCK, MCKEES_ROCKS_BLOCK});
end;

-- Tag 5-0
elseif (Detector == 5 and Reader == 0) then
-- status: in block NextBlockNo, occupancy already set
if (LLLoop == false) then
if (Func50 == nil) then
Func50 = createStopInYard(false, SwitchClearingInches[50][MyDirection]); -- clear yard switch
end;
Func50("@ 5-0 ", EngineNo, TagLocation, CarModel, {OFF_LAYOUT_WEST});
else
if (Func50 == nil) then
Func50 = createMainLine2MainLine(false, 0); -- no switches if staying on Mainline
end;
-- engine never stops in this block except for deadlock
Func50("@ 5-0 ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, ALIQUIPPA_EAST, {MCKEES_ROCKS_BLOCK});
end;
end;

return true; -- true=continue to process tags, false=stop processing tags
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button or the Stop() function is called
print("All Newly Detected Tags:");
local k, v;
for k,v in pairs(Tags) do -- dump out the Tags table
-- . . . . . . . . . . . . . . . .
-- print format: 6619044A 88080101030108011B02202031353536
print(k .. " " .. v);
end
SetSpeed(0, 0, MyEngineNo, MyTIUNo);
Rate(1, 1, 2, MyEngineNo, MyTIUNo);
print("Shutdown in Progress.....");
--
SetFlagName(98, "Last Block");
SetFlagName(99, "Current Block");
SetFlagName(100, "Next Block");
--
if (StartedFlag) then
print("Shutting down the Engine");
Sleep(10);
EngineShutdown(MyEngineNo, MyTIUNo);
StartedFlag = false;
end
RFIDFlag = false;
ResetinProgress = false;
print(string.format("cleanup() Complete at %.2f", RunTime()));
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function DTOButton(Eng, TIU)
print(string.format("Engine %d Trip Odometer %.3f Smiles", Eng, DTO(Eng, MyTIUNo)));
return true;
end
Function01Name, Function01Label = DTOButton, "Show DTO";
--[[---------------------------------------------------------------------------------------]]
function DODButton(Eng, TIU)
print(string.format("Engine %d Odometer %.3f Smiles", Eng, DOD(Eng, MyTIUNo)));
return true;
end
Function02Name, Function02Label = DODButton, "Show DOD";
--[[---------------------------------------------------------------------------------------]]
function SoundOffButton(Eng, TIU)
print("Sound Off");
S1 = GetVolume(MASTER_VOLUME, MyEngineNo, MyTIUNo); -- get Volume
print(" Sound Off - Engine " .. MyEngineNo .. " Level was " .. S1);
EngineSoundSave = S1;
SoundOff = true;
SetVolume(MASTER_VOLUME, 0, 0, MyEngineNo, MyTIUNo);
return true;
end
Function19Name, Function19Label = SoundOffButton, "Sound Off";
--[[---------------------------------------------------------------------------------------]]
function SoundOnButton(Eng, TIU)
print("Sound On");
if (SoundOff) then
S1 = EngineSoundSave;
print(" Sound On - Engine " .. MyEngineNo .. " Level restored to " .. S1);
SoundOff = false;
SetVolume(MASTER_VOLUME, S1, 0, MyEngineNo, MyTIUNo);
end;
return true;
end
Function20Name, Function20Label = SoundOnButton, "Sound On";
--[[---------------------------------------------------------------------------------------]]
function InitButton(Eng, TIU)
print("Initialize");
print("Set all Switches to Normal postion");
local k, v;
-- set all switches to NORMAL position
for k,v in pairs(Switches) do
print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
Switch(NORMAL, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
Sleep(0.5);
end
if (Debug() > 3) then
-- set all switches to REVERSE
for k,v in pairs(Switches) do
print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
Switch(REVERSE, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
Sleep(0.5);
end;
-- set all switches to NORMAL
for k,v in pairs(Switches) do
print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
Switch(NORMAL, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
Sleep(0.5);
end;
end;
-- clear occupancy flags
local x;
for x = FIRST_BLOCK, LAST_BLOCK do
OverrideOccupancy(x, 0);
end;
--[[-----------------------------]]
-- clear all tag handling functions
local Func30 = nil;
local Func51 = nil;
local Func10 = nil;
local Func41 = nil;
local Func21 = nil;
local Func20 = nil;
local Func40 = nil;
local Func50 = nil;

print("Initialization is Complete");
return true;
end
Function06Name, Function06Label = InitButton, "Initialize Layout";
--[[---------------------------------------------------------------------------------------]]
function StartButton(Eng, TIU)
print("Start the Run");

--[[-----------------------------]]
-- clear all tag handling functions
local Func30 = nil;
local Func51 = nil;
local Func10 = nil;
local Func41 = nil;
local Func21 = nil;
local Func20 = nil;
local Func40 = nil;
local Func50 = nil;

--[[------------------------------------------]]
LastBlockNo = OFF_LAYOUT_EAST;
CurrentBlockNo = StartBlockNo;
OverrideOccupancy(StartBlockNo, MyEngineNo); -- force set occupancy flag
-- check that MCKEES_ROCKS_BLOCK is not occupied
NextBlockNo = MCKEES_ROCKS_BLOCK;
SetOccupancy(NextBlockNo, MyEngineNo); -- occupied block
DisplayBlockNames("MainLine2Passing : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
--print("StationName = " .. Pretty(StationName));
--print("StationName = " .. Pretty(BlockName));
--print("Switches = " .. Pretty(Switches));
--print("NextBlock = " .. Pretty(NextBlock));

-- start the engine
Rate(1, 1, 0, MyEngineNo, MyTIUNo);
print("setup : Set accell/decell rates to 1");
if (StartedFlag == false) then
EngineStartUp(MyEngineNo, MyTIUNo);
print("Starting up the Engine");
StartedFlag = true;
Sleep(20);
end
SetDirection(FORWARD, 0, MyEngineNo, MyTIUNo);
RFIDFlag = true;
Whistle(ON, 20, MyEngineNo, MyTIUNo);
Whistle(OFF, 22, MyEngineNo, MyTIUNo);
print("Trip Odometer " .. DTO(MyEngineNo, MyTIUNo));
Sleep(3);

-- position MCKEES_ROCKS_YARD switch as needed
-- depending on value of CurrentBlockNo
if (CurrentBlockNo == MCKEES_ROCKS_YARD1_BLOCK) then
Switch(NORMAL, Switches[MCKEES_ROCKS_YARD][CHAN_NUMBER], Switches[MCKEES_ROCKS_YARD][AIU_NUMBER], Switches[MCKEES_ROCKS_YARD][TIU_NUMBER], 0);
end;
if (CurrentBlockNo == MCKEES_ROCKS_YARD2_BLOCK) then
Switch(REVERSE, Switches[MCKEES_ROCKS_YARD][CHAN_NUMBER], Switches[MCKEES_ROCKS_YARD][AIU_NUMBER], Switches[MCKEES_ROCKS_YARD][TIU_NUMBER], 0);
end;
--
SetSpeed(MyEngineSpeed, 5, MyEngineNo, MyTIUNo);

--[[----------------------------------------------------]]
print(string.format("RunTime = %.2f seconds", RunTime()));
-- TODO once both engines exit the yard, throw the Lionel-Ives Switch
return true;
end
Function07Name, Function07Label = StartButton, "Start Run";
--[[---------------------------------------------------------------------------------------]]

function event(FromPC, Type, P1, P2, P3)
print("event() Running - From PC# ", FromPC, " EventType = ", Type, " Parameters = ", P1, " ", P2, " ", P3);
return true; -- false=event failed, true=event succeeded
end
--[[---------------------------------------------------------------------------------------]]

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] .. " = ", flag)
end;
return true;
end
Function08Name, Function08Label = OccButton, "Occupancy Flags";
--[[---------------------------------------------------------------------------------------]]
--[[---------------------------------------------------------------------------------------]]
-- Run the engine back to the start (in reverse)
function ResetButton()
if (ResetinProgress) then
return; -- already being reset
end;
print("Return to Initial Position");
-- stop the engine
SetSpeed(0, 0, MyEngineNo, MyTIUNo);
RFIDFlag = false;
Sleep(5);
--[[-----------------------------]]
-- clear all tag handling functions
local Func30 = nil;
local Func51 = nil;
local Func10 = nil;
local Func41 = nil;
local Func21 = nil;
local Func20 = nil;
local Func40 = nil;
local Func50 = nil;

-- position MCKEES_ROCKS_YARD switch as needed
-- depending on value of StartBlockNo
if (StartBlockNo == MCKEES_ROCKS_YARD1_BLOCK) then
Switch(NORMAL, Switches[MCKEES_ROCKS_YARD][CHAN_NUMBER], Switches[MCKEES_ROCKS_YARD][AIU_NUMBER], Switches[MCKEES_ROCKS_YARD][TIU_NUMBER], 0);
end;
if (StartBlockNo == MCKEES_ROCKS_YARD2_BLOCK) then
Switch(REVERSE, Switches[MCKEES_ROCKS_YARD][CHAN_NUMBER], Switches[MCKEES_ROCKS_YARD][AIU_NUMBER], Switches[MCKEES_ROCKS_YARD][TIU_NUMBER], 0);
end;
--
-- start the engine
if (not StartedFlag) then
print("Starting up the Engine");
EngineStartUp(MyEngineNo, MyTIUNo);
StartedFlag = true;
Sleep(20);
end
-- direction to reverse
SetDirection(REVERSE, 0, MyEngineNo, MyTIUNo);
MyEngineSpeed = BlockSpeed[MyDirection][CurrentBlockNo];
SetSpeed(MyEngineSpeed, 5, MyEngineNo, MyTIUNo);
SetCounter(C3, MyEngineSpeed);
ResetinProgress = true;
return true
end;
Function09Name, Function09Label = ResetButton, "Initial Position";
--[[---------------------------------------------------------------------------------------]]

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 LLLButton()
if (LLLoop) then
print("Lionel-Ives Loop Off");
LLLoop = false;
else
print("Lionel-Ives Loop On");
LLLoop = true;
end;
return true;
end
Function05Name, Function05Label = LLLButton, "Lionel-Ives Loop";
--[[---------------------------------------------------------------------------------------]]


I made a video of this script running on my layout. You can view it here:  https://youtu.be/UqPsYRVCMPw

Here is the messages window of the Program Control script if you want to follow along.

Here is the Messages window when running the Thinking Engines Westbound.lua  script

Success compiling C:\trains\MTH\PC Control\Scripts\Thinking Engine Westbound.lua
setup() Running
Engine Number = 1
Westbound
Waiting for RFID Tag Detection
Start the Run
OverrideOccupancy() flag on McKees Rocks Yard 2 Block to 1
Set occupancy flag on McKees Rocks Block to 1
MainLine2Passing : Baltimore-South (simulated) > McKees Rocks Yard 2 Block > McKees Rocks Block
setup : Set accell/decell rates to 1
Starting up the Engine
Trip Odometer 0.32083883820908
RunTime = 37.30 seconds
tag(69.90) : Detector 3 Reader 0 Eng#1 P&LE U28B #2808 Tag on Front Truck
MainLine2Passing : @ 3-0 Engine 1 detected
McKees Rocks Block : Speed 22 Smph
TrySetOccupancy() flag on J&L Thru Track to 1
MainLine2Passing : McKees Rocks Yard 2 Block > McKees Rocks Block > J&L Thru Track
MainLine2Passing : Switch Aliquippa East to Normal
tag(70.90) : Detector 3 Reader 0 Eng#1 P&LE U28B #2808 Tag on Rear Truck
tag(73.40) : Detector 3 Reader 0 Car P&LE Boxcar #9826
tag(74.70) : Detector 3 Reader 0 Car PRR Tank #498748
tag(75.70) : Detector 3 Reader 0 Car P&LE Caboose #227
MainLine2Passing : @ 3-0 Caboose detected
ClearOccupancy() flag on McKees Rocks Yard 2 Block
tag(77.90) : Detector 5 Reader 1 Eng#1 P&LE U28B #2808 Tag on Front Truck
Passing2MainLine : @ 5-1 Engine 1 detected
J&L Thru Track : Speed 27 Smph
Passing2MainLine : McKees Rocks Block > J&L Thru Track > Aliquippa Block
TryMultiSetOccupancy() flag on Aliquippa Block to 1
Passing2MainLine : Switch Aliquippa West to Normal
tag(78.40) : Detector 5 Reader 1 Eng#1 P&LE U28B #2808 Tag on Rear Truck
tag(80.10) : Detector 5 Reader 1 Car P&LE Boxcar #9826
tag(81.10) : Detector 5 Reader 1 Car PRR Tank #498748
tag(81.90) : Detector 5 Reader 1 Car P&LE Caboose #227
Passing2MainLine : @ 5-1 Caboose detected
Passing2MainLine : J&L Thru Track : Switch Clearing Distance = 30.000 in - Speed 27 Smph = Sleep Delay 3.030 seconds
ClearOccupancy() flag on McKees Rocks Block
tag(98.90) : Detector 1 Reader 0 Eng#1 P&LE U28B #2808 Tag on Front Truck
MainLine2Passing : @ 1-0 Engine 1 detected
Aliquippa Block : Speed 34 Smph
TrySetOccupancy() flag on College Thru Track to 1
MainLine2Passing : J&L Thru Track > Aliquippa Block > College Thru Track
MainLine2Passing : Switch College East to Normal
tag(99.30) : Detector 1 Reader 0 Eng#1 P&LE U28B #2808 Tag on Rear Truck
tag(100.70) : Detector 1 Reader 0 Car P&LE Boxcar #9826
tag(101.60) : Detector 1 Reader 0 Car PRR Tank #498748
tag(102.20) : Detector 1 Reader 0 Car P&LE Caboose #227
MainLine2Passing : @ 1-0 Caboose detected
ClearOccupancy() flag on J&L Thru Track
tag(174.80) : Detector 4 Reader 1 Eng#1 P&LE U28B #2808 Tag on Front Truck
Passing2MainLine : @ 4-1 Engine 1 detected
College Thru Track : Speed 21 Smph
Passing2MainLine : Aliquippa Block > College Thru Track > New Castle Block
TryMultiSetOccupancy() flag on New Castle Block to 1
Passing2MainLine : Switch College West to Normal
tag(175.10) : Detector 4 Reader 1 Eng#1 P&LE U28B #2808 Tag on Rear Truck
tag(176.50) : Detector 4 Reader 1 Car P&LE Boxcar #9826
tag(177.50) : Detector 4 Reader 1 Car PRR Tank #498748
tag(178.40) : Detector 4 Reader 1 Car P&LE Caboose #227
Passing2MainLine : @ 4-1 Caboose detected
Passing2MainLine : College Thru Track : Switch Clearing Distance = 34.000 in - Speed 21 Smph = Sleep Delay 4.416 seconds
ClearOccupancy() flag on Aliquippa Block
tag(192.40) : Detector 2 Reader 1 Eng#1 P&LE U28B #2808 Tag on Front Truck
MainLine2Passing : @ 2-1 Engine 1 detected
New Castle Block : Speed 28 Smph
TrySetOccupancy() flag on Struthers Thru Track to 1
MainLine2Passing : College Thru Track > New Castle Block > Struthers Thru Track
MainLine2Passing : Switch Struthers East to Normal
tag(192.90) : Detector 2 Reader 1 Eng#1 P&LE U28B #2808 Tag on Rear Truck
tag(194.60) : Detector 2 Reader 1 Car P&LE Boxcar #9826
tag(195.60) : Detector 2 Reader 1 Car PRR Tank #498748
tag(196.40) : Detector 2 Reader 1 Car P&LE Caboose #227
MainLine2Passing : @ 2-1 Caboose detected
ClearOccupancy() flag on College Thru Track
tag(220.30) : Detector 2 Reader 0 Eng#1 P&LE U28B #2808 Tag on Front Truck
Passing2MainLine : @ 2-0 Engine 1 detected
Struthers Thru Track : Speed 26 Smph
Passing2MainLine : New Castle Block > Struthers Thru Track > Youngstown Block
TryMultiSetOccupancy() flag on Youngstown Block to 1
Passing2MainLine : Switch Struthers West to Normal
tag(220.60) : Detector 2 Reader 0 Eng#1 P&LE U28B #2808 Tag on Rear Truck
tag(222.20) : Detector 2 Reader 0 Car P&LE Boxcar #9826
tag(223.30) : Detector 2 Reader 0 Car PRR Tank #498748
tag(224.20) : Detector 2 Reader 0 Car P&LE Caboose #227
Passing2MainLine : @ 2-0 Caboose detected
Passing2MainLine : Struthers Thru Track : Switch Clearing Distance = 32.000 in - Speed 26 Smph = Sleep Delay 3.357 seconds
ClearOccupancy() flag on New Castle Block
tag(234.70) : Detector 4 Reader 0 Eng#1 P&LE U28B #2808 Tag on Front Truck
MainLine2Yard : @ 4-0 Engine 1 detected
Youngstown Block : Speed 22 Smph
TrySetOccupancy() flag on Youngstown Yard 1 Block to 1
MainLine2Yard : Struthers Thru Track > Youngstown Block > Youngstown Yard 1 Block
tag(235.10) : Detector 4 Reader 0 Eng#1 P&LE U28B #2808 Tag on Rear Truck
tag(236.90) : Detector 4 Reader 0 Car P&LE Boxcar #9826
tag(238.20) : Detector 4 Reader 0 Car PRR Tank #498748
tag(239.20) : Detector 4 Reader 0 Car P&LE Caboose #227
MainLine2Yard : @ 4-0 Caboose detected
ClearOccupancy() flag on Struthers Thru Track
tag(269.00) : Detector 5 Reader 0 Eng#1 P&LE U28B #2808 Tag on Front Truck
StopInYard : @ 5-0 Engine 1 detected
StopInYard : Youngstown Block > Youngstown Yard 1 Block > Cleveland-Ashtabula (simulated)
Youngstown Yard 1 Block : Target Distance = 100 in - Stopping Distance = 29.010 in - Speed 22 Smph = Stop Delay 8.800 seconds
CalculateBlockStopDelay() : Set speed to 0 after 8.8003660384913 seconds
tag(269.50) : Detector 5 Reader 0 Eng#1 P&LE U28B #2808 Tag on Rear Truck
tag(271.50) : Detector 5 Reader 0 Car P&LE Boxcar #9826
tag(272.70) : Detector 5 Reader 0 Car PRR Tank #498748
tag(273.70) : Detector 5 Reader 0 Car P&LE Caboose #227
StopInYard : @ 5-0 Caboose detected
StopInYard : Youngstown Yard 1 Block : Switch Clearing Distance = 24.000 in - Speed 22 Smph = Sleep Delay 2.975 seconds
StopInYard : Switch cleared after 2.9752066115702 seconds
ClearOccupancy() flag on Youngstown Block
StopInYard : @ 5-0 Stopped!
[STOP]
All Newly Detected Tags:
E6650A4A 8801010103010401260F202032383038
C653134A 88005000505001011D03202039383236
36FA124A 880002FF03FF02071903343938373438
3698074A 88000200010004011802202020323237
66C9054A 8801020103010401260F202032383038
Shutdown in Progress.....
Shutting down the Engine
cleanup() Complete at 317.00



Here is a video which shows two scripts running each controlling one engine. One engine is going westbound and the other engine is going eastbound. Every movement and switch setting is being controlled by a script.  https://youtu.be/pJG5EGqB2G4

Here is a drawing of the layout stretched out to be the single track point-to-point road between McKees Rocks and Youngstown. Of course the P&LE was 4 track during it's heyday but you can only model so much in O-scale. Click on the thumbnail image below to download the full size drawing. In running the railroad "for real", this view represents the real world a little better. The main line is shown in red. There are three loops (including the Lionel-Ives (or Balloon)  Block) used to run the railroad in "open-house" or continuous running mode.




Fixed Signals Display in RTC v4.5.0

Version 4.5.0 of the RTC program added two features I call "Fixed Signals" and "Cab Signals". I found that as I wrote more and more complicated scripts, I needed a way to visualize the setting of 1, 2, and 3 head signals around the layout (even if they don't physically exsist!).

The flags described in the previous section is not exactly signals. I wanted to learn more about real railroad signaling and how it worked.

Bruce Chubb developed a set of hardware and software that implemented three different types of railroad signaling schemes. https://www.jlcenterprises.net/.

He published a great tutorial in 14 issues of Railroad Model Craftsman from December 2015 to April 2017. He describes three schemes:
  1.     ABS - Automatic Block Signaling  - Part 7 June 2016 and Part 8 August 2016
  2.     APB - Absolute Permissive Block Signaling - Part 9 September 2016
  3.     CTC - Centralized Traffic Control - Part 10 October 2016

The routines that I ended up writing can support all three of these schemes. I implemented only APB signaling. Bruce's "Railroader's C/MRI Applications Handbook Volume 2 - Signaling Systems" describes these schemes. Look at chapter 19  on ABS as an introduction and then study chapter 20 on APB to understand what I did here. (The other sections describe the hardware that Bruce sells.)

Here is what I did:

I designed a simple signal head which could contain 1, 2, or 3 lights.
I designed a way to add these signals graphically to the Layout window.
I designed lua functions that would let you control which lights were on or off and their color (Red, Yellow or Green).
I designed a way for lua scripts to read the status of each signal just as if the train engineer were looking out the window at them.

To use my implementation, you do not need to have physical signals on your layout. You could, of course, add them and control them with the AIU commands. Maybe I'll do this someday.

The fixed signals consist of 1-3 lights. The lights can be Red, Green or Yellow. Above the lights is the signal name. This is usualy a two letter shorthand. Sometimes, three or four letters will fit and are allowed. Below the lights is the Number Plate. The Number Plate can display a number of up to four digits. It can be blank and it can display the letter "A". A number on the Number Plate indicates a permissive signal. A blank or the letter "A" indicates an absolute signal. This is what makes up the APB "Absolute-Permissive Block" signaling.

  Under construction as I add more details to this section


There can be up to 199 signals displayed in the Layout window.

I defined 17 common aspects that match what is described in the 7 June 1942 P&LE Employee Time Table No.140.

Constants that support Fixed Signals on the Layout Window

-----  definitions in defines.lua of constants that support the Fixed Signals Display

-- Aspect Selection
AsNone = 1;                 -- Invalid Aspect or the Signal does not exist
AsDark = 2;                 -- All Lights Off
AsStopSignal = 3;           -- Rule 292 without Number Plate or with 'A' on Number Plate. Indication: Stop
AsClear = 4;                -- Rule 281 Indication: Proceed
AsStopandProceed = 5;       -- Rule 291 with Number Plate. Indication: Stop: Then proceed at Restricting Speed.
AsApproach = 6;             -- Rule 285    Indication: Proceed prepared to stop at next signal.
                                           Train exceeding Medium Speed must at once reduce to that speed.
AsMediumClear = 7;          -- Rule 283    Indication: Proceed: Medium Speed within interlocking limits.
AsMediumApproach = 8;       -- Rule 286    Indication: Proceed at Medium Speed preparing to stop at next signal.
AsSlowClear = 9;            -- Rule 287    Indication: Proceed: Slow Speed within interlocking limits.
AsSlowApproach = 10;        -- Rule 288    Indication: Proceed preparing to stop at next signal: Slow Speed within interlocking limits.
AsApproachMedium = 11;      -- Rule 282    Indication: Proceed approaching next signal at Medium Speed.
AsApproachSlow = 12;        -- Rule 284    Indication: Proceed approaching next signal at Slow Speed.
                                           Train exceeding Medium Speed must at once reduce to that speed.
AsRestricting = 13;         -- Rule 290         Indication: Proceed at Restricted Speed.
AsApproachLimited = 14;     -- Rule 281 (B)     Indication: Proceed approaching next signal at Limited Speed.
AsLimitedClear = 15;        -- Rule 281 (C)     Indication: Proceed: Limited Speed within interlocking limits.
AsLimitedApproach = 16;     -- Rule 281 (D)     Indication: Proceed at Limited Speed preparing to stop at next signal.
AsMediumApproachSlow = 17;  -- Rule 283 (B)     Indication: Proceed at Medium Speed approaching next signal at slow speed.


-- Aspect Names - in lua, arrays are '1' based
AspectNames = {"Dark", "Stop Signal", "Clear", "Stop and Proceed", "Approach", "Medium Clear",
    "Medium Approach", "Slow Clear", "Slow Approach", "Approach Medium", "Approach Slow",
    "Restricting", "Approach Limited", "Limted Clear", "Limited Approach", "Limted Clear", "Limited Approach", "Medium Approach Slow"};

-- Signal Head Types (Number of Lights)
Hd3Light = 3
Hd2Light = 2
Hd1Light = 1
HdNone = 0

-- Signal Types based on Bruce Chubb's articles and books
UNDEFINED_SIGNAL_TYPE = 0;
PERMISSIVE_IN_OPEN_COUNTRY = 1;
ABSOLUTE_HEAD_BLOCK = 2;
PERMISSIVE_HEAD_BLOCK = 3;
APPROACH_TO_HEAD_BLOCK = 4;

Functions that access the Fixed Signals on the Layout Window

ShowLayout(); -- If the Layout window is not visible, this function will make it visible. If you are
    -- using Signals, you must call this function otherwise the signals won't be visible. Since
    -- signals are saved between runs of RTC, this function will show the signals generated
    -- by the previous run. Always returns true;

LayoutShowing(); -- If the Layout window is visble (from a call to ShowLayout()), returns true;
    -- Otherwise returns false.
    -- You can use this function to determine if you can process signals or not. The Layout window
    -- must be visible and signals must have been created on it in order to do anything with signals.

SetSignal(SignalNo, Aspect); -- Sets a signalhead to the aspect given. If the signalhead
    -- does not yet exist, it is created. SignalNo is between 1-199.
    -- Aspect is given in the defines listed above.
    -- Returns the signalhead's aspect as an integer.
    -- example: Asp = SetSignal(2, AsStop);

GetAspect(SignalNo); -- returns the aspect of given signalhead as an integer, the number of lights as an integer and
    -- the signal type such as PERMISSIVE_IN_OPEN_COUNTRY. If the signalhead
    -- does not exist, the function fails with an error message. SignalNo is between 1-199.
    -- example: Asp, Lights, Type = GetAspect(3);
    -- example: Asp = GetAspect(33);

CreateSignal(SignalNo, Direction, SignalType, Row, Col SignalName, Aspect, NumHeads, NumberPlate);
    -- Sets a signalhead name to the Name given and the signalhead aspect to Aspect. If the signalhead
    -- does not yet exist, it is created. SignalNo is between 1-199. Signalhead
    -- name should be limited to 2-4 characters to fit nicely on the signal.
    -- Direction is EB, WB, NB, or SB and is used only for information display for hints by each signal head
    -- SignalType is the type of signal as described in Bruce Chubb's articles and books such as PERMISSIVE_IN_OPEN_COUNTRY
    -- The number plate is set to NumberPlate if it is greater than 0. If NumberPlate equals zero,
    -- the number plate is set either to blank or the letter 'A' (for absolute)
    -- Returns the signalhead's aspect as an integer.
    -- example to set signal #25 to 2 light, Clear :
          -- CreateSignal(25, EB, PERMISSIVE_IN_OPEN_COUNTRY, 300, 300, "J2", AsClear,  Hd2Light, 0);
    -- example to delete signal 2:
          -- CreateSignal(2, EB, 0, 0, 0, "", 0, 0, 0);

GetSignalName(SignalNo);  -- returns three values. The first is the name of the
    -- given signalhead as a string. If the signalhead
    -- does not exist, it returns the string "Undefined". The second is the value of the number plate.
    -- The third value is the Signal Type as described in Bruce Chubb's articles and books
    -- SignalNo is between 1-199.
    -- example: nam, num = GetSignalName(22);
    -- example: nam, num, ttype = GetSignalName(2);

DeleteAllSignals(); -- Clears the Layout Window of all signals. The Layout Window is
    -- saved between runs of RTC so it will contain the signals generated by the
    -- previous run. Use this function to clear all of the signals if you want to start
    -- from scratch. Always returns true;



Master Signals Test.lua

This script tests the signals shown in the Layout window. The script sets up the testing environment and then sets several different aspects for the signals. It reads the aspect after it is set to confirm that the aspect was set correctly.

Here is the script  Master Signals Test.lua 
(this may not be as up to date as the zip file linked to below):
--[[
---------------------------------------------------------------------------
Remote Train Control Program for Windows

© Copyright 2021 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
title = "Master Signals Test"
require([[defines]]);
require([[functions]]);

local Count = 0;
-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "J";
Tab02Label = "K";
Tab03Label = "L";

ErrCount = 0;
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");
ErrCount = 0;
print("Start Test @ ", RunTime());
print("Showing the Layout Window");
ShowLayout();
--
DeleteAllSignals(); -- clear the window

CreateSignal(1, WB, ABSOLUTE_HEAD_BLOCK, 0, 0, "QB", AsDark, Hd3Light, 0);
CreateSignal(2, EB, APPROACH_TO_HEAD_BLOCK, 100, 100, "BB", AsClear, Hd3Light, 11);

Asp = GetAspect(2);
print("Signal 2 Aspect (2) = ", Asp);
if (Asp ~= AsClear) then ErrCount = ErrCount + 1; end;
Asp = GetAspect(1);
print("Signal 1 Aspect (0) = ", Asp);
if (Asp ~= AsDark) then ErrCount = ErrCount + 1; end;

nam = GetSignalName(1);
print("Signal 1 Name (QB) = ", nam);
if (nam ~= "QB") then ErrCount = ErrCount + 1; end;

nam, num = GetSignalName(22);
print("Signal 22 Name (**) = ", nam);
if (nam ~= "**") then ErrCount = ErrCount + 1; end;
print("Signal 22 Number Plate (0) = ", num);
if (num ~= 0) then ErrCount = ErrCount + 1; end;

--print(ErrCount .. " Error(s)");

Sleep(1);
Aspect = SetAspect(1, AsApproach);
if (Aspect ~= AsApproach) then ErrCount = ErrCount + 1; end;
Sleep(1);
Aspect = SetAspect(1, AsStopSignal);
if (Aspect ~= AsStopSignal) then ErrCount = ErrCount + 1; end
Sleep(1);

--print(ErrCount .. " Error(s)");

DeleteSignal(2); -- delete signal 2
Sleep(3);
CreateSignal(2, WB, PERMISSIVE_IN_OPEN_COUNTRY, 200, 200, "CC", AsApproach, 2, 3456); -- recreate signal 2, one row down
nam, num, ttype = GetSignalName(2);
print("Signal 2 Name (CC) = ", nam);
if (nam ~= "CC") then ErrCount = ErrCount + 1; end;
print("Signal 2 Number Plate (3456) = ", num);
if (num ~= 3456) then ErrCount = ErrCount + 1; end;
print("Signal 2 type (", PERMISSIVE_IN_OPEN_COUNTRY, ") = ", ttype);
if (ttype ~= PERMISSIVE_IN_OPEN_COUNTRY) then ErrCount = ErrCount + 1; end;
Asp = GetAspect(2);
print("Aspect 2 (", AsApproach, ") = ", Asp);
if (Asp ~= AsApproach) then ErrCount = ErrCount + 1; end;

return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
-- Always call Sleep(X) at least once in this function if it returns true;
print("loop() Running");
Sleep(1);

print("Almost Done...");
return Stop("DONE");
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button or the Stop() function is called
print("cleanup() Running");
if (ErrCount == 0) then
print("No errors");
else
print(ErrCount .. " Error(s)");
end;
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]




The Master Signal Test creates several signals on the Layout Window and then displays several aspects on each signal.



Since this is only a test, the signals are created along the left side of the Layout window. Normally you would create signals in the Layout window about where they belong along the track.



Cab Head Up Display (HUD)

Version 4.4 of the RTC program added a feature I call "Cab Signals". With supporting lua script code, two signals will appear in the engine cab (that is, the Program Control window) which can reflect the status of the next two signals ahead of the engine on the layout.


Constants that support the Cab Display in the Program Control Window

-----  definitions in defines.lua of constants that support the Cab Display

--[[------------Signals on the Program Control Cab Display-----------------]]
HOMESIGNAL = 0;
DISTANTSIGNAL = 1;

--  includes aspect names, etc from the defines listed in the Fixed Signals section

Functions that access the Cab Display on the Program Control Window


CreateCab(HomeSignal, DistantSignal);    -- create one or two signals in the Program Control window with from 1 to 3
    -- lights on each one. Use HdNone for the distant signal if you don't want that one displayed.

    -- example:   CreateCab(Hd3Light, Hd2Light);   
    -- create a 3 light Home signal and a 2 light Distant signal


RegisterCabSignals(HomeSignal, DistantSignal);    -- Tells the RTC program to reflect two signals from the Layout window
    -- to the two HUD signals in the in the Program Control window.
    -- Whenever the signals in the Layout window changes, the new
    -- aspect will show in the HUD signal. As the engine moves from block
    -- to block, it is up to the script change the registration
    -- so that the correct two signals have been registered.
    -- example:   RegisterCabSignals(5, 8);    
    -- associate the Home cab signal with signal #5 and
the Distant cab signal with signal #8.

SetCabSignal(SignalHead, Aspect, SignalName, NumberPlate, SignalType);    
    -- sets the aspect, signal name, numberplate and signal type

    -- examples:   SetCabSignal(DISTANTSIGNAL, AsStop, "TO", 45, PERMISSIVE_HEAD_BLOCK);
    -- aspect AsStop with the signal name "TO",
    --             number plate (45) and sets the signal type to 
PERMISSIVE_HEAD_BLOCK
    -- if the signal type were set to ABSOLUTE_HEAD_BLOCK, an "A" would appear where the number plate would be

SetCabSignal(SignalHead, Aspect, SignalName, NumberPlate);    -- sets the aspect, signal name and numberplate
    -- examples:   SetCabSignal(DISTANTSIGNAL, AsStop, "TO", 45);   
    -- aspect AsStop with the signal name "TO" and number plate (45) display

    --             SetCabSignal(HOMESIGNAL, AsDark, "**", 0);       
    -- a signal with all the lights off (AsDark)


SetCabSignal(SignalHead, Aspect, SignalName);    -- sets the aspect and signal name
    -- example:    SetCabSignal(HOMESIGNAL, AsStop, "QA");    
    -- aspect AsStop with the signal name "QA" with no number plate


SetCabSignal(SignalHead, Aspect);    -- sets the aspect without changing the signal name or numberplate
    -- example:    SetCabSignal(HOMESIGNAL, AsClear);    
    -- aspect AsClear (signal name and number plate are not changed)


GetCabAspect(SignalHead);    -- returns the aspect of the signal (such as AsStop), the number of Lights on the Signal Head
    -- and the signal type (such as ABSOLUTE_HEAD_BLOCK)

    -- example:    Aspect, NumLights, Type = GetCabAspect(
DISTANTSIGNAL);
    -- example:    Aspect = GetCabAspect(HOMESIGNAL);


Cab HUD Test.lua

This script tests the HUD shown in the Program Control window. The script sets up the testing environment and then cycles through all of the possible aspects of the HUD.

Here is the script  Cab HUD Test.lua 
(this may not be as up to date as the zip file linked to below):
--[[
---------------------------------------------------------------------------
Remote Train Control Program for Windows

© Copyright 2021 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
title = "Cab HUD Test"
require([[defines]]);
require([[functions]]);

local Count = 0;
-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "X";
Tab02Label = "Y";
Tab03Label = "Z";

ErrCount = 0;

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");
print("Showing the Layout Window");
ShowLayout();
--
CreateCab(HdNone, HdNone); -- delete both signals if they exist
CreateCab(Hd3Light, Hd3Light); -- create two 3 light signals
-- set Aspect, Signal Name, Number Plate and Signal Type
SetCabSignal(HOMESIGNAL, AsStopSignal, "QA", 1234, PERMISSIVE_IN_OPEN_COUNTRY);
--print("exit");
--return false;
---[[
Asp, NumLights, TType = GetCabAspect(HOMESIGNAL);
if (Asp ~= AsStopSignal) then
print("Home Signal is not Stop, is ", Asp);
Errcount = ErrCount + 1;
end;
if (TType ~= PERMISSIVE_IN_OPEN_COUNTRY) then
print("Home Signal Type is not Permissive in Open Country, is ", TType);
Errcount = ErrCount + 1;
end;
-- set Aspect, Signal Name, Number Plate and Signal Type
SetCabSignal(DISTANTSIGNAL, AsClear, "TO", 0, ABSOLUTE_HEAD_BLOCK); -- the Signal Name and Number Plate display
Asp, NumLights, TType = GetCabAspect(DISTANTSIGNAL);
if (Asp ~= AsClear) then
print("Distant Signal is not Clear, is ", Asp);
Errcount = ErrCount + 1;
end;
if (TType ~= ABSOLUTE_HEAD_BLOCK) then
print("Distant Signal Type is not Absolute Head Block, is ", TType);
Errcount = ErrCount + 1;
end;
--]]
--print("Early exit");
--return Stop("DONE");
---[[
Sleep(4);
print("3 light home signal only - Dark");
CreateCab(Hd3Light, 0); -- create one 3 light signal, deleting the distant signal
Sleep(4);
print("No signals (deleted)");
CreateCab(0, 0); -- delete both signals
Sleep(4);
print("Two 3 light signals. Home Signal QA 24 Stop");
CreateCab(Hd3Light, Hd3Light); -- create two 3 light signals
-- set Aspect and Signal Name
SetCabSignal(HOMESIGNAL, AsStopSignal, "QA", 24, PERMISSIVE_HEAD_BLOCK);
Sleep(4);
print("Distant Signal TT Absolute Stop");
-- set Aspect, Signal Name, Number Plate and Signal Type
SetCabSignal(DISTANTSIGNAL, AsStopSignal, "TT", 0, ABSOLUTE_HEAD_BLOCK);
Sleep(4);
return true; -- false=setup failed, true=setup succeeded
--]]
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
-- Always call Sleep(X) at least once in this function if it returns true;
print("loop() Running");
for x = AsStopSignal, AsLast-1 do
-- set Aspect only
print("Home Signal set to ", AspectNames[x]);
SetCabSignal(HOMESIGNAL, x); -- no Signal Name, no Number Plate and no Signal Type
Asp, NumLights = GetCabAspect(HOMESIGNAL);
if (Asp ~= x) then print("Home Signal Aspect not equal to ", x, " is ", Asp); ErrCount = ErrCount + 1; end;
print("Distant Signal set to ", AspectNames[AsLast - x + 1]);
SetCabSignal(DISTANTSIGNAL, AsLast - x + 1); -- no Signal Name, no Number Plate and no Signal Type
Asp, NumLights, TType = GetCabAspect(DISTANTSIGNAL);
if (Asp ~= AsLast - x + 1) then print("Distant Signal Aspect not equal to ", AsLast - x + 1); ErrCount = ErrCount + 1; end;
Sleep(1);
end;
print("Almost Done...");
return Stop("DONE");
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button or the Stop() function is called
print("cleanup() Running");
if (ErrCount == 0) then
print("No errors");
else
print(ErrCount .. " Error(s)");
end;

return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]






Here is a screen capture of the HUD when running the Cab Heads Up Display (HUD) test. The two three-head signals are shown on the right side of the Messages Window. The upper signal is the Home signal - the signal in front of the engine. The lower signal is the Distant signal - the next signal after the Home signal.

This test creates two signals and then displays different aspects on each one.

In the setup() function, the signals are created with:

CreateCab(Hd3Light, Hd3Light);-- create two 3 light signals

Then the Home and Distant signals are defined with:

    SetCabSignal(HOMESIGNAL, AsStopSignal, "QA", 1234,
    PERMISSIVE_IN_OPEN_COUNTRY);

    SetCabSignal(DISTANTSIGNAL, AsClear, "TO", 0,
    ABSOLUTE_HEAD_BLOCK);


Then the Home and Distant signals are changed with:

    SetCabSignal(HOMESIGNAL, AsStopSignal, "QA", 24,
    PERMISSIVE_HEAD_BLOCK);

    SetCabSignal(DISTANTSIGNAL, AsStopSignal, "TT", 0,
    ABSOLUTE_HEAD_BLOCK);


These are signals that are displaying when I captured the screen. The Home signal is named "QA", its signal number is set to 24 and it is a permissive head block signal (see Bruce Chubb's tutorials to learn what this means.

The Distant signal is named "TT" and is an absolute head block signal (designated by the "A" below the lights).

The test then displays all of the aspects on the signals. At the point where I captured the screen, the Home signal is displaying a Medium Clear aspect and the Distant signal is displaying an Approach Slow aspect. The code is:

    SetCabSignal(HOMESIGNAL, x);
    SetCabSignal(DISTANTSIGNAL, AsLast - x + 1);

Using the terminalogy of signaling, the Home signal is showing indicating "Proceed: Medium Speed within interlocking limits." and the Distant signal is indicating "Proceed approaching next signal at Slow Speed. Train exceeding Medium Speed must at once reduce to that speed."

The script reads back the setting to test that the aspect was set as expected (for both Home and Distant HUD signal):

    Asp, TType = GetCabAspect(DISTANTSIGNAL);
    if (Asp ~= AsLast - x + 1) then
        print("Distant Signal Aspect not equal to ",
             AsLast - x + 1); ErrCount = ErrCount + 1;

        end;


Registering HUD Test.lua

This script tests the signals shown in the Layout window and the HUD in the Program Control window. The script sets up the testing environment and then sets several different aspects for the signals. It reads the aspect after it is set to confirm that the aspect was set correctly.

Here is the script  Registering HUD Test.lua 
(this may not be as up to date as the zip file linked to below):
--[[
---------------------------------------------------------------------------
Remote Train Control Program for Windows

© Copyright 2021 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
title = "Registering HUD Test"
require([[defines]]);
require([[functions]]);

local Count = 0;
-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "Not Used";
Tab02Label = "Not Used";
Tab03Label = "Not Used";

Tags = {}; -- table to hold all of the tags detected in sequence
TagCount = 0;
-- state machine states
IDLE = 0;
SM = IDLE;
ErrCount = 0;
-- Signals created by this script are displayed in the cab
-- by the "Cab Signal Test.lua"
-- script which should be run in a different PC window
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");
math.randomseed(os.time()); -- random seed
--
print("Showing the Layout Window");
ShowLayout();
Sleep(1);
print("Deleting All Signals");
DeleteAllSignals(); -- clear the window
Sleep(1);
print("Creating Signals 1, 2, 3, 4, 5, 6, 25, 26");
CreateSignal( 1, WB, ABSOLUTE_HEAD_BLOCK, 0, 0, "AA", AsDark, Hd3Light, 0); -- Set signal #1 to 3 light, Dark
Sleep(1);
CreateSignal( 2, WB, PERMISSIVE_IN_OPEN_COUNTRY, 100, 0, "BB", AsDark, Hd3Light, 268); -- Set signal #2 to 3 light, Dark, Number Plate
Sleep(1);
CreateSignal( 3, WB, PERMISSIVE_IN_OPEN_COUNTRY, 200, 0, "CC", AsDark, Hd3Light, 111); -- Set signal #3 to 3 light, Dark, Number Plate
Sleep(1);
CreateSignal( 4, WB, ABSOLUTE_HEAD_BLOCK, 300, 0, "DD", AsDark, Hd3Light, 0); -- Set signal #4 to 3 light, Dark
Sleep(1);
CreateSignal( 5, EB, ABSOLUTE_HEAD_BLOCK, 300, 150, "G2", AsDark, Hd2Light, 0); -- Set signal #5 to 2 light, Dark
Sleep(1);
CreateSignal( 6, EB, PERMISSIVE_IN_OPEN_COUNTRY, 400, 150, "G3", AsDark, Hd1Light, 55); -- Set signal #6 to 1 light, Dark
Sleep(1);
CreateSignal(25, EB, ABSOLUTE_HEAD_BLOCK, 300, 300, "J2", AsClear, Hd2Light, 0); -- Set signal #25 to 1 light, Clear
Sleep(1);
CreateSignal(26, EB, PERMISSIVE_IN_OPEN_COUNTRY, 400, 300, "J3", AsApproachLimited, Hd3Light, 20); -- Set signal #26 to 3 light, Approach Limited
Sleep(1);
print("Signals Created");

Sleep(6);
SetAspect(1, AsMediumClear); -- Set signal #1 to Medium Clear (4)
Asp = GetAspect(1);
if (Asp ~= AsMediumClear) then print("Signal 1 Aspect not Medium Clear (4)"); ErrCount = ErrCount + 1; end;

SetAspect(2, AsApproach); -- Set signal #2 to AsApproach (3)
Asp = GetAspect(2);
if (Asp ~= AsApproach) then print("Signal 2 Aspect not Approach (3)"); ErrCount = ErrCount + 1; end;

SetAspect(5, AsApproachLimited); -- Set signal #5 to AsApproachLimited (12)
Asp, NumLights = GetAspect(5);
if (Asp ~= AsApproachLimited) then print("Signal 5 Aspect not Approach Limited (12)"); ErrCount = ErrCount + 1; end;
if (NumLights ~= Hd2Light) then print("Signal 5 Number of lights not Hd2Light (2)"); ErrCount = ErrCount + 1; end;

SetAspect(6, AsClear); -- Set signal #6 to AsClear (2)
Asp, NumLights = GetAspect(6);
if (Asp ~= AsClear) then print("Signal 6 Aspect not Clear (2)"); ErrCount = ErrCount + 1; end;
if (NumLights ~= Hd1Light) then print("Signal 6 Number of lights not Hd1Light (1)"); ErrCount = ErrCount + 1; end;

-- create and register the cab signals
print("Creating Cab Signals");
CreateCab(Hd3Light, Hd3Light); -- create light signals - (home, distant) -- create as 3 head signals so that they accept all possible aspects
print("Registering the Cab Signals #1 and #2");
RegisterCabSignals(1, 2); -- associate the Home HUD with signal #1 and the Distant HUD signal with signal #2
print("Reflecting signals from Layout window into the Cab Display");
print ("Home Signal : " .. AspectNames[(GetCabAspect(HOMESIGNAL))]);
print ("Distant Signal : " .. AspectNames[(GetCabAspect(DISTANTSIGNAL))]);

print("Reflecting signals from Layout window into the Cab Display");
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
-- Always call Sleep(X) at least once in this function if it returns true;
--print("loop() Running");
-- Fixed signals #1 and #2 are be associated to HUD Cab Home and Distant signals.
Sleep(2);
SetAspect(1, AsMediumClear); -- Set signal #1 to Medium Clear (4)
Asp, NumLights = GetAspect(1);
if (Asp ~= AsMediumClear) then print("Signal 1 Aspect not Medium Clear (4)"); ErrCount = ErrCount + 1; end;
if (NumLights ~= Hd3Light) then print("Signal 1 Number of Lights not Hd3Light (3)"); ErrCount = ErrCount + 1; end;
--
Sleep(2);
SetAspect(2, AsClear); -- Set signal #2 to Clear (2)
Asp = GetAspect(2);
if (Asp ~= AsClear) then print("Signal 2 Aspect not Clear (2)"); ErrCount = ErrCount + 1; end;
--
Sleep(2);
SetAspect(1, AsMediumApproach); -- Set signal #1 to Medium Approach (5)
Asp = GetAspect(1);
if (Asp ~= AsMediumApproach) then print("Signal 1 Aspect not Medium Approach (5)"); ErrCount = ErrCount + 1; end;
--
-- check that signal #1 is correctly reflected in the Home Cab Signal
--
Asp, NumLights = GetCabAspect(HOMESIGNAL);
if (Asp ~= AsMediumApproach) then print("Home Signal Aspect not Medium Approach (5)"); ErrCount = ErrCount + 1; end;
if (NumLights ~= Hd3Light) then print("Home Signal Number of Lights not Hd3Light (3)"); ErrCount = ErrCount + 1; end;
--
val = math.random(AsDark, AsLast - 1);
Sleep(2);
SetAspect(2, val); -- Set signal #2 to Random Number (val)
print("Signal 2 set to ", val, " - ", AspectNames[val]);
Asp = GetAspect(2);
if (Asp ~= val) then print("Signal 2 Aspect not ", AspectNames[val] , " (", val, ")"); end;
--
-- check that signal #2 is correctly reflected in the Distant Cab Signal
--
Asp, NumLights = GetCabAspect(DISTANTSIGNAL);
if (Asp ~= val) then print("Distant Signal Aspect not ", AspectNames[val], " (", val, ")"); ErrCount = ErrCount + 1; end;
if (NumLights ~= Hd3Light) then print("Distant Signal Number of Lights not Hd3Light (3)"); ErrCount = ErrCount + 1; end;
--
Sleep(2);
SetAspect(3, AsMediumApproach); -- Set signal #3 to Medium Approach (5)
Asp = GetAspect(3);
if (Asp ~= AsMediumApproach) then print("Signal 3 Aspect not Medium Approach (5)"); ErrCount = ErrCount + 1; end;
--
Sleep(2);
SetAspect(4, AsStopSignal); -- Set signal #4 to Stop (1)
Asp = GetAspect(4);
--
Sleep(2);
SetAspect(3, AsMediumClear); -- Set signal #3 to Medium Clear (4)
Asp = GetAspect(3);
if (Asp ~= AsMediumClear) then print("Signal 3 Aspect not Medium Clear (4)"); ErrCount = ErrCount + 1; end;
--
Sleep(2);
SetAspect(4, AsClear); -- Set signal #4 to Clear (2)
Asp = GetAspect(4);
if (Asp ~= AsClear) then print("Signal 4 Aspect not Clear (2)"); ErrCount = ErrCount + 1; end;
--
SetAspect(5, AsLimitedClear); -- Set signal #5 to AsLimitedClear (13)
Asp = GetAspect(5);
if (Asp ~= AsLimitedClear) then print("Signal 5 Aspect not Limited Clear (13)"); ErrCount = ErrCount + 1; end;


-- create and register the cab signals
print("Changing Cab Signals");
_, NumLightsHome = GetAspect(5);
_, NumLightsDistant = GetAspect(6);
CreateCab(NumLightsHome, NumLightsDistant); -- create light signals - (home, distant)
--
print("Registering the Cab Signals #5 and #6");
RegisterCabSignals(5, 6); -- associate the Home HUD with signal #5 and the Distant HUD signal with signal #6
print("Reflecting signals from Layout window into the Cab Display");
print("Editing Aspects Possible, press [STOP] to break out of loop");
for i=1,20,1 do
Sleep(1);
print ("Home Signal : " .. AspectNames[(GetCabAspect(HOMESIGNAL))]);
Sleep(1);
print ("Distant Signal : " .. AspectNames[(GetCabAspect(DISTANTSIGNAL))]);
end;
print("Almost Done...");
return Stop("DONE");
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.
--Beep(); -- sound a tone
BumpCounter(COUNTER01);

-- 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
-- next line shows a different way to quote a string using brackets
print(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)));
-- follow on to process the detected engine
else -- it's a car
print(string.format([[tag(%.2f) : Detector %d Reader %d Car %s %s #%s]],
RunTime(), Detector, Reader, GetRailroadName(Railroad), GetCarName(CarModel), CarNumber));
-- follow on to process the detected Caboose
if (CarModel ~= CABOOSE) then -- ignore cars other than Caboose
return true; -- true=continue to process tags, false=stop processing tags
end
end
--
print("Process Signal");
BumpCounter(COUNTER02);
--[[---------------------------------------------------------------------------------------]]
-- STATE MACHINE
--[[---------------------------------------------------------------------------------------]]
SetCounter(COUNTER04, SM);
--[[---------------------------------------------------------------------------------------]]
if (SM == IDLE) then -- do nothing
-- continue in this state
return true;
end
--[[---------------------------------------------------------------------------------------]]
if (SM == IDLE) then
-- continue in this state
return true;
end
return true; -- true=continue to process tags, false=stop processing tags
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button or the Stop() function is called
print("cleanup() Running");
-- delete all signals
print("Deleting all Signals");
DeleteSignal(1);
Sleep(1);
DeleteSignal(2);
Sleep(1);
DeleteSignal(3);
Sleep(1);
DeleteSignal(4);
Sleep(1);
DeleteSignal(5);
Sleep(1);
DeleteSignal(6);
Sleep(1);
DeleteSignal(25);
Sleep(1);
DeleteSignal(26);
Sleep(1);

if (ErrCount == 0) then
print("No errors");
else
print(ErrCount .. " Error(s)");
end;
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]




The Registering HUD Test creates several signals on the Layout Window and then displays several aspects on each signal. It then creates the HUD and associates those cab signals with Fixed signals #1 and #2. Then it displays several aspects on signals #1 and #2 and we can see those aspects reflected in the HUD.



Since this is only a test, the signals are created along the left side of the Layout window. Normally you would create signals in the Layout window about where they belong along the track.


Layout4.lua

  Under construction

The Layout4.lua script defines the layout. That is, it defines and blocks, signals, switch tracks, tag detectors and readers, stopping and starting distances for the engines and stopping locations for stations.

It defines the TIU number controlling the layout, the name of the layout and acceleration/deceleration rate to use.

This script defines my P&LE layout between McKees Rocks and Youngstown. You will have to make a script to define your layout.

Here is the script  Layout4.lua 
(this may not be as up to date as the zip file linked to below):
--[[
---------------------------------------------------------------------------
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
--[=[---------------------------------------------------------------------------------------
This file contains all of the details about Instrumented Layout #4.
The stopping and starting distances created by "Deceleration Test.lua" and "Acceleration Test.lua".

usage:

local Layout4 = require([[Layout4]]);

StopDelay = (Struthers_Passenger_StoppingDistance - Layout4.STOPDistance(EngineNo,CurrentSpeed)) * (48 / (12 * 5280 * CurrentSpeed)) * 3600;
SetSpeed(0, StopDelay, EngineNo, MyTIUNo);
-----------------------------------------------------------------------------------------]=]

-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local Layout = {}
Layout.__index = Layout

Layout.MASTER_TIU = 1; -- Controlling TIU Number
Layout.name = "P&LE Layout4"; -- Layout Name
Layout.AccDecRate = 1; -- Acceleration and Deceleration Rate

-----------------------------------------------------------------------------
-- Layout constants
-----------------------------------------------------------------------------
-- These constants describe your layout
--[[------------------------------- Stopping Distance for 1 Smph/Sec -----------------------------------------------]]

-- All tables in Lua are 1 based, that is, StoppingDistance[EngineNo][1] is the first item in the list
-- Measured stopping distance in inches (from "Deceleration Test.lua") for scale speeds from 1 Smph to 40 Smph
-- Table contains a table for each Engine Number
-- Deceleration set to 1 Smph/sec. Unless specified, each engine is pulling two freight
-- cars and a caboose.
-- These table constructors were created using "Deceleration Test.lua"
Layout.StoppingDistance1 = {
[ 1] = {[1] = 0.19377162629764, [2] = 0.57024221453259, [3] = 1.0740484429061, [4] = 1.6830449826987, [5] = 2.3640138408305, [6] = 3.1612456747406, [7] = 4.1854671280276, [8] = 4.346020761246, [9] = 6.1397923875427, [10] = 7.4961937716262, [11] = 8.7584775086504, [12] = 10.125951557094, [13] = 11.620761245675, [14] = 13.248442906575, [15] = 14.770934256055, [16] = 16.525951557093, [17] = 18.674048442907, [18] = 18.192387543252, [19] = 22.123183391004, [20] = 24.387543252595, [21] = 26.41384083045, [22] = 29.010380622837, [23] = 31.020069204152, [24] = 34.120415224913, [25] = 36.268512110726, [26] = 39.003460207613, [27] = 41.649826989619, [28] = 44.478892733565, [29] = 47.031141868512, [30] = 50.297577854671, [31] = 53.923875432526, [32] = 57.240138408304, [33] = 60.01384083045, [34] = 60.119031141869, [35] = 67.056055363323, [36] = 66.685121107266, [37] = 74.253287197233, [38] = 78.040138408305, [39] = 82.613148788927, [40] = 86.200692041522}
,
[ 2] = {[1] = 0.28107746360966, [2] = 0.55211644637755, [3] = 1.0088673247447, [4] = 1.6513300987123, [5] = 2.3841392002677, [6] = 3.1621214656175, [7] = 3.9902961351844, [8] = 4.9891249790862, [9] = 5.9477998996149, [10] = 7.0319558306841, [11] = 8.4825163125316, [12] = 9.6469800903456, [13] = 11.197925380626, [14] = 12.593274217835, [15] = 14.18939267191, [16] = 16.187050359712, [17] = 17.617533879874, [18] = 19.580056884725, [19] = 21.758407227706, [20] = 23.625564664548, [21] = 25.959511460599, [22] = 28.062573197256, [23] = 30.612347331437, [24] = 33.051698176342, [25] = 35.726953321064, [26] = 38.266688974402, [27] = 41.102559812615, [28] = 43.888238246613, [29] = 46.889744018738, [30] = 49.610172327254, [31] = 53.219006190396, [32] = 56.220511962523, [33] = 59.583403045005, [34] = 63.232390831521, [35] = 65.847415091182, [36] = 69.531537560649, [37] = 73.44654508951, [38] = 77.231052367408, [39] = 81.201271540907, [40] = 84.870336289107}
,
[ 3] = {[1] = 0.22145328719723, [2] = 0.5923875432526, [3] = 0.99100346020761, [4] = 1.5889273356401, [5] = 2.2089965397924, [6] = 3.0062283737024, [7] = 3.8089965397924, [8] = 4.7058823529412, [9] = 5.7522491349481, [10] = 6.9259515570934, [11] = 8.1882352941176, [12] = 9.3674740484429, [13] = 10.961937716263, [14] = 12.440138408305, [15] = 13.857439446367, [16] = 15.761937716263, [17] = 17.63321799308, [18] = 19.426989619377, [19] = 21.392387543253, [20] = 23.496193771626, [21] = 25.694117647059, [22] = 27.886505190312, [23] = 30.006920415225, [24] = 32.653287197232, [25] = 35.310726643599, [26] = 37.857439446367, [27] = 40.503806228373, [28] = 43.244290657439, [29] = 46.256055363322, [30] = 49.649826989619, [31] = 52.163321799308, [32] = 55.800692041523, [33] = 59.582006920415, [34] = 62.577162629758, [35] = 65.356401384083, [36] = 69.021453287197, [37] = 72.719723183391, [38] = 76.689273356401, [39] = 80.531487889274, [40] = 84.650519031141}
,
[30] = {[1] = 0.21591695501736, [2] = 0.56470588235294, [3] = 0.83044982698961, [4] = 1.5335640138409, [5] = 2.319723183391, [6] = 2.5190311418686, [7] = 3.8034602076124, [8] = 4.7114186851211, [9] = 5.9017301038062, [10] = 6.9148788927336, [11] = 7.9889273356402, [12] = 9.4560553633217, [13] = 10.912110726644, [14] = 12.429065743945, [15] = 14.023529411765, [16] = 15.645674740484, [17] = 16.232525951557, [18] = 19.255363321799, [19] = 21.497577854671, [20] = 23.307958477509, [21] = 25.793771626298, [22] = 27.731487889273, [23] = 30.167474048443, [24] = 32.564705882353, [25] = 34.873356401384, [26] = 37.58062283737, [27] = 40.088581314879, [28] = 43.011764705883, [29] = 46.267128027681, [30] = 49.544636678201, [31] = 52.357093425605, [32] = 55.496193771627, [33] = 58.884429065743, [34] = 62.305882352941, [35] = 65.467128027682, [36] = 66.762629757786, [37] = 72.664359861592, [38] = 76.423529411764, [39] = 80.642214532872, [40] = 84.417993079585}
,
[ 4] = {[1] = 0.28788927335638, [2] = 0.60899653979245, [3] = 1.123875432526, [4] = 1.2456747404844, [5] = 2.3750865051903, [6] = 3.1501730103806, [7] = 3.2885813148789, [8] = 5.1377162629759, [9] = 5.0159169550175, [10] = 6.1397923875433, [11] = 8.4207612456749, [12] = 8.4373702422145, [13] = 11.111418685121, [14] = 11.305190311419, [15] = 14.42214532872, [16] = 16.00553633218, [17] = 17.898961937716, [18] = 19.593079584775, [19] = 21.802076124567, [20] = 23.97785467128, [21] = 26.319723183391, [22] = 28.395847750865, [23] = 30.842906574395, [24] = 33.179238754325, [25] = 33.162629757785, [26] = 38.793079584775, [27] = 41.251211072664, [28] = 44.384775086505, [29] = 46.920415224913, [30] = 49.821453287197, [31] = 49.699653979238, [32] = 56.525951557093, [33] = 59.582006920415, [34] = 62.953633217993, [35] = 66.491349480969, [36] = 69.635986159169, [37] = 69.480968858131, [38] = 77.840830449826, [39] = 81.588927335639, [40] = 85.287197231834}
, -- Engine 7 P&LE GP7 #1501 is pulling three P&LE passenger cars
[ 7] = {[1] = 0.32110726643599, [2] = 0.70865051903115, [3] = 1.3453287197232, [4] = 1.522491349481, [5] = 2.7903114186851, [6] = 3.604152249135, [7] = 4.6892733564014, [8] = 5.6193771626298, [9] = 6.9314878892734, [10] = 8.0664359861592, [11] = 9.5723183391003, [12] = 10.790311418685, [13] = 9.9820069204153, [14] = 13.110034602076, [15] = 12.921799307958, [16] = 17.699653979239, [17] = 19.548788927336, [18] = 20.982698961938, [19] = 23.363321799308, [20] = 25.771626297578, [21] = 27.681660899654, [22] = 29.840830449827, [23] = 33.107266435986, [24] = 35.510034602076, [25] = 37.757785467128, [26] = 40.669896193771, [27] = 43.825605536332, [28] = 46.815224913495, [29] = 49.478200692042, [30] = 52.927335640138, [31] = 55.800692041523, [32] = 59.21660899654, [33] = 62.898269896193, [34] = 66.491349480969, [35] = 68.860899653979, [36] = 73.201384083045, [37] = 77.038062283737, [38] = 80.946712802768, [39] = 81.062975778547, [40] = 86.244982698962}
,
[ 8] = {[1] = 0.25511854693349, [2] = 0.63779636733372, [3] = 1.114757128992, [4] = 1.6693626658039, [5] = 2.3016129777695, [6] = 3.1057910061469, [7] = 4.0652585848316, [8] = 4.9304432222582, [9] = 6.084022738827, [10] = 7.193233812451, [11] = 9.1898137449738, [12] = 9.4172020150668, [13] = 12.223506031335, [14] = 14.059250358183, [15] = 13.837408143458, [16] = 15.506770809262, [17] = 18.906502749919, [18] = 21.324582890419, [19] = 23.4154457642, [20] = 25.74478901881, [21] = 27.669270231548, [22] = 29.915422655636, [23] = 32.344594906873, [24] = 35.062162037251, [25] = 36.958912973148, [26] = 40.303184360124, [27] = 43.458889864584, [28] = 46.437121597264, [29] = 49.498544160466, [30] = 56.048435550215, [31] = 53.34750658594, [32] = 56.076165827056, [33] = 62.337662337662, [34] = 66.247631372187, [35] = 66.669131580164, [36] = 73.008272865924, [37] = 77.794518648611, [38] = 81.038961038961, [39] = 84.305587650783, [40] = 87.239450940518}
,
[ 9] = {[1] = 0.15501730103806, [2] = 0.83044982698962, [3] = 1.4062283737024, [4] = 1.2346020761246, [5] = 3.2166089965398, [6] = 4.3072664359862, [7] = 3.3439446366782, [8] = 4.2131487889273, [9] = 5.6525951557094, [10] = 6.2062283737024, [11] = 8.2269896193772, [12] = 9.444982698962, [13] = 10.856747404844, [14] = 12.534256055363, [15] = 13.896193771626, [16] = 14.527335640138, [17] = 17.444982698962, [18] = 18.978546712803, [19] = 21.397923875433, [20] = 23.18615916955, [21] = 25.59446366782, [22] = 27.404844290658, [23] = 29.813148788927, [24] = 30.710034602076, [25] = 35.321799307959, [26] = 37.647058823529, [27] = 42.945328719723, [28] = 46.206228373702, [29] = 47.346712802768, [30] = 52.866435986159, [31] = 55.773010380623, [32] = 58.729411764706, [33] = 62.42214532872, [34] = 65.788235294118, [35] = 69.730103806228, [36] = 73.002076124567, [37] = 77.170934256055, [38] = 81.500346020761, [39] = 85.248442906574, [40] = 89.112802768166}
,
[10] = {[1] = 0.14292913097242, [2] = 0.6486783636446, [3] = 1.0994548536349, [4] = 1.2258921618031, [5] = 1.8470841541071, [6] = 3.265380915296, [7] = 4.0349993128406, [8] = 4.1229557011311, [9] = 5.1509459892805, [10] = 6.1294608090154, [11] = 7.2728938567959, [12] = 9.7961427458886, [13] = 9.8840991341794, [14] = 11.225434055614, [15] = 12.753676302167, [16] = 14.479820422374, [17] = 16.079527234413, [18] = 17.838655000229, [19] = 23.572312061936, [20] = 21.664757890879, [21] = 27.750240505749, [22] = 28.327454303907, [23] = 32.620825507353, [24] = 34.929680699986, [25] = 37.678317834074, [26] = 40.135599431949, [27] = 42.779788354941, [28] = 40.94369874937, [29] = 46.825782216318, [30] = 49.98121764625, [31] = 52.790324797289, [32] = 53.032204865088, [33] = 60.035732282743, [34] = 63.004260387558, [35] = 66.291630399927, [36] = 69.661459526318, [37] = 69.370103990104, [38] = 77.901873654313, [39] = 81.711484722159, [40] = 85.839937697558}
,
[11] = {[1] = 0.12139605462821, [2] = 0.57663125948406, [3] = 0.95978755690441, [4] = 1.4681335356601, [5] = 2.0789074355084, [6] = 2.9135053110774, [7] = 3.6342943854325, [8] = 4.6168437025796, [9] = 5.5576631259484, [10] = 6.5857359635812, [11] = 7.6289833080425, [12] = 9.0364188163885, [13] = 11.733687405159, [14] = 11.820940819423, [15] = 13.482549317147, [16] = 15.056904400607, [17] = 16.919575113809, [18] = 18.63808801214, [19] = 20.641122913505, [20] = 22.670713201821, [21] = 25.064491654021, [22] = 27.086494688923, [23] = 29.385432473445, [24] = 31.62746585736, [25] = 32.272382397572, [26] = 36.672989377845, [27] = 39.650986342944, [28] = 42.264795144158, [29] = 44.878603945372, [30] = 46.077389984825, [31] = 51.054628224583, [32] = 54.389226100152, [33] = 57.799696509863, [34] = 60.864946889227, [35] = 61.562974203339, [36] = 67.697268588771, [37] = 71.198786039454, [38] = 74.795144157815, [39] = 78.793626707132, [40] = 79.981031866464}
,
[12] = {[1] = 0.21075010398854, [2] = 0.52132920460323, [3] = 1.0759347414152, [4] = 1.9910338771549, [5] = 2.2627905901927, [6] = 3.0836067846744, [7] = 4.4534824605999, [8] = 5.479502703702, [9] = 5.7734436382123, [10] = 7.0767666497204, [11] = 8.2913527753385, [12] = 9.428294125803, [13] = 11.042196237926, [14] = 14.031520081342, [15] = 14.103618801128, [16] = 14.824605998983, [17] = 17.725192956511, [18] = 19.749503165873, [19] = 23.054952165272, [20] = 25.179091371262, [21] = 27.785737394278, [22] = 27.8689282248, [23] = 30.508850580025, [24] = 32.97129916347, [25] = 35.372741137866, [26] = 39.987059204141, [27] = 42.676896057679, [28] = 45.904700281924, [29] = 46.847529694505, [30] = 47.696076165827, [31] = 52.737440495448, [32] = 55.876507833804, [33] = 61.916162129686, [34] = 65.387992790128, [35] = 69.21477099413, [36] = 72.559042381106, [37] = 76.258261311642, [38] = 76.801774737718, [39] = 78.099551693858, [40] = 84.571798308454}
, -- Engine 13 P&LE GP7 #1500 is pulling three P&LE passenger cars
[13] = {[1] = 0.16055363321799, [2] = 0.5038062283737, [3] = 1.0629757785467, [4] = 1.6166089965398, [5] = 1.8934256055363, [6] = 2.9674740484429, [7] = 3.8477508650519, [8] = 4.8941176470588, [9] = 5.8574394463668, [10] = 6.9647058823529, [11] = 8.0885813148789, [12] = 9.5391003460208, [13] = 10.928719723183, [14] = 12.561937716263, [15] = 14.195155709343, [16] = 15.856055363322, [17] = 17.57785467128, [18] = 19.415916955017, [19] = 21.331487889273, [20] = 23.147404844291, [21] = 23.889273356401, [22] = 27.543252595156, [23] = 30.200692041522, [24] = 32.680968858131, [25] = 35.593079584775, [26] = 38.062283737024, [27] = 40.321107266436, [28] = 43.98615916955, [29] = 46.184083044983, [30] = 47.263667820069, [31] = 52.196539792388, [32] = 55.507266435986, [33] = 58.884429065744, [34] = 59.836678200692, [35] = 66.120415224914, [36] = 69.408996539792, [37] = 73.212456747405, [38] = 76.456747404844, [39] = 80.586851211072, [40] = 83.847750865053}
,
[14] = {[1] = 0.23293432546102, [2] = 0.43259231871338, [3] = 0.99828996626149, [4] = 1.6249942228589, [5] = 2.2905208670333, [6] = 3.017054120257, [7] = 3.8378703147387, [8] = 4.1817257475621, [9] = 5.9010029116791, [10] = 7.0601284836161, [11] = 8.1360632250312, [12] = 8.6740305957389, [13] = 10.79262374636, [14] = 12.578453574895, [15] = 14.197901742386, [16] = 15.861718352821, [17] = 17.403521745159, [18] = 19.372371400841, [19] = 21.612977769561, [20] = 23.481998428618, [21] = 25.933354901327, [22] = 26.415861718353, [23] = 30.314738642141, [24] = 32.993483384943, [25] = 35.638951795535, [26] = 38.007117437722, [27] = 40.69695429126, [28] = 43.658547857836, [29] = 44.429449554005, [30] = 49.376530942367, [31] = 52.693072052503, [32] = 55.715672228128, [33] = 59.109858113417, [34] = 62.498497943338, [35] = 65.615381060221, [36] = 69.580810648426, [37] = 70.540278227111, [38] = 77.234367056431, [39] = 80.817118824235, [40] = 84.63835097287}
,
[15] = {[1] = 0.21629615935665, [2] = 0.53796737070759, [3] = 1.0149281323659, [4] = 1.669362665804, [5] = 2.2073300365115, [6] = 3.0447843970976, [7] = 3.9210611452604, [8] = 4.8250681702639, [9] = 5.8344502472616, [10] = 6.9935758191986, [11] = 8.1804316679761, [12] = 9.6057678975828, [13] = 11.025558071821, [14] = 12.461986412164, [15] = 13.992697693765, [16] = 15.878356518926, [17] = 17.558811295466, [18] = 19.372371400841, [19] = 21.418865831677, [20] = 23.481998428618, [21] = 25.617229745344, [22] = 27.785737394279, [23] = 30.392383417295, [24] = 32.721726671905, [25] = 35.356102971761, [26] = 38.012663493091, [27] = 40.66922401442, [28] = 43.46998197532, [29] = 46.315108379165, [30] = 49.39871516384, [31] = 52.510052225355, [32] = 55.82659333549, [33] = 58.993390950686, [34] = 62.287747839349, [35] = 65.981420714517, [36] = 69.56971853769, [37] = 73.119193973287, [38] = 76.890511623608, [39] = 81.271895364422, [40] = 84.654989138975}
,
[16] = {[1] = 0.022184221472248, [2] = 0.4714147062904, [3] = 1.014928132366, [4] = 1.5140731154966, [5] = 1.9300272681061, [6] = 2.5844618015441, [7] = 3.8434163701067, [8] = 4.170633636826, [9] = 5.8178120811572, [10] = 7.0323982067753, [11] = 8.1915237787124, [12] = 9.4393862365397, [13] = 11.064380459398, [14] = 12.323335027962, [15] = 14.048158247447, [16] = 15.789619633036, [17] = 17.66418634746, [18] = 19.450016175995, [19] = 20.026805934279, [20] = 23.232425937052, [21] = 24.042150020798, [22] = 27.885566390905, [23] = 30.115080648887, [24] = 33.736654804271, [25] = 34.823681656422, [26] = 38.228959652447, [27] = 40.552756851689, [28] = 41.417941489115, [29] = 46.320654434533, [30] = 49.099228173961, [31] = 52.193927069371, [32] = 53.280953921524, [33] = 59.154226556362, [34] = 62.415307112816, [35] = 63.563340574016, [36] = 66.691315801636, [37] = 73.058187364237, [38] = 76.690853630356, [39] = 77.644775153672, [40] = 81.649027129454}
, -- P&LE MP15AD #1594
[17] = {[1] = 0.32167121135082, [2] = 0.57678975828431, [3] = 0.97055968942096, [4] = 1.6083560567547, [5] = 2.2461524240884, [6] = 3.0614225632019, [7] = 3.8212321486344, [8] = 4.7973378934233, [9] = 5.8566344687338, [10] = 6.8050099366825, [11] = 8.1527013911352, [12] = 9.7277811156818, [13] = 11.064380459398, [14] = 12.633914128576, [15] = 14.147987244073, [16] = 15.662060359569, [17] = 17.542173129362, [18] = 19.322456902528, [19] = 21.136017007903, [20] = 23.371077321255, [21] = 25.489670471877, [22] = 27.680362342285, [23] = 30.214909645515, [24] = 32.727272727273, [25] = 35.156444978509, [26] = 37.846281832048, [27] = 40.580487128531, [28] = 43.447797753847, [29] = 46.015621389287, [30] = 49.431991496048, [31] = 52.687525997134, [32] = 55.593659010029, [33] = 58.732726348385, [34] = 62.054813513888, [35] = 65.670841613903, [36] = 69.431067153488, [37] = 72.925082035402, [38] = 76.82395895919, [39] = 80.301335675001, [40] = 84.699357581919}
,
[170] = {[1] = 0.0, [2] = 0.022184221472248, [3] = 0.14974349493958, [4] = 0.51578314923489, [5] = 1.064842630679, [6] = 1.8634746036882, [7] = 2.5123630817577, [8] = 3.0558765078339, [9] = 4.00979803115, [10] = 5.1911078245593, [11] = 6.1283911817722, [12] = 6.9048389333085, [13] = 8.635208208162, [14] = 9.655682395896, [15] = 11.940657207562, [16] = 12.988861672136, [17] = 14.164625410177, [18] = 15.623237971992, [19] = 17.420159911264, [20] = 19.633036003143, [21] = 21.807089707447, [22] = 23.931228913435, [23] = 26.30494061099, [24] = 27.54725701345, [25] = 30.697416462542, [26] = 32.960207052734, [27] = 35.589037297222, [28] = 38.961038961038, [29] = 41.978093081296, [30] = 42.832185607986, [31] = 47.291214123955, [32] = 49.69265609835, [33] = 52.998105097749, [34] = 56.298008041781, [35] = 58.194758977676, [36] = 62.215649119564, [37] = 67.867079539677, [38] = 69.858113416831, [39] = 74.866201414243, [40] = 77.323103942321}
,
[18] = {[1] = 0.26785714285711, [2] = 0.38183890577508, [3] = 0.75797872340424, [4] = 1.265197568389, [5] = 2.3993161094225, [6] = 3.1629939209726, [7] = 4.0064589665653, [8] = 4.9582066869301, [9] = 5.1405775075987, [10] = 9.7169452887537, [11] = 9.4034954407296, [12] = 8.4973404255319, [13] = 10.651595744681, [14] = 12.338525835866, [15] = 15.746580547112, [16] = 17.450607902736, [17] = 17.410714285714, [18] = 17.781155015198, [19] = 21.867401215805, [20] = 25.759878419453, [21] = 28.039513677812, [22] = 30.353343465046, [23] = 32.547492401216, [24] = 33.242781155015, [25] = 35.858662613982, [26] = 38.189589665653, [27] = 41.198708206687, [28] = 44.025455927052, [29] = 47.245440729484, [30] = 50.174772036474, [31] = 52.910334346504, [32] = 56.18161094225, [33] = 59.293313069909, [34] = 63.066109422492, [35] = 66.987082066869, [36] = 70.115881458967, [37] = 73.723404255319, [38] = 77.422112462006, [39] = 81.48556231003, [40] = 81.474164133738}
-- these engines have not been tested yet:
-- P&LE 2-8-0 #9378 (can't run through balloon)
-- NYC 2-8-4 Berk #9401 (can't run through balloon)
}
--
-- Average stopping distance is all of the individual engine stopping distances
-- above, averaged by "Layout.AvgStopDist()"
-- Deceleration set to 1 Smph/sec.
--
Layout.AverageStoppingDistance1 = {};
--
--[[------------------------------- Stopping Distance for 4 Smph/Sec -----------------------------------------------]]

-- All tables in Lua are 1 based, that is, StoppingDistance[EngineNo][1] is the first item in the list
-- Measured stopping distance in inches (from "Deceleration Test.lua") for scale speeds from 1 Smph to 40 Smph
-- Table contains a table for each Engine Number
-- Deceleration set to 4 Smph/sec. Unless specified, each engine is pulling two freight
-- cars and a caboose.
-- These table constructors were created using "Deceleration Test.lua"
Layout.StoppingDistance4 = {
[ 1] = {[1] = 0.21038062283738, [2] = 0.42076124567475, [3] = 0.70311418685122, [4] = 0.9134948096886, [5] = 1.4283737024222, [6] = 1.8934256055363, [7] = 2.319723183391, [8] = 2.801384083045, [9] = 3.1612456747405, [10] = 3.764705882353, [11] = 4.3017301038062, [12] = 4.8442906574394, [13] = 5.3259515570934, [14] = 5.9792387543252, [15] = 6.6269896193772, [16] = 7.4795847750866, [17] = 8.1107266435987, [18] = 8.4761245674739, [19] = 9.3951557093425, [20] = 9.9266435986159, [21] = 10.740484429066, [22] = 11.897577854672, [23] = 12.429065743945, [24] = 13.503114186851, [25] = 14.101038062284, [26] = 15.346712802768, [27] = 16.293425605536, [28] = 16.863667820069, [29] = 17.799307958478, [30] = 18.950865051903, [31] = 19.786851211073, [32] = 20.700346020761, [33] = 22.029065743945, [34] = 23.280276816609, [35] = 24.310034602076, [36] = 24.874740484429, [37] = 26.823529411765, [38] = 27.930795847751, [39] = 28.622837370242, [40] = 30.615916955018}
,
[ 2] = {[1] = 0.17567341475633, [2] = 0.41659695499419, [3] = 0.78802074619334, [4] = 1.0490212481183, [5] = 1.360214154258, [6] = 1.6613685795547, [7] = 2.1131002175004, [8] = 2.4995817299651, [9] = 3.1320060230886, [10] = 3.377948803748, [11] = 4.1358541074113, [12] = 4.5825665049355, [13] = 5.0844905470977, [14] = 5.6817801572691, [15] = 6.2288773632258, [16] = 6.5852434331605, [17] = 7.3431487368244, [18] = 8.2014388489208, [19] = 8.9041325079473, [20] = 9.6921532541406, [21] = 10.24928894094, [22] = 10.911828676594, [23] = 12.196754224528, [24] = 12.412581562657, [25] = 13.607160783002, [26] = 14.149238748536, [27] = 15.027605822319, [28] = 15.815626568512, [29] = 16.92487870169, [30] = 17.778149573364, [31] = 19.429479672076, [32] = 19.419441191234, [33] = 20.789693826334, [34] = 22.009369248787, [35] = 23.464948971056, [36] = 24.403546929899, [37] = 25.301990965367, [38] = 26.772628408901, [39] = 27.701187886899, [40] = 28.398862305504}
,
[ 3] = {[1] = 0.15501730103806, [2] = 0.36539792387543, [3] = 0.52595155709342, [4] = 0.81384083044983, [5] = 1.2235294117647, [6] = 1.5114186851211, [7] = 1.8934256055363, [8] = 2.2256055363322, [9] = 2.718339100346, [10] = 3.0892733564014, [11] = 3.6982698961938, [12] = 4.2519031141868, [13] = 4.7058823529411, [14] = 5.2705882352941, [15] = 5.840830449827, [16] = 6.3114186851211, [17] = 7.1916955017301, [18] = 7.7065743944637, [19] = 8.2546712802769, [20] = 9.2678200692041, [21] = 9.7882352941175, [22] = 10.779238754325, [23] = 11.698269896194, [24] = 12.213148788927, [25] = 13.060207612457, [26] = 13.403460207613, [27] = 15.20276816609, [28] = 15.512802768166, [29] = 16.304498269896, [30] = 17.212456747405, [31] = 18.923148096886, [32] = 19.620761245675, [33] = 20.406920415225, [34] = 21.901730103807, [35] = 22.798615916955, [36] = 23.579238754325, [37] = 24.564705882353, [38] = 25.882352941177, [39] = 26.723875432526, [40] = 28.307266435986}
,
-- Engine 7 P&LE GP7 #1501 is pulling three P&LE passenger cars
[ 7] = {[1] = 0.16055363321799, [2] = 0.38754325259516, [3] = 0.55916955017301, [4] = 0.87474048442906, [5] = 1.2124567474048, [6] = 1.522491349481, [7] = 1.9875432525952, [8] = 2.2532871972318, [9] = 2.7515570934256, [10] = 3.2110726643598, [11] = 3.6871972318339, [12] = 4.2574394463668, [13] = 4.7833910034602, [14] = 5.3259515570934, [15] = 5.8740484429066, [16] = 6.4442906574395, [17] = 7.1474048442907, [18] = 7.8782006920416, [19] = 8.4207612456747, [20] = 9.0408304498271, [21] = 13.674740484429, [22] = 10.507958477509, [23] = 11.355017301038, [24] = 12.246366782007, [25] = 12.888581314879, [26] = 13.940484429066, [27] = 15.16955017301, [28] = 15.451903114187, [29] = 16.586851211073, [30] = 17.400692041523, [31] = 18.297577854671, [32] = 19.249826989619, [33] = 20.689273356402, [34] = 21.525259515571, [35] = 22.36678200692, [36] = 23.557093425605, [37] = 24.592387543253, [38] = 25.494809688581, [39] = 27.399307958477, [40] = 28.102422145328}
,
-- Engine 13 P&LE GP7 #1500 is pulling three P&LE passenger cars
[13] = {[1] = 0.033217993079574, [2] = 0.36539792387542, [3] = 0.54809688581314, [4] = 0.8636678200692, [5] = 1.201384083045, [6] = 1.516955017301, [7] = 1.9487889273356, [8] = 2.2975778546712, [9] = 2.7847750865052, [10] = 3.2166089965398, [11] = 3.6650519031142, [12] = 4.3349480968858, [13] = 4.7723183391004, [14] = 5.364705882353, [15] = 5.9072664359863, [16] = 6.5771626297578, [17] = 7.2858131487888, [18] = 7.7840830449826, [19] = 8.3930795847751, [20] = 9.0131487889274, [21] = 9.9432525951558, [22] = 10.723875432526, [23] = 11.393771626298, [24] = 12.02491349481, [25] = 12.921799307958, [26] = 13.768858131488, [27] = 14.981314878893, [28] = 15.9723183391, [29] = 16.559169550173, [30] = 17.904498269896, [31] = 18.768166089966, [32] = 19.487889273357, [33] = 20.611764705882, [34] = 21.741176470588, [35] = 22.737716262976, [36] = 23.357785467128, [37] = 24.453979238754, [38] = 26.541176470589, [39] = 27.426989619377, [40] = 28.501038062283}
,
[15] = {[1] = 0.14419743957049, [2] = 0.34385543282365, [3] = 0.57678975828548, [4] = 0.85963858205854, [5] = 1.1979479595143, [6] = 1.5584415584417, [7] = 2.0021259878913, [8] = 2.3459814207138, [9] = 2.8340342931091, [10] = 3.1889818366686, [11] = 3.7935018717937, [12] = 4.3148310763965, [13] = 4.775153671951, [14] = 5.3630355409712, [15] = 5.9176410777838, [16] = 6.6552664417424, [17] = 6.9547534316215, [18] = 7.9530433978838, [19] = 8.3745436058594, [20] = 9.1953598003414, [21] = 9.666774506633, [22] = 10.376669593752, [23] = 11.313952950964, [24] = 12.217959975968, [25] = 13.033230115081, [26] = 13.865138420299, [27] = 14.669316448676, [28] = 15.517862919998, [29] = 16.771271433193, [30] = 17.686370568933, [31] = 18.923140916023, [32] = 19.289180570319, [33] = 20.792161575079, [34] = 21.485418496095, [35] = 22.189767527846, [36] = 23.548551093036, [37] = 24.35827517678, [38] = 25.988815455006, [39] = 27.336506909461, [40] = 28.456810093822}
}

-- Average stopping distance is all of the individual engine stopping distances
-- above, averaged by "Layout.AvgStopDist()"
-- Deceleration set to 4 Smph/sec.
Layout.AverageStoppingDistance4 = {};

--[[------------------------------- Starting Distance for 1 Smph/Sec -----------------------------------------------]]

-- Measured starting distance in inches (from "Acceleration Test.lua") for scale speeds from 1 Smph to 40 Smph
-- Table contains a table for each Engine Number.
-- Acceleration set to 1 Smph/sec.
-- These table constructors were created using "Acceleration Test.lua"

Layout.StartingDistance1 = {
[12] = {[1] = 0.0, [2] = 0.0055460553685016, [3] = 0.0055460553679154, [4] = 0.56015159217997, [5] = 1.0260202431025, [6] = 1.3865138420299, [7] = 2.1130470952537, [8] = 3.1723436705643, [9] = 3.8656005915789, [10] = 4.7307852290054, [11] = 6.1783056800852, [12] = 7.2764246429729, [13] = 8.0085039515641, [14] = 9.3062809077044, [15] = 10.964551462772, [16] = 12.728197069834, [17] = 13.815223921986, [18] = 16.150113231964, [19] = 18.008041780284, [20] = 19.982437491334, [21] = 22.311780745944, [22] = 23.787031473864, [23] = 25.123630817581, [24] = 27.386421407773, [25] = 30.564311133706, [26] = 34.163701067615, [27] = 35.827517678051, [28] = 38.068124046772, [29] = 41.595415260896, [30] = 45.139344641124, [31] = 51.977630910015, [32] = 50.829597448814, [33] = 53.38078291815, [34] = 55.00023108564, [35] = 60.551832509127, [36] = 64.273235661136, [37] = 65.842769330314, [38] = 70.262975458706, [39] = 73.302213800435, [40] = 76.713037851828}
,
[120] = {[1] = 0.0055460553681194, [2] = 0.0, [3] = 0.24402643619725, [4] = 0.53242131533946, [5] = 1.1757637380413, [6] = 1.6360863335952, [7] = 2.2461524240884, [8] = 2.9227711789989, [9] = 4.2593705227157, [10] = 4.7529694504783, [11] = 6.4056939501779, [12] = 7.3097009751814, [13] = 8.3024448860748, [14] = 10.271294541757, [15] = 11.108748902343, [16] = 13.332717104959, [17] = 13.732033091464, [18] = 15.650968248833, [19] = 17.869390396081, [20] = 20.720062855294, [21] = 22.417155797939, [22] = 24.563479225401, [23] = 25.317742755465, [24] = 28.467902204557, [25] = 31.690160373434, [26] = 33.315154596293, [27] = 36.149188889402, [28] = 38.72255858021, [29] = 41.706336368258, [30] = 45.3001802468, [31] = 45.993437167814, [32] = 50.335998521052, [33] = 52.177288903268, [34] = 56.525396311873, [35] = 59.536904376762, [36] = 63.247215418034, [37] = 67.173822618662, [38] = 69.209224938762, [39] = 72.841891204881, [40] = 77.38411055137}
,
[121] = {[1] = 0.0055460553681194, [2] = 0.011092110736238, [3] = 0.34940148819152, [4] = 0.56015159218006, [5] = 3.8267782040024, [6] = 1.8357443268475, [7] = 2.6121920783843, [8] = 3.2333502796136, [9] = 4.6365022877478, [10] = 4.736331284374, [11] = 6.1616675139807, [12] = 6.8771086564681, [13] = 8.8570504228868, [14] = 9.6556823958959, [15] = 11.114294957711, [16] = 12.855756343301, [17] = 14.342099181957, [18] = 15.623237971992, [19] = 17.93039700513, [20] = 19.389009566946, [21] = 21.213661783057, [22] = 24.574571336137, [23] = 26.903914590747, [24] = 29.227711789989, [25] = 31.712344594907, [26] = 34.385543282341, [27] = 36.099274391089, [28] = 38.694828303369, [29] = 41.517770485742, [30] = 43.392337200166, [31] = 47.435411563526, [32] = 50.380366963997, [33] = 52.249387623053, [34] = 58.094929981051, [35] = 59.503628044553, [36] = 64.212229052086, [37] = 66.402920922494, [38] = 70.412718953645, [39] = 73.213476914545, [40] = 78.803900725609}
,
[122] = {[1] = 0.0, [2] = 0.016638166103746, [3] = 0.87073069279437, [4] = 0.77090169616838, [5] = 1.1646716273056, [6] = 2.8173961270054, [7] = 2.4569025280768, [8] = 3.1279752276192, [9] = 4.4091140176557, [10] = 5.4129500392848, [11] = 6.0230161297777, [12] = 7.8032999029442, [13] = 9.0067939178252, [14] = 9.7998798354676, [15] = 11.546887276424, [16] = 12.994407727504, [17] = 13.676572537782, [18] = 16.638166104358, [19] = 17.580995516938, [20] = 19.283634514951, [21] = 21.513148772935, [22] = 24.141979017424, [23] = 25.794703517123, [24] = 28.967047187688, [25] = 31.196561445672, [26] = 33.453805980497, [27] = 36.182465221612, [28] = 37.879558164255, [29] = 41.478948098165, [30] = 43.819383463512, [31] = 48.766464851874, [32] = 52.931552433331, [33] = 69.597448814531, [34] = 55.676849840551, [35] = 58.388870915561, [36] = 61.988260849471, [37] = 67.573138605167, [38] = 69.614086980635, [39] = 74.294957711328, [40] = 78.570966400148}
}
-- Average starting distance is all of the individual engine starting distances
-- above, averaged by "Layout.AvgStartDist()"
-- Acceleration set to 1 Smph/sec.
--
Layout.AverageStartingDistance1 = {};

--[[------------------------------- Starting Distance for 4 Smph/Sec -----------------------------------------------]]

-- All tables in Lua are 1 based, that is, StartingDistance[EngineNo][1] is the first item in the list
-- Measured starting distance in inches (from "Acceleration Test.lua") for scale speeds from 1 Smph to 40 Smph
-- Table contains a table for each Engine Number
-- Acceleration set to 4 Smph/sec. Unless specified, each engine is pulling two freight
-- cars and a caboose.
-- These table constructors were created using "Acceleration Test.lua"

Layout.StartingDistance4 = {
[12] = {[1] = 0.016638166103746, [2] = 0.0055460553679154, [3] = 0.077644775153161, [4] = 0.17747377177798, [5] = 0.27730276840515, [6] = 0.7986319730103, [7] = 0.78199380690421, [8] = 0.95392152331662, [9] = 1.6139021121244, [10] = 1.6749087211714, [11] = 1.6416323889639, [12] = 2.6787447428016, [13] = 2.8784027360536, [14] = 2.9116790682611, [15] = 3.8489624254763, [16] = 3.9155150898913, [17] = 4.1373573046173, [18] = 4.1872718029285, [19] = 5.5405093127515, [20] = 5.6458843647442, [21] = 5.8178120811566, [22] = 7.7367472385281, [23] = 7.6369182419009, [24] = 9.0511623607709, [25] = 9.2397282432871, [26] = 10.071636548505, [27] = 11.502518833481, [28] = 10.981189628876, [29] = 11.585709664002, [30] = 12.883486620141, [31] = 13.42145399085, [32] = 13.537921153579, [33] = 20.991819568334, [34] = 15.717520913251, [35] = 15.573323473681, [36] = 17.974765448075, [37] = 19.594213615565, [38] = 19.594213615565, [39] = 20.14327309701, [40] = 22.223043860053}
,
[17] = {[1] = 0.0, [2] = 0.0055460553681198, [3] = 0.011092110736238, [4] = 0.14974349493923, [5] = 0.14974349493923, [6] = 0.15528955030735, [7] = 0.78199380690485, [8] = 0.9982899662615, [9] = 1.5695336691778, [10] = 1.902296991265, [11] = 1.8412903822157, [12] = 2.0076720432592, [13] = 2.834034293109, [14] = 2.8118500716366, [15] = 3.8822387576836, [16] = 4.2316402458751, [17] = 4.2371863012432, [18] = 5.2798447104497, [19] = 5.6070619771687, [20] = 5.2077459906641, [21] = 7.1821417017147, [22] = 7.5315431899063, [23] = 7.6313721865323, [24] = 7.060128483616, [25] = 9.3395572399131, [26] = 9.6224060636873, [27] = 10.714978971207, [28] = 11.435966169062, [29] = 11.652262328419, [30] = 11.61898599621, [31] = 15.024263992235, [32] = 13.759763368304, [33] = 14.025974025974, [34] = 16.349771225216, [35] = 16.355317280584, [36] = 16.33867911448, [37] = 19.128344964644, [38] = 19.971345380598, [39] = 19.427831954522, [40] = 22.311780745944}
}

-- Average starting distance is all of the individual engine starting distances
-- above, averaged by "Layout.AvgStartDist()"
-- Acceleration set to 4 Smph/sec.
Layout.AverageStartingDistance4 = {};

--[[-------------------------------Stations-----------------------------------------------]]
ALIQUIPPA = 101;
COLLEGE = 102;
STRUTHERS = 103;
--J_L = 104;
MCKEES_ROCKS = 105;
WEST_ALIQUIPPA = 106;
MCKEES_ROCKS_YARD1 = 107
MCKEES_ROCKS_YARD2 = 108
YOUNGSTOWN = 109
--J_LTHRU = 110
--MILL = 111
YOUNGSTOWN_YARD1 = 112
YOUNGSTOWN_YARD2 = 113
NEWCASTLE = 114

--[[-------------------------------Station Names-------------------------------------------]]

StationNames = {
[ALIQUIPPA] = "Aliquippa",
[COLLEGE] = "College",
[STRUTHERS] = "Struthers",
--[J_L] = "J&L",
[MCKEES_ROCKS] = "McKees Rocks",
[WEST_ALIQUIPPA] = "West Aliquippa",
[MCKEES_ROCKS_YARD1] = "McKees Rocks Yard 1",
[MCKEES_ROCKS_YARD2] = "McKees Rocks Yard 2",
[YOUNGSTOWN] = "Youngstown",
--[J_LTHRU] = "J&L Thru",
--[MILL] = "Mill",
[YOUNGSTOWN_YARD1] = "Youngstown Yard 1",
[YOUNGSTOWN_YARD2] = "Youngstown Yard 2",
[NEWCASTLE] = "New Castle",
};
--[[------------------Layout Tag Detectors & Readers-----------------------------------]]
--
ALIQUIPPA_WEST_DETECTOR = 1;
ALIQUIPPA_WEST_READER = 0;

ALIQUIPPA_EAST_DETECTOR = 5;
ALIQUIPPA_EAST_READER = 1;

COLLEGE_WEST_DETECTOR = 2;
COLLEGE_WEST_READER = 1;

COLLEGE_EAST_DETECTOR = 4;
COLLEGE_EAST_READER = 1;

STRUTHERS_WEST_DETECTOR = 4;
STRUTHERS_WEST_READER = 0;

STRUTHERS_EAST_DETECTOR = 2;
STRUTHERS_EAST_READER = 0;

YOUNGSTOWN_YARD_DETECTOR = 5;
YOUNGSTOWN_YARD_READER = 0;

MCKEES_ROCKS_YARD_DETECTOR = 3;
MCKEES_ROCKS_YARD_READER = 0;

MONACA_EAST_DETECTOR = 6;
MONACA_EAST_READER = 0;

MONACA_WEST_DETECTOR = 6;
MONACA_WEST_READER = 1;

LOWELLVILLE_DETECTOR = 9;
LOWELLVILLE_READER = 0;

-- Detector 7 not used

-- Detector 8 not used

WEST_ALIQUIPPA_DETECTOR = 10;
WEST_ALIQUIPPA_READER = 0;

FALLSTON_DETECTOR = 10;
FALLSTON_READER = 1;

--NEW_DETECTOR = 100;
--NEW_READER = 0;

-- Combined values using the Detector # and the Reader #
ALIQUIPPA_WEST_TAG = ((ALIQUIPPA_WEST_DETECTOR * 10) + ALIQUIPPA_WEST_READER);
ALIQUIPPA_EAST_TAG = ((ALIQUIPPA_EAST_DETECTOR * 10) + ALIQUIPPA_EAST_READER);
COLLEGE_WEST_TAG = ((COLLEGE_WEST_DETECTOR * 10) + COLLEGE_WEST_READER);
COLLEGE_EAST_TAG = ((COLLEGE_EAST_DETECTOR * 10) + COLLEGE_EAST_READER);
STRUTHERS_WEST_TAG = ((STRUTHERS_WEST_DETECTOR * 10) + STRUTHERS_WEST_READER);
STRUTHERS_EAST_TAG = ((STRUTHERS_EAST_DETECTOR * 10) + STRUTHERS_EAST_READER);
YOUNGSTOWN_YARD_TAG = ((YOUNGSTOWN_YARD_DETECTOR * 10) + YOUNGSTOWN_YARD_READER);
MCKEES_ROCKS_YARD_TAG = ((MCKEES_ROCKS_YARD_DETECTOR * 10) + MCKEES_ROCKS_YARD_READER);
MONACA_EAST_TAG = ((MONACA_EAST_DETECTOR * 10) + MONACA_EAST_READER);
MONACA_WEST_TAG = ((MONACA_WEST_DETECTOR * 10) + MONACA_WEST_READER);
LOWELLVILLE_TAG = ((LOWELLVILLE_DETECTOR * 10) + LOWELLVILLE_READER);
WEST_ALIQUIPPA_TAG = ((WEST_ALIQUIPPA_DETECTOR * 10) + WEST_ALIQUIPPA_READER);
FALLSTON_TAG = ((FALLSTON_DETECTOR * 10) + FALLSTON_READER);
--NEW_TAG = ((NEW_DETECTOR * 10) + NEW_READER);

--[[-------------------------------Switches AIU and Channel-----------------------------]]

COLLEGE_WEST = 1;
COLLEGE_WEST_AIU = 1;
COLLEGE_WEST_CHAN = 1;

COLLEGE_EAST = 2;
COLLEGE_EAST_AIU = 1;
COLLEGE_EAST_CHAN = 2;

YOUNGSTOWN_YARD = 3;
YOUNGSTOWN_YARD_AIU = 1;
YOUNGSTOWN_YARD_CHAN = 3;

MCKEES_ROCKS_YARD = 4;
MCKEES_ROCKS_YARD_AIU = 1;
MCKEES_ROCKS_YARD_CHAN = 4;

ALIQUIPPA_EAST = 5;
ALIQUIPPA_EAST_AIU = 1;
ALIQUIPPA_EAST_CHAN = 5;

J_L_SLAG_TRACK = 6;
J_L_SLAG_TRACK_AIU = 1;
J_L_SLAG_TRACK_CHAN = 6;

HOLE_TRACK = 7;
HOLE_TRACK_AIU = 1;
HOLE_TRACK_CHAN = 7

J_L_MILL_TRACK = 8;
J_L_MILL_TRACK_AIU = 1;
J_L_MILL_TRACK_CHAN = 8;

ALIQUIPPA_WEST = 9;
ALIQUIPPA_WEST_AIU = 1;
ALIQUIPPA_WEST_CHAN = 9;

ALIQUIPPA_TEAM_TRACK = 10;
ALIQUIPPA_TEAM_TRACK_AIU = 1;
ALIQUIPPA_TEAM_TRACK_CHAN = 10;

BYPASS_WEST = 11;
BYPASS_WEST_AIU = 2;
BYPASS_WEST_CHAN = 1;

BYPASS_EAST = 12;
BYPASS_EAST_AIU = 2;
BYPASS_EAST_CHAN = 2;

--AIU2 CHAN3 NC
--AIU2 CHAN4 NC

STRUTHERS_WEST = 15;
STRUTHERS_WEST_AIU = 2;
STRUTHERS_WEST_CHAN = 5;

STRUTHERS_EAST = 16;
STRUTHERS_EAST_AIU = 2;
STRUTHERS_EAST_CHAN = 6;

CAMPBELL_YARD = 17;
CAMPBELL_YARD_AIU = 2;
CAMPBELL_YARD_CHAN = 7;

MONACA_CUTOFF = 18;
MONACA_CUTOFF_AIU = 2;
MONACA_CUTOFF_CHAN = 8;

-- AIU2 CHAN9 - two switches connected to this channel
BALLOON = 19;
BALLOON_AIU = 2;
BALLOON_CHAN = 9;

PM_MOORE_TRESTLE = 20;
PM_MOORE_TRESTLE_AIU = 2;
PM_MOORE_TRESTLE_CHAN = 10;


--[[-------------------------------Switch Names-------------------------------------------]]

SwitchName = {
[COLLEGE_WEST] = "College West",
[COLLEGE_EAST] = "College East",
[YOUNGSTOWN_YARD] = "Youngstown Yard",
[MCKEES_ROCKS_YARD] = "McKees Rocks Yard",
[ALIQUIPPA_EAST] = "Aliquippa East",
[J_L_SLAG_TRACK] = "J&L Slag Track",
[HOLE_TRACK] = "Hole",
[J_L_MILL_TRACK] = "J&L Mill",
[ALIQUIPPA_WEST] = "Aliquippa West",
[ALIQUIPPA_TEAM_TRACK] = "Aliquippa Team",
[BYPASS_WEST] = "Bypass West",
[BYPASS_EAST] = "Bypass East",
[STRUTHERS_WEST] = "Struthers West",
[STRUTHERS_EAST] = "Struthers East",
[CAMPBELL_YARD] = "Campbell Yard",
[MONACA_CUTOFF] = "Monaca Cutoff",
[PM_MOORE_TRESTLE] = "PM Moore Trestle",
[BALLOON] = "Lionel-Ives Switch",
};

--[[----------------------------------Superior Direction-----------------------------------]]
SuperiorDirection = WB;
--[[----------------------------------Blocks-------------------------------------------]]
-- Real physical blocks are numbered from 1 to 100 but must be sequential starting at 1
FIRST_BLOCK = 1; -- FIRST_BLOCK is the lowest physical block number
YOUNGSTOWN_YARD2_BLOCK = 1;
YOUNGSTOWN_YARD1_BLOCK = 2;
YOUNGSTOWN_BLOCK = 3;
STRUTHERS_THRU_TRACK = 4
STRUTHERS_BLOCK = 4;
STRUTHERS_STATION_TRACK = 5;
NEW_CASTLE_BLOCK = 6;
COLLEGE_THRU_TRACK = 7;
COLLEGE_BLOCK = 7;
COLLEGE_STATION_TRACK = 8;
ALIQUIPPA_BLOCK = 9;
J_L_PASSING_TRACK = 10;
J_L_THRU_TRACK = 11;
J_L_BLOCK = 11;
MCKEES_ROCKS_BLOCK = 12;
MCKEES_ROCKS_YARD2_BLOCK = 13;
MCKEES_ROCKS_YARD1_BLOCK = 14;
LIONEL_IVES_BLOCK = 15; -- AKA BALLOON_TRACK
BALLOON_TRACK = 15; -- AKA LIONEL_IVES_BLOCK
MONACA_BLOCK = 16;
BEAVER_FALLS_BLOCK = 17;
LOWELLVILLE_BLOCK = 18;
WEST_ALIQUIPPA_BLOCK = 19;
FALLSTON_BLOCK = 20;
--NEW_BLOCK = 0;
LAST_BLOCK = 20; -- LAST_BLOCK is the highest physical block number used

-- Simulated blocks can be numbered from 101 to 200
CLEVELAND_EL = 101;
ASHTABULA_NYC = 103;
CUMBERLAND_B_O = 102;
POINTS_SOUTH_N_W = 104;
--[[----------------------------------Block Names---------------------------------------]]

BlockName = {
[YOUNGSTOWN_YARD2_BLOCK] = "Youngstown Yard 2 Block",
[YOUNGSTOWN_YARD1_BLOCK] = "Youngstown Yard 1 Block",
[YOUNGSTOWN_BLOCK] = "Youngstown Block",
[STRUTHERS_THRU_TRACK] = "Struthers Thru Track",
[STRUTHERS_STATION_TRACK] = "Struthers Station Track",
[NEW_CASTLE_BLOCK] = "New Castle Block",
[COLLEGE_THRU_TRACK] = "College Thru Track",
[COLLEGE_STATION_TRACK] = "College Station Track",
[ALIQUIPPA_BLOCK] = "Aliquippa Block",
[J_L_PASSING_TRACK] = "J&L Passing Track",
[J_L_THRU_TRACK] = "J&L Thru Track",
[MCKEES_ROCKS_BLOCK] = "McKees Rocks Block",
[MCKEES_ROCKS_YARD2_BLOCK] = "McKees Rocks Yard 2 Block",
[MCKEES_ROCKS_YARD1_BLOCK] = "McKees Rocks Yard 1 Block",
[LIONEL_IVES_BLOCK] = "Lionel-Ives Block", -- AKA Balloon Track
[CLEVELAND_EL] = "Cleveland via Erie-Lackawanna (simulated)",
[ASHTABULA_NYC] = "Ashtabula via New York Central (simulated)",
[CUMBERLAND_B_O] = "Cumberland via Baltimore & Ohio (simulated)",
[POINTS_SOUTH_N_W] = "South via Norfolk & Western (simulated)",
[MONACA_BLOCK] = "Monaca Block",
[BEAVER_FALLS_BLOCK] = "Beaver Falls Block",
[LOWELLVILLE_BLOCK] = "Lowellville Block",
[WEST_ALIQUIPPA_BLOCK] = "West Aliquippa Block",
[FALLSTON_BLOCK] = "Fallston Block",
--[NEW_BLOCK] = "New Block",
};

--[[-------------------------Tags and which block is on each side of the tag -----------------------]]
NextBlock = {
[EB] = {
[YOUNGSTOWN_YARD_TAG] = YOUNGSTOWN_BLOCK,
[STRUTHERS_WEST_TAG] = STRUTHERS_STATION_TRACK,
[STRUTHERS_EAST_TAG] = LOWELLVILLE_BLOCK,
[LOWELLVILLE_TAG] = NEW_CASTLE_BLOCK,
[COLLEGE_WEST_TAG] = COLLEGE_STATION_TRACK,
[COLLEGE_EAST_TAG] = BEAVER_FALLS_BLOCK,
[FALLSTON_TAG] = FALLSTON_BLOCK,
[MONACA_WEST_TAG] = MONACA_BLOCK,
[MONACA_EAST_TAG] = WEST_ALIQUIPPA_BLOCK;
[WEST_ALIQUIPPA_TAG] = ALIQUIPPA_BLOCK,
[ALIQUIPPA_WEST_TAG] = J_L_PASSING_TRACK,
[ALIQUIPPA_EAST_TAG] = MCKEES_ROCKS_BLOCK,
[MCKEES_ROCKS_YARD_TAG] = BALLOON_TRACK,
--[NEW_TAG] = NEW_BLOCK,
},
[WB] = {
[YOUNGSTOWN_YARD_TAG] = BALLOON_TRACK,
[STRUTHERS_WEST_TAG] = YOUNGSTOWN_BLOCK,
[STRUTHERS_EAST_TAG] = STRUTHERS_THRU_TRACK,
[LOWELLVILLE_TAG] = LOWELLVILLE_BLOCK,
[COLLEGE_WEST_TAG] = NEW_CASTLE_BLOCK,
[COLLEGE_EAST_TAG] = COLLEGE_THRU_TRACK,
[FALLSTON_TAG] = BEAVER_FALLS_BLOCK,
[MONACA_WEST_TAG] = FALLSTON_BLOCK,
[MONACA_EAST_TAG] = MONACA_BLOCK,
[WEST_ALIQUIPPA_TAG] = WEST_ALIQUIPPA_BLOCK,
[ALIQUIPPA_WEST_TAG] = ALIQUIPPA_BLOCK,
[ALIQUIPPA_EAST_TAG] = J_L_THRU_TRACK,
[MCKEES_ROCKS_YARD_TAG] = MCKEES_ROCKS_BLOCK,
--[NEW_TAG] = NEW_BLOCK,
},
};

--[[---------------------------------Next Signals---------------------------------------]]
SignalNames = {
[1] = "LI",
[2] = "YG",
[5] = "YG",
[6] = "ST",
[9] = "ST",
[10] = "LO",
[11] = "LO",
[12] = "NC";
[13] = "NC",
[14] = "CO",
[17] = "CO",
[18] = "BF",
[19] = "BF",
[20] = "FS",
[21] = "FS",
[22] = "MO",
[25] = "MO",
[26] = "QA",
[27] = "QA",
[28] = "AL",
[29] = "AL",
[30] = "JL",
[33] = "JL",
[34] = "MK",
[37] = "MK",
[38] = "LI",
}
NextSignal = {
[EB] = {
[YOUNGSTOWN_BLOCK] = {
[HOMESIGNAL] = 6,
[DISTANTSIGNAL] = 10,
},
[STRUTHERS_BLOCK] = { -- AKA STRUTHERS_THRU_TRACK
[HOMESIGNAL] = 10,
[DISTANTSIGNAL] = 12,
},
[STRUTHERS_STATION_TRACK] = {
[HOMESIGNAL] = 10,
[DISTANTSIGNAL] = 12,
},
[LOWELLVILLE_BLOCK] = {
[HOMESIGNAL] = 12,
[DISTANTSIGNAL] = 14,
};
[NEW_CASTLE_BLOCK] = {
[HOMESIGNAL] = 14,
[DISTANTSIGNAL] = 18,
},
[COLLEGE_BLOCK] = { -- AKA COLLEGE_THRU_TRACK
[HOMESIGNAL] = 18,
[DISTANTSIGNAL] = 22,
},
[COLLEGE_STATION_TRACK] = {
[HOMESIGNAL] = 18,
[DISTANTSIGNAL] = 22,
},
[BEAVER_FALLS_BLOCK] = {
[HOMESIGNAL] = 20,
[DISTANTSIGNAL] = 22,
},
[FALLSTON_BLOCK] = {
[HOMESIGNAL] = 22,
[DISTANTSIGNAL] = 26,
},
[MONACA_BLOCK] = {
[HOMESIGNAL] = 26,
[DISTANTSIGNAL] = 28,
},
[WEST_ALIQUIPPA_BLOCK] = {
[HOMESIGNAL] = 28,
[DISTANTSIGNAL] = 30,
},
[ALIQUIPPA_BLOCK] = {
[HOMESIGNAL] = 30,
[DISTANTSIGNAL] = 34,
},
[J_L_BLOCK] = { -- AKA J_L_THRU_TRACK
[HOMESIGNAL] = 34,
[DISTANTSIGNAL] = 38,
},
[J_L_PASSING_TRACK] = {
[HOMESIGNAL] = 34,
[DISTANTSIGNAL] = 38,
},
[MCKEES_ROCKS_BLOCK] = {
[HOMESIGNAL] = 38,
[DISTANTSIGNAL] = 2,
},
[LIONEL_IVES_BLOCK] = {
[HOMESIGNAL] = 2,
[DISTANTSIGNAL] = 6,
},
[YOUNGSTOWN_YARD2_BLOCK] = {
[HOMESIGNAL] = 2,
[DISTANTSIGNAL] = 6,
},
[YOUNGSTOWN_YARD1_BLOCK] = {
[HOMESIGNAL] = 2,
[DISTANTSIGNAL] = 6,
},
[MCKEES_ROCKS_YARD2_BLOCK] = {
[HOMESIGNAL] = 0,
[DISTANTSIGNAL] = 0,
},
[MCKEES_ROCKS_YARD1_BLOCK] = {
[HOMESIGNAL] = 0,
[DISTANTSIGNAL] = 0,
},
--[NEW_BLOCK] = {
-- [HOMESIGNAL] = 0,
-- [DISTANTSIGNAL] = 0,
-- },
},
[WB] = {
[LIONEL_IVES_BLOCK] = {
[HOMESIGNAL] = 37,
[DISTANTSIGNAL] = 33,
},
[MCKEES_ROCKS_BLOCK] = {
[HOMESIGNAL] = 33,
[DISTANTSIGNAL] = 29,
},
[J_L_BLOCK] = { -- AKA J_L_THRU_TRACK
[HOMESIGNAL] = 29,
[DISTANTSIGNAL] = 27,
},
[J_L_PASSING_TRACK] = {
[HOMESIGNAL] = 29,
[DISTANTSIGNAL] = 27,
},
[ALIQUIPPA_BLOCK] = {
[HOMESIGNAL] = 27,
[DISTANTSIGNAL] = 25,
},
[WEST_ALIQUIPPA_BLOCK] = {
[HOMESIGNAL] = 25,
[DISTANTSIGNAL] = 21,
},
[MONACA_BLOCK] = {
[HOMESIGNAL] = 21,
[DISTANTSIGNAL] = 19,
},
[FALLSTON_BLOCK] = {
[HOMESIGNAL] = 19,
[DISTANTSIGNAL] = 17,
},
[BEAVER_FALLS_BLOCK] = {
[HOMESIGNAL] = 17,
[DISTANTSIGNAL] = 13,
},
[COLLEGE_BLOCK] = { -- AKA COLLEGE_THRU_TRACK
[HOMESIGNAL] = 13,
[DISTANTSIGNAL] = 11,
},
[COLLEGE_STATION_TRACK] = {
[HOMESIGNAL] = 13,
[DISTANTSIGNAL] = 11,
},
[NEW_CASTLE_BLOCK] = {
[HOMESIGNAL] = 11,
[DISTANTSIGNAL] = 9,
},
[LOWELLVILLE_BLOCK] = {
[HOMESIGNAL] = 9,
[DISTANTSIGNAL] = 5,
},
[STRUTHERS_BLOCK] = { -- AKA STRUTHERS_THRU_TRACK
[HOMESIGNAL] = 5,
[DISTANTSIGNAL] = 1,
},
[STRUTHERS_STATION_TRACK] = {
[HOMESIGNAL] = 5,
[DISTANTSIGNAL] = 1,
},
[YOUNGSTOWN_BLOCK] = {
[HOMESIGNAL] = 1,
[DISTANTSIGNAL] = 37,
},
[MCKEES_ROCKS_YARD2_BLOCK] = {
[HOMESIGNAL] = 37,
[DISTANTSIGNAL] = 33,
},
[MCKEES_ROCKS_YARD1_BLOCK] = {
[HOMESIGNAL] = 37,
[DISTANTSIGNAL] = 33,
},
[YOUNGSTOWN_YARD2_BLOCK] = {
[HOMESIGNAL] = 0,
[DISTANTSIGNAL] = 0,
},
[YOUNGSTOWN_YARD1_BLOCK] = {
[HOMESIGNAL] = 0,
[DISTANTSIGNAL] = 0,
},
--[NEW_BLOCK] = {
-- [HOMESIGNAL] = 0,
-- [DISTANTSIGNAL] = 0,
-- },
}
}


--[[---------------------------------Block Speeds---------------------------------------]]
-- Speeds
NORMAL_SPEED = 30;
-- AsClear
LIMITED_SPEED = 25;
-- AsApproachLimited
-- AsLimitedClear
-- AsLimitedApproach
MEDIUM_SPEED = 20;
-- AsApproachMedium
-- AsMediumClear
-- AsApproach
-- AsMediumApproach
SLOW_SPEED = 15;
-- AsApproachSlow
-- AsSlowClear
-- AsSlowApproach
RESTRICTED_SPEED = 10;
-- AsRestricting
-- AsStopandProceed

RAMMING_SPEED = 15; -- speed in Smph that helps assure successful coupling
--[[---------------------------------------------------------------------------------------]]
BlockSpeed = {
[EB] = { -- "Slow" Freight
[YOUNGSTOWN_YARD2_BLOCK] = RESTRICTED_SPEED,
[YOUNGSTOWN_YARD1_BLOCK] = RESTRICTED_SPEED,
[YOUNGSTOWN_BLOCK] = MEDIUM_SPEED,
[STRUTHERS_THRU_TRACK] = MEDIUM_SPEED,
[STRUTHERS_STATION_TRACK] = SLOW_SPEED,
[NEW_CASTLE_BLOCK] = LIMITED_SPEED,
[COLLEGE_THRU_TRACK] = MEDIUM_SPEED,
[COLLEGE_STATION_TRACK] = SLOW_SPEED,
[ALIQUIPPA_BLOCK] = LIMITED_SPEED,
[J_L_PASSING_TRACK] = SLOW_SPEED,
[J_L_THRU_TRACK] = MEDIUM_SPEED,
[MCKEES_ROCKS_BLOCK] = SLOW_SPEED,
[MCKEES_ROCKS_YARD2_BLOCK] = RESTRICTED_SPEED,
[MCKEES_ROCKS_YARD1_BLOCK] = RESTRICTED_SPEED,
[CLEVELAND_EL] = 0,
[ASHTABULA_NYC] = 0,
[CUMBERLAND_B_O] = 0,
[POINTS_SOUTH_N_W] = 0,
[LIONEL_IVES_BLOCK] = SLOW_SPEED, -- very slow through here in case of stop
[MONACA_BLOCK] = SLOW_SPEED,
[BEAVER_FALLS_BLOCK] = LIMITED_SPEED,
[LOWELLVILLE_BLOCK] = LIMITED_SPEED,
[WEST_ALIQUIPPA_BLOCK] = LIMITED_SPEED,
[FALLSTON_BLOCK] = LIMITED_SPEED,
--[NEW_BLOCK] = LIMITED_SPEED,
},
[WB] = { -- "Fast" Freight
[YOUNGSTOWN_YARD2_BLOCK] = RESTRICTED_SPEED,
[YOUNGSTOWN_YARD1_BLOCK] = RESTRICTED_SPEED,
[YOUNGSTOWN_BLOCK] = LIMITED_SPEED,
[STRUTHERS_THRU_TRACK] = MEDIUM_SPEED,
[STRUTHERS_STATION_TRACK] = SLOW_SPEED,
[NEW_CASTLE_BLOCK] = LIMITED_SPEED,
[COLLEGE_THRU_TRACK] = MEDIUM_SPEED,
[COLLEGE_STATION_TRACK] = SLOW_SPEED,
[ALIQUIPPA_BLOCK] = NORMAL_SPEED,
[J_L_PASSING_TRACK] = SLOW_SPEED,
[J_L_THRU_TRACK] = MEDIUM_SPEED,
[MCKEES_ROCKS_BLOCK] = SLOW_SPEED,
[MCKEES_ROCKS_YARD2_BLOCK] = RESTRICTED_SPEED,
[MCKEES_ROCKS_YARD1_BLOCK] = RESTRICTED_SPEED,
[CLEVELAND_EL] = 0,
[ASHTABULA_NYC] = 0,
[CUMBERLAND_B_O] = 0,
[POINTS_SOUTH_N_W] = 0,
[LIONEL_IVES_BLOCK] = SLOW_SPEED, -- slow through here in case of stop
[MONACA_BLOCK] = SLOW_SPEED,
[BEAVER_FALLS_BLOCK] = NORMAL_SPEED,
[LOWELLVILLE_BLOCK] = LIMITED_SPEED,
[WEST_ALIQUIPPA_BLOCK] = LIMITED_SPEED,
[FALLSTON_BLOCK] = LIMITED_SPEED,
--[NEW_BLOCK] = LIMITED_SPEED,
},
};

--[[--------------------------------Switch Structure----------------------------------]]
-- fields in the Switches table
AIU_NUMBER = 1;
CHAN_NUMBER = 2;
TIU_NUMBER = 3;
NORMAL_SEGMENT = 4;
REVERSE_SEGMENT = 5;
COMMON_SEGMENT = 6;
-- NORMAL and REVERSE defined in defines.lua
Switches = {
[COLLEGE_WEST] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = COLLEGE_WEST_AIU,
[CHAN_NUMBER] = COLLEGE_WEST_CHAN,
[NORMAL_SEGMENT] = COLLEGE_THRU_TRACK,
[REVERSE_SEGMENT] = COLLEGE_STATION_TRACK,
[COMMON_SEGMENT] = NEW_CASTLE_BLOCK
},

[COLLEGE_EAST] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = COLLEGE_EAST_AIU,
[CHAN_NUMBER] = COLLEGE_EAST_CHAN,
[NORMAL_SEGMENT] = COLLEGE_THRU_TRACK,
[REVERSE_SEGMENT] = COLLEGE_STATION_TRACK,
[COMMON_SEGMENT] = BEAVER_FALLS_BLOCK
},

[YOUNGSTOWN_YARD] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = YOUNGSTOWN_YARD_AIU,
[CHAN_NUMBER] = YOUNGSTOWN_YARD_CHAN,
[NORMAL_SEGMENT] = YOUNGSTOWN_YARD2_BLOCK,
[REVERSE_SEGMENT] = YOUNGSTOWN_YARD1_BLOCK,
[COMMON_SEGMENT] = YOUNGSTOWN_BLOCK
},

[MCKEES_ROCKS_YARD] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = MCKEES_ROCKS_YARD_AIU,
[CHAN_NUMBER] = MCKEES_ROCKS_YARD_CHAN,
[NORMAL_SEGMENT] = MCKEES_ROCKS_YARD1_BLOCK,
[REVERSE_SEGMENT] = MCKEES_ROCKS_YARD2_BLOCK,
[COMMON_SEGMENT] = MCKEES_ROCKS_BLOCK
},

[ALIQUIPPA_EAST] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = ALIQUIPPA_EAST_AIU,
[CHAN_NUMBER] = ALIQUIPPA_EAST_CHAN,
[NORMAL_SEGMENT] = J_L_THRU_TRACK,
[REVERSE_SEGMENT] = J_L_PASSING_TRACK,
[COMMON_SEGMENT] = MCKEES_ROCKS_BLOCK
},

[J_L_SLAG_TRACK] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = J_L_SLAG_TRACK_AIU,
[CHAN_NUMBER] = J_L_SLAG_TRACK_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},

[HOLE_TRACK] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = HOLE_TRACK_AIU,
[CHAN_NUMBER] = HOLE_TRACK_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},

[J_L_MILL_TRACK] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = J_L_MILL_TRACK_AIU,
[CHAN_NUMBER] = J_L_MILL_TRACK_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},

[ALIQUIPPA_WEST] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = ALIQUIPPA_WEST_AIU,
[CHAN_NUMBER] = ALIQUIPPA_WEST_CHAN,
[NORMAL_SEGMENT] = J_L_THRU_TRACK,
[REVERSE_SEGMENT] = J_L_PASSING_TRACK,
[COMMON_SEGMENT] = ALIQUIPPA_BLOCK
},

[ALIQUIPPA_TEAM_TRACK] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = ALIQUIPPA_TEAM_TRACK_AIU,
[CHAN_NUMBER] = ALIQUIPPA_TEAM_TRACK_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},

[BYPASS_WEST] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = BYPASS_WEST_AIU,
[CHAN_NUMBER] = BYPASS_WEST_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},

[BYPASS_EAST] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = BYPASS_EAST_AIU,
[CHAN_NUMBER] = BYPASS_EAST_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},

[STRUTHERS_WEST] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = STRUTHERS_WEST_AIU,
[CHAN_NUMBER] = STRUTHERS_WEST_CHAN,
[NORMAL_SEGMENT] = STRUTHERS_THRU_TRACK,
[REVERSE_SEGMENT] = STRUTHERS_STATION_TRACK,
[COMMON_SEGMENT] = YOUNGSTOWN_BLOCK
},

[STRUTHERS_EAST] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = STRUTHERS_EAST_AIU,
[CHAN_NUMBER] = STRUTHERS_EAST_CHAN,
[NORMAL_SEGMENT] = STRUTHERS_THRU_TRACK,
[REVERSE_SEGMENT] = STRUTHERS_STATION_TRACK,
[COMMON_SEGMENT] = LOWELLVILLE_BLOCK
},

[CAMPBELL_YARD] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = CAMPBELL_YARD_AIU,
[CHAN_NUMBER] = CAMPBELL_YARD_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},

[MONACA_CUTOFF] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = MONACA_CUTOFF_AIU,
[CHAN_NUMBER] = MONACA_CUTOFF_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},

[PM_MOORE_TRESTLE] = {
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = PM_MOORE_TRESTLE_AIU,
[CHAN_NUMBER] = PM_MOORE_TRESTLE_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},
[BALLOON] = { -- Lionel and Ives switches
[TIU_NUMBER] = Layout.MASTER_TIU,
[AIU_NUMBER] = BALLOON_AIU,
[CHAN_NUMBER] = BALLOON_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},
};


--[[-------Distance in inches from a tag detector to when nearby switches are cleared-----------]]
-- Returns nil if no switch is associated with the tag detector
-- This distance is used to time it takes for the caboose to hit
-- the detector and then roll enough to have the caboose clear
-- all switches in the direction of travel.
SwitchClearingInches = {
[MCKEES_ROCKS_YARD_TAG] = {
[EB] = 24, -- this depends on the state of Ives Switch, otherwise 0
[WB] = 0,
},
[ALIQUIPPA_EAST_TAG] = {
[WB] = 30,
[EB] = 0,
},
[ALIQUIPPA_WEST_TAG] = {
[EB] = 56, -- to clear two switches into J&L thru and passing tracks
[WB] = 0,
},
[COLLEGE_EAST_TAG] = {
[WB] = 34,
[EB] = 0,
},
[COLLEGE_WEST_TAG] = {
[EB] = 32,
[WB] = 0,
},
[STRUTHERS_EAST_TAG] = {
[WB] = 32,
[EB] = 0,
},
[STRUTHERS_WEST_TAG] = {
[EB] = 34,
[WB] = 0,
},
[YOUNGSTOWN_YARD_TAG] = {
[WB] = 24, -- this depends on the state of Ives Switch, otherwise 0
[EB] = 0,
},
[MONACA_EAST_TAG] = { -- no switches on either side of this detector
[EB] = 0,
[WB] = 0,
},
[FALLSTON_TAG] = { -- no switches on either side of this detector
[EB] = 0,
[WB] = 0,
},
[WEST_ALIQUIPPA_TAG] = { -- no switches on either side of this detector
[EB] = 0,
[WB] = 0,
},
[MONACA_WEST_TAG] = { -- no switches on either side of this detector
[EB] = 0,
[WB] = 0,
},
[LOWELLVILLE_TAG] = { -- no switches on either side of this detector
[EB] = 0,
[WB] = 0,
},
--[NEW_TAG] = {
-- [EB] = 0,
-- [WB] = 0,
-- },
};

--[[---------------------Station stopping distances in inches after tag detection-----------]]
-- fields in the Distances table
PASSENGER = 1;
FREIGHT = 2;
DETECTOR = 3;
READER = 4;
STATION = 5;

--
-- Stopping distances for stops at a station from a tag reader
--
-- Stations at:
-- Aliquippa
-- McKees Rocks
-- Youngstown
-- Struthers
-- New Castle
-- College
-- West Aliquippa
--
-- TBD 2 McKees Rocks and 2 Youngstown Yard tracks for interchange
--
-- This table has entries for every block that has a station. Includes both eastbound and westbound entries.
--
local StDiStation = {
[ALIQUIPPA_BLOCK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from J&L thru track
[STATION] = ALIQUIPPA,
[PASSENGER] = 88,
[FREIGHT] = 88,
[DETECTOR] = ALIQUIPPA_WEST_DETECTOR,
[READER] = ALIQUIPPA_WEST_READER,
},
[EB] = { -- Passenger Station stop EB from West Aliquippa
[STATION] = ALIQUIPPA,
[PASSENGER] = 163,
[FREIGHT] = 163,
[DETECTOR] = WEST_ALIQUIPPA_DETECTOR,
[READER] = WEST_ALIQUIPPA_READER,
},
},
[WEST_ALIQUIPPA_BLOCK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from Aliquippa
[STATION] = WEST_ALIQUIPPA,
[PASSENGER] = 90,
[FREIGHT] = 90,
[DETECTOR] = WEST_ALIQUIPPA_DETECTOR,
[READER] = WEST_ALIQUIPPA_READER,
},
[EB] = { -- Passenger Station stop EB from Monaca
[STATION] = WEST_ALIQUIPPA,
[PASSENGER] = 185,
[FREIGHT] = 185,
[DETECTOR] = MONACA_EAST_DETECTOR,
[READER] = MONACA_EAST_READER,
},
},
[STRUTHERS_STATION_TRACK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from Lowellville
[STATION] = STRUTHERS,
[PASSENGER] = 85,
[FREIGHT] = 85,
[DETECTOR] = STRUTHERS_EAST_DETECTOR,
[READER] = STRUTHERS_EAST_READER,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[STATION] = STRUTHERS,
[PASSENGER] = 95,
[FREIGHT] = 95,
[DETECTOR] = STRUTHERS_WEST_DETECTOR,
[READER] = STRUTHERS_WEST_READER,
},
},

[STRUTHERS_THRU_TRACK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from Lowellville
[STATION] = STRUTHERS,
[PASSENGER] = 98,
[FREIGHT] = 98,
[DETECTOR] = STRUTHERS_EAST_DETECTOR,
[READER] = STRUTHERS_EAST_READER,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[STATION] = STRUTHERS,
[PASSENGER] = 110,
[FREIGHT] = 110,
[DETECTOR] = STRUTHERS_WEST_DETECTOR,
[READER] = STRUTHERS_WEST_READER,
},
},

[COLLEGE_STATION_TRACK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from Beaver Falls
[STATION] = COLLEGE,
[PASSENGER] = 85,
[FREIGHT] = 85,
[DETECTOR] = COLLEGE_EAST_DETECTOR,
[READER] = COLLEGE_EAST_READER,
},
[EB] = { -- Passenger Station stop EB from New Castle
[STATION] = COLLEGE,
[PASSENGER] = 100,
[FREIGHT] = 100,
[DETECTOR] = COLLEGE_WEST_DETECTOR,
[READER] = COLLEGE_WEST_READER,
}
},

[COLLEGE_THRU_TRACK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from Beaver Falls
[STATION] = COLLEGE,
[PASSENGER] = 105,
[FREIGHT] = 105,
[DETECTOR] = COLLEGE_EAST_DETECTOR,
[READER] = COLLEGE_EAST_READER,
},
[EB] = { -- Passenger Station stop EB from New Castle
[STATION] = COLLEGE,
[PASSENGER] = 103,
[FREIGHT] = 103,
[DETECTOR] = COLLEGE_WEST_DETECTOR,
[READER] = COLLEGE_WEST_READER,
}
},

[MCKEES_ROCKS_BLOCK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from McKees Rocks Yard or Lionel-Ives Block
[STATION] = MCKEES_ROCKS,
[PASSENGER] = 65,
[FREIGHT] = 65,
[DETECTOR] = MCKEES_ROCKS_YARD_DETECTOR,
[READER] = MCKEES_ROCKS_YARD_READER,
},
[EB] = { -- Passenger Station stop EB from J&L Passing Track
[STATION] = MCKEES_ROCKS,
[PASSENGER] = 55,
[FREIGHT] = 55,
[DETECTOR] = ALIQUIPPA_EAST_DETECTOR,
[READER] = ALIQUIPPA_EAST_READER,
},
},
[NEW_CASTLE_BLOCK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from College Thru Track
[STATION] = NEWCASTLE,
[PASSENGER] = 85,
[FREIGHT] = 85,
[DETECTOR] = COLLEGE_WEST_DETECTOR,
[READER] = COLLEGE_WEST_READER,
},
[EB] = { -- Passenger Station stop EB from Lowellville
[STATION] = NEWCASTLE,
[PASSENGER] = 115,
[FREIGHT] = 115,
[DETECTOR] = LOWELLVILLE_DETECTOR,
[READER] = LOWELLVILLE_READER,
},
},
[YOUNGSTOWN_BLOCK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB from Struthers
[STATION] = YOUNGSTOWN,
[PASSENGER] = 206,
[FREIGHT] = 206,
[DETECTOR] = STRUTHERS_WEST_DETECTOR,
[READER] = STRUTHERS_WEST_READER,
},
[EB] = { -- Passenger Station stop EB from Youngstown Yard or Lionel-Ives Block
[STATION] = YOUNGSTOWN,
[PASSENGER] = 84,
[FREIGHT] = 84,
[DETECTOR] = YOUNGSTOWN_YARD_DETECTOR,
[READER] = YOUNGSTOWN_YARD_READER,
},
},
[YOUNGSTOWN_YARD1_BLOCK] = { -- Stop at Station
[WB] = { -- Youngstown Yard1 stop WB from Youngstown
[STATION] = YOUNGSTOWN_YARD1,
[PASSENGER] = 117,
[FREIGHT] = 117,
[DETECTOR] = YOUNGSTOWN_YARD_DETECTOR,
[READER] = YOUNGSTOWN_YARD_READER,
},
[EB] = {}, -- no EB traffic
},
[YOUNGSTOWN_YARD2_BLOCK] = { -- Stop at Station
[WB] = { -- Youngstown Yard2 stop WB from Youngstown
[STATION] = YOUNGSTOWN_YARD2,
[PASSENGER] = 118,
[FREIGHT] = 118,
[DETECTOR] = YOUNGSTOWN_YARD_DETECTOR,
[READER] = YOUNGSTOWN_YARD_READER,
},
[EB] = {}, -- no EB traffic
},
[MCKEES_ROCKS_YARD1_BLOCK] = { -- Stop at Station
[WB] = {}, -- no WB traffic
[EB] = { -- McKees Rocks Yard1 Stop EB from McKees Rocks
[STATION] = MCKEES_ROCKS_YARD1,
[PASSENGER] = 101,
[FREIGHT] = 101,
[DETECTOR] = MCKEES_ROCKS_YARD_DETECTOR,
[READER] = MCKEES_ROCKS_YARD_READER,
},
},
[MCKEES_ROCKS_YARD2_BLOCK] = { -- Stop at Station
[WB] = {}, -- no WB traffic
[EB] = { -- McKees Rocks Yard2 Stop EB from McKees Rocks
[STATION] = MCKEES_ROCKS_YARD1,
[PASSENGER] = 102,
[FREIGHT] = 102,
[DETECTOR] = MCKEES_ROCKS_YARD_DETECTOR,
[READER] = MCKEES_ROCKS_YARD_READER,
},
},
--[[
[NEW_BLOCK] = { -- Stop at Station
[WB] = { -- Passenger Station stop WB
[STATION] = NEWSTATION,
[PASSENGER] = 0,
[FREIGHT] = 0,
[DETECTOR] = NEW_DETECTOR,
[READER] = NEW_READER,
},
[EB] = { -- Passenger Station stop EB
[STATION] = NEWSTATION,
[PASSENGER] = 0,
[FREIGHT] = 0,
[DETECTOR] = NEW_DETECTOR,
[READER] = NEW_READER,
},
},
]]
}

--
--[[----Stopping distances for stops within a block in inches after tag detection------]]
--
-- These are meant to be stopping distances in conjuction with "Stop and Proceed" Signal
-- Note: Short trains are 50 inches long (Diesel engine, 2 cars and a caboose)
--
-- This table also gives us the next detector by looking at the opposite direction (see RFID Sensor Test.lua).
--
-- This table has entries for every block both eastbound and west bound
--
local StDiBlock = {
[ALIQUIPPA_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 170,
[FREIGHT] = 170,
[DETECTOR] = ALIQUIPPA_WEST_DETECTOR,
[READER] = ALIQUIPPA_WEST_READER,
},
[EB] = {
[PASSENGER] = 170,
[FREIGHT] = 170,
[DETECTOR] = WEST_ALIQUIPPA_DETECTOR,
[READER] = WEST_ALIQUIPPA_READER
},
},
[WEST_ALIQUIPPA_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 195,
[FREIGHT] = 195,
[DETECTOR] = WEST_ALIQUIPPA_DETECTOR,
[READER] = WEST_ALIQUIPPA_READER,
},
[EB] = {
[PASSENGER] = 195,
[FREIGHT] = 195,
[DETECTOR] = MONACA_EAST_DETECTOR,
[READER] = MONACA_EAST_READER
},
},
[STRUTHERS_THRU_TRACK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 80,
[FREIGHT] = 80,
[DETECTOR] = STRUTHERS_EAST_DETECTOR,
[READER] = STRUTHERS_EAST_READER,
},
[EB] = { -- Eastbound normally does not take the thru track
[PASSENGER] = 80,
[FREIGHT] = 80,
[DETECTOR] = STRUTHERS_WEST_DETECTOR,
[READER] = STRUTHERS_WEST_READER,
},
},

[STRUTHERS_STATION_TRACK] = { -- Stop In Block
[WB] = { -- Westbound normally does not take the station track
[PASSENGER] = 90,
[FREIGHT] = 90,
[DETECTOR] = STRUTHERS_EAST_DETECTOR,
[READER] = STRUTHERS_EAST_READER,
},
[EB] = {
[PASSENGER] = 90,
[FREIGHT] = 90,
[DETECTOR] = STRUTHERS_WEST_DETECTOR,
[READER] = STRUTHERS_WEST_READER,
},
},

[COLLEGE_THRU_TRACK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 100,
[FREIGHT] = 100,
[DETECTOR] = COLLEGE_EAST_DETECTOR,
[READER] = COLLEGE_EAST_READER,
},
[EB] = { -- Eastbound normally does not take the thru track
[PASSENGER] = 100,
[FREIGHT] = 100,
[DETECTOR] = COLLEGE_WEST_DETECTOR,
[READER] = COLLEGE_WEST_READER,
}
},

[COLLEGE_STATION_TRACK] = { -- Stop In Block
[WB] = { -- Westbound normally does not take the station track
[PASSENGER] = 95,
[FREIGHT] = 95,
[DETECTOR] = COLLEGE_EAST_DETECTOR,
[READER] = COLLEGE_EAST_READER,
},
[EB] = {
[PASSENGER] = 95,
[FREIGHT] = 95,
[DETECTOR] = COLLEGE_WEST_DETECTOR,
[READER] = COLLEGE_WEST_READER,
}
},

[MCKEES_ROCKS_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 52,
[FREIGHT] = 52,
[DETECTOR] = MCKEES_ROCKS_YARD_DETECTOR,
[READER] = MCKEES_ROCKS_YARD_READER,
},
[EB] = {
[PASSENGER] = 50,
[FREIGHT] = 50,
[DETECTOR] = ALIQUIPPA_EAST_DETECTOR,
[READER] = ALIQUIPPA_EAST_READER,
},
},

[NEW_CASTLE_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 117,
[FREIGHT] = 117,
[DETECTOR] = COLLEGE_WEST_DETECTOR,
[READER] = COLLEGE_WEST_READER,
},
[EB] = {
[PASSENGER] = 117,
[FREIGHT] = 117,
[DETECTOR] = LOWELLVILLE_DETECTOR,
[READER] = LOWELLVILLE_READER,
},
},

[LOWELLVILLE_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 115,
[FREIGHT] = 115,
[DETECTOR] = LOWELLVILLE_DETECTOR,
[READER] = LOWELLVILLE_READER,
},
[EB] = {
[PASSENGER] = 115,
[FREIGHT] = 115,
[DETECTOR] = STRUTHERS_EAST_DETECTOR,
[READER] = STRUTHERS_EAST_READER,
},
},

[YOUNGSTOWN_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 215,
[FREIGHT] = 215,
[DETECTOR] = STRUTHERS_WEST_DETECTOR,
[READER] = STRUTHERS_WEST_READER,
},
[EB] = {
[PASSENGER] = 215,
[FREIGHT] = 215,
[DETECTOR] = YOUNGSTOWN_YARD_DETECTOR,
[READER] = YOUNGSTOWN_YARD_READER,
},
},

[MONACA_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 120,
[FREIGHT] = 120,
[DETECTOR] = MONACA_EAST_DETECTOR,
[READER] = MONACA_EAST_READER,
},
[EB] = {
[PASSENGER] = 120,
[FREIGHT] = 120,
[DETECTOR] = MONACA_WEST_DETECTOR,
[READER] = MONACA_WEST_READER,
},
},

[BEAVER_FALLS_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 120,
[FREIGHT] = 120,
[DETECTOR] = FALLSTON_DETECTOR,
[READER] = FALLSTON_READER,
},
[EB] = {
[PASSENGER] = 120,
[FREIGHT] = 120,
[DETECTOR] = COLLEGE_EAST_DETECTOR,
[READER] = COLLEGE_EAST_READER,
},
},

[FALLSTON_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 185,
[FREIGHT] = 185,
[DETECTOR] = MONACA_WEST_DETECTOR,
[READER] = MONACA_WEST_READER,
},
[EB] = {
[PASSENGER] = 185,
[FREIGHT] = 185,
[DETECTOR] = FALLSTON_DETECTOR,
[READER] = FALLSTON_READER,
},
},
[J_L_PASSING_TRACK] = { -- Stop In Block
[WB] = { -- Westbound normally does not take the passing track
[PASSENGER] = 150,
[FREIGHT] = 150,
[DETECTOR] = ALIQUIPPA_EAST_DETECTOR,
[READER] = ALIQUIPPA_EAST_READER,
},
[EB] = {
[PASSENGER] = 130,
[FREIGHT] = 130,
[DETECTOR] = ALIQUIPPA_WEST_DETECTOR,
[READER] = ALIQUIPPA_WEST_READER,
},
},

[J_L_THRU_TRACK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 120,
[FREIGHT] = 120,
[DETECTOR] = ALIQUIPPA_EAST_DETECTOR,
[READER] = ALIQUIPPA_EAST_READER,
},
[EB] = { -- Eastbound normally does not take the thru track
[PASSENGER] = 120,
[FREIGHT] = 120,
[DETECTOR] = ALIQUIPPA_WEST_DETECTOR,
[READER] = ALIQUIPPA_WEST_READER,
},
},

[YOUNGSTOWN_YARD1_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 146,
[FREIGHT] = 146,
[DETECTOR] = YOUNGSTOWN_YARD_DETECTOR,
[READER] = YOUNGSTOWN_YARD_READER,
},
[EB] = {}, -- no EB traffic
},

[YOUNGSTOWN_YARD2_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 145,
[FREIGHT] = 145,
[DETECTOR] = YOUNGSTOWN_YARD_DETECTOR,
[READER] = YOUNGSTOWN_YARD_READER,
},
[EB] = {}, -- no EB traffic
},

[MCKEES_ROCKS_YARD1_BLOCK] = { -- Stop In Block
[WB] = {}, -- no WB traffic
[EB] = {
[PASSENGER] = 100,
[FREIGHT] = 100,
[DETECTOR] = MCKEES_ROCKS_YARD_DETECTOR,
[READER] = MCKEES_ROCKS_YARD_READER,
},
},

[MCKEES_ROCKS_YARD2_BLOCK] = { -- Stop In Block
[WB] = {}, -- no WB traffic
[EB] = {
[PASSENGER] = 100,
[FREIGHT] = 100,
[DETECTOR] = MCKEES_ROCKS_YARD_DETECTOR,
[READER] = MCKEES_ROCKS_YARD_READER,
},
},

[LIONEL_IVES_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 65,
[FREIGHT] = 65,
[DETECTOR] = YOUNGSTOWN_YARD_DETECTOR,
[READER] = YOUNGSTOWN_YARD_READER,
},
[EB] = {
[PASSENGER] = 63,
[FREIGHT] = 63,
[DETECTOR] = MCKEES_ROCKS_YARD_DETECTOR,
[READER] = MCKEES_ROCKS_YARD_READER,
},
},
--[[
[NEW_BLOCK] = { -- Stop In Block
[WB] = {
[PASSENGER] = 0,
[FREIGHT] = 0,
[DETECTOR] = NEW_DETECTOR,
[READER] = NEW_READER,
},
[EB] = {
[PASSENGER] = 0,
[FREIGHT] = 0,
[DETECTOR] = NEW_DETECTOR,
[READER] = NEW_READER,
},
},
]]
}

--[[-----------------------------------------------------------------------------]]
-- high level API
--[[-----------------------------------------------------------------------------]]
function Layout.StationName(Num)
return StationNames[Num];
end
--[[-----------------------------------------------------------------------------]]
function Layout.NEXTBlock(Direction, TagNumber)
return NextBlock[Direction][TagNumber];
end
--[[-----------------------------------------------------------------------------]]
function Layout.STOPBlock(block, Direction, Parameter)
if (StDiBlock[block] == nil) then return nil; end;
if (StDiBlock[block][Direction] == nil) then return nil; end;
if (StDiBlock[block][Direction][Parameter] == nil) then return nil; end;
return StDiBlock[block][Direction][Parameter]
end
--[[-----------------------------------------------------------------------------]]

function Layout.STOPStation(block, Direction, Parameter)
if (StDiStation[block] == nil) then return nil; end;
if (StDiStation[block][Direction] == nil) then return nil; end;
if (StDiStation[block][Direction][Parameter] == nil) then return nil; end;
return StDiStation[block][Direction][Parameter]
end
--[[-----------------------------------------------------------------------------]]

function Layout.STOPDistance(Engine, Speed)
--[[ This original code, returned the individual engine stopping distances
-- as generated by "Deceleration Test.lua"
--
if (Layout.AccDecRate == 1) then
if (Layout.StoppingDistance1[Engine] ~= nil) then
if (Layout.StoppingDistance1[Engine][Speed] ~= nil) then
return Layout.StoppingDistance1[Engine][Speed];
else
return 0
end
else
return 0
end
end;
if (Layout.AccDecRate == 4) then
if (Layout.StoppingDistance4[Engine] ~= nil) then
if (Layout.StoppingDistance4[Engine][Speed] ~= nil) then
return Layout.StoppingDistance4[Engine][Speed];
else
return 0
end
else
return 0
end
end;
--]]
-- This new code returns the average stopping distance for a particular
-- speed averaged over all engines
-- The average is generated by code at the end of this file as
-- it calls "Layout.AvgStopDist();"
--
if (Layout.AccDecRate == 1) then
--print("AverageStoppingDistance1[" .. Speed .. "] = " .. Layout.AverageStoppingDistance1[Speed]);
if (Layout.AverageStoppingDistance1[Speed] ~= nil) then
return Layout.AverageStoppingDistance1[Speed];
end
end;
if (Layout.AccDecRate == 4) then
if (Layout.AverageStoppingDistance4[Speed] ~= nil) then
return Layout.AverageStoppingDistance4[Speed];
end
end;
return 0;
end
--[[-----------------------------------------------------------------------------]]

function Layout.STARTDistance(Engine, Speed)

-- This code returns the average Starting distance for a particular
-- speed averaged over all engines
-- The average is generated by code at the end of this file as
-- it calls "Layout.AvgStartDist();"
--
if (Layout.AccDecRate == 1) then
--print("AverageStartingDistance1[" .. Speed .. "] = " .. Layout.AverageStartingDistance1[Speed]);
if (Layout.AverageStartingDistance1[Speed] ~= nil) then
return Layout.AverageStartingDistance1[Speed];
end
end;
if (Layout.AccDecRate == 4) then
if (Layout.AverageStartingDistance4[Speed] ~= nil) then
return Layout.AverageStartingDistance4[Speed];
end
end;
return 0;
end
--[[-----------------------------------------------------------------------------]]

function Layout.StartingisEmpty(Engine)
return (StartingDistance[Engine] == nil)
end
--[[-----------------------------------------------------------------------------]]

function Layout.Starting(Engine, Speed)
if (StartingDistance[Engine] ~= nil) then
if (StartingDistance[Engine][Speed] ~= nil) then
return StartingDistance[Engine][Speed]
else
return 0
end
else
return 0
end
end
--[[-----------------------------------------------------------------------------]]

function Layout.AvgStopDist()
-- This code generates the average stopping distances by speed over all
-- engines. The resulting Table is available to the running script by
-- calling Layout.STOPDistance(Engine, Speed)
--
local SD;
if (Layout.AccDecRate == 1) then
SD = Layout.StoppingDistance1;
end;
if (Layout.AccDecRate == 4) then
SD = Layout.StoppingDistance4;
end;

print("Creating Average Stopping Distances by Speed");
local AverageDistance = {};
local iCount = 0;
local EngNumber = 0;
--local TableConstructor = "Average Stopping Distance " .. Layout.AccDecRate .. " Smph = {";
for iEng, aDist in pairs (SD) do
--print("Data Set " .. iEng .. " Distance ", aDist[1]);
--print("Data Set " .. iEng);
EngNumber = iEng;
iCount = iCount + 1;
for iSpd, iNches in pairs (aDist) do
if (AverageDistance[iSpd] == nil) then
AverageDistance[iSpd] = 0;
end
--print("Speed " .. iSpd .. " Stopping Distance " .. iNches);
AverageDistance[iSpd] = AverageDistance[iSpd] + iNches;
end;
end;
print("Averaging " .. iCount .. " Data Sets");
for iSpd, _ in pairs (SD[EngNumber]) do -- use one of the Data Set tables to iterate over speeds
AverageDistance[iSpd] = AverageDistance[iSpd] / iCount;
--TableConstructor = TableConstructor .. "[" .. iSpd .. "] = " .. AverageDistance[iSpd] .. ", ";
end;

--if (TableConstructor ~= nil) then
-- TableConstructor = string.sub(TableConstructor, 1, -3) .. "}"; -- remove ending comma
-- print (TableConstructor);
-- end;
--
if (Layout.AccDecRate == 1) then
Layout.AverageStoppingDistance1 = AverageDistance;
end;
if (Layout.AccDecRate == 4) then
Layout.AverageStoppingDistance4 = AverageDistance;
end;
end;
--[[-----------------------------------------------------------------------------]]

function Layout.AvgStartDist()
-- This code generates the average starting distances by speed over all
-- engines. The resulting Table is available to the running script by
-- calling Layout.STARTDistance(Engine, Speed)
--
local SD;
if (Layout.AccDecRate == 1) then
SD = Layout.StartingDistance1;
end;
if (Layout.AccDecRate == 4) then
SD = Layout.StartingDistance4;
end;

print("Creating Average Starting Distances by Speed");
local AverageDistance = {};
local iCount = 0;
local EngNumber = 0;
--local TableConstructor = "Average Starting Distance " .. Layout.AccDecRate .. " Smph = {";
for iEng, aDist in pairs (SD) do
--print("Data Set " .. iEng .. " Distance ", aDist[1]);
--print("Data Set " .. iEng);
EngNumber = iEng;
iCount = iCount + 1;
for iSpd, iNches in pairs (aDist) do
if (AverageDistance[iSpd] == nil) then
AverageDistance[iSpd] = 0;
end
--print("Speed " .. iSpd .. " Starting Distance " .. iNches);
AverageDistance[iSpd] = AverageDistance[iSpd] + iNches;
end;
end;
print("Averaging " .. iCount .. " Data Sets");
for iSpd, _ in pairs (SD[EngNumber]) do -- use one of the Data Set tables to iterate over speeds
AverageDistance[iSpd] = AverageDistance[iSpd] / iCount;
--TableConstructor = TableConstructor .. "[" .. iSpd .. "] = " .. AverageDistance[iSpd] .. ", ";
end;

--if (TableConstructor ~= nil) then
-- TableConstructor = string.sub(TableConstructor, 1, -3) .. "}"; -- remove ending comma
-- print (TableConstructor);
-- end;
--
if (Layout.AccDecRate == 1) then
Layout.AverageStartingDistance1 = AverageDistance;
end;
if (Layout.AccDecRate == 4) then
Layout.AverageStartingDistance4 = AverageDistance;
end;
end;
--[[------------------------------Create Layout Graph---------------------------------------]]

function Layout:CreateGraph(G)
-- This function creates the track layout graph.
-- This graph is for Instrumented Layout #4.1

-- Youngstown Yard
-- Track 1
G:addDoubleVertex(0, 1) -- double vertex
G:addUndirectedEdge(1, 2, 5, YOUNGSTOWN_YARD1_BLOCK)
G:addDoubleVertex(2, 3) -- double vertex
-- Track 2
G:addDoubleVertex(36, 37) -- double vertex
G:addUndirectedEdge(37, 38, 5, YOUNGSTOWN_YARD2_BLOCK)
G:addDoubleVertex(38, 39) -- double vertex
-- switch track
G:addSwitchTrack(4, 3, 39, YOUNGSTOWN_YARD); -- common vertex, normal vertex, reverse vertex, Switch Name
G:addDoubleVertex(4, 5) -- double vertex
--[[--------------Line between Youngstown Yard and Struthers------------------]]
G:addUndirectedEdge(5, 6, 5, YOUNGSTOWN_BLOCK)
--[[--------------Struthers------------------]]
G:addDoubleVertex(6, 7) -- double vertex
G:addSwitchTrack(7, 8, 40, STRUTHERS_WEST); -- common vertex, normal vertex, reverse vertex, Switch Name
-- Struthers Thru Track with different costs depending on direction
G:addDoubleVertex(8, 9) -- double vertex
G:addDirectedEdge(9, 10, 15, STRUTHERS_THRU_TRACK) -- Struthers Through Track Eastward
G:addDirectedEdge(10, 9, 5, STRUTHERS_THRU_TRACK) -- Struthers Through Track Westward

-- Struthers Station Track with different costs depending on direction
G:addDoubleVertex(40, 41) -- double vertex
G:addDirectedEdge(41, 42, 7, STRUTHERS_STATION_TRACK) -- Eastward
G:addDirectedEdge(42, 41, 20, STRUTHERS_STATION_TRACK) -- Westward
G:addDoubleVertex(42, 43) -- double vertex

G:addDoubleVertex(10, 11) -- double vertex
G:addSwitchTrack(12, 11, 43, STRUTHERS_EAST); -- common vertex, normal vertex, reverse vertex, Switch Name
G:addDoubleVertex(12, 13) -- double vertex

--[[--------------Line between Struthers, Lowellville and College------------------]]
G:addUndirectedEdge(13, 60, 5, NEW_CASTLE_BLOCK)
G:addUndirectedEdge(60, 14, 5, LOWELLVILLE_BLOCK)
--[[--------------College------------------]]
G:addDoubleVertex(14, 15) -- double vertex
G:addSwitchTrack(15, 16, 44, COLLEGE_WEST); -- common vertex, normal vertex, reverse vertex, Switch Name
-- College Thru Track with different costs depending on direction
G:addDoubleVertex(16, 17) -- double vertex
G:addDirectedEdge(17, 18, 15, COLLEGE_THRU_TRACK) -- College Through Track Eastward
G:addDirectedEdge(18, 17, 5, COLLEGE_THRU_TRACK) -- College Through Track Westward

-- College Station Track with different costs depending on direction
G:addDoubleVertex(44, 45) -- double vertex
G:addDirectedEdge(45, 46, 7, COLLEGE_STATION_TRACK) -- Eastward
G:addDirectedEdge(46, 45, 20, COLLEGE_STATION_TRACK) -- Westward
G:addDoubleVertex(46, 47) -- double vertex

G:addDoubleVertex(18, 19) -- double vertex
G:addSwitchTrack(20, 19, 47, COLLEGE_EAST); -- common vertex, normal vertex, reverse vertex, Switch Name
G:addDoubleVertex(20, 21) -- double vertex

--[[--------------Line between College and Aliquippa------------------]]
G:addUndirectedEdge(21, 22, 5, ALIQUIPPA_BLOCK)
--[[--------------Aliquppa------------------]]
G:addDoubleVertex(22, 23) -- double vertex

G:addSwitchTrack(23, 24, 48, ALIQUIPPA_WEST); -- common vertex, normal vertex, reverse vertex, Switch Name
-- J&L Aliquippa Thru Track with different costs depending on direction
G:addDoubleVertex(24, 25) -- double vertex
G:addDirectedEdge(25, 26, 15, J_L_THRU_TRACK) -- J&L Aliquippa Through Track Eastward
G:addDirectedEdge(26, 25, 5, J_L_THRU_TRACK) -- J&L Aliquippa Through Track Westward

-- J&L Passing Track with different costs depending on direction
G:addDoubleVertex(48, 49) -- double vertex
G:addDirectedEdge(49, 50, 7, J_L_PASSING_TRACK) -- Eastward
G:addDirectedEdge(50, 49, 20, J_L_PASSING_TRACK) -- Westward
G:addDoubleVertex(50, 51) -- double vertex
G:addDoubleVertex(26, 27) -- double vertex
G:addSwitchTrack(28, 27, 51, ALIQUIPPA_EAST); -- common vertex, normal vertex, reverse vertex, Switch Name
G:addDoubleVertex(28, 29) -- double vertex
--[[--------------Line between Aliquippa and McKees Rocks Yard------------------]]
G:addUndirectedEdge(29, 30, 5, MCKEES_ROCKS_BLOCK)
--[[--------------McKees Rocks Yard------------------]]
G:addDoubleVertex(30, 31) -- double vertex
G:addSwitchTrack(31, 32, 52, MCKEES_ROCKS_YARD); -- common vertex, normal vertex, reverse vertex, Switch Name
-- Track 2
G:addDoubleVertex(52, 53) -- double vertex
G:addUndirectedEdge(53, 54, 5, MCKEES_ROCKS_YARD2_BLOCK)
G:addDoubleVertex(54, 55) -- double vertex
-- Track 1
G:addDoubleVertex(32, 33) -- double vertex
G:addUndirectedEdge(33, 34, 5, MCKEES_ROCKS_YARD1_BLOCK)
G:addDoubleVertex(34, 35) -- double vertex
--
--
-- templates:
-- G:adddirectedEdge(000, 000, 0) -- (vertex1, vertex2, weight)
-- G:addUndirectedEdge(000, 000, 0) -- (vertex1, vertex2, weight)
-- G:addDoubleVertex(000, 000) -- double vertex
-- G:addSwitchTrack(000, 000, 000, 000, 000, 000); -- (common vertex, normal vertex, reverse vertex, TIU, AIU, Channel)
--
local requiredVertices = 56; -- 0 to 55
return requiredVertices
end

print("Layout : " .. Layout.name);
print("Acc/Dec Rate = " .. Layout.AccDecRate);
Layout.AvgStopDist(); -- one time use only
Layout.AvgStartDist(); -- one time use only

return Layout







Layout4 APB Master.lua

All of the functions described above give us the ability to show and control signals on the Layout window and the Program Control window.

We need a script to monitor the RFID detectors and as engines and cabooses are detected, the script needs to update the status of the signals. The code in the script is based on Bruce Chubb's Visual Basic program shown in the RMC series of articles and in his books.

The script has to know the layout - locations of blocks, signals, and switch tracks. This is the script "Layout4.lua". Because of the way that the RFID work, that is, they indicate when a train moves from one block to the next, we need to specify which blocks are occupied when the script starts (initial conditions).

This is the script that makes the signals come alive. It does not control any trains. It only gets the information from each tag detected and then sets the signals to the correct aspect. That is an important point, the signals only indicate the status of the railroad, they do not convey any authority for train movement. Under TT&TO, train orders and the timetable convey this authority. Signals give additional information to the engineer about the status of the track ahead. This script controls the signals as an Absolute Permissive Block (APB) signaling system.

Here is the script  Layout4 APB Master.lua 
(this may not be as up to date as the zip file linked to below):
--[[
---------------------------------------------------------------------------
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
title = "Layout4 APB Master"
require([[defines]]);
require([[functions]]);

--[[
Accepts events from "Layout4 Block Occupancy.lua" to simulate a train running across the layout
--]]
--[[
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

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 shuld be no traffic sticks set

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.
---------------------------------------------------------------------------]]
local Count = 0;
-- These labels appear on each of the tabs for the Function Buttons
local Tab01Label = "Sim Movement";
local Tab02Label = "Not Used";
local Tab03Label = "Reserved";

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 SavedSig = {}; -- table to hold the last known Signal Aspects
local BK = {}; -- table to hold the APB Master Block Occupancy Status
local TUMCNT = {}; -- table to hold all of the Tumbledown Counters
--
-- Traffic Stick Directions
local WEST = 1;
local EAST = 2;
local NDT = 0; -- No direction-of-traffic
-- state machine states
local IDLE = 0;
local SM = IDLE;
local ErrCount = 0;
local BeepOn = false;
local SoundOff = false;
local LLLoop = true; -- start in looping mode
local Layout4;
-- 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 MyTIUNo, Enum;

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");
-- Instrumented Layout 4
Layout4 = require([[Layout4]]); -- All of the details about the Layout
--
MyTIUNo = Layout4.MASTER_TIU;
--
ShowLayout(); -- show the Layout Window
-- Next line not needed - This script should run independantly from Engine control scripts
--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");
-- Layout using the bypass block - Balloon Block
-- 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.
]]
-----------------------------------------------------------------------------------------------
-- Set Loop Switch
if (LLLoop) then
Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0)
else
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0)
end;
--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)
--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);
Sleep(3); -- time for all of the switches to align
--
-- Westbound
--
CreateSignal( 1, WB, PERMISSIVE_IN_OPEN_COUNTRY, 370,280, "LI", AsStopandProceed, 2, 1); -- Permissive signal in open country
FSR[1] = NDT;
Sig[1] = AsStopandProceed;
TUMCNT[1] = 0;
--
CreateSignal( 5, WB, ABSOLUTE_HEAD_BLOCK, 800,260, "YG", AsStopSignal, 1, 0); -- Absolute Head Block Signal
FSR[5] = nil;
Sig[5] = AsStopSignal;
TUMCNT[5] = 0;
--
CreateSignal( 9, WB, PERMISSIVE_HEAD_BLOCK, 1190,240, "ST", AsStopandProceed, 3, 9); -- Permissive head block signal
FSR[9] = NDT;
Sig[9] = AsStopandProceed;
TUMCNT[9] = 0;
--
CreateSignal(11, WB, APPROACH_TO_HEAD_BLOCK, 1270,240, "LO", AsStopandProceed, 2, 11); -- Signal in approach to head block signal
FSR[11] = NDT;
Sig[11] = AsStopandProceed;
TUMCNT[11] = 0;
--
CreateSignal(13, WB, ABSOLUTE_HEAD_BLOCK, 1450,240, "NC", AsStopSignal, 1, 0); -- Absolute Head Block Signal
FSR[13] = NDT;
Sig[13] = AsStopSignal;
TUMCNT[13] = 0;
--
CreateSignal(17, WB, PERMISSIVE_HEAD_BLOCK, 1750,240, "C0", AsStopandProceed, 3, 17); -- Permissive head block signal
FSR[17] = NDT;
Sig[17] = AsStopandProceed;
TUMCNT[17] = 0;
--
CreateSignal(19, WB, APPROACH_TO_HEAD_BLOCK, 1833,240, "BF", AsStopandProceed, 2, 19); -- Signal in approach to head block signal
FSR[19] = NDT;
Sig[19] = AsStopandProceed;
TUMCNT[21] = 0;
--
CreateSignal(21, WB, PERMISSIVE_IN_OPEN_COUNTRY, 2000,240, "FS", AsStopandProceed, 2, 21); -- Permissive signal in open country
FSR[21] = NDT;
Sig[21] = AsStopandProceed;
TUMCNT[21] = 0;
--
CreateSignal(25, WB, PERMISSIVE_IN_OPEN_COUNTRY, 2100,240, "MO", AsStopandProceed, 2, 25); -- Permissive signal in open country
FSR[25] = NDT;
Sig[25] = AsStopandProceed;
TUMCNT[25] = 0;
--
CreateSignal(27, WB, PERMISSIVE_IN_OPEN_COUNTRY, 2320,240, "QA", AsStopandProceed, 2, 27); -- Permissive signal in open country
FSR[27] = NDT;
Sig[27] = AsStopandProceed;
TUMCNT[27] = 0;
--
CreateSignal(29, WB, ABSOLUTE_HEAD_BLOCK, 2675,240, "AL", AsStopSignal, 1, 0); -- Absolute Head Block Signal
FSR[29] = NDT;
Sig[29] = AsStopSignal;
TUMCNT[29] = 0;
--
CreateSignal(33, WB, PERMISSIVE_HEAD_BLOCK, 3280,240, "JL", AsStopandProceed, 3, 33); -- Permissive head block signal
FSR[33] = NDT;
Sig[33] = AsStopandProceed;
TUMCNT[33] = 0;
--
CreateSignal(37, WB, APPROACH_TO_HEAD_BLOCK, 3430,240, "MK", AsStopandProceed, 2, 37); -- Signal in approach to head block signal
FSR[37] = NDT;
Sig[37] = AsStopandProceed;
TUMCNT[37] = 0;
--
-----------------------------------------------------------------------------------------------
--
-- Eastbound
--
CreateSignal(2, EB, APPROACH_TO_HEAD_BLOCK, 370,440, "YG", AsStopandProceed, 2, 2); -- Signal in approach to head block signal
FSR[2] = NDT;
Sig[2] = AsStopandProceed;
TUMCNT[2] = 0;
--
CreateSignal(6, EB, PERMISSIVE_HEAD_BLOCK, 800,430, "ST", AsStopandProceed, 3, 6); -- Permissive head block signal
FSR[6] = NDT;
Sig[6] = AsStopandProceed;
TUMCNT[6] = 0;
--
CreateSignal(10, EB, ABSOLUTE_HEAD_BLOCK, 1190,410, "LO", AsStopSignal, 1, 0); -- Absolute Head Block Signal
FSR[10] = NDT;
Sig[10] = AsStopSignal;
TUMCNT[10] = 0;
--
CreateSignal(12, EB, APPROACH_TO_HEAD_BLOCK, 1270,410, "NC", AsStopandProceed, 2, 12); -- Signal in approach to head block signal
FSR[12] = NDT;
Sig[12] = AsStopandProceed;
TUMCNT[12] = 0;
--
CreateSignal(14, EB, PERMISSIVE_HEAD_BLOCK, 1450,410, "CO", AsStopandProceed, 3, 14); -- Permissive head block signal
FSR[14] = NDT;
Sig[14] = AsStopandProceed;
TUMCNT[14] = 0;
--
CreateSignal(18, EB, ABSOLUTE_HEAD_BLOCK, 1750,410, "BF", AsStopSignal, 1, 0); -- Absolute Head Block Signal
FSR[18] = NDT;
Sig[18] = AsStopSignal;
TUMCNT[18] = 0;
--
CreateSignal(20, EB, PERMISSIVE_IN_OPEN_COUNTRY, 1833,410, "FS", AsStopandProceed, 2, 20); -- Permissive signal in open country
FSR[20] = NDT;
Sig[20] = AsStopandProceed;
TUMCNT[20] = 0;
--
CreateSignal(22, EB, PERMISSIVE_IN_OPEN_COUNTRY, 2000,410, "MO", AsStopandProceed, 2, 22); -- Permissive signal in open country
FSR[22] = NDT;
Sig[22] = AsStopandProceed;
TUMCNT[22] = 0;
--
CreateSignal(26, EB, PERMISSIVE_IN_OPEN_COUNTRY, 2100,410, "QA", AsStopandProceed, 2, 26); -- Permissive signal in open country
FSR[26] = NDT;
Sig[26] = AsStopandProceed;
TUMCNT[26] = 0;
--
CreateSignal(28, EB, APPROACH_TO_HEAD_BLOCK, 2320,410, "AL", AsStopandProceed, 2, 28); -- Signal in approach to head block signal
FSR[28] = NDT;
Sig[28] = AsStopandProceed;
TUMCNT[28] = 0;
--
CreateSignal(30, EB, PERMISSIVE_HEAD_BLOCK, 2675,410, "JL", AsStopandProceed, 3, 30); -- Permissive head block signal
FSR[30] = NDT;
Sig[30] = AsStopandProceed;
TUMCNT[30] = 0;
--
CreateSignal(34, EB, ABSOLUTE_HEAD_BLOCK, 3280,410, "MK", AsStopSignal, 1, 0); -- Absolute Head Block Signal
FSR[34] = NDT;
Sig[34] = AsStopSignal;
TUMCNT[34] = 0;
--
CreateSignal(38, EB, PERMISSIVE_IN_OPEN_COUNTRY, 3430,410, "LI", AsStopandProceed, 2, 38); -- Permissive signal in open country
FSR[38] = NDT;
Sig[38] = AsStopandProceed;
TUMCNT[38] = 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[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;

-- CLear all Blocks
print("Set all Blocks as Unoccupied");
for Block = FIRST_BLOCK, LAST_BLOCK do
BK[Block] = UNOCCUPIED; -- this flag is used by this APB Master thread
-- this script clears the global flags as we startup a run
OverrideOccupancy(Block, UNOCCUPIED); -- force the flag to show unoccupied -- This flag is used by the Engine PC threads
end;
---[[

--[[--------------------------------------------]]
-- Ask the dispatcher for starting block number
-- Note: when running with "Layout4 Blick xB Occupancy.lua", it is not necessary to to select a starting block
-- as those test scripts will set the starting block number to occupied
--
local BlockNameList = {}; -- table to hold all of the block numbers and names

BlockNameList[1] = "0 None"; -- use Index 1 for "None"

for BlockNameIndex, Name in pairs(BlockName) do
if (BlockNameIndex <= 100) then
--print(Name .. "(" .. BlockNameIndex .. ")");
BlockNameList[BlockNameIndex + 1] = BlockNameIndex .. " " .. Name;
end;
end;
--
-- read Starting Block Numbers
print("Select Occupied Block Number");
--
-- read up to 10 occupied blocks
--
for Blk=1,10 do
local OccupiedBlock = InputDropDown(title, "Select Occupied Block ('None' for no more)", 1, 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;
if (OccupiedBlock == STRUTHERS_STATION_TRACK) then OccupiedBlock = STRUTHERS_BLOCK; end; -- correct for passing and station tracks
if (OccupiedBlock == COLLEGE_STATION_TRACK) then OccupiedBlock = COLLEGE_BLOCK; end;
if (OccupiedBlock == J_L_PASSING_TRACK) then OccupiedBlock = J_L_BLOCK; end;
BK[OccupiedBlock] = BK[OccupiedBlock] + 1; -- indicate that the block is occupied. -- 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

-- this script does not need to set the global flags
--OverrideOccupancy(OccupiedBlock, MANUALFLAG); -- force the flag to show occupied
print("Occupied Block = " .. BlockName[OccupiedBlock]);
end;
-- Stop the Garbage collector since this is a real-time program
-- Might also be possible to stop and restart it at the right time
-- Maybe stop it when the selected engine tag is encountered and restart
-- it when the selected caboose tag is encountered
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 BK[] 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(Block1, Block2, Sig1, Sig2)
if (((BK[Block1] ~= UNOCCUPIED) and (BK[Block2] ~= UNOCCUPIED)) and ((FSR[Sig1] == NDT) and (FSR[Sig2] == NDT))) then
if (Sig[Sig1] ~= AsStopSignal and Sig[Sig1] ~= AsStopandProceed) then
print("Traffic Stick at signal #", Sig1, " set to WEST");
FSR[Sig1] = WEST;
end;
if (Sig[Sig2] ~= AsStopSignal and Sig[Sig2] ~= AsStopandProceed) then
print("Traffic Stick at signal #", Sig2, " set to EAST");
FSR[Sig2] = EAST;
end;
end;
end;
--
local TUMMAX = 15; --maximum value for tumbledown delay counter
--
--
-- 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
--print("PermissiveinOpenCountry(" .. NextBlock .. "," .. ThisSig .. "," .. OppositeSig .. "," .. NextSig .. "," .. (MyDir or "nil") .. "," .. OppositeDir .. ")");
if (FSR[OppositeSig] == nil) then Stop("Traffic Stick nil in PermissiveinOpenCountry - Opposite Side #" .. OppositeSig); end;
if (FSR[NextSig] == nil) then Stop("Traffic Stick 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 (BK[NextBlock] ~= UNOCCUPIED) then goto POCAsStopandProceed; end;
-- Check for both kinds of Stop Aspects
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;
::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], " BK[NextBlock] ", BK[NextBlock]);
if (BK[NextBlock] ~= UNOCCUPIED) then goto HBPAsStopandProceed; end;
--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;
if (GetSwitchStatus(Switches[Switch1][CHAN_NUMBER], Switches[Switch1][AIU_NUMBER], Switches[Switch1][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 3 blocks between sidings
if (FSR[NextSig] == nil) then
Stop("Traffic Stick nil in HeadBlockAbsolute - Next Signal This Side #" .. NextSig);
end;
--Calculate Signal Head Block Absolute using Traffic Stick on next signal
if (BK[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;

function ApproachtoHeadBlock(NextBlock, ThisSig, OppositeSig, NextSig, MyDir, OppositeDir) -- Uses Traffic Sticks
-- This function never changes OppositeSig
-- this function never changes NextSig
--print("ApproachtoHeadBlock(" .. NextBlock .. "," .. ThisSig .. "," .. OppositeSig .. "," .. NextSig .. "," .. (MyDir or "nil") .. "," .. OppositeDir .. ")");
if (FSR[OppositeSig] == nil) then Stop("Traffic Stick 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 (BK[NextBlock] ~= UNOCCUPIED) then goto AHBAsStopandProceed; 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 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(Engine, TIU)
-- 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 #)
SetTrafficStick(LIONEL_IVES_BLOCK, YOUNGSTOWN_BLOCK, 1, 2);
SetTrafficStick(LOWELLVILLE_BLOCK, NEW_CASTLE_BLOCK, 11, 12); -- Now 2 blocks between sidings
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);
SetTrafficStick(MCKEES_ROCKS_BLOCK, LIONEL_IVES_BLOCK, 37, 38);

-- CALCULATE WESTBOUND SIGNAL ASPECTS
ApproachtoHeadBlock(MCKEES_ROCKS_BLOCK, 37, 38, 33, nil, EAST);
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_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);
HeadBlockAbsolute(YOUNGSTOWN_BLOCK, 5, nil, 1, WEST, nil); -- does not change NextSig #1
PermissiveinOpenCountry(LIONEL_IVES_BLOCK, 1, 2, 37, WEST, EAST);

-- CALCULATE EASTBOUND SIGNAL ASPECTS
ApproachtoHeadBlock(YOUNGSTOWN_BLOCK, 2, 1, 6, nil, WEST); -- does not change OppositeSig #1
HeadBlockPermissive(STRUTHERS_BLOCK, 6, nil, 10, STRUTHERS_EAST, STRUTHERS_WEST, nil, nil);
HeadBlockAbsolute(LOWELLVILLE_BLOCK, 10, nil, 12, EAST, nil);
ApproachtoHeadBlock(NEW_CASTLE_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);
HeadBlockAbsolute(MCKEES_ROCKS_BLOCK, 34, nil, 38, EAST, nil);
PermissiveinOpenCountry(LIONEL_IVES_BLOCK, 38, 37, 2, EAST, WEST);

-- CLEAR TRAFFIC STICK FOLLOWING SIGNAL UPGRADES

if (Sig[1] ~= AsStopSignal and Sig[1] ~= AsStopandProceed and TUMCNT[1] == 0) then FSR[1] = NDT; end;
if (Sig[2] ~= AsStopSignal and Sig[2] ~= AsStopandProceed and TUMCNT[2] == 0) then FSR[2] = NDT; end;

if (Sig[11] ~= AsStopSignal and Sig[11] ~= AsStopandProceed and TUMCNT[11] == 0) then FSR[11] = NDT; end;
if (Sig[12] ~= AsStopSignal and Sig[12] ~= AsStopandProceed and TUMCNT[12] == 0) then FSR[12] = NDT; end;

if (Sig[19] ~= AsStopSignal and Sig[19] ~= AsStopandProceed and TUMCNT[19] == 0) then FSR[19] = NDT; end;
if (Sig[20] ~= AsStopSignal and Sig[20] ~= AsStopandProceed and TUMCNT[20] == 0) then FSR[20] = NDT; end;

if (Sig[21] ~= AsStopSignal and Sig[21] ~= AsStopandProceed and TUMCNT[21] == 0) then FSR[21] = NDT; end;
if (Sig[22] ~= AsStopSignal and Sig[22] ~= AsStopandProceed and TUMCNT[22] == 0) then FSR[22] = NDT; end;

if (Sig[25] ~= AsStopSignal and Sig[25] ~= AsStopandProceed and TUMCNT[25] == 0) then FSR[25] = NDT; end;
if (Sig[26] ~= AsStopSignal and Sig[26] ~= AsStopandProceed and TUMCNT[26] == 0) then FSR[26] = NDT; end;

if (Sig[27] ~= AsStopSignal and Sig[27] ~= AsStopandProceed and TUMCNT[27] == 0) then FSR[27] = NDT; end;
if (Sig[28] ~= AsStopSignal and Sig[28] ~= AsStopandProceed and TUMCNT[28] == 0) then FSR[28] = NDT; end;

if (Sig[37] ~= AsStopSignal and Sig[37] ~= AsStopandProceed and TUMCNT[37] == 0) then FSR[37] = NDT; end;
if (Sig[38] ~= AsStopSignal and Sig[38] ~= AsStopandProceed and TUMCNT[38] == 0) then FSR[38] = NDT; end;

-- Display signal aspects
for Signal = 1, 38 do
if (Sig[Signal] ~= nil) then -- nil means no change
if (SavedSig[Signal] ~= Sig[Signal]) then -- if no change, don't need to SetAspect
SetAspect(Signal, Sig[Signal]);
--print("SetAspect on signal # ", Signal, " to ", AspectNames[Sig[Signal]]);
print("(", RunTime(),") SetAspect on signal # ", Signal, " to ", AspectNames[Sig[Signal]], " was ", AspectNames[SavedSig[Signal]]);
SavedSig[Signal] = Sig[Signal]; -- save current aspect
end;
end;
-- TODO consider saving signal here
end;
-- consider approach lighting but that is hard to do since
-- we don't have a connection between block and the signals at each end
-- of the block (on opposite sides)

Sleep(0.1);

return true;
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)));
-- follow on to process the detected engine
else -- it's a car
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));
-- follow on to process non-engine tags
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_YARD_DETECTOR and Reader == MCKEES_ROCKS_YARD_READER) then
if (Func30 == nil) then
Func30 = createMainLine(false);
end;
Func30("@ MCKEES_ROCKS_YARD_TAG ", EngineNo, TagLocation, CarModel, LIONEL_IVES_BLOCK, MCKEES_ROCKS_BLOCK);

-- 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_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_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 8-1
elseif (Detector == YOUNGSTOWN_YARD_DETECTOR and Reader == YOUNGSTOWN_YARD_READER) then
if (Func50 == nil) then
Func50 = createMainLine(false);
end;
Func50("@ YOUNGSTOWN_YARD_TAG ", EngineNo, TagLocation, CarModel, YOUNGSTOWN_BLOCK, LIONEL_IVES_BLOCK);
--
end;

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

function createMainLine(flag)
local WaitforCaboose = flag;
local MyLastBlockNo;
local function MainLine(DetectorID, EngineNo, TagLocation, CarModel, Block1, Block2)
---- uses globals: BK[]
if (isEngine(EngineNo) and ((TagLocation == TAG_ON_FRONT_TRUCK) or
(not WaitforCaboose and (TagLocation == TAG_ON_REAR_TRUCK)))) then -- if its an engine
print("Signal : " .. DetectorID .. "Engine " .. EngineNo .. " detected");
MyLastBlockNo = nil; -- reset last block number
if (BK[Block1] == UNOCCUPIED) then
print("Signal : occupying " .. BlockName[Block1] .. "(" .. Block1 .. ")");
BK[Block1] = BK[Block1] + 1;
MyLastBlockNo = Block2;
WaitforCaboose = true; -- save current state for the end of train detection call of this function
return true;
end;
if (BK[Block2] == UNOCCUPIED) then
print("Signal : occupying " .. BlockName[Block2] .. "(" .. Block2 .. ")");
BK[Block2] = BK[Block2] + 1;
MyLastBlockNo = Block1;
WaitforCaboose = true; -- save current state for the end of train detection call of this function
return true;
end;
---[[
if (BK[Block1] ~= UNOCCUPIED and BK[Block2] ~= UNOCCUPIED) then
-- special case of engine entering a siding block with the other track occupied by another train
-- occupy the siding and clear the head block when a end of train comes along
if (Block1 == ALIQUIPPA_BLOCK or
Block1 == MCKEES_ROCKS_BLOCK or
Block1 == BEAVER_FALLS_BLOCK or
Block1 == NEW_CASTLE_BLOCK or
Block1 == LOWELLVILLE_BLOCK or
Block1 == YOUNGSTOWN_BLOCK) then
print("Signal (Siding) : occupying " .. BlockName[Block2] .. "(" .. Block2 .. ")");
BK[Block2] = BK[Block2] + 1; -- bump the siding occupancy count
MyLastBlockNo = Block1; -- setup to decrement the head block occupancy count
WaitforCaboose = true; -- save current state for the end of train detection call of this function
return true;
end;
if (Block2 == ALIQUIPPA_BLOCK or
Block2 == MCKEES_ROCKS_BLOCK or
Block2 == BEAVER_FALLS_BLOCK or
Block2 == NEW_CASTLE_BLOCK or
Block2 == LOWELLVILLE_BLOCK or
Block2 == YOUNGSTOWN_BLOCK) then
print("Signal (Siding) : occupying " .. BlockName[Block1] .. "(" .. Block1 .. ")");
BK[Block1] = BK[Block1] + 1; -- bump the siding occupancy count
MyLastBlockNo = Block2; -- setup to decrement the head block occupancy count
WaitforCaboose = true; -- save current state for the end of train detection call of this function
return true;
end;
end;
--]]
return Stop("MainLine : Illegal Block Status");

elseif (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION) and WaitforCaboose) then
print("Signal : " .. DetectorID .. "End of Train detected");
BK[MyLastBlockNo] = BK[MyLastBlockNo] - 1; -- decrement the occupancy count
print("Signal : clearing " .. BlockName[MyLastBlockNo] .. "(" .. MyLastBlockNo .. ")");
MyLastBlockNo = nil; -- once MyLastBlockNo is used, set it to nil to catch possible future errors
WaitforCaboose = false;
end;
return true;
end;

return MainLine;
end;
--[[---------------------------------------------------------------------------------------]]

function cleanup(Engine, TIU)
-- 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
ReqBlockEvents(false); -- cancel request block occupancy flag events from the Layout
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["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";
--[[---------------------------------------------------------------------------------------]]

function OccButton(Eng, TIU)
printc(clGreen, "Detection Status -------------------------");
local x, FColor;
for x = FIRST_BLOCK, LAST_BLOCK do
local flag = BK[x];
if (flag == UNOCCUPIED) then
FColor = clBlack;
else
FColor = clRed;
end;
printc(FColor, BlockName[x] .. "(" .. x .. ") = ", flag)
end;
return true;
end
Function01Name, Function01Label = OccButton, "Detection Status";
--[[---------------------------------------------------------------------------------------]]

function F02()
local response = InputBox(title, "Occupy Block Number?", ALIQUIPPA_BLOCK);
local Enum = tonumber(response);
if (Enum ~= nil and Enum ~= 0) then -- if the result is a number, then use it, otherwise, ignore it
-- this script does not need to set the global flags
--OverrideOccupancy(Enum, MANUALFLAG); -- This flag is used by the Engine PC threads
if (Enum == STRUTHERS_STATION_TRACK) then Enum = STRUTHERS_BLOCK; end; -- correct for passing and station tracks
if (Enum == COLLEGE_STATION_TRACK) then Enum = COLLEGE_BLOCK; end;
if (Enum == J_L_PASSING_TRACK) then Enum = J_L_BLOCK; end;
BK[Enum] = BK[Enum] + 1; -- this flag is used by this APB Master thread
print("Occupy " .. BlockName[Enum] .. "(" .. Enum .. ")");
end
return true;
end
Function02Name, Function02Label = F02, "Occupy Block";
--[[---------------------------------------------------------------------------------------]]

function F03()
local response = InputBox(title, "Clear Block Number?", ALIQUIPPA_BLOCK);
local Enum = tonumber(response);
if (Enum ~= nil and Enum ~= 0) then -- if the result is a number, then use it, otherwise, ignore it
-- this script does not need to set the global flags
--OverrideOccupancy(Enum, UNOCCUPIED); -- This flag is used by the Engine PC threads
if (Enum == STRUTHERS_STATION_TRACK) then Enum = STRUTHERS_BLOCK; end; -- correct for passing and station tracks
if (Enum == COLLEGE_STATION_TRACK) then Enum = COLLEGE_BLOCK; end;
if (Enum == J_L_PASSING_TRACK) then Enum = J_L_BLOCK; end;
BK[Enum] = UNOCCUPIED; -- this flag is used by this APB Master thread
print("Clear " .. BlockName[Enum] .. "(" .. Enum .. ")");
end
return true;
end
Function03Name, Function03Label = F03, "Clear Block";
--[[---------------------------------------------------------------------------------------]]

function F04()
for Block = FIRST_BLOCK, LAST_BLOCK do
BK[Block] = UNOCCUPIED; -- this flag is used by this APB Master thread
-- this script does not need to set the global flags
--OverrideOccupancy(Block, UNOCCUPIED); -- This flag is used by the Engine PC threads
end;
print("All blocks manually cleared");
return true;
end
Function04Name, Function04Label = F04, "Clear All Blocks";
--[[---------------------------------------------------------------------------------------]]
function LLLButton()
if (LLLoop) then
print("Balloon Loop Off");
LLLoop = false;
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);
else
print("Balloon Loop On");
LLLoop = true;
Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);
end;
return true;
end
Function06Name, Function06Label = LLLButton, "Balloon Loop";
--[[---------------------------------------------------------------------------------------]]
function StartButton(Eng, TIU)
print("Start the Run");
-- TODO this neeeds to be thought out more wrt running
-- in non-loop (LLLoop == false) mode

--[[----------------------------------------------------]]
print(string.format("RunTime = %.2f seconds", RunTime()));
return true;
end
Function07Name, Function07Label = StartButton, "Start Run";
--[[---------------------------------------------------------------------------------------]]
--
-- Routine
-- 1. to accept TAG_EVENT from "Layout4 Block Occupancy.lua"
-- 2. to accept BLOCK_OCCUPANCY_EVENT from "Layout4 Block 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, FLAG_NUMBER, FLAG_VALUE, 0); -- set block occupancy flag
-- res = SetEvent(1, TAG_EVENT, DetectorNo, ReaderNo, ENGINE_OR_CABOOSE); -- simulate a tag
--
function event(FromPC, Type, P1, P2, P3)
if (FromPC > PCMAX) then
print("event() - From Layout Window - EventType = ", Type, " Parameters = ", P1, " ", P2, " ", P3);
else
print("event() - From PC# ", FromPC, " - EventType = ", Type, " Parameters = ", P1, " ", P2, " ", P3);
end
if (Type == BLOCK_OCCUPANCY_EVENT) then
-- Block Number must be non zero
if (P1 < 1 or P1 > FLAGLIMIT) then
return false;
end;
-- Block Number = P1
-- Flag Value = P2, value can be -1, 0 or 1 to (FLAGLIMIT -1)
-- P3 not used
-- update the block number for passing and station tracks
if (P1 == STRUTHERS_STATION_TRACK) then
P1 = STRUTHERS_BLOCK;
end;
if (P1 == COLLEGE_STATION_TRACK) then
P1 = COLLEGE_BLOCK;
end;
if (P1 == J_L_PASSING_TRACK) then
P1 = J_L_BLOCK;
end;

BK[P1] = P2; -- set occupancy flag

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
tag(P1, P2, 1561, TAG_ON_FRONT_TRUCK, UNKNOWN_CARMODEL, P_AND_LE, "1561", "NONE") ;
else -- caboose
tag(P1, P2, NOT_AN_ENGINE, TAG_ON_FRONT_TRUCK, CABOOSE, P_AND_LE, "123", "NONE") ;
end;
end;
return true; -- false=event failed, true=event succeeded
end

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


Based on the Program for the APB example defined in the August 2016 Railroad Model Craftsman by Bruce Chubb,  Version 3.0 Revision 07/17/2016 as updated by release 4 in 2019.

When the script is started, it creates all of the signals and sets their aspects to AsStop. You can see from this image how I placed the signals on the layout. I drew the layout as a point to point single track line. The signals above the red main line are the westbound signals (toward Youngstown) and the signals below the main line are the eastbound signals (toward McKees Rocks). You can click on the image to see a full size layout image.

Then it asks which blocks are occupied. Enter each block and end by entering a zero.

Since I captured this screen, I've improved the popup that asks for the occupied block(s). The new popup can actually show the names of all of the blocks in the layout for you to choose from.
I entered block 9 (Aliquippa). The signals are set to indicate that block 9 is occupied. At this point, the script is waiting for RFID tag detections. The detection of an engine tag causes the next block to become occupied. The detection of a caboose causes the last block to become unoccupied. You can click on the image to see a full size layout image.


With block 9 occupied, most of the signals are showing the aspect "Clear". Around block 9, though, the signals are more restrictive. The westbound absolute one-light signal #29 shows the aspect 
"Stop" and the eastbound absolute one-light signal #18 shows the aspect "Stop".  These absolute Stop signals prevent any engine from a move into block 9. The permissive signals #25 and #30 both show "Stop and Proceed". This permits the engine occupying block 9 to leave the block in either direction (though the system does not yet know the direction of the engine.)

So lets say that our engine is westbound. When our engine enters block 16, the system knows at that point that the engine is westbound. Block 16 is occupied. Signal 25 drops from "Clear" to "Stop and Proceed". When our caboose leaves block 9, that block become unoccupied. Signal #29 rises from "Stop" to Approach, allowing another westbound engine to enter block 9. Note that the absolute signal #18 is still at "Stop" to prevent any eastbound engine from moving into the path of our engine.

Signal #21 and #17 still both show "Clear" so our engine can keep on moving west. In this matter, our engine can continue into block 7. Once in block 7, signals #17 and #14 show aspect "Stop and Proceed". This allows another engine enter the passing siding at block 8. Remember that in APB signaling, getting an aspect like "Stop and Proceed" does not give you permission to proceed but just says that if you have the authority of a timetable or a train order to enter the siding (while our engine is on the main), you may do so.
Here is the layout window at the point where block 7 is occupied. You can click on the image to see a full size layout image.




Here is a screen capture video of my layout with an engine starting in block 9 (Aliquippa Block), going around in a big loop and ending again in block 9. You can see the signals changing in front of the engine and behind the engine.

Image loading....

Layout4 APB Master





Thinking Engine Headway and Signals.lua

With the Layout 4 APB Master.lua script running in one Program Control window, we can now run an engine controlling script in another PC window. Here is a script that runs an engine observing the indications of all of the signals. It uses tag detection to know where each engine is located. This script runs an engine either eastbound or westbound using the Balloon (Lionel-Ives) track loop in order to have a continuous run.

The purpose of this script is to run two engines in two PC windows. Set up one engine to run eastbound and the other engine to run westbound. The two engines will run around the layout in opposite directions stopping in a passing or thru track as needed to avoid a cornfield meet. You can start the two engines in any two blocks on the layout and the script will control them to avoid each other.


Here is the script  Thinking Engine Headway and Signals.lua 
(this may not be as up to date as the zip file linked to below):
--[[
---------------------------------------------------------------------------
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]]);
--
--[[
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 _;
--
--[[---------------------------------------------------------------------------------------]]
local title = "Signals";
local MyDirection = nil;
local OppDirection = nil;
local Headway = false; -- Set to true to enable Headway decision making
--[[
Headway decision making allows the engine to move into the next block
if that block is unoccupied and the signal for that block shows a clear aspect.

This is different than the non-headway condition. Then the engine can only move
into the next block if that block and all blocks into the next siding
show a clear aspect.

--]]
local Signals = false; -- Set to true to incorporate signals into the decision making
--[[---------------------------------------------------------------------------------------]]
local TagCount = 0;
-- Counter Labels
Counter01Label = "tags()";
Counter03Label = "Block Speed Limit";
local C3 = COUNTER03;
Counter04Label = "Target Distance";
local C4 = COUNTER04;
Counter05Label = "Stopping Distance";
local C5 = COUNTER05;
Counter06Label = "Stop Delay";
local C6 = COUNTER06;

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

local StartBlockNo, EndBlockNo;
local CurrentBlockNo, LastBlockNo, NextBlockNo;
local StartedFlag = false;
local RFIDFlag = false;
local ResetinProgress = false;

local LLLoop = true; -- start in looping mode
local Layout4;
local MyTIUNo;
local MyEngineNo = 16;
Title(title, MyEngineNo);
local SoundOff = false;
local EngineSoundSave;
local BeepOn = false;
local BlockSpeedLimit = 0; -- Smph
local LastBlockSpeedLimit = 0; -- Smph
local ExpectedReader = 0; -- check that we get the next expected tag reader
-- 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; -- Split New Castle Block
local Func40 = nil;
local Func50 = nil;
local Func60 = nil;
local Func61 = nil;
local Func100 = nil; -- Split Alquippa block
local Func101 = nil; -- Split Beaver Falls Block

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

function StopAfterDelay(StoppingBlockNo, EngineNo, direction, LastBlockSpeedLimit, BSL)
-- Calculate the stop delay based on parameters
--
-- LastBlockSpeedLimit is the speed at which the block was entered
-- BlockSpeedLimit is the speed to which the engine is being slowed before it is asked to stop
-- LastBlockSpeedLimit and BlockSpeedLimit may be equal
-- Either one may be greater than the other
--
-- Returns the stop delay in seconds and the target distance in inches
local Target_Station;
local Target_Distance;
local StopDelay;

local BlockSpeedLimit = BSL or LastBlockSpeedLimit;

Target_Station = BlockName[StoppingBlockNo];
if (EngineNo == 7 or EngineNo == 13) then
Target_Distance = Layout4.STOPBlock(StoppingBlockNo, direction, PASSENGER); --Passenger StoppingDistance;
else
Target_Distance = Layout4.STOPBlock(StoppingBlockNo, direction, FREIGHT); --Freight StoppingDistance;
end
if (Target_Distance == nil) then
Target_Distance = 0;
StopDelay = 0;
return EmergencyStop("SAD() : Target_Distance missing in Layout description");
end
SetCounter(C4, Target_Distance);
--print(BlockName[StoppingBlockNo] .. " " .. direction .. ", Target Distance = " .. Target_Distance);
--
-- First calculate the distance from entrance of the block until the engine gets down to Intermediate Speed
-- Note: this distance may be zero if the two speeds are the same
local StoppingDistance = Layout4.STOPDistance(EngineNo, BlockSpeedLimit);
-- then calculate the extra distance caused by the engine slowing down or the sufet distance caused
-- by the engine speeding up from the speed of the last block with the speed of this block.
local StepDistance = Layout4.STOPDistance(EngineNo, LastBlockSpeedLimit) - StoppingDistance;
print(string.format("SAD() : LastBlockSpeedLimit = %d Smph - Stopping Distance = %.3f in - BlockSpeedLimit = %d Smph - Stopping Distance = %.3f in - Step Distance = %.3f in",
LastBlockSpeedLimit, Layout4.STOPDistance(EngineNo, LastBlockSpeedLimit), BlockSpeedLimit, StoppingDistance, StepDistance));
-- Subtract these two values from the target distance and calcuate the Stop Delay
--
-- Equation for Stop Delay calculation
-- Time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr / (12 in/ft * 5280 ft/mile * X Smiles/hr)
StopDelay = (Target_Distance - StoppingDistance - StepDistance) * (48 / (12 * 5280 * BlockSpeedLimit)) * 3600;
-- if the StopDelay value comes out negative, that means we have to stop
-- very quickly. If that happens, set the Decell rate very high and then
-- set the speed to zero.
SetCounter(C5, StoppingDistance);
SetCounter(C6, StopDelay);
print(string.format("SAD() : %s : Target Distance = %d in - Stopping Distance = %.3f in - Speed %d Smph = Stop Delay %.3f seconds",
BlockName[StoppingBlockNo], Target_Distance, StoppingDistance, BlockSpeedLimit, StopDelay));
if (StopDelay < 0) then
StopDelay = 0;
return EmergencyStop("SAD() : Engine cannot stop in time (Stopping Distance > Target_Distance)");
end;
-- Stop delay any positive value including 0
if (StopDelay == 0) then
Rate(8, 8, StopDelay, EngineNo, MyTIUNo); -- stop as quickly as possible
end;
LastSentSpeed = SetSpeed(0, StopDelay, EngineNo, MyTIUNo); -- stop engine;
if (StopDelay == 0) then
Rate(Layout4.AccDecRate, Layout4.AccDecRate, StopDelay, EngineNo, MyTIUNo);
end;
print(string.format("Set speed to 0 after %.3f seconds", StopDelay))
return StopDelay, Target_Distance;
end;
--[[---------------------------------------------------------------------------------------]]
function createMainLine2Passing(flag)
local WaitforCaboose = flag;
--local SwitchClearingDistance = SwDistance or 24;
local EngineStopped = false;
local StopDelay = 0;
local GotBlock = false;
local StopTime = 0;
local Target_Distance = 0;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local HomeSignal, DistantSignal;
local TrainTime = 0;
local function MainLine2Passing(DetectorID, EngineNo, TagLocation, CarModel, Normal_Track, Reverse_Track, ThisSwitch)
-- uses globals: MyEngineNo, LastBlockNo, CurrentBlockNo, NextBlockNo
if (EngineNo == MyEngineNo and ((TagLocation == TAG_ON_FRONT_TRUCK) or
(not WaitforCaboose and (TagLocation == TAG_ON_REAR_TRUCK)))) then -- if MyEngineNo in tag

--[[--- Engine Detected -----]]

TrainTime = RunTime(); -- Engine detected time
print("ML2P : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
local Spd = GetSpeed(MyEngineNo, MyTIUNo); -- get speed as engine covers tag
if (Spd ~= BlockSpeedLimit) then
return EmergencyStop("ML2P : Speed over tag = " .. Spd .. " Smph - expected = " .. BlockSpeedLimit .. " Smph");
end;
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Register the new block's home and distant signals
local Aspect;
local NumLightsHome, NumLightsDistant;
HomeSignal = NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL]; -- next signal number
Aspect, NumLightsHome = GetAspect(HomeSignal); -- 1, 2 or 3 lights on the signal head
DistantSignal = NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]
_, NumLightsDistant = GetAspect(DistantSignal);
CreateCab(NumLightsHome, NumLightsDistant); -- create light signals - (home, distant)
RegisterCabSignals(HomeSignal, DistantSignal);
-- Adjust speed for the current block
LastBlockSpeedLimit = BlockSpeedLimit;
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo, Aspect); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
LastSentSpeed = SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("ML2P : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
print ("ML2P : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 1 ---------------------------------------------------------------------------------------------------
-- check if home signal is AsStopandProceed. If it is, stop in this block. Set EngineStopped = true.
-- NOTE: Mainline2Passing home signal can only be a permissive signal
if (Signals and (Aspect == AsStopandProceed)) then
-- stop in this block
print("ML2P : Home Signal aspect is Stop and Proceed. Stop the engine");
-- stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
if (Signals and (Aspect == AsStopSignal)) then
return EmergencyStop("ML2P : Home Signal aspect is an unexpected absolute signal.");
end;
-----------------------------------------------------------------------------------------------------]]
-- set next block number
if (MyDirection == SuperiorDirection) then
NextBlockNo = Normal_Track; -- Westbound trains are superior, take the through track
else
NextBlockNo = Reverse_Track; -- Eastbound trains are inferior, take the passing/station track
end;
-- If we didn't stop on a stop signal or stop and proceed, then attempt to obtain occupancy lock
if (EngineStopped == false) then
-- check for occupancy of thru track or passing/station track
-- choose based on superiority.
-- If we can't get a lock, schedule a stop once the entire train is in the block.
if (MyDirection == SuperiorDirection) then
if (TrySetOccupancy(Normal_Track, MyEngineNo)) then -- Thru track
-- Got a lock on the next block
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
else
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit,BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end
else -- MyDirection ~= SuperiorDirection
if (TrySetOccupancy(Reverse_Track, MyEngineNo)) then -- passing/station track
-- got a lock on the next block
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
else
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit,BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
end;
end;
--[=[
----- NOTE : all blocks are longer than 50 inches
-- engine was stopped quickly
if (not GotBlock and Target_Distance <= 50) then
-- Caboose may not have passed over detector yet and it may occupy two blocks
-- wait for one block to be locked (default wait 10 minutes)
-- wait for either Normal_Track or Reverse_Track to lock
local result;
if (MyDirection == SuperiorDirection) then
result, NextBlockNo = SingleSetOccupancy({Normal_Track}, MyEngineNo);
if (not result) then
return EmergencyStop("ML2P : Timed out waiting for block: " .. BlockName[Normal_Track]);
end;
else
result, NextBlockNo = SingleSetOccupancy({Reverse_Track}, MyEngineNo);
if (not result) then
return EmergencyStop("ML2P : Timed out waiting for block: " .. BlockName[Reverse_Track]);
end;
end;

GotBlock = true; -- indicate Block is locked
if (EngineStopped) then
-- start moving if stopped
local Aspect = GetCabAspect(HOMESIGNAL);
print ("ML2P : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 2 -----------------------------------------------------------------------------------------------
if (Signals and (Aspect == AsStopandProceed)) then
-- stop in this block
print("ML2P : Home Signal aspect is Stop and Proceed. Engine is stopped, waiting for home signal to clear");
local WaitTime = 1800; -- seconds
while (Aspect == AsStopandProceed) do
-- wait 'forever'
if (((WaitTime >> 1) % 10) == 0) then
print("Waiting for home signal at block " .. BlockName[CurrentBlockNo] .. " to clear");
end;
Sleep(2);
WaitTime = WaitTime - 2;
if (WaitTime <= 0) then
return EmergencyStop("Timed out waiting for home signal at block " .. BlockName[CurrentBlockNo] .. " to clear");
end;
end;
end;
-- NOTE: Mainline2Passing can only be permissive signal
if (Signals and Aspect == AsStopSignal) then
return EmergencyStop("ML2P : Home signal is an unexpected absolute signal"); -- Error in layout design
end;
-----------------------------------------------------------------------------------------------------]]
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 5;
else
StartDelay = StopDelay - UsedUpTime + 5; -- UsedUpTime always <= StopDelay
end;
--print("ML2P : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
LastBlockSpeedLimit = BlockSpeedLimit;
LastSentSpeed = SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("ML2P : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[CurrentBlockNo], BlockSpeedLimit, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
end;
--]=]
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
DisplayBlockNames("ML2P : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

--[[--- Caboose Detected -----]]

elseif (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION) and WaitforCaboose) then
-- in the elseif block, use the local copy of CurrentBlockNo and LastBlockNo
-- ASAP Once the caboose is detected (without any clearing delay) return switch at entrance of passing/station track back
-- to NORMAL if it is not already at NORMAL.
print("ML2P : " .. DetectorID .. "End of Train after " .. RunTime() - TrainTime .. " seconds (Stop Delay = " .. StopDelay .. ")");
--
if (ThisSwitch ~= nil) then
Switch(NORMAL, Switches[ThisSwitch][CHAN_NUMBER], Switches[ThisSwitch][AIU_NUMBER], Switches[ThisSwitch][TIU_NUMBER], 0);
print("ML2P : " .. SwitchName[ThisSwitch] .. " to NORMAL");
end;
--
--print("MyCurrentBlockNo " .. MyCurrentBlockNo .. " MyDirection " .. MyDirection);
--print("Opposite Direction " .. OppDirection);
--print("Detector = " .. DETECTOR .. " Reader = " .. READER);
--print(" Detector = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR));
--print(" Reader = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER));
ExpectedReader = Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR) * 10 + Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER);
print("Expected Reader Changed to " .. ExpectedReader);
--
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (not GotBlock) then
-- wait for block to be locked (default wait 10 minutes)
-- wait for MyNextBlockNo to be locked (either the thru or passing/station track)
local result;
-- set occupancy flag in MyNextBlockNo
if (MyNextBlockNo == Normal_Track) then
result, MyNextBlockNo = SingleSetOccupancy({Normal_Track}, MyEngineNo);
if (not result) then
return EmergencyStop("ML2P : Timed out waiting for block: " .. BlockName[Normal_Track]);
end;
end;
if (MyNextBlockNo == Reverse_Track) then
result, MyNextBlockNo = SingleSetOccupancy({Reverse_Track}, MyEngineNo);
if (not result) then
return EmergencyStop("ML2P : Timed out waiting for block: " .. BlockName[Reverse_Track]);
end;
end;
GotBlock = true; -- indicate needed Block is locked
end;


if (EngineStopped) then
-- start moving if stopped
-- Check if Stop and Proceed aspect is showing
local Aspect = GetCabAspect(HOMESIGNAL);
print ("ML2P : Home Signal in ", BlockName[MyCurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 3 -----------------------------------------------------------------------------------------------
if (Signals and (Aspect == AsStopandProceed)) then
-- if we are stopped in this block due to Stop and Proceed aspect, wait 10 seconds before restarting
Sleep(10);
end;
-- NOTE: Mainline2Passing can only be permissive signal
-----------------------------------------------------------------------------------------------------]]

local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 5;
else
StartDelay = StopDelay - UsedUpTime + 5; -- UsedUpTime always <= StopDelay
end;
--print("ML2P : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
LastBlockSpeedLimit = BlockSpeedLimit;
LastSentSpeed = SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("ML2P : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[MyCurrentBlockNo], BlockSpeedLimit, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;

WaitforCaboose = false;
end;
return true;
end;

return MainLine2Passing;
end;
--[[---------------------------------------------------------------------------------------]]
function createMainLine2MainLine(flag)
local WaitforCaboose = flag;
local EngineStopped = false;
local StopDelay = 0
local GotBlock = false;
local StopTime = 0;
local Target_Distance = 0;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local TrainTime = 0;
local function MainLine2MainLine(DetectorID, EngineNo, TagLocation, CarModel, ThisSwitch, Block)
-- uses globals: MyEngineNo, LastBlockNo, CurrentBlockNo, NextBlockNo
if (type(Block) ~= "table") then
return EmergencyStop("ML2ML Block not a table");
end
if (EngineNo == MyEngineNo and ((TagLocation == TAG_ON_FRONT_TRUCK) or
(not WaitforCaboose and (TagLocation == TAG_ON_REAR_TRUCK)))) then -- if MyEngineNo in tag

--[[--- Engine Detected -----]]

TrainTime = RunTime(); -- Engine detected time
print("ML2ML " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
local Spd = GetSpeed(MyEngineNo, MyTIUNo); -- get speed as engine covers tag
if (Spd ~= BlockSpeedLimit) then
return EmergencyStop("ML2ML : Speed over tag = " .. Spd .. " Smph - expected = " .. BlockSpeedLimit .. " Smph");
end;
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Register the new block's home and distant signals
local Aspect;
local NumLightsHome, NumLightsDistant;
local HomeSignal = NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL]; -- next signal number
Aspect, NumLightsHome = GetAspect(HomeSignal); -- 1, 2 or 3 lights on the signal head
local DistantSignal = NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]
_, NumLightsDistant = GetAspect(DistantSignal);
CreateCab(NumLightsHome, NumLightsDistant); -- create light signals - (home, distant)
RegisterCabSignals(HomeSignal, DistantSignal);
-- Adjust speed for the current block
LastBlockSpeedLimit = BlockSpeedLimit;
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo, Aspect); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
LastSentSpeed = SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("ML2ML : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
print ("ML2ML : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 1 -----------------------------------------------------------------------------------------------
-- Check if home signal is AsStopandProceed. If it is, stop in this block. Set EngineSTopped = true.
-- NOTE: MainLine2MainLine can only be permissive signal
if (Signals and (Aspect == AsStopandProceed)) then
-- stop in this block
print("ML2ML : Home Signal aspect is Stop and Proceed. Stop the engine");
-- stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
if (Signals and (Aspect == AsStopSignal)) then
return EmergencyStop("ML2ML : Home Signal is an unexpected absolute signal.");
end;
-----------------------------------------------------------------------------------------------------]]
NextBlockNo = Block[1]; -- set NextBlockNo to Block[1]
-- If we didn't stop on a stop signal or stop and proceed, then attempt to obtain occupancy lock
if (EngineStopped == false) then
-- no engine entrance switch settings
-- moving into a block requires the block to be empty (== 0) or
-- be already reserved for this engine (== EnginNo)
if (not TryMultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
EngineStopped = true; -- engine was stopped
GotBlock = false;
StopTime = RunTime();
else
-- got a lock on the needed block(s), continue on
StopDelay = 0;
EngineStopped = false; -- engine was not stopped
GotBlock = true;
StopTime = 0;
end;
end;
--[=[
----- NOTE : all blocks are longer than 50 inches
-- engine was stopped quickly
if (not GotBlock and Target_Distance <= 50) then
-- Caboose may not have passed over detector yet and train may occupy two blocks
-- wait for one/all blocks to be locked (default wait 10 minutes)
if (not MultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then -- set occupancy flag in all blocks
return EmergencyStop("ML2ML : Timed out waiting for " .. BlockName[NextBlockNo]);
end
GotBlock = true; -- indicate Block is locked
if (EngineStopped) then
-- start moving if stopped
local Aspect = GetCabAspect(HOMESIGNAL);
print ("ML2ML : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 2 -----------------------------------------------------------------------------------------------
if (Signals and (Aspect == AsStopandProceed)) then
-- stop in this block
print("ML2ML : Home Signal aspect is Stop and Proceed. Engine is stopped, waiting for home signal to clear");
local WaitTime = 1800; -- seconds
while (Aspect == AsStopandProceed) do
-- wait 'forever'
if (((WaitTime >> 1) % 10) == 0) then
print("Waiting for home signal at block " .. BlockName[CurrentBlockNo] .. " to clear");
end;
Sleep(2);
WaitTime = WaitTime - 2;
if (WaitTime <= 0) then
return EmergencyStop("Timed out waiting for home signal at block " .. BlockName[CurrentBlockNo] .. " to clear");
end;
end;
end;
-- NOTE: MainLine2MainLine can only be permissive signal
if (Signals and Aspect == AsStopSignal) then
return EmergencyStop("ML2ML : home signal is an unexpected absolute signal");
end;
-----------------------------------------------------------------------------------------------------]]
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 5;
else
StartDelay = StopDelay - UsedUpTime + 5; -- UsedUpTime always <= StopDelay
end;
--print("ML2ML : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
LastBlockSpeedLimit = BlockSpeedLimit;
LastSentSpeed = SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("ML2ML : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[CurrentBlockNo], BlockSpeedLimit, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
end;
--]=]
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
DisplayBlockNames("ML2ML : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

--[[--- Caboose Detected -----]]

elseif (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION) and WaitforCaboose) then
print("ML2ML : " .. DetectorID .. "End of Train after " .. RunTime() - TrainTime .. " seconds (Stop Delay = " .. StopDelay .. ")");
-- on any MainLine exit readers, the switch is before the tag reader so
-- the SwitchClearingDistance is zero
-- ASAP Once the caboose is detected (without any clearing delay) return switch at exit of passing/station track back
-- to NORMAL - this switch was set as result of the engine entering that last block
--
if (ThisSwitch ~= nil) then
Switch(NORMAL, Switches[ThisSwitch][CHAN_NUMBER], Switches[ThisSwitch][AIU_NUMBER], Switches[ThisSwitch][TIU_NUMBER], 0);
print("ML2ML : " .. SwitchName[ThisSwitch] .. " to NORMAL");
end;
--
--print("MyCurrentBlockNo " .. MyCurrentBlockNo .. " MyDirection " .. MyDirection);
--print("Opposite Direction " .. OppDirection);
--print("Detector = " .. DETECTOR .. " Reader = " .. READER);
--print(" Detector = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR));
--print(" Reader = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER));
ExpectedReader = Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR) * 10 + Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER);
print("Expected Reader Changed to " .. ExpectedReader);
--
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (not GotBlock) then
-- wait for one/all blocks to be locked (default wait 10 minutes)
if (not MultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then -- set occupancy flag in all blocks
return EmergencyStop("ML2ML : Timed out waiting for " .. BlockName[MyNextBlockNo]);
end;
GotBlock = true;
end;
if (EngineStopped) then
-- start moving if stopped
local Aspect = GetCabAspect(HOMESIGNAL);
print ("ML2ML : Home Signal in ", BlockName[MyCurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 3 -----------------------------------------------------------------------------------------------
if (Signals and (Aspect == AsStopandProceed)) then
-- if we are stopped in this block due to Stop and Proceed aspect, wait 10 seconds before restarting
Sleep(10);
end;
-- NOTE: MainLine2MainLine can only be permissive signal
-----------------------------------------------------------------------------------------------------]]
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 5;
else
StartDelay = StopDelay - UsedUpTime + 5; -- UsedUpTime always <= StopDelay
end;
--print("ML2ML : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
LastBlockSpeedLimit = BlockSpeedLimit;
LastSentSpeed = SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("ML2ML : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[MyCurrentBlockNo], BlockSpeedLimit, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;

WaitforCaboose = false;
end;
return true;
end;

return MainLine2MainLine;
end;
--[[---------------------------------------------------------------------------------------]]
function createPassing2MainLine(flag, SwDistance)
local WaitforCaboose = flag;
local SwitchClearingDistance = SwDistance or 0;
local EngineStopped = false;
local StopDelay = 0;
local GotBlock = false;
local StopTime = 0;
local Target_Distance = 0;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local TrainTime = 0;
local function Passing2MainLine(DetectorID, EngineNo, TagLocation, CarModel, Normal_Track, Reverse_Track, ThisSwitch, NextSwitch, Block)
-- uses globals: MyEngineNo, LastBlockNo, CurrentBlockNo, NextBlockNo
if (type(Block) ~= "table") then
return EmergencyStop("P2ML : Block not a table");
end
if (EngineNo == MyEngineNo and ((TagLocation == TAG_ON_FRONT_TRUCK) or
(not WaitforCaboose and (TagLocation == TAG_ON_REAR_TRUCK)))) then -- if MyEngineNo in tag

--[[--- Engine Detected -----]]

TrainTime = RunTime(); -- Engine detected time
print("P2ML : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
local Spd = GetSpeed(MyEngineNo, MyTIUNo); -- get speed as engine covers tag
if (Spd ~= BlockSpeedLimit) then
return EmergencyStop("P2ML : Speed over tag = " .. Spd .. " Smph - expected = " .. BlockSpeedLimit .. " Smph");
end;
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Register the new block's home and distant signals
local Aspect;
local NumLightsHome, NumLightsDistant;
local HomeSignal = NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL]; -- next signal number
Aspect, NumLightsHome = GetAspect(HomeSignal); -- 1, 2 or 3 lights on the signal head
local DistantSignal = NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]
_, NumLightsDistant = GetAspect(DistantSignal);
CreateCab(NumLightsHome, NumLightsDistant); -- create light signals - (home, distant)
RegisterCabSignals(HomeSignal, DistantSignal);
-- Adjust speed for the current block
LastBlockSpeedLimit = BlockSpeedLimit;
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo, Aspect); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
LastSentSpeed = SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("P2ML : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
print ("P2ML : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 1 -----------------------------------------------------------------------------------------------
-- Check if home signal is AsStopSignal. If it is, stop in this block. Set EngineSTopped = true.
-- NOTE: Passing2MainLine is the only case where an absolute signal can and must appear
if (Signals and (Aspect == AsStopSignal)) then
print("P2ML : Home Signal aspect is Stop Signal. Stop the engine");
-- stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
if (Signals and (Aspect == AsStopandProceed)) then
return EmergencyStop("P2ML : Home Signal is an unexpected permissive signal.");
end;
-----------------------------------------------------------------------------------------------------]]
--
-- ASAP, set the switch at entrance to passing/station track
--
if (ThisSwitch ~= nil) then
if (CurrentBlockNo == Normal_Track) then
Switch(NORMAL, Switches[ThisSwitch][CHAN_NUMBER], Switches[ThisSwitch][AIU_NUMBER], Switches[ThisSwitch][TIU_NUMBER], 0);
print("P2ML : " .. SwitchName[ThisSwitch] .. " to NORMAL");
else
Switch(REVERSE, Switches[ThisSwitch][CHAN_NUMBER], Switches[ThisSwitch][AIU_NUMBER], Switches[ThisSwitch][TIU_NUMBER], 0);
print("P2ML : " .. SwitchName[ThisSwitch] .. " to REVERSE");
end;
end;
NextBlockNo = Block[1]; -- set NextBlockNo to Block[1]
-- If we didn't stop on a stop signal or stop and proceed, then attempt to obtain occupancy lock
if (EngineStopped == false) then
-- moving into a block requires the block to be empty (== 0) or
-- be already reserved for this engine (== MyEngineNo)
if (not TryMultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
EngineStopped = true; -- engine was stopped
GotBlock = false;
StopTime = RunTime();
else
-- got a lock on the needed block(s), continue on
StopDelay = 0;
EngineStopped = false; -- engine was not stopped
GotBlock = true;
StopTime = 0;
end;
end;
--[=[
----- NOTE : all blocks are longer than 50 inches
-- Engine was stopped quickly
if (not GotBlock and Target_Distance <= 50) then
-- Caboose may not have passed over detector yet and train may occupy two blocks
-- wait for one/all blocks to be locked (default wait 10 minutes)
if (not MultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then -- set occupancy flag in all blocks
return EmergencyStop("P2ML : Timed out waiting for " .. BlockName[NextBlockNo]);
end
GotBlock = true; -- indicate Block is locked
if (EngineStopped) then
-- start moving if stopped
local Aspect = GetCabAspect(HOMESIGNAL);
print ("P2ML : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 2 -----------------------------------------------------------------------------------------------
-- Check that home signal is not AsStopSignal
-- NOTE: Passing2MainLine is the only case where an absolute signal can and must appear
if (Signals and Aspect == AsStopSignal) then
print("P2ML : Stop Signal");
WaitforStopSignaltoClear();
end;
-----------------------------------------------------------------------------------------------------]]
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 5;
else
StartDelay = StopDelay - UsedUpTime + 5; -- UsedUpTime always <= StopDelay
end;
--print("P2ML : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
LastBlockSpeedLimit = BlockSpeedLimit;
LastSentSpeed = SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("P2ML : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[CurrentBlockNo], BlockSpeedLimit, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
end;
--]=]
if (GotBlock) then
-- set NextSwitch switch
if (CurrentBlockNo == Normal_Track) then
Switch(NORMAL, Switches[NextSwitch][CHAN_NUMBER], Switches[NextSwitch][AIU_NUMBER], Switches[NextSwitch][TIU_NUMBER], 0);
print("P2ML : Next Switch " .. SwitchName[NextSwitch] .. " to NORMAL");
elseif (CurrentBlockNo == Reverse_Track) then
Switch(REVERSE, Switches[NextSwitch][CHAN_NUMBER], Switches[NextSwitch][AIU_NUMBER], Switches[NextSwitch][TIU_NUMBER], 0);
print("P2ML : NextSwitch " .. SwitchName[NextSwitch] .. " to REVERSE");
end;
end;
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
DisplayBlockNames("P2ML : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

--[[--- Caboose Detected -----]]

elseif (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION) and WaitforCaboose) then
-- in the elseif block, use the local copy of CurrentBlockNo and LastBlockNo
print("P2ML : " .. DetectorID .. "End of Train after " .. RunTime() - TrainTime .. " seconds (Stop Delay = " .. StopDelay .. ")");
if (SwitchClearingDistance > 0) then
-- equation for Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
local SleepDelay = (SwitchClearingDistance) * (48 / (12 * 5280 * BlockSpeedLimit)) * 3600;
print(string.format("P2ML : %s : Switch Clearing Distance = %.3f in - Speed %d Smph = Sleep Delay %.3f seconds",
BlockName[MyCurrentBlockNo], SwitchClearingDistance, BlockSpeedLimit, SleepDelay));
Sleep(SleepDelay); -- give the caboose time to clear the switch
print("P2ML : " .. "Switch cleared after " .. SleepDelay .. " seconds")
end;
--
-- Once we reach the switch clearing distance, return switch at entrance to passing/station track back
-- to NORMAL - this switch was set as result of the engine entering the block
--
if (ThisSwitch ~= nil) then
Switch(NORMAL, Switches[ThisSwitch][CHAN_NUMBER], Switches[ThisSwitch][AIU_NUMBER], Switches[ThisSwitch][TIU_NUMBER], 0);
print("P2ML : " .. SwitchName[ThisSwitch] .. " to NORMAL");
end;
--
--print("MyCurrentBlockNo " .. MyCurrentBlockNo .. " MyDirection " .. MyDirection);
--print("Opposite Direction " .. OppDirection);
--print("Detector = " .. DETECTOR .. " Reader = " .. READER);
--print(" Detector = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR));
--print(" Reader = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER));
ExpectedReader = Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR) * 10 + Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER);
print("Expected Reader Changed to " .. ExpectedReader);
--
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (not GotBlock) then
-- wait for one/all blocks to be locked (default wait 10 minutes)
if (not MultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then -- set occupancy flag in all blocks
return EmergencyStop("P2ML : Timed out waiting for " .. BlockName[MyNextBlockNo]);
end;
GotBlock = true; -- indicate Block is locked
-- set next switch (passing/station track exit) based on current block
if (NextSwitch ~= nil and GotBlock) then
-- set NextSwitch switch
if (MyCurrentBlockNo == Normal_Track) then
Switch(NORMAL, Switches[NextSwitch][CHAN_NUMBER], Switches[NextSwitch][AIU_NUMBER], Switches[NextSwitch][TIU_NUMBER], 0);
print("P2ML : Next Switch " .. SwitchName[NextSwitch] .. " to NORMAL");
end;
if (MyCurrentBlockNo == Reverse_Track) then
Switch(REVERSE, Switches[NextSwitch][CHAN_NUMBER], Switches[NextSwitch][AIU_NUMBER], Switches[NextSwitch][TIU_NUMBER], 0);
print("P2ML : Next Switch " .. SwitchName[NextSwitch] .. " to REVERSE");
end;
end;
end;

if (EngineStopped) then
-- start moving if stopped
local Aspect = GetCabAspect(HOMESIGNAL);
print ("P2ML : Home Signal in ", BlockName[MyCurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 3 -----------------------------------------------------------------------------------------------
-- check if home signal is AsStopSignal
-- NOTE: Passing2Mainline is the only case where an absolute signal can and must appear
if (Signals and Aspect == AsStopSignal) then
print("P2ML : Stop Signal");
WaitforStopSignaltoClear();
end;
-----------------------------------------------------------------------------------------------------]]
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 5;
else
StartDelay = StopDelay - UsedUpTime + 5; -- UsedUpTime always <= StopDelay
end;
--print("P2ML : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
LastBlockSpeedLimit = BlockSpeedLimit;
LastSentSpeed = SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("P2ML : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[MyCurrentBlockNo], BlockSpeedLimit, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;

WaitforCaboose = false;
end;
return true;
end;

return Passing2MainLine;
end;
--[[---------------------------------------------------------------------------------------]]

function createMainLine2Yard(flag, SwDistance)
local WaitforCaboose = flag;
local SwitchClearingDistance = SwDistance or 24;
local EngineStopped = false;
local StopDelay = 0;
local GotBlock = false;
local StopTime = 0;
local Target_Distance;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local TrainTime = 0;
local function MainLine2Yard(DetectorID, EngineNo, TagLocation, CarModel, Yard1, Yard2, ThisSwitch, Block)
if (type(Block) ~= "table") then
return EmergencyStop("ML2Y : Block not a table");
end
if (EngineNo == MyEngineNo and ((TagLocation == TAG_ON_FRONT_TRUCK) or
(not WaitforCaboose and (TagLocation == TAG_ON_REAR_TRUCK)))) then -- if MyEngineNo in tag

--[[--- Engine Detected -----]]

TrainTime = RunTime(); -- Engine detected time
print("ML2Y : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
local Spd = GetSpeed(MyEngineNo, MyTIUNo); -- get speed as engine covers tag
if (Spd ~= BlockSpeedLimit) then
return EmergencyStop("ML2Y : Speed over tag = " .. Spd .. " Smph - expected = " .. BlockSpeedLimit .. " Smph");
end;
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Register the new block's home and distant signals
local Aspect;
local NumLightsHome, NumLightsDistant;
local HomeSignal = NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL]; -- next signal number
Aspect, NumLightsHome = GetAspect(HomeSignal); -- 1, 2 or 3 lights on the signal head
local DistantSignal = NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]
_, NumLightsDistant = GetAspect(DistantSignal);
CreateCab(NumLightsHome, NumLightsDistant); -- create light signals - (home, distant)
RegisterCabSignals(HomeSignal, DistantSignal);
-- Adjust speed for the current block
LastBlockSpeedLimit = BlockSpeedLimit;
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo, Aspect); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
LastSentSpeed = SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("ML2Y : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
print ("ML2Y : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 1 ---------------------------------------------------------------------------------------------------
-- Check if home signal is AsStopandProceed. If it is, stop in this block. Set EngineSTopped = true.
-- Then go on to check TrySetOccupancy and if we get it, start moving again.
-- NOTE: MainLine2Yard can only be a permissive signal
if (Signals and (Aspect == AsStopandProceed)) then
-- stop in this block
print("ML2Y : Home Signal aspect is Stop and Proceed. Stop the engine");
-- stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
if (Signals and (Aspect == AsStopSignal)) then
return EmergencyStop("ML2Y : Home Signal is an unexpected absolute signal.");
end;
-----------------------------------------------------------------------------------------------------]]
NextBlockNo = Block[1]; -- set NextBlockNo to Block[1]
-- If we didn't stop on a stop signal or stop and proceed, then attempt to obtain occupancy lock
if (EngineStopped == false) then
-- check for occupancy clear Yard2 or Yard1 - Ending block must be unoccupied otherwise stop engine
if (TrySetOccupancy(NextBlockNo, MyEngineNo)) then
StopDelay = 0;
EngineStopped = false;
GotBlock = true;
StopTime = 0;
else
-- if we can't get a lock, stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
end;
--[=[
----- NOTE : all blocks are longer than 50 inches
-- engine was stopped quickly
if (not GotBlock and Target_Distance <= 50) then
-- Caboose may not have passed over detector yet and it may occupy two blocks
-- wait for all blocks to be locked (default wait 10 minutes)
-- wait for either Normal_Track or Reverse_Track to clear
SetOccupancy(NextBlockNo, MyEngineNo); -- set occupancy flag in NextBlockNo

GotBlock = true; -- indicate Block is locked
if (EngineStopped) then
-- start moving if stopped
local Aspect = GetCabAspect(HOMESIGNAL);
print ("ML2Y : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 2 -----------------------------------------------------------------------------------------------
-- NOTE: MainLine2Yard can only be a permissive signal
if (Signals and Aspect == AsStopSignal) then
return EmergencyStop("ML2Y : home signal is an unexpected absolute signal");
end;
-----------------------------------------------------------------------------------------------------]]
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 5;
else
StartDelay = StopDelay - UsedUpTime + 5; -- UsedUpTime always <= StopDelay
end;
--print("ML2Y : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
LastBlockSpeedLimit = BlockSpeedLimit;
LastSentSpeed = SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("ML2Y : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[CurrentBlockNo], BlockSpeedLimit, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;
end;
--]=]
if (GotBlock) then
-- set ThisSwitch switch
if (NextBlockNo == Yard1) then
Switch(NORMAL, Switches[ThisSwitch][CHAN_NUMBER], Switches[ThisSwitch][AIU_NUMBER], Switches[ThisSwitch][TIU_NUMBER], 0);
print("ML2Y : Switch " .. SwitchName[ThisSwitch] .. " to NORMAL");
end;
if (NextBlockNo == Yard2) then
Switch(REVERSE, Switches[ThisSwitch][CHAN_NUMBER], Switches[ThisSwitch][AIU_NUMBER], Switches[ThisSwitch][TIU_NUMBER], 0);
print("ML2Y : Switch " .. SwitchName[ThisSwitch] .. " to REVERSE");
end;
end;
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
DisplayBlockNames("ML2Y : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

--[[--- Caboose Detected -----]]

elseif (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION) and WaitforCaboose) then
print("ML2Y : " .. DetectorID .. "End of Train after " .. RunTime() - TrainTime .. " seconds (Stop Delay = " .. StopDelay .. ")");
if (SwitchClearingDistance > 0) then
-- equation for Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
local SleepDelay = (SwitchClearingDistance) * (48 / (12 * 5280 * BlockSpeedLimit)) * 3600;
print(string.format("ML2Y : %s : Switch Clearing Distance = %.3f in - Speed %d Smph = Sleep Delay %.3f seconds",
BlockName[MyCurrentBlockNo], SwitchClearingDistance, BlockSpeedLimit, SleepDelay));
Sleep(SleepDelay); -- give the caboose time to clear the switch
print("ML2Y : " .. "Switch cleared after " .. SleepDelay .. " seconds")
end;
--
--print("MyCurrentBlockNo " .. MyCurrentBlockNo .. " MyDirection " .. MyDirection);
--print("Opposite Direction " .. OppDirection);
--print("Detector = " .. DETECTOR .. " Reader = " .. READER);
--print(" Detector = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR));
--print(" Reader = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER));
ExpectedReader = Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR) * 10 + Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER);
print("Expected Reader Changed to " .. ExpectedReader);
--
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (not GotBlock) then
-- wait for MyNextBlockNo to be locked (either Yard track)
local result;
-- set occupancy flag in MyNextBlockNo
SetOccupancy(MyNextBlockNo, MyEngineNo); -- set occupancy flag in MyNextBlockNo
-- set NextSwitch switch
if (NextSwitch ~= nil) then
if (MyNextBlockNo == Yard1) then
Switch(NORMAL, Switches[NextSwitch][CHAN_NUMBER], Switches[NextSwitch][AIU_NUMBER], Switches[NextSwitch][TIU_NUMBER], 0);
print("ML2Y : Switch " .. SwitchName[NextSwitch] .. " to NORMAL");
end;
if (MyNextBlockNo == Yard2) then
Switch(REVERSE, Switches[NextSwitch][CHAN_NUMBER], Switches[NextSwitch][AIU_NUMBER], Switches[NextSwitch][TIU_NUMBER], 0);
print("ML2Y : Switch " .. SwitchName[NextSwitch] .. " to REVERSE");
end;
end;
GotBlock = true; -- indicate needed Block is locked
end;
if (EngineStopped) then
-- start moving if stopped
local Aspect = GetCabAspect(HOMESIGNAL);
print ("ML2Y : Home Signal in ", BlockName[MyCurrentBlockNo], " : " .. AspectNames[Aspect]);
---[[--TODO 3 -----------------------------------------------------------------------------------------------
-- NOTE: MainLine2Yard can only be a permissive signal
if (Signals and Aspect == AsStopSignal) then
return EmergencyStop("ML2Y : home signal is an unexpected absolute signal");
end;
-----------------------------------------------------------------------------------------------------]]
local UsedUpTime = RunTime() - StopTime; -- never negative
local StartDelay;
if (UsedUpTime > StopDelay) then -- if StopDelay == 0 then always true
StartDelay = 5;
else
StartDelay = StopDelay - UsedUpTime + 5; -- UsedUpTime always <= StopDelay
end;
--print("ML2Y : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
LastBlockSpeedLimit = BlockSpeedLimit;
LastSentSpeed = SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("ML2Y : %s : Speed %d Smph = Start Delay %.3f seconds",
BlockName[MyCurrentBlockNo], BlockSpeedLimit, StartDelay));
EngineStopped = false; -- indicate that we handled the stop here
end;

WaitforCaboose = false;
end;
return true;
end

return MainLine2Yard;
end;

--[[---------------------------------------------------------------------------------------]]
function createStopInYard(flag, SwDistance)
local WaitforCaboose = flag;
local SwitchClearingDistance = SwDistance or 24;
local EngineStopped = false;
local StopDelay = 0;
local StopTime = 0;
local Target_Distance;
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local TrainTime = 0;
local function StopInYard(DetectorID, EngineNo, TagLocation, CarModel, OffLayoutBlock)
-- uses globals: MyEngineNo, LastBlockNo, CurrentBlockNo, NextBlockNo
if (type(OffLayoutBlock) ~= "table") then
return EmergencyStop("SIY : OffLayoutBlock not a table");
end
if (EngineNo == MyEngineNo and ((TagLocation == TAG_ON_FRONT_TRUCK) or
(not WaitforCaboose and (TagLocation == TAG_ON_REAR_TRUCK)))) then -- if MyEngineNo in tag

--[[--- Engine Detected -----]]

TrainTime = RunTime(); -- Engine detected time
print("SIY : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
local Spd = GetSpeed(MyEngineNo, MyTIUNo); -- get speed as engine covers tag
if (Spd ~= BlockSpeedLimit) then
return EmergencyStop("SIY : Speed over tag = " .. Spd .. " Smph - expected = " .. BlockSpeedLimit .. " Smph");
end;
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Adjust speed for the current block
LastBlockSpeedLimit = BlockSpeedLimit;
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo, AsNone); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
LastSentSpeed = SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("SIY : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
--
NextBlockNo = OffLayoutBlock[1];

-- schedule stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
EngineStopped = true;
StopTime = RunTime();

WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
DisplayBlockNames("SIY : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

--[[--- Caboose Detected -----]]

elseif (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION) and WaitforCaboose) then
print("SIY : " .. DetectorID .. "End of Train after " .. RunTime() - TrainTime .. " seconds (Stop Delay = " .. StopDelay .. ")");
if (SwitchClearingDistance > 0) then
-- equation for Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
local SleepDelay = (SwitchClearingDistance) * (48 / (12 * 5280 * BlockSpeedLimit)) * 3600;
print(string.format("SIY : %s : Switch Clearing Distance = %.3f in - Speed %d Smph = Sleep Delay %.3f seconds",
BlockName[MyCurrentBlockNo], SwitchClearingDistance, BlockSpeedLimit, SleepDelay));
Sleep(SleepDelay); -- give the caboose time to clear the switch
print("SIY : " .. "Switch cleared after " .. SleepDelay .. " seconds")
end;
--
--print("MyCurrentBlockNo " .. MyCurrentBlockNo .. " MyDirection " .. MyDirection);
--print("Opposite Direction " .. OppDirection);
--print("Detector = " .. DETECTOR .. " Reader = " .. READER);
--print(" Detector = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR));
--print(" Reader = " .. Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER));
ExpectedReader = Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, DETECTOR) * 10 + Layout4.STOPBlock(MyCurrentBlockNo, OppDirection, READER);
print("Expected Reader Changed to " .. ExpectedReader);
--
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (EngineStopped) then
print("SIY : " .. DetectorID .. "Stopped!");
RFIDFlag = false;
end
WaitforCaboose = false;
end;
return true;
end;

return StopInYard;
end;

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

function setup(Engine, TIU)
print("setup() Running");

-- Instrumented Layout 4
Layout4 = require([[Layout4]]); -- All of the details about the Layout
-- an undirected weighted double vertex graph
MyTIUNo = Layout4.MASTER_TIU;
MyEngineNo = Engine; -- this engine number may be changed by the query below
Title(title, MyEngineNo);
print("Layout : " .. Layout4.name);

print([[Run "Layout APB Master.lua" to handle the signals]]);

math.randomseed(os.time()); -- random seed

-- this is done in Layout4 APB Master
--ClearAllOccupancy();

ShowLayout();
--[[--------------------------------------------]]
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);

-- Show new title with Engine # and Name and put Engine # into Engine Number Spinner
local Name = GetName(MyEngineNo);
Title(title .. " : Engine #" .. MyEngineNo .. " " .. Name, MyEngineNo);
--
-- start of run
--
--[[--------------------------------------------]]
-- Ask the dispatcher for direction
--
print("Select Direction");
local DirValue = InputDropDown(title, "Select Direction", MyDirection, {"1 Westbound", "2 Eastbound", "3 Northbound", "4 Southbound"});
--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 = "Signals WB"; -- Westbound
OppDirection = EB;
end;
if (MyDirection == EB) then
title = "Signals 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

for BlockNameIndex, Name in pairs(BlockName) do
if (BlockNameIndex <= 100) then
--print(Name .. "(" .. BlockNameIndex .. ")");
if (MyDirection == EB) then
-- don't allow selection of thru blocks
if ((BlockNameIndex ~= J_L_THRU_TRACK) and (BlockNameIndex ~= COLLEGE_THRU_TRACK) and (BlockNameIndex ~= STRUTHERS_THRU_TRACK)) then
BlockNameList[BlockNameIndex] = BlockNameIndex .. " " .. Name;
end;
end;
if (MyDirection == WB) then
-- don't allow selection of station/passing blocks
if ((BlockNameIndex ~= J_L_PASSING_TRACK) and (BlockNameIndex ~= COLLEGE_STATION_TRACK) and (BlockNameIndex ~= STRUTHERS_STATION_TRACK)) then
BlockNameList[BlockNameIndex] = BlockNameIndex .. " " .. Name;
end;
end;
end;
end;
--
-- read Starting Block Number
print("Select Starting Block");
--
local StartingBlock = InputDropDown(title, "Select Starting Block", YOUNGSTOWN_BLOCK, 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
--[[--------------------------------------------]]
print("Setup : Starting Block = " .. BlockName[StartBlockNo]);
CurrentBlockNo = StartBlockNo;
if (MyDirection == WB) then
EndBlockNo = YOUNGSTOWN_YARD1_BLOCK; -- EndBlockNo is not used because this sketch loops forever
else
EndBlockNo = MCKEES_ROCKS_YARD1_BLOCK; -- EndBlockNo is not used because this sketch loops forever
end
-- set the next expected detector and reader
--print("CurrentBlockNo " .. CurrentBlockNo .. " MyDirection " .. MyDirection);
--print("Opposite Direction " .. OppDirection);
--print("Detector = " .. DETECTOR .. " Reader = " .. READER);
--print(" Detector = " .. Layout4.STOPBlock(CurrentBlockNo, OppDirection, DETECTOR));
--print(" Reader = " .. Layout4.STOPBlock(CurrentBlockNo, OppDirection, READER));
ExpectedReader = Layout4.STOPBlock(CurrentBlockNo, OppDirection, DETECTOR) * 10 + Layout4.STOPBlock(CurrentBlockNo, OppDirection, READER)
print("Expected Reader = " .. ExpectedReader);
-----------------------------------------------------------------------------------------------------
-- create and register the cab signals
print("Setup : Creating Cab Signals");
-- Register the new block's home and distant signals
local NumLightsHome, NumLightsDistant;
local HomeSignal = NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL]; -- next signal number
_, NumLightsHome = GetAspect(HomeSignal); -- 1, 2 or 3 lights on the signal head
local DistantSignal = NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]
_, NumLightsDistant = GetAspect(DistantSignal);
CreateCab(NumLightsHome, NumLightsDistant); -- create light signals - (home, distant)
print("Setup : Registering the Cab Signals #", HomeSignal, " and #", DistantSignal);
RegisterCabSignals(HomeSignal, DistantSignal);
-- associate the Home cab signal with next signal and
-- the Distant cab signal with second signal.
-- Signals must be created by the "Layout4 Signal Master.lua"
-- script which should be run in a different PC window
print("Setup : Reflecting signals from Layout window into the Cab Display");
print("Setup : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[(GetCabAspect(HOMESIGNAL))]);
print("Setup : Distant Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[(GetCabAspect(DISTANTSIGNAL))]);

print("Setup : press [Initialize Layout] then [Start Run] to begin");
-----------------------------------------------------------------------------------------------------
--
BlockSpeedLimit = BlockSpeed[MyDirection][CurrentBlockNo];
LastBlockSpeedLimit = BlockSpeedLimit;
SetCounter(C3, BlockSpeedLimit);
--
-- Set Loop Switch
if (LLLoop) then
Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0)
else
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0)
end;
--[[-------------------------]]
if (MyDirection == EB) then
print("Setup : Eastbound");
else
print("Setup : Westbound");
end
--[[-------------------------]]
-- read up current sound volume
EngineSoundSave = GetVolume(MASTER_VOLUME, MyEngineNo, MyTIUNo); -- get Volume
--
OverrideOccupancy(CurrentBlockNo, MyEngineNo); -- force set occupancy flag on the starting block
-- Stop the Garbage collector since this is a real-time program
-- Might also be possible to stop and restart it at the right time
-- Maybe stop it when the selected engine tag is encountered and restart
-- it when the selected caboose tag is encountered
collectgarbage("stop")
print("Setup : Garbage Collection stopped");
--collectgarbage("collect")
--collectgarbage("restart")
--
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]

function DisplayBlockNames(Title, LastBlockNo, CurrentBlockNo, NextBlockNo)
print(Title .. BlockName[LastBlockNo] .. " > " .. BlockName[CurrentBlockNo] .. " > " .. BlockName[NextBlockNo]);
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

local MyReader = (Detector * 10) + Reader;

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)));
if (MyReader ~= ExpectedReader) then
return EmergencyStop("Got Reader = " .. MyReader .. " Expected Reader = " .. ExpectedReader);
end
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
-- (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
--[[--------------------------]]
-- Westbound trains are superior
--[[--------------------------]]
--[[--------------------------------- Westbound --------------------------------------------]]
--
-- ResetinProgress probably does not work anymore
--
if (MyDirection == WB) then
if (ResetinProgress) then
-- run back to MCKEES_ROCKS_YARD_TAG
if (Detector == MCKEES_ROCKS_YARD_DETECTOR and Reader == MCKEES_ROCKS_YARD_READER) then
local DetectorID = "@ MCKEES_ROCKS_YARD_TAG ";
local StopDelay = 0;
if (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION)) then -- going in reverse, execute on caboose detection
print(DetectorID .. "End of Train detected");
-- schedule stop in StartBlockNo
StopDelay = StopAfterDelay(StartBlockNo, EngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
SetDirection(FORWARD, StopDelay + 5, EngineNo, MyTIUNo);
ResetinProgress = false;
end;
end;
return true;

elseif (not RFIDFlag) then -- ignore tags unless engine is moving
return true;

-- MCKEES_ROCKS_YARD_TAG
elseif (Detector == MCKEES_ROCKS_YARD_DETECTOR and Reader == MCKEES_ROCKS_YARD_READER) then
if (Func30 == nil) then
Func30 = createMainLine2Passing(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func30("@ MCKEES_ROCKS_YARD_TAG ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, nil);

-- ALIQUIPPA_EAST_TAG
elseif (Detector == ALIQUIPPA_EAST_DETECTOR and Reader == ALIQUIPPA_EAST_READER) then
if (Func51 == nil) then
Func51 = createPassing2MainLine(false, SwitchClearingInches[ALIQUIPPA_EAST_TAG][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set
Func51("@ ALIQUIPPA_EAST_TAG ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, ALIQUIPPA_EAST, ALIQUIPPA_WEST,
{ALIQUIPPA_BLOCK, WEST_ALIQUIPPA_BLOCK, MONACA_BLOCK, FALLSTON_BLOCK, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK});

-- ALIQUIPPA_WEST_TAG
elseif (Detector == ALIQUIPPA_WEST_DETECTOR and Reader == ALIQUIPPA_WEST_READER) then
if (Func10 == nil) then
Func10 = createMainLine2MainLine(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func10("@ ALIQUIPPA_WEST_TAG ", EngineNo, TagLocation, CarModel, ALIQUIPPA_WEST,
{WEST_ALIQUIPPA_BLOCK, MONACA_BLOCK, FALLSTON_BLOCK, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK});

-- West Aliquippa detector
elseif (Detector == WEST_ALIQUIPPA_DETECTOR and Reader == WEST_ALIQUIPPA_READER) then
if (Func100 == nil) then
Func100 = createMainLine2MainLine(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func100("@ WEST_ALIQUIPPA_TAG ", EngineNo, TagLocation, CarModel, nil,
{MONACA_BLOCK, FALLSTON_BLOCK, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK});

-- MONACA_EAST_TAG
elseif (Detector == MONACA_EAST_DETECTOR and Reader == MONACA_EAST_READER) then
-- entering tunnel, reduce volume
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK and SoundOff == false) then
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 5/6), 0, MyEngineNo, MyTIUNo);
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 4/6), 1, MyEngineNo, MyTIUNo);
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 1/2), 2, MyEngineNo, MyTIUNo);
end;
if (Func60 == nil) then
Func60 = createMainLine2MainLine(false);
end;
Func60("@ MONACA_EAST_TAG ", EngineNo, TagLocation, CarModel, nil,
{FALLSTON_BLOCK, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK});

-- MONACA_WEST_TAG
elseif (Detector == MONACA_WEST_DETECTOR and Reader == MONACA_WEST_READER) then
-- leaving tunnel, increase volume
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK and SoundOff == false) then
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 4/6), 0, MyEngineNo, MyTIUNo);
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 5/6), 1, MyEngineNo, MyTIUNo);
SetVolume(MASTER_VOLUME, EngineSoundSave, 2, MyEngineNo, MyTIUNo);
end;
if (Func61 == nil) then
Func61 = createMainLine2MainLine(false);
end;
--Func61("@ MONACA_WEST_TAG ", EngineNo, TagLocation, CarModel, COLLEGE_THRU_TRACK, COLLEGE_STATION_TRACK, nil);
Func61("@ MONACA_EAST_TAG ", EngineNo, TagLocation, CarModel, nil,
{BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK});

elseif (Detector == FALLSTON_DETECTOR and Reader == FALLSTON_READER) then
if (Func101 == nil) then
Func101 = createMainLine2Passing(false);
end;
Func101("@ FALLSTON_TAG ", EngineNo, TagLocation, CarModel, COLLEGE_THRU_TRACK, COLLEGE_STATION_TRACK, nil);

-- COLLEGE_EAST_TAG
elseif (Detector == COLLEGE_EAST_DETECTOR and Reader == COLLEGE_EAST_READER) then
if (Func41 == nil) then
Func41 = createPassing2MainLine(false, SwitchClearingInches[COLLEGE_EAST_TAG][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set
Func41("@ COLLEGE_EAST_TAG ", EngineNo, TagLocation, CarModel, COLLEGE_THRU_TRACK, COLLEGE_STATION_TRACK, COLLEGE_EAST, COLLEGE_WEST,
{NEW_CASTLE_BLOCK, LOWELLVILLE_BLOCK, STRUTHERS_THRU_TRACK});

-- COLLEGE_WEST_TAG
elseif (Detector == COLLEGE_WEST_DETECTOR and Reader == COLLEGE_WEST_READER) then
if (Func21 == nil) then
Func21 = createMainLine2MainLine(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func21("@ COLLEGE_WEST_TAG ", EngineNo, TagLocation, CarModel, ALIQUIPPA_WEST,
{LOWELLVILLE_BLOCK, STRUTHERS_THRU_TRACK});

-- LOWELLVILLE_TAG
elseif (Detector == LOWELLVILLE_DETECTOR and Reader == LOWELLVILLE_READER) then
if (Func90 == nil) then
Func90 = createMainLine2Passing(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func90("@ LOWELLVILLE_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_STATION_TRACK, COLLEGE_WEST);

-- STRUTHERS_EAST_TAG
elseif (Detector == STRUTHERS_EAST_DETECTOR and Reader == STRUTHERS_EAST_READER) then
if (LLLoop == false) then
if (Func20 == nil) then
Func20 = createPassing2MainLine(false, SwitchClearingInches[STRUTHERS_EAST_TAG][MyDirection]);
end;
Func20("@ STRUTHERS_EAST_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_STATION_TRACK, STRUTHERS_WEST,
{YOUNGSTOWN_BLOCK, EndBlockNo});
else
if (Func20 == nil) then
Func20 = createPassing2MainLine(false, SwitchClearingInches[STRUTHERS_EAST_TAG][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set
Func20("@ STRUTHERS_EAST_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_STATION_TRACK, STRUTHERS_EAST, STRUTHERS_WEST,
{YOUNGSTOWN_BLOCK, LIONEL_IVES_BLOCK, MCKEES_ROCKS_BLOCK, J_L_THRU_TRACK});
end;

-- STRUTHERS_WEST_TAG
elseif (Detector == STRUTHERS_WEST_DETECTOR and Reader == STRUTHERS_WEST_READER) then
-- status: just entered NextBlockNo, occupancy already set
if (LLLoop == false) then
if (Func40 == nil) then
Func40 = createMainLine2Yard(false, SwitchClearingInches[STRUTHERS_WEST_TAG][MyDirection]);
end;
Func40("@ STRUTHERS_WEST_TAG ", EngineNo, TagLocation, CarModel, YOUNGSTOWN_YARD1_BLOCK, YOUNGSTOWN_YARD2_BLOCK, YOUNGSTOWN_YARD,
{EndBlockNo});
else
if (Func40 == nil) then
Func40 = createMainLine2MainLine(false); -- no switches if staying on Mainline
end;
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK) then
Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);
end;
-- engine never stops in this block except for deadlock
Func40("@ STRUTHERS_WEST_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_WEST,
{LIONEL_IVES_BLOCK, MCKEES_ROCKS_BLOCK, J_L_THRU_TRACK});
end;

-- YOUNGSTOWN_YARD_TAG
elseif (Detector == YOUNGSTOWN_YARD_DETECTOR and Reader == YOUNGSTOWN_YARD_READER) then
-- status: in block NextBlockNo, occupancy already set
if (LLLoop == false) then
if (Func50 == nil) then
Func50 = createStopInYard(false, SwitchClearingInches[YOUNGSTOWN_YARD_TAG][MyDirection]); -- clear yard switch
end;
Func50("@ YOUNGSTOWN_YARD_TAG ", EngineNo, TagLocation, CarModel, {CLEVELAND_EL});
else
if (Func50 == nil) then
Func50 = createMainLine2MainLine(false); -- no switches if staying on Mainline
end;
-- engine never stops in this block except for deadlock
Func50("@ YOUNGSTOWN_YARD_TAG ", EngineNo, TagLocation, CarModel, nil,
{MCKEES_ROCKS_BLOCK, J_L_THRU_TRACK});
end;
end;
end;
--[[--------------------------------- Eastbound --------------------------------------------]]

if (MyDirection == EB) then
if (ResetinProgress) then
-- run back to YOUNGSTOWN_YARD_TAG
if (Detector == YOUNGSTOWN_YARD_DETECTOR and Reader == YOUNGSTOWN_YARD_READER) then
local DetectorID = "@ YOUNGSTOWN_YARD_TAG ";
local StopDelay = 0;
if (EngineNo == 0 and (CarModel == CABOOSE or CarModel == OBSERVATION)) then -- going in reverse, execute on caboose detection
print(DetectorID .. "End of Train detected");
-- schedule stop in StartBlockNo
StopDelay = StopAfterDelay(StartBlockNo, EngineNo, MyDirection, LastBlockSpeedLimit, BlockSpeedLimit)
SetDirection(FORWARD, StopDelay + 5, EngineNo, MyTIUNo);
ResetinProgress = false;
end;
end;
return true;

elseif (not RFIDFlag) then -- ignore tags unless engine is moving
return true;

-- YOUNGSTOWN_YARD_TAG
elseif (Detector == YOUNGSTOWN_YARD_DETECTOR and Reader == YOUNGSTOWN_YARD_READER) then
if (Func50 == nil) then
Func50 = createMainLine2Passing(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func50("@ YOUNGSTOWN_YARD_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_STATION_TRACK, nil);

-- STRUTHERS_WEST_TAG
elseif (Detector == STRUTHERS_WEST_DETECTOR and Reader == STRUTHERS_WEST_READER) then
if (Func40 == nil) then
Func40 = createPassing2MainLine(false, SwitchClearingInches[STRUTHERS_WEST_TAG][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set. Set STRUTHERS_WEST to NORMAL on Caboose detection.
Func40("@ STRUTHERS_WEST_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_STATION_TRACK, STRUTHERS_WEST, STRUTHERS_EAST,
{LOWELLVILLE_BLOCK, NEW_CASTLE_BLOCK, COLLEGE_STATION_TRACK});

-- STRUTHERS_EAST_TAG
elseif (Detector == STRUTHERS_EAST_DETECTOR and Reader == STRUTHERS_EAST_READER) then
if (Func20 == nil) then
Func20 = createMainLine2MainLine(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock. Set STRUTHERS_EAST to NORMAL on Caboose detection.
Func20("@ STRUTHERS_EAST_TAG ", EngineNo, TagLocation, CarModel, STRUTHERS_EAST,
{NEW_CASTLE_BLOCK, COLLEGE_STATION_TRACK});

-- LOWELLVILLE_TAG
elseif (Detector == LOWELLVILLE_DETECTOR and Reader == LOWELLVILLE_READER) then -- Split New Castle Block
if (Func90 == nil) then
Func90 = createMainLine2Passing(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func90("@ LOWELLVILLE_TAG ", EngineNo, TagLocation, CarModel, COLLEGE_THRU_TRACK, COLLEGE_STATION_TRACK, nil);

-- COLLEGE_WEST_TAG
elseif (Detector == COLLEGE_WEST_DETECTOR and Reader == COLLEGE_WEST_READER) then
if (Func21 == nil) then
Func21 = createPassing2MainLine(false, SwitchClearingInches[COLLEGE_WEST_TAG][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set. Set COLLEGE_WEST to NORMAL on Caboose detection.
Func21("@ COLLEGE_WEST_TAG ", EngineNo, TagLocation, CarModel, COLLEGE_THRU_TRACK, COLLEGE_STATION_TRACK, COLLEGE_WEST, COLLEGE_EAST,
{BEAVER_FALLS_BLOCK, FALLSTON_BLOCK, MONACA_BLOCK, WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK});

-- COLLEGE_EAST_TAG
elseif (Detector == COLLEGE_EAST_DETECTOR and Reader == COLLEGE_EAST_READER) then
if (Func41 == nil) then
Func41 = createMainLine2MainLine(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock . Set COLLEGE_EAST to NORMAL on Caboose detection.
Func41("@ COLLEGE_EAST_TAG ", EngineNo, TagLocation, CarModel, COLLEGE_EAST,
{ FALLSTON_BLOCK, MONACA_BLOCK, WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK});

-- FALLSTON_TAG
elseif (Detector == FALLSTON_DETECTOR and Reader == FALLSTON_READER) then -- Split Beaver Falls Block
if (Func101 == nil) then
Func101 = createMainLine2MainLine(false);
end;
Func101("@ FALLSTON_TAG ", EngineNo, TagLocation, CarModel, nil,
{MONACA_BLOCK, WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK});

-- MONACA_WEST_TAG
elseif (Detector == MONACA_WEST_DETECTOR and Reader == MONACA_WEST_READER) then
-- entering tunnel, reduce volume
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK and SoundOff == false) then
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 5/6), 0, MyEngineNo, MyTIUNo);
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 4/6), 1, MyEngineNo, MyTIUNo);
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 1/2), 2, MyEngineNo, MyTIUNo);
end;
if (Func61 == nil) then
Func61 = createMainLine2MainLine(false);
end;
Func61("@ MONACA_WEST_TAG ", EngineNo, TagLocation, CarModel, nil,
{WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK});

-- MONACA_EAST_TAG
elseif (Detector == MONACA_EAST_DETECTOR and Reader == MONACA_EAST_READER) then
-- leaving tunnel, increase volume
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK and SoundOff == false) then
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 4/6), 0, MyEngineNo, MyTIUNo);
SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 5/6), 1, MyEngineNo, MyTIUNo);
SetVolume(MASTER_VOLUME, EngineSoundSave, 2, MyEngineNo, MyTIUNo);
end;
if (Func60 == nil) then
Func60 = createMainLine2MainLine(false);
end;

Func60("@ MONACA_EAST_TAG ", EngineNo, TagLocation, CarModel, nil,
{ALIQUIPPA_BLOCK, J_L_PASSING_TRACK});

-- WEST_ALIQUIPPA_TAG
elseif (Detector == WEST_ALIQUIPPA_DETECTOR and Reader == WEST_ALIQUIPPA_READER) then -- Split Aliquippa Block
if (Func100 == nil) then
Func100 = createMainLine2Passing(false);
end;
Func100("@ WEST_ALIQUIPPA_TAG ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, nil);

elseif (Detector == ALIQUIPPA_WEST_DETECTOR and Reader == ALIQUIPPA_WEST_READER) then
if (LLLoop == false) then
if (Func10 == nil) then
Func10 = createPassing2MainLine(false, SwitchClearingInches[ALIQUIPPA_WEST_TAG][MyDirection]);
end;
Func10("@ 1-0 ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, ALIQUIPPA_WEST, ALIQUIPPA_EAST,
{MCKEES_ROCKS_BLOCK, EndBlockNo});
else
if (Func10 == nil) then
Func10 = createPassing2MainLine(false, SwitchClearingInches[ALIQUIPPA_WEST_TAG][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set. Set ALIQUIPPA_WEST to NORMAL on Caboose detection.
Func10("@ ALIQUIPPA_WEST_TAG ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, ALIQUIPPA_WEST, ALIQUIPPA_EAST,
{MCKEES_ROCKS_BLOCK, LIONEL_IVES_BLOCK, YOUNGSTOWN_BLOCK, STRUTHERS_STATION_TRACK});
end;

-- ALIQUIPPA_EAST_TAG
elseif (Detector == ALIQUIPPA_EAST_DETECTOR and Reader == ALIQUIPPA_EAST_READER) then
-- status: just entered NextBlockNo, occupancy already set
if (LLLoop == false) then
if (Func51 == nil) then
Func51 = createMainLine2Yard(false, SwitchClearingInches[ALIQUIPPA_EAST_TAG][MyDirection]);
end;
Func51("@ ALIQUIPPA_EAST_TAG ", EngineNo, TagLocation, CarModel, MCKEES_ROCKS_YARD1_BLOCK, MCKEES_ROCKS_YARD2_BLOCK, MCKEES_ROCKS_YARD,
{EndBlockNo});
else
if (Func51 == nil) then
Func51 = createMainLine2MainLine(false);
end;
if (EngineNo == MyEngineNo and TagLocation == TAG_ON_FRONT_TRUCK) then
Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);
end;
-- engine never stops in this block except for deadlock. Set ALIQUIPPA_EAST to NORMAL on Caboose detection.
Func51("@ ALIQUIPPA_EAST_TAG ", EngineNo, TagLocation, CarModel, ALIQUIPPA_EAST,
{LIONEL_IVES_BLOCK, YOUNGSTOWN_BLOCK, STRUTHERS_STATION_TRACK});
end;

-- MCKEES_ROCKS_YARD_TAG
elseif (Detector == MCKEES_ROCKS_YARD_DETECTOR and Reader == MCKEES_ROCKS_YARD_READER) then
-- status: in block NextBlockNo, occupancy already set
if (LLLoop == false) then
if (Func30 == nil) then
Func30 = createStopInYard(false, SwitchClearingInches[MCKEES_ROCKS_YARD_TAG][MyDirection]); -- clear yard switch
end;
Func30("@ MCKEES_ROCKS_YARD_TAG ", EngineNo, TagLocation, CarModel, {POINTS_SOUTH_N_W});
else
if (Func30 == nil) then
Func30 = createMainLine2MainLine(false); -- no switches if staying on Mainline
end;
-- engine never stops in this block except for deadlock
Func30("@ MCKEES_ROCKS_YARD_TAG ", EngineNo, TagLocation, CarModel, nil,
{YOUNGSTOWN_BLOCK, STRUTHERS_STATION_TRACK});
end;
end;

end;

return true; -- true=continue to process tags, false=stop processing tags
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button or the Stop() function is called
print("All Newly Detected Tags:");
local k, v;
for k,v in pairs(Tags) do -- dump out the Tags table
-- . . . . . . . . . . . . . . . .
-- print format: 6619044A 88080101030108011B02202031353536
print(k .. " " .. v);
end
LastSentSpeed = SetSpeed(0, 0, MyEngineNo, MyTIUNo);
Rate(Layout4.AccDecRate, Layout4.AccDecRate, 2, MyEngineNo, MyTIUNo);
print("Shutdown in Progress.....");
--
if (StartedFlag) then
print("Shutting down the Engine");
Sleep(10);
EngineShutdown(MyEngineNo, MyTIUNo);
StartedFlag = false;
end
RFIDFlag = false;
ResetinProgress = false;
print(string.format("cleanup() Complete at %.2f", RunTime()));
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function Plus5Button(Eng, TIU)
BlockSpeedLimit = BlockSpeedLimit + 5;
LastSentSpeed = SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
SetCounter(C3, BlockSpeedLimit)
return true;
end
Function01Name, Function01Label = Plus5Button, "Speed +5";
--[[---------------------------------------------------------------------------------------]]
function Minus5Button(Eng, TIU)
BlockSpeedLimit = BlockSpeedLimit - 5;
LastSentSpeed = SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
SetCounter(C3, BlockSpeedLimit)
return true;
end
Function02Name, Function02Label = Minus5Button, "Speed -5";
--[[---------------------------------------------------------------------------------------]]
function DTOButton(Eng, TIU)
print(string.format("Engine %d Trip Odometer %.3f Smiles", MyEngineNo, DTO(MyEngineNo, MyTIUNo)));
return true;
end
Function21Name, Function21Label = DTOButton, "Show DTO";
--[[---------------------------------------------------------------------------------------]]
function DODButton(Eng, TIU)
print(string.format("Engine %d Odometer %.3f Smiles", MyEngineNo, DOD(MyEngineNo, MyTIUNo)));
return true;
end
Function22Name, Function22Label = DODButton, "Show DOD";
--[[---------------------------------------------------------------------------------------]]
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 HeadwayButton(Eng, TIU)
print("Toggle Headway");
-- toggle Headway flag
Headway = not Headway;
print("Headway set to " .. tostring(Headway));
return true;
end
Function04Name, Function04Label = HeadwayButton, "Headway";
--[[---------------------------------------------------------------------------------------]]
function SignalsButton(Eng, TIU)
print("Toggle Signals");
-- toggle Signals flag
Signals = not Signals;
print("Signals set to " .. tostring(Signals));
return true;
end
Function05Name, Function05Label = SignalsButton, "Signals";
--[[---------------------------------------------------------------------------------------]]
function SoundOffButton(Eng, TIU)
print("Sound Off");
if (SoundOff == false) then
-- read up current sound volume
EngineSoundSave = GetVolume(MASTER_VOLUME, MyEngineNo, MyTIUNo); -- get Volume
print(" Sound Off - Engine " .. MyEngineNo .. " Level was " .. EngineSoundSave);
SoundOff = true;
SetVolume(MASTER_VOLUME, 0, 3, MyEngineNo, MyTIUNo);
end;
return true;
end
Function19Name, Function19Label = SoundOffButton, "Sound Off";
--[[---------------------------------------------------------------------------------------]]
function SoundOnButton(Eng, TIU)
print("Sound On");
if (SoundOff == true) then
print(" Sound On - Engine " .. MyEngineNo .. " Level restored to " .. EngineSoundSave);
SoundOff = false;
SetVolume(MASTER_VOLUME, EngineSoundSave, 3, MyEngineNo, MyTIUNo);
end;
return true;
end
Function20Name, Function20Label = SoundOnButton, "Sound On";
--[[---------------------------------------------------------------------------------------]]
function InitButton(Eng, TIU)
print("Initialize");
print("Set all Switches to NORMAL postion");
local k, v;
-- set all switches to NORMAL position
for k,v in pairs(Switches) do
print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
Switch(NORMAL, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
Sleep(0.5);
end
if (Debug() > 3) then
-- set all switches to REVERSE
for k,v in pairs(Switches) do
print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
Switch(REVERSE, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
Sleep(0.5);
end;
-- set all switches to NORMAL
for k,v in pairs(Switches) do
print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
Switch(NORMAL, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
Sleep(0.5);
end;
end;
-- do not clear occupancy flags - its done in Layout4 APB Master
--[[
local x;
for x = FIRST_BLOCK, LAST_BLOCK do
OverrideOccupancy(x, UNOCCUPIED);
end;
]]
--[[-----------------------------]]
-- clear all tag handling functions
Func30 = nil;
Func51 = nil;
Func10 = nil;
Func41 = nil;
Func21 = nil;
Func20 = nil;
Func90 = nil; -- Split New Castle Block
Func40 = nil;
Func50 = nil;
Func60 = nil;
Func61 = nil;
Func100 = nil; -- Split Alquippa block
Func101 = nil; -- Split Beaver Falls Block

print("Initialization is Complete");
return true;
end
Function06Name, Function06Label = InitButton, "Initialize Layout";
--[[---------------------------------------------------------------------------------------]]
function StartButton(Eng, TIU)
print("Start the Run");

--[[-----------------------------]]
-- clear all tag handling functions
Func30 = nil;
Func51 = nil;
Func10 = nil;
Func41 = nil;
Func21 = nil;
Func20 = nil;
Func90 = nil; -- Split New Castle Block
Func40 = nil;
Func50 = nil;
Func60 = nil;
Func61 = nil;
Func100 = nil; -- Split Alquippa block
Func101 = nil; -- Split Beaver Falls Block

--[[------------------------------------------]]
--print("StationName = " .. Pretty(StationName));
--print("StationName = " .. Pretty(BlockName));
--print("Switches = " .. Pretty(Switches));
--print("NextBlock = " .. Pretty(NextBlock));
print("Headway set to " .. tostring(Headway));
print("Signals set to " .. tostring(Signals));
-- start the engine
Rate(Layout4.AccDecRate, Layout4.AccDecRate, 0, MyEngineNo, MyTIUNo);
print("setup : Set accell/decell rates to " .. Layout4.AccDecRate);
if (StartedFlag == false) then
EngineStartUp(MyEngineNo, MyTIUNo);
print("Starting up the Engine");
StartedFlag = true;
Sleep(20);
end
SetDirection(FORWARD, 0, MyEngineNo, MyTIUNo);
OverrideOccupancy(CurrentBlockNo, MyEngineNo); -- force set occupancy flag for starting block
--[[--------------------------------- Westbound --------------------------------------------]]
if (MyDirection == WB) then
--
-- Engine can start in any block, set exit switches from meet points as necessary
--
if (CurrentBlockNo == MCKEES_ROCKS_YARD1_BLOCK and MyDirection == WB) then
LastBlockNo = POINTS_SOUTH_N_W;
NextBlockNo = MCKEES_ROCKS_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, J_L_THRU_TRACK}, MyEngineNo);
-- position MCKEES_ROCKS_YARD switch as needed depending on value of CurrentBlockNo
Switch(NORMAL, Switches[MCKEES_ROCKS_YARD][CHAN_NUMBER], Switches[MCKEES_ROCKS_YARD][AIU_NUMBER], Switches[MCKEES_ROCKS_YARD][TIU_NUMBER], 0);
-- position BALLOON switch as needed after locking next block
LLLoop = false;
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);

elseif (CurrentBlockNo == MCKEES_ROCKS_YARD2_BLOCK and MyDirection == WB) then
LastBlockNo = CUMBERLAND_B_O;
NextBlockNo = MCKEES_ROCKS_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, J_L_THRU_TRACK}, MyEngineNo);
-- position MCKEES_ROCKS_YARD switch as needed depending on value of CurrentBlockNo
Switch(REVERSE, Switches[MCKEES_ROCKS_YARD][CHAN_NUMBER], Switches[MCKEES_ROCKS_YARD][AIU_NUMBER], Switches[MCKEES_ROCKS_YARD][TIU_NUMBER], 0);
-- position BALLOON switch as needed after locking next block
LLLoop = false;
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);

elseif (CurrentBlockNo == MCKEES_ROCKS_BLOCK and MyDirection == WB) then -- "McKees Rocks Block"
if (LLLoop == true) then
LastBlockNo = LIONEL_IVES_BLOCK;
NextBlockNo = J_L_THRU_TRACK;
else
LastBlockNo = StartBlockNo;
NextBlockNo = J_L_THRU_TRACK;
end;
-- check that the Next Block is not occupied
SingleSetOccupancy({NextBlockNo}, MyEngineNo);

elseif (CurrentBlockNo == J_L_THRU_TRACK and MyDirection == WB) then -- "J&L Thru Track"
LastBlockNo = MCKEES_ROCKS_BLOCK;
NextBlockNo = ALIQUIPPA_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, WEST_ALIQUIPPA_BLOCK, MONACA_BLOCK, FALLSTON_BLOCK, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK}, MyEngineNo);
-- Position ALIQUIPPA_WEST after locking next block
Switch(NORMAL, Switches[ALIQUIPPA_WEST][CHAN_NUMBER], Switches[ALIQUIPPA_WEST][AIU_NUMBER], Switches[ALIQUIPPA_WEST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == J_L_PASSING_TRACK and MyDirection == WB) then -- "J&L Passing Track" -- Westbound trains don't use the passing track
LastBlockNo = MCKEES_ROCKS_BLOCK;
NextBlockNo = ALIQUIPPA_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, WEST_ALIQUIPPA_BLOCK, MONACA_BLOCK, FALLSTON_BLOCK, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK}, MyEngineNo);
-- Position ALIQUIPPA_WEST after locking next block
Switch(REVERSE, Switches[ALIQUIPPA_WEST][CHAN_NUMBER], Switches[ALIQUIPPA_WEST][AIU_NUMBER], Switches[ALIQUIPPA_WEST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == ALIQUIPPA_BLOCK and MyDirection == WB) then -- "Aliquippa Block"
LastBlockNo = J_L_THRU_TRACK;
NextBlockNo = WEST_ALIQUIPPA_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, MONACA_BLOCK, FALLSTON_BLOCK, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == WEST_ALIQUIPPA_BLOCK and MyDirection == WB) then -- "West Aliquippa Block"
LastBlockNo = J_L_THRU_TRACK;
NextBlockNo = MONACA_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, FALLSTON_BLOCK, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == MONACA_BLOCK and MyDirection == WB) then -- "Monaca Block"
LastBlockNo = ALIQUIPPA_BLOCK;
NextBlockNo = FALLSTON_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, BEAVER_FALLS_BLOCK, COLLEGE_THRU_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == FALLSTON_BLOCK and MyDirection == WB) then -- "Fallston Block"
LastBlockNo = MONACA_BLOCK;
NextBlockNo = BEAVER_FALLS_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, COLLEGE_THRU_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == BEAVER_FALLS_BLOCK and MyDirection == WB) then -- "Beaver Falls Block"
LastBlockNo = FALLSTON_BLOCK;
NextBlockNo = COLLEGE_THRU_TRACK;
-- check that the Next Block is not occupied
SingleSetOccupancy({NextBlockNo}, MyEngineNo);

elseif (CurrentBlockNo == COLLEGE_THRU_TRACK and MyDirection == WB) then -- "College Thru Track"
LastBlockNo = BEAVER_FALLS_BLOCK;
NextBlockNo = NEW_CASTLE_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, LOWELLVILLE_BLOCK, STRUTHERS_THRU_TRACK}, MyEngineNo);
-- Position COLLEGE_WEST after locking next block
Switch(NORMAL, Switches[COLLEGE_WEST][CHAN_NUMBER], Switches[COLLEGE_WEST][AIU_NUMBER], Switches[COLLEGE_WEST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == COLLEGE_STATION_TRACK and MyDirection == WB) then -- "College Station Track" -- Westbound trains don't use the station track
LastBlockNo = BEAVER_FALLS_BLOCK;
NextBlockNo = NEW_CASTLE_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, LOWELLVILLE_BLOCK, STRUTHERS_THRU_TRACK}, MyEngineNo);
-- Position COLLEGE_WEST after locking next block
Switch(REVERSE, Switches[COLLEGE_WEST][CHAN_NUMBER], Switches[COLLEGE_WEST][AIU_NUMBER], Switches[COLLEGE_WEST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == NEW_CASTLE_BLOCK and MyDirection == WB) then -- "New Castle Block"
LastBlockNo = COLLEGE_THRU_TRACK;
NextBlockNo = LOWELLVILLE_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, STRUTHERS_THRU_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == LOWELLVILLE_BLOCK and MyDirection == WB) then -- "Lowellville Block"
LastBlockNo = NEW_CASTLE_BLOCK;
NextBlockNo = STRUTHERS_THRU_TRACK;
-- check that the Next Block is not occupied
SingleSetOccupancy({NextBlockNo}, MyEngineNo);

elseif (CurrentBlockNo == STRUTHERS_THRU_TRACK and MyDirection == WB) then -- "Struthers Thru Track"
LastBlockNo = LOWELLVILLE_BLOCK;
NextBlockNo = YOUNGSTOWN_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, LIONEL_IVES_BLOCK, MCKEES_ROCKS_BLOCK, J_L_THRU_TRACK}, MyEngineNo);
-- Position STRUTHERS_WEST after locking next block
Switch(NORMAL, Switches[STRUTHERS_WEST][CHAN_NUMBER], Switches[STRUTHERS_WEST][AIU_NUMBER], Switches[STRUTHERS_WEST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == STRUTHERS_STATION_TRACK and MyDirection == WB) then -- "Struthers Station Track" -- Westbound trains don't use the station track
LastBlockNo = LOWELLVILLE_BLOCK;
NextBlockNo = YOUNGSTOWN_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, LIONEL_IVES_BLOCK, MCKEES_ROCKS_BLOCK, J_L_THRU_TRACK}, MyEngineNo);
-- Position STRUTHERS_WEST after locking next block
Switch(REVERSE, Switches[STRUTHERS_WEST][CHAN_NUMBER], Switches[STRUTHERS_WEST][AIU_NUMBER], Switches[STRUTHERS_WEST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == YOUNGSTOWN_BLOCK and MyDirection == WB) then -- "Youngstown Block"
if (LLLoop == true) then
LastBlockNo = STRUTHERS_THRU_TRACK;
NextBlockNo = LIONEL_IVES_BLOCK;
else
LastBlockNo = STRUTHERS_THRU_TRACK;
NextBlockNo = EndBlockNo;
end;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, MCKEES_ROCKS_BLOCK, J_L_THRU_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == LIONEL_IVES_BLOCK and MyDirection == WB) then -- "Lionel-Ives Block" -- AKA Balloon Track
LastBlockNo = YOUNGSTOWN_BLOCK;
NextBlockNo = MCKEES_ROCKS_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, J_L_THRU_TRACK}, MyEngineNo);
end;
end;

--[[--------------------------------- Eastbound --------------------------------------------]]
if (MyDirection == EB) then
--
-- Engine can start in any block, set exit switches from meet points as necessary
--
if (CurrentBlockNo == YOUNGSTOWN_YARD1_BLOCK and MyDirection == EB) then
LastBlockNo = ASHTABULA_NYC;
NextBlockNo = YOUNGSTOWN_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, STRUTHERS_STATION_TRACK}, MyEngineNo);
-- position YOUNGSTOWN_YARD switch as needed depending on value of CurrentBlockNo
Switch(NORMAL, Switches[YOUNGSTOWN_YARD][CHAN_NUMBER], Switches[YOUNGSTOWN_YARD][AIU_NUMBER], Switches[YOUNGSTOWN_YARD][TIU_NUMBER], 0);
-- position BALLOON switch as needed after locking next block
LLLoop = false;
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);

elseif (CurrentBlockNo == YOUNGSTOWN_YARD2_BLOCK and MyDirection == EB) then
LastBlockNo = CLEVELAND_EL;
NextBlockNo = YOUNGSTOWN_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, STRUTHERS_STATION_TRACK}, MyEngineNo);
-- position YOUNGSTOWN_YARD switch as needed depending on value of CurrentBlockNo
Switch(REVERSE, Switches[YOUNGSTOWN_YARD][CHAN_NUMBER], Switches[YOUNGSTOWN_YARD][AIU_NUMBER], Switches[YOUNGSTOWN_YARD][TIU_NUMBER], 0);
-- position BALLOON switch as needed after locking next block
LLLoop = false;
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);

elseif (CurrentBlockNo == YOUNGSTOWN_BLOCK and MyDirection == EB) then -- "Youngstown Block"
if (LLLoop == true) then
LastBlockNo = LIONEL_IVES_BLOCK;
NextBlockNo = STRUTHERS_STATION_TRACK;
else
LastBlockNo = StartBlockNo;
NextBlockNo = STRUTHERS_STATION_TRACK;
end;
-- check that the Next Block is not occupied
SingleSetOccupancy({NextBlockNo}, MyEngineNo);

elseif (CurrentBlockNo == STRUTHERS_STATION_TRACK and MyDirection == EB) then -- "Struthers Station Track"
LastBlockNo = YOUNGSTOWN_BLOCK;
NextBlockNo = LOWELLVILLE_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, NEW_CASTLE_BLOCK, COLLEGE_STATION_TRACK}, MyEngineNo);
-- Position STRUTHERS_EAST after locking next block
Switch(REVERSE, Switches[STRUTHERS_EAST][CHAN_NUMBER], Switches[STRUTHERS_EAST][AIU_NUMBER], Switches[STRUTHERS_EAST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == STRUTHERS_THRU_TRACK and MyDirection == EB) then -- "Struthers Thru Track" -- Eastbound trains don't use the thru track
LastBlockNo = YOUNGSTOWN_BLOCK;
NextBlockNo = LOWELLVILLE_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, NEW_CASTLE_BLOCK, COLLEGE_STATION_TRACK}, MyEngineNo);
-- Position STRUTHERS_EAST after locking next block
Switch(NORMAL, Switches[STRUTHERS_EAST][CHAN_NUMBER], Switches[STRUTHERS_EAST][AIU_NUMBER], Switches[STRUTHERS_EAST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == LOWELLVILLE_BLOCK and MyDirection == EB) then -- "Lowellville Block"
LastBlockNo = STRUTHERS_STATION_TRACK;
NextBlockNo = NEW_CASTLE_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, COLLEGE_STATION_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == NEW_CASTLE_BLOCK and MyDirection == EB) then -- "New Castle Block"
LastBlockNo = LOWELLVILLE_BLOCK;
NextBlockNo = COLLEGE_STATION_TRACK;
-- check that the Next Block is not occupied
SingleSetOccupancy({NextBlockNo}, MyEngineNo);

elseif (CurrentBlockNo == COLLEGE_STATION_TRACK and MyDirection == EB) then -- "College Station Track"
LastBlockNo = NEW_CASTLE_BLOCK;
NextBlockNo = BEAVER_FALLS_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, FALLSTON_BLOCK, MONACA_BLOCK, WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK}, MyEngineNo);
-- Position COLLEGE_EAST after locking next block
Switch(REVERSE, Switches[COLLEGE_EAST][CHAN_NUMBER], Switches[COLLEGE_EAST][AIU_NUMBER], Switches[COLLEGE_EAST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == COLLEGE_THRU_TRACK and MyDirection == EB) then -- "College Thru Track" -- Eastbound trains don't use the thru track
LastBlockNo = NEW_CASTLE_BLOCK;
NextBlockNo = BEAVER_FALLS_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, FALLSTON_BLOCK, MONACA_BLOCK, WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK}, MyEngineNo);
-- Position COLLEGE_EAST after locking next block
Switch(NORMAL, Switches[COLLEGE_EAST][CHAN_NUMBER], Switches[COLLEGE_EAST][AIU_NUMBER], Switches[COLLEGE_EAST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == BEAVER_FALLS_BLOCK and MyDirection == EB) then -- "Beaver Falls Block"
LastBlockNo = COLLEGE_STATION_TRACK;
NextBlockNo = FALLSTON_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, MONACA_BLOCK, WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == FALLSTON_BLOCK and MyDirection == EB) then -- "Fallston Block"
LastBlockNo = BEAVER_FALLS_BLOCK;
NextBlockNo = MONACA_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, WEST_ALIQUIPPA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == MONACA_BLOCK and MyDirection == EB) then -- "Monaca Block"
LastBlockNo = FALLSTON_BLOCK;
NextBlockNo = WEST_ALIQUIPPA_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == WEST_ALIQUIPPA_BLOCK and MyDirection == EB) then -- "West Aliquippa Block"
LastBlockNo = MONACA_BLOCK;
NextBlockNo = ALIQUIPPA_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, J_L_PASSING_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == ALIQUIPPA_BLOCK and MyDirection == EB) then -- "Aliquippa Block"
LastBlockNo = WEST_ALIQUIPPA_BLOCK;
NextBlockNo = J_L_PASSING_TRACK;
-- check that the Next Block is not occupied
SingleSetOccupancy({NextBlockNo}, MyEngineNo);

elseif (CurrentBlockNo == J_L_PASSING_TRACK and MyDirection == EB) then -- "J&L Passing Track"
LastBlockNo = ALIQUIPPA_BLOCK;
NextBlockNo = MCKEES_ROCKS_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, LIONEL_IVES_BLOCK, YOUNGSTOWN_BLOCK, STRUTHERS_STATION_TRACK}, MyEngineNo);
-- Position ALIQUIPPA_EAST after locking next block
Switch(REVERSE, Switches[ALIQUIPPA_EAST][CHAN_NUMBER], Switches[ALIQUIPPA_EAST][AIU_NUMBER], Switches[ALIQUIPPA_EAST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == J_L_THRU_TRACK and MyDirection == EB) then -- "J&L Thru Track" -- Eastbound trains don't use the thru track
LastBlockNo = ALIQUIPPA_BLOCK;
NextBlockNo = MCKEES_ROCKS_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, LIONEL_IVES_BLOCK, YOUNGSTOWN_BLOCK, STRUTHERS_STATION_TRACK}, MyEngineNo);
-- Position ALIQUIPPA_EAST after locking next block
Switch(NORMAL, Switches[ALIQUIPPA_EAST][CHAN_NUMBER], Switches[ALIQUIPPA_EAST][AIU_NUMBER], Switches[ALIQUIPPA_EAST][TIU_NUMBER], 0);

elseif (CurrentBlockNo == MCKEES_ROCKS_BLOCK and MyDirection == EB) then -- "McKees Rocks Block"
if (LLLoop == true) then
LastBlockNo = J_L_PASSING_TRACK;
NextBlockNo = LIONEL_IVES_BLOCK;
else
LastBlockNo = J_L_PASSING_TRACK;
NextBlockNo = EndBlockNo;
end;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, YOUNGSTOWN_BLOCK, STRUTHERS_STATION_TRACK}, MyEngineNo);

elseif (CurrentBlockNo == LIONEL_IVES_BLOCK and MyDirection == EB) then -- "Lionel-Ives Block" -- AKA Balloon Track
LastBlockNo = MCKEES_ROCKS_BLOCK;
NextBlockNo = YOUNGSTOWN_BLOCK; -- check that the Next Block is not occupied
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, STRUTHERS_STATION_TRACK}, MyEngineNo);
end;
end;
--
DisplayBlockNames("Start Run : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
--
RFIDFlag = true;
Whistle(ON, 20, MyEngineNo, MyTIUNo);
Whistle(OFF, 22, MyEngineNo, MyTIUNo);
print("Trip Odometer " .. DTO(MyEngineNo, MyTIUNo));
Sleep(30);

--
BlockSpeedLimit = BlockSpeed[MyDirection][StartBlockNo];
LastSentSpeed = SetSpeed(BlockSpeedLimit, 5, MyEngineNo, MyTIUNo);
SetCounter(C3, BlockSpeedLimit);
LastBlockSpeedLimit = BlockSpeedLimit;
--
--[[----------------------------------------------------]]
print(string.format("RunTime = %.2f seconds", RunTime()));
-- TODO once both engines exit the yard, throw the Balloon Switches
return true;
end
Function07Name, Function07Label = StartButton, "Start Run";
--[[---------------------------------------------------------------------------------------]]

function event(FromPC, Type, P1, P2, P3)
print("event() Running - From PC# ", FromPC, " EventType = ", Type, " Parameters = ", P1, " ", P2, " ", P3);
return true; -- false=event failed, true=event succeeded
end
--[[---------------------------------------------------------------------------------------]]

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
Function08Name, Function08Label = OccButton, "Occupancy Flags";
--[[---------------------------------------------------------------------------------------]]
-- Run the engine back to the start (in reverse)
function ResetButton()
if (ResetinProgress) then
return; -- already being reset
end;
print("Return to Initial Position");
-- stop the engine
LastSentSpeed = SetSpeed(0, 0, MyEngineNo, MyTIUNo);
RFIDFlag = false;
Sleep(5);
--[[-----------------------------]]
-- clear all tag handling functions
Func30 = nil;
Func51 = nil;
Func10 = nil;
Func41 = nil;
Func21 = nil;
Func20 = nil;
Func90 = nil; -- Split New Castle Block
Func40 = nil;
Func50 = nil;
Func60 = nil;
Func61 = nil;
Func100 = nil; -- Split Alquippa block
Func101 = nil; -- Split Beaver Falls Block

-- position yard switch as needed
-- depending on value of StartBlockNo
if (StartBlockNo == MCKEES_ROCKS_YARD1_BLOCK) then
Switch(NORMAL, Switches[MCKEES_ROCKS_YARD][CHAN_NUMBER], Switches[MCKEES_ROCKS_YARD][AIU_NUMBER], Switches[MCKEES_ROCKS_YARD][TIU_NUMBER], 0);

elseif (StartBlockNo == MCKEES_ROCKS_YARD2_BLOCK) then
Switch(REVERSE, Switches[MCKEES_ROCKS_YARD][CHAN_NUMBER], Switches[MCKEES_ROCKS_YARD][AIU_NUMBER], Switches[MCKEES_ROCKS_YARD][TIU_NUMBER], 0);

elseif (StartBlockNo == YOUNGSTOWN_YARD1_BLOCK) then
Switch(NORMAL, Switches[YOUNGSTOWN_YARD][CHAN_NUMBER], Switches[YOUNGSTOWN_YARD][AIU_NUMBER], Switches[YOUNGSTOWN_YARD][TIU_NUMBER], 0);

elseif (StartBlockNo == YOUNGSTOWN_YARD2_BLOCK) then
Switch(REVERSE, Switches[YOUNGSTOWN_YARD][CHAN_NUMBER], Switches[YOUNGSTOWN_YARD][AIU_NUMBER], Switches[YOUNGSTOWN_YARD][TIU_NUMBER], 0);
end;
--
-- start the engine
if (not StartedFlag) then
print("Starting up the Engine");
EngineStartUp(MyEngineNo, MyTIUNo);
StartedFlag = true;
Sleep(20);
end
-- direction to reverse
SetDirection(REVERSE, 0, MyEngineNo, MyTIUNo);
--
BlockSpeedLimit = BlockSpeed[MyDirection][CurrentBlockNo];
LastSentSpeed = SetSpeed(BlockSpeedLimit, 5, MyEngineNo, MyTIUNo);
SetCounter(C3, BlockSpeedLimit);
LastBlockSpeedLimit = BlockSpeedLimit;
--
ResetinProgress = true;
return true
end;
Function12Name, Function12Label = ResetButton, "Initial Position";
--[[---------------------------------------------------------------------------------------]]

function ZeroSpeedButton()
print("Speed set to 0 Smph, was " .. LastSentSpeed .. " Smph");
SetSpeed(0, 0, MyEngineNo, MyTIUNo);
return true;
end
Function09Name, Function09Label = ZeroSpeedButton, "Set Speed = 0";
--[[---------------------------------------------------------------------------------------]]

function ResumeSpeedButton()
-- resume speed at LastSentSpeed
print("Resume Speed " .. LastSentSpeed .. " Smph");
LastSentSpeed = SetSpeed(LastSentSpeed, 0, MyEngineNo, MyTIUNo);
return true;
end
Function10Name, Function10Label = ResumeSpeedButton, "Resume Speed";
--[[---------------------------------------------------------------------------------------]]

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";
--[[---------------------------------------------------------------------------------------]]
function LLLButton()
if (LLLoop) then
print("Balloon Loop Off");
LLLoop = false;
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);
else
print("Balloon Loop On");
LLLoop = true;
Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);
end;
return true;
end
Function16Name, Function16Label = LLLButton, "Balloon Loop";
--[[---------------------------------------------------------------------------------------]]
-- Generate a block speed based on Signal Aspects with some randomness for Normal and Limited speeds
function AdjustSpeed(BlockNo, Aspect)
local MySpeed, SignaledSpeed;
-- each block has a speed limit
MySpeed = BlockSpeed[MyDirection][BlockNo];
SignaledSpeed = NORMAL_SPEED;
---[[--TODO---------------------------------------------------------------------------------------------------
if (Signals) then
-- each block also has a speed limit based on the signal aspect
if (Aspect == AsClear) then
SignaledSpeed = NORMAL_SPEED;
elseif (Aspect == AsApproachLimited or Aspect == AsLimitedClear or Aspect == AsLimitedApproach) then
SignaledSpeed = LIMITED_SPEED;
elseif (Aspect == AsApproachMedium or Aspect == AsMediumClear or Aspect == AsApproach or Aspect == AsMediumApproach) then
SignaledSpeed = MEDIUM_SPEED;
elseif (Aspect == AsApproachSlow or Aspect == AsSlowClear or Aspect == AsSlowApproach) then
SignaledSpeed = SLOW_SPEED;
-- a stop aspect does not require restricting speeds
--elseif (Aspect == AsRestricting or Aspect == AsStopandProceed or Aspect == AsStopSignal) then
elseif (Aspect == AsRestricting) then
SignaledSpeed = RESTRICTED_SPEED;
end;
--print("MySpeed = " .. MySpeed .. " Smph - Signaled Speed = " .. SignaledSpeed .. " Smph");
if (SignaledSpeed < MySpeed) then MySpeed = SignaledSpeed; end;
end;
-----------------------------------------------------------------------------------------------------]]
--[[
if (MySpeed >= 25) then -- adjust speed for some randomness in high speed blocks
if (math.random(1,5) == 1) then
MySpeed = (MySpeed // 5 ) * 4;
end;
end;
--]]
---[[--TODO---------------------------------------------------------------------------------------------------
if (Signals) then
print("MySpeed = " .. MySpeed .. " Smph - Signaled Speed = " .. SignaledSpeed .. " Smph");
else
print("MySpeed = " .. MySpeed .. " Smph");
end;
-----------------------------------------------------------------------------------------------------]]
return MySpeed;
end;
--[[---------------------------------------------------------------------------------------]]
function WaitforStopSignaltoClear(Wait)
local Aspect = (GetCabAspect(HOMESIGNAL));
-- check that home signal is AsStopSignal, if it is, we MUST Stop until it clears
if (Aspect == AsStopSignal) then
-- we are stopped, so wait until the Stop Signal aspect is cleared.
print("Home Signal aspect is Stop Signal. Wait for it to clear before proceeding.");
local WaitTime = Wait or 1800; -- let's say that 30 minutes is 'forever'
-- Wait for the signal to changed from Stop Signal
while (Aspect == AsStopSignal) do
-- wait 'forever'
if (((WaitTime >> 1) % 10) == 0) then
print("Waiting for Stop Signal to clear");
end;
Sleep(2);
WaitTime = WaitTime - 2;
if (WaitTime <= 0) then
return EmergencyStop("Timed out waiting for Stop Signal to clear");
end;
Aspect = (GetCabAspect(HOMESIGNAL));
end;
print ("Home Signal cleared to : " .. AspectNames[Aspect]);
end;
end;
--[[---------------------------------------------------------------------------------------]]

Here are two videos of this script running with two engines. It's hard to get a good video but when you look at this remember that both engines and all switch tracks are being controled by this script. Both videos were taken at the same time so if you open each one in a separate window and press the play button at the same time, you will be able to follow along better what is happening.

I am running three scripts:
  1.     Layout4 APB Master.lua
  2.     Thinking Engine Headway and Signals.lua (first copy)
  3.     Thinking Engine Headway and Signals.lua (second copy)

Script #1 controls the virtual signals which populate my virtural layout
Script #2 controls the first of two engines/trains on my real layout
Script #3 controls the second of two engines/trains on my real layout

You can see that the two trains are headed in different directions my layout which is setup as a single-track continous run with 3 sets of thru/passing tracks. The two engines/trains startup and head out in different directions. When they reach a potential meet, one train stops on the thru track. The other train switches off onto the passing track. Then both engines/trains resume their run. These "meets" occur on all of the 3 sets of thru/passing tracks.

I wrote the Thinking Engine scripts to do this. Other scripts can be written to have the engines/trains respond anyway that you want them to.


Image loading....

Thinking Engines Video of Layout


----------


Image loading....

Thinking Engines Screen Capture of RTC Running the Script

----------




Downloads

You can download RTC from here. Includes all of the above scripts and many more.

NOTE - AS I AM CONSTANTLY DEVELOPING THE RTC PROGRAM, THIS ZIP FILE MAY BE OUT OF DATE. IF YOU ARE GOING TO NEED IT, CONTACT ME FOR THE LATEST VERSION.


This site prepared and maintained by Mark DiVecchio

email :  markd@silogic.com

SD&A HOME
 
 Mark's Home Page

The DiVecchio genealogy home page
The Frazzini genealogy home page

This site will be under construction for a while.