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

General Tools

The general objects available to control your layout.

JMRI: Scripting Examples

The JMRI distributions come with a "jython" directory that contains a few examples. As of the time of writing these are:

SampleSound.py
A straight-forward set of statements to load and play a sound. There are some comments on how to add various options.
SetTurnouts.py
Sets a couple of turnouts to specific positions, waiting between each operation. Because we want to have the rest of the program keep running, there are a few more statements involved than just the commands to throw the turnouts. There's also a separate page that talks more about this.
ListenerExample.py
Example of listing to a Turnout so that the script can do something every time the Turnout's state changes.
Sensor-sound.py
An example of using a "listener" to play a sound every time a particular sensor changes.
AutomatonExample.py
A simple automation example, this runs a train back and forth between two blocks.
OpsProgExample.py
A more complicated automation example. When a given sensor changes, this script will use ops-mode programming (programming on the main) to change the momentum value for a particular locomotive.
SigletExample.py
An example of driving a signal from a script.
JButtonExample.py
This is an example of adding an object to the GUI, in this case a button. When clicked, it doesn't really do anything except print a message, but it is an example after all!

The rest of this page contains examples of specific techniques.

Invoking another script file from a script

execfile("filename.py");
		    

Loading a panel file from within a script

jmri.InstanceManager.configureManagerInstance().load(java.io.File("filename.xml"))
		    

Communicating between scripts

All scripts share a single address space, 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 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.

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..."
(I haven't actually typed these into a script & run it, so there might be typos, sorry)

Listening 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).

Playing 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()