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

530 lines
21 KiB
Plaintext

<!DOCTYPE html>
<html lang="en">
<head>
<meta name="generator" content="HTML Tidy for HTML5 for Apple macOS version 5.8.0">
<title>LogixNG Reference - Chapter 9</title>
<meta name="author" content="Daniel Bergqvist">
<meta name="author" content="Dave Sand">
<meta name="keywords" content="jmri LogixNG reference formula">
<!--#include virtual="/help/en/parts/Style.shtml" -->
</head>
<body>
<!--#include virtual="/help/en/parts/Header.shtml" -->
<div id="mBody">
<div id="mainContent" class="no-sidebar">
<h1>LogixNG Reference - Chapter 9</h1>
<h2>Formula</h2>
<p>LogixNG has native support for complex calculations with the tool "Formula". Formula
supports almost all the Java operators and you can use local variables and functions with
formula. In many cases, like the action Turnout, you can choose to use formula to get the
turnout you want to act on, or to get the new state you want to set.</p>
<p>Local variables, which are explained in <a href="chapter8.shtml">chapter 8</a>, can be
used directly in formula. So if you have a local variable <em>index</em>, you can for example
have the formula <em>"IT" + str(index)</em>, which adds the string "IT" and the value of
<em>index</em>. This can be useful if you for example want to set all the turnouts IT1, IT2,
IT3, ..., IT10 to thrown. You can then use the <strong>For</strong> action to iterate from 1
to 10 and to set each of these turnouts to thrown.</p>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h3>The Formula expressions</h3>
<p>There are three expressions for formula: Analog Formula, Digital Formula and String
Formula. They all work the same way, except that Analog Formula returns a floating point
number, Digital Formula returns a boolean value (true or false), and String Formula returns a
string. The expression Formula can have child expressions, for example reading an analog
value or reading the state of a sensor. You use the result of the child expressions by using
the name of the female socket in the formula. So if you have an expression Formula which has
a child E1 to which an expression Sensor is connected, you can use the result of the
expression Sensor in the formula by the identifier E1 which points to the female socket and
its connected expression.</p>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h3>Operators</h3>
<p>Formula supports most of the Java operators. A list of the Java operators, together with
the priority of them, is on <a href=
"https://introcs.cs.princeton.edu/java/11precedence/">this page</a>.</p>
<p>Currently supported operators are:</p>
<div style="margin-left: 2em">
<table>
<tr>
<th>Operator</th>
<th>Description</th>
<th>Associativity</th>
</tr>
<tr>
<td>v++<br>v--</td>
<td>post-increment<br>post-decrement</td>
<td>not associative</td>
</tr>
<tr>
<td>++v<br>--v</td>
<td>pre-increment<br>pre-decrement</td>
<td>right to left</td>
</tr>
<tr>
<td>-</td>
<td>unary minus</td>
<td>right to left</td>
</tr>
<tr>
<td>!</td>
<td>unary logical NOT</td>
<td>right to left</td>
</tr>
<tr>
<td>~</td>
<td>unary bitwise NOT</td>
<td>right to left</td>
</tr>
<tr>
<td>* / %</td>
<td>multiply, divide, modulo</td>
<td>left to right</td>
</tr>
<tr>
<td>+ -<br>
+</td>
<td>additive<br>
string concatenation</td>
<td>left to right</td>
</tr>
<tr>
<td>&lt;&lt;<br>
&gt;&gt;<br>
&gt;&gt;&gt;
</td>
<td>shift left<br>
shift right<br>
unsigned shift right
</td>
<td>left to right</td>
</tr>
<tr>
<td>&lt; &lt;=<br>
&gt; &gt;=</td>
<td>relational</td>
<td>not associative</td>
</tr>
<tr>
<td>==<br>
!=</td>
<td>equality</td>
<td>left to right</td>
</tr>
<tr>
<td>&amp;&amp;</td>
<td>logical AND</td>
<td>left to right</td>
</tr>
<tr>
<td>^^</td>
<td>boolean XOR</td>
<td>left to right</td>
</tr>
<tr>
<td>||</td>
<td>logical OR</td>
<td>left to right</td>
</tr>
<tr>
<td>?:</td>
<td>ternary</td>
<td>right to left</td>
</tr>
<tr>
<td>= += -=<br>
*= /= %=<br>
&amp;= |= ^=<br>
&lt;&lt;= &gt;&gt;= &gt;&gt;&gt;=
</td>
<td>assignment</td>
<td>right to left</td>
</tr>
</table>
</div>
<p>Note that for the calculations to work, each operand must have the correct type. For
example, if you have a local variable <strong>MyVar</strong> that has a number as a string and
you want to subtrack from it, like <code>MyVar - 1</code>, you need to convert the string in
MyVar to an integer or a float. Example: <code>int(MyVar) - 1</code> or <code>float(MyVar) - 1
</code>. The same rules apply to concatenating a string and an integer. The integer has to be
converted to a string, such as <code>"IT" + str(index)</code></p>
<p>In this example, the <strong>A1</strong> section uses the <a href="chapter3.shtml">For</a>
action to toggle 5 turnouts. The turnout user names are T1 thru T5. The
<strong><em>index</em></strong> <a href="chapter8.shtml">local variable</a> is used to supply
the number. The index value has to converted to a string before concatenating with the
"T"</p>
<p>The <strong>A2</strong> section is an example of a digital expression. This a simple one
with just the "and" operator.</p>
<div style="margin-left: 2em;">
<a href="images/chapter9/formula_example.png"><img src=
"images/chapter9/formula_example.png" alt="Chapter 9 formula_example" width="700" height=
"500"></a>
</div>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h3 id="functions">Functions</h3>
<p>Formula supports functions, like sin(x) and random(). Some functions takes one or several
parameters. A function has an identifier, for example "sin", followed by a left parentheses,
optional one or several parameters separated by comma, and then closed by a right
parentheses.</p>
<p>The dialog boxes for editing an action or expression, and the dialog box for editing
variables, has a button "Formula functions". If you click on that button, you get a new
dialog box that shows the functions that are available and the documentation on each of
them.</p>
<p>For JMRI developers: The functions are defined in the package
<em>jmri.jmrit.logixng.util.parser.functions</em> and each module has its own Java class.
Each function is its own class that implements the <em>Function</em> interface.</p>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h4>Function Lists</h4>
<ul>
<li>Clock
<div style="margin-left: 2em;">
<a href="images/chapter9/functions_clock.png"><img src=
"images/chapter9/functions_clock.png" alt="Chapter 9 clock functions"></a>
</div>
</li>
<li>Common
<div style="margin-left: 2em;">
<a href="images/chapter9/functions_common.png"><img src=
"images/chapter9/functions_common.png" alt="Chapter 9 common functions"></a>
</div>
</li>
<li>Convert
<div style="margin-left: 2em;">
<a href="images/chapter9/functions_convert.png"><img src=
"images/chapter9/functions_convert.png" alt="Chapter 9 convert functions"></a>
</div>
</li>
<li>Java<span class="since">since 5.1.3</span>
<div style="margin-left: 2em;">
<a href="images/chapter9/functions_java.png"><img src=
"images/chapter9/functions_java.png" alt="Chapter 9 java functions"></a>
</div>
</li>
<li>Layout<span class="since">since 4.25.8</span>
<div style="margin-left: 2em;">
<a href="images/chapter9/functions_layout.png"><img src=
"images/chapter9/functions_layout.png" alt="Chapter 9 layout functions"></a>
</div>
</li>
<li>Math
<div style="margin-left: 2em;">
<a href="images/chapter9/functions_math.png"><img src=
"images/chapter9/functions_math.png" alt="Chapter 9 math functions"></a>
</div>
</li>
<li>NamedBean
<div style="margin-left: 2em;">
<a href="images/chapter9/functions_named_bean.png"><img src=
"images/chapter9/functions_named_bean.png" alt="Chapter 9 named bean functions"></a>
</div>
</li>
<li>String
<div style="margin-left: 2em;">
<a href="images/chapter9/functions_string.png"><img src=
"images/chapter9/functions_string.png" alt="Chapter 9 string functions"></a>
</div>
</li>
</ul>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h4>Documentation of the functions</h4>
<p>To make it easier to use the functions, each function has some documentation.</p>
<p>Each action/expression has a <strong>Formula functions</strong> button that opens a dialog
box with documentation of the functions.</p>
<div style="margin-left: 2em;">
<a href="images/chapter9/function_1.png"><img src="images/chapter9/function_1.png" alt=
"Chapter 9 function button" width="922" height="270"></a>
</div>
<p>The functions are grouped in modules to make it easier to find the functions. Select the
module you are interested in.</p>
<div style="margin-left: 2em;">
<a href="images/chapter9/function_2.png"><img src="images/chapter9/function_2.png" alt=
"Chapter 9 select module" width="500" height="621"></a>
</div>
<p>Then select the function you are interested in.</p>
<div style="margin-left: 2em;">
<a href="images/chapter9/function_3.png"><img src="images/chapter9/function_3.png" alt=
"Chapter 9 select function" width="500" height="621"></a>
</div>
<p>In this case, the function <strong>fastClock()</strong> take a string parameter which can
have any of the values <em>hour</em>, <em>min</em> or <em>minOfDay</em>.</p>
<p>Some functions, for example the function <strong>random()</strong>, can take different
numbers of parameters. The default is <code>0.0 &lt;= x &lt; 1.0</code>. Supplying
<em>max</em> or <em>min and max</em> values can change the range.</p>
<div style="margin-left: 2em;">
<a href="images/chapter9/function_4.png"><img src="images/chapter9/function_4.png" alt=
"Chapter 9 random functions" width="500" height="621"></a>
</div>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h3>Access to Java Methods</h3>
<p>Sometimes it is possible to access Java methods. For example, for most of the JMRI beans
it is possible to access the public methods in the bean's Interface. This can also apply to
some of the main Java classes, such as String.</p>
<h5>Java Arrays<span class="since">since 5.7.5</span></h5>
<p>Some Java methods return a Java Array instead of a List. The LogixNG Formula has been
enhanced to work with a Java Array.</p>
<p>Here is an example using the Java array created by the String <strong>split</strong> method.</p>
<pre style="border: 2px solid #778899; margin: 1em; padding: 0.1em;">
LogixNG: IQ:AUTO:0001
ConditionalNG: IQC:AUTO:0001
! A
Many
::: Local variable "a_b", init to String "Hello_World"
::: Local variable "a", init to None ""
::: Local variable "b", init to None ""
::: Local variable "a_b_array", init to None ""
! A1
Digital Formula: a_b_array = a_b.split("\_")
! A2
Digital Formula: a = a_b_array[0]
! A3
Digital Formula: b = a_b_array[1]
! A4
Log local variables
</pre>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h3 id="jythonFunction">Advanced: Add new function with Jython</h3>
<p>It's possible to create a new function using Jython to be used by a formula. The code below
gives an example that you can use as a template. A new function is added by creating a new
class that extends the class <strong>Function</strong> and implements these methods:</p>
<div style="margin-left: 2em">
<table>
<tr>
<th>Method</th>
<th>Description</th>
</tr>
<tr>
<td>getModule</td>
<td>The name of the module that the function belongs to</td>
</tr>
<tr>
<td>getName</td>
<td>The name of the function</td>
</tr>
<tr>
<td>getDescription</td>
<td>Description of the function for the user</td>
</tr>
<tr>
<td>getConstantDescriptions</td>
<td>Description of any constants</td>
</tr>
<tr>
<td>calculate</td>
<td>Calculate the function</td>
</tr>
</table>
</div>
<p>Example Jython script that defines the <strong>getBlockValue</strong> function that takes
the name of a block as its parameter:</p>
<pre style="border: 2px solid #778899; margin: 1em; padding: 0.1em;">
import jmri
class BlockValue(jmri.jmrit.logixng.util.parser.Function):
def getModule(self):
return 'Custom'
def getName(self):
return 'getBlockValue'
def getDescription(self):
return 'Get the current value for the specified block name.'
def getConstantDescriptions(self):
return None
def calculate(self, symbolTable, parameterList):
if (parameterList.size() != 1):
raise jmri.jmrit.logixng.util.parser.WrongNumberOfParametersException("Function requires one parameter")
blockName = parameterList.get(0).calculate(symbolTable)
block = blocks.getBlock(blockName)
return None if block is None else block.getValue()
jmri.InstanceManager.getDefault(jmri.jmrit.logixng.util.parser.FunctionManager).put("getBlockValue", BlockValue())
</pre>
<h6>Sample LogixNG conditional</h6>
<pre style="border: 2px solid #778899; margin: 1em; padding: 0.1em;">
LogixNG: Test Script Function
ConditionalNG: Get Block Value
! A
Many
::: Local variable "BlockValue", init to None ""
! A1
Digital Formula: BlockValue = getBlockValue("TestBlock")
?* E1
! A2
Log local variables
</pre>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h4>Calculate the function</h4>
<p>The function <strong>calculate</strong> takes a number of arguments as a
<strong>List&lt;ExpressionNode&gt;</strong>. We first check the number of arguments by
calling the method <strong>size()</strong> and if that's correct, we get the arguments by
calling the method <strong>get(index)</strong> where "index" is the index of the
argument.</p>
<p>But to do something useful with the arguments, we need to calculate each argument we want
to use. We do that by calling the method <strong>calculate</strong> on each argument we want
to use.</p>
<p>We then do the calculation, which in this case finds the requested block and returns the
current value.</p>
<p>Since the local variables symbol table is also passed to the calculate function, local
variables are also available to the script using <code>symbolTable.getValue(name)</code> and
<code>symbolTable(name, new value)</code>.</p>
<!-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -->
<h4>Important!</h4>
<p>The Jython script needs to loaded before the LogixNG runs that references the custom
function. The best approach is to add a start up action to run the script. When the xml
data file is loaded, the script will be ready to process the custom function request.</p>
<p>A function may set turnouts, sensors, and other things on the layout. You may for example
create the function <strong>setTurnout(turnout, newState)</strong>. But it's important to
remember that a ConditionalNG runs on a separate thread so if you set a turnout or a sensor,
you must do that on the layout thread. Formula is always run on the thread that the
ConditionalNG is run on, so if a function updates the layout or the GUI, it needs to do it on
the layout thread or the GUI thread.</p>
<p>See the later chapter on threads (may not exist yet) for more information of LogixNG
threads.</p>
<h3>Developer Note: Adding a new function</h3>
<p>Select a suitable module for the new function. Each module has its own class and most
module classes reside in the <em>jmri.jmrit.logixng.util.parser.functions</em> package,
although it's possible to put a module in another Java package if desired. If a module is
aimed for an particular part of JMRI, for example LocoNet, it might be better to put it in the
<em>jmri.jmrix.loconet.logixng</em> package.</p>
<p>The module has a <strong>getFunctions()</strong> method which tells the
<strong>FunctionManager</strong> which functions each module provides. This method returns a
set of functions. To create a new function, add a new anonymous class to this set. Have the
new class extend the <strong>AbstractFunction</strong> class.</p>
<p>The <strong>AbstractFunction</strong> constructor takes three parameters. The module, the name of the
function and the description of the function. The anonymous class then needs to implement the
<strong>calculate()</strong> method which has the actual implementation of the new function.</p>
<p>The <strong>calculate()</strong> method takes two parameters, the symbol table and the
parameters to the function. It then does the desired calculation for the function and then
returns the value. <strong>parameterList.size()</strong> gives the number of parameters for
the function. <strong>parameterList.get(index).calculate(symbolTable)</strong> returns the
value of parameter index.</p>
<p>The <em>jmri.util.TypeConversionUtil</em> class has some useful utility methods to convert
the parameters to some types, for example String, long and double.</p>
<p>Example: The <strong>sqrt()</strong> function in the Math module. The
<strong>addSqrtFunction()</strong> method is called from the MathFunctions.getFunctions()
method.</p>
<pre>
private void addSqrtFunction(Set&lt;Function&gt; functionClasses) {
functionClasses.add(new AbstractFunction(this, "sqrt", Bundle.getMessage("Math.sqrt_Descr")) {
@Override
public Object calculate(SymbolTable symbolTable, List&lt;ExpressionNode&gt; parameterList)
throws JmriException {
if (parameterList.size() == 1) {
double param = TypeConversionUtil.convertToDouble(parameterList.get(0).calculate(symbolTable), false);
return Math.sqrt(param);
} else {
throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
}
}
});
}
</pre>
<hr>
<p><a href="chapter10.shtml">Chapter 10 - Modules</a>
</p>
<p><a href="index.shtml">Return to the Reference TOC</a>
</p>
<!--#include virtual="/help/en/parts/Footer.shtml" -->
</div>
<!-- closes #mainContent-->
</div>
<!-- closes #mBody-->
<script src="/js/help.js"></script>
</body>
</html>