1- Automation Overview

Before digging in the details of how the automation software is implemented, this section is mostly educational and gives some background on the kind of equipment selected for this automation project.

Automating a model train layout requires three different aspects: sensors to detect the train, control of turnouts, and a way to connect all these to a computer running the control software. Here's a schematic overview of the system used on this layout:

The core of the automation is a Linux computer running JMRI, which interfaces with the NCE command station. Detectors are connected to the block panel to monitor which blocks draw power. DCC accessory decoders control the turnouts. JMRI provides a way to read data from the block sensors and control turnouts.

The system is not using any of the routes and logix modules of JMRI. Their system is in my opinion fairly limited in capabilities, not very flexible, yet at the same time extremely complex and obscure to set up that the benefit offered. Their UI model is a click-o-drome where everything is configured using a confusing bazillion of poorly design dialog boxes and it’s fairly impossible to get an overview at a glance of the programmed system.

Instead, as explained below, Conductor is programmed using a single text file that describes all the events and actions in a very simple manner using a rich and descriptive custom programming language. Those familiar with Grafcet and ladder logic as used in industrial programmable logic controllers (PLC) should feel right at home.

One of the main goal of the changes made to the layout is to be actually as unobtrusive as possible. Most notably, the layout must be able to function in DCC without the aid of the computer (e.g. in case of malfunction).

We don’t use Railcom and the system does not know which locomotive is on a given block.

Such block detection only detects engines (which motors draw power from the track) and lighted cars. They do not detect cars which are electrically isolated from the track. This is not a problem in the automation we are currently running but it’s something everyone who has to deal with block detection must keep in mind -- for example how does one detect that part of a train has detached.

The typical work-around for this is to add resistors on cars’ trucks.

Originally, the NCE Switch-8 was chosen because it can control the Tortoise turnouts from the mainline and due to its advertised capability of being driven by non-momentary toggles.

It so turned out that I quickly realized not all the mainline turnouts had been converted to Tortoise. There are still a number of old Fulgurex turnout motors on the layout. As explained on this page http://ralf.alfray.com/trains/nce_fulgurex.html, this can be solved by extra relays or DS64.

Finally, due to cost and time constraints, not all the blocks and not all the turnouts are equipped with sensors and DCC accessory control. A strategic number of them have been equipped as required by the first phase of the automation, with the goal to add more later as the system gets developed.

There are two automated routes with two different trains. Each route works in a similar principle: a train is stopped and waiting at a starting point. When an activation button is pressed, the train moves up the route till another specific point where it stops and reverse course towards the starting point. This is a point-to-point back-and-forth travel.

I do get a few comments that this is a “simple” point-to-point automation. It’s true that this kind of “shuttle” scheme has been done repeatedly in DC using relays and there are readily available commercial modules doing just that.

However my plan all along was to offer more than just a shuttle-type automation. Eventually what I want is to do route management where a given train runs along a predetermined route and only enters it when it is safe to enter. For example right now we have a single passenger train on the mainline. What I envision later is multiple trains on the mainline, waiting for critical sections of the routes being free and automatically stopping and using sidings to pass each other.

Even with the current “shuttle” automation design, the flexible automation has proven useful to automate tasks beyond pure animation -- see section 3 below about the Conductor Event Language for an example.

The current system is just a first incarnation and as such still has some early limitations. Still I think it offers a fairly good flexibility.

2- Conductor

As explained above, the automation is controlled by a Linux computer running JMRI, a custom Java program named “Conductor” and an Android application named “RTAC”, the “Randall Train Automation Controller”. I built Conductor and RTAC specifically for the Randall automation, yet they should be flexible enough to be reused for other train automation projects.

Features of Conductor:

Conductor is a Java app that integrates with JMRI.

It has access to the turnouts, sensors and throttles defined in JMRI.

Conductor is controlled using an automation script with a custom language that I developed for that purpose.

This allows the automation to be easily modified without recompiling the program.

That screenshot looks complex and probably intimidating.

Luckily, as I explain later, the museum staff will not have to interact with it.

The Conductor app is invoked from JMRI using its Jython extension bridge. Conductor’s main role is to run a script that drives the automation. It uses an event-based language I created for that purpose that updates turnouts and engines in reaction to a combination of sensors and timer inputs. Sensors can be either activation buttons or track occupancy sensors defined in JMRI. Depending on the state of the automation and the location of the engines, the script can change their speed, change the lights, blow the horn, set turnouts as required. It is essentially a fully automated DCC cab.

The Randall museum uses an NCE command station and boosters. Sensors are detected using NCE AIU01 modules. Turnouts are controlled using NCE Switch-8 or Digitrax DS64 modules. Since Conductor interfaces with JMRI, it is not specially tied to NCE hardware. It can interface with any throttle, sensor or turnout that can be controlled via JMRI.

One key feature of the automation script is that it is both timer-based and sensor-based. Model train motors are analog and their speed and running is not precise enough for automation. The only way for an automation to be reliable is to physically know the location of the train on the track, which is typically done using block occupancy sensors. The track is divided in blocks and electrical sensors detect when a train enters and leaves specific blocks. These events are precise as they are directly related to specific locations on the track that are known and never change. Such location events are ideal to control the speed of the train, to tell it to start, accelerate, stop, etc. Timer events are good enough for less critical tasks like blowing the train horns. Usually both techniques are combined, which results in excellent control, for example a train can be set to stop after entering a block with a delay so that it actually stops in front of a station when taking momentum into account and can then leave after a specific time.

For ease of developing and testing, the Conductor app also has a second custom language to simulate the automation. This simulates the progression of the train through the layout, simulating the activation of track occupancy sensors as if the actual engines were moving along.

Here’s an example of the event-based script on the left and the simulation script on the right:

In this example the piece of script on the left above indicates that when the train is stopped on block B503b (the main passenger station) and the activation button is pressed, turn on the train sound & lights and start moving at a reduced station speed. After 2 seconds, blow the horn. Once the train reaches block B503a, accelerate to full speed.

On the right side, the simulation script waits for the same activation button to be pressed and once pressed simulates the train moving from block B503b to B503a after 8 seconds. The simulation script can then be used to test the automation script even when the computer is not connected to the layout and no train is actually moving. Once the simulation is perfected, the system can be connected to the layout to try with actual trains.

3- Conductor Event Language

The event language uses a simple “conditions → actions” model. The script engine evaluates all instructions in parallel in a 30 Hz loop and then executes instructions when their conditions match. This is directly inspired by Grafcets and ladder logic as used in industrial programmable logic controllers.

Here’s an example of an event script:

Sensor B320 = NS784

Sensor B321 = NS785

Sensor B330 = NS786

Turnout T330 = NT330

!PA-Toggle & !B330 & B320 -> T330 Normal

!PA-Toggle & !B330 & B321 -> T330 Reverse

It’s important to remember that all the “conditions → actions” lines are evaluated in parallel, all at the same time. Once they have all been evaluated, the actions will be carried out in the order they are defined in the script.

The “Sensor” line registers a sensor, which must be declared in JMRI. “NS784” is a JMRI sensor for an NCE AIU01 on address 50, bit 1. On a LocoNet bus, a similar line would look like:

Sensor NAME = LS01

The sensor name is used in the script and is independent from the sensor “username” used in JMRI.

Similarly, the “Turnout” line defines a turnout which must have been declared in JMRI. In this case, “NT330” is a turnout a DCC address 330 on an NCE bus in JMRI.

Turnout T330 = NT330

In JMRI these are the “system names” and they are found in the Sensors and Turnouts tables.

Below are actual conditions → actions lines.

!B330 & B320 & !B321 -> T330 Normal

!B330 & B321 & !B320 -> T330 Reverse

These two lines mean that:

If the Block 330 is off, and block Block 320 turns on, then set the turnout T330 to Normal.

Similarly, if the Block 330 is off, and Block 321 turns on, then set the turnout T330 to Reverse.

If both blocks 320 and 321 turn on, the program does not alter the turnout.

On the Randall layout, this corresponds to this piece of track:

On the Randall layout, operators typically run from right to left on this section of the track. A very very common error is for operators to forget to align their turnouts and then be surprised when their train shorts or derail. These 2 event lines fix that: when a train is on block 320 (right) and block 330 after the turnout is empty, the Sonora turnout can be automatically aligned towards the incoming train on 320. Same pattern for a train coming from 321.

This shows how the script does not have to deal just with the “animation” of the layout. It can also be used to automate parts of the layout that have simple usage patterns.

Let’s take another example, which this time is a simplified version the Passenger Automation train as can be seen in the video above:

# Define which DCC engine to control

Throttle Loco = 204

# Define various states for the automation

Enum State = Idle Station Up Summit Down

# Define some speeds to use

Int Station-Speed = 8

Int Summit-Speed = 12

# Define some timers

Timer Timer-Acquire-Route = 1

Timer Timer-Release-Route = 1

# --- Train starts in "Idle" state.

# Once the main automation toggle is turned on, turn on the sound and light,

# and set the train in the "Station" state.

State == Idle & Toggle ->

Loco Sound = 1;

Loco Light = 1;

Loco Stop ;

State = Station

# --- State: Station

# Departure from Station (going up) when users press the activation button

State == Station & B503b & Toggle & Loco Stopped & Run3 ->

State = Up ;

Reset Timers

# --- State: Going Up

# Start the train at station speed and align all turnouts as desired.

State == Up & B503b & Loco Stopped ->

Loco Light = 1 ;

Loco Sound = 1 ;

Loco Forward = Station-Speed ;

Loco Horn ;

Timer-Acquire-Route Start

Timer-Acquire-Route ->

T311 Reverse;

T320 Reverse ;

T321 Normal ;

T330 Normal

# Once we reach the next block, go to full track speed

State == Up & B503a -> Loco Forward = Full-Speed

# Mid-Station doppler sound on the way up

State == Up & B320 + 27 -> Loco F3 = 1 ; Loco F3 = 0

# Reduce speed when reaching the Sonora bridge

# Speed up again after the tunnel on the way up

State == Up & B330 -> Loco Forward = Sonora-Speed

State == Up & B330 + 12 -> Loco Forward = Full-Speed ; Loco Horn

# Reaching the Summit, change the state of the automation

State == Up & B360 -> State = Summit

# --- State: Summit

# At the top first reduce a bit the speed

State == Summit & B370 + 9 -> Loco Forward = Summit-Speed

# Then stop and after a delay and a few horn effects, reverse direction.

State == Summit & B370 + 14 ->

Loco Stop ;

Loco Horn

State == Summit & B370 + 22 ->

Loco Horn;

Loco Reverse = Summit-Speed

# Once the train reaches the next block, change to the "going down" state of the automation

# and make sure all turnouts are still aligned as desired.

State == Summit & !B370 & B360 & Loco Reverse ->

State = Down ;

Timer-Acquire-Route Start

...

There are a few interesting things to point out here.

First, this line defines which DCC address (a.k.a. throttle) to use:

Throttle Loco = 204

Many throttles can be defined, each with a specific name. Each throttle represent one or more DCC addresses, so it’s easy to create “soft consists”.

These are then used in the script as actions to stop the engine, change the speed or act on functions. For example the following line defines multiple actions to be done on that DCC address when the conditions are met, namely turn the lights on (F0), set the sound (F8), set the seed to forward, blow the horn (F2) and turn on function F3:

conditions ->

Loco Light = 1 ;

Loco Sound = 1 ;

Loco Forward = Station-Speed ;

Loco Horn ;

Loco F3 = 1

The speed can be set using direct values or using named integer constants. The advantage of using the named constants is that they can be easily changed in a script and cary their own semantics:

Throttle Loco = 204

Int Diverging-Speed = 8

Int Full-Speed = 24

condition-1 -> Loco Forward = Full-Speed

condition-2 -> Loco Forward = 28

condition-3 -> Loco Reverse = Diverging-Speed

condition-4 -> Loco Stop

The engine’s direction can be used as a condition, so it’s easy to have different behavior when the engines are going forward or reverse; for example the following lines would set different speed on a given block depending on the direction of the engine, or would blow the horn when it stops:

Block-1 & Loco Forward -> Loco Forward = Full-Speed

Block-1 & Loco Reverse -> Loco Reverse = Diverging-Speed

Block-1 & Loco Stopped -> Loco Horn

An integral part of the automation is having actions vary with time. For this, timers can be defined with a predetermined time in seconds. An action starts a timer and when the timer goes off, it can execute one or more actions. Example:

Since a lot of the program has to deal with performing an action a number of seconds after a train enters a specific block, there is a special shortcut syntax for this:

Int Diverging-Speed = 8

Block-1 + 2 & Loco Forward -> Loco Horn

Block-1 + 4 & Loco Forward -> Loco Forward = Diverging-Speed

Both example above do the same thing: the train enters block-1 and this starts a timer. 2 seconds in the block the horn is blown, and 4 seconds in the block, the speed is changed. However these actions only happen if the train is moving forward when the timer expires, so there could be a different behavior if the train is going in reverse or stopped.

Finally, an important part of the automation program is defining “states”. States are free-form keywords defined for whatever makes sense in the current program. For example the Randall Passenger automation has states for the train being idle (automation is turned off), waiting at the station, going up, reaching the summit and then going down. This allows the program to have easily have different behaviors depending on the state of the automation.

In this made-up example, the first line indicates if the trains is in state “station” and on the station block, then pushing the automation button starts the train and changes it to the “going up” state. This makes the program much easier to understand and follow.

The states can then be used to differentiate the behavior of the train on a given block depending on whether it’s going in one direction or another. This is similar to using the engine’s direction, only with an easier to understand semantic.

4- RTAC

Once the system is installed on the Randall layout, the staff in charge does not have to access the computer. It is essentially running as a “headless” computer hidden away under the layout, set to automatically turn on when the layout’s power is turned on and then to automatically shutdown when the layout power is turned off.

To be able to easily interact and monitor the state of the automation, two tablets are provided that run a custom Android app named “RTAC”, which is the visible part of the “Randall Train Automation Controller”.

Features of RTAC:

RTAC is a custom Android app that runs on two tablets.

It’s synchronized with Conductor to display the automation status and the track occupancy.

RTAC automatically connects to the Conductor server using the Zeroconf protocol.

RTAC features an “Emergency Stop” which users can use to stop the automation, for example if a train derails. Once the Conductor software goes in emergency stop, the tablet can be used to reset the system with some clear indication on screen of what to do.

Both tablets are synchronized so that the display is the same on both and either can be used to trigger the emergency stop or reset the automation.

The Conductor app is also a server. It communicates with the two tablets to display the status of the automation and the track occupancy. The tablets are connected over wifi with the same private network as the computer driving the automation. The Conductor app and the tablets find each other using Zeroconf; no configuration is needed on the tablet side.

The tablet software is designed to operate in “kiosk mode” meaning that as soon as the tablet is turned on, the RTAC software takes over and prevents users from doing anything else with the tablet. Normally only the museum staff should have access to these tablets, and they should not be able to close or dismiss the app by mistake.

Pressing the “E-Stop” (Emergency Stop) button on either tablet asks for a confirmation dialog. Once the staff confirms the order, the Conductor app stops the train immediately using the NCE/DCC emergency stop command (which means trains stop immediately, regardless of their decelerating momentum). The staff is then instructed to bring back the trains to their expected origin location and then reset the automation.

Although Conductor and RTAC are specific to the Randall Museum's layout for now, they are designed to be flexible and be reused to automate other model train layouts. The source is available here; it is a standard Android application built using Android Studio and Gradle; it uses Dagger, Mockito, Robolectric, JSON; it relies on my own custom RX library, and my own KV network protocol. Please contact me if interested in details.