Problem cancelling timer from lambda

Hi,

I’m having problems with cancelling scheduled timer, I’m pretty sure it used to work fine but now it doesn’t (I’m running OH 2.5.4-1).

I have created this simple rule:

var Timer timer = null

val cancelTimer = [|
    if (timer !== null) {
        logInfo("Test", "Cancelling timer")
        timer.cancel()
        timer = null
    } else {
        logInfo("Test", "Timer is null")
    }
]

rule "Test"
when
    Item gr_doors changed to ON
then
    if (timer === null) {
        timer = createTimer(now.plusSeconds(10), [|
            logInfo("Test", "Timer executed")
            timer = null
        ])
        logInfo("Test", "Timer created: " + timer.toString())
    }
end

rule "Test 2"
when
    Item gr_doors changed to OFF
then
    cancelTimer.apply()
end

When I open and close doors, I’d expect the “Timer executed” message not to log since timer should be cancelled, but in OH logs I see:

2020-05-06 19:15:48.247 [INFO ] [.eclipse.smarthome.model.script.Test] - Timer created: org.eclipse.smarthome.model.script.internal.actions.TimerImpl@dc8738
2020-05-06 19:15:52.223 [INFO ] [.eclipse.smarthome.model.script.Test] - Timer is null
2020-05-06 19:15:58.204 [INFO ] [.eclipse.smarthome.model.script.Test] - Timer executed

Which means cancelTimer lambda got executed but didn’t see the timer created in “Test” rule. Why doesn’t this work?

This code, as written, never could have worked. In the global context (i.e. where timer and cancelTimer are defined) there really is no context. This means cancelTimer can’t see timer. You have to pass timer as an argument for it to see it. This has been the case since OH 1.6.

Beyond that, except for the log statements that entire lambda is unnecessary. You can do the same thing with two lines of code:

timer?.cancel
timer = null

Finally, if you starting to head down the path of wanting to create lambdas to do stuff like this I cannot encourage you strongly enough to abandon Rules DSL and move to Scripted Automation. Right now Python is the best documented and has the most users and examples.

Rules DSL global lambdas are not thread safe, they suppress errors, everything needs to be passed to it as an argument but you can only have seven arguments, and they can only be accessed from the one .rules file. When I see lambdas used like this, it’s a code smell. You are heading towards hard to diagnose and random errors and brittle code that is hard to maintain.

Interesting syntax. Shouldn’t it be timer?.cancel()? Is there a similar construct in Python?

In Rules DSL the parens are optional if there are no arguments to the function. So timer.cancel and timer.cancel() are equivalent.

There is no ? operator I’m aware of in Python, but I’m not a Python expert. In rules DSL the ? means only do the stuff after the ? if the stuff before the ? is not null.

Thanks, didn’t know rules dsl have safe navigation operator. The lambda is the only way to get any sort of code reuse in rules dsl since there is no way to define function. I was trying to avaoid copying those two lines of code, but I guess cancelling already cancelled timer should not throw any error, so I might just keep it at timer?.cancel.

Not necessarily true. See the Design Patterns for lots of ways to avoid duplicative code. Separation of Behaviors, Associated Items, How to Structure a Rule, etc. all show various ways to get code reuse without lambdas. But if you insist on using functions, abandon Rules DSL now and use something that actually supports functions, like Python.

You must bend to Rules DSL. If you won’t bend, use a language that is easier to bend to work the way you want it to.