Blog & News

About the Model Railroad

RTAC Software


Randall Train Automation Controller:

“Conductor” and “RTAC” software


Starting in 2016, I worked on automating the Randall Museum's model railroad, built by the Golden Gate Model Railroad Club.

The model railroad is an old HO DC model railroad that has been converted to DCC using an NCE command station and boosters. I wrote a fairly extensive description of the layout, which can be found here:

The mainline turnouts are controlled using rotary toggles (non-momentary) that are directly connected to their corresponding Tortoise or PFM Fulgurex slow-motion turnout motors, as I explained here.

The model railroad is fairly large, and to get started we decided to only automate two parts of the layout: there are two specific automated routes, called the “passenger” and “branchline” routes.

Video of the Passenger Automation

Video of the Branchline Automation

I proposed several other automated lines and as this initial phase works well, we might expand it later.

The automation is designed to be used by the museum staff and consequently the whole system is designed to be as seamless as possible.

1- Automation Overview

2- Conductor

3- Conductor Event Language


1- Automation Overview

Before digging into 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 railroad 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 for the benefit offered. Their UI model is a click-o-drome where everything is configured using a confusing bazillion of poorly designed dialog boxes and it’s fairly impossible to get an overview at a glance of the programmed system.

Instead 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. The language design was inspired by my experience with ladder logic  (such as Grafcet) as used in industrial programmable logic controllers.

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

This design and the choice made imply there are some compromises:

  • Block detection is limited to pure basic “something is drawing power” detection.
  • 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 the page NCE Switch-8 and PFM Fulgurex, this can be solved by extra relays or DS64.
    • It also turned out that the NCE Switch-8 did not actually work with non-momentary toggles, which was solved by reprogramming them as explained in NCE Button Board and non-momentary contacts.
  • 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 reverses 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. This kind of “shuttle” scheme has indeed been done repeatedly in DC using relays and there are readily available commercial modules doing just that.

The system I created allows us to do much more than just run the trains -- we have the same fine control over DCC functions as operators do. The trains are programmed to accelerate and decelerate depending on where they are in their route, as well as use all the DCC functions such as lights, bell, and horn. Moreover, speed and functions can be easily customized for each specific engine, a feature very useful to us as we routinely rotate our trains every few months to avoid excessive tear.

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

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 can run on all platforms supported by JMRI (Windows, Mac, and Linux).
  • 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.
  • The script responds to sensor events and controls DCC turnouts and DCC engines via JMRI.
  • This allows the automation to be easily modified without recompiling the program.

That screenshot looks complex and probably intimidating. This is only seen on the server, and end-users (layout operators, museum staff)  do 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 into 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 ladder logic as used in industrial programmable logic controllers (such as Grafcet).

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 at 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 B330 is off, and block B320 turns on, then set the turnout T330 to Normal.
  • Similarly, if the block B330 is off, and block B321 turns on, then set the turnout T330 to Reverse.
  • If both blocks B320 and B321 turn on, the program does not alter the turnout.

On the Randall Model Railroad, this corresponds to this piece of track:

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

This shows how the script does not have to deal with just 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:

Timer Delayed-Horn = 2

Timer Delayed-Speed-Reduction = 4

Int Diverging-Speed = 8


Block-1 & Loco Forward -> Delayed-Horn Start ; Delayed-Speed-Reduction Start


Delayed-Horn --> Loco Horn

Delayed-Speed-Reduction & Loco Forward -> Loco Forward = Diverging-Speed

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 examples 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 easily have different behaviors depending on the state of the automation.

Enum State = Idle Station Up Summit Down


State == Station & Block-Station & Button-Pushed & Loco Stopped -> State = Up; Loco Forward = 8

State == Up   & Block-1 -> do something

State == Down & Block-1 -> do something else

In this made-up example, the first line indicates if the train 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.


Once the system is installed on the Randall Model Railroad, 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 model railroad 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.


[Back to main page]