YetAnotherAutoTrain.py -- Data driven automatic trains

Jython (Java based Python) scripting is a common tool for running trains automatically. The Python language is relatively easy to use and provides easy access to JMRI facilities, such as turnouts, sensors, blocks, etc. Access to JMRI throttles provides the ability to control locomotives.

The typical script sets turnouts, acquires a locomotive, runs the locomotive while keeping track of its location. Some scripts use occupancy sensors for the location, others rely on time durations.

Frequently the original script needs modifications as additional details are added. When a second train is added which has to share track with the first one, the script becomes ever more complicated.

YAAT is designed to eliminate the programming by using text files that contain English like phrases. For example:

      sensor = sensors.getSensor('S-Sensor')
      If sensor is not None:
          sensor.setKnownState(ACTIVE)
      
vs.
      Set sensor S-Sensor active
      

Each train has its own text file, but they can coordinate actions by using JMRI sensors, etc.

Using YAAT

YAAT works by referencing JMRI objects, such as turnouts, sensors, etc. This means that the layout definition needs to accurately describe the layout that will run the trains that YAAT will be controlling.

Some of the YAAT actions have specific requirements. The signal head and signal mast actions are obvious. The block actions need to have blocks assigned to Layout Editor track components, such as track segments and turnouts.

Since the YetAnotherAutoTrain.py script needs to be modified, the script should be copied from the JMRI install location to the user files location. See Help ⇒ Locations.

Global Settings

logLevel
YAAT sends logging information to either the JMRI system console or the script monitor located at Panels ⇒ Script Output. Zero is no logging, 4 provides the maximum detail.
statusSensor
Optional. Provide feedback to JMRI indicating that one or more train threads are running.
masterSensor
Optional. If this sensor becomes active, YAAT will terminate all of the train threads. NOTE: Trains might keep running at their last speed.
saveYAATcompiles
If set to True, the train compiles will be retained. See Compiled Trains.

Trains

Each train needs a set of actions. The orignal design had the actions included in the script file. That feature will be removed in a future release.

Define actions:

External File
Create a text file with one action per line. Blank lines and lines starting with a comment character, #, are ok. Add the train name and file name to the trainList. The file name can be the complete path or the file name can include a keyword for the location, such as "preference:" which is replaced by the path to the user files location at run time.
File of files
In addition to modifying the script's trainList, it is possible to create a text file that has the same format as the trainList. If the file exists, its contents will be added to the trainList. The file is located in the yaat directory in the user files location. The filename is TrainList.txt. This file is optional.
Embedded
The actions are added to a Python list. Each action is enclosed in single or double quotes and end with a comma at the end of the line. The embedded method requires a unique block of code at the end of the script for each embedded action list. Note: This method will be removed in a future release. It requires too much manual code modification.

Action Phrases

The following descriptions used the following formatting:

Standard Actions

Assign <long | short> address <dccaddr>[ as <train name>[ in <block name>]]
Create a JMRI throttle using the DCC addresss. If a block name is supplied, the optional train name will be used for block tracking.
Loop
Marks the end of the one time start up actions, such as the throttle assignment, positioning a train before starting repeating actions, etc.
Print <message text>
Display a message in the script output window or the system console log. Useful for debugging.
Repeat if sensor <sensor name> is <active | inactive>
Skip the remaining steps and start over. Use the same sensor as Stop with the opposite test. This provides a cleanup section before Stop. Requires that a "Loop" action was included in the action list.
Set block <block name> <occupied | unoccupied | reserved | free>
The occupied and unoccupied states are used to simulate train movement. It works best if a simulation sensor is used in conjunction with an If statement. Reserved and free control the alternate track color.
Set direction to <forward | reverse>
Set function key <0 to 28> <on | off>[, wait <n> seconds]
Set the function key on or off. The number can be from 0 to 28. If seconds is greater than zero, the opposite action will be performed after the number of seconds has passed.
Set route <route name>
Set sensor <sensor name> <active | inactive>
Can be used to pass status to other trains.
Set speed to <0 to 1.0>
Set turnout <turnout name> <closed | thrown>[, wait <n> seconds]
The process will wait for up to 5 seconds for turnout feedback. If seconds is entered and greater than zero, a wait allows the turnout command to complete, capacitors to recharge, etc.
Start when sensor <sensor name> is <active | inactive>
An optional action that defers running the train until the condition has been satisfied. This can also be used to pause a train between runs.
Stop if sensor <sensor name> is <active | inactive>
This action needs to be the last one in the list. If the sensor state matches, the throttle will be released and the script stopped. If it does not, the script will do the sequence of actions again. If the Stop action is missing, the script will run forever, until the script thread is killed, or JMRI is stopped.
Wait for <n> seconds
Wait until the time has expired. Normally used for station stops.
Wait for block <block name> to become <occupied | unoccupied | reserved | free>
Wait for sensor <sensor name> to become <active | inactive>
Wait for signal head <head name> to [not] show <appearance name> [or ...]
The appearance names are language specific. Use the signal head table to get the available appearance names.
Wait for signal mast <mast name> to [not] display <aspect name> [or ...]
Use the signal mast table to get the valid aspect names. Remember that the names vary based on signal mast type.
Wait while signal mast <mast name> speed is less than <aspect name> speed
Use the signal mast table to get the valid aspect names. Remember that the names vary based on signal mast type.

If/Else/Endif Actions

The If and Endif actions are required. The Else action is optional and is used to separate the true and false actions. Nesting is supported.

If block <block name> is <occupied | unoccupied | reserved | free>
If sensor <sensor name> is <active | inactive>
If signal head <head name> does [not] show <appearance> [or ...]
If signal mast <mast name> does [not] display <aspect> [or ...]
If speed for signal mast <mast name> is <eq | ne | lt | gt | le | ge> <speed name>
For information on speed names, look at the JMRI install location: xml/signals/signalSpeeds.xml
Else
Endif

GoSub/Sub/EndSub Actions

The sub routines are placed at the end of an embedded list or text file. The sub routines cannot be nested, but a sub routine can call another sub routine. The sub routine name cannot have spaces. Control returns to the statement after the CallSub when the sub routine is finished.

CallSub <subname>
Sub <subname>
EndSub <subname>

Dispatcher Support (Created by Bill Fitch)

Dispatch using file <traininfo.xml>[, type <USER, value <dccAddress> | ROSTER, value <roster entry name> | OPERATIONS, value <train name>>]
The Dispatcher train info filename is required. The optional USER, ROSTER and OPERATIONS keywords can override the train info content. Examples:
  • Dispatch using file routefrom1-2.xml (uses the train info xml file set up by "Save Train info" in "Create New Train" in dispatcher)
  • Dispatch using file routefrom1-2.xml, type USER, value 3 (uses train with dcc address 3 instead of the train in the xml file
  • Dispatch using file routefrom1-2.xml, type ROSTER, value diesel104 (uses diesel104 from roster instead of the train in the xml file)
The <traininfo.xml> file will have been generated by dispatcher prior to running YAAT and will have been placed in preference:dispatcher/traininfo by dispatcher. Note: "preference:" is the keyword for the user files location.

YAAT Demo

A demonstration system is available at YAAT Demo. The zip file is a JMRI profile. After expanding the zip file, place the YAAT.jmri directory next to the other profiles and it should show up in the profile list. The demo panel is based on JMRI 4.20.

Running the demo

After starting PanelPro, click on the Open Panel File button and select yaat demo.xml.

YAAT demo

Click on the Load YAAT button to start the process. After several seconds the button label will change to Running. Click on the Paused button to start a train. The button label will change to Run. Cllcking again will pause the train when it finishes a loop. Click on Stop to terimnate the train and remove it from the system.

The Stop Threads button will terminate all trains immediately. Active trains might continue running at the last throttle setting.

Advanced Features

Compiled Trains

The standard YAAT process compiles each train from its text file or from the embedded content. THe result of the compile process is a set of tokens that are interpreted while the train is running. The duration of the compile step depends on the number of trains and the number of actions.

If the saveYAATcompiles option is true, a compiled train will be used instead of doing the compile step.

A compiled train is located at the preference:yaatp/ directory. When YAAT is started, the date/time for each train source file is checked. If it is greater than the compiled train, if any, the compile will occur. Otherwise the compiled version will be loaded. Embedded trains are always compiled.

Custom Extensions

An extension is a separate Python file that contains additional actions.

Each action requires a do<name> method (def) and a compile<name> method (def). The format of an action name is verb_noun, such as Set_memory... The typical space for built-in actions is replaced with an underscore.

The custom actions are added to the customExtensions dictionary. The key is the file name and the data is a list of actions.