Skip to main content
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.
Local variables, which are explained in chapter 8, can be used directly in formula. So if you have a local variable index, you can for example have the formula "IT" + str(index), which adds the string "IT" and the value of index. 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 For action to iterate from 1 to 10 and to set each of these turnouts to thrown.
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.
Formula supports most of the Java operators. A list of the Java operators, together with the priority of them, is on this page.
Currently supported operators are:
|right to left|
|-||unary minus||right to left|
|!||unary logical NOT||right to left|
|~||unary bitwise NOT||right to left|
|* / %||multiply, divide, modulo||left to right|
|left to right|
unsigned shift right
|left to right|
|equality||left to right|
|&&||logical AND||left to right|
|||||logical OR||left to right|
|?:||ternary||right to left|
|= += -=
*= /= %=
&= |= ^=
<<= >>= >>>=
|assignment||right to left|
Note that for the calculations to work, each operand must have the correct type. For
example, if you have a memory IM1 that has a number as a string and you want to subtrack from
IM1 - 1, you need to convert the string in IM1 to an integer or a
int(IM1) - 1 or
float(IM1) - 1. The same rules
apply to concatenating a string and an integer. The integer has to be converted to a string.
"IT" + str(index)
In this example, the A1 section uses the For action to toggle 5 turnouts. The turnout user names are T1 thru T5. The index local variable is used to supply the number. The index value has to converted to a string before concatenating with the "T"
The A2 section is an example of a digital expression. This a simple one with just the "and" operator.
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 parantheses, optional one or several parameters separated by comma, and then closed by a right parantheses.
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.
One may notice that there is several modules with one or two functions in each module. For example, the module Math has the functions random() and sin(). The reason for so many modules for so few functions is that the number of functions is expected to grow. For example, the Math module should have cos(), tan(), atan() and sqrt(), but these are not implemented yet.
For JMRI developers: The functions are defined in the package jmri.jmrit.logixng.util.parser.functions and each module has its own Java class. Each function is its own class that implements the Function interface.
To make it easier to use the functions, each function has some documentation.
Each action/expression has a Formula functions button that opens a dialog box with documentation of the functions.
The functions are grouped in modules to make it easier to find the functions. Select the module you are interested in.
Then select the function you are interested in.
In this case, the function fastClock() take a string parameter which can have any of the values hour, min or minOfDay.
Some functions, for example the function random(), can take different
numbers of parameters. The default is
0.0 <= x < 1.0. Supplying
max or min and max values can change the range.
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 Function and implements these methods:
|getModule||The name of the module that the function belongs to|
|getName||The name of the function|
|getDescription||Description of the function for the user|
|getConstantDescriptions||Description of any constants|
|calculate||Calculate the function|
Example Jython script that defines the getBlockValue function that takes the name of a block as its parameter:
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())
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
The function calculate takes a number of arguments as a List<ExpressionNode>. We first check the number of arguments by calling the method size() and if that's correct, we get the arguments by calling the method get(index) where "index" is the index of the argument.
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 calculate on each argument we want to use.
We then do the calculation, which in this case finds the requested block and returns the current value.
Since the local variables symbol table is also passed to the calculate function, local
variables are also available to the script using
symbolTable(name, new value).
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.
A function may set turnouts, sensors, and other things on the layout. You may for example create the function setTurnout(turnout, newState). 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.
See the later chapter on threads (may not exist yet) for more information of LogixNG threads.
Copyright © 1997 - 2022 JMRI Community. JMRI®, DecoderPro®, PanelPro™, DispatcherPro™, OperationsPro™, SignalPro™, SoundPro™, TrainPro™, Logix™, LogixNG™ and associated logos are our trademarks. Additional information on copyright, trademarks and licenses is linked here.