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 .

(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

Background

Like the cartoon above 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

"Layout" and Flags Display

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.


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.
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. The layout
    -- window is also made visible by all of the flag setting functions listed below.

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.

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 Block) used to run the railroad in "open-house" mode.




Fixed Signals Display

Version 4.4 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.


NOTE: this feature is not ready for prime time and is not enabled in the released code.

  Under construction


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

I defined 17 common aspects that match what is described in the 19xx P&LE Employee

Constants that support the Fixed Signals 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 Window


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: CreateSignal(25, EB, PERMISSIVE_IN_OPEN_COUNTRY, 300, 300, "J2", AsClear,  Hd2Light, 0);    -- Set signal #25 to 2 light, Clear   
    -- example: CreateSignal(2, EB, 0, 0, 0, "", 0, 0, 0);    -- delete signal 2

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 Signal Window of all signals. The Signal 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;

ShowSignals(); -- If the Signal window is not visible, this function will make it visible. The Signal
    -- window is also made visible by all of the Signal setting functions listed above. Since
    -- signals are saved between runs of RTC, this function will show the signals generated
    -- by the previous run. 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 = "Not Used";
Tab02Label = "Not Used";
Tab03Label = "Not Used";

ErrCount = 0;
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");
ErrCount = 0;
print("Start Test @ ", RunTime());
ShowSignals(); -- not really needed since first SetSignal() will show the Signal Window
DeleteAllSignals(); -- clear the window

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

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)");

CreateSignal(2, EB, 0, 0, 0, "", 0, 0, 0); -- delete signal 2
Sleep(3);
CreateSignal(2, WB, PERMISSIVE_IN_OPEN_COUNTRY, 200, 200, "CC", AsApproach, Hd2Light, 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 = "Not Used";
Tab02Label = "Not Used";
Tab03Label = "Not Used";

ErrCount = 0;

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");
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, 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, 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");
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 = 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, 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
--
ShowSignals(); -- not really needed since first CreateSignal() will show the Signal Window
DeleteAllSignals(); -- clear the window
print("Creating Signals 1, 2, 3, 4, 5, 6, 25, 26");
CreateSignal(1, WB, PERMISSIVE_IN_OPEN_COUNTRY, 0, 0, "AA", AsDark, Hd3Light, 0); -- Set signal #1 to 3 light, Dark
CreateSignal(2, WB, PERMISSIVE_IN_OPEN_COUNTRY, 100, 0, "BB", AsDark, Hd3Light, 268); -- Set signal #2 to 3 light, Dark, Number Plate
CreateSignal(3, WB, PERMISSIVE_IN_OPEN_COUNTRY, 200, 0, "CC", AsDark, Hd3Light, 111); -- Set signal #3 to 3 light, Dark, Number Plate
CreateSignal(4, WB, PERMISSIVE_IN_OPEN_COUNTRY, 300, 0, "DD", AsDark, Hd3Light, 0); -- Set signal #4 to 3 light, Dark
CreateSignal(5, EB, PERMISSIVE_IN_OPEN_COUNTRY, 300, 150, "G2", AsDark, Hd2Light, 0); -- Set signal #5 to 2 light, Dark
CreateSignal(6, EB, PERMISSIVE_IN_OPEN_COUNTRY, 400, 150, "G3", AsDark, Hd1Light, 0); -- Set signal #6 to 1 light, Dark
CreateSignal(25, EB, PERMISSIVE_IN_OPEN_COUNTRY, 300, 300, "J2", AsClear, Hd2Light, 0); -- Set signal #25 to 1 light, Clear
CreateSignal(26, EB, PERMISSIVE_IN_OPEN_COUNTRY, 400, 300, "J3", AsApproachLimited, Hd3Light, 0); -- Set signal #26 to 3 light, Approach Limited
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 = GetAspect(5);
if (Asp ~= AsApproachLimited) then print("Signal 5 Aspect not Approach Limited (12)"); ErrCount = ErrCount + 1; end;

SetAspect(6, AsClear); -- Set signal #6 to AsClear (2)
Asp = GetAspect(6);
if (Asp ~= AsClear) then print("Signal 6 Aspect not Clear (2)"); 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 = GetAspect(1);
if (Asp ~= AsMediumClear) then print("Signal 1 Aspect not Medium Clear (4)"); 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 = GetCabAspect(HOMESIGNAL);
if (Asp ~= AsMediumApproach) then print("Home Signal Aspect not Medium Approach (5)"); 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 = GetCabAspect(DISTANTSIGNAL);
if (Asp ~= val) then print("Distant Signal Aspect not ", AspectNames[val], " (", val, ")"); 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;

print("Almost Done...");
--return true; --
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");
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 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. 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 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 = "Layout4 APB Master"
require([[defines]]);
require([[functions]]);
--[[
When using this module, all variables (including functions!) must be declared through
a regular assignment (even assigning nil will do) in a strict scope before being used
anywhere or assigned to inside a nested scope.
https://github.com/lua-stdlib/strict
]]
local _ENV = require 'std.strict' (_G) -- strict.lua will detect undeclared global variables
--
--[[
---------------------------------------------------------------------------
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
---------------------------------------------------------------------------]]
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 = "Not Used";

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 all of the 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 EngineSoundSave = {}; -- Engine sound value indexed by EngineNo
local Layout4;
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;
local Func60 = nil;
local Func61 = nil;
--
local MyTIUNo, Enum;

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");
MASTER_TIU = TIU;
-- Instrumented Layout 4
Layout4 = require([[Layout4]]); -- All of the details about the Layout
--
MyTIUNo = MASTER_TIU;
--
ShowSignals(); -- show the Signal Window
DeleteAllSignals(); -- clear the window of any old signals
print("Creating Signals");
-- Layout using the bypass block - Lionel-Ives Block
-- 10 signals in each direction with 1 head on each signal
-- Most signals are Permissive
-- Aignals 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
]]

print("All siding switches to NORMAL");
Switch(NORMAL, Switches[ALIQUIPPA_WEST][CHAN_NUMBER], Switches[ALIQUIPPA_WEST][AIU_NUMBER], Switches[ALIQUIPPA_WEST][TIU_NUMBER], 0)
Switch(NORMAL, Switches[ALIQUIPPA_EAST][CHAN_NUMBER], Switches[ALIQUIPPA_EAST][AIU_NUMBER], Switches[ALIQUIPPA_EAST][TIU_NUMBER], 0)
Switch(NORMAL, Switches[COLLEGE_WEST][CHAN_NUMBER], Switches[COLLEGE_WEST][AIU_NUMBER], Switches[COLLEGE_WEST][TIU_NUMBER], 0)
Switch(NORMAL, Switches[COLLEGE_EAST][CHAN_NUMBER], Switches[COLLEGE_EAST][AIU_NUMBER], Switches[COLLEGE_EAST][TIU_NUMBER], 0)
Switch(NORMAL, Switches[STRUTHERS_WEST][CHAN_NUMBER], Switches[STRUTHERS_WEST][AIU_NUMBER], Switches[STRUTHERS_WEST][TIU_NUMBER], 0)
Switch(NORMAL, Switches[STRUTHERS_EAST][CHAN_NUMBER], Switches[STRUTHERS_EAST][AIU_NUMBER], Switches[STRUTHERS_EAST][TIU_NUMBER], 0)
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(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(21, WB, APPROACH_TO_HEAD_BLOCK, 1900,240, "BF", AsStopandProceed, 2, 21); -- Signal in approach to head block signal
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(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, "NC", AsStopSignal, 1, 0); -- Absolute Head Block Signal
FSR[10] = NDT;
Sig[10] = AsStopSignal;
TUMCNT[10] = 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(22, EB, PERMISSIVE_IN_OPEN_COUNTRY, 1900,410, "MO", AsStopandProceed, 2, 22); -- Permissive signal in open country
FSR[22] = NDT;
Sig[22] = AsStopandProceed;
TUMCNT[22] = 0;

CreateSignal(26, EB, APPROACH_TO_HEAD_BLOCK, 2100,410, "AL", AsStopandProceed, 2, 26); -- Signal in approach to head block signal
FSR[26] = NDT;
Sig[26] = AsStopandProceed;
TUMCNT[26] = 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, "] = " , 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;
Func40 = nil;
Func50 = nil;
Func60 = nil;
Func61 = nil;

-- setup which blocks are occupied
for Block = FIRST_BLOCK, LAST_BLOCK do
BK[Block] = CLR;
end;
---[[
-- must ask the dispatcher
print("Select Starting Block Number");
for k, v in pairs(BlockName) do
if (k <= 100) then
print(v .. "(" .. k .. ")");
end;
end;
--
-- read Starting Block Number
--
local response = InputQuery(title, "Enter 1st Engine Starting Block# (0 for none)", ALIQUIPPA_BLOCK);
if (response == false) then
return Stop("Canceled");
end;
local Enum = tonumber(response);
if (Enum ~= nil and Enum ~= 0) then -- if the result is a block number, then use it, otherwise, ignore it
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;
print("1st Starting Block = " .. BlockName[Enum]);
response = InputQuery(title, "Enter 2nd Engine Starting Block# (0 for none)", YOUNGSTOWN_BLOCK);
if (response == false) then
return Stop("Canceled");
end;
Enum = tonumber(response);
if (Enum ~= nil and Enum ~= 0) then -- if the result is a block number, then use it, otherwise, ignore it
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;
print("2nd Starting Block = " .. BlockName[Enum]);
end;
end;


-- 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] ~= CLR) and (BK[Block2] ~= CLR)) 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
--
function PermissiveinOpenCountry(NextBlock, ThisSig, OppositeSig, NextSig, MyDir, OppositeDir) -- Uses Traffic Sticks
if (FSR[OppositeSig] == nil) then print("Traffic Stick nil in PermissiveinOpenCountry - Opposite Side #", OppositeSig); end;
if (FSR[NextSig] == nil) then print("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 (BK[NextBlock] ~= CLR) then goto POCAsStopandProceed; end;
if (FSR[OppositeSig] == OppositeDir) then goto POCAsStopandProceed; end;
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)
--Calculate Signal Head Block Permissive - no Traffic Stick involved
if (BK[NextBlock] ~= CLR) 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;
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 FAILS if less than 3 blocks between sidings
if (FSR[NextSig] == nil) then print("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] ~= CLR) 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
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 HeadBlockAbsolute1(NextBlock, ThisSig, OppositeSig, NextSig, Switch1, Switch2, MyDir, OppositeDir)
--Calculate Signal Head Block Absolute when there is 1 block between sidings - no Traffic Stick involved
if (BK[NextBlock] ~= CLR) then goto HBA1AsStop; end; -- Stop - next block is occupied by virtue of a train being in the block
-- No train in the through track, check the passing siding
--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 HBA1AsStop; end;
if (GetSwitchStatus(Switches[Switch1][CHAN_NUMBER], Switches[Switch1][AIU_NUMBER], Switches[Switch1][TIU_NUMBER]) == REVERSE)
then goto HBA1AsStop; end;
-- Stop - next block is occupied by virtue of a train being in the next siding and the crew has aligned one of the switches for the siding
-- 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
-- else next signal (signal in advance of ThisSig) is Stop so set signal to Approach indicating we are clear one block ahead and must prepare to stop.
if (Sig[NextSig] ~= AsStopSignal and Sig[NextSig] ~= AsStopandProceed) then Sig[ThisSig] = AsClear; else Sig[ThisSig] = AsApproach; end
TUMCNT[ThisSig] = 0;
goto HBA1Done;
::HBA1AsStop::
if (Sig[ThisSig] == AsStopSignal) then goto HBA1Done; end;
if (TUMCNT[ThisSig] == TUMMAX) then Sig[ThisSig] = AsStopSignal; TUMCNT[ThisSig] = 0; goto HBA1Done; end;
TUMCNT[ThisSig] = TUMCNT[ThisSig] + 1;
::HBA1Done::
return;
end;

function ApproachtoHeadBlock(NextBlock, ThisSig, OppositeSig, NextSig, MyDir, OppositeDir) -- Uses Traffic Sticks
if (FSR[OppositeSig] == nil) then print("Traffic Stick nil in ApproachtoHeadBlock - Opposite Side #", OppositeSig); end;
--Calculate Signal Approach to Head Block using Traffic Stick on opposite signal
if (BK[NextBlock] ~= CLR) then goto AHBAsStopandProceed; end;
if (FSR[OppositeSig] == OppositeDir) then goto AHBAsStopandProceed; end;
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
-- use (western side block, eastern side block, westbound signal #, eastbound signal #)
SetTrafficStick(LIONEL_IVES_BLOCK, YOUNGSTOWN_BLOCK, 1, 2);
SetTrafficStick(BEAVER_FALLS_BLOCK, MONACA_BLOCK, 21, 22);
SetTrafficStick(MONACA_BLOCK, ALIQUIPPA_BLOCK, 25, 26);
SetTrafficStick(MCKEES_ROCKS_BLOCK, LIONEL_IVES_BLOCK, 37, 38);

---[[
-- Original set of Signals with three sidings
-- CALCULATE WESTBOUND SIGNAL ASPECTS
ApproachtoHeadBlock(MCKEES_ROCKS_BLOCK, 37, 38, 33, WEST, EAST);
HeadBlockPermissive(J_L_BLOCK, 33, 34, 29, ALIQUIPPA_WEST, ALIQUIPPA_EAST, WEST, EAST);
HeadBlockAbsolute(ALIQUIPPA_BLOCK, 29, 30, 25, WEST, EAST);
PermissiveinOpenCountry(MONACA_BLOCK, 25, 26, 21, WEST, EAST);
ApproachtoHeadBlock(BEAVER_FALLS_BLOCK, 21, 22, 17, WEST, EAST);
HeadBlockPermissive(COLLEGE_BLOCK, 17, 18, 13, COLLEGE_WEST, COLLEGE_EAST, WEST, EAST);
HeadBlockAbsolute1(NEW_CASTLE_BLOCK, 13, 14, 9, WEST, EAST); -- 1 block between sidings
HeadBlockPermissive(STRUTHERS_BLOCK, 9, 10, 5, STRUTHERS_WEST, STRUTHERS_EAST, WEST, EAST);
HeadBlockAbsolute(YOUNGSTOWN_BLOCK, 5, 6, 1, WEST, EAST);
PermissiveinOpenCountry(LIONEL_IVES_BLOCK, 1, 2, 37, WEST, EAST);

-- CALCULATE EASTBOUND SIGNAL ASPECTS
ApproachtoHeadBlock(YOUNGSTOWN_BLOCK, 2, 1, 6, EAST, WEST);
HeadBlockPermissive(STRUTHERS_BLOCK, 6, 5, 10, STRUTHERS_EAST, STRUTHERS_WEST, EAST, WEST);
HeadBlockAbsolute1(NEW_CASTLE_BLOCK, 10, 9, 14, EAST, WEST); -- 1 block between sidings
HeadBlockPermissive(COLLEGE_BLOCK, 14, 13, 18, COLLEGE_EAST, COLLEGE_WEST, EAST, WEST);
HeadBlockAbsolute(BEAVER_FALLS_BLOCK, 18, 17, 22, EAST, WEST);
PermissiveinOpenCountry(MONACA_BLOCK, 22, 21, 26, EAST, WEST);
ApproachtoHeadBlock(ALIQUIPPA_BLOCK, 26, 25, 30, EAST, WEST);
HeadBlockPermissive(J_L_BLOCK, 30, 29, 34, ALIQUIPPA_EAST, ALIQUIPPA_WEST, EAST, WEST);
HeadBlockAbsolute(MCKEES_ROCKS_BLOCK, 34, 33, 38, EAST, WEST);
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[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[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;
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 == 3 and Reader == 0) then
if (Func30 == nil) then
Func30 = createMainLine(false);
end;
Func30("@ 3-0 ", EngineNo, TagLocation, CarModel, LIONEL_IVES_BLOCK, MCKEES_ROCKS_BLOCK);

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

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

-- Tag 6-0
elseif (Detector == 6 and Reader == 0) then
if (Func60 == nil) then
Func60 = createMainLine(false);
end;
Func60("@ 6-0 ", EngineNo, TagLocation, CarModel, ALIQUIPPA_BLOCK, MONACA_BLOCK);

-- Tag 6-1
elseif (Detector == 6 and Reader == 1) then
if (Func61 == nil) then
Func61 = createMainLine(false);
end;
Func61("@ 6-1 ", EngineNo, TagLocation, CarModel, MONACA_BLOCK, BEAVER_FALLS_BLOCK);

-- Tag 4-1
elseif (Detector == 4 and Reader == 1) then
if (Func41 == nil) then
Func41 = createMainLine(false);
end;
Func41("@ 4-1 ", EngineNo, TagLocation, CarModel, BEAVER_FALLS_BLOCK, COLLEGE_BLOCK);

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

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

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

-- Tag 5-0
elseif (Detector == 5 and Reader == 0) then
if (Func50 == nil) then
Func50 = createMainLine(false);
end;
Func50("@ 5-0 ", 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] == CLR) then
print("Signal : occupying " .. BlockName[Block1] .. "(" .. Block1 .. ")");
BK[Block1] = BK[Block1] + 1;
MyLastBlockNo = Block2;-- save current state for the Caboose detection call of this function
-- save current state for the Caboose detection call of this function
WaitforCaboose = true;
return true;
end;
if (BK[Block2] == CLR) then
print("Signal : occupying " .. BlockName[Block2] .. "(" .. Block2 .. ")");
BK[Block2] = BK[Block2] + 1;
MyLastBlockNo = Block1;-- save current state for the Caboose detection call of this function
-- save current state for the Caboose detection call of this function
WaitforCaboose = true;
return true;
end;
---[[
if (BK[Block1] ~= CLR and BK[Block2] ~= CLR) 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 caboose comes along
if (Block1 == ALIQUIPPA_BLOCK or
Block1 == MCKEES_ROCKS_BLOCK or
Block1 == BEAVER_FALLS_BLOCK or
Block1 == NEW_CASTLE_BLOCK or
Block1 == YOUNGSTOWN_BLOCK) then
print("Signal : occupying " .. BlockName[Block2] .. "(" .. Block2 .. ")");
BK[Block2] = BK[Block2] + 1; -- bump the siding occupancy count
MyLastBlockNo = Block1; -- setup to decrement the head block occupancy count
end;
if (Block2 == ALIQUIPPA_BLOCK or
Block2 == MCKEES_ROCKS_BLOCK or
Block2 == BEAVER_FALLS_BLOCK or
Block2 == NEW_CASTLE_BLOCK or
Block2 == YOUNGSTOWN_BLOCK) then
print("Signal : occupying " .. BlockName[Block1] .. "(" .. Block1 .. ")");
BK[Block1] = BK[Block1] + 1; -- bump the siding occupancy count
MyLastBlockNo = Block2; -- setup to decrement the head block occupancy count
end;
end;
--]]
-- save current state for the Caboose detection call of this function
WaitforCaboose = true;
return true;

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
print("Signal : " .. DetectorID .. "Caboose 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");
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 == CLR) 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) then -- if the result is a number, then use it, otherwise, ignore it
BK[Enum] = BK[Enum] + 1;
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) then -- if the result is a number, then use it, otherwise, ignore it
BK[Enum] = CLR;
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] = CLR;
end;
print("All blocks manually cleared");
return true;
end
Function04Name, Function04Label = F04, "Clear All Blocks";
--[[---------------------------------------------------------------------------------------]]
--
-- Test routine to accept events from "Layout 4 Block Occupancy.lua"
--
function event(FromPC, Type, P1, P2, P3)
print("event() - From PC# ", FromPC, " EventType = ", Type, " Parameters = ", P1, " ", P2, " ", P3);
if (Type == BLOCK_OCCUPANCY_EVENT) then
if (P1 ~= 0) then
if (P2 >= CLR) then
BK[P1] = P2; -- set occupancy flag
end;
end;
end;
if (Type == TAG_EVENT) then
-- Detector = P1
-- Reader = P2
-- P3 ~= 0 means engine, P3 == 0 means CABOOSE
-- 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.
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 Eastbound.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. This script runs an engine eastbound using the Balloon track loop in order to have a continuous run.

  Under construction


Here is the script  Thinking Engine Headway Eastbound.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
local MyDirection;
title = "Headway ";
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 StartBlockNo, EndBlockNo;
local CurrentBlockNo, LastBlockNo, NextBlockNo;
local StartedFlag = false;
local RFIDFlag = false;
local ResetinProgress = false;
local Headway = false;
local LLLoop = true; -- start in looping mode
local Layout4;
local MyTIUNo, Enum;
local MyEngineNo = 12;
Title(title, MyEngineNo);
local SoundOff = false;
local EngineSoundSave;
local BeepOn = false;
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;
local Func60 = nil;
local Func61 = nil;

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

function StopAfterDelay(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];
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, "StopAfterDelay() : 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, "StopAfterDelay() : Engine cannot stop in time (Stopping Distance > Target_Distance)");
return StopDelay, Target_Distance;
end;
print(string.format("StopAfterDelay() : %s : Target Distance = %d in - Stopping Distance = %.3f in - Speed %d Smph = Stop Delay %.3f seconds",
BlockName[StoppingBlockNo], Target_Distance, StoppingDistance, EngSpeed, StopDelay));
-- 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;
print(string.format("StopAfterDelay() : 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 BlockSpeedLimit; -- Smph
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
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
print("MainLine2Passing : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- TODO initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Adjust speed for the current block
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("MainLine2Passing : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
--
RegisterCabSignals(NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]);
-----------------------------------------------------------------------------------------------------
local Aspect = (GetCabAspect(HOMESIGNAL));
print ("MainLine2Passing : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
-- TODO check if home signal is AsStopSignal or 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 when AsStopSignal is cleared and
-- block occupancy is obtained.
if (Aspect == AsStopSignal or Aspect == AsStopandProceed) then
-- stop in this block
print("MainLine2Passing : Home Signal aspect is Stop Signal or Stop and Proceed. Stop the engine");
-- stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
-----------------------------------------------------------------------------------------------------
-- set next block number
if (MyDirection == SuperiorDirection) then
NextBlockNo = Normal_Track; -- Westbound trains are superior, take the through track if possible
else
NextBlockNo = Reverse_Track; -- Eastbound trains are inferior, take the passing track if possible
end;
-- TODO 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 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
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, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end
else -- MyDirection ~= SuperiorDirection
if (TrySetOccupancy(Reverse_Track, 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, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
end;
end;

-- 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 Stop("MainLine2Passing : Timed out waiting for " .. BlockName[Normal_Track]);
end;
else
result, NextBlockNo = SingleSetOccupancy({Reverse_Track}, MyEngineNo);
if (not result) then
return Stop("MainLine2Passing : Timed out waiting for " .. BlockName[Reverse_Track]);
end;
end;

GotBlock = true; -- indicate Block is locked
if (EngineStopped) then
-- start moving if stopped
local Aspect = (GetCabAspect(HOMESIGNAL));
print ("MainLine2Passing : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
-- NOTE: mainline to passing is never an Absolute 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("MainLine2Passing : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("MainLine2Passing : %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("MainLine2Passing : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
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 (Stop Delay = " .. StopDelay .. ")");
--
-- ASAP Once the caboose is detected (without any clearing delay) return switch at entrance of passing track back
-- to NORMAL - this switch was set to REVERSE as result 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("MainLine2Passing : " .. SwitchName[ThisSwitch] .. " to Normal");
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 10 minutes)
-- wait for MyNextBlockNo to be locked (either the thru or passing track)
local result;
-- set occupancy flag in MyNextBlockNo
if (MyNextBlockNo == Normal_Track) then
result, MyNextBlockNo = SingleSetOccupancy({Normal_Track}, MyEngineNo);
if (not result) then
return Stop("MainLine2Passing : Timed out waiting for " .. BlockName[Normal_Track]);
end;
end;
if (MyNextBlockNo == Reverse_Track) then
result, MyNextBlockNo = SingleSetOccupancy({Reverse_Track}, MyEngineNo);
if (not result) then
return Stop("MainLine2Passing : Timed out waiting for " .. BlockName[Reverse_Track]);
end;
end;
GotBlock = true; -- indicate needed Block is locked
end;

if (EngineStopped) then
-- start moving if stopped
local Aspect = (GetCabAspect(HOMESIGNAL));
print ("MainLine2Passing : Home Signal in ", BlockName[MyCurrentBlockNo], " : " .. AspectNames[Aspect]);
-- NOTE: Mainline2Passing is never an Absolute 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("MainLine2Passing : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("MainLine2Passing : %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 BlockSpeedLimit; -- Smph
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local function MainLine2MainLine(DetectorID, EngineNo, TagLocation, CarModel, ThisSwitch, 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) or
(not WaitforCaboose and (TagLocation == TAG_ON_REAR_TRUCK)))) then -- if MyEngineNo in tag
print("MainLine2MainLine : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- TODO initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Adjust speed for the current block
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("MainLine2MainLine : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
--
RegisterCabSignals(NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]);
-----------------------------------------------------------------------------------------------------
local Aspect = (GetCabAspect(HOMESIGNAL));
print ("MainLine2MainLine : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
-- TODO check if home signal is AsStopSignal or AsStopandProceed. If it is, stop in this block. Set EngineSTopped = true.
-- Then go on to check TryMultiSetOccupancy and if we get it, start moving again.
if (Aspect == AsStopSignal or Aspect == AsStopandProceed) then
-- stop in this block
print("MainLine2MainLine : Home Signal aspect is Stop Signal or Stop and Proceed. Stop the engine");
-- stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
-----------------------------------------------------------------------------------------------------
NextBlockNo = Block[1]; -- set NextBlockNo to Block[1]
-- TODO 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, 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;

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 10 minutes)
if (not MultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then -- set occupancy flag in all blocks
return Stop("MainLine2MainLine : 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 ("MainLine2MainLine : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
-- NOTE: No Absolute signals between mainline blocks
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("MainLine2MainLine : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("MainLine2MainLine : %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("MainLine2Mainline : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
print("MainLine2MainLine : " .. DetectorID .. "Caboose detected (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 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("MainLine2MainLine : " .. SwitchName[ThisSwitch] .. " to Normal");
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 10 minutes)
if (not MultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then -- set occupancy flag in all blocks
return Stop("MainLine2MainLine : Timed out waiting for " .. BlockName[MyNextBlockNo]);
end;
GotBlock = true;
end;
if (EngineStopped) then
-- start moving if stopped
local Aspect = (GetCabAspect(HOMESIGNAL));
print ("MainLine2MainLine : Home Signal in ", BlockName[MyCurrentBlockNo], " : " .. AspectNames[Aspect]);
-- NOTE: mainline to mainline is never an Absolute 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("MainLine2MainLine : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("MainLine2MainLine : %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 BlockSpeedLimit; -- Smph
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
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 Stop("Passing2MainLine : 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
print("Passing2MainLine : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- TODO initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Adjust speed for the current block
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("Passing2MainLine : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
--
RegisterCabSignals(NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]);
-----------------------------------------------------------------------------------------------------
local Aspect = (GetCabAspect(HOMESIGNAL));
print ("Passing2MainLine : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
-- TODO check if home signal is AsStopSignal or AsStopandProceed. If it is, stop in this block. Set EngineSTopped = true.
-- Then go on to check TryMultiSetOccupancy and if we get it, start moving again.
if (Aspect == AsStopSignal or Aspect == AsStopandProceed) then
-- stop in this block
print("Passing2MainLine : Home Signal aspect is Stop Signal or Stop and Proceed. Stop the engine");
-- stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
-----------------------------------------------------------------------------------------------------
--
-- ASAP, set the switch at entrance to passing 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("Passing2MainLine : " .. SwitchName[ThisSwitch] .. " to Normal");
else
Switch(REVERSE, Switches[ThisSwitch][CHAN_NUMBER], Switches[ThisSwitch][AIU_NUMBER], Switches[ThisSwitch][TIU_NUMBER], 0);
print("Passing2MainLine : " .. 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, 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;

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
-- it may occupy two blocks
-- wait for 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 Stop("Passing2MainLine : 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 ("Passing2MainLine : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
-- TODO check that home signal is not AsStopSignal (but it can be AsStopandProceed)
-- NOTE: Passing to mainline is the only case where an Absolute Stop Signal can appear
WaitforStopSignaltoClear();
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("Passing2MainLine : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("Passing2MainLine : %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("Passing2MainLine : Next Switch " .. SwitchName[NextSwitch] .. " to Normal");
end;
if (CurrentBlockNo == Reverse_Track) then
Switch(REVERSE, Switches[NextSwitch][CHAN_NUMBER], Switches[NextSwitch][AIU_NUMBER], Switches[NextSwitch][TIU_NUMBER], 0);
print("Passing2MainLine : NextSwitch " .. SwitchName[NextSwitch] .. " to Reverse");
end;
end;
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
DisplayBlockNames("Passing2MainLine : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
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("Passing2MainLine : " .. DetectorID .. "Caboose detected (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("Passing2MainLine : %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("Passing2MainLine : " .. "Switch cleared after " .. SleepDelay .. " seconds")
end;
--
-- Once we reach the switch clearing distance, return switch at entrance to passing 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("Passing2MainLine : " .. SwitchName[ThisSwitch] .. " to Normal");
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 10 minutes)
if (not MultiSetOccupancy(Headway and {Block[1]} or Block, MyEngineNo)) then -- set occupancy flag in all blocks
return Stop("Passing2MainLine : Timed out waiting for " .. BlockName[MyNextBlockNo]);
end;
GotBlock = true; -- indicate Block is locked
-- set next switch (passing 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("Passing2MainLine : 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("Passing2MainLine : Next Switch " .. SwitchName[NextSwitch] .. " to Reverse");
end;
end;
end;

if (EngineStopped) then
-- start moving if stopped
local Aspect = (GetCabAspect(HOMESIGNAL));
print ("Passing2MainLine : Home Signal in ", BlockName[MyCurrentBlockNo], " : " .. AspectNames[Aspect]);
-- check that home signal is not AsStopSignal (but it can be AsStopandProceed)
-- NOTE: Passing2Mainline is the only case where an Absolute Stop Signal can appear
WaitforStopSignaltoClear();
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("Passing2MainLine : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("Passing2MainLine : %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 BlockSpeedLimit; -- Smph
local MyCurrentBlockNo, MyLastBlockNo, MyNextBlockNo;
local function MainLine2Yard(DetectorID, EngineNo, TagLocation, CarModel, Yard1, Yard2, ThisSwitch, Block)
if (type(Block) ~= "table") then return Stop("MainLine2Yard : 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
print("MainLine2Yard : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Adjust speed for the current block
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("MainLine2Yard : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
--
RegisterCabSignals(NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]);
-----------------------------------------------------------------------------------------------------
local Aspect = (GetCabAspect(HOMESIGNAL));
print ("MainLine2Yard : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
-- TODO check if home signal is AsStopSignal or 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.
if (Aspect == AsStopSignal or Aspect == AsStopandProceed) then
-- stop in this block
print("MainLine2Yard : Home Signal aspect is Stop Signal or Stop and Proceed. Stop the engine");
-- stop in CurrentBlockNo
StopDelay, Target_Distance = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
NextBlockNo = Block[1]; -- ending block number
-----------------------------------------------------------------------------------------------------
-- TODO 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, BlockSpeedLimit)
EngineStopped = true;
GotBlock = false;
StopTime = RunTime();
end;
end;
-- 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 ("MainLine2Yard : Home Signal in ", BlockName[CurrentBlockNo], " : " .. AspectNames[Aspect]);
-- NOTE: mainline to yard is never an Absolute 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("MainLine2Yard : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("MainLine2Yard : %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("MainLine2Yard : 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("MainLine2Yard : Switch " .. SwitchName[ThisSwitch] .. " to Reverse");
end;
end;
WaitforCaboose = true;
-- save current state for the Caboose detection call of this function
DisplayBlockNames("MainLine2Yard : ", LastBlockNo, CurrentBlockNo, NextBlockNo);
MyCurrentBlockNo = CurrentBlockNo;
MyLastBlockNo = LastBlockNo;
MyNextBlockNo = NextBlockNo;
return true;

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
print("MainLine2Yard : " .. DetectorID .. "Caboose detected (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("MainLine2Yard : %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("MainLine2Yard : " .. "Switch cleared after " .. SleepDelay .. " seconds")
end;
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("MainLine2Yard : 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("MainLine2Yard : 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 ("MainLine2Yard : Home Signal in ", BlockName[MyCurrentBlockNo], " : " .. AspectNames[Aspect]);
-- NOTE: mainline to yard is never an Absolute 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("MainLine2Yard : Used Up Time ", UsedUpTime, " Stop Delay ", StopDelay, " Start Delay ", StartDelay);
SetSpeed(BlockSpeedLimit, StartDelay, MyEngineNo, MyTIUNo);
print(string.format("MainLine2Yard : %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 BlockSpeedLimit; -- Smph
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) or
(not WaitforCaboose and (TagLocation == TAG_ON_REAR_TRUCK)))) then -- if MyEngineNo in tag
print("StopInYard : " .. DetectorID .. "Engine " .. MyEngineNo .. " detected");
LastBlockNo = CurrentBlockNo;
CurrentBlockNo = NextBlockNo;
-- initialize block local variables
EngineStopped = false;
GotBlock = false;
--
-- Adjust speed for the current block
BlockSpeedLimit = AdjustSpeed(CurrentBlockNo); -- adjust speed for some randomness in high speed blocks
SetCounter(C3, BlockSpeedLimit);
SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
print(string.format("StopInYard : %s : Speed %d Smph", BlockName[CurrentBlockNo], BlockSpeedLimit));
--
NextBlockNo = OffLayoutBlock[1];

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

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

elseif (EngineNo == 0 and CarModel == CABOOSE and WaitforCaboose) then
print("StopInYard : " .. DetectorID .. "Caboose detected (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("StopInYard : %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("StopInYard : " .. "Switch cleared after " .. SleepDelay .. " seconds")
end;
if (MyLastBlockNo ~= 0) then
ClearOccupancy(MyLastBlockNo, MyEngineNo); -- clear LastBlockNo occupancy
end;
if (EngineStopped) then
print("StopInYard : " .. DetectorID .. "Stopped!");
RFIDFlag = false;
end
WaitforCaboose = false;
end;
return true;
end;

return StopInYard;
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;
Title(title, MyEngineNo);

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

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);
end
end
--
ShowLayout();
--
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("Setup : Engine Number = " .. MyEngineNo);
-- Show new title with Engine # and Name and put Engine # into Engine Number Spinner
local Name = GetName(MyEngineNo);
Title(title .. "#" .. MyEngineNo .. " " .. Name, MyEngineNo);
if (Layout4.StoppingisEmpty(MyEngineNo,1) == nil) then
return Stop("No StoppingDistance Table for Engine " .. MyEngineNo);
end
--
-- start of run
---------
local Enum = tonumber(InputBox(title, "Enter Direction " .. WB .. "=" .. DirectionNames[WB] .. " or " .. EB .. "=" .. DirectionNames[EB], MyDirection));
if (Enum ~= nil) then -- if the result is a number, 1 or 2, then use it, otherwise, ignore it
if (Enum == WB) then
MyDirection = Enum;
end;
if (Enum == EB) then
MyDirection = Enum;
end;
end;
print("Setup : Direction = " .. DirectionNames[MyDirection]);
--
-- must ask the dispatcher
print("Select Starting Block Number");
for k, v in pairs(BlockName) do
if (k <= 100) then
print(v .. "(" .. k .. ")");
end;
end;
--
-- read Starting Block Number
--
local response = InputQuery(title, "Enter Starting Block Number (0 for none)", ALIQUIPPA_BLOCK);
if (response == false) then
return Stop("Canceled");
end;
local Enum = tonumber(response);
if (Enum ~= nil and Enum ~= 0) then -- if the result is a block number, then use it, otherwise, ignore it
StartBlockNo = Enum;
print("Setup : Starting Block = " .. BlockName[Enum]);
end

CurrentBlockNo = StartBlockNo;
EndBlockNo = MCKEES_ROCKS_YARD1_BLOCK; -- EndBlockNo is not used because this sketch loops forever
--
-- create and register the cab signals
print("Setup : Creating Cab Signals");
CreateCab(Hd3Light, Hd3Light); -- create light signals - (home, distant) -- create as 3 head signals so that they accept all possible aspects
print("Setup : Registering the Cab Signals #", NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], " and #", NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]);
RegisterCabSignals(NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], NextSignal[MyDirection][CurrentBlockNo][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))]);

SetCounter(C3, BlockSpeed[MyDirection][StartBlockNo]);
--[[-------------------------]]
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(StartBlockNo, MyEngineNo); -- force set occupancy flag
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
if (EngineNo > 0) then -- it's an engine
if (EngineNo == MyEngineNo) then
if (BeepOn) then Beep(ASTERISK); end;
-- next line shows a different way to quote a string using brackets
printc(clGreen, string.format([[tag(%.2f) : Detector %d Reader %d Eng#%d %s %s #%s %s]],
RunTime(), Detector, Reader, EngineNo, GetRailroadName(Railroad), GetEngineName(CarModel), CarNumber, GetTagLocation(TagLocation)));
else
-- ignore engines that are not me
return true; -- true=continue to process tags, false=stop processing tags
end;
-- follow on to process the detected engine
else -- it's a car
if (CarModel == CABOOSE) 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-Caboose cars
return true; -- true=continue to process tags, false=stop processing tags
end;
-- 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 and MyDirection == EB) then
-- run back to Tag 5-0
if (Detector == 5 and Reader == 0) then
local DetectorID = "@ 5-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 = StopAfterDelay(StartBlockNo, MyEngineNo, WB, BlockSpeedLimit)
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 5-0
elseif (Detector == 5 and Reader == 0) 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("@ 5-0 ", EngineNo, TagLocation, CarModel, STRUTHERS_THRU_TRACK, STRUTHERS_STATION_TRACK, nil);

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

-- Tag 2-0
elseif (Detector == 2 and Reader == 0) then
if (Func20 == nil) then
Func20 = createMainLine2Passing(false);
end;
-- status: just entered NextBlockNo, occupancy already set
-- engine never stops in this block except for deadlock
Func20("@ 2-0 ", EngineNo, TagLocation, CarModel, COLLEGE_THRU_TRACK, COLLEGE_STATION_TRACK, STRUTHERS_EAST);

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

-- Tag 4-1
elseif (Detector == 4 and Reader == 1) 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("@ 4-1 ", EngineNo, TagLocation, CarModel, COLLEGE_EAST,
{MONACA_BLOCK, ALIQUIPPA_BLOCK, J_L_PASSING_TRACK});

-- Tag 6-1
elseif (Detector == 6 and Reader == 1) 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("@ 6-1 ", EngineNo, TagLocation, CarModel, nil,
{ALIQUIPPA_BLOCK, J_L_PASSING_TRACK});

-- Tag 6-0
elseif (Detector == 6 and Reader == 0) 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 = createMainLine2Passing(false);
end;
Func60("@ 6-0 ", EngineNo, TagLocation, CarModel, J_L_THRU_TRACK, J_L_PASSING_TRACK, nil);

-- Tag 1-0
elseif (Detector == 1 and Reader == 0) then
if (LLLoop == false) then
if (Func10 == nil) then
Func10 = createPassing2MainLine(false, SwitchClearingInches[10][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[10][MyDirection]);
end;
-- status: in block NextBlockNo, occupancy already set. Set ALIQUIPPA_WEST to NORMAL on Caboose detection.
Func10("@ 1-0 ", 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;

-- Tag 5-1
elseif (Detector == 5 and Reader == 1) then
-- status: just entered NextBlockNo, occupancy already set
if (LLLoop == false) then
if (Func51 == nil) then
Func51 = createMainLine2Yard(false, SwitchClearingInches[51][MyDirection]);
end;
Func51("@ 5-1 ", 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("@ 5-1 ", EngineNo, TagLocation, CarModel, ALIQUIPPA_EAST,
{LIONEL_IVES_BLOCK, YOUNGSTOWN_BLOCK, STRUTHERS_STATION_TRACK});
end;

-- Tag 3-0
elseif (Detector == 3 and Reader == 0) then
-- status: in block NextBlockNo, occupancy already set
if (LLLoop == false) then
if (Func30 == nil) then
Func30 = createStopInYard(false, SwitchClearingInches[30][MyDirection]); -- clear yard switch
end;
Func30("@ 3-0 ", 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("@ 3-0 ", EngineNo, TagLocation, CarModel, nil,
{YOUNGSTOWN_BLOCK, STRUTHERS_STATION_TRACK});
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.....");
--
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 ClearButton(Eng, TIU)
print("Clear Occupancy Flags");
-- clear occupancy flags
local x;
for x = FIRST_BLOCK, LAST_BLOCK do
OverrideOccupancy(x, 0);
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 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;
-- clear occupancy flags
local x;
for x = FIRST_BLOCK, LAST_BLOCK do
OverrideOccupancy(x, 0);
end;
--[[-----------------------------]]
-- clear all tag handling functions
Func30 = nil;
Func51 = nil;
Func10 = nil;
Func41 = nil;
Func21 = nil;
Func20 = nil;
Func40 = nil;
Func50 = nil;
Func60 = nil;
Func61 = 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
Func30 = nil;
Func51 = nil;
Func10 = nil;
Func41 = nil;
Func21 = nil;
Func20 = nil;
Func40 = nil;
Func50 = nil;
Func60 = nil;
Func61 = nil;

--[[------------------------------------------]]
--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);
--
-- Engine can start in any block
--
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
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);
end;

if (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
Switch(NORMAL, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0);
end;

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

if (CurrentBlockNo == STRUTHERS_STATION_TRACK and MyDirection == EB) then -- "Struthers Station Track"
LastBlockNo = YOUNGSTOWN_BLOCK;
NextBlockNo = NEW_CASTLE_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, 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);
end;

if (CurrentBlockNo == STRUTHERS_THRU_TRACK and MyDirection == EB) then -- "Struthers Thru Track" -- Eastbound trains don't use the thru track
LastBlockNo = YOUNGSTOWN_BLOCK;
NextBlockNo = NEW_CASTLE_BLOCK;
MultiSetOccupancy(Headway and {NextBlockNo} or {NextBlockNo, 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);
end;

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

if (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, MONACA_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);
end;

if (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, MONACA_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);
end;

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

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

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

if (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);
end;

if (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);
end;

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

if (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;
if (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;
--
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);

local BlockSpeedLimit = BlockSpeed[MyDirection][StartBlockNo];
SetSpeed(BlockSpeedLimit, 5, MyEngineNo, MyTIUNo);
SetCounter(C3, BlockSpeedLimit)

--[[----------------------------------------------------]]
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] .. "(" .. 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
Func30 = nil;
Func51 = nil;
Func10 = nil;
Func41 = nil;
Func21 = nil;
Func20 = nil;
Func40 = nil;
Func50 = nil;
Func60 = nil;
Func61 = nil;

-- 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);
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;
if (StartBlockNo == YOUNGSTOWN_YARD1_BLOCK) then
Switch(NORMAL, Switches[YOUNGSTOWN_YARD][CHAN_NUMBER], Switches[YOUNGSTOWN_YARD][AIU_NUMBER], Switches[YOUNGSTOWN_YARD][TIU_NUMBER], 0);
end;
if (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);
local BlockSpeedLimit = BlockSpeed[MyDirection][CurrentBlockNo];
SetSpeed(BlockSpeedLimit, 5, MyEngineNo, MyTIUNo);
SetCounter(C3, BlockSpeedLimit);
ResetinProgress = true;
return true
end;
Function12Name, Function12Label = ResetButton, "Initial Position";
--[[---------------------------------------------------------------------------------------]]

function ResumeSpeedButton()
print("Resume Speed " .. BlockSpeed[MyDirection][CurrentBlockNo] .. " Smph");
SetSpeed(BlockSpeed[MyDirection][CurrentBlockNo], 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("Lionel-Ives Loop Off");
LLLoop = false;
else
print("Lionel-Ives Loop On");
LLLoop = true;
end;
return true;
end
Function05Name, Function05Label = LLLButton, "Lionel-Ives Loop";
--[[---------------------------------------------------------------------------------------]]
-- Generate a block speed with some randomness
function AdjustSpeed(BlockNo)
local MySpeed;
MySpeed = BlockSpeed[MyDirection][BlockNo];
if (MySpeed >= 30) then -- adjust speed for some randomness in high speed blocks
if (math.random(1,4) ~= 1) then
MySpeed = MySpeed // 2;
end;
end;
return MySpeed;
end;
--[[---------------------------------------------------------------------------------------]]












Layout4.lua


  Under construction



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

if (Layout4.StoppingisEmpty(iEng,1) == nil) then
return Stop("No StoppingDistance Table for Engine " .. iEng);
end

StopDelay = (Struthers_Passenger_StoppingDistance - Layout4.stopping(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
if (MASTER_TIU == nil) then
MASTER_TIU = 1;
end;

-----------------------------------------------------------------------------
-- Layout constants
-----------------------------------------------------------------------------
-- These constants describe your layout

-- 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.
-- These table constructors were created using "Deceleration Test.lua"
local StoppingDistance = {
--[ 1] = {[1] = 0.18269896193772, [2] = 0.71418685121107, [3] = 1.2179930795848, [4] = 1.3287197231834, [5] = 2.3695501730104, [6] = 2.5356401384083, [7] = 3.4214532871972, [8] = 4.2740484429066, [9] = 6.4, [10] = 7.3245674740485, [11] = 8.8193771626297, [12] = 9.998615916955, [13] = 10.059515570934, [14] = 13.015916955017, [15] = 14.455363321799, [16] = 16.531487889273, [17] = 18.026297577855, [18] = 19.908650519031, [19] = 22.43875432526, [20] = 24.01660899654, [21] = 26.341868512111, [22] = 28.78339100346, [23] = 28.401384083045, [24] = 30.997923875432, [25] = 36.274048442907, [26] = 39.263667820069, [27] = 41.721799307959, [28] = 44.406920415225, [29] = 47.745328719723, [30] = 47.103114186851, [31] = 50.618685121107, [32] = 57.024221453287, [33] = 60.456747404844, [34] = 63.247058823529, [35] = 66.862283737024, [36] = 70.510726643599, [37] = 74.917647058823, [38] = 78.38892733564, [39] = 82.435986159169, [40] = 81.61660899654}
[ 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}
,
[ 3] = {[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}
,
[ 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}
,
[13] = {[1] = 0.1771626297578, [2] = 0.55916955017299, [3] = 0.96885813148789, [4] = 1.5446366782007, [5] = 1.8602076124567, [6] = 2.9896193771626, [7] = 3.8754325259515, [8] = 4.7612456747405, [9] = 5.8131487889273, [10] = 6.8871972318339, [11] = 7.4685121107266, [12] = 9.6387543252595, [13] = 9.9266435986159, [14] = 11.388235294118, [15] = 13.990311418685, [16] = 14.560553633218, [17] = 16.254671280277, [18] = 19.393771626298, [19] = 21.425605536332, [20] = 23.667820069204, [21] = 24.060899653979, [22] = 26.41937716263, [23] = 29.946020761246, [24] = 32.359861591696, [25] = 35.122491349481, [26] = 37.65813148789, [27] = 40.459515570934, [28] = 43.426989619377, [29] = 46.322491349481, [30] = 47.26366782007, [31] = 52.163321799308, [32] = 55.734256055363, [33] = 58.934256055363, [34] = 62.33356401384, [35] = 65.59446366782, [36] = 69.364705882353, [37] = 70.510726643599, [38] = 77.132179930796, [39] = 80.564705882352, [40] = 84.728027681661}
,
[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.12755927346674, [2] = 0.35494754355963, [3] = 0.85409252669034, [4] = 1.6471784443315, [5] = 2.2350603133521, [6] = 2.9449554004714, [7] = 3.8766927023155, [8] = 4.8694366132089, [9] = 5.7845357489486, [10] = 6.9824837084623, [11] = 9.6334981744236, [12] = 10.76489346952, [13] = 12.256782363544, [14] = 13.759763368304, [15] = 14.968803438554, [16] = 17.736285067246, [17] = 17.525534963257, [18] = 21.801543652077, [19] = 25.988815455008, [20] = 26.221749780469, [21] = 30.64195590886, [22] = 27.796829505015, [23] = 30.148356981097, [24] = 32.632989786015, [25] = 35.406017470075, [26] = 37.779729167629, [27] = 40.502842353376, [28] = 43.458889864584, [29] = 46.409391320424, [30] = 49.143596616906, [31] = 52.665341775662, [32] = 55.920876276748, [33] = 56.464389702824, [34] = 69.270231547812, [35] = 70.229699126496, [36] = 73.546240236632, [37] = 77.766788371771, [38] = 80.916947820863, [39] = 84.643897028239, [40] = 89.474511253871}
,
[16] = {[1] = 0.21075010398854, [2] = 0.47141470629015, [3] = 1.1313952950964, [4] = 1.6305402782271, [5] = 2.1463234274622, [6] = 3.0004159541526, [7] = 3.8267782040024, [8] = 4.8361602810001, [9] = 6.4833387253316, [10] = 7.0545824282479, [11] = 8.1527013911355, [12] = 9.6334981744234, [13] = 10.88136063225, [14] = 13.582289596524, [15] = 14.264454406803, [16] = 15.967093404816, [17] = 17.63645607062, [18] = 20.6146878033, [19] = 23.099320608217, [20] = 23.287886490733, [21] = 25.600591579239, [22] = 28.013125664371, [23] = 30.309192586773, [24] = 32.921384665157, [25] = 33.426075703656, [26] = 42.089014188658, [27] = 42.954198826085, [28] = 45.300180246799, [29] = 46.376114988215, [30] = 49.526274437307, [31] = 59.697739982437, [32] = 58.117114202523, [33] = 61.78305680085, [34] = 65.4101770116, [35] = 68.682349678791, [36] = 69.536442205482, [37] = 72.570134491843, [38] = 77.156722281278, [39] = 80.839303045709, [40] = 84.255673152471}
,
[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}
,
-- following are place holders until actual measurements are taken
-- A&S 0-8-0 #211
[ 2] = {[1] = 0.1, [2] = 0.6, [3] = 1.0, [4] = 1.5, [5] = 2.1, [6] = 2.9, [7] = 3.6, [8] = 4.6, [9] = 5.6, [10] = 6.6, [11] = 7.6, [12] = 9.0, [13] = 11.7, [14] = 11.8, [15] = 13.5, [16] = 15.1, [17] = 16.9, [18] = 18.6, [19] = 20.6, [20] = 22.7, [21] = 25.1, [22] = 27.1, [23] = 29.4, [24] = 31.6, [25] = 32.3, [26] = 36.7, [27] = 39.7, [28] = 42.3, [29] = 44.9, [30] = 46.1, [31] = 51.1, [32] = 54.4, [33] = 57.8, [34] = 60.9, [35] = 61.6, [36] = 67.7, [37] = 71.2, [38] = 74.8, [39] = 78.8, [40] = 80.0}
,
-- P&LE 2-8-0 #9378
[ 5] = {[1] = 0.1, [2] = 0.6, [3] = 1.0, [4] = 1.5, [5] = 2.1, [6] = 2.9, [7] = 3.6, [8] = 4.6, [9] = 5.6, [10] = 6.6, [11] = 7.6, [12] = 9.0, [13] = 11.7, [14] = 11.8, [15] = 13.5, [16] = 15.1, [17] = 16.9, [18] = 18.6, [19] = 20.6, [20] = 22.7, [21] = 25.1, [22] = 27.1, [23] = 29.4, [24] = 31.6, [25] = 32.3, [26] = 36.7, [27] = 39.7, [28] = 42.3, [29] = 44.9, [30] = 46.1, [31] = 51.1, [32] = 54.4, [33] = 57.8, [34] = 60.9, [35] = 61.6, [36] = 67.7, [37] = 71.2, [38] = 74.8, [39] = 78.8, [40] = 80.0}
,
-- NYC Berk #9401
[ 6] = {[1] = 0.1, [2] = 0.6, [3] = 1.0, [4] = 1.5, [5] = 2.1, [6] = 2.9, [7] = 3.6, [8] = 4.6, [9] = 5.6, [10] = 6.6, [11] = 7.6, [12] = 9.0, [13] = 11.7, [14] = 11.8, [15] = 13.5, [16] = 15.1, [17] = 16.9, [18] = 18.6, [19] = 20.6, [20] = 22.7, [21] = 25.1, [22] = 27.1, [23] = 29.4, [24] = 31.6, [25] = 32.3, [26] = 36.7, [27] = 39.7, [28] = 42.3, [29] = 44.9, [30] = 46.1, [31] = 51.1, [32] = 54.4, [33] = 57.8, [34] = 60.9, [35] = 61.6, [36] = 67.7, [37] = 71.2, [38] = 74.8, [39] = 78.8, [40] = 80.0}
,
-- P&LE RS-3 #8356
[17] = {[1] = 0.1, [2] = 0.6, [3] = 1.0, [4] = 1.5, [5] = 2.1, [6] = 2.9, [7] = 3.6, [8] = 4.6, [9] = 5.6, [10] = 6.6, [11] = 7.6, [12] = 9.0, [13] = 11.7, [14] = 11.8, [15] = 13.5, [16] = 15.1, [17] = 16.9, [18] = 18.6, [19] = 20.6, [20] = 22.7, [21] = 25.1, [22] = 27.1, [23] = 29.4, [24] = 31.6, [25] = 32.3, [26] = 36.7, [27] = 39.7, [28] = 42.3, [29] = 44.9, [30] = 46.1, [31] = 51.1, [32] = 54.4, [33] = 57.8, [34] = 60.9, [35] = 61.6, [36] = 67.7, [37] = 71.2, [38] = 74.8, [39] = 78.8, [40] = 80.0}
,
}

-- 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"
local StartingDistance = {
[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}
,
[12] = {[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}
--
-- This table is incomplete
-- I stopped work on it because it probably is not really necessary
--
}

--[[-------------------------------Stations-----------------------------------------------]]
ALIQUIPPA = 101;
COLLEGE = 102;
STRUTHERS = 103;
J_L = 104;
MCKEESROCKS = 105;
WESTALIQUIPPA = 106;
MCKEESROCKSYARD1 = 107
MCKEESROCKSYARD2 = 108
YOUNGSTOWN = 109
J_LTHRU = 110
MILL = 111
YOUNGSTOWNYARD1 = 112
YOUNGSTOWNYARD2 = 113
NEWCASTLE = 114

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

StationNames = {
[ALIQUIPPA] = "Aliquippa",
[COLLEGE] = "College",
[STRUTHERS] = "Struthers",
[J_L] = "J&L",
[MCKEESROCKS] = "McKees Rocks",
[WESTALIQUIPPA] = "West Aliquippa",
[MCKEESROCKSYARD1] = "McKees Rocks Yard 1",
[MCKEESROCKSYARD2] = "McKees Rocks Yard 2",
[YOUNGSTOWN] = "Youngstown",
[J_LTHRU] = "J&L Thru",
[MILL] = "Mill",
[YOUNGSTOWNYARD1] = "Youngstown Yard 1",
[YOUNGSTOWNYARD2] = "Youngstown Yard 2",
[NEWCASTLE] = "New Castle",
};
--[[-------------------------------Layout Tag 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;
MCKEESROCKS_YARD_DETECTOR = 3;
MCKEESROCKS_YARD_READER = 0;
TUNNEL_EAST_DETECTOR = 6;
TUNNEL_EAST_READER = 0;
TUNNEL_WEST_DETECTOR = 6;
TUNNEL_WEST_READER = 1;

--[[-------------------------------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",
};

--[[---------------------------------Event Types------------------------------------------]]
NOEVENT = 0;
RUNTOEVENT = 1;
STARTUPENGINE = 2;
SHUTDOWNENGINE = 3;
ONSHEET = 4;
BLOCK_OCCUPANCY_EVENT = 5;
TAG_EVENT = 6;
--[[----------------------------------Superior Direction-----------------------------------]]
SuperiorDirection = WB;
--[[----------------------------------Blocks-------------------------------------------]]
-- Real physical blocks can be numbered from 1 to 100
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
LAST_BLOCK = 17; -- LAST_BLOCK is the highest physical block number

-- 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",
};
--[[---------------------------------Next Signals---------------------------------------]]
SignalNames = {
[1] = "LI",
[2] = "YG",
[5] = "YG",
[6] = "ST",
[9] = "ST",
[10] = "NC",
[13] = "NC",
[14] = "CO",
[17] = "CO",
[18] = "BF",
[21] = "BF",
[22] = "MO",
[25] = "MO",
[26] = "AL",
[29] = "AL",
[30] = "JL",
[33] = "JL",
[34] = "MK",
[37] = "MK",
[38] = "L1",
}
NextSignal = {
[EB] = {
[YOUNGSTOWN_BLOCK] = {
[HOMESIGNAL] = 6,
[DISTANTSIGNAL] = 10,
},
[STRUTHERS_BLOCK] = {
[HOMESIGNAL] = 10,
[DISTANTSIGNAL] = 14,
},
[STRUTHERS_STATION_TRACK] = {
[HOMESIGNAL] = 10,
[DISTANTSIGNAL] = 14,
},
[NEW_CASTLE_BLOCK] = {
[HOMESIGNAL] = 14,
[DISTANTSIGNAL] = 18,
},
[COLLEGE_BLOCK] = {
[HOMESIGNAL] = 18,
[DISTANTSIGNAL] = 22,
},
[COLLEGE_STATION_TRACK] = {
[HOMESIGNAL] = 18,
[DISTANTSIGNAL] = 22,
},
[BEAVER_FALLS_BLOCK] = {
[HOMESIGNAL] = 22,
[DISTANTSIGNAL] = 26,
},
[MONACA_BLOCK] = {
[HOMESIGNAL] = 26,
[DISTANTSIGNAL] = 30,
},
[ALIQUIPPA_BLOCK] = {
[HOMESIGNAL] = 30,
[DISTANTSIGNAL] = 34,
},
[J_L_BLOCK] = {
[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,
},
},
[WB] = {
[LIONEL_IVES_BLOCK] = {
[HOMESIGNAL] = 37,
[DISTANTSIGNAL] = 33,
},
[MCKEES_ROCKS_BLOCK] = {
[HOMESIGNAL] = 33,
[DISTANTSIGNAL] = 29,
},
[J_L_BLOCK] = {
[HOMESIGNAL] = 29,
[DISTANTSIGNAL] = 25,
},
[J_L_PASSING_TRACK] = {
[HOMESIGNAL] = 29,
[DISTANTSIGNAL] = 25,
},
[ALIQUIPPA_BLOCK] = {
[HOMESIGNAL] = 25,
[DISTANTSIGNAL] = 21,
},
[MONACA_BLOCK] = {
[HOMESIGNAL] = 21,
[DISTANTSIGNAL] = 17,
},
[BEAVER_FALLS_BLOCK] = {
[HOMESIGNAL] = 17,
[DISTANTSIGNAL] = 13,
},
[COLLEGE_BLOCK] = {
[HOMESIGNAL] = 13,
[DISTANTSIGNAL] = 9,
},
[COLLEGE_STATION_TRACK] = {
[HOMESIGNAL] = 13,
[DISTANTSIGNAL] = 9,
},
[NEW_CASTLE_BLOCK] = {
[HOMESIGNAL] = 9,
[DISTANTSIGNAL] = 5,
},
[STRUTHERS_BLOCK] = {
[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,
},
}
}


--[[---------------------------------Block Speeds---------------------------------------]]

BlockSpeed = {
[EB] = { -- "Slow" Freight
[YOUNGSTOWN_YARD2_BLOCK] = 10,
[YOUNGSTOWN_YARD1_BLOCK] = 10,
[YOUNGSTOWN_BLOCK] = 20,
[STRUTHERS_THRU_TRACK] = 15,
[STRUTHERS_STATION_TRACK] = 10,
[NEW_CASTLE_BLOCK] = 22,
[COLLEGE_THRU_TRACK] = 15,
[COLLEGE_STATION_TRACK] = 10,
[ALIQUIPPA_BLOCK] = 25,
[J_L_PASSING_TRACK] = 10,
[J_L_THRU_TRACK] = 15,
[MCKEES_ROCKS_BLOCK] = 20,
[MCKEES_ROCKS_YARD2_BLOCK] = 10,
[MCKEES_ROCKS_YARD1_BLOCK] = 10,
[CLEVELAND_EL] = 0,
[ASHTABULA_NYC] = 0,
[CUMBERLAND_B_O] = 0,
[POINTS_SOUTH_N_W] = 0,
[LIONEL_IVES_BLOCK] = 15, -- very slow through here in case of stop
[MONACA_BLOCK] = 25,
[BEAVER_FALLS_BLOCK] = 25,
},
[WB] = { -- "Fast" Freight
[YOUNGSTOWN_YARD2_BLOCK] = 10,
[YOUNGSTOWN_YARD1_BLOCK] = 10,
[YOUNGSTOWN_BLOCK] = 22,
[STRUTHERS_THRU_TRACK] = 15,
[STRUTHERS_STATION_TRACK] = 10,
[NEW_CASTLE_BLOCK] = 28,
[COLLEGE_THRU_TRACK] = 15,
[COLLEGE_STATION_TRACK] = 10,
[ALIQUIPPA_BLOCK] = 35,
[J_L_PASSING_TRACK] = 10,
[J_L_THRU_TRACK] = 15,
[MCKEES_ROCKS_BLOCK] = 22,
[MCKEES_ROCKS_YARD2_BLOCK] = 10,
[MCKEES_ROCKS_YARD1_BLOCK] = 10,
[CLEVELAND_EL] = 0,
[ASHTABULA_NYC] = 0,
[CUMBERLAND_B_O] = 0,
[POINTS_SOUTH_N_W] = 0,
[LIONEL_IVES_BLOCK] = 18, -- very slow through here in case of stop
[MONACA_BLOCK] = 35,
[BEAVER_FALLS_BLOCK] = 35,
},
};

--[[--------------------------------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] = 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] = MASTER_TIU,
[AIU_NUMBER] = COLLEGE_EAST_AIU,
[CHAN_NUMBER] = COLLEGE_EAST_CHAN,
[NORMAL_SEGMENT] = COLLEGE_THRU_TRACK,
[REVERSE_SEGMENT] = COLLEGE_STATION_TRACK,
[COMMON_SEGMENT] = ALIQUIPPA_BLOCK
},

[YOUNGSTOWN_YARD] = {
[TIU_NUMBER] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = MASTER_TIU,
[AIU_NUMBER] = STRUTHERS_EAST_AIU,
[CHAN_NUMBER] = STRUTHERS_EAST_CHAN,
[NORMAL_SEGMENT] = STRUTHERS_THRU_TRACK,
[REVERSE_SEGMENT] = STRUTHERS_STATION_TRACK,
[COMMON_SEGMENT] = NEW_CASTLE_BLOCK
},

[CAMPBELL_YARD] = {
[TIU_NUMBER] = 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] = 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] = 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] = MASTER_TIU,
[AIU_NUMBER] = BALLOON_AIU,
[CHAN_NUMBER] = BALLOON_CHAN,
[NORMAL_SEGMENT] = 0,
[REVERSE_SEGMENT] = 0,
[COMMON_SEGMENT] = 0
},
};

--[[-----------------------------Next Block Structure---------------------------]]

NEXT_BLOCK = 1;
SWITCH = 2;
SWITCH_POSITION = 3;

NextBlock = {
[MCKEES_ROCKS_YARD2_BLOCK] = {
[WB] = {
[REVERSE] = {
[NEXT_BLOCK] = MCKEES_ROCKS_BLOCK,
[SWITCH] = MCKEES_ROCKS_YARD,
[SWITCH_POSITION] = REVERSE,
},
},
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = CUMBERLAND_B_O;
}
}, -- no Eastbound connection
},

[MCKEES_ROCKS_YARD1_BLOCK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = MCKEES_ROCKS_BLOCK,
[SWITCH] = MCKEES_ROCKS_YARD,
[SWITCH_POSITION] = NORMAL,
},
},
[EB] = {
[REVERSE] = {
[NEXT_BLOCK] = POINTS_SOUTH_N_W,
}
}, -- no Eastbound connection
},

[MCKEES_ROCKS_BLOCK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = J_L_THRU_TRACK,
[SWITCH] = ALIQUIPPA_EAST,
[SWITCH_POSITION] = NORMAL,
},
[REVERSE] = {
[NEXT_BLOCK] = J_L_PASSING_TRACK,
[SWITCH] = ALIQUIPPA_EAST,
[SWITCH_POSITION] = REVERSE,
},
},
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = MCKEES_ROCKS_YARD1_BLOCK,
[SWITCH] = MCKEES_ROCKS_YARD,
[SWITCH_POSITION] = NORMAL,
},
[REVERSE] = {
[NEXT_BLOCK] = MCKEES_ROCKS_YARD2_BLOCK,
[SWITCH] = MCKEES_ROCKS_YARD,
[SWITCH_POSITION] = REVERSE,
},
},
},

[J_L_THRU_TRACK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = ALIQUIPPA_BLOCK,
[SWITCH] = ALIQUIPPA_WEST,
[SWITCH_POSITION] = NORMAL,
},
},
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = MCKEES_ROCKS_BLOCK,
[SWITCH] = ALIQUIPPA_EAST,
[SWITCH_POSITION] = NORMAL,
},
},
},

[J_L_PASSING_TRACK] = {
[WB] = {
[REVERSE] = {
[NEXT_BLOCK] = ALIQUIPPA_BLOCK,
[SWITCH] = ALIQUIPPA_WEST,
[SWITCH_POSITION] = REVERSE,
},
},
[EB] = {
[REVERSE] = {
[NEXT_BLOCK] = MCKEES_ROCKS_BLOCK,
[SWITCH] = ALIQUIPPA_EAST,
[SWITCH_POSITION] = REVERSE,
},
},
},

[ALIQUIPPA_BLOCK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = J_L_THRU_TRACK,
[SWITCH] = COLLEGE_EAST,
[SWITCH_POSITION] = NORMAL,
},
[REVERSE] = {
[NEXT_BLOCK] = J_L_PASSING_TRACK,
[SWITCH] = COLLEGE_EAST,
[SWITCH_POSITION] = REVERSE,
},
},
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = J_L_THRU_TRACK,
[SWITCH] = ALIQUIPPA_WEST,
[SWITCH_POSITION] = NORMAL,
},
[REVERSE] = {
[NEXT_BLOCK] = J_L_PASSING_TRACK,
[SWITCH] = ALIQUIPPA_WEST,
[SWITCH_POSITION] = REVERSE,
},
},
},

[COLLEGE_THRU_TRACK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = NEW_CASTLE_BLOCK,
[SWITCH] = COLLEGE_WEST,
[SWITCH_POSITION] = NORMAL,
},
},
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = ALIQUIPPA_BLOCK,
[SWITCH] = COLLEGE_EAST,
[SWITCH_POSITION] = NORMAL,
},
},
},

[COLLEGE_STATION_TRACK] = {
[WB] = {
[REVERSE] = {
[NEXT_BLOCK] = NEW_CASTLE_BLOCK,
[SWITCH] = COLLEGE_WEST,
[SWITCH_POSITION] = REVERSE,
},
},
[EB] = {
[REVERSE] = {
[NEXT_BLOCK] = ALIQUIPPA_BLOCK,
[SWITCH] = COLLEGE_EAST,
[SWITCH_POSITION] = REVERSE,
},
},
},

[NEW_CASTLE_BLOCK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = STRUTHERS_THRU_TRACK,
[SWITCH] = STRUTHERS_EAST,
[SWITCH_POSITION] = NORMAL,
},
[REVERSE] = {
[NEXT_BLOCK] = STRUTHERS_STATION_TRACK,
[SWITCH] = STRUTHERS_EAST,
[SWITCH_POSITION] = REVERSE,
},
},
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = STRUTHERS_THRU_TRACK,
[SWITCH] = COLLEGE_WEST,
[SWITCH_POSITION] = NORMAL,
},
[REVERSE] = {
[NEXT_BLOCK] = STRUTHERS_STATION_TRACK,
[SWITCH] = COLLEGE_WEST,
[SWITCH_POSITION] = REVERSE,
},
},
},

[STRUTHERS_THRU_TRACK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = YOUNGSTOWN_BLOCK,
[SWITCH] = STRUTHERS_WEST,
[SWITCH_POSITION] = NORMAL,
},
},
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = NEW_CASTLE_BLOCK,
[SWITCH] = STRUTHERS_EAST,
[SWITCH_POSITION] = NORMAL,
},
},
},
[STRUTHERS_STATION_TRACK] = {
[WB] = {
[REVERSE] = {
[NEXT_BLOCK] = YOUNGSTOWN_BLOCK,
[SWITCH] = STRUTHERS_WEST,
[SWITCH_POSITION] = REVERSE,
},
},
[EB] = {
[REVERSE] = {
[NEXT_BLOCK] = NEW_CASTLE_BLOCK,
[SWITCH] = STRUTHERS_EAST,
[SWITCH_POSITION] = REVERSE,
},
},
},

[YOUNGSTOWN_BLOCK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = YOUNGSTOWN_YARD1_BLOCK,
[SWITCH] = YOUNGSTOWN_YARD,
[SWITCH_POSITION] = NORMAL,
},
[REVERSE] = {
[NEXT_BLOCK] = YOUNGSTOWN_YARD2_BLOCK,
[SWITCH] = YOUNGSTOWN_YARD,
[SWITCH_POSITION] = REVERSE,
},
},
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = STRUTHERS_THRU_TRACK,
[SWITCH] = STRUTHERS_WEST,
[SWITCH_POSITION] = NORMAL,
},
[REVERSE] = {
[NEXT_BLOCK] = STRUTHERS_STATION_TRACK,
[SWITCH] = STRUTHERS_WEST,
[SWITCH_POSITION] = REVERSE,
},
},
},

[YOUNGSTOWN_YARD2_BLOCK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = ASHTABULA_NYC,
}
}, -- no Westbound connection
[EB] = {
[REVERSE] = {
[NEXT_BLOCK] = YOUNGSTOWN_BLOCK,
[SWITCH] = YOUNGSTOWN_YARD,
[SWITCH_POSITION] = REVERSE,
},
},
},

[YOUNGSTOWN_YARD1_BLOCK] = {
[WB] = {
[NORMAL] = {
[NEXT_BLOCK] = CLEVELAND_EL,
}
}, -- no Westbound connection
[EB] = {
[NORMAL] = {
[NEXT_BLOCK] = YOUNGSTOWN_BLOCK,
[SWITCH] = YOUNGSTOWN_YARD,
[SWITCH_POSITION] = NORMAL,
},
},
},
}

--[[-------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 = {
[30] = {
[EB] = 24, -- this depends on the state of Ives Switch, otherwise 0
[WB] = 0,
},
[51] = {
[WB] = 30,
[EB] = 0,
},
[10] = {
[EB] = 56, -- to clear two switches into J&L thru and passing tracks
[WB] = 0,
},
[41] = {
[WB] = 34,
[EB] = 0,
},
[21] = {
[EB] = 32,
[WB] = 0,
},
[20] = {
[WB] = 32,
[EB] = 0,
},
[40] = {
[EB] = 34,
[WB] = 0,
},
[50] = {
[WB] = 24, -- this depends on the state of Ives Switch, otherwise 0
[EB] = 0,
},
[60] = { -- no switches on either side of this detector
[EB] = 0,
[WB] = 0,
},
[61] = { -- no switches on either side of this detector
[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;

--
-- Stopping distances for stops at a station
--
local StDiStation = {
[ALIQUIPPA] = {
[WB] = { -- Passenger Station stop WB from McKees Rocks
[PASSENGER] = 130,
[FREIGHT] = 120,
[DETECTOR] = 1,
[READER] = 0,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[PASSENGER] = 380,
[FREIGHT] = 380,
[DETECTOR] = 4,
[READER] = 1,
},
},
[STRUTHERS] = {
[WB] = { -- Passenger Station stop WB from McKees Rocks
[PASSENGER] = 75,
[FREIGHT] = 75,
[DETECTOR] = 2,
[READER] = 0,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[PASSENGER] = 95,
[FREIGHT] = 95,
[DETECTOR] = 4,
[READER] = 0,
},
},
[COLLEGE] = {
[WB] = { -- Passenger Station stop WB from McKees Rocks
[PASSENGER] = 85,
[FREIGHT] = 85,
[DETECTOR] = 4,
[READER] = 1,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[PASSENGER] = 105,
[FREIGHT] = 105,
[DETECTOR] = 2,
[READER] = 1,
}
},
[NEWCASTLE] = {
[WB] = { -- Passenger Station stop WB from McKees Rocks
[PASSENGER] = 90,
[FREIGHT] = 90,
[DETECTOR] = 2,
[READER] = 1,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[PASSENGER] = 270,
[FREIGHT] = 270,
[DETECTOR] = 2,
[READER] = 0,
}
},
[WESTALIQUIPPA] = { -- Passenger Station stop from McKees Rocks
[WB] = {
[PASSENGER] = 250,
[FREIGHT] = 250,
[DETECTOR] = 1,
[READER] = 0,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[PASSENGER] = 250,
[FREIGHT] = 250,
[DETECTOR] = 4,
[READER] = 1,
},
},
[MCKEESROCKS] = {
[WB] = { -- Passenger Station stop WB from McKees Rocks
[PASSENGER] = 53,
[FREIGHT] = 53,
[DETECTOR] = 3,
[READER] = 0,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[PASSENGER] = 50,
[FREIGHT] = 50,
[DETECTOR] = 5,
[READER] = 1,
},
},

[YOUNGSTOWN] = {
[WB] = { -- Passenger Station stop WB from McKees Rocks
[PASSENGER] = 270,
[FREIGHT] = 270,
[DETECTOR] = 4,
[READER] = 0,
},
[EB] = { -- Passenger Station stop EB from Youngstown
[PASSENGER] = 50,
[FREIGHT] = 50,
[DETECTOR] = 5,
[READER] = 0,
},
},
[J_L] = {
[WB] = { -- Siding Track stop WB from McKees Rocks
[PASSENGER] = 133,
[FREIGHT] = 133,
[DETECTOR] = 5,
[READER] = 1,
},
[EB] = { -- Siding Track stop EB from Youngstown
[PASSENGER] = 137,
[FREIGHT] = 137,
[DETECTOR] = 1,
[READER] = 0,
},
},

[J_LTHRU] = {
[WB] = { -- Thru Track stop WB from McKees Rocks
[PASSENGER] = 133,
[FREIGHT] = 133,
[DETECTOR] = 5,
[READER] = 1,
},
[EB] = { -- Thru Track stop EB from Youngstown
[PASSENGER] = 165,
[FREIGHT] = 165,
[DETECTOR] = 1,
[READER] = 0,
},
},

[MILL] = {
[EB] = {
[PASSENGER] = 127,
[FREIGHT] = 127,
[DETECTOR] = 1,
[READER] = 0,
},
[WB] = {
[PASSENGER] = 127,
[FREIGHT] = 127,
[DETECTOR] = 5,
[READER] = 1,
},
},

[YOUNGSTOWNYARD1] = {
[WB] = {
[PASSENGER] = 117,
[FREIGHT] = 117,
[DETECTOR] = 5,
[READER] = 0,
},
[EB] = {}, -- no EB traffic
},

[YOUNGSTOWNYARD2] = {
[WB] = {
[PASSENGER] = 118,
[FREIGHT] = 118,
[DETECTOR] = 5,
[READER] = 0,
},
[EB] = {}, -- no EB traffic
},

[MCKEESROCKSYARD1] = {
[EB] = {
[PASSENGER] = 101,
[FREIGHT] = 101,
[DETECTOR] = 3,
[READER] = 0,
},
[WB] = {}, -- no WB traffic
},

[MCKEESROCKSYARD2] = {
[EB] = {
[PASSENGER] = 102,
[FREIGHT] = 102,
[DETECTOR] = 3,
[READER] = 0,
},
[WB] = {}, -- to WB traffic
},
}

--
-- Stopping distances for stops at a block location
-- Note: Short trains are 50 inches long (Diesel engine, 2 cars and a caboose)
--
local StDiBlock = {
[ALIQUIPPA_BLOCK] = {
[WB] = {
[PASSENGER] = 395,
[FREIGHT] = 395,
[DETECTOR] = 1,
[READER] = 0,
},
[EB] = {
[PASSENGER] = 395,
[FREIGHT] = 395,
[DETECTOR] = 6,
[READER] = 0,
},
},

[STRUTHERS_THRU_TRACK] = {
[WB] = {
[PASSENGER] = 75,
[FREIGHT] = 75,
[DETECTOR] = 2,
[READER] = 0,
},
[EB] = {
[PASSENGER] = 75,
[FREIGHT] = 75,
[DETECTOR] = 4,
[READER] = 0,
},
},

[STRUTHERS_STATION_TRACK] = {
[WB] = {
[PASSENGER] = 75,
[FREIGHT] = 75,
[DETECTOR] = 2,
[READER] = 0,
},
[EB] = {
[PASSENGER] = 75,
[FREIGHT] = 75,
[DETECTOR] = 4,
[READER] = 0,
},
},

[COLLEGE_THRU_TRACK] = {
[WB] = {
[PASSENGER] = 85,
[FREIGHT] = 85,
[DETECTOR] = 4,
[READER] = 1,
},
[EB] = {
[PASSENGER] = 85,
[FREIGHT] = 85,
[DETECTOR] = 2,
[READER] = 1,
}
},

[COLLEGE_STATION_TRACK] = {
[WB] = {
[PASSENGER] = 85,
[FREIGHT] = 85,
[DETECTOR] = 4,
[READER] = 1,
},
[EB] = {
[PASSENGER] = 85,
[FREIGHT] = 85,
[DETECTOR] = 2,
[READER] = 1,
}
},

[MCKEES_ROCKS_BLOCK] = {
[WB] = {
[PASSENGER] = 50,
[FREIGHT] = 50,
[DETECTOR] = 3,
[READER] = 0,
},
[EB] = {
[PASSENGER] = 50,
[FREIGHT] = 50,
[DETECTOR] = 5,
[READER] = 1,
},
},

[NEW_CASTLE_BLOCK] = {
[WB] = {
[PASSENGER] = 280,
[FREIGHT] = 280,
[DETECTOR] = 3,
[READER] = 0,
},
[EB] = {
[PASSENGER] = 280,
[FREIGHT] = 280,
[DETECTOR] = 5,
[READER] = 1,
},
},

[YOUNGSTOWN_BLOCK] = {
[WB] = {
[PASSENGER] = 250,
[FREIGHT] = 250,
[DETECTOR] = 4,
[READER] = 0,
},
[EB] = {
[PASSENGER] = 250,
[FREIGHT] = 250,
[DETECTOR] = 5,
[READER] = 0,
},
},

[MONACA_BLOCK] = {
[WB] = {
[PASSENGER] = 150,
[FREIGHT] = 150,
[DETECTOR] = 6,
[READER] = 0,
},
[EB] = {
[PASSENGER] = 150,
[FREIGHT] = 150,
[DETECTOR] = 6,
[READER] = 1,
},
},
[BEAVER_FALLS_BLOCK] = {
[WB] = {
[PASSENGER] = 325,
[FREIGHT] = 325,
[DETECTOR] = 6,
[READER] = 1,
},
[EB] = {
[PASSENGER] = 325,
[FREIGHT] = 325,
[DETECTOR] = 1,
[READER] = 1,
},
},

[J_L_PASSING_TRACK] = {
[WB] = {
[PASSENGER] = 125,
[FREIGHT] = 125,
[DETECTOR] = 5,
[READER] = 1,
},
[EB] = {
[PASSENGER] = 125,
[FREIGHT] = 125,
[DETECTOR] = 1,
[READER] = 0,
},
},

[J_L_THRU_TRACK] = {
[WB] = {
[PASSENGER] = 120,
[FREIGHT] = 120,
[DETECTOR] = 5,
[READER] = 1,
},
[EB] = {
[PASSENGER] = 120,
[FREIGHT] = 120,
[DETECTOR] = 1,
[READER] = 0,
},
},

[YOUNGSTOWN_YARD1_BLOCK] = {
[WB] = {
[PASSENGER] = 106,
[FREIGHT] = 106,
[DETECTOR] = 5,
[READER] = 0,
},
[EB] = {}, -- no EB traffic
},

[YOUNGSTOWN_YARD2_BLOCK] = {
[WB] = {
[PASSENGER] = 105,
[FREIGHT] = 105,
[DETECTOR] = 5,
[READER] = 0,
},
[EB] = {}, -- no EB traffic
},

[MCKEES_ROCKS_YARD1_BLOCK] = {
[EB] = {
[PASSENGER] = 100,
[FREIGHT] = 100,
[DETECTOR] = 3,
[READER] = 0,
},
[WB] = {}, -- no WB traffic
},

[MCKEES_ROCKS_YARD2_BLOCK] = {
[EB] = {
[PASSENGER] = 100,
[FREIGHT] = 100,
[DETECTOR] = 3,
[READER] = 0,
},
[WB] = {}, -- no WB traffic
},

[LIONEL_IVES_BLOCK] = {
[WB] = {
[PASSENGER] = 20,
[FREIGHT] = 20,
[DETECTOR] = 5,
[READER] = 0,
},
[EB] = {
[PASSENGER] = 20,
[FREIGHT] = 20,
[DETECTOR] = 3,
[READER] = 0,
},
},
}

--[[-----------------------------------------------------------------------------]]
-- high level API
--[[-----------------------------------------------------------------------------]]

function Layout.STOPBlock(block, Direction, ConsistType)
return StDiBlock[block][Direction][ConsistType]
end
--[[-----------------------------------------------------------------------------]]

function Layout.STOPStation(Locale, Direction, ConsistType)
return StDiStation[Locale][Direction][ConsistType]
end
--[[-----------------------------------------------------------------------------]]

function Layout.StoppingisEmpty(Engine)
return (StoppingDistance[Engine] == nil)
end
--[[-----------------------------------------------------------------------------]]

function Layout.Stopping(Engine, Speed)
if (StoppingDistance[Engine] ~= nil) then
if (StoppingDistance[Engine][Speed] ~= nil) then
return StoppingDistance[Engine][Speed];
else
return 0
end
else
return 0
end
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
--[[------------------------------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 and College------------------]]
G:addUndirectedEdge(13, 14, 5, NEW_CASTLE_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


return Layout











Downloads

You can download RTC from here. Includes all of the above scripts and many more. NOTE - 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.