Info on JMRI:
Development tools
Code Structure
Functional Info
Techniques and Standards
How To
Background Info

Jython access to JMRI tools

This page discusses access to the JMRI libraries from Jython, a Java version of Python. The emphasis is on using JMRI capabilities from a command-line Jython interpreter. See the scripting pages if you are more interested in Jython scripting within JMRI.

Introduction

Python is a widely used scripting language that's available on many types of computers. A Java-based varient, called Jython, has been integrated with JMRI to make it easy to control a model railroad from the command line of a computer.

To use the JMRI library from Jython, you have to ensure that Jython will search the correct jar files when it starts up. We provide a "jmrithon" startup script that will do that for you:

% ./jmrithon
execFileJython 2.1 on java1.4.1_01 (JIT: null)
(Type "copyright", "credits" or "license" for more information.

>>> 

Once you get the Jython prompt of ">>>", you can type Python statements.

We've provided a few Python command files to make setup easier.

For example, you can run the JmriDemo application from the Jython command line, which is then available for manipulating objects like turnouts and sensors:

execfile("JmriDemo.py")
     0 JmriDemo.JMRIdemo                     INFO  - program starts [main]
 11109 JmriDemo.JMRIdemo                     INFO  - main initialization done [main]

>>> turnouts.newTurnout("LT1", "")
jmri.jmrix.loconet.LnTurnout@1a8f49

>>> turnouts.getTurnout("LT1").getCommandedState()
1

>>> lt1 = turnouts.getTurnout("LT1")

>>> lt1.setCommandedState(CLOSED)

>>> lt1.commandedState
2

>>> lt1.commandedState = THROWN

>>> lt1.commandedState
4

>>> 
Note that this is running a complete version of the JmriDemo 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.

Setup

Jython support is not (yet) standard in JMRI. To use this:
  1. You must first have a working JMRI installation. In particular, start the JmriDemo application and configure it to connect to your DCC hardware, then save, quit and restart the program. Make sure that the connection is working for the menu-based tools before trying to use Jython.
  2. Download and install Jython from http://www.jython.org. Development is using the 2.1 version, so you might want to start with that. On MacOS X, the installer does not run, so you have to manually invoke the jython_21.class file; see the MacOS X note on the jython web site. Make sure the installation is complete by invoking jython from the command line:
       % jython
       Jython 2.1 on java1.4.1_01 (JIT: null)
       Type "copyright", "credits" or "license" for more information.
       >>> ^D
    
    If that works, your installation is OK. If not, you'll have to fix the installation before you'll be able to do the next step.

    If the installer didn't run, download the jython_21.class file from SourceForge, and enter at a command prompt:

      java -cp . jython_21
    
    then follow the prompts through the installer.

    If the installer did run, but the jython command isn't found:

    • Windows: Find the jython.bat file which the installer created, and copy it to your C:\WINDOWS directory.
    • Linux: Make sure that the jython install directory is on your path.
  3. Now you just have to have the required classes in the CLASSPATH used when jython starts up. The required list in Unix syntax is:
      setenv CLASSPATH .:jmri.jar:lib/log4j.jar:lib/collections.jar:lib/crimson.jar:lib/jdom-jdk11.jar
    

    To save typing, the "jmrithon" script is available for use on Linux and MacOS X systems:
      % ./jmrithon
      Jython 2.1 on java1.4.1_01 (JIT: null)
      Type "copyright", "credits" or "license" for more information.
      >>> ^D
    

    The first time you run this, you'll get a number of messages about "processing modified jar"; these can be ignored, as they are normal.

Using Jython directly

This section of the page is a pile of useful information on controlling JMRI from Jython directly.

Note that you don't need most of this when running the JmriDemo.py or similar scripts, as they handle starting a complete application.

Initialization

JMRI uses the LogJ system extensively for logging information during program execution. To configure that when running with Jython, you should do:
  import org
  org.apache.log4j.PropertyConfigurator.configure("default.lcf")
before starting any of the JMRI classes. The "default.lcf" is the name of the logging control file to be used; it can be omitted.

To make JMRI classes available, you have to do:

  import jmri
As currently set up, this does not run any initialization code to start the hardware connections, etc. One way to do that is to load a configuration file, perhaps one you've created earlier via the JmriDemo preferences panel:
  import java.io
  configfile = java.io.File(jmri.jmrit.XmlFile.prefsDir()+"JmriDemoConfig2.xml")
  jmri.InstanceManager.setConfigureManager(jmri.configurexml.ConfigXmlManager())
  jmri.InstanceManager.configureManagerInstance().load(configfile)
to activate the JMRI classes and connect to your layout hardware. "JmriDemoConfig2.xml" is the name of the configuration file from the JmriDemo program; you can use another name if desired. The configuration file controls the layout connection, and any other options that may have been set when it was created.

Alternately, if you want to start the complete JmriDemo application, including the menu bar, spash screen, etc, you can do:

 import apps
 apps.JmriDemo.JMRIdemo.main([]) 

This will start the program, including its startup configuration, etc.

To simplify this startup even further, you can do:

 execfile("JmriDemo.py")
to invoke these commands.

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.turnoutManagerInstance().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 in the .py scripts listed above:

These can then be referenced directly in Jython as
   t2 = turnouts.provideTurnout("12");
   
   dcc.
Note that the variable t2 did not need to be declared.

Juthon 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.

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 directly invoke 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.

To exit, either ^C from the command line, or use the exit command from the menu.

Using Python for signal logic and automation

The existing JMRI "Automat" classes provide hooks for user layout automation, including signaling. But they require that you write Java code and compile it into .class files.

The JythonAutomaton class is intended to make it easier to code custom layout automation, as it will allow you to invoke a Jython script. Once it's integrated into the configuration process, you'll be able to code your automation in one or more .py files, and have the program start running them at startup.

Next steps

We're just learning how to best to integrate JMRI and Python, so there are still some basic mysteries.

Initialization and modules

The idea of Python "modules" isn't yet clearly understood. There are three ways to start the program via a .py file:
  1.   % ./jmrithon
      Jython 2.1 on java1.4.1_01 (JIT: null)
      Type "copyright", "credits" or "license" for more information.
      >>> import JmriDemo
    
    This works, in the sense that the program starts up. But the convenience methods are all in the JmriDemo namespace:
      >>> turnouts.newTurnout("LT1","")
      Traceback (innermost last):
        File "", line 1, in ?
      NameError: turnouts
    
      >>> JmriDemo.turnouts.newTurnout("LT1","")
      jmri.jmrix.loconet.LnTurnout@36b91b
    
    which makes for significantly more typing later. And it's confusing, because depending on whether you started JmriDemo, DecoderPro or some other application, the names will be different.
  2.   % ./jmrithon
      >>>  execfile("JmriDemo.py")
    
    or the simpler form
      % ./jmrithon -JmriDemo.py
    
    This looks somewhat ugly, as it's not using the normal Python modules technique. But the convenience methods are in the top-level name space:
      turnouts.newTurnout("LT1", "")
    

    We need to understand whether this is a real issue. Most Python programs don't use that top-level namespace, and there's probably a good reason!
We need to understand how the developers of Jython intended this to be done.

Termination

Once the JMRI main thread (or Swing GUI thread?) has started, ^D is not sufficient to exist the program. You have to select "Quit" from the actual file menu, or ^C the program.

It would be good to understand what's preventing the program from stopping when it gets the ^D.