Rule that can count an event

Hi,

i want to create a rule which counts an event and at a speicifc number triggers another item to be activated.

exsample: If the Doorbell is pushed five times within 10 seconds (only everyday, from 6am to 8pm) then the door opener is activated.
Thanks

This might be something to start with (untested!):

var Timer doorbellTimer = null

var doorbellCounter = 0

rule "push doorbell 5 times within 10 seconds to activate door opener"
when
  Item Doorbell changed to ON
then
  if (now.getHour() < 6 || now.getHour() >= 20) {
    if (doorbellCounter != 0) doorbellCounter = 0
    return;
  }

  doorbellCounter += 1

  if (doorbellCounter == 5) {
    doorbellTimer?.cancel()
    doorOpener.sendCommand(ON)
    doorbellCounter = 0
  } else {
    if (doorbellTimer === null) {
      doorbellTimer = createTimer(now.plusSeconds(10), [|
        doorbellTimer = null

        doorbellCounter = 0
      ])
    }
  }
end

How do you want the count to decay? For example Marco’s post ten seconds after the last time the button is pressed the count resets all the way back to 0. But you could have it count up and then gradually count down also.

If you are using UI rules you can put the test for time of day into the condition part of the rule instead on inline as part of the action of the rule.

Which rules language are you using?

thank you for the example. I will try it and let you know how it worked.

You are right, in DSL this might be a more elegant solution:

import java.util.Map

val Map<Number, Timer> doorbellTimers = newHashMap

var doorbellCounter = 0

rule "push doorbell 5 times within 10 seconds to activate door opener"
when
  Item Doorbell changed to ON
then
  if (now.getHour() < 6 || now.getHour() >= 20) {
    if (doorbellCounter != 0) doorbellCounter = 0
    return;
  }

  doorbellCounter += 1

  if (doorbellCounter == 5) {
    doorOpener.sendCommand(ON)
    doorbellCounter = 0
  } else {
    if (doorbellTimers.get(doorbellCounter) === null) {
      doorbellTimers.put(doorbellCounter, createTimer(now.plusSeconds(2), [|
        doorbellTimers.put(doorbellCounter, null)

        if (doorbellCounter > 0) doorbellCounter += -1
      ]))
    }
  }
end
1 Like

Given you have a Timer for each press of the button, you don’t really need doorbellCounter anymore. You can use doorbellTimers.size instead.

For those using JavaScript, Marco’s solution as a Script Action would be something like the following if using GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the develop.

NOTE: I’m just typing this in to the forum, it’s not been tested.

var OPENHAB_CONF = java.lang.System.getenv("OPENHAB_CONF");
load(OPENHAB_CONF+'/automation/lib/javascript/community/timerMgr.js');

this.timers = (this.timers === undefined) ? new TimerMgr() : this.timers;
this.uid = (this.uid === undefined) ? 0 : this.uid;

// The test for time would be in a Condition for the rule

this.uid++;
this.timers.check("Doorbell"+this.uid, "10s");

// I need to add a function to TimerMgr to get the count so the following isn't necessary
var count = 0;
for(var key in this.timers){
    count++;
}

if(count == 5) {
    doorOpener.sendCommand(ON);
    this.timersCancelAll();
}

I created TimerMgr for just such situations where one needs to manage a bunch of timers based on a key. The code above loads the TimerMgr library and initializes the TimerMgr Object and a one up counter so we can have a unique ID per Timer. We increment the uid and the call check on TimerMgr which checks to see if there already is a timer with that ID and if not creates one. The TimerMgr will handle clearing the Timer out once it expires and we don’t really need to do anything when the Timer does go off so that’s it. Then we get a count of the Timers in timers and if it’s 5 we trigger the actions and delete all the scheduled Timers.

The rule will be even simpler if I implement the count stuff in TimerMgr itself, for which I’m creating an issue right now so I don’t forget.

1 Like

Indeed, well, then for future reference:

import java.util.Map

val Map<Number, Timer> doorbellTimers = newHashMap

rule "push doorbell 5 times within 10 seconds to activate door opener"
when
  Item Doorbell changed to ON
then
  if (now.getHour() < 6 || now.getHour() >= 20) {
    return;
  }

  if (doorbellTimers.size == 4) {
    doorOpener.sendCommand(ON)
    
    for (i : 3 .. 0) {
      doorbellTimers.get(i)?.cancel()
    }
  } else {
    if (doorbellTimers.get(doorbellTimers.size) === null) {
      doorbellTimers.put(doorbellTimers.size, createTimer(now.plusSeconds(2), [|
        doorbellTimers.put(doorbellTimers.size, null)
      ]))
    }
  }
end

Going back to marcos reply, its not working for me. But i am probably
doing something wrong.

I have added the rule under Scrips in the OP3 setting section.

Is that correct?

Log says:

==> /var/log/openhab/openhab.log <==
2021-08-27 12:00:43.821 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘d600ba0e0a’ failed: :1:10 Expected ; but found DoorbellTimer
var Timer DoorbellTimer = null
^ in at line number 1 at column number 10

No.
@duiffie rule is intended to go in a xxx.rules file, and won’t work in GUI entry, because you have nowhere “outside” of your GUI rule to define “globals” that persist between rule runs.

You can do something similar, but it’ll need rewriting.

i have have entered a .rules with the rule from marco and i am getting this back:

2021-08-30 10:18:38.171 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model ‘rule-5doorbell.rules’
2021-08-30 10:18:38.835 [WARN ] [e.runtime.internal.RuleContextHelper] - Variable ‘Counter’ on rule file ‘rule-5doorbell.rules’ cannot be initialized with value ‘org.eclipse.xtext.xbase.impl.XNumberLiteralImpl@1cb2ced (value: 0)’: An error occurred during the script execution: null

Can you paste the full contents of the .rules file (by using code fences)?

I got it to work. had some typos from my side…thank you

1 Like