How to make an item that counts things

Hey guys,

I hope I am posting this in the right place. The members of this forum have taught me so much over the years and so I want to give back in a small way by posting this thread.

Recently I had the need to count how many times something happened and was looking for a way to do it via rules.

In my case I have a rule that sends me a notification in certain cases but wanted to limit the number it was sending throughout the day. This could be used for a variety of other purposes though.

I started off by making the dummy item I will use to do the counting:

Dimmer CounterItem //counts the number of times something happens

Then I made a rule to do the counting:

EDIT: Changes incorporated from post by Rossko57 below.

rule "Counting Rule"
when 
//something happens
//Item is updated.
//etc.
then
  var addone = 1   // first count if needed
  if (CounterItem.state instanceof DecimalType) {
  addone = ((CounterItem.state as DecimalType).intValue + 1)
  }
   postUpdate(CounterItem,addone)
   logInfo("counting", "CounterItem set to: " + addone) //per comment from Rossko57 below, don't use 'CounterItem.state.toString' here because the item state probably wont update by the time this rule runs.
 end

If the initial state of CounterItem is 0, this rule will change it to 1. The next time the rule runs, it will change it to 2 and so on. For fun, you can make a switch item and set the rule to run when that switch item is updated. Keep flipping the switch while watching the logs and youll see the number grow every time.

To implement this into another rule you can simply use something like the below statement in another rule. In my case this limited the number of notifications:

if(CounterItem.state < 3) {
//do stuff
//send notification
//etc.
}
else { //don't do stuff
     logInfo("home.rules", "notification not sent because 3 have already been sent today.")
 }

Finally I have another rule reset the state of CounterItem to 0 after I want the counting period to be over. for me its at 12:01 am every day.

rule "Counting reset"
when
    Time cron "0 1 0 1/1 * ? *" //12:01am everyday
then
    logInfo("home.rules", "CounterItem reset to 0")
    CounterItem.postUpdate(0) 
end

Hope this helps someone out there!
JM

3 Likes

EDIT: Per Rossko57’s comment below, i have updated the OP per his suggestions to avoid this issue.

I forgot to mention this in the OP:

the counting rule will fail when you first make the CounterItem because it’s initial state upon creation is null and it can’t add 1 to null. The solution to this is to run the reset rule first, setting it to zero, then the counting rule will work.

Couple of little tips

Yes, you should use addone in the log, as per the comment - the previous postUpdate willnot yet have completed on most systems, the rule does not stop and wait for that tohappen.

You can auto-fix the start-up error with

then
   var addone = 1   // first count if needed
   if (CounterItem.state instanceof DecimalType) {
      addone = ((CounterItem.state as DecimalType).intValue + 1)
   }
...
1 Like

Thanks for the comment. Good advice on using the addone variable in the logs.

I am not familiar with the ‘instanceof’ wording but that seems very useful! I really like the way you conditioned addone on the state of CounterItem.

I updated the OP accordingly.

This is a nice tutorial. I like the simplicity and clarity of it. Thanks for posting! I moved it to the Solutions category as most of the good tutorials like this one end up there. It helps users find them.

My biggest comment is the docs (and I concur) state that when you can, you should use the method on the Item instead of the Action when updating or sending a command.

CounterItem.postUpdate(addone)

If one were to want to use this in JavaScript in the UI it would look something like:

triggers: []
conditions: []
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript
      script: >
        var currVal = if(items["CounterItem"].class === DecimalType.class) ?
        items["CounterItem"] : 0;

        events.postUpdate("CounterItem", (currVal + 1));
    type: script.ScriptAction

Note that the triggers are not defined. That’s an exercise to the student. (I’ve also not tested it, there might be a typo).

You could expand this concept to become more generic. For example, let’s say you have lots of things you want to independently count. Instead of copy/paste/editing this rule over and over, you could make it more generic.

  • Create a Group Item with all the Items you want to count.
  • Create a Count Item for each member of the new Group named the same as the Item with “_Count” appended
  • Put the new Count Items into a Group too.

Then in Rules DSL it would be something like:

rule "Counting Rule"
when
    Member of Counting changes // or any other desired Item event
then
    val countItem = CountGroup.members.findFirst[ i | i.name == triggeringItem.name+"_Count" ]
    var currCount = if(countItem.state instanceof DecimalType) countItem.state else 0
    countItem.postUpdate(currCount + 1)    
end

The reset rule would be something like

rule "Reset counts"
when
    Time cron 0 1 * * * ? * //I've no idea if this is right
then
    CountGroup.sendCommand(0)
end

The command will be forwarded to all members of CountGroup, resetting them all to 0 in one line.

As a JavaScript UI Rule the Script Action would be something like

var currVal = if(items[event.itemName+"_Count"].class === DecimalType.class) ?
items[event.itemName+"_Count"] : 0;

Notice with clever naming and a Group we can now use the same rule, and that rule is really no more complicated, to keep up with any number of counters. :slight_smile:

I’ve actually implemented an alternative approach to solving this problem using a timestamp. I’ve a library located at GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the development of new Rules. called rateLimit. The way it works is you have a class with a run method where you can pass it a function to execute and a timeout. If you call run again (even with a different function) before the last timeout, it will drop it.

Thus I can do something like:

from community.rate_limit import RateLimit

latch = RateLimit()
...

    # From a Rule, call the latch to limit how often the a command can be sent.
    # Let's say we don't want an alert more than once per hour. Any subsequent
    # calls to this latch will be dropped until an hour has passed.
    latch.run(lambda: events.sendCommand("Alert", "Somethings wrong!"), hours=1)

If that rule runs again and tries to run before the hour is up, the alert will not be sent.

I’ve a JavaScript version not yet posted to my github.

var RateLimit = function() {
  'use strict';

  var OPENHAB_CONF = java.lang.System.getenv("OPENHAB_CONF");
  this.ZonedDateTime = Java.type("java.time.ZonedDateTime");
  this.log = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.RateLimit");
  this.log.debug("Building the RateLimit object.");
  load(OPENHAB_CONF+'/automation/lib/javascript/community/timeUtils.js');
  this.until = this.ZonedDateTime.now().minusSeconds(1);
  this.log.debug("RateLimit is ready to operate");
}

RateLimit.prototype.run = function(func, when){
  var now = this.ZonedDateTime.now();
  if(now.isAfter(this.until)) {
    this.until = toDateTime(when);
    func();
  }
}

Usage in a JavaScript UI created rule would be something along the lines of:

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

this.latch = (this.latch === undefined) ? new RateLimit() : this.latch;
...
this.latch(function(){ events.sendCommand("AlertItem", "Important alert!"); }, "1d");

No matter how often that rule runs, AlertItem will only be commanded once an hour.

1 Like

Hey thanks for the compliments and comments! I am not as technical as some in here so its easy to keep things simple!

Can you help me understand, or point me to a resource that explains the difference between:

CounterItem.postUpdate(addone)

and

postUpdate(CounterItem,addone)

http://www.openhab.org/docs/configuration/rules-dsl.html#manipulating-item-states

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.