LogixNG Reference - Chapter 99

Misc Items

+---------------------- Note ---------------------+ | The content in this file is not to be published.| +-------------------------------------------------+

LogixNG system names

System names in JMRI consists of a system prefix and a type letter. For LogixNG, the system prefix is usually 'I' and the type letter is 'Q'. But LogixNG consists of several sub types and if all of them would have its own type letter, the letters in the alphabet would run out. Therefore LogixNG has sub type letters, except for LogixNG itself.

There is usually no need to work directly with system names and it's recommended to let JMRI to auto generate them. But they can be created manually if desired.

LogixNG types and example system names

LogixNG type System name Auto system name
LogixNG IQ0001 IQ:AUTO:0001
Analog Action IQAA0001 IQAA:AUTO:0001
Analog Expression IQAE0001 IQAE:AUTO:0001
ConditionalNG IQC0001 IQC:AUTO:0001
Digital Action IQDA0001 IQDA:AUTO:0001
Digital Boolean action IQDB0001 IQDB:AUTO:0001
Digital Expression IQDE0001 IQDE:AUTO:0001
String Action IQSA0001 IQSA:AUTO:0001
String Expression IQSE0001 IQSE:AUTO:0001
Table IQT0001 IQT:AUTO:0001

"Auto system name" refers to system names that are generated automaticly.

All system names consists of the system prefix, the type letter, possible a sub type, possible :AUTO: and a number. The number may begin with zeros and the system name IQ1 is different from the system name IQ0001.

The LogixNG data types Map and Table are used to make generic LogixNGs. For a table, its columns and rows can also be accessed as NamedBeans.

LogixNG table structure

A Table is a data type that holds a two dimensional array, like a spreadsheet.

Example:

A B C D
 1  IQT22 Yard table
 2  West yard East yard North yard
 3  Left entrance of the yard
 4  Leftmost turnout IT101 IT201 IT301
 5  Left turnout IT103 IT203 IT303
 6  Right entrance of the yard
 7  Rightmost turnout IT112 IT212 IT312
 8  Right turnout IT114 IT214 IT314

LogixNG can use references to access layout items like turnouts in an indirect way. It's done by entering the system name or user name in curly brackets. If a ActionTurnout has the turnout IT1, it will access that turnout directly. But if the ActionTurnout has the turnout {IM2}, it will read that memory and if that memory has a string as a value, it will use that string value to find the turnout. So if the memory IM2 has the value IT5, and the ActionTurnout has the turnout [IM2], the ActionTurnout will read the memory IM2 and find out that it points to the turnout IT5 and therefore do it's action on turnout IT5. The benefit of this is that the Memory can be changed during execution and therefore the same ActionTurnout can be used to access different turnouts at different times.

It's possible to use indirect access recursive. If a ActionTurnout has the turnout {IM5}, and that Memory has the value {IM14}, and that the Memory IM14 has the value IT3, the ActionTurnout will access IT3. The ActionTurnout has an indirect turnout IM5 so it will read that Memory. And since that memory has the value {IM14} which also is an indirect access, it will read the memory IM14 and find out that it has the value IT3, and therefore use IT3. This is also true for tables. If the cell IQT1[5,3] has the value '{IM3}', and the memory IM3 has the value 'IT5', the cell IQT1[5,3] will point to IT5.

A table can be used to create a lookup table. It's accessed by either its system name or its user name, followed by a left square bracket, the name of the column, a comma, the name of the row, and a right square bracket.

Instead of the names of the row and column, it's also possible to use the row number or the column number. Note that row 1 has the system name and user name of the table, row 2 has the names of the columns, and column 1 has the names of the rows. Note that for columns, 1 is row A, 2 is row B, 22 is V, 23 is W, 26 is Z, 27 is AA and 28 is AB.

Note that spreadsheet software, like Excel and LibreOffice Calc, has cell <column letter><row number> while references in JMRI has table[row,column]. Example: Cell B3 is table[3,2], since B3 is row 3 and column 2.

Example from the table above. These examples assume that IM3 has the value 'West yard', IM4 has the value 'Rightmost turnout' and IM5 has the value 'IQT22'.

Cell Value Note
IQT22[1,1] IQT22 Cell A1 has the system name of the table
IQT22[1,2] Yard table Cell B1 has the user name of the table
IQT22[2,2] West yard Cell B2 has the name of column B
IQT22[4,1] Leftmost turnout Cell A4 has the name of row 4
Yard table[4,1] Leftmost turnout The user name of the table can be used to access the table
IQT22[5,3] IT203 Cell C5 has the value 'IT203'
IQT22[Left turnout,North yard] IT303 Column 'North yard' and row 'Left turnout' has the cell D5 with the value 'IT303'
Yard table[Left turnout,North yard] IT303 Column 'North yard' and row 'Left turnout' has the cell D5 with the value 'IT303'
IQT22[Leftmost turnout,{IM3}] IT101 IM3 is in curly brackets and have the value 'West yard' so this points to cell B4
IQT22[{IM4},East yard] IT212 IM4 is in curly brackets and have the value 'Rightmost turnout' so this points to cell C7
IQT22[{IM4},{IM3}] IT112 Column {IM3} and row {IM4} points to cell B7
{IM5}[{IM4},{IM3}] IT112 Even the table name can be accessed indirectly

Note that a Memory can point to a table. For example, if the memory IM7 has the value '{Yard table[{IM3},Leftmost turnout]}', LogixNG will look at IM7, and find that it's value is in curly brackets. It will then resolve the value inside these curly brackets, which is 'Yard table[{IM3},Leftmost turnout]'. It will then resolve the value of IM3 which has the value 'West yard'. It will then get the table cell 'Yard table[West yard,Leftmost turnout]' which is cell B4 with the value 'IT101'.

The ActionForEach iterates over a comma separated list of values. It can be used with tables by using the keywords __columns__ and __rows__ . Note that it's two underscore characters before and after. {IQT1[__columns__]} gives a comma separated list of all the column names in the table IQT1. {IQT1[__rows__]} gives a comma separated list of all the row names in the table IQT1. It's also possible to write {Yard table[__columns__]} and {Yard table[__rows__]}. You can even use this syntax in ActionMemory to assign a Memory the list of column names or row names. Note however that column names or row names that are empty is seen as colums or rows with a comment and therefore they are not included.

Tables are loaded by a start up action. They are created in a spreadsheet, like Microsoft Excel or LibreOffice Calc and then exported to a CSV file, separated by TAB characters. The table is read only and is not stored in the panel filee, so it must be reloaded each time JMRI starts.

Note

If a name has the characters comma, left or righ square bracket or left or right curly brackets, these characters must be escaped by preceding them with a backslash. Examples: \, \[ \] \{ \} \\

If a reference contains a backslash, it will take some more time to evaluate it than if it doesn't contain any backslash. So if it's possible to not use these special characters in references or names of beans, it's recommended.

Scripts

All types of LogixNG actions and expressions may use Python/Jython scripts. Below follows the description of scripts for digital expressions, but the same pattern applies to all the types of actions and expressions.

The digital expression that handles scripts is ExpressionScript.

The script that is executed by ExpressionScript must declare a class that extend the class AbstractScriptDigitalExpression. The script must override the method evaluate(). For a script that listen to other beans, it's recommended to also override the methods registerScriptListeners() and unregisterScriptListeners().

When called, the script must tell the caller which class to use. It does that by setting the variable params._scriptClass.set(instance)

Example of a digital expression script:

import jmri

class MyExpression(jmri.jmrit.logixng.digital.expressions.AbstractScriptDigitalExpression):

  l = lights.provideLight(\"IL1\")

  def registerScriptListeners(self):
    self.l.addPropertyChangeListener(\"KnownState\", self);

  def unregisterScriptListeners():
    l.removePropertyChangeListener(\"KnownState\", this);

  def evaluate(self):
    return self.l.commandedState == ON

params._scriptClass.set(MyExpression(params._parentExpression))

ActionForEach

ActionForEach is a for-loop.

This action has a list of items. Either a constant list, or it can use a NamedBeanMap.

The ActionForEach action loops thru the list. It takes the first item in the list, assigns it to a Memory, and then executes its child action. Then it takes the second item in the list, assigns it to the Memory, and executes the child action.

List of digital boolean actions

This section lists all the digital boolean actions that is currently available with LogixNG.

Digital boolean actions takes a true/false value and do something. The most common use is the digital action Logix together with the digital boolean action OnChange.

Digital boolean actions have system names with the letters DB (for Digital Boolean action). Example: IQDB002 or IQDB:0053. System names with colon are auto generated system names.

Items in red may need rethink or removal. I have ideas, but not always sure if these ideas are worth keeping. /Daniel

Items in blue is not up to date with documentation. The documentation tells how they should work, but the code is not finished yet. /Daniel

LogixNG table structure

A Table is a data type that holds a two dimensional array, like a spreadsheet.

Example:

A B C D
 1  IQT22 Yard table
 2  West yard East yard North yard
 3  Left entrance of the yard
 4  Leftmost turnout IT101 IT201 IT301
 5  Left turnout IT103 IT203 IT303
 6  Right entrance of the yard
 7  Rightmost turnout IT112 IT212 IT312
 8  Right turnout IT114 IT214 IT314

LogixNG can use references to access layout items like turnouts in an indirect way. It's done by entering the system name or user name in curly brackets. If a ActionTurnout has the turnout IT1, it will access that turnout directly. But if the ActionTurnout has the turnout {IM2}, it will read that memory and if that memory has a string as a value, it will use that string value to find the turnout. So if the memory IM2 has the value IT5, and the ActionTurnout has the turnout [IM2], the ActionTurnout will read the memory IM2 and find out that it points to the turnout IT5 and therefore do it's action on turnout IT5. The benefit of this is that the Memory can be changed during execution and therefore the same ActionTurnout can be used to access different turnouts at different times.

It's possible to use indirect access recursive. If a ActionTurnout has the turnout {IM5}, and that Memory has the value {IM14}, and that the Memory IM14 has the value IT3, the ActionTurnout will access IT3. The ActionTurnout has an indirect turnout IM5 so it will read that Memory. And since that memory has the value {IM14} which also is an indirect access, it will read the memory IM14 and find out that it has the value IT3, and therefore use IT3. This is also true for tables. If the cell IQT1[5,3] has the value '{IM3}', and the memory IM3 has the value 'IT5', the cell IQT1[5,3] will point to IT5.

A table can be used to create a lookup table. It's accessed by either its system name or its user name, followed by a left square bracket, the name of the column, a comma, the name of the row, and a right square bracket.

Instead of the names of the row and column, it's also possible to use the row number or the column number. Note that row 1 has the system name and user name of the table, row 2 has the names of the columns, and column 1 has the names of the rows. Note that for columns, 1 is row A, 2 is row B, 22 is V, 23 is W, 26 is Z, 27 is AA and 28 is AB.

Note that spreadsheet software, like Excel and LibreOffice Calc, has cell <column letter><row number> while references in JMRI has table[row,column]. Example: Cell B3 is table[3,2], since B3 is row 3 and column 2.

Example from the table above. These examples assume that IM3 has the value 'West yard', IM4 has the value 'Rightmost turnout' and IM5 has the value 'IQT22'.

Cell Value Note
IQT22[1,1] IQT22 Cell A1 has the system name of the table
IQT22[1,2] Yard table Cell B1 has the user name of the table
IQT22[2,2] West yard Cell B2 has the name of column B
IQT22[4,1] Leftmost turnout Cell A4 has the name of row 4
Yard table[4,1] Leftmost turnout The user name of the table can be used to access the table
IQT22[5,3] IT203 Cell C5 has the value 'IT203'
IQT22[Left turnout,North yard] IT303 Column 'North yard' and row 'Left turnout' has the cell D5 with the value 'IT303'
Yard table[Left turnout,North yard] IT303 Column 'North yard' and row 'Left turnout' has the cell D5 with the value 'IT303'
IQT22[Leftmost turnout,{IM3}] IT101 IM3 is in curly brackets and have the value 'West yard' so this points to cell B4
IQT22[{IM4},East yard] IT212 IM4 is in curly brackets and have the value 'Rightmost turnout' so this points to cell C7
IQT22[{IM4},{IM3}] IT112 Column {IM3} and row {IM4} points to cell B7
{IM5}[{IM4},{IM3}] IT112 Even the table name can be accessed indirectly

Note that a Memory can point to a table. For example, if the memory IM7 has the value '{Yard table[{IM3},Leftmost turnout]}', LogixNG will look at IM7, and find that it's value is in curly brackets. It will then resolve the value inside these curly brackets, which is 'Yard table[{IM3},Leftmost turnout]'. It will then resolve the value of IM3 which has the value 'West yard'. It will then get the table cell 'Yard table[West yard,Leftmost turnout]' which is cell B4 with the value 'IT101'.

The ActionForEach iterates over a comma separated list of values. It can be used with tables by using the keywords __columns__ and __rows__ . Note that it's two underscore characters before and after. {IQT1[__columns__]} gives a comma separated list of all the column names in the table IQT1. {IQT1[__rows__]} gives a comma separated list of all the row names in the table IQT1. It's also possible to write {Yard table[__columns__]} and {Yard table[__rows__]}. You can even use this syntax in ActionMemory to assign a Memory the list of column names or row names. Note however that column names or row names that are empty is seen as colums or rows with a comment and therefore they are not included.

Tables are loaded by a start up action. They are created in a spreadsheet, like Microsoft Excel or LibreOffice Calc and then exported to a CSV file, separated by TAB characters. The table is read only and is not stored in the panel filee, so it must be reloaded each time JMRI starts.

curly brackets, these characters must be escaped by preceding them with a backslash. Examples: \, \[ \] \{ \} \\

If a reference contains a backslash, it will take some more time to evaluate it than if it doesn't contain any backslash. So if it's possible to not use these special characters in references or names of beans, it's recommended.

LogixNG is both the name of the JMRI tool and the name of the main component of that tool.

Enabled and Execution enabled

The intention with LogixNG is to be similar to Logix, in order to make it easier for users of Logix to understand how LogixNG works.

This has resulted in a big challenge when it comes to Enabled, since in Logix, a Conditional that is not Enabled will still calculate its variables but not execute its actions.

In LogixNG, there is a need to be able to completely disable the calculation too, and not only the execution of actions. Therefore, in LogixNG, if a LogixNG or a ConditionalNG is not enabled, it's not evaluated at all.

But LogixNG also needed to be somewhat compatible with Logix, for example to be able to import Logixs to LogixNG. In order to solve that, LogixNG has execution enabled. If a LogixNG or a ConditionalNG doesn't have execution enabled, it's only evaluated but not executed.

There is however a problem with execution enabled . Not all of the actions supports this. Only the actions IfThenElse and Logix supports execution enabled.

Scripts

All types of LogixNG actions and expressions may use Python/Jython scripts. Below follows the description of scripts for digital expressions, but the same pattern applies to all the types of actions and expressions.

The digital expression that handles scripts is ExpressionScript.

The script that is executed by ExpressionScript must declare a class that extend the class AbstractScriptDigitalExpression. The script must override the method evaluate(). For a script that listen to other beans, it's recommended to also override the methods registerScriptListeners() and unregisterScriptListeners().

When called, the script must tell the caller which class to use. It does that by setting the variable params._scriptClass.set(instance)

Example of a digital expression script:

import jmri

class MyExpression(jmri.jmrit.logixng.digital.expressions.AbstractScriptDigitalExpression):

  l = lights.provideLight(\"IL1\")

  def registerScriptListeners(self):
    self.l.addPropertyChangeListener(\"KnownState\", self);

  def unregisterScriptListeners():
    l.removePropertyChangeListener(\"KnownState\", this);

  def evaluate(self):
    return self.l.commandedState == ON

params._scriptClass.set(MyExpression(params._parentExpression))

Indirect adressing of NamedBeans

In some cases, it may be useful to use indirect addressing of NamedBeans.

This can be done in two ways:

Using a Memory that has the name of the NamedBean

Lets say we have an ActionTurnout that should throw a particulat turnout. But we wnat to be able to select which turnout to throw at a later time. We can solve that by storing the system name or user name in a Memory. We then tell the ActionTurnout to read the Memory and lookup the turnout on the fly.

Since LogixNG fully supports string manipulation and calculations of both integers and floating point values, where are plenty of possibilities here.

Warning

There is one major drawback with this. Since LogixNG doesn't know in advance which NamedBeans will be accessed indirectly, it's not possible for LogixNG to automaticly register listeners to the named beans that should trigger on change. For example, an ExpressionTurnout registers a listener on the turnout, if the name of the turnout is given, but if the ExpressionTurnout is using indirect addressing of the turnout, it's not able to do that.

The solution is to use the ActionListenOnBeans and tell this action which beans to listen on. Any time any property of any of the named beans this action listen to, the ConditionalNG will be executed.

Example:

A ConditionalNG is using turnouts IT1, IT2, IT3, IT4 and IT5 in it's expressions and wants each of them to trigger on change. Turnouts IT1 and IT3 and IT4 are directly accessed in expressions and IT2, IT3 and IT5 is indirect accessed.

Each turnout that is directly addressed in an expression will the ConditionalNG listen to automaticly, so IT1, IT3 and IT4 will be listen to. But in order to listen on turnouts IT2 and IT5, the ActionListenOnBeans needs to be used.

The ActionListenOnBeans can be placed anythere in the ConditionalNG tree, as long as it and its parents are enabled. If you disable this action, or any of its parent actions, the ActionListenOnBeans will not listen on its named beans.

ActionFor

ActionFor is a for-loop. It has four children.

Then ActionFor is executed, the Init action is run once. After that, the Condition expression is evaluated. If the condition returns true, the Action action is executed and at last the Next action is executed. Then the process is repeated, except that the Init action is only executed once.

ActionForEach

ActionForEach is a for-loop.

This action has a list of items. Either a constant list, or it can use a NamedBeanMap.

The ActionForEach action loops thru the list. It takes the first item in the list, assigns it to a Memory, and then executes its child action. Then it takes the second item in the list, assigns it to the Memory, and executes the child action.

The Logix action reads a digital child expression and then executes boolean actions. Each boolean action takes the result from the expression and takes action accordingly. The recommended child boolean actions for this action is the OnChange action. The Logix action, together with the OnChange child boolean actions, acts like the JMRI Logix, with explains the name of this action.

List of digital boolean actions

This section lists all the digital boolean actions that is currently available with LogixNG.

Digital boolean actions takes a true/false value and do something. The most common use is the digital action Logix together with the digital boolean action OnChange.

Digital boolean actions have system names with the letters DB (for Digital Boolean action). Example: IQDB002 or IQDB:0053. System names with colon are auto generated system names.

Items in red may need rethink or removal. I have ideas, but not always sure if these ideas are worth keeping. /Daniel

Items in blue is not up to date with documentation. The documentation tells how they should work, but the code is not finished yet. /Daniel