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

RTC Control Language - Scripting


RTC Control Langauge - Scripting

This Page last updated on .

As Mike Hewett pointed out, one of the limitations of the new "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......



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". Lua, means "Moon" in Portugese. 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.


Some other links:

Programming in Lua (First Edition)
Programming in Lua (Forth 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 a coding example.

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.

Videos recorded in May 2019 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.

Here are the functions available to the script. 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. To display a formated string, use
    -- print(string.format(fmt, vars, ..))
    -- Multiple arguments, which can be strings, 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

BumpCounter(ID);   -- Increments one of the counters on the Program Control window
DecCounter(ID);   -- Decrements one of the counters on the Program Control window
GetCounter(ID);    -- returns the value in one of the counters on the program control window
    -- constant ID as follows:
    -- COUNTER01 - User defined counter 1
    -- COUNTER02 - User defined counter 2
    -- The next three counters are used by RTC when in RFID mode. You can freely use them
    -- if you are not using the RFID tag detection system
    -- COUNTER03 - Number of Tags using substitution
    -- COUNTER04 - Unique Tag Counter
    -- COUNTER05 - Total Tag Counter
    -- COUNTER06 - Dispatch List Counter
    -- use this: require("defines"); to define these constants.

Functions that control engines

-- these funtions return true on success and false on failure unless noted

Setting(iSet, when, EngineNo/LashUpNo, TIUNo);
Schedule(Command, when, Command, EngineNo/LashUpNo, TIUNo);
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);
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)
GetSpeed(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
GetInstSpeed(Engine, TIU);    -- return the instantaneous speed of
    -- the engine as an integer in Smph
    -- returns nil on error
PlaySound(iSoundNo, when, EngineNo/LashUpNo, TIUNo);
Sound(bState, when, EngineNo/LashUpNo, TIUNo);
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

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

Functions that control Accessories and Switches

-- these funtions return true on success and false on failure unless noted

Accessory(bState, iAIUNo, iChan, when, EngineNo, TIUNo);
Switch(bState, iAIUNo, iChan, when, EngineNo, TIUNo);

Functions that present layout status or other information

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()). 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, TIU);    -- 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, TIU);    -- returns the Engine's trip odometer mileage (DTO) as a number
    -- returns nil on error
DOD(Engine, TIU);    -- returns the Engine's odometer mileage (DOD) as a number
    -- returns nil on error
DCH(Engine, TIU);    -- returns the Engine's chronometer in seconds (DCH) as a number
    -- returns nil on error

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.
    -- Sleep() should always be called like this to correctly terminate on [STOP]:
    -- if (not Sleep(10)) then return false; end -- in case Sleep() is terminated early by [STOP]
Beep();    -- sound the PC's standard system beep
    -- always returns true
SetLED(LED#, State, Caption);    -- There are four LED (1 thru 4), "State" is true to
    -- set the LED to green or false to set the LED to red.
    -- Caption (in quotes) is optional and if given, sets the text under the LED to that value.
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 use the default value. Returns the result as a string.
    -- Returns nil if the parameters are invalid.
InputQuery(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] 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.
Stop();    -- call from loop(). Stop the script and run the function cleanup(). Use only as the
    -- value for a return statement as:   return Stop();
    -- This is for an immediate abort of the script. Usually you must press the [STOP] button
    -- to run cleanup() and end the script.

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();
    -- Sends an emergency stop command to all TIU
    -- always returns true

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 button
-- or 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 the status of 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 exisits  (engines 1-99)
    -- returns nil on error
IsActive(EngineNo);  -- returns true if the engine is active (engines 1-99)
    -- returns nil on error




The script files are plain text. To edit them, you can use notepad, metapad or Notepad++. The latter does syntax highlighting and knows the syntax of Lua.

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

setup(Engine, TIU, Debug)  --  is called once at the start, it is passed the engine number (1-99), the TIU number (1-5) from the main RTC window and the debug level (0-9) from the Debug window. If you don't know how to access the debug window, send me an email. Value is 0 for no debug to 9 for maximum debug information. 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.
 tag(Detector, Reader, EngineNo, TagLocation, CarType, TagPacket)    is called each time a packet 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. Look at the RTC_NFC.h file for values.
  • CarType - the car type as coded into the tag. Look at the RTC_NFC.h file for values.
  • 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).

Note that this tag() function is not passed a TIU number or debug level. if you need that information, save the values passed to the setup() function in global variables like MyTIU, and MyDebug.

---------------------------------------------------------------------------OR---------------------------------------------------------------------------

 loop(Engine, TIU,  Debug)  --   can be used when the RFID tag system is not active.  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. On the Setup window, the check box for [ ] Enable RFID must not be checked.

loop() will use up 100% of the CPU when 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.
 tick(Engine, TIU, Debug)  --  is called once per second. It is passed the engine number (1-99), the TIU number (1-5) from the main RTC window and the debug level (0-9) from the Debug window. Value is 0 for no debug to 9 for maximum debug information. 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, Debug)  --     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.

If you are running this without using the RFID tag readers, 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"
author = "SanDiegoMark"
require("defines");
require("functions");

-- Counter Labels
Counter01Label = "Do Nothing";

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU, Debug)
-- the setup() function is called once at the beginning of the run
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU, Debug)
-- The loop() function is repeatedly called if the RFID system is not enabled
-- Always call Sleep() at least once in this function
if (not Sleep(10)) then return false; end -- in case Sleep() is terminated early by [STOP]
BumpCounter(COUNTER01);
return true; -- false=do not loop, true=continue to loop
end
--[[---------------------------------------------------------------------------------------]]
function tag(Detector, Reader, EngineNo, TagLocation, CarType, TagPacket)
-- the tag() function is called if the RFID system is enabled and a tag is detected
return true; -- true=continue to process tags, false=stop processing tags
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU, Debug)
-- 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, Debug)
-- the tick() function is called once per second
-- do not call Sleep() from this function
return true; -- false=stop calling tick(), true=continue to tick
end
--[[---------------------------------------------------------------------------------------]]
function DL(Engine, TIU, Debug)
-- 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
return true;
end
Function01Name, Function01Label = DL, "Do Nothing";
--[[---------------------------------------------------------------------------------------]]


This script shows the use of two configuration variables, "title" and "author". If you assign strings to these variables, they are used to add a meaningful description to the Program Control window. In this example, the Program Control window will display Do Nothing Test (SanDiegoMark).

To use the tick()  function, just define it in your script.

Here is a example listing of a tick function

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




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 twice 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 other 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 progess 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 asl long as it return true. Return false to break the loop. When you press the [Stop] button, 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 the zip file linked to below):

-- Semicolons are not required in Lua code

require("defines");

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU, Debug)
-- 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
-- debug : debug level as selected on the Debug window, values 0 (off) to 9 (maximum)
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
if (not Sleep(10)) then return false; end -- returns false if terminated
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Engine, TIU, Debug)
-- 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
-- Debug : debug level as selected on the Debug window, values 0 (off) to 9 (maximum)
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);
if (not Sleep(10)) then return false; end -- returns false if terminated
Whistle(ON, 20, MyEngineNo, MyTIUNo);
Whistle(OFF, 22, MyEngineNo, MyTIUNo);
print(string.format("loop() Sleep(10) Started at %d", RunTime()));
if (not Sleep(10)) then return false; end -- returns false if terminated
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);
if (not Sleep(10)) then return false; end -- returns false if terminated
end

return false; -- false=do not loop, true=continue to loop
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU, Debug)
-- 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, Debug)
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, Debug)
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, Debug)
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 10 special interactive functions can be defined. They are named "Function01" through "Function10". All ten functions can be accessed through the use of 10 buttons which appear on the Program Control window. 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, Debug)
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 words "NOP" on the button and when the user presses the button, to call the function NOP(Eng, TIU, Debug).


Here is a example listing of Helper Functions

--[[---------------------------------------------------------------------------------------]]
function BlowWhistle(Eng, TIU, Debug)
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, Debug)
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, Debug)
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";
--[[---------------------------------------------------------------------------------------]]


Helper Counters

Two counters can be defined and will appear on the Program Control window. Your script can increment these counters to display information to the user. They are named "Counter01" and "Counter02". They are bottom two counters in the column of counters at the right edge of the window.

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(COUNTER02);  -- increment Counter 2
BumpCounter(COUNTER01);  -- increment Counter 1
DecCounter(COUNTER02);  -- decrement Counter 2
DecCounter(COUNTER01);  -- decrement Counter 1
If you are not in RFID mode, you can also use counters 3,4, 5 and 6.

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"
author = " 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, Debug)
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, Debug)
-- Always call Sleep() at least once in this function.
BumpCounter(SMSTATES); -- bump states counter
if (not Sleep(10)) then return false; end -- returns false if terminated
return true; -- false=do not loop, true=continue to loop
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU, Debug)
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, Debug)
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";
--[[---------------------------------------------------------------------------------------]]





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"
author = "Mark DiVecchio"

-- 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, Debug)
-- 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);
if (not Sleep(2)) then return false; end -- in case Sleep() is terminated early by [STOP]
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, CarType, 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 CarType %d]],Detector, Reader, EngineNo, TagLocation, CarType));
else -- it's a car
print(string.format([[tag() : Detector %d Reader %d CarType %d]],Detector, Reader, CarType));
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 (isCarType(CarType, 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 (isCarType(CarType, 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 (isCarType(CarType, 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 (isCarType(CarType, 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 (isCarType(CarType, 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 (isCarType(CarType, 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, Debug)
-- 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, Debug); -- 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, Debug)
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, Debug)
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, Debug)
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, Debug)
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, Debug)
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 in the 'Debug' variable 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().



defines.lua

defines.lua contains constants used the main script.


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
STARTED = true; -- Engine = state
SHUTDOWN = false;
ON = true; -- AIU = Accessory = State
OFF = false;
FORWARD = true; -- Engine = direction
REVERSE = false;
STRAIGHT = true; -- AIU = Switch = State
DIVERGE = false;
CLOSED = true; -- AIU = Switch = State
THROWN = false;

RAMMING_SPEED = 15; -- speed in Smph the 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 CarType), otherwise Engine Number 1-99 (look at CarType)
-- 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
NOT_APPLICABLE = 80
--EngineType; -- 0 = not an engine, 1 = Diesel, 2 = Steam (Engines Only)
NOT_AN_ENGINE = 0
DIESEL_ENGINE_TYPE = 1
STEAM_ENGINE_TYPE = 2
--Coupler; -- 0 = no coupler, 1 = rear coupler only, 2 = front coupler only, 3 = front and rear couplers
NO_COUPLER = 0
REAR_COUPLER_ONLY = 1
FRONT_COUPLER_ONLY = 2
FRONT_AND_REAR_COUPLER = 3
NOT_APPLICABLE = 80
--SXS; -- 0 = no SXS Sound, 1 = has SXS sound at clip 42 (Engines only)
NO_SXS_SOUND = 0
HAS_SXS_SOUND = 1
NOT_APPLICABLE = 80
--CarType; -- see below
-- if EngineNo == NOT_AN_ENGINE
UNKNOWN_CARTYPE = 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_CAR = 7 -- 7 = Coal Car
ORE_CAR = 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_ENGINETYPE = 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
DODDLEBUG = 6 -- 6 = Doddlebug
SW1200 = 7 -- 7 = SW1200
SW1500 = 8 -- 8 = SW1500
SWITCHER_0_4_0 = 9 -- 9 = 0-4-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
-- xx-255 TBD
--Railroad; -- see below
UNKNOWN_OR_OTHER = 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
--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
--[[---------------------------------------------------------------------------------------]]
-- BumpCounter()
-- These define the fields in the Program Control screen that the PC_Thread and Lua Script can increment
COUNTER01 = 0 -- User define Counter 1
COUNTER02 = 1 -- User defined Counter 2
AGSUB = 2 -- Number of Tags using substitution
UNIQTAG = 3 -- Unique Tag Counter
TAGCNT = 4 -- Total Tag Counter
DLCNT = 5 -- Dispatch List Entry Counter
--[[---------------------------------------------------------------------------------------]]






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.


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):

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

© Copyright 2019 by Mark DiVecchio

This file is part of Remote Train Control.

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

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

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

Home Page : http://www.silogic.com/trains/RTC_Running.html
---------------------------------------------------------------------------]]

-- Semicolons are not required in Lua code

-- this file 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 isCarType(CarType, x)
if (CarType == 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):

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

© Copyright 2019 by Mark DiVecchio

This file is part of Remote Train Control.

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

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

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

Home Page : http://www.silogic.com/trains/RTC_Running.html
---------------------------------------------------------------------------]]
-- Semicolons are not required in Lua code
title = "Lashup startup shutdown Test"
author = "Mark DiVecchio"
require("defines");

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU, Debug)
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, Debug)
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, Debug)
-- 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.


Downloads

You can download RTC v3.99 from here. Includes all of the above scripts and several more.

This scripting will only work with version 4.0+ (or v3.99 beta)  of the RTC program. I don't have V4.0 up on the web site yet (needs more testing), again, use v3.99 beta.


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.