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.



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 the program to handle a script language. Its a lot of work.

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. The final decision 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.


Background Information

The script language is similar to C or Pascal.

Here are the functions available to the script. I hope each one is self-explainatory. The parameters to each function call can be:


Functions that interact with the Program Control window

PrintString(string);   -- displays the "string" in the
    -- Program Control "Messages" Window. To display a formated string, use
    -- PrintString(string.format(fmt, vars, ..))
    -- Use string concatenation for simple display:
    -- PrintString("TIU " .. TIUNo .. " Engine " .. EngineNo);
    -- You can use print() in place of PrintString().

BumpCounter(ID);   -- Increments one of the counters
    -- on the Program Control window
    -- constant ID as follows:
    -- COUNTER01 - Engines Detected Counter
    -- COUNTER02 - undedicated Counter
    -- TAGSUB - Number of Tags using substitution
    -- UNIQTAG - Unique Tag Counter
    -- TAGCNT - Total Tag Counter
    -- DLCNT - Dispatch List Entry Counter
    --
    -- requires dofile("defines.rcl") to define these constants.

Functions that control engines

-- these funtions return true on success and false on failure 
Setting(iSet, when, EngineNo, TIUNo);
SetVolume(Chan, Level, when, EngineNo, TIUNo);
Schedule(Command, when, Command, EngineNo, TIUNo);
ShutDown(when, EngineNo, TIUNo);
    -- simple shutting down of an engine.
ShutDown(when, LashUpNo, TIUNo);

    -- simple shutting down of a Lashup.
StartUp(when, EngineNo, TIUNo);
    -- simple starting up of an engine.
StartUp(when, LashUpNo, TIUNo, LashUpEngineList);
    -- simple starting up of a Lashup.
Accessory(bState, iAIUNo, iChan, when, EngineNo, TIUNo);

Switch(bState, iAIUNo, iChan, when, EngineNo, TIUNo);
Whistle(bState, when, EngineNo, TIUNo);
Bell(bState, when, EngineNo, TIUNo);
Markers(bState, when, EngineNo, TIUNo);
Beacon(bState, when, EngineNo, TIUNo);
CabChat(bState, when, EngineNo, TIUNo);
Smoke(bState, when, EngineNo, TIUNo);
ChuffRate(iRate, when, EngineNo, TIUNo);
Rate(iAcc, iDec, when, EngineNo, TIUNo);
OpenCoupler(bCoupler, when, EngineNo, TIUNo);
SetDirection(bDIR, when, EngineNo, TIUNo);
GetDirection(Engine, TIU);    -- return the direction as an boolean, true = forward, false = reverse
Throttle(iMPH, when, EngineNo, TIUNo);
    -- sends only a speed command
SetSpeed(iMPH, when, EngineNo, 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, engine may be accellerating or
            decellerating to reach this speed

PlaySound(iSoundNo, when, EngineNo, TIUNo);
Sound(bState, when, EngineNo, TIUNo);
ProtoWhistle(bState, when, EngineNo, TIUNo);
SetQuill(iTone, when, EngineNo, TIUNo);
    -- sets the quilling whistle tone, values 0-3
    -- Turn on Proto Whistle first, engine must support this feature
SmokeWhistle
(bState, when, EngineNo, TIUNo);
    --
engine must support this feature
SwingingBell(bState, when, EngineNo, TIUNo);
    --
engine must support this feature

Functions that present layout status or other information

RunTime();     -- return value is the Run Time in
    -- seconds since the script was started
TIUVersion();  -- return value is the version number of the last connected TIU
    -- typical values 5.3, 6.01, etc
CommStatus();  -- return 3 values giving the status of the two serial ports
    -- used by the RTC program
    -- usage: TIUSerialConnected, TIUConnected, RFIDSerialConnected = CommStatus();
RAM(address, Engine, TIU);    -- returns the byte at the RAM address given as address
    -- 0x0D  Engine number
    -- 0x23  Engine Type, 0x00 = Steam  0x05 or 0x85 = Diesel
    -- 0x24 and 0x25   16 bit status (ie ON/OFF) of lights and other engine features
DTO(Engine, TIU);    -- returns the Engine's trip odometer mileage (DTO) as a float


Functions that make script writing easier

Sleep(seconds);   -- causes the script to pause for the given number of seconds.
    -- Any commands which are queued up for execution will run as scheduled.

Functions that start up or shutdown engines

EmergencyStop();
    -- Sends an emergency stop command to all TIU

-- 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 button or switches will
-- move just as if someone activated the button
-- or switch
EngineStartUp(EngineNo, TIUNo);

EngineShutdown(EngineNo, TIUNo);
    -- EngineNo is between 1-99
LashUpStartUp(LashUpNo, TIUNo, LashUpEngineList);

LashUpShutdown(LashUpNo, TIUNo);
    -- LashUpNo is between 101-120
    -- LashUpEngineList : a string containing numeric values for Lead EngineNo,
    -- Middle EngineNos (if any), Trailing EngineNo. Add 128 to number if the
    -- engine is reverse running.

-- 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 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)
IsActive(EngineNo);  -- returns true if the engine is active (engines 1-99)




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

This script file follows the scheme developed for the Arduino. Three functions are required:

setup(Engine, TIU, debug)    is called once at the start, it is passed the engine number (1-99) and 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. Returns true on success, false on failure.
loop(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 four decoded fields from the tag packet received and the complete tag packet itself 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.
Returns true to continue accepting tags, false to quit accepting tags. Function main() is not used.

--OR--

main(Engine, TIU,  debug)    is used when the RFID tag system is not active. On the Setup window, the check box for [ ] Enable RFID must not be checked. Function loop() is not used.
cleanup(Engine, TIU, debug)     is called once when the user presses the [STOP] button on the Program Control window. Returns true on success, false on failure.

If you are running this without using the RFID tag readers, just put all of your code in the main() function.

The minimal script which just turns off the smoke and blows the horn :

Here is a example listing of a simple script

--[[---------------------------------------------------------------------------------------]]
-- Short comments start with two dashes
--[[
Long mulitline comments
start with two dashes and two left brackets.
They end with two right brackets. ]]

ON = true;
OFF = false;
NOW = 0 -- Seconds in the future to send the command, values 0 to 9,999 seconds
-- NOW is automatically set to zero at the start of main(), cleanup() and Functionxx()
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU, debug)
--
Smoke(OFF, NOW, Engine, TIU);
return true;
end
--[[---------------------------------------------------------------------------------------]]
function main(Engine, TIU, debug)
--
Whistle(ON, NOW, Engine, TIU);
Whistle(OFF, NOW+3, Engine, TIU);
return true;
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU, debug)
--
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. 

dofile(filename) is called twice in the script. This call reads in the script in the named file and processes it as if it were included here. Scripts using the RFID tag readers requires two other files : "defines.rcl" & "functions.rcl". They are shown below also. Note that the "functions.rcl" file is only needed if you are processing my RFID tag system.

PrintString(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. If you use the "defines.rcl" file (via dofile()), it redefines the print() function to be same as PrintString().

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 main() function. Each is run exactly once. 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 the example script:

Here is a listing of the Startup_shutdown_engine.rcl  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

dofile("defines.rcl");

NOW = 0 -- Seconds in the future to send the command, values 0 to 9,999 seconds
-- NOW is automatically set to zero at the start of main(), cleanup() and Functionxx()
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU, debug)
PrintString("setup()");

EngineNo = Engine; -- Engine number selected on the RTC Main window
MyTIUNo = TIU; -- TIU number selected on the RTC Main window
MyDebug = debug; -- debug level, values 0 (off) to 9 (maximum)
if (MyDebug > 0) then PrintString(string.format("Debug level %d", MyDebug)); end
-- initialize the layout
AC, IAC = Counts(MyTIUNo);
PrintString("TIU" .. MyTIUNo .. ": " .. AC .. " Active Engines - " .. IAC .. " Inactive Engines");
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function main(Engine, TIU, debug)
-- NOW is automatically set to zero at the start of main()
-- 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)
PrintString(string.format("main(): Engine %d TIU %d", Engine, TIU));
if (debug > 0) then PrintString(string.format("Debug level %d", debug)); end
-- EngineStartUp(EngineNo, MyTIUNo);
StartUp(NOW, EngineNo, MyTIUNo);
Whistle(ON, NOW+20, Engine, MyTIUNo);
Whistle(OFF, NOW+22, Engine, MyTIUNo);
PrintString(string.format("main() Sleep Started at %d", RunTime()));
if (not Sleep(5)) then return false; end -- return false if terminated
PrintString(string.format("main() Sleep(5) Complete at %d", RunTime()));
return true; -- false=main failed, true=main succeeded
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU, debug)
-- NOW is automatically set to zero at the start of cleanup()
-- this function is called once when the user presses the [STOP] button
--PrintString("cleanup()");
--EngineShutdown(EngineNo, MyTIUNo);
ShutDown(NOW, EngineNo, MyTIUNo);
PrintString(string.format("cleanup() Complete at %d", RunTime()));
-- NOP
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]
-- you can create up to 10 functions
--[[---------------------------------------------------------------------------------------]]
function NOP(Eng, TIU, debug)
-- NOW is automatically set to zero at the start of Function()
PrintString("NOP()");
PrintString(string.format("Function10() : Eng %d TIU %d debug %d", Eng, TIU, debug));
return true;
end
Function10Name, Function10Label = NOP, "NOP";
--[[---------------------------------------------------------------------------------------]]
function SW1200(Eng, TIU, debug)
-- NOW is automatically set to zero at the start of Function()
PrintString("SW1200()");
SW1200PFA(NOW, 4, MyTIUNo); -- only works with Engine 4, the SW1200
return true;
end
Function01Name, Function01Label = SW1200, "SW 1200 PFA";
--[[---------------------------------------------------------------------------------------]]
function GP7(Eng, TIU, debug)
-- NOW is automatically set to zero at the start of Function()
PrintString("GP7()");
GP7PFA(NOW, 7, MyTIUNo); -- only works with Engines 7 and 13, the GP7's
return true;
end
Function02Name, Function02Label = GP7, "GP7 PFA";
--[[---------------------------------------------------------------------------------------]]

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);
PrintString("GP7 PFA");
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);
PrintString("A&S SW1200 #1208 PFA");
return PFADelay;
end
--[[---------------------------------------------------------------------------------------]]




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 the Select Function drop down box and the [Run Function] button. There are two short cut buttons to run Function01 and Function02. These buttons can be pushed anytime but will not run until the main() function is complete or the loop() function has completed processing a tag.


Here is a example listing of the Helper Functions

--[[---------------------------------------------------------------------------------------]]
function BlowWhistle(Eng, TIU, debug)
-- NOW is automatically set to zero at the start of Function()
PrintString("BlowWhistle()");
PrintString(string.format("BlowWhistle(): Eng %d TIU %d debug %d", Eng, TIU, debug));
PrintString("BlowWhistle(): Eng " .. Eng .. " TIU " .. TIU .. " debug " .. debug);
Whistle(ON, NOW, Eng, TIU);
Whistle(OFF, NOW+1, Eng, TIU);
return true;
end
Function01Name, Function01Label = BlowWhistle, "Blow Whistle";
--[[---------------------------------------------------------------------------------------]]
function SimplePrint(Eng, TIU, debug)
-- NOW is automatically set to zero at the start of Function()
PrintString("SimplePrint()");
PrintString(string.format("SimplePrint(): Eng %d TIU %d debug %d", Eng, TIU, debug));
PrintString(string.format("NOW %d", NOW));
return true;
end
Function02Name, Function02Label = SimplePrint, "NOW";
-- you can create 10 functions which get hooked to the 10 button on the Program Control window
--[[---------------------------------------------------------------------------------------]]
function NOP(Eng, TIU, debug)
-- NOW is automatically set to zero at the start of Function()
PrintString("NOP()");
PrintString(string.format("NOP() : Eng %d TIU %d debug %d", Eng, TIU, debug));
return true;
end
Function10Name, Function10Label = NOP, "NOP";
--[[---------------------------------------------------------------------------------------]]






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. 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 loop() 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 Loop Demo:

Here is a listing of the runtrains.rcl 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 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.

dofile("defines.rcl");
dofile("functions.rcl");

EngineNo = 99
TIUNo = 99
NOW = 0 -- Seconds in the future to send the command, values 0 to 9,999 seconds
-- NOW is automatically set to zero at the start of setup(), cleanup() and Functionxx()
DETECTED = true
NOTDETECTED = false

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

-- 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
-- NOW is automatically set to zero at the start of setup()
PrintString("setup()");
EngineNo = Engine; -- Engine number selected on the Main window
MyTIUNo = TIU; -- TIU number selected on the Main window
MyDebug = debug; -- debug level, values 0 (off) to 9 (maximum)
if (MyDebug > 0) then PrintString(string.format("Debug level %d", MyDebug)); end
if (MyDebug > 0) then PrintString("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
-- all tables in Lua are 1 based, that is, T[1] is the first item in the list
EngineDetectionOrder = {0, 0, 0}; -- clear the tables
EnginesUsedNum = {0, 0, 0};
EnginesUsedFlag = {0, 0, 0};
Got = {0, 0, 0};
math.randomseed(os.time()); -- random seed
assert(true); -- debug only
-- initialize the layout

-- Start All Active Engines - 1-99 (DCS 2-100)
PrintString("Starting All Active Engines");
StartUpActiveEngines(MyTIUNo, 15);

for iEng = 1, 99 do -- check all possible engines on one TIU
if (IsEngineRunning(iEng, MyTIUNo)) then -- IsConsistRunning() REQUIRES use of StartUpActiveEngines() or EngineStartUp()
PrintString(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
-- volume
SetVolume(MASTER_VOLUME, 70, NOW, iEng, MyTIUNo);
-- Numberboards
Setting(CMD_NUMBERBOARDS_ON, NOW + 18, iEng, MyTIUNo); -- not a dedicated switch
-- Set smoke off
Smoke(OFF, NOW + 2, iEng, MyTIUNo);
-- Cab Chatter Off
CabChat(OFF, NOW + 3, iEng, MyTIUNo);
-- Set Marker lights on
Markers(ON, NOW + 15, iEng, MyTIUNo);
-- Set Acc/Dec rate to 1/1 Smph/sec
Rate(1, 1, NOW, iEng, MyTIUNo);
end
end
PrintString(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
PrintString(string.format("SetSpeed for Engine : %d", iEng));
SetSpeed(12, NOW + 20, iEng, MyTIUNo);
end

-- set State Machine to first state
SM = DETECTENGINES;
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function loop(Detector, Reader, EngineNo, TagLocation, CarType, TagPacket)
-- this function is called each time a tag is detected
-- NOW is automatically set to zero at the start of loop()
-- Line below shows a different way to quote a string using brackets
if (EngineNo > 0) then -- its an engine
PrintString(string.format([[loop() : Detector %d Reader %d EngineNo %d TagLocation %d CarType %d]],Detector, Reader, EngineNo, TagLocation, CarType));
else -- it's a car
PrintString(string.format([[loop() : Detector %d Reader %d CarType %d]],Detector, Reader, CarType));
end
-- The complete packet received is in TagPacket
-- (some of the most used fields have already been extracted into the first parameters to loop()
-- 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 PrintString(string.format("Packet %s", TagPacket)); end
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, NOW, 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
PrintString ("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, NOW + 3, EngineDetectionOrder[jDex], MyTIUNo);
SetSpeed(18, NOW + 6, EngineDetectionOrder[jDex], MyTIUNo);
SetSpeed(20, NOW + 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;
PrintString("Three Engines Detected");
else
SM = IDLE;
PrintString("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
PrintString(string.format("Engine # %d detected (Index : %d)", EngineNo, iEngineDetectionCount));
BumpCounter(ENGDET); -- 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, NOW + STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[1]) then SetSpeed(0, NOW + 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, NOW + STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[2]) then SetSpeed(0, NOW + 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, NOW + STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[3]) then SetSpeed(0, NOW + 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(NOW, 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) == 0) then -- Special sounds for SW1200 #1208 Engine #4
local iDly = SW1200PFA(NOW, 4, MyTIUNo);
if (iDly > PFADelay) then
PFADelay = iDly;
end
end
-- Counter to keep track of States
BumpCounter(UNDED); -- bump the Countr2 on the Program Control window

SetSpeed(15 + math.random(5), NOW + PFADelay + 8 + math.random(20), EngineDetectionOrder[1], MyTIUNo); -- @Detector 3 Reader 0
SetSpeed(15 + math.random(5), NOW + PFADelay + 8 + math.random(20), EngineDetectionOrder[2], MyTIUNo); -- @Detector 2 Reader 0
SetSpeed(15 + math.random(5), NOW + 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
Switch(DIVERGE, 1, 1, NOW + PFADelay + 8 + 35 + 20, 7, MyTIUNo); -- College East
Switch(DIVERGE, 1, 2, NOW + PFADelay + 8 + 35 + 23, 7, MyTIUNo); -- College West
Detector1Passenger = false;
else
-- set the switches to straight, Engine Number is not used but must be specified
Switch(STRAIGHT, 1, 1, NOW + PFADelay + 8 + 35 + 20, 7, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 2, NOW + PFADelay + 8 + 35 + 23, 7, 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, NOW + STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[1]) then SetSpeed(0, NOW + 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(NOW, 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, NOW + STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[2]) then SetSpeed(0, NOW + 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, NOW + STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[3]) then SetSpeed(0, NOW + 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(UNDED); -- bump the Countr2 on the Program Control window

SetSpeed(14 + math.random(5), NOW + PFADelay + 8 + math.random(20), EngineDetectionOrder[1], MyTIUNo); -- @Detector 1 Reader 0
SetSpeed(14 + math.random(5), NOW + PFADelay + 8 + math.random(20), EngineDetectionOrder[2], MyTIUNo); -- @Detector 3 Reader 0
SetSpeed(14 + math.random(5), NOW + 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
Switch(DIVERGE, 1, 1, NOW + PFADelay + 8 + 35 + 20, 7, MyTIUNo); -- College East
Switch(DIVERGE, 1, 2, NOW + PFADelay + 8 + 35 + 23, 7, MyTIUNo); -- College West
Detector1Passenger = false;
else
-- set the switches to straight, Engine Number is not used but must be specified
Switch(STRAIGHT, 1, 1, NOW + PFADelay + 8 + 35 + 20, 7, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 2, NOW + PFADelay + 8 + 35 + 23, 7, 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, NOW + STOPDELAY, EngineDetectionOrder[1], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[1]) then SetSpeed(0, NOW + 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, NOW + STOPDELAY, EngineDetectionOrder[2], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[2]) then SetSpeed(0, NOW + 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(NOW, 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, NOW + STOPDELAY, EngineDetectionOrder[3], MyTIUNo); end -- already detected set Throttle to 0
if (not Got[3]) then SetSpeed(0, NOW + 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(UNDED); -- bump the Countr2 on the Program Control window
SetSpeed(12 + math.random(5), NOW + PFADelay + 8 + math.random(20), EngineDetectionOrder[1], MyTIUNo); -- @Detector 2 Reader 0
SetSpeed(12 + math.random(5), NOW + PFADelay + 8 + math.random(20), EngineDetectionOrder[2], MyTIUNo); -- @Detector 1 Reader 0
SetSpeed(12 + math.random(5), NOW + 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
Switch(DIVERGE, 1, 1, NOW + PFADelay + 8 + 35 + 20, 7, MyTIUNo); -- College East
Switch(DIVERGE, 1, 2, NOW + PFADelay + 8 + 35 + 23, 7, MyTIUNo); -- College West
Detector1Passenger = false;
else
-- set the switches to straight, Engine Number is not used but must be specified
Switch(STRAIGHT, 1, 1, NOW + PFADelay + 8 + 35 + 20, 7, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 2, NOW + PFADelay + 8 + 35 + 23, 7, 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
-- NOW is automatically set to zero at the start of cleanup()
PrintString("cleanup()");
PrintString("Shutting Down Active Engines");
ShutDownActiveEngines(MyTIUNo);
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);
PrintString("GP7 PFA");
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);
PrintString("A&S SW1200 #1208 PFA");
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(6);
if (switchval == 1) then
Starttime = NOW + 30 + math.random(10);
Endtime = Starttime + 8 + math.random(10);
Bell(ON, Starttime, iEngineNo, iTIUNo); -- send bell on
Bell(OFF, Endtime, iEngineNo, iTIUNo); -- send bell off
PrintString("RandomAction: Bell");
end
if (switchval == 2) then
Starttime = NOW + 25 + math.random(10);
Endtime = Starttime + 5 + math.random(10);
Whistle(ON, Starttime, iEngineNo, iTIUNo); -- send Whistle/Horn on
Whistle(OFF, Endtime, iEngineNo, iTIUNo); -- send Whistle/Horn off
PrintString("RandomAction: Horn/Whistle");
end
if (switchval == 3) then
Starttime = NOW + 25 + math.random(11);
PlaySound(42, Starttime, iEngineNo, iTIUNo); -- SXS Toot
PrintString("RandomAction: SXS");
end
if (switchval == 4) then
Starttime = NOW + 25 + math.random(10);
Endtime = Starttime + 15;
Smoke(ON, Starttime, iEngineNo, iTIUNo); -- send Smoke on
Smoke(OFF, Endtime, iEngineNo, iTIUNo); -- send Smoke off
PrintString("RandomAction: Smoke");
end
if (switchval == 5) then
Starttime = NOW + math.random(10);
Endtime = Starttime + 60;
CabChat(ON, Starttime, iEngineNo, iTIUNo); -- set Cab Chatter on
CabChat(OFF, Endtime, iEngineNo, iTIUNo); -- set Cab Chatter off
PrintString("RandomAction: Cab Chatter");
end
if (switchval == 6) then
-- No sounds some of the time
PrintString("RandomAction: Quiet");
end
return retv;
end
--[[---------------------------------------------------------------------------------------]]

function SW1200(Eng, TIU, debug)
-- NOW is automatically set to zero at the start of Functionxx()
PrintString("SW1200()");
SW1200PFA(NOW, 4, TIU); -- only works with Engine 4, the SW1200
return true;
end
Function01Name, Function01Label = SW1200, "SW 1200 PFA";
--[[---------------------------------------------------------------------------------------]]
function GP7(Eng, TIU, debug)
-- NOW is automatically set to zero at the start of Functionxx()
PrintString("GP7()");
GP7PFA(NOW, 7, TIU); -- only works with Engines 7 and 13, the GP7's
return true;
end
Function02Name, Function02Label = GP7, "GP7 PFA";
--[[---------------------------------------------------------------------------------------]]



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). Calls StartUpActiveEngines() to start 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

loop()    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 loop() 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.rcl

defines.rcl contains constants used the main script.


Here is a listing of the defines.rcl 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
-- 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"
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
ENGDET = 0 -- Engines Detected Counter
UNDED = 1 -- undedicated Counter
AGSUB = 2 -- Number of Tags using substitution
UNIQTAG = 3 -- Unique Tag Counter
TAGCNT = 4 -- Total Tag Counter
DLCNT = 5 -- Dispatch List Entry Counter
--[[---------------------------------------------------------------------------------------]]





functions.rcl

functions.rcl 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.rcl 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 loop()

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

dofile("defines.rcl");

NOW = 0 -- Seconds in the future to send the command, values 0 to 9,999 seconds
-- NOW is automatically set to zero at the start of main(), cleanup() and Functionxx()
--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU, debug)
PrintString("setup()");
MyTIUNo = TIU;
-- switches and accessories
Switch(STRAIGHT, 1, 2, NOW + 3, EngineNo, MyTIUNo); -- College East
Switch(STRAIGHT, 1, 3, NOW + 5, EngineNo, MyTIUNo); -- College West
return true; -- false=setup failed, true=setup succeeded
end
--[[---------------------------------------------------------------------------------------]]
function main(Engine, TIU, debug)
PrintString("main()");
MyTIUNo = TIU;

LashUpEngineList = {};
LashUpEngineList[1] = 1; -- head engine
LashUpEngineList[2] = 5; -- middle engine(s)
LashUpEngineList[3] = 13; -- tail engine
PrintString(string.format("Table %02X-%02X", LashUpEngineList[1], LashUpEngineList[2]));

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

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

return true; -- false=main failed, true=main succeeded
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU, debug)
-- NOW is automatically set to zero at the start of cleanup()
-- this function is called once when the user presses the [STOP] button
ShutDown(NOW, 101, MyTIUNo);
return true; -- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]




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.X beta)  of the RTC program. I don't have it up on the web site yet (needs more testing), again, send me an email for a beta copy.


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.