Files
JIMRI/help/en/html/tools/scripting/Start.shtml
T
2026-06-17 14:00:51 +02:00

356 lines
15 KiB
Plaintext

<!DOCTYPE html>
<html lang="en">
<head>
<meta name="generator" content="HTML Tidy for HTML5 for Apple macOS version 5.8.0">
<meta name="keywords" content=
"start jmri jython, hello world jython, jython layout commands, jython access jmri objects, access jmri tables">
<title>JMRI: Getting Started with Scripting</title><!--#include virtual="/help/en/parts/Style.shtml" -->
</head>
<body>
<!--#include virtual="/help/en/parts/Header.shtml" -->
<div id="mBody">
<!--#include virtual="Sidebar.shtml" -->
<div id="mainContent">
<!-- Page Body -->
<h1 id="StartTopOfPage">JMRI: Getting Started with Scripting</h1>
<p>The easiest way to learn how to use scripts in JMRI is to study the simplest examples
first and work your way into more complex details. The sections below discuss various aspects
of scripting that will help you to understand the more complex scripts found in the <a href=
"Examples.shtml" target="_blank">JMRI examples library</a> included in the distribution and
online:</p>
<ul>
<li>
<a href="#helloworld">Running "Hello World"</a>
</li>
<li>
<a href="#debugging">Basic debugging tools in JMRI</a>
</li>
<li>
<a href="#layoutcommands">Sample layout commands</a>
</li>
<li>
<a href="#objects">Access to JMRI Objects</a>
</li>
<li>
<a href="#scripts">Script files, listeners, and classes</a>
</li>
</ul>
<h4>CAUTION: Python and its Jython variant do not use braces, brackets, or parentheses to
indicate program structure, but rather rely on formatting, specifically indentation. To avoid
confusion, it is STRONGLY recommended that you DO NOT USE TABS but rather use spaces (four is
typical) to show statements that part of a class or controlled structure where you might have
used some bracketing. Most program editors have a setting to automatically changes tabs to a
set number of spaces to make this easier.</h4>
<h2 id="helloworld">Running "Hello World"</h2>
<div class="para">
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.<br>
To do that:
<ul>
<li>
<a href="../../../package/jmri/jmrit/beantable/images/jmriScriptWindow.png"><img src=
"../../../package/jmri/jmrit/beantable/images/jmriScriptWindow.png" width="250" height=
"136" class="floatRight" alt="JMRI Jython Script Window"></a>From the
<strong>Scripting</strong> {<em>Old: <strong>Panels</strong></em>} menu, select
<strong>Script Entry</strong>. This will give you a window where you can type one or
more commands to execute. (Note that it might take a little while the first time you do
this as the program finds its libraries; it will be faster the next time) The commands
stay in the window, so you can edit them and rerun them until you like them.
</li>
<li>From the <strong>Scripting</strong> {<em>Old: <strong>Panels</strong></em>} menu,
select <strong>Script Output</strong>. This creates a window that shows the output from
the script commands you issue.</li>
<li>To see this in operation, type
<pre style="font-family: monospace;">
print "Hello World"
</pre>in the input window, and click "Execute". You should see the output in the Script Output
window immediately:
<pre style="font-family: monospace;">
&gt;&gt;&gt; print "Hello World"
Hello World
</pre>
</li>
<li>Python also evaluates expressions, etc. Remove the contents of the input window
(select it and hit delete), and enter
<pre style="font-family: monospace;">
print 1+2
</pre>then click execute. The output window should then show something like:
<pre style="font-family: monospace;">
&gt;&gt;&gt; print 1+2
3
</pre>
</li>
</ul>
<p><a href="#StartTopOfPage">[Go to top of page]</a>
</p>
</div>
<h2 id="debugging">Basic debugging tools in JMRI</h2>
<!--Section added by Jerry Grochow 2018-10-18 based on comments found in the JMRI Users online forum from
Cliff in BajaSoCal and Bob Jacobson-->
<p>JMRI does not provide a full "development environment," but it does provide some basic
information that can help with debugging your scripts.</p>
<p>Open the JMRI System Console window (<strong>Help &rArr; System Console</strong>). This will
give you instant information pertaining to WARN and ERROR messages. Text can be copied to a
text editor for further review.</p>
<p>Some Jython errors create a Java exception which will be displayed in the System Console.
If the Java error does not include the script line number, finding the source of the problem
can be difficult.</p>
<p>The Java exception will normally contain a line that points to the current Python
<strong>def</strong> or the implied <strong>def</strong> for the first level code. Look for the
<strong>at</strong> line that contains <strong>&lt;script&gt;:</strong> followed by a number.
The number is the line number of the last line in the current <strong>def</strong>. This helps
narrow the scope of code to be reviewed.</p>
<pre>
12:41:20,129 jmri.jmrit.automat.AbstractAutomaton WARN - Unexpected Exception ends AbstractAutomaton thread [theInstan20]
org.python.core.PyException: TypeError: cannot concatenate 'str' and 'int' objects
at org.python.core.Py.TypeError(Py.java:236) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.PyObject._basic_add(PyObject.java:2091) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.PyObject._add(PyObject.java:2068) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.pycode._pyx11.handle$4(<strong>&lt;script&gt;:993</strong>) ~[?:?]
at org.python.pycode._pyx11.call_function(&lt;script&gt;) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:173) ~[jython-standalone-2.7.2.jar:2.7.2]
...
</pre>
<p>Open the Script Output window (<strong>Scripting</strong> {<em>Old:
<strong>Panels</strong></em>} <strong>&rArr; Script Output</strong>). This will display the
results of any "print" statements in your script as well as some error messages (NOTE that
some errors go here and some to the System Console so you you should have both windows open
when you are debugging). Text can be copied to a text editor for further review.</p>
<p>Investigate the information found in the <a href="../../doc/Technical/Logging.shtml"
target="_blank">technical reference</a> to be better able to insert more of your own DEBUG
and INFO messages into the session.log file, and also the JMRI System Console window. You
might also investigate the "default_lcf.xml" file that is described in that link.</p>
<p>Note to Mac OS users: BBEdit and similar tools do some syntax checking. For PC users,
Notepad++ is a good tool for getting indentation correct.</p>
<p><a href="#StartTopOfPage">[Go to top of page]</a></p>
<h2><a id="layoutcommands">Sample layout commands</a>
</h2>
<div class="para">
<pre style="font-family: monospace;">
&gt;&gt;&gt; lt1 = turnouts.provideTurnout("1")
&gt;&gt;&gt; lt1.setCommandedState(CLOSED)
&gt;&gt;&gt; print lt1.commandedState
2
&gt;&gt;&gt; lt1.commandedState = THROWN
&gt;&gt;&gt; print lt1.commandedState
4
&gt;&gt;&gt; turnouts.provideTurnout("1").getCommandedState()
1
</pre>
<p>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.</p>
<p>This also shows some of the simplifications that Jython and the Python language brings
to using JMRI code. The Java member function:</p>
<pre style="font-family: monospace;">
turnout.SetCommandedState(jmri.Turnout.CLOSED);
</pre>can also be expressed in Jython as:
<pre style="font-family: monospace;">
turnout.commandedState = CLOSED
</pre>
<p>This results in much easier-to-read code.</p>
<p>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 <a href=
"https://www.oreilly.com/library/view/jython-essentials/9781449397364/" target="_blank">Jython Essentials</a> book
published by O'Reilly. The <a href="https://www.jython.org/" target="_blank">jython.org web
site</a> is also very useful.</p>
<p><a href="#StartTopOfPage">[Go to top of page]</a>
</p>
</div>
<h2 id="objects">Access to JMRI Objects</h2>
<div class="para">
JMRI uses the factory-pattern extensively to get access to objects. In Java this results in
verbose code like
<pre style="font-family: monospace;">
Turnout t2 = InstanceManager.getDefault(TurnoutManager.class).newTurnout("LT2", "turnout 2");
t2.SetCommandedState(Turnout.THROWN)
</pre>Jython simplifies that by allowing us to provide useful variables, and by shortening certain
method calls.
<p>To get easier access to the SignalHead, Sensor and Turnout managers and the
CommandStation object, several shortcut variables are defined:</p>
<ul>
<li>sensors</li>
<li>turnouts</li>
<li>lights</li>
<li>signals (SignalHeads)</li>
<li>masts (SignalMasts)</li>
<li>routes</li>
<li>blocks</li>
<li>reporters</li>
<li>idtags</li>
<li>memories</li>
<li>powermanager</li>
<li>addressedProgrammers</li>
<li>globalProgrammers</li>
<li>dcc (current command station)</li>
<li>audio</li>
<li>shutdown (current shutdown manager)</li>
<li>layoutblocks</li>
<li>warrants</li>
<li>sections</li>
<li>transits</li>
</ul>
These can then be referenced directly in Jython as
<pre style="font-family: monospace;">t2 = turnouts.provideTurnout("12");
</pre>Note that the variable t2 did not need to be declared.
<p>Jython provides a shortcut for parameters that have been defined with Java-Bean-like get
and set methods:</p>
<pre style="font-family: monospace;">
t2.SetCommandedState(Turnout.THROWN)
</pre>can be written as
<pre style="font-family: monospace;">
t2.commandedState = THROWN
</pre>
<p>where the assignment is actually invoking the set method.</p>
<p>Also note that THROWN was defined when running the Python script at startup;<br>
CLOSED, ACTIVE, INACTIVE, ON, OFF, INCONSISTENT, <br>
LOCKED, UNLOCKED, PUSHBUTTONLOCKOUT, CABLOCKOUT, <br>
RED, YELLOW, GREEN, LUNAR, DARK, <br>
FLASHRED, FLASHYELLOW, FLASHGREEN are also defined. ( <a href=
"https://github.com/JMRI/JMRI/blob/master/java/src/jmri/script/JmriScriptEngineManager.java">
These shortcut bindings are defined in Java</a> )</p>
<p>A similar mechanism can be used to check the state of something:</p>
<pre style="font-family: monospace;">
&gt;&gt;&gt; print sensors.provideSensor("3").knownState == ACTIVE
1
&gt;&gt;&gt; print sensors.provideSensor("3").knownState == INACTIVE
0
</pre>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.
<p>You can directly invoke more complicated methods, e.g. to send a DCC packet to the rails
you type:</p>
<pre style="font-family: monospace;">
dcc.sendPacket([0x01, 0x03, 0xbb], 4)
</pre>This sends that three-byte packet four times, and then returns to the command line. Also see
<a href="WhatWhere.shtml#names">JMRI Jython : The difference between System Names and User
Names</a>
<p><a href="#StartTopOfPage">[Go to top of page]</a>
</p>
</div>
<h2 id="scripts">Script files, listeners, and classes</h2>
<div class="para">
<p>[See also <a href="HowTo.shtml" target="_blank">the scripting "How To" page</a> for more
information on these topics.]</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p><a href="#StartTopOfPage">[Go to top of page]</a>
</p>
</div>
<!--#include virtual="/help/en/parts/Footer.shtml" -->
</div>
<!-- closes #mainContent-->
</div>
<!-- closes #mBody-->
<script src="/js/help.js"></script>
</body>
</html>