Group triggered rule creating independent timer for each item in the group

I have X10 light switch modules which have status feedback, that is, they send a message when they are switched on/off locally, so OH knows their real status even if they are switched locally.

I want to implement the following behaviour on my home installation:

When any of those light switches are switched on locally, I want to switch them off (from OH) after 60 seconds.

I know that I can create a rule that triggers whith a Group “received update” event (I will include all these “time-controlled” light switches in the group), the problem is that I need to create an independent timer for each one of the items inside the group that were switched on, and I do not know how to achieve this.

Any clues?

NOTES:

  • I’m just typing in the below, there are certainly typos.
  • When you trigger a rule on a group update the rule will trigger multiple times for each Item update. It has to do with how the new state of the group is calculated. But triggering on changed for a group almost never work like you want.
import java.util.Map
import org.openhab.model.script.actions.*

val Map<String, Timer> lightTimers = newHashmap

rule "Turn Off Lights"
when
    Item gTimerLights received update
then
    gTimerLights.members.filter[light | light.state == ON].forEach[light |
        // Only create a Timer if there isn't one already
        if(lightTimers.get(light.name) != null) {
            lightTimers.put(light.name, createTimer(now.plusSeconds(60), [|
                light.sendCommand(OFF)
                lightTimers.put(light.name, null) // remove the timer from the Hashmap
            ]
        }
    ]
end

Theory of operation:

The timers are kept in a Map using the name of the light’s Item as the key and a Timer as the value. When the Group receives and update, all members of the Group that are ON and do not already have a Timer set will have a new Timer created that will trigger in 60 seconds. Inside the Timer we send that light the OFF command and remove the Timer from the Map.

Your proposal looks nice and does not depend on persistence as my solution bellow (based on your suggestion I found on this post: More efficient Rule ). I will need to do some test to check which one works better.

This is my solution adding an override if I want to keep the light ON after the 60 seconds: If I want to I can switch ON/OFF twice and the light will not be switched OFF from the rule.

rule "Luces temporizadas"
when
	Item gLucesTimer received command
then
	// Hack to get at the most recently pressed button
	Thread::sleep(100) // experiment with this value, the purpose is to give persistence time to save the state so lastUpdate will work properly. You may not need it at all but if the wrong button keeps coming up, make this sleep longer
val luz = gLucesTimer.members.sortBy[lastUpdate].last

if (receivedCommand == ON)
{
	var Timer timer = createTimerWithArgument(now.plusSeconds(60), luz,
		[ x |
			var i = gLucesTimer.members.filter(s|s.name == luz).head
			if(i.updatedSince(now.minusSeconds(60)))
			{
				sendCommand(luz, OFF)
			}
		]
	)
}
end

Do you see any problem/optimization with my solution?

Thanks!

That case is slightly different because what he really needed to know was which button was pressed in the Group and the only way to really know that is be checking its timestamp. In the case above we can surmise which was the most recently pressed by finding the light that is ON but doesn’t have a Timer set for it yet, which is what that code does. And since it doesn’t depend on Persistence it addresses the issue brought up by smerschjo in that thread that it won’t handle the case where multiple buttons are pressed at the same time.

I like it. Nice and elegant way to do it. I might steal that sometime. And you do need the persistence for that.

I do see a couple of potential problems though.

The first problem is unless you plan on commanding all of your Lights as a group (i.e. all at the same time) which I don’t think you want based on your first posting, the received command trigger won’t work. A Group doesn’t receive a command unless the Group is explicitly commanded (e.g. a rule sends it a command or it is put on your sitemap as a commendable thing (e.g. a switch). Since I’m pretty sure you want to trigger the rule when the Items are commanded, not the Group, you need to use “received update” as your trigger. But that will cause a third problem (see below)

The second problem I see is, let’s say you switch the light ON and a Timer is created. Then you switch it OFF. No problem so far. Then you switch it ON again before the 60 seconds is up. You have no check to see if a Timer is already set so a new Timer will be created. The first timer will execute, see that the switch was updated less than 60 seconds ago and not do anything. But when the second timer executes the switch hasn’t updated since 60 seconds and the switch is turned off. Thus your light will always be switched off 60 seconds after the last time it was turned on.

My first inclination was going to be to adapt my Map<String, Timer> approach from above and use the existence of a Timer in the Map to indicate that this is a second ON command within the 60 seconds… but now if we switch to using received update the rule will get triggered multiple times per ON event and the Timer will always be overridden. Gah!

So how I’ve solved this problem in the past is to suck it up and do the ugly thing of triggering the rule on each of the Items individually rather than the Group. Then the rule only gets triggered once per event. The rest of the rule can remain unchanged (after we solve the above). We could add another Map of timestamps and compare the lastUpdate time with the value in the Map and only override the Timer if it’s different.

You will have to determine which is going to be more work for you.

Yes you are right, and besides that (As far as I understand) the “received command” is triggered when an action is issued over an item from the UI and “received update” is triggered when the state of an item is updated from the X10 binding (which is what I want cause I want to detect when the switch is activated locally).

Yes, my mistake.

What do you mean by the rule will be triggered multiple times per ON event? If the binding “updates” an item state once when it gets the ON state from the switch module, do you think the rule will be executed multiple times? Is this an expected behaviour?

Maybe this will be the best sollution, in fact I am not living in a Palace, and I have to apply this rule to just 6 lights :slight_smile:

Thanks for your help!

When you trigger a rule using received update on a Group the rule gets triggered multiple times per ON event. This is because of the way the new state is calculated for the Group. What happens is the Group gets the new state of each of its Items in turn a incrementally calculates its new state. Each step in the calculation results in an update to the Group’s state so your rule gets triggered multiple times. So yes, your rule will trigger multiple times per per ON state from the switch module. It is an expected behavior.

As I pointed before I will make a rule for each item.

Now I am questioning which persistence service to use. Considering that I am using a Raspberry pi 2 (low memory, SD card storage, etc…) Which persistence service will work better considering performance and SD card wearing?

Looking at the wiki, I found that db4o and RRD4J should be the best options? What is the difference between them? Any other suggestions?

You don’t need a separate rule for each Item. You can have the one rule as written above and just trigger the rule on the Items instead of the Group.

rule "Luces temporizadas"
when
    Item Light 1 received command or
    Item Light 2 received command or 
    ...

It largely depends on what you want to do with the saved data.

First some common advice. Any persistence approach you use will be causing lots of writes which can cause problems on an SD card. So you might consider either greatly limiting which Items you persist to the bare minimum or moving the DB off of the SD card (e.g. an external HD/SSD or hosting a DB server like MySQL somewhere else).

Now for the real answer. My advice is to not use just one. I have three tiers of persistence.

Tier 1: I use MapDB for all my items which save every change and restoreOnStartup. I like MapDB for this because it is very small and it can save Strings, not just numbers (more on that later). It stays small because MapDB saves ONLY the most recent value of each Item. You might ask, “what’s the point?” The advantage is when OH restarts all of my Items will be restored to their previous state instead of being undefined or requiring me to write rules to repopulate them.

Tier 2: I have a few Items that I want to chart or need to reference their previous values. For these Items I use rrd4j. The nice thing about rrd4j is that the DB remains a fixed size so I don’t have to worry about maintenance later on if the DB grows too big. However, to keep this fixed size rrd4j “compresses” the data as it ages by replacing the values from a certain period of time with the average (e.g. replacing the values for every minute with the average of every five values). So rrd4j is great for recent data but not so great for old data. It is also limited to numerical data (Number, Switch, Contact, etc). And, because of the way rrd4j “compresses” data you must save values every minuted regardless of how often the Item changes state if you want to cart it. Thus is can be a little hard on your SD card.

rrd4j is the default persistence if you don’t set it in openhab.cfg.

Tier 3: I actually have nothing in Tier 3. This tier would be for Items which you want to study and analyze. This means you want to keep exact values forever or you want to use external tools to process or present (e.g. chart) the data.

To set this up you can use Groups for each tier and just add or remove Items from each group depending on how you want it persisted. Set up the .persist files using the Groups.

Thanks a lot for the explanation.

You say that I must save data every minute, but as my idea is to know if a switch state was updated X seconds before I will need to store the value on the persistence service when it changes instead of periodically, right?

I didn’t make clear. You do both every change and every minute. rrd4j needs data at least every minute but more often is fine.

You can try it with just every change and see if it works for querying lastUpdated. But if you get a null exception in the logs try everyMinute and everyChange.

I understand now, but considering that my persistance is needed for switches whose state change only let’s say 10 times a day, forcing to store it once per minute its going to keep writing to the SD card unnecessary.

What about using db4o persistence instead of rrd4j?

It might work just fine without once a minute. I don’t know for sure, it is something you will have to test.

My problem with db4o is it is embedded so there are not a lot of good tools to interact with it and it will continue to grow forever so you will have to perform periodic maintenance on it. Without tools this maintenance amounts to deleting the DB files and starting over again.