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

More RFID Tag Videos


RTC Control Langauge - Scripting

This Page last updated on .

As Mike Hewett pointed out, one of the limitations of the new (in 2018) "Program Control" ability is that you have to know how to program in C++ and have the complete RTC development environment on your computer. Not at all practical......

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



To let the user write his own scripts to control the RTC program, I needed a scheme which would read in a script written in a (fairly) simple control language. That script would be able to send commands to the TIU.

Searching on the Internet turned up several possibilities. I was amazed that people had written programs to handle script languages. Its a lot of work. I found several good candidates.

I picked one, called "Lua". From the lua wikipedia page:

Lua (/ˈlə/ LOO; from Portuguese: lua [ˈlu.(w)ɐ] meaning moon)[a] is a lightweight, multi-paradigm programming language designed primarily for embedded use in applications.[2] Lua is cross-platform, since the interpreter is written in ANSI C,[3] and has a relatively simple C API.[4]

Lua was originally designed in 1993 as a language for extending software applications to meet the increasing demand for customization at the time. It provided the basic facilities of most procedural programming languages, but more complicated or domain-specific features were not included; rather, it included mechanisms for extending the language, allowing programmers to implement such features. As Lua was intended to be a general embeddable extension language, the designers of Lua focused on improving its speed, portability, extensibility, and ease-of-use in development.

Lua was created in 1993 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar Celes, members of the Computer Graphics Technology Group (Tecgraf) at the Pontifical Catholic University of Rio de Janeiro, in Brazil


For information, the other scripting language I looked at was AngelScript. My selection of Lua was that Lua had been compiled using the Borland/Embarcadero C++ compilers. After some testing, I found that I could link the Lua code with the RTC code that I've written. I probably could have gotten AngelScript to link if I spent more time on it.


What is Lua?

From the Lua home page:

Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description.

Lua combines simple procedural syntax with powerful data description constructs based on associative arrays and extensible semantics. Lua is dynamically typed, runs by interpreting bytecode with a register-based virtual machine, and has automatic memory management with incremental garbage collection, making it ideal for configuration, scripting, and rapid prototyping.


Whew! A lot of multisyllabic words there. Some other links:

Programming in Lua (First Edition)
Programming in Lua (Fourth Editon on Amazon)
Lua Programming
Lua Documentation
Tutorial

Lua can do very complex things. We only need a small subset of what it can do. I'll describe some of the language below when I show coding examples.

1978 May - Model Railroader

Connecting with RTC

The RTC program is written in Borland/Embarcadero C++. It was easy to link the Lua library into RTC because of all the work done by others and released on the Internet for free use.

From chapter 27 of Programming in Lua by Roberto Ierusalimschy :

Lua is an embedded language. This means that Lua is not a stand-alone application, but a library that we can link with other applications to incorporate Lua facilities into them.

I created a new window on RTC titled "Program Control". The button to open the window is [Prog Ctrl]. On that window, you can select the script file, run it, and monitor its progress. You can run up to 5 independent scripts at the same time by using the [Next] and [Prev] buttons on the Program Control screen or by using the right click popup menu on the [Prog Ctrl] button.

Videos are in this YouTube Playlist.

Background Information

The script language is similar to C or Pascal. You can use the scripting language with or without using my RFID train detectors. As a standard, you should name all of your script files using the ".lua" extension.


The Program Control window

Here are some screen captures of the Program Control window. Before we get into writing scripts, I'll describe the buttons and fields on this window.

To access the Program Control window, press the [Prog Ctrl] button on the main window to access PC1. You can also right click on the button to access the popup menu. On the popup menu, you can select any of the 10 available PC windows.
The top of the window allows you to select the script file to use. Press the [Browse] button to select a file. You can also use the dropdown button to reselect a previous file. The program expects files to have the ".lua" extension.

The next set of buttons are used to select the function. Press [Run Script] to compile and run a script. Press [Compile Script] to just compile a script. Press [Interpreter] to open an Lua command line interpreter.

Next are the function buttons and the [STOP] button. You have 30 function buttons which you can program to perform operations. Not shown in this older screen capture are three tabs each of which selects 10 function buttons. The [STOP] button stops the script.

On the right are counters. There are 7 counters. The top one always shows the run time in seconds. The next three are used by the RFID readers if they are enabled on the Setup window. There are three more not shown just below these. These 3 and the 3 RFID counters can by used by your script to display information. The [Reset] button, clears the 6 counters to zero. A little further down on this page is a screen capture which shows the 7 counters.

The Messages box displays information from the script using print() command. [Copy All] copies all of the messages to the Windows clipboard. [Save] writes the messages to a file. [Clear] clears the message box.
The Detected Tags window can be displayed if the RFID system is enabled. It shows all previously detected and newly detected tags. To view the Detected Tags window, press the [Show Tags] button on the Program Control window.

The tab "Engine Data Tags" shows tags that have been programmed with engine/car information. The tab "UID Only Tags" shows tags that have not been programmed and only contain the tag UID.
If you right click on the Script File input box, you will get this popup menu. Select [Edit this Script] to pass the script file to an editor. The program tries to find these four editors in this order - Notepad++, metapad, notepad, and wordpad.

I recommend Notepad++ for editing Lua files. Notepad and wordpad come with all versions of Windows.

You can remove a script from the dropdown list or completely clear the list.
You can't disconnect from the TIU when a script is running. This message reminds you to press the [STOP] button before you try to disconnect.
This is what the window looks like if you select the "Counter Label Test.lua" script and press the [Compile Script] button. That test script defines 10 helper function buttons and all 6 available counters. I'll describe how to use these below.

The message in the Messages window lets you know that the script compiled successfully.

Notice here that the "RFID Enabled" LED is visible and green to indicate that the RFID train detection system is enabled.

[Emergency Stop] sends instructions to all 5 possible TIU's to cut off all power to the track. [Prev] and [Next] show the previous and next Program Control windows. There can up to 10 of them. [Hide] minimizes the window but does not otherwise affect any running script.
This shows the window after you press the [Run Script] button. Messages appear from the script and the counters begin to increment under control of the script.

When you press [Run Script], the script is compiled first so you don't have press [Compile Script].

The Run Time counter turns green to indicate that the script is running. The [STOP] button is enabled.
The user has pressed the [STOP] button and the script has stopped. Shown on the left for the first time are 2 of the 4 available LED's. These can be programmed by the script to display in red, green or yellow along with a label. I'll describe how to use these below.
This shows the window after the [Interpreter] button is pushed. A command input line opens at the bottom of the Messages window. You can type lua expressions and statements and they will be executed. Look at the Interpreter section below for details.

You can use the command input line dropdown list to select previous commands. You can also use the up arrow and down arrow keys.
If you use the InputBox or InputQuery functions, a box like this will open where the user can type a response. The window title, prompt, and default value can be programmed.
Here is what the Program Control window will look like if you are not using the RFID train detection system.



Scripts


Script Structure

The script files are plain text. To edit them, you can use notepad, wordpad, metapad or Notepad++. The last editor does syntax highlighting and knows the syntax of Lua. That is the one that I use.

This script file uses functions setup(), tag(), loop(), tick() and cleanup():


setup(Engine, TIU)  --  is called once at the start, it is passed the engine number (1-99) and the TIU number (1-5) from the main RTC window. This function MUST be present in your script. Return true on success, false on failure or to end the script. If this function returns false, loop() and tag() will not be run. If this function returns falsecleanup() will be run.

 loop(Engine, TIU)  --  This function is NOT called during setup() or cleanup(). This function is called over and over as long it returns true. Return false to break out of the loop (you must still press [STOP] to run cleanup() and end the script). If the function does not appear in your script, RTC does not attempt to call it. loop() is called in both non-RFID and RFID enabled scripts.

loop() will use up 100% of the CPU as it is called over and over. If this function returns true, you should always call Sleep() at least once to give other parts of RTC time to run.

 tag(Detector, Reader, EngineNo, TagLocation, CarModel, CarNumber, TagPacket)  --  is called each time a tag is detected by the RFID tag readers. Requires the RFID Train Detection system to be enabled on the setup window. On the Setup window, the check box for [X ] Enable RFID must be checked. Passes in the detector number, the reader number, three decoded fields from the tag packet received and the complete tag packet itself from which you can decode the other fields.
  • Detector - the detector number as coded into the Arduino sketch. Starts at 1. The RFID system can support up to 127 detectors.
  • Reader - the reader number, starts at 0. Each detector can support up to 6 readers.
  • EngineNo - the engine number as coded into the tag. Zero if the tag is not on an engine.
  • TagLocation - the location of the tag as coded into the tag. A number. Look at the "defines.lua" file for values.
  • CarModel - the car model or engine model as coded into the tag. A number. Look at the "defines.lua" file for values.
  • Railroad - the railroad name as coded into the tag. A Number, Look at the "defines.lua" file for values.
  • CarNumber - the 6 character car or engine number as coded into the tag. A string which can contain number or letters. Leading and trailing spaces have been removed.
  • TagPacket - the entire packet of information returned by the detector. 4 bytes of UID followed by 16 bytes of Block 4 data coded into the tag. Look at the RTC_NFC.h file for values.
Return true to continue accepting tag detections, false to quit accepting tag detections (you must still press [STOP] to run cleanup() and end the script).

The helper script "functions.lua" contains functions to help you decode the values passed into the tag() function.

Note that the tag() function is not passed a TIU number since a tag is not associated with a TIU. if you need that information, save the value passed to the setup() function in a global variable like MyTIU.

 tick(Engine, TIU)  --  is called approximately once per second. It is passed the engine number (1-99) and the TIU number (1-5) from the main RTC window This function is NOT called during setup() or cleanup(). Return true on success, false on failure at which time the tick() function will no longer be called but the rest of the script will continue to run.

Note: RTC does not guarantee this routine is called exactly once per second. If RTC script processing is extensive, a tick may be skipped. Always use RunTime() to get the exact time.

If the function does not appear in your script, RTC does not attempt to call it.

Do not call the Sleep() function from this function.

 cleanup(Engine, TIU)  --  is called once when the user presses the [STOP] button on the Program Control window. Return true on success, false on failure. If the function does not appear in your script, RTC does not attempt to call it.
event(FromPCNo, P1, P2, P3)  --  is called by any PC window using the SetEvent() function. It is used for commnications between the PC windows. Return true on success, false on failure. "FromPCNo" is the Program Control window # of the script which executed the SetEvent() function. P1, P2 & P3 are integers which are being sent along with the event. Look at the SetEvent() description below for details of how this function is used. If the function does not appear in your script, RTC does not attempt to call it.

If you are running a script without using the RFID detection system, just put all of your code in the loop() function.

Scripts do not stop on their own. Even if
loop() or tag() returns false, you need to press [STOP] or call the function Stop() to terminate the script so you can compile or run another script.

The minimal script (not using RFID) which does nothing but shows the structure of a script file:

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

-- Semicolons are not required in Lua code
-- Do Nothing Test
title = "Do Nothing Test"
require("defines");
require("functions");

-- Counter Labels
Counter01Label = "Do Nothing";

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
-- the setup() function is called once at the beginning of the run
-- Do nothing
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
-- The loop() function is repeatedly called if present
-- Always call Sleep() at least once in this function
Sleep(10);
-- Do nothing
return true; -- false=do not loop, true=continue to loop
end
--[[---------------------------------------------------------------------------------------]]
function tag(Detector, Reader, EngineNo, TagLocation, CarModel, Railroad, CarNumber, TagPacket)
-- the tag() function is called if the RFID system is enabled and a tag is detected
-- Do nothing
return true; -- true=continue to process tags, false=stop processing tags
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- the cleanup() function is called once when the [STOP] button is pressed
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function tick(Engine, TIU)
-- the tick() function is called once per second
-- do not call Sleep() from this function
-- Do nothing
return true; -- false=stop calling tick(), true=continue to tick
end
--[[---------------------------------------------------------------------------------------]]
function DL(Engine, TIU)
-- the DL() function is called when the Function 1 button is pressed
-- the line just below will change the label on the Function 1 button
-- Do nothing
return true;
end
Function01Name, Function01Label = DL, "Do Nothing";
--[[---------------------------------------------------------------------------------------]]


This script shows the use of a configuration variable, "title. If you assign a string to this variable, it is used to add a meaningful description to the Program Control window. In this example, the Program Control window will display Do Nothing Test. You can also use the function "Title(text, EngineNo)" to change the title during a run and enter an engine number into the EngineNo spinner.

Here is a 2nd example of a script that uses the RFID tag detectors and also does almost nothing:


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

-- Semicolons are not required in Lua code
-- Do Nothing Test
title = "Do Nothing Test"
require("defines");
require("functions");

-- Counter Labels
Counter01Label = "Do Nothing";

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
-- the setup() function is called once at the beginning of the run
print("setup(): Engine Number " .. Engine .. " TIU Number " .. TIU .. " Debug " .. Debug());
-- Do nothing
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
-- The loop() function is repeatedly called
-- Always call Sleep() at least once in this function if it returns true
print(string.format("loop %.2f", RunTime()));
Sleep(10);
-- Do nothing
return true; -- false=do not loop, true=continue to loop
end
--[[---------------------------------------------------------------------------------------]]
function tag(Detector, Reader, EngineNo, TagLocation, CarModel, Railroad, CarNumber, TagPacket)
-- this function is called each time a tag is detected
-- this function always waits until loop() finishes
if (EngineNo > 0) then -- its an engine
print(string.format([[tag(%.2f) : Detector %d Reader %d EngineNo %d TagLocation %d EngineModel %d]],
RunTime(), Detector, Reader, EngineNo, TagLocation, CarModel));
else -- it's a car
print(string.format([[tag(%.2f) : Detector %d Reader %d CarModel %d]], RunTime(),
Detector, Reader, CarModel));
end
-- Do nothing
return true;
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- the cleanup() function is called once when the [STOP] button is pressed
print(string.format("cleanup %.2f", RunTime()));
-- Do nothing
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function tick(Engine, TIU)
-- the tick() function is called once per second
-- do not call Sleep() from this function
print(string.format("tick --> %.2f", RunTime()));
-- Do nothing;
return true; -- false=stop calling tick(), true=continue to tick
end
--[[---------------------------------------------------------------------------------------]]
function DL(Engine, TIU)
-- the DL() function is called when the Function 1 button is pressed
print("Do Nothing " .. PCNo() .. " Debug " .. Debug());
-- Do nothing
return true;
end
Function01Name, Function01Label = DL, 'Print("Do Nothing")';
--[[---------------------------------------------------------------------------------------]]




To use the tick()  function, just define it in your script. It is called approximately once per second.

Here is a example listing of a tick function

--[[---------------------------------------------------------------------------------------]]
function tick(Engine, TIU)
-- Do not call Sleep() from this function
print("tick " .. RunTime());
-- do something
return true;
end
--[[---------------------------------------------------------------------------------------]]





Functions that do the hard work

Here are the functions available to the script. These functions are included automatically in your script if you use  require("defines") .

The parameters to each function call can be:


Functions that interact with the Program Control window

print(...);   -- displays the arguments in the
    -- Program Control "Messages" Window. Displays in Black.
    -- To display a formated string, use
        print(string.format(fmt, vars, ..))
    -- Multiple arguments, which can be strings, booleans, integers or numbers, are
    -- concatenated for display:
        print("TIU ", TIUNo, " Engine ", EngineNo);
        print("TIU " .. TIUNo .. " Engine " .. EngineNo);
    -- these two print functions calls produce the same result

printc(color, ...);    -- same as print(...) but displays in color
    -- Colors are defined in "defines.lua" and are:
    -- clRed
    -- clGreen
    -- clYellow
    -- clBlue
    -- clFuchsia
    -- clBlack (default if the color is not defined)

BumpCounter(ID);   -- Increments one of the counters on the Program Control window by 1
DecCounter(ID);    -- Decrements one of the counters by 1
GetCounter(ID);    -- Returns the value in one of the counters
ClearCounter(ID);  -- Sets one of the counters to zero.
SetCounter(ID, value);  -- sets one of the counters to the value given
    -- constant ID as follows:
    -- COUNTER01 - User defined counter 1
    -- COUNTER02 - User defined counter 2
    -- COUNTER03 - User defined counter 3
    -- COUNTER04 - User defined counter 4
    -- COUNTER05 - User defined counter 5
    -- COUNTER06 - User defined counter 6
    -- ENGINENO - the value in the Engine Number spinner on the Program Control window.
    --
    -- The next counter is used by the program and probably should not be changed by your script
    -- although your script can "GetCounter" it:
    -- RUNTIME - the run time of your script in tenths of seconds
    -- The next three counters appear on the Tags Detected window:
    -- TAGCNT - the number of tags detected by the tag detection readers during this run
    -- UNIQTAG - the number of unique tags ever detected by the tag detection readers
    -- TAGSUB - the number of UID only tags detected that were replaced by tag data from previous tags
    --
    -- Use this: require("defines") to define these constants.

SetLED(LED#, State, Caption);    -- There are four LED on the left edge of the Program Control
    -- window (1 thru 4), "State" is "clRed" to set the LED to red, "clGreen" to set the LED to
    -- green or "clYellow" to set the LED to yellow. Use "clClear" to make the LED disappear.
    -- Caption (in quotes) is optional and if given,
    -- sets the text under the LED to that value. There is only
    -- room for 6-8 characters under the LED.

Title(textvalue, EngNo);    -- sets the title bar text on the Program Control window and optionally,
    -- the engine number in the spinner on the Program Control window. If the engine number
    -- is not given, the value in the spinner is not changed. If the textvalue is an empty string,
    -- the title on the Program Control window is not changed.
    -- examples
        Title(title .. " - " .. GetName(EngineNo));
        Title(title .. " - " .. GetName(EngineNo), EngineNo);
    -- You can read back the Engine number from the spinner with:
        var = GetCounter(ENGINENO);

Functions that control engines

These funtions return true on success and false on failure unless noted.
If "when" is greater than 0, a true return only means that the command was successfully queued for future execution.

Setting(iSet, when, EngineNo/LashUpNo, TIUNo);
    --
sends the "ab" command with value iSet

Schedule(Command, when, Command, EngineNo/LashUpNo
, TIUNo);
    --
sends any Command passed to it. For example "u2", "w4", "s010", etc.

Whistle(bState, when, EngineNo/LashUpNo
, TIUNo);

Bell(bState, when, EngineNo/LashUpNo
, TIUNo);

Markers(bState, when, EngineNo/LashUpNo
, TIUNo);

Beacon(bState, when, EngineNo/LashUpNo
, TIUNo);

CabChat(bState, when, EngineNo/LashUpNo
, TIUNo);

Smoke(bState, when, EngineNo/LashUpNo
, TIUNo);

ChuffRate(iRate, when, EngineNo/LashUpNo
, TIUNo);

Rate(iAcc, iDec, when, EngineNo/LashUpNo
, TIUNo); -- sets the Acceleration and Decelleration
    -- rates of the engine. Rates can be from 1 to 25 Smph/sec. Value of 0 for either means no change.

GetRate(
EngineNo/LashUpNo, TIUNo);   -- returns the Acceleration and Decelleration Rates
    -- of the engine.
    -- usage:
        Acc, Dec = GetRate(Eng,TIU);


OpenCoupler(bCoupler, when, EngineNo/LashUpNo
, TIUNo);

SetDirection(bDIR, when, EngineNo/LashUpNo
, TIUNo);
    -- bDIR is FORWARD or REVERSE

GetDirection(
Engine, TIU);    -- return the direction as an boolean,
    -- true = 
FORWARD, false = REVERSE
    -- returns nil on error

Throttle(iMPH, when, EngineNo/LashUpNo
, TIUNo);
    -- sends only a speed command

SetSpeed(iMPH, when, 
EngineNo/LashUpNo, TIUNo);
    -- changes the speed with all of the appropriate
    -- bells and whistles (like SFS and SRS)

GetSetSpeed(
Engine, TIU);    -- return the set speed as an integer in Smph
    -- engine may be accelerating or decelerating to reach this speed
    -- returns nil on error

GetSpeed(Engine, TIU)
;    -- return the instantaneous speed of
    -- the engine as an integer in Smph
    -- returns nil on error

PlaySound(iSoundNo, when, EngineNo/LashUpNo
, TIUNo);
    -- Plays the sound given by iSoundNo. The sound number corresponds to the clip
    -- number as shown by my ADPCM program.

Sound
(bState, when, EngineNo/LashUpNo
, TIUNo);
    -- Turn the sound off or on. Sets Master Volume to 0 (OFF) or 100 (ON)

ProtoWhistle(bState, when, EngineNo/LashUpNo
, TIUNo);

SetQuill(iTone, when, 
EngineNo/LashUpNo, TIUNo);
    -- sets the quilling whistle tone, iTone has values 0-3
    -- Turn on Proto Whistle first, engine must support this feature

SmokeWhistle
(bState, when, EngineNo/LashUpNo, TIUNo);
    --
engine must support this feature

SwingingBell(bState, when, EngineNo/LashUpNo
, TIUNo);
    --
engine must support this feature

SetVolume(Chan, Level, when, EngineNo/LashUpNo
, TIUNo);
    -- set the Level(0-100) of the channel given by Chan.

GetVolume(Chan, EngineNo, TIUNo)
;
    -- returns the volume level (0-100) of the channel given by Chan as an integer.
    -- returns nil on error.
    -- To mute the sounds from an engine, first call GetVolume with Chan of MASTER_VOLUME. Save
    -- the volume, then use SetVolume with Chan of Master_VOLUME and Level of zero. To unmute,
    -- use SetVolume with Chan of MASTER_VOLUME and for Level, use the saved volume.


NOTE: Any command without a "when" parameter may take 1-2 seconds to complete. If you need to "Get" the result of a "Set" for confirmation, you should Sleep() for a few seconds before the "Get" command.

Functions that control Accessories and Switches

These funtions return true on success and false on failure unless noted

Accessory(bState, iChan, AIUNo, TIUNo, when);
    -- Set actual accessory state
    -- bState is ON or OFF
    -- iChan is 1 to 10
    -- AIUNo is 1 to 5
    -- TIUNo is 1 to 5


Switch(bState, iChan, AIUNo, TIUNo, when);
    -- Set acutal switch state
    -- bState as described above, ie : NORMAL or REVERSE
    -- iChan is 1 to 10
    -- AIUNo is 1 to 5
    -- TIUNo is 1 to 5


VirtualAccessory(bState, iChan, AIUNo, TIUNo)
;
    -- Set virtual accessory state - does not send any commands to the TIU.
    -- Useful to pass information between PC threads. Use GetAccStatus() to read
    -- the position set by this function. You can set Virtual Accessories even in TIU that don't exist.
    -- bState is ON or OFF
    -- iChan is 1 to 10
    -- AIUNo is 1 to 5
    -- TIUNo is 1 to 5

GetSwitchPositionNames();
   -- returns two values, both strings containing the words used
    -- to indicate the position of switches.
    -- For example "Straight"/"Diverge", "Closed"/
"Open", "Normal"/"Reverse".
    -- The default values used in the program are "Normal"/"Reverse".
    -- usage:
        ClosedName, OpenName = GetSwitchPositionNames();

GetSwitchStatus(iChan, AIUNo, TIUNo);   
-- returns the state of the switch on the
    -- iChan is 1 to 10
    -- AIUNo is 1 to 5
    -- TIUNo is 1 to 5

    -- returns true if the switch is in the Normal position
    -- return false if the swtich is in the Reverse position

GetAccStatus(iChan, AIUNo, TIUNo); 
 -- returns the state of the accessory on the
    -- iChan is 1 to 10
    -- AIUNo is 1 to 5
    -- TIUNo is 1 to 5

    -- return true if the accessory is ON
    -- returns false if the accessory is OFF

GetSwitchName(iChan, AIUNo, TIUNo);   
-- returns the name of the switch on the
    -- given Channel, AIU and TIU as a string.
    -- iChan is 1 to 10
    -- AIUNo is 1 to 5
    -- TIUNo is 1 to 5

    -- returns "*" if the AIU or Channel is not defined.

GetAccName(iChan, AIUNo, TIUNo); 
 -- returns the name of the accessory on the
    -- given Channel, AIU and TIU as a string.
    -- iChan is 1 to 10
    -- AIUNo is 1 to 5
    -- TIUNo is 1 to 5

    -- returns "*" if the AIU or Channel is not defined.

Functions that present layout or engine status

RunTime();     -- return value is the Run Time in seconds as a number since the script was
    -- started. As a "number", it is floating point and should never be compared for
    -- equality (except to 0.0). If you need to do that, convert the number to an integer
    -- with:
math.floor(RunTime() + 0.5). When used with string.format() remember to
    -- use a floating point specifier like "%.2f".

MicroSeconds
();    -- returns the value of the Windows Performance Counter in microseconds
    -- as an integer.
    -- This value does not have an absolute meaning. Always use the difference between
    -- two calls as an elapsed time.

TIUVersion
();  -- return value is the version number of the last connected TIU as a number

    -- typical values 5.3, 6.01, etc

CommStatus
();  -- return 3 values giving the status of the two serial ports
    -- used by the RTC program. Returns true or false.
    -- usage:
        TIUSerialConnected, TIUConnected, RFIDSerialConnected = CommStatus();


RAM(address, Engine, 
TIUNo);    -- returns the byte at the RAM address given as address.
    -- The first 1024 bytes (address 0x000 to 0x3FF) can be read.
    -- returns nil on error
    -- You can download my documentation for this function here - Engine RAM Mapping.pdf.

DTO(Engine, 
TIUNo);    -- returns the Engine's trip odometer mileage (DTO) as a number
    -- returns nil on error

DOD(Engine, 
TIUNo);    -- returns the Engine's odometer mileage (DOD) as a number
    -- returns nil on error

DCH(Engine, 
TIUNo);    -- returns the Engine's chronometer in seconds (DCH) as a number
    -- returns nil on error

PCNo()
;    -- returns the number of the Program Control window running this script
    -- A number from 1 to 5 as an integer


Info(Engine, TIUNo);
   -- under development

Functions that make script writing easier

Sleep(seconds);   -- causes the script to pause for the given number of seconds (rounded
    -- to the nearest 0.1 of a second.
    -- Any commands which are queued up for execution will run as
    -- scheduled while the script is sleeping
    -- pressing the [STOP] button will terminate a sleep in progress but will not stop
    -- queued up commands
    -- Sleep() is the only function that moves time forward.

Beep
(sound);    -- play alert or notification sounds (you must have each sound enabled
    -- on your computer or the sounds won't play). Sounds are defined in "defines.lua" and are:
    -- Beep();  play default beep sound
    -- Beep(DEFAULT_BEEP); play default beep sound
    -- Beep(CRITICAL_STOP); play critical stop sound
    -- Beep(QUESTION); play question sound
    -- Beep(EXCLAMATION); play exclamation sound
    -- Beep(ASTERISK); play asterisk sound
    -- returns true on success, false on failure

ClearDispatchList()
;    -- Clears any pending commands in the dispatch list. Useful if
    -- you are exiting on an error condition.

CountDispatchList()
;    -- returns the number of entries in the dispatch list. Useful if you want
    -- to wait for the dispatch list to empty on an error condition. Returns -1 if the
    -- dispatch list is not running

InputBox(Caption, Prompt, Default)
;    -- Displays an input box using Caption and Prompt. Input
    -- area is set to the value in Default. The user can enter a new value and press [OK] or
    -- press [Cancel] to return the default value. Returns the result as a string.
    -- Returns nil if the parameters are invalid.
    -- Use the InputBox function when there is a default value that should be used when the user
    -- chooses the [Cancel] button (or presses [Esc]) to exit the dialog. If the script needs to
    -- know whether the user chooses [OK] or [Cancel], use the InputQuery function instead.

InputQuery(Caption, Prompt, Default)
;    -- Displays an query box using Caption and Prompt. Input
    -- area is set to the value in Default. The user can enter a new value and press [OK] which
    -- returns the new value as a string. Pressing [Cancel] or [Esc] causes the function to
    -- return false. Returns nil if the parameters are invalid.
    -- If the default value should be used when the user cancels out of the dialog, use InputBox().

Stop
(message, beep);    -- Stop the script and run the function cleanup().
    -- Use only as the parameter for a return statement as:
        return Stop(message);
    -- example:
        if (test) then return Stop("End of Test"); end;
    -- Always returns false. Displays optional message if given. Message is displayed in Red.
    -- Second parameter is also optional, set to true to get a "EXCLAMATION" beep when stopping.
    -- This is for an immediate stop of the script. Usually you must press the [STOP] button
    -- to run cleanup() and end the script.

EmergencyStop(message, beep);
    -- see next section

Debug();   -- returns the value of the global RTC debug level as set on the debug window. If you
    -- don't know how to access the debug window, send me an email. An integer between 0 (no debug
    -- messages) to 9 (full debug messages).

Pretty(Var);
   -- returns a string which is a reasonable representation of the contents of the
    -- variable "Var". "Var" can be any Lua type
    -- This function needs defines.lua to be included with
        require("defines");

PrettyPrint(Var);
   -- Calls Pretty(Var) and displays the resultant string in
    -- Messages window. Same as print(Pretty(Var).
    -- This function needs defines.lua to be included with
        require("defines");

PopupMessage(Message, Caption, SecondMessageLine, Delay);    -- Displays a Message in a popup window using
    -- an optional Caption, an optional SecondMessageLine and an optional Delay time in seconds.
    -- If not given, Delay is set to 2 seconds. This is how long the popup is on the display.
    -- If not given, SecondMessageLine is blank.
    -- If not given, Caption is "Information".
    -- At least one parameter, the Message itself, is required.

Functions that start up or shutdown engines

NOTE: Commands which startup or shutdown engines may take up to 20 seconds for the startup or shutdown sound sequence to complete

EmergencyStop(message, beep);
    -- Displays optional message if present. Then sends an emergency stop command to all TIU
    -- Always returns false. Causes all TIU to remove power from the track.
    --
Displays optional message if given. Displays message in red.
    -- Second parameter is also optional, set to true to get a "CRITICAL_STOP" beep when stopping.

    -- Usually after this function, you must exit RTC, power down the TIU and then start everything again.
    -- Use only as the parameter for a return statement as:
        return 
EmergencyStop();
    -- example:
      
if (test) then
          return EmergencyStop("Emergency Stop - Engine " .. Engine .. " did not stop on command");   
          -- Send Emergency Stop to all TIU

          end

StartUp(when, EngineNo, TIUNo);
    -- simple starting up of an engine. Use 
EngineStartUp() to get an Operations Window

ShutDown
(when, EngineNo, TIUNo);

    -- simple shutting down of an engine. Use EngineShutDown() to close an Operations Window

StartUp(when, LashUpNo, TIUNo, LashUpEngineList);
    -- simple starting up of a Lashup.
Use LashUpStartUp() to get an Operations Window

ShutDown(when, LashUpNo, TIUNo)
;

    -- simple shutting down of a Lashup. Use LashUpShutDown() to close an Operations Window

These four functions open/close the Operations Window for the given engine or lashup on the given TIU.
The effect of having the Operation window open is that if you call any function (like Bell(), Whistle(), etc) that has buttons or switches on the window, those buttons or switches will move just as if someone manually activated the buttonor switch. You can also manually control the engines (but that might mess up the script)

EngineStartUp(EngineNo, TIUNo);


EngineShutDown(EngineNo, TIUNo)
;
    -- EngineNo is between 1-99

LashUpStartUp(LashUpName, LashUpNo, TIUNo, LashUpEngineList);


LashUpShutDown(LashUpNo, TIUNo)
;
    -- LashUpName is a 16 character name for the Lashup
    -- LashUpNo is between 101-120
    -- LashUpEngineList : a table containing numeric values for Lead EngineNo,
    -- Middle EngineNos (if any), Trailing EngineNo. Add 128 to number if the
    -- engine is reverse running. usage:  LashUpEngineList = {3,32,21,128+4};
    -- where engine 3 is the lead engine, engines 32 and 21 are middle engines and
    -- engine 4 (running in reverse) is the trailing engine.

These three functions open/close the Operations Window for each active engine on the given TIU.
Delay is the time between each engine's action (for dramatic effect) and can be omitted.
The effect of having the Operation window open is that if you call any function (like Bell(), Whistle(), etc) that has buttons or switches on the window, those button or switches will move just as if someone manually activated them.

StartUpActiveEngines(TIUNo, Delay);

ShutDownActiveEngines(TIUNo, Delay)
;


ShutDownAllEngines(TIUNo, Delay)
;

Functions that return information about Engines and Lashups

IsEngineRunning(EngineNo, TIUNo);   -- returns true if
    -- the engine was started with StartUpActiveEngines()or EngineStartUp().
    -- Engines started with
    -- StartUp() are not seen by this function

IsLashUpRunning
(LashUpNo, TIUNo);   -- returns true if
    -- the lashup was started with LashUpStartUp()


Counts
(TIUNo);
    -- returns two values, the number of active engines and
    -- the number of inactive engines on the given TIU
    -- usage:  
        ActiveCount, InactiveCount = Counts(TIUNo);


Exists(EngineNo)
;   -- returns true if the engine exists, otherwise returns false  (engines 1-99)
    -- returns nil on error

IsActive(EngineNo)
;  -- returns true if the engine is active, otherwise returns false (engines 1-99)
    -- returns nil on error

GetName(EngineNo);
   -- Returns two values, the engine name as a string and the PS
    -- level as an integer for EngineNo (engines 1-99)
    -- example:
        Name, PS = GetName(Engine);


GetEngineName(CarModel)
; -- In the tag() funciton, if the EngineNo read from the tag is greater than 0,
    -- use the CarModel value with this function to get a string containing the model name of the
    -- engine, eg "SW1500", "GP7", etc.

GetCarName(CarModel)
; -- In the tag() function, if the EngineNo read from the tag is 0, use the CarModel
    -- value with this function to get a string containing the model name of the car, eg "Boxcar",
    -- "Coach", etc.

GetRailroadName(Railroad)
; -- In the tag() function, use the Railroad value with this function
    -- to get a string containing the railroad name contained in the detected tag, eg "P&LE", etc.

GetEngineType(Engineype)
; -- In the tag() function, use the Engine Type value with this function
    -- to get a string containing the type of engine ("Steam", "Diesel", etc).

GetTagLocation(TagLocation)
; -- In the tag() function, use the Tag Location value with this function
    -- to get a string containing the tag location contained in the detected tag, eg "Front Truck", etc.

GetCouplerLocation(TagLocation);
 -- In the tag() function, use the Tag Location value with this function
    -- to get a string containing the the location of the tag on the car/engine, eg "Front and Rear Couplers", etc.



First Easy Example

The listing of the script is in the box below. I'm not going to describe everything that you see there but I'll try to point out the major points with comments in the code. 

require(filename) is called in the script. This call reads in the script in the named file (after appending .lua) and processes it as if it were included here. Scripts using the RFID tag readers requires two files : "defines" & "functions". They are shown below also. Note that the "functions" file is only needed if you are processing my RFID tag system. When you create these files, append the file type ".lua" to the names.

print(string) displays the string on the Program Control window's message box. Used to monitor the progress of the script and to show its status.

This example does not use the RFID tag readers. On the Setup window, the check box for [ ] Enable RFID must not be checked.

When you press the [Run Script] button on the Program Control window, the setup() function is processed followed by the loop() function. setup() is run once and loop() is run over and over as long as it return true. Return false to break the loop. When you press the [Stop] button, the loop() function is terminated and the cleanup() function is run.

The Sleep(Delay) causes the script to pause for the given number of seconds.

Here is an example script (not using RFID but uses Helper Functions - see the next section):

Here is a listing of the Startup_shutdown_engine.lua file
(this may not be as up to date as in the zip file linked to below):

-- Semicolons are not required in Lua code

require("defines");

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
-- this function is called once when the [Run Script] button is pushed
-- Engine : Engine number selected on the RTC Main window
-- TIU : TIU number selected on the RTC Main window
print("setup()");

MyEngineNo = Engine; -- Engine number selected on the RTC Main window
MyTIUNo = TIU; -- TIU number selected on the RTC Main window
MyDebug = Debug(); -- debug level as selected on the Debug window, values 0 (off) to 9 (maximum)
if (MyDebug > 0) then
print(string.format("setup(): Debug level %d", MyDebug));
print("setup(): Engine Number " .. MyEngineNo .. " TIU Number " .. TIU);
end
print("TIU : " .. TIU);
assert((TIU >= 1 and TIU <= 5), "TIU Number out of range");
-- initialize the layout as needed
AC, IAC = Counts(MyTIUNo); -- Counts() returns two values
print("TIU" .. MyTIUNo .. ": " .. AC .. " Active Engines - " .. IAC .. " Inactive Engines");
-- if you used defines.lua, you can use print() in place of print()
-- switches and accessories
Switch(STRAIGHT, 1, 2, 3, MyEngineNo, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 3, 5, MyEngineNo, MyTIUNo); -- College West
Sleep(10);
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
-- this function is called once after setup() completes and returns true
-- just after the setup() function completes
-- Engine : Engine number selected on the RTC Main window
-- TIU : TIU number selected on the RTC Main window
MyEngineNo = Engine; -- Engine number selected on the RTC Main window
MyTIUNo = TIU; -- TIU number selected on the RTC Main window
MyDebug = Debug(); -- debug level as selected on the Debug window, values 0 (off) to 9 (maximum)

print(string.format("loop(): Engine %d MyTIUNo %d", MyEngineNo, MyTIUNo));
if (MyDebug > 0) then
print("Debug level " .. Debug());
end
StartUp(0, MyEngineNo, MyTIUNo);
Sleep(10);
Whistle(ON, 20, MyEngineNo, MyTIUNo);
Whistle(OFF, 22, MyEngineNo, MyTIUNo);
print(string.format("loop() Sleep(10) Started at %d", RunTime()));
Sleep(10);
print(string.format("loop() Sleep(10) Complete at %d", RunTime()));

for Count = 1, 2 do
Bell(ON, 15, MyEngineNo, MyTIUNo);
Bell(OFF, 20, MyEngineNo, MyTIUNo);
Sleep(10);
end

return false; -- false=do not loop, true=continue to loop
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button
--print("cleanup()");
ShutDown(0, MyEngineNo, MyTIUNo);
print(string.format("cleanup() Complete at %d", RunTime()));
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function BlowWhistle(Eng, TIU)
print("BlowWhistle()");
print(string.format("BlowWhistle(): Eng %d TIU %d Debug %d", Eng, TIU, Debug()));
print("BlowWhistle(): Eng " .. Eng .. " TIU " .. TIU .. " Debug " .. Debug());
Whistle(ON, 0, Eng, TIU);
Whistle(OFF, 1, Eng, TIU);
return true;
end
Function01Name, Function01Label = BlowWhistle, "Blow Whistle";
--[[---------------------------------------------------------------------------------------]]
function SimplePrint(Eng, TIU)
print("SimplePrint()");
print(string.format("SimplePrint(): Eng %d TIU %d Debug %d", Eng, TIU, Debug()));
return true;
end
Function02Name, Function02Label = SimplePrint, "Simple Print";
-- you can create 10 functions which get hooked to the 10 button on the Program Control window
--[[---------------------------------------------------------------------------------------]]
function NOP(Eng, TIU)
print("NOP()");
print(string.format("NOP() : Eng %d TIU %d Debug %d", Eng, TIU, Debug()));
return true;
end
Function10Name, Function10Label = NOP, "NOP";
--[[---------------------------------------------------------------------------------------]]




Helper Functions

Up to 30 special interactive helper functions can be defined. They are named "Function01" through "Function30". All thirty functions can be accessed through the use of 10 buttons which appear on the Program Control window. You select the buttons in groups of 10 using the tabs above the buttons.  If you press one of these buttons while loop() or tag() is running, the function will be executed after loop() or tag() completes.

To use these functions, first define the function in your Lua script:
function NOP(Eng, TIU)
print("NOP() This script does (almost) nothing");
print(string.format("NOP() : Eng %d TIU %d Debug %d", Eng, TIU, Debug()));
return true;
end
Following the function, define the function name and button label:
Function10Name, Function10Label = NOP, "NOP";
This line tells the Program Control window to put the phrase "NOP" on the button and when the user presses the button, to call the function NOP(Eng, TIU).


Here is a example listing of Helper Functions

--[[---------------------------------------------------------------------------------------]]
function BlowWhistle(Eng, TIU)
print("BlowWhistle()");
print(string.format("BlowWhistle(): Eng %d TIU %d Debug %d", Eng, TIU, Debug()));
print("BlowWhistle(): Eng " .. Eng .. " TIU " .. TIU .. " Debug " .. Debug());
Whistle(ON, 0, Eng, TIU);
Whistle(OFF, 1, Eng, TIU);
return true;
end
Function01Name, Function01Label = BlowWhistle, "Blow Whistle";
--[[---------------------------------------------------------------------------------------]]
function SimplePrint(Eng, TIU)
print("SimplePrint()");
print(string.format("SimplePrint(): Eng %d TIU %d Debug %d", Eng, TIU, Debug()));
return true;
end
Function02Name, Function02Label = SimplePrint, "Simple Print";
-- you can create 30 functions which get hooked to the 30 button on the Program Control window
--[[---------------------------------------------------------------------------------------]]
function NOP(Eng, TIU)
print("NOP() This script does (almost) nothing");
print(string.format("NOP() : Eng %d TIU %d Debug %d", Eng, TIU, Debug()));
return true;
end
Function10Name, Function10Label = NOP, "NOP";
--[[---------------------------------------------------------------------------------------]]


If you use more than 10 buttons, you can setup 3 tabs to make up to 30 buttons available for functions. These statements should be placed in your script before the setup() function.

-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "Engine"
Tab02Label = "Destination"
Tab03Label = "Spare";


Counters

Six counters can be defined and will appear on the Program Control window. Your script can increment, decrement or set these counters to display information to the user. They are named "COUNTER01" through "COUNTER06".

To use the counters, at the beginning of your script, define the labels which should appear on the Program Control Window
-- Counter Labels
Counter01Label = "Engines Detected";
Counter02Label = "State Machine";

Then in your script, when you want to increment the counters, do this:
BumpCounter(ID);   -- Increments one of the counters on the Program Control window by 1
DecCounter(ID); -- Decrements one of the counters by 1
GetCounter(ID); -- Returns the value in one of the counters
ClearCounter(ID); -- Sets one of the counters to zero.
SetCounter(ID, value); -- sets one of the counters to the value given
Here is an example (fairly useless) script (requires defines.lua):

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

-- Semicolons are not required in Lua code
-- Counter and Function Label Test
title = "Counter and Function Label Test and Tick Test"
require("defines");

-- Counter Labels
Counter01Label = "Engines Detected";
Counter02Label = "State Machine";
Counter03Label = "Counter03";
Counter04Label = "Counter04";
Counter05Label = "Counter05";

ENGDET = COUNTER01;
SMSTATES = COUNTER02;
EnginesUsed = {"GP38-2", "GP7", "SW1500"};
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
for idx,Eng in ipairs(EnginesUsed) do
BumpCounter(ENGDET); -- Engines Detected counter should bump to 3
print(idx .. " Engine " .. Eng);
end
BumpCounter(COUNTER03);
TIUSerialPortConnected, TIUConnected, RFIDSerialPortConnected = CommStatus();
print("TIUSerialPortConnected : " .. tostring(TIUSerialPortConnected) .. " - TIUConnected : " .. tostring(TIUConnected) .. " - RFIDSerialPortConnected : " .. tostring(RFIDSerialPortConnected));
if (RFIDSerialPortConnected) then
print("Turn off RFID Enable and rerun");
return false;
end;

return true;
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
-- Always call Sleep() at least once in this function.
BumpCounter(SMSTATES); -- bump states counter
Sleep(10);
return true; -- false=do not loop, true=continue to loop
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
BumpCounter(SMSTATES); -- bump states counter
BumpCounter(COUNTER05);
print("Counter01 = " .. GetCounter(COUNTER01));
print("Counter02 = " .. GetCounter(COUNTER02));
print("Counter03 = " .. GetCounter(COUNTER03));
print("Counter04 = " .. GetCounter(COUNTER04));
print("Counter05 = " .. GetCounter(COUNTER05));
print("Counter06 = " .. GetCounter(COUNTER06));
--
return true;
end
--[[---------------------------------------------------------------------------------------]]
function tick(Engine, TIU)
print(string.format("tick %.2f", RunTime()));
BumpCounter(COUNTER04);
return true;
end
--[[---------------------------------------------------------------------------------------]]
Function01Name, Function01Label = loop, "Loop01";
Function02Name, Function02Label = loop, "Loop02";
Function03Name, Function03Label = loop, "Loop03";
Function04Name, Function04Label = loop, "Loop04";
Function05Name, Function05Label = cleanup, "Cleanup05";
Function06Name, Function06Label = setup, "Setup06";
Function07Name, Function07Label = setup, "Setup07";
Function08Name, Function08Label = setup, "Setup08";
Function09Name, Function09Label = setup, "Setup09";
Function10Name, Function10Label = tick, "tick10";
--[[---------------------------------------------------------------------------------------]]





July 1954
by Joe Salame
Model Railroader Magazine

LED's

There are four LED on the left edge of the Program Control window (1 thru 4), "State" is clRed to set the LED to red, clGreen to set the LED to green or clYellow to set the LED to yellow. Use clClear to make the LED disappear. Caption (in quotes) is optional and if given, sets the text under the LED to that value. There is only room for 6-8 characters under the LED.

Use the SetLED()  function as shown in this example script:


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

-- Semicolons are not required in Lua code
title = "LED Test"

SwitchCase = 0;

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

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
-- this function is called once when the [Run Script] button is pushed
print("setup()");
SetLED(1, clGreen, "Speed"); -- set status LED to green
SetLED(2, clClear, "%"); -- set status LED to clear
SetLED(3, clRed, "Error"); -- set status LED to red
SetLED(4, clYellow, "Warning"); -- set status LED to yellow
return true;
end
--[[---------------------------------------------------------------------------------------]]
function loop() -- parameters not used
-- Always call Sleep() at least once in this function since it returns true;
Sleep(1);
-- cycle the LED through different colors and states
if (SwitchCase == 0) then
SwitchCase = 1;
SetLED(1,clYellow);
SetLED(2,clGreen);
SetLED(3,clClear);
SetLED(4,clRed);
return true;
end
if (SwitchCase == 1) then
SwitchCase = 2;
SetLED(1,clRed);
SetLED(2,clYellow);
SetLED(3,clGreen);
SetLED(4,clClear);
return true;
end
if (SwitchCase == 2) then
SwitchCase = 3;
SetLED(1,clClear);
SetLED(2,clRed);
SetLED(3,clYellow);
SetLED(4,clGreen);
return true;
end
if (SwitchCase == 3) then
SwitchCase = 0;
SetLED(1,clGreen);
SetLED(2,clClear);
SetLED(3,clRed);
SetLED(4,clYellow);
return true;
end
return true;
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
--
SetLED(1, clClear);
SetLED(2, clClear);
SetLED(3, clClear);
SetLED(4, clClear);

return true;
end
--[[---------------------------------------------------------------------------------------]]







Interpreter

A Lua interpreter is available when running scripts and when you press the [Interpreter] button. This is an advanced feature that you might find useful as you become more adept at understanding Lua.

A command line input box opens under the Messages window. You can type Lua commands into that box and then press Enter. You can type in any command that will fit in the 511 character input line.

There are two types of input allowed, expressions and statements.

Expressions do not return a value. They perform an operation and discard the result.  In the Lua interpreter, expressions are always preceeded by an equal "=" sign. Examples of expressions are:

    =5
    =654/45343
    =232+445*45

Statements perform assignments and call functions. Examples of statements are:

    aa = 55
    BumpCounter(1)
    print(aa)

The interpreter is useful in helping debug your scripts. For example, if your script is not doing what you expect, you can print out its variables. If your setup() function assigned the parameter Engine to MyEngine,
you can type:

    print(MyEngine)

to have the interpreter display the Engine number in the Messages window. If the EngineNo is wrong, you can change it:

    MyEngine = 22

Real World Example

For my first real script, I took the "Loop Demo" that I showed in the last few videos on my RFID Train Detection web page and converted it to a Lua script named "Three Engine Demo". The listing of the script is in the box below. I'm not going to describe everything that you see there but I tried to point out the major points with comments in the code. This real world example requires the use of my RFID tag detectors. On the Setup window, the check box for [X ] Enable RFID must be checked. The function tag() is called each time a tag is detected.

Here is a schematic of the layout. The RFID tag readers are shown in RED. There are 3 Arduino detectors, two with a single RFID tag reader and one with two RFID tag readers.




Here is the code for the Three Engine Demo (using my RFID train detectors):

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

-- Semicolons are not required in Lua code
title = "3 Engine Demo"

-- This script is written to use 3 engines. The Engine List on the Main window
-- should have exactly three active engines and the rest should be inactive.

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

MyTIUNo = 99; -- Global TIU number
MyDebug = 0; -- Global Debug Value

DETECTED = true;
NOTDETECTED = false;

STOPDELAY = 2; -- delay from tag detection to engine speed 0 command

-- all tables in Lua are 1 based, that is, T[1] is the first item in the list
EngineDetectionOrder = {0}; -- clear the tables
EnginesUsedNum = {0};
EnginesUsedFlag = {0};
EngineSpeedSave = {0};
Got = {0};
-- Counter Labels
Counter01Label = "Engines Detected";
Counter02Label = "State Machine";
-- table to hold all of the tags detected in sequence
Tags = {};
-- state machine states
IDLE = 0;
DETECTENGINES = 1;
READYGO_3 = 2;
READYGO_3a = 3;
READYGO_3b = 4;
SM = IDLE;

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
-- this function is called once when the [Run Script] button is pushed
print("setup()");
-- Engine number selected on the Main window is not used
MyTIUNo = TIU; -- TIU number selected on the Main window
MyDebug = Debug(); -- debug level, values 0 (off) to 9 (maximum)
if (MyDebug > 0) then print(string.format("Debug level %d", MyDebug)); end
if (MyDebug > 0) then print("Debug level " .. tonumber(MyDebug)); end
-- initialize variables
TagCount = 0;
iEngineCount = 0;
iEngineDetectionCount = 0;
iReqEngines = 3; -- number of engines required for this program
UniqueTagCount = 0; -- number of unique tags found

-- make sure that RFID is connected
TIUSerialPortConnected, TIUConnected, RFIDSerialPortConnected = CommStatus();
print("TIUSerialPortConnected : " .. tostring(TIUSerialPortConnected) .. " - TIUConnected : " .. tostring(TIUConnected) .. " - RFIDSerialPortConnected : " .. tostring(RFIDSerialPortConnected));
if (not RFIDSerialPortConnected) then
print("RFID Port must be enabled for this script");
return false; -- false=setup failed
end

math.randomseed(os.time()); -- random seed
assert(true); -- debug only
-- initialize the layout

-- Start All Active Engines - 1-99 (DCS 2-100)
print("Starting All Active Engines");

for iEng = 1, 99 do -- check all possible engines on one TIU
if (Exists(iEng)) then
if (IsActive(iEng)) then
print("Engine " .. iEng .. " Starting");
EngineStartUp(iEng, MyTIUNo);
Sleep(2);
end
end

if (IsEngineRunning(iEng, MyTIUNo)) then -- IsConsistRunning() REQUIRES use of StartUpActiveEngines() or EngineStartUp()
print(string.format("Initalizing Engine %d", iEng));
-- save the numbers of all started engines
iEngineCount = iEngineCount + 1; -- Count the number of engines running
EnginesUsedNum[iEngineCount] = iEng; -- EnginesUsedXXXX[] is 1 based
EnginesUsedFlag[iEngineCount] = NOTDETECTED;-- flag as not yet detected in motion
-- Numberboards
Setting(CMD_NUMBERBOARDS_ON, 18, iEng, MyTIUNo); -- not a dedicated switch
-- Set smoke off
Smoke(OFF, 2, iEng, MyTIUNo);
-- Cab Chatter Off
CabChat(OFF, 3, iEng, MyTIUNo);
-- Set Marker lights on
Markers(ON, 15, iEng, MyTIUNo);
-- Set Acc/Dec rate to 1/1 Smph/sec
Rate(1, 1, 0, iEng, MyTIUNo);
end
end
print(string.format("Engines Started %d", iEngineCount));
-- Start each train moving forward - go slow to help insure tag detection
-- Since we don't know the position of each engine, we start each at exactly
-- the same speed at exactly the same time, once we have detected the engines
-- and located them on the layout, we can vary the movement times and running speeds.
for iDex = 1, iEngineCount do -- for each running engine, set the speed to 12 Smph
local iEng = EnginesUsedNum[iDex]; -- EnginesUsedXXXX[] is 1 based
print(string.format("SetSpeed for Engine : %d", iEng));
SetSpeed(12, 20, iEng, MyTIUNo);
end

-- set State Machine to first state
SM = DETECTENGINES;
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function tag(Detector, Reader, EngineNo, TagLocation, CarModel, Railroad, CarNumber, TagPacket)
-- this function is called each time a tag is detected
-- Line below shows a different way to quote a string using brackets
if (MyDebug > 4) then Beep(); end; -- sound a tone
if (MyDebug > 1) then
if (EngineNo > 0) then -- its an engine
print(string.format([[tag() : Detector %d Reader %d EngineNo %d TagLocation %d CarModel %d]],Detector, Reader, EngineNo, TagLocation, CarModel));
else -- it's a car
print(string.format([[tag() : Detector %d Reader %d CarModel %d]],Detector, Reader, CarModel));
end
end
-- The complete packet received is in TagPacket
-- (some of the most used fields have already been extracted into the first parameters to tag()
-- First 8 digits are the 4 byte Tag UID in hexidecimal
-- Final 32 digits are the 16 bytes of Block 4 Information read from the Tag in hexidecimal
if (MyDebug > 4) then print(string.format("Packet %s", TagPacket)); end
Tags[string.sub(TagPacket,1,8)] = string.sub(TagPacket,9);
TagCount = TagCount + 1; -- count the number of tags

--[[---------------------------------------------------------------------------------------]]
-- STATE MACHINE
--[[---------------------------------------------------------------------------------------]]

--[[---------------------------------------------------------------------------------------]]
if (SM == IDLE) then -- do nothing
return true;
end
--[[---------------------------------------------------------------------------------------]]
if (SM == DETECTENGINES) then
-- As engines run around the layout, go until all of them are detected by Detector 1 reader 0
-- Save the tag in the tag list
--
-- TBD - add test to be sure that we detected tags from all tag readers
--
-- single toot on engine rear truck detection from all detectors and readers
if (isEngine(EngineNo) and isTagRear(TagLocation)) then
PlaySound(41, 0, EngineNo, MyTIUNo); -- Short Toot
end

-- Schedule a random noise on engine rear truck detection on all detectors and all readers
if (isEngine(EngineNo) and isTagRear(TagLocation)) then
RandomAction(EngineNo, MyTIUNo);
end

if (isEngine(EngineNo) and isTagFront(TagLocation) and ((isDetector(Detector, 1) and isReader(Reader, 0)))) then
for iDex = 1, iEngineCount do
-- EnginesUsedXXX[] is 1 based
if (EnginesUsedNum[iDex] == EngineNo) then -- is the engine in the Engine List?
if (EnginesUsedFlag[iDex] == DETECTED) then -- has it already been detected (then all engines looped)
-- already detected so we have detected them all
print ("All Engines Detected");
-- speed up the engines to 20 Smph for the first loop
-- make the speed up from 12 to 20 more gradual
for jDex = 1, iEngineDetectionCount do
SetSpeed(15, 3, EngineDetectionOrder[jDex], MyTIUNo);
SetSpeed(18, 6, EngineDetectionOrder[jDex], MyTIUNo);
SetSpeed(20, 9, EngineDetectionOrder[jDex], MyTIUNo);
end
-- at this point, EngineDetectionOrder[1] is just beyond Detector 1 Reader 0
Got = {false, false, false}; -- set all Got[] to false
-- Require three engines to run this script
if (iEngineCount == 3) then
SM = READYGO_3;
print("Three Engines Detected");
else
SM = IDLE;
print("Three Engines Not Detected, setting state machine to IDLE");
end
PFADelay = 0;
Detector1Passenger = false;
-- All engines have been detected and we know their order on the layout
return true;
end
-- At this point, we have detected a new engine
EnginesUsedFlag[iDex] = DETECTED; -- flag as detected in motion - EnginesUsed[] is 0 based
-- EngineDetectionOrder[] is 1 based
iEngineDetectionCount = iEngineDetectionCount + 1; -- Count number of detected engines
EngineDetectionOrder[iEngineDetectionCount] = EngineNo; -- remember the detection order
print(string.format("Engine # %d detected (Index : %d)", EngineNo, iEngineDetectionCount));
BumpCounter(COUNTER01); -- bump the Engines Detected Counter on the Program Control window
return true;
end
end
end
-- continue in this state
return true;
end
--[[---------------------------------------------------------------------------------------]]
if (SM == READYGO_3) then
-- All engines are detected, their order is known - stop them over a reader
-- This should pick off the engines starting at the last one. This would make
-- sure that if two engines are between detectors, we pick them off from the last one
--
-- The state machine will now loop over three states, READYGO_3, READYGO_3a, and READYGO_3b.
-- Each state will move the engine up to the next detector and then stop it there.
--
-- Schedule a random noise on engine rear truck detection on all detectors and all readers
if (isEngine(EngineNo) and isTagRear(TagLocation)) then
RandomAction(EngineNo, MyTIUNo);
end

-- run first [1] engine to Detector 3 Reader 0
-- Detect both front and rear tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 3) and isReader(Reader, 0)))) then
if (EngineNo == EngineDetectionOrder[1]) then
if (Got[1]) then Throttle(0, STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[1]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end
Got[1] = true;
end
end

-- run second [2] engine to Detector 2 Reader 0 or Reader 1
-- Detect both front and rear tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 2) and isReader(Reader, 0)) or (isDetector(Detector, 2) and isReader(Reader, 1)))) then
if (EngineNo == EngineDetectionOrder[2]) then
if (Got[2]) then Throttle(0, STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[2]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end
Got[2] = true;
end
end

-- run third [3] engine to Detector 1 Reader 0
-- Detect both front and read tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 1) and isReader(Reader, 0)))) then
if (EngineNo == EngineDetectionOrder[3]) then
if (Got[3]) then Throttle(0, STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[3]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end
Got[3] = true;
end
if (isCarModel(CarModel, GP7) and isTagFront(TagLocation)) then -- Special sounds for GP7 #1501
local iDly = GP7PFA(0, EngineNo, MyTIUNo);
if (iDly > PFADelay) then
PFADelay = iDly;
end
end
-- set flag if a passenger train being pulled by a GP7 was detected
if (isCarModel(CarModel, GP7)) then
Detector1Passenger = true;
else
Detector1Passenger = false;
end
end

if (Got[1] and Got[2] and Got[3]) then -- set speed for all engines and go to next state
if (math.random(5) == 1) then -- Special sounds for SW1200 #1208 Engine #4
local iDly = SW1200PFA(0, 4, MyTIUNo);
if (iDly > PFADelay) then
PFADelay = iDly;
end
end
-- Counter to keep track of States
BumpCounter(COUNTER02); -- bump the Counter02 on the Program Control window

SetSpeed(15 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[1], MyTIUNo); -- @Detector 3 Reader 0
SetSpeed(15 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[2], MyTIUNo); -- @Detector 2 Reader 0
SetSpeed(15 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[3], MyTIUNo); -- @Detector 1 Reader 0
--
-- if a passenger train is leaving Detector 1 Reader 0, set switches to diverge
-- otherwise, set the switches to straight
-- Both after the appropriate delay to let the trains get underway
if (Detector1Passenger) then
-- set the switches to diverge, Engine Number is not used but must be specified
print("College East & West set to THROWN/DIVERGE in " .. PFADelay + 8 + 35 + 20);
Switch(DIVERGE, 1, 1, PFADelay + 8 + 35 + 20, EngineNo, MyTIUNo); -- College East
Switch(DIVERGE, 1, 2, PFADelay + 8 + 35 + 23, EngineNo, MyTIUNo); -- College West
Detector1Passenger = false;
else
-- set the switches to straight, Engine Number is not used but must be specified
print("College East & West set to CLOSED/STRAIGHT in " .. PFADelay + 8 + 35 + 20);
Switch(STRAIGHT, 1, 1, PFADelay + 8 + 35 + 20, EngineNo, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 2, PFADelay + 8 + 35 + 23, EngineNo, MyTIUNo); -- College West
end
Got = {false, false, false}; -- set all Got[] to false
SM = READYGO_3a; -- go to next state
PFADelay = 0;
end
-- continue in this state
return true;
end
--[[---------------------------------------------------------------------------------------]]
if (SM == READYGO_3a) then

-- All engines are detected, their order is known - stop them over a reader
-- Schedule a random noise on engine rear truck detection on all detectors and all readers
if (isEngine(EngineNo) and isTagRear(TagLocation)) then
RandomAction(EngineNo, MyTIUNo);
end

-- run first [1] engine to Detector 1 Reader 0
-- Detect both front and rear tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 1) and isReader(Reader, 0)))) then
if (EngineNo == EngineDetectionOrder[1]) then
if (Got[1]) then Throttle(0, STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[1]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end
Got[1] = true;
end
if (isCarModel(CarModel, GP7) and isTagFront(TagLocation)) then -- Special sounds for GP7 #1501
local iDly = GP7PFA(0, EngineNo, MyTIUNo);
if (iDly > PFADelay) then
PFADelay = iDly;
end
end
-- set flag if a passenger train being pulled by a GP7 was detected
if (isCarModel(CarModel, GP7)) then
Detector1Passenger = true;
else
Detector1Passenger = false;
end
end

-- run second [2] engine to Detector 3 Reader 0
-- Detect both front and read tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 3) and isReader(Reader, 0)))) then
if (EngineNo == EngineDetectionOrder[2]) then
if (Got[2]) then Throttle(0, STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[2]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end
Got[2] = true;
end
end
-- run third [3] engine to Detector 2 Reader 0 or Reader 1
-- Detect both front and read tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 2) and isReader(Reader, 0)) or (isDetector(Detector, 2) and isReader(Reader, 1)))) then
if (EngineNo == EngineDetectionOrder[3]) then
if (Got[3]) then Throttle(0, STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[3]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end
Got[3] = true;
end
end

if (Got[1] and Got[2] and Got[3]) then -- set speed for all engines and go to next state
-- Counter to keep track of States
BumpCounter(COUNTER02); -- bump the Counter02 on the Program Control window

SetSpeed(14 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[1], MyTIUNo); -- @Detector 1 Reader 0
SetSpeed(14 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[2], MyTIUNo); -- @Detector 3 Reader 0
SetSpeed(14 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[3], MyTIUNo); -- @Detector 2 Reader 0
--
-- if a passenger train is leaving Detector 1 Reader 0, set switches to diverge
-- otherwise, set the switches to straight
-- Both after the appropriate delay to let the trains get underway
if (Detector1Passenger) then
-- set the switches to diverge, Engine Number is not used but must be specified
print("College East & West set to THROWN/DIVERGE in " .. PFADelay + 8 + 35 + 20);
Switch(DIVERGE, 1, 1, PFADelay + 8 + 35 + 20, EngineNo, MyTIUNo); -- College East
Switch(DIVERGE, 1, 2, PFADelay + 8 + 35 + 23, EngineNo, MyTIUNo); -- College West
Detector1Passenger = false;
else
-- set the switches to straight, Engine Number is not used but must be specified
print("College East & West set to CLOSED/STRAIGHT in " .. PFADelay + 8 + 35 + 20);
Switch(STRAIGHT, 1, 1, PFADelay + 8 + 35 + 20, EngineNo, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 2, PFADelay + 8 + 35 + 23, EngineNo, MyTIUNo); -- College West
end
Got = {false, false, false}; -- set all Got[] to false
SM = READYGO_3b; -- go to next state
PFADelay = 0;
end

-- continue in this state
return true;
end
--[[---------------------------------------------------------------------------------------]]
if (SM == READYGO_3b) then
-- All engines are detected, their order is known - stop them over a reader
-- Schedule a random noise on engine rear truck detection on all detectors and all readers
if (isEngine(EngineNo) and isTagRear(TagLocation)) then
RandomAction(EngineNo, MyTIUNo);
end

-- run first [1] engine to Detector 2 Reader 0 or Reader 1
-- Detect both front and rear tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 2) and isReader(Reader, 0)) or (isDetector(Detector, 2) and isReader(Reader, 1)))) then
if (EngineNo == EngineDetectionOrder[1]) then
if (Got[1]) then Throttle(0, STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[1]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end
Got[1] = true;
end
end

-- run second [2] engine to Detector 1 Reader 0
-- Detect both front and read tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 1) and isReader(Reader, 0)))) then
if (EngineNo == EngineDetectionOrder[2]) then
if (Got[2]) then Throttle(0, STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[2]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end
Got[2] = true;
end
if (isCarModel(CarModel, GP7) and isTagFront(TagLocation)) then -- Special sounds for GP7 #1501
local iDly = GP7PFA(0, EngineNo, MyTIUNo);
if (iDly > PFADelay) then
PFADelay = iDly;
end
end
-- set flag if a passenger train being pulled by a GP7 was detected
if (isCarModel(CarModel, GP7)) then
Detector1Passenger = true;
else
Detector1Passenger = false;
end
end

-- run third [3] engine to Detector 3 Reader 0
-- Detect both front and read tags as a double check to make sure we detected an engine
if (isEngine(EngineNo) and ((isDetector(Detector, 3) and isReader(Reader, 0)))) then
if (EngineNo == EngineDetectionOrder[3]) then
if (Got[3]) then Throttle(0, STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[3]) then SetSpeed(0, STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end
Got[3] = true;
end
end

if (Got[1] and Got[2] and Got[3]) then -- set speed for all engines and go to next state
-- Counter to keep track of States
BumpCounter(COUNTER02); -- bump the Counter02 on the Program Control window
SetSpeed(12 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[1], MyTIUNo); -- @Detector 2 Reader 0
SetSpeed(12 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[2], MyTIUNo); -- @Detector 1 Reader 0
SetSpeed(12 + math.random(5), PFADelay + 8 + math.random(20), EngineDetectionOrder[3], MyTIUNo); -- @Detector 3 Reader 0
--
-- if a passenger train is leaving Detector 1 Reader 0, set switches to diverge
-- otherwise, set the switches to straight
-- Both after the appropriate delay to let the trains get underway
if (Detector1Passenger) then
-- set the switches to diverge, Engine Number is not used but must be specified
print("College East & West set to THROWN/DIVERGE in " .. PFADelay + 8 + 35 + 20);
Switch(DIVERGE, 1, 1, PFADelay + 8 + 35 + 20, EngineNo, MyTIUNo); -- College East
Switch(DIVERGE, 1, 2, PFADelay + 8 + 35 + 23, EngineNo, MyTIUNo); -- College West
Detector1Passenger = false;
else
print("College East & West set to CLOSED/STRAIGHT in " .. PFADelay + 8 + 35 + 20);
-- set the switches to straight, Engine Number is not used but must be specified
Switch(STRAIGHT, 1, 1, PFADelay + 8 + 35 + 20, EngineNo, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 2, PFADelay + 8 + 35 + 23, EngineNo, MyTIUNo); -- College West
end
Got = {false, false, false}; -- set all Got[] to false
SM = READYGO_3; -- go to next state
PFADelay = 0;
end
-- 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
print("cleanup()");
for idx,Engine in ipairs(EnginesUsedNum) do
print(idx .. " Shutdown Engine " .. Engine);
EngineShutDown(Engine, MyTIUNo);
end
ShowTags(Engine, TIU); -- dump out the Tags table
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]

function GP7PFA(when, EngineNo, TIUNo)
--
-- Run the GP7 PFA sequence
--
-- returns additional delay for PFA on success
--
local PFADelay = when + 38;
SetVolume(ENGINE_VOLUME, 25, when + 10, EngineNo, TIUNo); -- reduce engine volume for the PFA sequence
PlaySound(110, when + 15, EngineNo, TIUNo); -- Now Boarding
PlaySound(111, when + 18, EngineNo, TIUNo); -- Ticketed Passengers Only
PlaySound(112, when + 21, EngineNo, TIUNo); -- Let me help you
PlaySound(113, when + 24, EngineNo, TIUNo); -- Let me take your baggage
PlaySound(114, when + 28, EngineNo, TIUNo); -- Thanks
PlaySound(115, when + 34, EngineNo, TIUNo); -- All Aboard
SetVolume(ENGINE_VOLUME, 100, when + 40, EngineNo, TIUNo);
print("GP7 PFA " .. PFADelay);
return PFADelay;
end
--[[---------------------------------------------------------------------------------------]]

function SW1200PFA(when, EngineNo, TIUNo)
--
-- Run the SW1200 PFA sequence
--
-- returns additional delay for PFA on success
--
local PFADelay = when + 6 + 20 + 73; -- its a long sequence
StartUp(when + 6, EngineNo, MyTIUNo); -- Startup takes 20 seconds
-- when + 6 + 20
PlaySound(80, when + 6 + 20, EngineNo, TIUNo);
PlaySound(81, when + 6 + 20 + 5, EngineNo, TIUNo);
PlaySound(82, when + 6 + 20 + 5 + 10, EngineNo, TIUNo);
PlaySound(83, when + 6 + 20 + 5 + 10 + 3, EngineNo, TIUNo);
PlaySound(84, when + 6 + 20 + 5 + 10 + 3 + 4, EngineNo, TIUNo);
PlaySound(85, when + 6 + 20 + 5 + 10 + 3 + 4 + 4, EngineNo, TIUNo);
PlaySound(86, when + 6 + 20 + 5 + 10 + 3 + 4 + 4 + 5, EngineNo, TIUNo);
PlaySound(87, when + 6 + 20 + 5 + 10 + 3 + 4 + 4 + 5 + 3, EngineNo, TIUNo);
PlaySound(88, when + 6 + 20 + 5 + 10 + 3 + 4 + 4 + 5 + 3 + 5, EngineNo, TIUNo);
PlaySound(89, when + 6 + 20 + 5 + 10 + 3 + 4 + 4 + 5 + 3 + 5 + 5, EngineNo, TIUNo);
PlaySound(90, when + 6 + 20 + 5 + 10 + 3 + 4 + 4 + 5 + 3 + 5 + 5 + 4, EngineNo, TIUNo);
ShutDown (when + 6 + 20 + 5 + 10 + 3 + 4 + 4 + 5 + 3 + 5 + 5 + 4 + 5, EngineNo, TIUNo);
print("A&S SW1200 #1208 PFA " .. PFADelay);
return PFADelay;
end
--[[---------------------------------------------------------------------------------------]]
-----------------------------------------------------------------------------
-- Schedule a Random Noise
--
-- Params:
-- EngineNo
-- TIUNo
--
-- returns true on success
--
-----------------------------------------------------------------------------
function RandomAction(iEngineNo, iTIUNo)
-- Schedule a random noise
local retv = true;

-- pick sound/effect based on a random number
-- can be adjusted if there is too much or too little background noise
--
-- choose from:
-- 1. Turn the bell on for a random number of seconds
-- 2. Blow the horn/whistle for a random number of seconds
-- 3. Play the crossing sound n42
-- 4. Turn the smoke on for a random number of seconds
-- 5. Turn on Cab Chatter for a random number of seconds
-- 6. No sounds
--
local Starttime = 0;
local Endtime = 0;
local switchval = math.random(7); -- returns 1 through 7
if (switchval == 1) then
Starttime = 30 + math.random(10);
Endtime = Starttime + 5 + math.random(5);
Bell(ON, Starttime, iEngineNo, iTIUNo); -- send bell on
Bell(OFF, Endtime, iEngineNo, iTIUNo); -- send bell off
print("RandomAction: Bell");
end
if (switchval == 2) then
Starttime = 25 + math.random(10);
Endtime = Starttime + 3 + math.random(3);
Whistle(ON, Starttime, iEngineNo, iTIUNo); -- send Whistle/Horn on
Whistle(OFF, Endtime, iEngineNo, iTIUNo); -- send Whistle/Horn off
print("RandomAction: Horn/Whistle");
end
if (switchval == 3) then
Starttime = 25 + math.random(11);
PlaySound(42, Starttime, iEngineNo, iTIUNo); -- SXS Toot
print("RandomAction: SXS");
end
if (switchval == 4) then
Starttime = 25 + math.random(10);
Endtime = Starttime + 46;
Smoke(ON, Starttime, iEngineNo, iTIUNo); -- send Smoke on
Smoke(OFF, Endtime, iEngineNo, iTIUNo); -- send Smoke off
print("RandomAction: Smoke");
end
if (switchval == 5) then
Starttime = math.random(10);
if (math.random(2) == 1) then -- math.random returns 1 or 2
CabChat(ON, Starttime, iEngineNo, iTIUNo); -- set Cab Chatter on
print("RandomAction: Cab Chatter On");
else
CabChat(OFF, Starttime, iEngineNo, iTIUNo); -- set Cab Chatter off
print("RandomAction: Cab Chatter Off");
end
end
if (switchval == 6) then
-- No sounds some of the time
print("RandomAction: Quiet");
end
return retv;
end
--[[---------------------------------------------------------------------------------------]]

function SW1200Button(Eng, TIU)
print("SW1200Button()");
SW1200PFA(0, 4, TIU); -- only works with Engine 4, the SW1200
return true;
end
Function01Name, Function01Label = SW1200Button, "SW 1200 PFA";
--[[---------------------------------------------------------------------------------------]]
function GP7Button(Eng, TIU)
print("GP7Button()");
GP7PFA(0, 7, TIU); -- only works with Engines 7 and 13, the GP7's
return true;
end
Function02Name, Function02Label = GP7Button, "GP7 PFA";
--[[---------------------------------------------------------------------------------------]]
function PauseButton(Eng, TIU)
print("PauseButton()");
for idx,Engine in ipairs(EnginesUsedNum) do
S1 = GetSpeed(Engine, MyTIUNo); -- get Speed
print(idx .. " Pause Engine " .. Engine .. " Speed " .. S1);
EngineSpeedSave[idx] = S1;
SetSpeed(0, 0, Engine, MyTIUNo);
end
return true;
end
Function06Name, Function06Label = PauseButton, "Pause";
--[[---------------------------------------------------------------------------------------]]
function ResumeButton(Eng, TIU)
print("ResumeButton()");
for idx,Engine in ipairs(EnginesUsedNum) do
S1 = EngineSpeedSave[idx];
print(idx .. " Resume Engine " .. Engine .. " Speed " .. S1);
SetSpeed(S1, 0, Engine, MyTIUNo);
end
return true;
end
Function07Name, Function07Label = ResumeButton, "Resume";
--[[---------------------------------------------------------------------------------------]]
function ShowTags(Eng, TIU)
print("Tags detected:");
for k,v in pairs(Tags) do -- dump out the Tags table
print(k .. " " .. v);
end
return true;
end
Function10Name, Function10Label = ShowTags, "Show Tags";
--[[---------------------------------------------------------------------------------------]]



setup()    This function first initializes variables. Note that the script uses the value returned by the 'Debug()' function call to display various levels of debugging information. The value of 'Debug()' is set by the Debug window and can be between 0 (no debug messages) to 9 (full debug messages). Starts all of the active engines. We need three. Choose the three engines on the main window Engine List. Then script looks at all 99 possible engines for the ones that are running. Sets the volume, smoke and few other things. Counts the number of running engines (iEngineCount) and saves a list of the three started engines (EnginesUsedNum). Clears the detected flags (EnginesUsedFlag). Set the speed to 12 Smph for all running engines. Sets the state machine (SM) to the first state - DETECTENGINES

tag()    All of the engines are running at 12 Smph. Each time a tag is detected, this function is called. Based on the value of the state machine variable (SM), run the part of the script for each state:

DETECTENGINES    In this state, we are just trying to detect all of the running engines and ascertain their order on the layout. As each tag on a front engine truck is detected, toot the horn.  This just signals to me that the script is processing tags. We care about tags on an engine, on the front truck and detected by Detector 1. As each engine is detected, I record the order (EngineDetectedOrder). When I detect the first engine again, all of the engines have gone all away around the layout once. The script increases the speed of the three engines to 15, 18, & 20 Smph respectively. Now I should have detected 3 engines. If not, the script displays a message and goes into the IDLE state. If so, the script goes into READYGO_3 state. (Now, having written this description, I see that I should have more checking for the case where more than 3 engines are detected. I'll leave that has an exercise for you.)
READYGO_3, READYGO_3a, & READYGO_3b    These three states are basically identical so I will write one description. As each tag is detected, the tag() function is called and the state machine value (SM) will cause one of these states to execute. Tags from other non-engine cars are ignored. Knowing now the order of the three engines, the script will run the engines until each passes over one of the three detectors (1, 2 & 3). Then it stops the engine. When all three engines are stopped (at a detector), the script pauses for a few seconds and then the script starts the engines moving again at different random speeds. The three states detect the three engines over the three detectors. The state machine (SM) is set to the next of these states and waits for each of the three engines to run up to the next stopping point (at the next detector). So the state machine runs these three states in sequence over and over until the [STOP] button is pushed.
READYGO_3, READYGO_3a, & READYGO_3b    These three states are basically identical so I will write one description. As each tag is detected, the tag() function is called and the state machine value (SM) will cause one of these states to execute. Tags from other non-engine cars are ignored. Knowing now the order of the three engines, the script will run the engines until each passes over one of the three detectors (1, 2 & 3). Then it stops the engine. When all three engines are stopped (at a detector), the script pauses for a few seconds and then the script starts the engines moving again at different random speeds. The three states detect the three engines over the three detectors. The state machine (SM) is set to the next of these states and waits for each of the three engines to run up to the next stopping point (at the next detector). So the state machine runs these three states in sequence over and over until the [STOP] button is pushed.

cleanup()    This function is called when you press the [STOP] button. It stops all active engines and terminates the script.

GP7PFA()    This function runs the PFA for a GP7. Called randomly during the running of the script.

SW1200PFA()    This function runs the PFA for a SW1200. In particular, the Aliquippa & Southern SW1200 #1208. Called randomly during the running of the script.

RandomAction()    This function runs a randomly selected sound or light action. This was done to just add some additional sound and light to the running of the script.

SW1200()    This function is called when the you press the [Function01] button (labeled: "SW 1200 PFA") on the Program Control window. It, in turn, calls SW1200PFA().

GP7()    This function is called when the you press the [Function02] button (labeled: "GP7 PFA") on the Program Control window. It, in turn, calls GP7PFA().



Needed Support Scripts

I've written two simple scripts that define constants and functions that make writing scripts easier. They are defines.lua and functions.lua. I've used these in some of the example scripts shown above.


defines.lua

defines.lua contains constants used the main script. For example, in your script, instead of writing out the command to turn on the bell as "w4", you can just write "BELL_ON".

Use this statement in your script so the script can access these defines:

require("defines");

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

-- Semicolons are not required in Lua code
-- Commands from RTCMegaMaster.h
BELL_ON = "w4"
BELL_OFF = "bFFFB"
HDLT_ON ="ab1"
HDLT_OFF = "ab0"
WHISTLE_ON = "w2"
WHISTLE_OFF = "bFFFD"
DIRECTION_FORWARD = "d0"
DIRECTION_REVERSE = "d1"
DIRECTION_CHANGE = "d%d"
COMMAND_MODE = "m4"
COMMAND_STARTUP = "u4"
COMMAND_SHUTDOWN = "u5"
COMMAND_STARTUP_EXTENDED = "u6"
COMMAND_SHUTDOWN_EXTENDED = "u7"
COMMAND_STOP = "s0"
COUPLER_FRONT = "c0"
COUPLER_REAR = "c1"
SMOKE_EXHAUST_MIN = "ab12"
SMOKE_EXHAUST_MED = "ab11"
SMOKE_EXHAUST_MAX = "ab10"
SMOKE_EXHAUST_ON = "abF"
SMOKE_EXHAUST_OFF = "abE"
MARKERS_ON = "ab1C"
MARKERS_OFF = "ab1D"
BEACON_ON = "abD"
BEACON_OFF = "abC"
CAB_CHAT_ON = "ab13"
CAB_CHAT_OFF = "ab14"
CHUFF_RATE_2 = "g2"
CHUFF_RATE_4 = "g4"
COUPLER_CLASH = "h1"
--
AUX1_ON = "ab3"
AUX1_OFF = "ab2"
AUX2_ON = "ab4"
AUX2_OFF = "ab5"
AUX3_ON = "ab6"
AUX3_OFF = "ab7"
--
ACC_ON = "Ae%02d"
ACC_OFF = "Af%02d"
ACC_ALL_ON = "AaFFFFFFFFFFFFFF"
ACC_ALL_OFF = "Aa00000000000000"
SW_STRAIGHT = "Ac%02d"
SW_DIVERGE = "Ad%02d"
SW_ALL_STRAIGHT = "Ab05555555555555555555555555"
SW_ALL_DIVERGE = "Ab0AAAAAAAAAAAAAAAAAAAAAAAAA"
SW_CLOSED = SW_STRAIGHT
SW_THROWN = SW_DIVERGE
SW_ALL_CLOSED = SW_ALL_STRAIGHT
SW_ALL_THROWN = SW_ALL_DIVERGE
SPEED_SET = "s%d"
PLAY_SOUND = "n%d"
SINGLE_TOOT = "n41"
CROSSING = "n42"
DOUBLE_TOOT = "n43"
TRIPLE_TOOT = "n44"
IDLE_SOUND = "i%d"
VOLUME_CHANGE = "v%1d%03d"
MASTER_VOLUME = 0
ENGINE_VOLUME = 1
ACCENT_VOLUME = 2
HORN_VOLUME = 3
BELL_VOLUME = 4
COUPLER_CMD = "c%d"
FRONT = 0
REAR = 1
SETCMD = "ab%X"
CMD_HDLT_OFF = 0x00
CMD_HDLT_ON = 0x01
CMD_INT_OFF = 0x02
CMD_INT_ON = 0x03
CMD_MARS_OFF = 0x04
CMD_MARS_ON = 0x05
CMD_DITCH_OFF = 0x06
CMD_DITCH_ON = 0x07
CMD_BEACON_ON = 0x0D
CMD_BEACON_OFF = 0x0C
CMD_SMOKE_OFF = 0x0E
CMD_SMOKE_ON = 0x0F
CMD_SMOKE_MAX = 0x10
CMD_SMOKE_MED = 0x11
CMD_SMOKE_MIN = 0x12
CMD_MARKERS_ON = 0x1C
CMD_MARKERS_OFF = 0x1D
CMD_NUMBERBOARDS_ON = 0x05
CMD_NUMBERBOARDS_OFF = 0x04
ACC_RATE = "Da%d"
DEC_RATE = "Dd%d"
EMERGENCY_STOP = "o0"
LABOR_REV_UP = "r16"
LABOR_REV_DOWN = "r17"
LABOR_REV_NORMAL = "r15"

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

-- Constants
-- Engine state
STARTED = true;
SHUTDOWN = false;
-- AIU Acessory state
ON = true;
OFF = false;
-- Engine direction
FORWARD = true;
REVERSE = false;
-- AIU switch position
STRAIGHT = true;
CLOSED = true;
NORMAL = true;
OPEN = false;
DIVERGE = false;
THROWN = false;
REVERSE = false;
-- Coupler
FRONT = true;
REAR = false;

RAMMING_SPEED = 15; -- speed in Smph that helps assure successful coupling

-- Packet Constants from RTC_NFC.h
-- Engine Data in binary - 16 bytes
--TagID; -- 0 = tag not programmed, 0x88 = tag is programmed
TAG_PROGRAMMED = 0x88
--EngineNo; -- 0 = not an Engine or LashUp (look at CarModel), otherwise Engine Number 1-99 (look at CarModel)
-- 101-120 LashUp Number
NOT_AN_ENGINE = 0
--TagLocation; -- 0 = Tag not on a truck, 1 = Tag on Front truck, 2 = Tag on Rear truck (Engines and Cars)
NOT_ON_A_TRUCK = 0
TAG_ON_FRONT_TRUCK = 1
TAG_ON_REAR_TRUCK = 2
TAG_ON_TRUCK = 4
TAG_AT_MIDPOINT = 5
--EngineType; -- 0 = not an engine, 1 = Diesel, 2 = Steam (Engines Only)
NOT_AN_ENGINE = 0
DIESEL_ENGINE_TYPE = 1
STEAM_ENGINE_TYPE = 2
GAS_MECHANICAL_ENGINE_TYPE = 3
GAS_ELECTRIC_ENGINE_TYPE = 4
STEAM_TURBINE_ENGINE_TYPE = 5
ELECTRIC_TYPE = 6
TURBINE_TYPE = 7
DIESEL_MECHANICAL_TYPE = 8
--Coupler
NO_COUPLER = 0
REAR_REMOTE_COUPLER_ONLY = 1
FRONT_REMOTE_COUPLER_ONLY = 2
FRONT_AND_REAR_TEMOTE_COUPLERS = 3
COUPLER_UNKNOWN = 4
REAR_MANUAL_COUPLER_ONLY = 5
FRONT_MANUAL_COUPLER_ONLY = 6
FRONT_AND_REAR_REMOTE_COUPLERS = 7
REAR_DUMMY_COUPLER_ONLY = 8
FRONT_DUMMY_COUPLER_ONLY = 9
FRONT_DUMMY_AND_REAR_MANUAL_COUPLERS = 10
NOT_APPLICABLE = 0x80 -- Coupler is only used for Engines
--SXS; -- 0 = no SXS Sound, 1 = has SXS sound at clip 42 (Engines only)
NO_SXS_SOUND = 0
HAS_SXS_SOUND = 1
--CarModel; -- see below
-- if EngineNo == NOT_AN_ENGINE
UNKNOWN_CARMODEL = 0 -- 0 = unknown
BOXCAR = 1 -- 1 = Boxcar
TANK_CAR = 2 -- 2 = Tank Car
FLAT_CAR = 3 -- 3 = Flat Car
CABOOSE = 4 -- 4 = Caboose
TENDER = 5 -- 5 = Tender
GONDOLA = 6 -- 6 = Gondola
COAL_HOPPER = 7 -- 7 = Coal Car
ORE_HOPPER = 8 -- 8 = Ore Car
MOW_FLATCAR = 9 -- 9 = MOW Flatcar
MOW_CRANE_CAR = 10 -- 10 = MOW Crane Car
MOW = 11 -- 11 = MOW Misc
COACH = 12 -- 12 = Passenger Coach
OBSERVATION = 13 -- 13 = Passenger Observation
BAGGAGE = 14 -- 14 = Baggage
-- xx-255 TBD
-- if EngineNo != NOT_AN_ENGINE (Lead Engine for LashUps)
UNKNOWN_ENGINEMODEL = 0 -- 0 = unknown
GP38_2 = 1 -- 1 = GP38-2
GP7 = 2 -- 2 = GP7
GP9 = 3 -- 3 = GP9
U28B = 4 -- 4 = U28B
BERKSHIRE = 5 -- 5 = Berkshire
DOODLEBUG = 6 -- 6 = Doodlebug
SW1200 = 7 -- 7 = SW1200
SW1500 = 8 -- 8 = SW1500
SWITCHER_0_6_0 = 9 -- 9 = 0-6-0 Switcher
SWITCHER_0_8_0 = 10-- 10 = 0-8-0 Switcher
H_9 = 11 -- 11 = 2-8-0 H-9 Consolidation
SW_9 = 12 -- 12 = SW-9
RS_3 = 13 -- 13 = Alco RS-3
-- xx-255 TBD
--Railroad; -- see below
UNKNOWN_RAILROAD = 0
P_AND_LE = 1
A_AND_S = 2
NYC = 3
B_AND_O = 4
P_AND_E = 5
LE_AND_E = 6
P_RR = 7
NH = 8
RDG = 9
J_AND_L = 10
Isalys = 11
LV = 12
-- Many more defined see "Tag Programmer"
--CarLength; -- Length of car in centimeters, coupler face to coupler face (include Tender)
--TagOffset; -- Distance from center of tag to coupler face in centimeters
-- for engines/cars with two tags, the distance must be same for both tags
--CarNumber[6]; -- Up to 6 digit engine/car number in ASCII (w/leading spaces) " 2808" -- NOT NULL TERMINATED
--[[---------------------------------------------------------------------------------------]]
-- The first six counters define the fields in the Program Control window that the Lua scripts can
-- increment (BumpCounter()), decrement (DecCounter()), read (GetCounter()) and clear (ClearCounter()).
-- Be careful if you change the TAGSUB, UNIQTAG or TAGCNT counters. These counters
-- are used by the tag detection system and changing them at the wrong time may confuse
-- the tag detection. Those counters appear on the Tags Detected List window.
-- The RUNTIME counter counts seconds and its probably not a good idea to change this value.
COUNTER01 = 0 -- User defined Counter 1
COUNTER02 = 1 -- User defined Counter 2
COUNTER03 = 2 -- User defined Counter 3
COUNTER04 = 3 -- User defined Counter 4
COUNTER05 = 4 -- User defined Counter 5
COUNTER06 = 5 -- User defined Counter 6
RUNTIME = 6 -- Run Time Counter 7
TAGSUB = 10 -- The number of UID only tags detected that were replaced by tag data from previous tags
UNIQTAG = 11 -- The number of unique tags ever detected by your and previous scripts
TAGCNT = 12 -- The number of tags detected by your script
--[[---------------------------------------------------------------------------------------]]
-- Beep() parameter
DEFAULT_BEEP = 0;
CRITICAL_STOP = 1;
QUESTION = 2;
EXCLAMATION = 3;
ASTERISK = 4;

--[[---------------------------------------------------------------------------------------]]
-- LED colors for the SetLED() and other functions that require a color value
clGreen = 0;
clYellow = 1;
clRed = 2;
clClear = 3;
clBlack = 4;
clBlue = 5;
clFuchsia = 6;
--[[---------------------------------------------------------------------------------------]]
-- Packet Types
-- These numeric values are the different responses that RTC can receive
-- from the Detectors
-- Start of Packet Character
START_OF_PACKET = 0xFF
-- End of Packet Character
END_OF_PACKET = 0xF0

-- NOP - ACK - NAK - WACK
NOP = 0x00
ACK = 0x61
NAK = 0x62
WACK = 0x63

--Error:
--0x10 Didn't find PN53x detector on port X
PN53xNOTFOUND = 0x10
--0x11 Unable to read Block X
CANTREADBLOCK = 0x11
--0x12 Tag not Programmed
TAGNOTPROG = 0x12
--0x13 Unable to authenticate
AUTHERROR = 0x13
--0x14 SOP character missing
SOPMISSING = 0x14
--0x15 Write block error
WRITEERROR = 0x15
--0x16 No Tag Detected When Expected
NOTAGDETECTED = 0x16

--Tag Data (TAGDATABLOCK4 also returns the Tag UID):
--0x20 not used
--0x21 Block 4
TAGDATABLOCK4 = 0x21
--0x22 Block 5
TAGDATABLOCK5 = 0x22
--0x23 Block 6
TAGDATABLOCK6 = 0x23
--0x24 Block 7
TAGDATABLOCK7 = 0x24

--Information:
--0x01 Hello X RFID Readers Attached (RTCNFCIRQ version in Reader byte and TagCount byte)
HELLOPACKET = 0x01
--0x03 Detector Y Reader X Detected
TAGDETECTED = 0x03
--0x04 Found chip PN5xx
PN53xFOUND = 0x04
--0x05 Waiting for an ISO14443A tag detection
WAITING = 0x05
--0x06 Detector Y Sensor X Detected
SENSORDETECTED = 0x06
--[[---------------------------------------------------------------------------------------]]
--[[
Copyright (c) 2016 Scott Lembcke and Howling Moon Software

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

TODO:
* Print short function arguments as part of stack location.
]]
function Pretty(obj, max_depth)
if max_depth == nil then max_depth = 3 end

-- Returns true if a table has a __tostring metamethod.
local function coerceable(tbl)
local meta = getmetatable(tbl)
return (meta and meta.__tostring)
end

local function recurse(obj, depth)
if type(obj) == "string" then
-- Dump the string so that escape sequences are printed.
return string.format("%q", obj)
elseif type(obj) == "table" and depth < max_depth and not coerceable(obj) then
local str = "{"

for k, v in pairs(obj) do
local pair = Pretty(k, 0).." = "..recurse(v, depth + 1)
str = str..(str == "{" and pair or ", "..pair)
end

return str.."}"
else
-- tostring() can fail if there is an error in a __tostring metamethod.
local success, value = pcall(function() return tostring(obj) end)
return (success and value or "<!!error in __tostring metamethod!!>")
end
end

return recurse(obj, 0)
end
PrettyPrint = function(value, depth) print(Pretty(value, depth)) end


functions.lua

functions.lua uses several functions called by the main script. These functions help in processing the RFID tags otherwise it is not needed.

Use this statement in your script so the script can access these functions:

require("functions");

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

-- Semicolons are not required in Lua code

-- this file is only needed if you are processing RFID tag detections in tag()

function isEngine(EngNo)
if (EngNo > 0) then return true; end
return false;
end

function notEngine(EngNo)
if (EngNo == 0) then return true; end
return false;
end

function isTagRear(TagLocation)
if (TagLocation == TAG_ON_REAR_TRUCK) then return true; end
return false;
end

function isTagFront(TagLocation)
if (TagLocation == TAG_ON_FRONT_TRUCK) then return true; end
return false;
end

function isDetector(Detector, x)
if (Detector == x) then return true; end
return false;
end

function isReader(Reader, x)
if (Reader == x) then return true; end
return false;
end

function isCarModel(CarModel, x)
if (CarModel == x) then return true; end
return false;
end






Lashups

This scripting control fully supports lashups. Lashups use numbers from 101 to 120.



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

title = "Lashup startup shutdown Test"
require("defines");

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup()");
MyTIUNo = TIU;
-- switches and accessories
Switch(STRAIGHT, 1, 2, 3, Engine, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 3, 5, Engine, MyTIUNo); -- College West
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
print("loop()");
MyTIUNo = TIU;

LashUpEngineList = {};
LashUpEngineList[1] = 13; -- head engine
-- middle engine(s) if any
LashUpEngineList[2] = 14; -- tail engine
print(string.format("Table with engines %d-%d", LashUpEngineList[1], LashUpEngineList[2]));

StartUp(0, 101, TIU, LashUpEngineList);
Smoke(OFF, 0, 101, MyTIUNo);

Whistle(ON, 20, 101, MyTIUNo);
Whistle(OFF, 22, 101, MyTIUNo);

return false; -- false=do not loop, true=continue to loop
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button
ShutDown(0, 101, MyTIUNo);
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]



Running Compiled Lua Scripts

Lua is an interpreted language that is compiled into bytecode before its run. If you have a very large Lua script, it may take some time to compile. You don't have to do this each time it is run. You can compile into bytecode, save the bytecode and have RTC run that bytecode.

Use the "luac.exe" compiler. The compiler produces a bytecode file named "luac.out". Rename it to your script name, keeping the ".out" extension. Then you can select that file from the [Browse] button on the Program Control window. Here is the command line that I use to compile a script (using the v5.3 Lua compiler):
luac53 -o script.out script.lua

You can also compile files if you use the require ("defines"); or require ("functions"); scripts in your main script.

To get the compiler, look for the file "lua-5.3.5_Win64_bin.zip" (or maybe a newer version number) at LuaBinaries on SourceForge. I've included a copy of the compiler in the Scripts folder of the RTC distribution.


Events


Function that Generates Events



SetEvent(DestPCNo, Type, P1, P2, P3);    -- passes the four integer parameters to the Program Control window
    -- specified by DestPCNo. As of RTC v4.4.2, there can be 10 PC windows numbered 1 through 10. The
    -- maximum may be raised in the future. In order for the destination PC window to receive the event,
    -- the script running in that window must have a function declared as "event(FromPCNo, Type, P1, P2, P3)".
    -- The receiving script will execute the event() function, presenting the source PC window number
    -- along with the four parameter values. The parameters are lua integers which are defined as
    -- 64 bit signed values. The meaning and contents of these parameters are not fixed and can be
    -- whatever you wish. Type/P1/P2/P3 are optional and if not given, zero is passed.
    -- The meaning of Type, P1, P2 and P3 must be agreed to by both the sender and receiver
    -- of the event. An example of Event Type values can be seen in the script distances.lua.
    -- The function returns nil if the destination PC window does not exist
    -- otherwise returns true when the event is sent. The function does NOT wait for the event
    -- to complete.



Here is the function that receives events

event(FromPCNo, Type, P1, P2, P3)  --  is called by any PC window using the SetEvent() function. It is used for commnications between the PC windows. Return true on success, false on failure. "FromPCNo" is the Program Control window # of the script which executed the SetEvent() function. Type, P1, P2 & P3 are integers which are being sent along with the event. Look at the SetEvent() description above for details of how this function is used. If the function does not appear in your script, RTC does not attempt to call it. Any PC window can send an event to itself.


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

-- Semicolons are not required in Lua code
title = "Event Test"
require([[defines]]);
require([[functions]]);

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
print("setup() Running");
print("This is Program Control Window #" .. PCNo());
print("This script requires 'event.lua' be running in PC window #1 and #2");
if (PCNo() < 1 or PCNo() > 2) then
return false; -- need two PC windows 1 and 2
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");
if (PCNo() == 1) then
DestPC = 2;
end;
if (PCNo() == 2) then
DestPC = 1;
end
print("Sending an event to PC Window #" .. DestPC .. " in 10 seconds");
Sleep(10);
res = SetEvent(DestPC, 123456789012345, PCNo(), -876765, 556677);
if (type(res) == "nil") then
print("nil : invalid destination Program Control number - script not running");
return Stop("DONE");
end;
if (type(res) == "boolean") then
print(res, " : success");
end;
print("Almost Done...");
Sleep(10);
return Stop("DONE");
end
--[[---------------------------------------------------------------------------------------]]
function event(FromPC, Type, P1, P2, P3)
print("event() - From PC# ", FromPC, " EventType = ", Type, " Parameters = ", P1, " ", P2, " ", P3);
return true; -- false=event failed, true=event succeeded
end

--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button
print("cleanup() Running");
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]


When you run the event.lua script in both PC window #1 and #2, here is what PC window #1 should look like after successful completion.
Here is what PC window #2 should look like.




Global Integers and Strings

These global variables are meant to share information between the 10 possible running scripts. They can be used as interlocking flags to prevent different scripts from accessing the same resource at the same time. When you successfully set a value, you get a lock on that index. As long as the other scripts use the "Try Set" routines to see if they can lock the same index, you will own the lock on that index. The "Try Set" functions are "atomic", that is, they cannot get interrupted between the time they check if an index is locked until they lock the index (if possible).

Functions that access the Global Integers and Strings



SetGlobalInt(index, value);
GetGlobalInt(index);
TrySetGLobalInt(index, value);

TryMultiSetGlobalInt(table, value); -- There are 100 global integer locations where any Program Control window
    -- can store or retrieve a value. This locations are useful for communications between Program
    -- control windows. Index is 1-100, value is any integer. Use
    -- SetGlobalInt() to store a value and GetGlobalInt() to retrieve a value.
    -- TrySetGlobalInt() attempts to set a value into a global integer and will only succeed if the
    -- global integer 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 lock, it will return false.
    -- TryMultiSetGlobalInt() attemps to set a value into several global integers and will only succeed
    -- if all of the global integers 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 global integers, false
    -- if it cannot. The second value returned is the index number of the global integer which would
    -- not lock otherwise returns zero. table is a lua table, for example : {4, 22, 12}

SetGlobalString(index, string);
GetGlobalString(index);  -- There are 100 global string locations where any Program Control window
    -- can store or retrieve a string. These locations are useful for communications between Program
    -- Control windows. Index is 1-100, string is a string. Use SetGlobalString() to store a string
    -- and GetGlobalString() to retrieve a string.



Here is a test script for these global variables to give you an idea of how to use them:

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

title = "Global Variables Test"
require("defines");
require("functions");

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
-- this function is called once when the [Run Script] button is pushed
-- Engine : Engine number selected on the RTC Main window
-- TIU : TIU number selected on the RTC Main window
print("setup()");
for x = 1, 15 do
SetGlobalInt(x, 0);
print("GetGlobalInt(" .. x .. ") (zero) = ", GetGlobalInt(x))
end;
SetGlobalInt(3, 23456);
SetGlobalInt(99, 33);

SetGlobalString(4, "My String");
-- RunTime() must be converted to an integer
SetGlobalString(8, string.format("The time is %d seconds", math.floor(RunTime() + 0.5)));

print(string.format("RunTime = %.2f seconds", RunTime()));
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU)
-- must call Sleep() at least once in this function
print("loop()");
print("GetGlobalInt(3) (23456) = ", GetGlobalInt(3));
GlobalInt99 = GetGlobalInt(99);
print("GetGlobalInt(99) (++) = ", GlobalInt99);
GlobalInt99 = GlobalInt99 + 1;
SetGlobalInt(99, GlobalInt99);

print("GetGlobalString(4) ('MyString') = ", GetGlobalString(4));
print("GetGlobalString(8) = ", GetGlobalString(8));
--print("GetGlobalString(11) = ", GetGlobalString(11)); -- gets "index out of range" error
for x = 1, 15 do
print("GetGlobalInt(" .. x .. ") = ", GetGlobalInt(x))
end;
Sleep(5);
SetGlobalString(8, string.format("The time is %d seconds", math.floor(RunTime() + 0.5)));
print("loop exit");
return true; -- false=loop failed, true=loop succeeded
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
-- this function is called once when the user presses the [STOP] button
print(string.format("cleanup() Complete at %.2f", RunTime()));
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]

When you run the Global Variables test, this is what the PC window should look like. This test repeats over and over until you press [STOP].


"Layout" and Flags Display

Under Construction

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 block 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 Block Flag. Then point to where you want to place a block flag.

Type in the block 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 blocks that I have defined so far. When flags are first added, they are Green. That means the block is not occupied. They turn Red once the running script recognizes that they are occupied. Note the three blocks (98, 99, and 100) which are floating in space. I use these not as 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 block 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);
TryMultiSetFlagt(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. 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 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.

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 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 thumb 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.






Downloads

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


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.