JMRI is...

Scripting

Information on writing scripts to control JMRI in more detail:

Python

JMRI scripts are in Python, a popular general-purpose computer language

Tools

JMRI provides powerful tools for working with your layout.

Layout Automation

JMRI can be used to automate parts of your layout, from simply controlling a crossing gate to running trains in the background.

JMRI: Scripting FAQ

Frequently asked questions about scripting JMRI with Jython

Where can I learn more about the Jython language?

See the Python & JMRI page for more info, including pointers to articles, etc. See also the navigation links to the left.

How do Jython and Python differ?

For the purposes of writing JMRI scripts, they don't differ very much. Most of the differences involve what happens in somewhat contrived error cases. There are also some restrictions on what you can do with the computer's configuration information, etc, in Jython, but these are not things a JMRI script is likely to need.

Some additional information on the differences is available here.

Where can I find some examples of JMRI scripts?

See the examples page. Also, the introductory page shows some of the basic commands.

What do those words like "import", "class", etc, in the example files mean?

They're part of the jython language used for the scripting.

The imports allow you to refer to things by shorter names, essentially telling jython "search the jarray, jmri packages and recognize all the names there". For somebody trying to understand this script, you can just treat them as "ensuring the program can find parts we want".

"class" means "start the definition of a group of things that go together" (all you other experts, please don't jump on me about this; I understand both intrinsic/extrinsic polymorphism, I'm just trying to get the general idea across).

For example, in the SigletExample.py file is a description of a "class" called SigletExample, which contains two routines/functions/members: A subroutine called "defineIO", and one called "setOutput"

This "class" is associated with another called "Siglet" (actually jmri.jmrit.automat.Siglet; that's that long naming thing again), which knows when to invoke routines by those two names to get done what you want.

Essentially, you're defining two parts ("defineIO" & "setOutput") that plug into a pre-existing structure to drive signals. That pre-existing structure is very powerful, and lets you do all sorts of things, but also provides this method to try to keep it simpler.

OK, at this point most people's eyes are fully glazed over. Your best bet when starting with this stuff is to use the "copy and modify" approach to software development. It's good to try to understand the entire content of the file, but don't worry about understanding it well enough to be able to recreate it from scratch. Instead, just modify little bits and play with it.

Where can I find more information on the JMRI classes?

The class documentation pages include automatically-built summary information on every class.

There are a lot of classes! To help you find things, it might be useful to look at the page on JMRI structure, which is part of the JMRI technical documentation. Note the references on the left-hand side.

Are there required naming conventions?

In many of the sample files, turnouts are referred to by names like "to12", signals by names like "si21", and sensors by names like "bo45". These conventions grew out of how some older code was written, and they can make the code clearer. But they are in no way required; the program doesn't care what you call variables.

For example, "self.to12" is just the name of a variable. You can call it anything you want, e.g. self.MyBigFatNameForTheLeftTurnout

The "self" part makes it completely local; "self" refers to "an object of the particular class I'm defining right now". Alternately, you can define a global variable, but that's not recommended. If you have multiple scripts running (and you can have as many as you'd like; we recommend that you put each signal head in a separate one), the variables can get confused if you use the same variable name to mean too different things. Using "self" like this one does makes sure that doesn't happen.

Note that turnouts, etc, do have "System Names" that look like "LT12". You'll see those occasionally, but that's something different from the variable names in a script file.

Can a script access the JMRI application windows?

Your scripts can change the properties of all the main JMRI windows. They're all jmri.util.JmriJFrame objects, so they have all the various methods of a Swing JFrame. For example, this code snippet

window = jmri.util.JmriJFrame.getFrameList()[1]
window.setLocation(java.awt.Point(0,0))

locates the application's main window, and sets its location to the upper left corner of the screen.

The jmri.util.JmriJFrame.getFrameList() call in the first line returns a list of the existing windows. The [0] element of this list is the original splash screen and the [1] element is the main window; after that, they're the various windows in the order they are created. To find a particular one, you can index through the list checking e.g. the window's title with the getTitle() method.

What's the difference between the "Siglet" and "AbstractAutomaton" classes?

(Maybe not a frequently asked question, but it needs to go somewhere)

Some examples use the AbstractAutomaton class as a base, while others use the Siglet class. This is because those are intended for two different purposes.

"Siglet" is meant to be used for driving signals. You provide two pieces of code:

defineIO
which defines the various sensors, turnouts and signals that the output signal depends on as input when calculating the appearance of the signal.
setOutout
which recalculates the signal appearance from the defined inputs.

The Siglet base class then handles all of the listening for changes, setting up for parallel execution, etc. Your defineIO routine will be called once at the beginning, and after than any time one or more of the inputs changes, your setOutput routine will be called to recalculate the signal appearance.

Of course, you can use this class to calculate other things than signal appearances. But the key element is that the calculation is redone when the inputs change, and only when the inputs change.

AbstractAutomaton is a more general class that's intended to allow more powerful operations (and Siglet actually uses that more powerful base). You define two functions:

init
which is called exactly once to do any one-time setup you need
handle
which is called over and over and over again until it returns FALSE.
Using AbstractAutomoton provides you with a number of tools: you can wait for a particular sensor to go active, do something, then wait for a different sensor to go inactive, etc. This allows you much more freedom to create complicated & powerful sequences than the Siglet class, because Siglets are limited to doing just one thing (they aren't intended to do sequences of operations).

For more info on the routines that AbstractAutomaton provides to help you, see the Javadocs for the class. (Scroll down to the section called "Method Summary")

How can I limit the priority of a script?

If the script runs a loop that's supposed to update something, it can't be written to run continuously or else it will just suck up as much computer time as it can. Rather, it should wait.

The best thing to do is to wait for something to change. For example, if your script looks at some sensors to decide what to do, wait for one of those sensors to change (see the sample scripts for examples)

Simpler, but not as efficient, is to just wait for a little while before checking again. For example

    waitMsec(1000)
causes an automaton or Siglet script to wait for 1000 milliseconds (one second) before continuing.

For just a simple script, something that doesn't use the Automat or Siglet classes, you can sleep by doing

from time import sleep
sleep(10)
The first line defines the "sleep" routine, and only needs to be done once. The second line then sleeps for 10 seconds. Note that the accuracy of this method is not as good as using one of the special classes.

How do I load a panel file from within a script?

jmri.InstanceManager.getDefault(jmri.ConfigureManager.class).load(java.io.File("filename.xml"))
That looks for "filename.xml" in the JMRI program directory, which is not a good place to keep your files. (They tend to get lost or damaged when JMRI is updated). See the next question for a solution to this.

How do I find a file in the preferences directory?

You can always specify the complete pathname to a file, e.g. C:\Documents and Files\mine\JMRI\filename.xml or /Users/mine/.jmri/filename.xml. This is not very portable from computer to computer, however, and can become a pain to keep straight.

JMRI provides a routine to convert "portable" names to names your computer will recognize:

fullname = FileUtil.getExternalFilename("preference:filename.xml")
The "preference:" means to look for that file starting in the preferences directory on the current computer. Other choices are "program:" and "home:", see the documentation.

How do I invoke another script file from a script?

execfile("filename.py")
That will look for the file in the top-level JMRI program directory, which might now be what you want. If you want JMRI to search for the file in the default scripts directory (which you can set in preferences), use the slightly more complex form:
execfile(FileUtil.getExternalFilename("scripts:filename.py"))
The call to FileUtil.getExternalFilename(..) translates the string using JMRI's standard prefixes. The "scripts:" tells JMRI to search in the default scripts directory. You can also use other prefixes, see the documentation.

Can I communicate between scripts?

All scripts run in the same default namespace, which means that a variable like "x" refers to the same location in all scripts. This allows you to define a procedure, for example, in one script, and use it elsewhere. For example, if a "definitions.py" file contained:
def printStatus() :
  print "x is", x
  print "y is", y
  print "z is", z
  return

x = 0
y = 0
z = 0
Once that file has been executed, any later script can invoke the printStatus() routine in the global namespace whenever needed.
You can also share variables, which allows two routines to share information. In the example above, the x, y, and z variables are available to anybody. This can lead to obscure bugs if two different routines are using a variable of the same name, without realizing that they are sharing data with each other. Putting your code into "classes" is a way to avoid that.

Note that scripts imported into another script using import statements are not in the same namespace as other scripts and do not share variables or routines. To share variables from the default namespace with an imported script, you need to explicitly add the shared variable:

import myImport
myImport.x = x # make x available to myImport

Can a script wait for more than one thing to change?

If your script is based on a Siglet or AbstractAutomaton class (e.g. if you're writing a "handle" routine"), there's a general "waitChange" routine which waits for any of several sensors to change before returning to you. Note that more than one may change at the same time, so you can't assume that just one has a different value! And you'll then have to check whether they've become some particular state. It's written as:
    self.waitChange([self.sensorA, self.sensorB, self.sensorC])
where you've previously defined each of those "self.sensorA" things via a line like:
    self.sensorA = sensors.provideSensor("21")
You can then check for various combinations like:
   if self.sensorA.knownState == ACTIVE :
        print "The plane! The plane!"
   elif self.sensorB.knownState == INACTIVE :
        print "Would you believe a really fast bird?"
   else
        print "Nothing to see here, move along..."
You can also wait for any changes in objects of multiple types:
    self.waitChange([self.sensorA, self.turnoutB, self.signalC])
Finally, you can specify the maximum time to wait before continuing even though nothing has changed:
    self.waitChange([self.sensorA, self.sensorB, self.sensorC], 5000)
will wait a maximum of 5000 milliseconds, e.g. 5 seconds. If nothing has changed, the script will continue anyway.

Can a script listen to more than one Turnout?

JMRI objects (Turnouts, Sensors, etc) can have "Listeners" attached to them. These are then notified when the status of the object changes. If you're using the Automat or Siglet classes, you don't need to use this capability; those classes handle all the creationg and registering of listeners. But if you want to do something special, you may need to use that capability.

A single routine can listen to more than one Turnout, Sensor, etc.

If you retain a reference to your listener object, you can attach it to more than one object:

m = MyListener()
turnouts.provideTurnout("12").addPropertyChangeListener(m)
turnouts.provideTurnout("13").addPropertyChangeListener(m)
turnouts.provideTurnout("14").addPropertyChangeListener(m)

But how does the listener know what changed?

A listener routine looks like this:

class MyListener(java.beans.PropertyChangeListener):
  def propertyChange(self, event):
    print "change",event.propertyName
    print "from", event.oldValue, "to", event.newValue
    print "source systemName", event.source.systemName
    print "source userName", event.source.userName

When the listener is called, it's given an object (called event above) that contains the name of the property that changed, plus the old and new values of that property.

You can also get a reference to the original object that changed via "name", and then do whatever you'd like through that. In the example above, you can retrieve the systemName, userName (or even other types of status).

How can I get a script to play a sound?

The jython/SampleSound.py file shows how to play a sound within a script. Briefly, you load a sound into a variable ("snd" in this case), then call "play()" to play it once, etc.

Note that if more than one sound is playing at a time, the program combines them as best it can. Generally, it does a pretty good job.

You can combine the play() call with other logic to play a sound when a Sensor changes, etc. Ron McKinnon provided an example of doing this. It plays a railroad crossing bell when the sensor goes active.

# It listens for changes to a sensor,
# and then plays a sound file when sensor active

import jarray
import jmri

# create the sound object by loading a file
snd = jmri.jmrit.Sound("resources/sounds/Crossing.wav")

class SensndExample(jmri.jmrit.automat.Siglet) :

        # Modify this to define all of your turnouts, sensors and 
        # signal heads.
        def defineIO(self):
                
                # get the sensor 
                self.Sen1Sensor = sensors.provideSensor("473")
                                
                 # Register the inputs so setOutput will be called when needed.
                 self.setInputs(jarray.array([self.Sen1Sensor], jmri.NamedBean))

                return

        # setOutput is called when one of the inputs changes, and is
        # responsible for setting the correct output
        #
        # Modify this to do your calculation.
        def setOutput(self):
                                
                if self.Sen1Sensor.knownState==ACTIVE:
                        snd.play()

                return
        
# end of class definition

# start one of these up
SensndExample().start()

Is JMRI's support for Python complete?

JMRI scripting uses Jython, a Java-implemented form of the Python language. The basic language is pretty complete, but not all of the Python libraries are available. Some "import" statements that you might read in a book might not work because of missing libraries.

Support is improving all the time, though, and you might want to try a more modern version of Jython than the one that JMRI distributes. To do that, install Jython on you computer, then add a python.properties file in your user files directory or preferences directory that sets the python configuration variables, e.g. on Windows:

python.path = C:\\jython2.7.0\\Lib\\site\-packages
python.startup = 

Note that the double-back-slashes are necessary. They'll appear as single-back-slashes in the final value, which you can check with the "Context" item from the main JMRI Help menu. On Mac use something like:

python.path = /Users/username/jython2.7.0/Lib/site-packages
python.startup = 
or where-ever you've installed the new Jython. Linux is similar.