For/While Loops?

Hey All,

New to openHab, and over ten years since I’ve written anything in Java (mostly Python since). Anyway, I’m playing around, trying to get a test system going, and I can’t for the life of me figure out how to get a simple for/while loop running. Any thoughts would be appreciated. I’m running the code below, which seems correct according to Java syntax, but it’s giving me the following error:

Multiple markers at this line
- no viable alternative at input ')'
- Couldn’t resolve reference to JvmIdentifiableElement ‘+’.
- Couldn’t resolve reference to JvmIdentifiableElement ‘i’.
- This expression is not allowed in this context, since it doesn’t cause any side effects.
- Couldn’t resolve reference to JvmIdentifiableElement ‘<’.
- mismatched input ‘=’ expecting ':'

import org.openhab.model.script.actions.Timer var Timer timer = null

rule “Test”
when
/* Seconds, Minutes, Hours, Day-of-Month, Month, Day-of-Week, Year (optional field)*/
Time cron “0 * * ? * * *”
then
for(int i=1; i<4; i++){
sendCommand(LampBedRoom, ON)

    timer = createTimer(now.plusSeconds(10)) [|
        sendCommand(LampBedRoom, OFF)
        ]
    timer=null    
    
}

end

You are in luck. The rules language is not Java. It is a Domain Specific Language with the same lineage as Xtend. And for and while loops do not have the same syntax. The big difference for a traditional for loop like this is that you declare a variable using “var”, not its data type. One of the features of Xtend is that it is dynamically typed and figures out the type for you (in most cases), similar to Python.

So this should work in Xtend:

for(var i=1; i<4; i++) {

But openHAB rules are not Xtend and this doesn’t work. Gah! so we have to use the while loop thusly:

var i = 1
while ((i=i+1) < 4) {
    // do stuff
}

For completeness, usually when we are using for loops in openHAB rules we are actually iterating over a list of things, most often the list of members of a group. In this case we have some handy methods to use.

This will log the value of all the Items in myGroup:

myGroup.members.forEach[ item | logInfo("My Rule", item.name + " = " + item.state.toString) ]

This will log the value of all the Items in myGroup that are currently set to ON:

myGroup.members.filter(item|item.state == ON).forEach[item | logInfo("My Rule", item.name + " is ON")]

When you get some more experience with openHAB, come back here as these two lines represent, in my opinion, the most powerful constructs in rules creation.

Finally the rule makes no sense.

As written (assuming the for loop worked) this is what the rule would do:

Every minute (i.e. every 0th second of the minute) the rule will execute.

Every time the rule executes it sends the ON command to LampBedRoom, creates a timer for ten seconds in the future, then nulls out the timer three times in rapid succession.

My concerns are:

  • The body of the for loop looks like something you would only want to execute once, not three times within milliseconds of each other
  • Why store the timer in a variable called “timer” if you immediately null “timer” back out, essentially losing your reference to the timer? I’m honestly not sure what happens in this case. It might work just fine (i.e. the timer still exists out there somewhere and will trigger at the right time) or it might not (i.e. it will be collected by the garbage collector before it can trigger). It is also bad practice, in my opinion, to create a timer and throw away your reference to it, even if it will run OK.

Now I’m going to take a guess and say what you really want to do is have the light turn on for ten seconds, turn off then turn on again for ten seconds three times. If this is the case this would be a better approach:

Items:

Switch LampTest
Switch LampBedRoom ...

Rule:

rule "Test"
when
    Item LampTest received command
then
    var i = 0
    while((i=i+1) < 3) {
        sendCommand(LampBedRoom, ON)
        Thread::sleep(10000)
        sendCommand(LampBedRoom, OFF)
        Thread::sleep(10000)
    }
end

Put LampTest on your sitemap and you can manually trigger the rule rather than having it trigger every minute.

11 Likes

Thanks Rlkoshak, that’s EXACTLY what I was looking for. Glad you pointed the timer issue, I assumed the timer and sleep were basically the same thing. Tried it out with the timer, just for fun, and sure enough it went bezerk.

Timers are very powerful and something you will need to become familiar with eventually. A Timer is a way to schedule some code to execute in the future asynchronously. Once the timer is created the rest of your rule will execute and exit.

For example, I have a rule that gets triggered when openHAB decides no one is home. However, I may just be in the back yard or going to get the mail and will be right back so I don’t want to house to go into away mode until it is sure no one is home. So I set a Timer to go off in five minutes. The code in the timer double checks whether I’m still gone and only then will it set the house into away mode. If I returned the code in the timer exits without doing anything.

Have fun!

Just to emphasize @rlkoshak 's point, in most cases with timer-scheduled actions and non-trivial timer-lengths, you WILL want to check something(s) at execution-time of the timer’s command, i.e. , the sendCommand clause (usually), viz,

timer = createTimer(now.plusSeconds(10)) [|
        sendCommand(LampBedRoom, OFF)
        ]

Search for “PROXY” or see this discussion Patterns/Proxy/Etc for multiple examples.

For loops you can use also following syntax:

    (1..3).forEach[
        sendCommand(LampBedRoom, ON)
        Thread::sleep(10000)
        sendCommand(LampBedRoom, OFF)
        Thread::sleep(10000)
    ]
9 Likes

I never thought of doing it like that. Nice

When you put If … then condition in this loop and condition gives true answer modify the i value above the max limit and it jumps out of the loop. Perfect, thanks!

Rlkoshak thank you, helped me a lot!

Next, I want to use the i for selecting data from JSON data.
I did it this way (working) but expect it can be done smarter…

       var i = -1
        while ((i=i+1) < 4) {
            val _name = "$.["+i+"].name"
            val _serial = "$.["+i+"].serialNumber"
            val _type = "$.["+i+"].productType.type"
            val _tagname = transform("JSONPATH", _name, _mqtt)
            val _messageType = transform("JSONPATH", _type, _mqtt)
            val _id = transform("JSONPATH", _serial, _mqtt)

The following was tried but failed

            val _tagname = transform("JSONPATH", "$.[i].name" , _mqtt)
            val _messageType = transform("JSONPATH", "$.[i].serialNumber" , _mqtt)
            val _id = transform("JSONPATH", "$.["i"].productType.type" , _mqtt)

Any hint to improve or was it done correct.
(As you guessed, I’m not a professional programmer but hobbist)
Ferry

In Rules DSL this is probably as good as it gets.

I’d probably use @druciak’s approach with a forEach instead of the while loop.

(1..4).forEach[ i |
    val _name = "$.["+i+"].name"
    val _serial = "$.["+i+"].serialNumber"
    val _type = "$.["+i+"].productType.type"
    val _tagname = transform("JSONPATH", _name, _mqtt)
    val _messageType = transform("JSONPATH", _type, _mqtt)
    val _id = transform("JSONPATH", _serial, _mqtt)
]

That’s because "$.[i].name" is a meaningless JSONPATH. i isn’t a number.

Maybe consider to split the string in another way:

(1..4).forEach[ i |
    val      strHead = "$.[" + i + "]."
    val     _tagname = transform("JSONPATH", strHead + "name", _mqtt)
    val _messageType = transform("JSONPATH", strHead + "productType.type", _mqtt)
    val          _id = transform("JSONPATH", strHead + "serialNumber", _mqtt)
    ...
]

Another thing… it may be that you want the data from a specific part, but the node has no name. Let’s say you’re searching only for serialNumber 1234, then it would be easier to select it directly via JSONpath:

_tagname = transform("JSONPATH", "$.[?(@.serialNumber=='1234')].name",_mqtt)