What This Is All About: Getting Lots of Bits Out of JMRI
JMRI is a free software application that runs on Windows, Linux or a MAC that can perform a multitude of functions. It makes programming complex DCC decoders much easier. It also is used for dispatching, signaling, switching, route control, train control, interfacing to WiFi controls and cell phone throttles, and more. Some of these applications need to throw switches – yes, track switches, but more than that too. They could turn on a light or motor or animation. It can couple a fast clock’s time to sequence town and building lights. It could turn on remote panel lamps to indicate train position or block occupancy. These pieces of data or information need to get “out” of JMRI to make them useful. This article describes a low cost mechanism to make this happen.
We are going to focus on the Turnout Table in JMRI because it usually represents two states per turnout – Open and Thrown. We will take up to 68 table entries at a time and connect them to the 68 respective pins on an Arduino Mega2560. From there, you are on your own as far as this article goes. A quick survey of the literature or even MRH itself will lead you to a multitude of control and display possibilities for your imagination. It will also automatically initialize all the turnouts you have set up, upon connection to JMRI. The hardware connection can be made with most any off-the-shelf Arduino board (unmodified). The Arduino Mega2560 will support up to 68 turnouts, an Arduino Uno, Pro Mini, or Nano will support up to 18 outputs.
These will connect via any USB port, to your computer running JMRI. This will be the same USB serial port you use to load the Arduino board. I’ve written several times before about installing and loading Arduino software: in MRH December 2016, A modeler’s introduction to the Arduino http://mrhpub.com/2016-12-dec/online/ and here: in MRH March 2017, DCC projects using the Arduino http://mrhpub.com/2017-03-mar/online/html5/
A script (Turnout_Table_Build.py) is included that you can run in JMRI to set up most any size “Turnout Table” you need, with the appropriate naming convention for these channels. All JMRI scripts and Arduino sketches can be downloaded from here:
http://mrhpub.com/files/users/geoffbfiles/JMRI_Channels_Rev2.zip
This is a companion article to this: SMA28 JMRI Sensor Channels – Direct Arduino to JMRI Communications - Simple Support for Lots of Detectors https://forum.mrhmag.com/post/sma28-jmri-sensor-channels-%E2%80%93-direct-arduino-to-jmri-communications-simple-support-for-lots-of-12210793 describing an Arduino to JMRI input channel.
Build Details
This “Turnout Channel” is built with an Arduino Mega2560 controller and an optional Mega Sensor shield, for about $12-16 total depending on where you purchase them. Here is what I used:
MEGA 2560 R3 ATMEGA16U2 ATMEGA2560-16AU Board + USB Cable For Arduino https://www.ebay.com/itm/253733606559 11.95 (you can get a Chinese knock-off for $7)
MEGA2560 ATmega2560-16AU Sensor Shield V2.0
https://www.ebay.com/itm/201004504826 2.56
The Arduino Mega2560 controller is loaded exactly like the other Aduino’s I described in the December 2016 issue of MRH A modeler’s introduction to the Arduino http://mrhpub.com/2016-12-dec/online/
With exactly the same free tools that can be downloaded here: https://www.arduino.cc/en/Main/Software
The Mega shield board literally plugs into the top of the Mega2560 board, and has two screw terminals where you can connect a 5 Volt power supply to power the assembly. The shield board makes attaching your devices easier. Each corresponding signal (S) pin is numbered on the board from 2-53, and then from A0-A15 (corresponding to sensors 54-69). Pins 0 and 1 are taken for USB communication. These will correspond directly to the turnout number in the JMRI table. Each numbered connection on the shield provides a group of 3 pins with the signal connection (S), a+5 Volt pin (V), and a ground (negative side connection G). You can connect to the pins with jumper wires already made of varying lengths available from many sources including Pololu.com and ebay.com that will slide onto these pins directly. With a little care, you can also solder connections too.
Jumpers from Pololu.com
The Arduino Mega2560 will use all 68 pins as digital outputs. An Arduino UNO, a Pro Mini, and a Nano will have 18 pins available (Digital pins 2-13, and A0-A5 or Digital pins 14-19) for outputs. There are two different pre-configured sketches to load either a Mega or one of the smaller boards. Both sketches assume all pins are scanned as outputs and will match JMRI user-defined turnout names from AT2-AT69 or AT2-AT19 . The AT designation is completely arbitrary, but if you want to change it you will have to edit the JMRI TurnoutDataTransfer Python Script provided. When you download the Arduino sketch into your board, you must make note of the Serial Port on your computer used for communication between the Arduino IDE edior and your board. Your should always plug your Arduino USB cable into the same USB port/socket, so you will get your operating system to assign the same USB port. If your switch computers, it is likely the USB port number will be different. This is important because you must make sure the JMRI TurnoutDataTransfer.py Python Script is edited (line 17: portname = "COM5" with the correct COM port number.
Load the appropriate Arduino sketch onto your favorite board, attach a sensor shield if needed, and leave the board connected to your computer that you will run JMRI.
Setting Up The JMRI Turnout Table
You have some options for setting up your turnout table for data transfer to your Arduino. The JMRI transfer script that will communicate with your Arduino will look for a set of user names in your turnout table set up as AT2 – AT69, or shorter if you like. So if you already have entries in your table already set up and you haven’t given them user names you can edit each entry and add these in sequence, like AT2, AT3, AT4, etc… or you can use the included script in the zip file called: Turnout_Table_Build.py which looks like this:
import jarray
import jmri
import java
import purejavacomm
# Define a turnout
def DataSet(sname, uname) :
t = turnouts.newTurnout(sname, uname)
t.setState(THROWN)
# Create a list of turnouts
first_turnout = 2
last_turnout = 69
for x in range(first_turnout, last_turnout+1) :
ssname = "IT"+str(x)
uuname = "AT"+str(x)
DataSet(ssname,uuname)
You can change first_turnout and last_turnout to whatever range you would like. Remember the Arduino Mega2560 can handle 68 and the smaller ones can handle up to 18. Using the system name prefix IT will set these up as internally referenced turnouts (not a hardware address). This script is easier to use than the internal sequenced add in the turnout table. After getting the entries correct, remember to save your panel by using Panels-> Save Panels… and name and save for later. You can set this file up to automatically load in the Preferences-> Startup menu.
Now locate where you unzipped the TurnoutDataTransfer.py file you edited with the correct COM port.
From the JMRI menu go to Panels -> Run Script and find your up to date TurnoutDataTransfer.py file. Run the script and you should see all your turnouts go from “Unknown” to “Closed.” If you do not want the turnouts initialized at startup simply delete line 44 in the TurnoutDataTransfer.py script: turnout.setCommandedState(CLOSED)
Options in the Arduino Sketches
These comments (also in the Arduino sketches) outline some more options for you:
The sketch is preconfigured for an Arduino Mega2560, pins 2-69, no inversion, no offset
Transmission is of the form "ATxxx.S where AT is the user name prefix from the JMRI turnout table
xxx is the number of the turnout (User name is built of the form AT123,
and "S" is the "State" of the turnout where Closed=0 and Thrown=1
It is assumed that the Arduino starts up before the Python Script
The script will set up listeners for all its turnouts and only update them when they change
The Arduino will update the appropriate pin, which can have an offset, and be o[ptionally inverted
The serial port assigned to this Arduino must correspond to the Serial Port in the corresponding TurnoutDataTransfer.py Script #define Data_Pin_Max 70 // Max sensor pin NUMBER (plus one) Mega=70,UNO,Pro Mini,Nano=20
#define Data_Pin_Start 2 // Starting Sensor Pin number (usually 2 as 0/1 are TX/RX
#define Data_Pin_Offset 0 // This Offset will be ADDED to the value of each Turnout number to determine the Data pin
// number used by the Arduino, so pin AT12 will set pin 12+Data_Offset)
// This would allow one Arduino Turnout Data channel to use AT2-69 set pins 2-69 and another to
// use AT70-137 to set its pins 2-69 for example; this offset can also be negative
#define Data_Invert 0 // Set Data_Active_Low to 1 to invert incomin Turnout data Closed=1 Thrown=0
// Set Data_Active_Low to 0 to leave incoming data untouched Closed=0 Thrown=1
Last Comments
Do not get hung up with the term “Turnout” !! It is a throwback to early DCC days and terminology. Relative to this application it is simply a switch. BUT, with the power and connections internal to JMRI these can be linked to and controlled by many other things. So start by just thinking about them as a switch that, now, can reach outside of JMRI.
it should be possible to burden the Mega2560 with a bit more work, like using the turnout bits to move servos and the like -- just one possibility.
It should also be noted, due to the elegance (
yes elegance!) with which JMRI is built that you should also be able to extract info from other tables (like lights) in JMRI. Each output channel to one Arduino needs to be unique, but that should not be much of a limitation with USB hubs and USB serial ports. In order to run multiple sensor channels you will need to make each
TurnoutDataTransfer script independent of each other. You will need to edit and copy each script for each COM port and change the following lines:
line 16: global extportin # the name extportin must be unique for each com port
line 17: portname = "COM8" # COMx MUST be the same as the corresponding Arduino Port
line 22: port = extportin # same as above
line 23: extportin = port # same as above
For some reason, I could not use exportin8, exportin9, etc. These had to be ending with an alphabetic character not a number, like exportina, exportinb, exportinc, ...
I am sure there are more possibilities yet to be invented.
I hope modelers can take this as a starting point to build some really great models and layouts.
Have fun!
Best regards,
Geoff Bunza