[SOLVED] Increase/decrease Item value in steps until given state

HI Folks,

I’m trying to manipulate a value with while function and timer…

looks like this:

Rule "increase volume"
when
    Time cron "0 30 10 * * ? *" or
    Item TestSwitch received command ON
then
    val vol = 50
    while ( (vol=vol+1) < 86 ) {
        volTimer = createTimer(now.plusSeconds(15))[ |
            SqueezeLivingRoomVolume.sendCommand(vol)
            logInfo("Squeeze", "Living Room: increasing volume to "+vol)
            volTimer = null
         ]
    }
end

but this isn’t working.

log throws this error

==> /var/log/openhab2/openhab.log <==
2019-02-19 19:02:19.670 [ERROR] [ntime.internal.engine.RuleEngineImpl] 
- Rule 'increase volume':
An error occurred during the script execution:
Couldn't invoke 'assignValueTo' for feature JvmVoid: 
(eProxyURI: squeeze.rules#|::0.2.2.2.0.2::0::/1)

i have no idea what this error is telling me…
any suggestions??

Not an expert on this, but shouldn’t ‘while’ be ‘if’?

An if statement checks if an expression is true or false, and then runs the code inside the statement only if it is true. The code is only run once

A while statement is a loop. Basically, it continues to execute the code in the while statement for however long the expression is true.

as far as I know… and in my case I need a loop which executes the timer until the desired value is reached.

edit:

I googled for it but still don’t really understand what

Couldn't invoke 'assignValueTo' for feature JvmVoid

is trying to tell me…

I came across this post - Parallel while loops

But I also see Rich typing up some info, he probably can provide better advice than I. :wink:

This code can’t possibly be what you intend to do. Here is what it does, in English, assuming it worked as written without the error.

  1. Initialize the vol variable to 50

  2. enter a while loop that will loop from 50 to 85 (35 iterations).

  3. Inside each iteration create a Timer that goes off in 15 seconds.

  4. Inside the timer send the vol value to the Squeezebox.

As a result, you will end up with 35 Timers that in 15 seconds (plus a few milliseconds) all run at the same time and send the command to the Squeezebox at pretty much the same time.

It looks like what you are after is a Thread::sleep, not a Timer. Thread::sleep(x) vs timers in rules. A Timer schedules something to take place in the future. A Thread::sleep causes the Rule to wait the given amount of time before continuing.

But Thread::sleep is a dangerous thing to use. Long running Rules can cause all sorts of problems.

So use a Design Pattern: Looping Timers instead.

var Timer volTimer = null

rule "increase volume"
when
    Time cron "0 30 10 * * ? *" or
    Item TestSwitch received command ON
then
    volTimer?.cancel

    var vol = 51
    volTimer = createTimer(now, [|
        SqueezeLivingRoomVolulme.sendCommand(vol)
        logInfo("Squeeze", "Living Room: increasing volume to " + vol)

        vol = vol + 1
        if(vol < 86) volTimer.reschedule(now.plusSeconds(15))
        else volTimer = null
    ])
end    

NOTE: The error message you are seeing is almost certainly caused by the fact that you are trying to assign a new value to a val. val means constant. You need to define vol as a var.

Also, I’m not sure if you can embed the iteration inside the while loop like that. But a while loop is not the right loop to implement this anyway. You would want to use a for loop.

for(var vol = 50; vol < 86; vol = vol + 1) {
1 Like

Hi Rich,

you’re absolutely right… I tested around a bit - disturbed by a phone call - but got stuck with Thread::sleep what I was trying to avoid because of the known side-effects…

rule "increase volume"
when
    Time cron "0 30 10 * * ? *" or
    Item TestSwitch received command ON
then
    while ( (vol=vol+1) < 86 ) {
        SqueezeLivingRoomVolume.sendCommand(vol)
        logInfo("Squeeze", "Living Room: increasing volume to "+vol)
        Thread::sleep(15000)
    }
end

but it’s working…

I will try your code tomorrow because its getting late I have to go to bed now :wink:

will post my results tomorrow

thanks

Ok its working! Nice! I didn’t know that there is this easy way to restart a timer

volTimer.reschedule()

btw could you please explain the questionmark in

volTimer?.cancel

:wink: and I don’t really understand why the timer has to be canceled before it is running… or is this because of using the reschedule feature?? Thanks for your help!! I’m still learning…

It’s the same as

if(volTimer !== null) volTimer.cancel

You cannot guarantee that this Rule will not be triggered again while it is already processing a previous event (i.e. volTimer is not null). This Rule takes almost 9 minutes to complete and you can trigger it manually.

There are two ways to avoid ending up with two copies of the Timer running in this case. Cancel the old one before starting the new one, which is what the above code does. Or ignore the new event and let the old Timer continue to process. That would replace the volTimer?.cancel with

if(volTimer !== null) return;

thanks this helps me to understand :slight_smile:

the manual triggering was only for testing purposes… but you’re right the rule takes a long period of time to end.
Good to know that the volTimer?.cancel isn’t essentually neccessary but will provide safety.

Thanks again for the clarification!