Proper way of recursion in Rules

I want to create a rule that whenever I open my window OH will wait 15 minutes to say “window is open for 15 mins” and then every 5 minutes it will remind me about it.

val ADD_MINUTES = 5
val HUMIDITY_MAX_LEVEL = 55
val INIT_DELAY = 15

rule "super-duper-rule"
when
    Item window received update OPEN
then
        var secondsPassed = 0;
        var nextMinutesToSay = INIT_DELAY
        while(secondsPassed / 60.0 < MAX_MINUTES_TO_RUN && window.state == OPEN) {
                Thread::sleep(1_000)
                secondsPassed++
                if (secondsPassed / 60.0 > nextMinutesToSay) {
                        say(nextMinutesToSay+ " minutes passed")
                        nextMinutesToSay += ADD_MINUTES
                }
        }
end

I’m not super happy with this solution cause it has Thread::sleep that will block whole rule, but I did not found a way to run createTimer with recursive calls.

Any ideas how to rewrite this rule?

It’s very simple with a timer…

val ADD_MINUTES = 5
val INIT_DELAY = 15
var Timer tWindow = null

rule "super-duper-rule"
when
    Item window changed
then
    tWindow?.cancel
    if(newState == OPEN)
        tWindow = createTimer(now.plusMinutes(INIT_DELAY), [
            val iMinutes = (now.toEpochSecond - window.previousState().timestamp.toEpochSecond)/60
            say(iMinutes.toString + " minutes passed")
            tWindow.reschedule(now.plusMinutes(ADD_MINUTES))
        ])
end

Don’t use received update if you don’t need to. You only want to execute the rule when the Item changed.
First thing, the rule will do is to cancel the timer, if there is any.
Then, only if the new state is OPEN, the timer will be created.
When the timer expires, the time from the last state change is queried from the default persistence. This is used to calculate the time since the last change. I’m pretty sure that it should be previousState(true), but I got an error with a strange message rrd4j does not allow querys without a begin date which makes no sense at all. Maybe there is an issue with rrd4j for this query.
after saying the amount of time which has passed, the timer gets rescheduled and that’s it.

Great rule!

Why not save the time of change in a variable instead of using persistence?

Thanks! I was not aware about reschedule and that you can take prev state time

Thanks :slight_smile:

I always try not to save information which is already available

This is what the rule template Threshold Alert and Open Reminder [4.0.0.0;4.9.9.9] does.

  1. Install that template from the Add-on store → Automation screen.
  2. create the rule that implements your alert, the name of the Item will be alertItem
  3. Put your window Items into a Group
  4. Instantiate an instance of the rule template with:
  • Triggering Group: the Group created in 3
  • Threshold State: OPEN
  • Comparison: ==
  • Alert Delay: PT15M
  • Reminder Period: PT5M
  • Alert Rule: Rule created in 2

Leave everything else set to the default and the rule you create in step 2 will get called when ever any member of the Group created in a step 3 remains in the OPEN state for 15 minutes and it gets called again every five minutes thereafter until the Item is no longer OPEN.

It also has features like the ability to call a rule when the window first changes to OPEN, when it changes to something other than OPEN (e.g. CLOSED), and you can define do not disturb periods where the alert rules will not be called. Custom timeouts can be defined on an Item by Item basis trough Item metadata so each door (in my case) can have it’s own unique alerting time and repeated alert times.

The only code you should need to write is your alert. The rule template handles the rest.

Here’s my rule that tells me when I leave a door open for too long which gets called by this rule template. You will see all I have to mess with is the alerting logic. All the Timer stuff is handled by the rule template.

configuration: {}
triggers: []
conditions: []
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript
      script: >
        var {alerting} = require('rlk_personal');


        var KEY = 'hasAlerted';

        var hasAlerted = cache.private.get(KEY, () => false);

        var item = items[alertItem];


        console.info(item.label + ' is now ' + item.state + ': alerting - ' + isAlerting + ' has alerted - ' + hasAlerted + ' initial alert - ' + isInitialAlert);


        if(!isAlerting && hasAlerted && !isInitialAlert) {
          alerting.sendAlert(item.label + ' is now closed: initial - ' + isInitialAlert, logger);
          cache.private.put(KEY, false);
        }

        else if(isAlerting && !hasAlerted) {
          alerting.sendAlert(item.label +' has been open for a long time! initial - ' + isInitialAlert, logger);
          cache.private.put(KEY, true); 
        }

        else {
          console.log('Not alerting or alert not ended');
        }
    type: script.ScriptAction

Rule templates support a lot of common use cases like this. They are a great resource.

That’s also interesting! I will keep an eye on this addon