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: Getting Started with Scripting

Running "Hello World"

There are several ways to use Python scripts with JMRI. The easiest is to use the built-in support in the standard JMRI applications: PanelPro, DecoderPro, etc.
To do that:

Sample layout commands


>>> lt1 = turnouts.provideTurnout("1")

>>> lt1.setCommandedState(CLOSED)

>>> print lt1.commandedState
2

>>> lt1.commandedState = THROWN

>>> print lt1.commandedState
4

>>> turnouts.provideTurnout("1").getCommandedState()
1

Note that this is running a complete version of the JMRI application; all of the windows and menus are presented the same way, configuration is done by the preferences panel, etc. What the Jython connection adds is a command line from which you can directly manipulate things.

This also shows some of the simplifications that Jython and the Python language brings to using JMRI code. The Java member function:

        turnout.SetCommandedState(jmri.Turnout.CLOSED);
can also be expressed in Jython as:
        turnout.commandedState = CLOSED

This results in much easier-to-read code.

There are a lot of useful Python books and online tutorials. For more information on the Jython language and it's relations with Java, the best reference is the Jython Essentials book published by O'Reilly. The jython.org web site is also very useful.

Access to JMRI

JMRI uses the factory-pattern extensively to get access to objects. In Java this results in verbose code like
   Turnout t2 = InstanceManager.getDefault(TurnoutManager.class).newTurnout("LT2", "turnout 2");
   t2.SetCommandedState(Turnout.THROWN)
Jython simplifies that by allowing us to provide useful variables, and by shortening certain method calls.

To get access to the SignalHead, Sensor and Turnout managers and the CommandStation object, several shortcut variables are defined:

These can then be referenced directly in Jython as
   t2 = turnouts.provideTurnout("12");
   
   dcc.sendPacket(new byte[]{0x12, 0x32, 0x4E}, 5)
Note that the variable t2 did not need to be declared.

Jython provides a shortcut for parameters that have been defined with Java-Bean-like get and set methods:

   t2.SetCommandedState(Turnout.THROWN)
can be written as
   t2.commandedState = THROWN
where the assignment is actually invoking the set method. Also note that THROWN was defined when running the Python script at startup; CLOSED, ACTIVE, INACTIVE, RED, YELLOW and GREEN are also defined. (The shortcuts are all defined in a file called "jmri_defaults.py" that you can find in the "jython" directory of the distribution)

A similar mechanism can be used to check the state of something:

>>> print sensors.provideSensor("3").knownState == ACTIVE 
1
>>> print sensors.provideSensor("3").knownState == INACTIVE
0
Note that Jython uses "1" to indicate true, and "0" to indicate false, so sensor 3 is currently active in this example. You can also use the symbols "True" and "False" respectively.

You can directly invoke more complicated methods, e.g. to send a DCC packet to the rails you type:

   
   dcc.sendPacket([0x01, 0x03, 0xbb], 4) 
This sends that three-byte packet four times, and then returns to the command line.

Script files, listeners, and classes

Scripting would not be very interesting if you had to type the commands each time. So you can put scripts in a text file and execute them by selecting the "Run Script..." menu item, or by using the "Advanced Preferences" to run the script file when the program starts.

Although the statements we showed above were so fast you couldn't see it, the rest of the program was waiting while you run these samples. This is not a problem for a couple statements, or for a script file that just does a bunch of things (perhaps setting a couple turnouts, etc) and quits. But you might want things to happen over a longer period, or perhaps even wait until something happens on the layout before some part of your script runs. For example, you might want to reverse a locomotive when some sensor indicates it's reached the end of the track.

There are a couple of ways to do this. First, your script could define a "listener", and attach it to a particular sensor, turnout, etc. A listener is a small subroutine which gets called when whatever it's attached to has it's state change. For example, a listener subroutine attached to a particular turnout gets called when the turnout goes from thrown to closed, or from closed to thrown. The subroutine can then look around, decide what to do, and execute the necessary commands. When the subroutine returns, the rest of the program then continues until the listened-to object has it's state change again, when the process repeats.

For more complicated things, where you really want your script code to be free-running within the program, you define a "class" that does what you want. In short form, this gives you a way to have independent code to run inside the program. But don't worry about this until you've got some more experience with scripting.