Rules: How to access varible located outside foreach loop

Hello,
How does one access a varible outside a foreachloop for use within the loop?

Example:

var String foo = “50”
var ArrayList sl = fixture.get(“switches”) as ArrayList
sl.forEach [ ts |
logInfo(“motion_tripped”, " setting dimmer: " + ts.name)
sendCommand(ts, foo)
]

The syntax checker in the OpenHAB Designer says:
“Cannot refer to a non-final variable foo from within a closure”

Thanks!
Brian

Unfortunately you can’t. Its a limitation of the language unfortunately. When I’ve encountered this sort of problem I’ve either found a way to not need the reference to the local variable or resorted to using a while loop, regular for loop, or Iterator.

I would love to be proven wrong though.

Bummer, thanks Rich!

You may be able to do it in this round-about manner:

import org.openhab.core.library.items.*
import org.openhab.core.library.types.StringType

var StringType foo = new StringType("50")

rule "test rule 2"
when
    Time cron "0/5 * * * * ?"
then
    var sl = GWindow.members
    sl.forEach [ ts |
        logInfo("test-const", " setting dimmer: " + ts.name)
        ts.sendCommand(foo)
    ]
end

Not sure how supported that’ll be, but it does compile, and it sends the command to the target Item (in my case derived from Group, but should be similar for you)

2 Likes

Alternatively as an ugly workaround, declare a number item and update it with the variable value?
Items file: Number myNumber

var String foo = “50”
myNumber.sendCommand(foo)

var ArrayList sl = fixture.get(“switches”) as ArrayList
sl.forEach [ ts |
logInfo(“motion_tripped”, " setting dimmer: " + ts.name)
sendCommand(ts, myNumber.state as DecimalType)
]

1 Like

That should work fine and I should elaborate on my original answer. You can access an outside variable in the foreach but only to read it. You can’t modify it.

An even easier approach (equally as ugly) would be to declare a hashMap or arryList using “val” with your variable in it. The container would be final so the foreach can get to it but you can still modify the stuff inside.

1 Like

Hi.

as long as you don’t have to modify “foo” it is working by defining it as final.
This is done by replacing

var String foo = "50"

with

val String foo = "50"

As this does not make sense in the most cases, try to use the StringBuilder class instead…

import java.util.StringBuilder


val StringBuilder foo = new StringBuilder
foo.append("50")

the methods of foo will be accessible in the forEach loop all the time.

Bye
Lars

2 Likes

@guessed @LarsK Thank you both!

Its a bit independent from the problem above. I guess the issue in your case is that .state is always a StringType. Try the following:

rule "follow"
  when TRIGGER changed
  then
    G.members.forEach(member|member.sendCommand(TRIGGER.state as OnOffType))
  end

The difference is that the parameter of the sendCommand is casted to the needed Type and not send as String.

I hope it works and helps. :slightly_smiling:

The best guard against Uninitialized is to use Persistence with restoreOnStartup (preferred) or a System started rule that sets your Items to a default (less preferred).

Indeed but in my experience, since I set up persistence nearly a year ago I have never seen an Undefined state in any of my rules. It is pretty rock solid in my experience, reliable enough that I have no checks for it in any of my rules.