Design Pattern: Working with Groups in Rules

Hmmmm. I’m not sure. Now that I’ve gone back to my sitemap to check I’m finding that Item to be undefined as well (that is what the -'s mean).

What version of OH are you using?

Hello!

I’m using OH2 nightly. I’ve solved it using a rule. Not the best solution, but it works. Thank you for your help.

Best regards,
Davor

Hi Rich,
I’m looking at your example code of groups and trying to incorporate something similar but i’m not sure its possible (although it looks likeit could be).

Basically instead of storing a value in a Item, is it possible to create a variable and store it there instead when it loops though a group of items.

For example here’s what i’m trying to accomplish:

Item:
vGarageLight (gSoffitLights)
vHouseLight (gSoffitLights)

  1. Door Opens, check gSoffitLights (gSoffitLights.members.filter[i|i.state != 0].forEach [ i | ???]) group for all lights currently ON and the dimmer value.

  2. Create a variable called “item.name + __restore” and store the dimmer value of the light in that. Eg. vGrageLight_restore, vHouseLight_restore

How would code it to store the variable in a variable, is that possible?

First I’ll say I usually recommend storing such things in Items because you then can take advantage of restoreOnStartup which will allow your Rules to behave more consistently during OH startup or .rules files reloading. In those cases all your vars get wiped out and reinitialized.

To answer your specific question:

Whenever you see [ ] in the code above you are seeing a lambda. These lambdas have a limitation that they can only reference and use vals (i.e. constants). So, you have a problem because once you set a val you cannot change it later which I think would eliminate the possibility to achieve what you are trying to achieve.

So you have three approaches you can use to solve this problem (in my preferred order):

  1. Set up persistence on the Lights (not MapDB because we need the previous value) and get the last Dimmer values from persistence using item.previousState(true).state as PercentType in your 2. restore loop.

  2. Add new Items with findable names (like above) and store the current Dimmer values in those Items. The references to Items are constant so you can access and postUpdate to them from inside the lambdas.

  3. Create a global val Map<String, PercentType> prevDimmerVals = newHashMap and in your 1. forEach put the current Dimmer value into prevDimmerVals using the i.name as the key. In 2. you can get the old Dimmer value back out to apply to the lights. This works because the Map is a val but provides methods that lets us change it from inside the lambda.

1 Like

As I understand it, there is no guarantee that this gives the item that triggered the rule, because
a) 100ms may be to short to wait for the update during high load
b) 100ms may be to long to wait for the update in case several items are updated at the same time.

So, is there any better way to do it?

Anyhow, please don’t call such a dirty workaround a design pattern… it’s more an anti-pattern…

2 Likes

Of course, simply have individual rules triggered on individual Items, so that you will have access to exactly what the trigger was.
See Ben Jones lambda example above for one way to re-use common code across many rules.

  1. One must work within the limitations the Rules DSL imposes if one is to work within the Rules DSL. If you trigger a rule by a group or by multiple items, this is the ONLY way to get the triggering item.

  2. If you want guaranteed don’t use this approach. If you want something that works 99% of the time in the home automation context this is an excellent way to simplify your errors and reduce lines of code.

  3. That is one example line among eight demonstrated ways to use groups in rules. Would you there the baby it with the bath water?

So, if the only way to achieve something that works 99% of the time in the given problem domain and which makes up a small fraction of the article is an antipattern, that is your option and one I wholly disagree with.

Hello Rich,

Thanks for the very detailed rule guidance.

I have a problem with the “lastUpdate” property.

I have persistence (mapdb, and influxdb) both are set to “everyChange”

When i try to use the mapdb within the rule for a group of two items, only one is getting updating correctly with the lastUpdate property, while the other is somehow shows the last update is from 2 weeks ago which is wrong.

When i try with influx, it shows both are updated exactly at the same time (which is also wrong).

Any idea what can cause this behaviour?

The MapDB problem looks like for some reason the Item is not being persisted to MapDB. Double check your .persist file and make sure the Item is configured to be saved.

Add a sleep before calling lastUpdate to make sure persitence has a chance to catch up. Sometimes a rule can run too fast and execute before the value has been saved.

Add a chart for InfluxDB to see what is actually in the database. Or do some manual queries.

Compare the values in the DB with what you are seeing in events.log. The binding or one of your rules might be updating these Items behind the scenes.

Keep in mind that when using everyChange, lastUpdate will only change the timestamp if the new value posted to the Item is a change. If it is just an update with the same value the timestamp will not be updated. If this is the case I recommend using everyUpdate instead of everyChange.

Hi @rlkoshak - great pattern, thank you.
Can i use:

rule "A Door Sensor Changed"
when
    Item gDoorSensors changed 
then

to shorten my code, instead of list all items in group?

You can use any trigger on a Group that you can on an Item. However, it may not work like you need it to.

In this case, it would not work as you want because gDoorSensors will only change when all the doors are closed and one opens or all the doors become closed.

Imagine this scenario.

Step Doors State gSensors.state Rule Triggers?
1 All closed CLOSED No
2 One door opens OPEN Yes
3 Second door opens OPEN No
4 Third door opens OPEN No
5 Third door closes OPEN No
6 Second door closes OPEN No
7 First door closes CLOSED Yes

As you can see, the Rule would trigger when the first door opens but will not trigger again until ALL the doors are closed again. You would miss all the openings and closings of all the other doors after the first one opens. That is almost certainly not the behavior you want.

The only way to trigger a Rule for every change to a Group’s members is to use received update, though then your Rule has to deal with the fact that the Rule will be triggered multiple times for one change to one of its members. Often this doesn’t matter but in cases like the rule above, it does matter and it was far easier to write the rule with one trigger per door and know the Rule will only trigger once per change than it was to manage filtering, reentrant locks, and all the other code necessary to deal with the fact that the Rule would trigger multiple times per change to a door, and that those multiple Rules will be running at the same time.

1 Like

Is any right way to use something like:

Group:Number:SUM  gDoorSensors "[%d]"

to manage 10+ items?

You either have to deal with the fact that the Rule gets triggered 9+ times for every update to a member (e.g. make it so what the rule does can be done multiple times in a row and get the same result, add locks to avoid multiple instances of the rule from running that the same time, etc) or you have to list all 10+ Items as rule triggers individually.

Or you can consider the JSR223 Rules which might have more feature to let you handle that a little more reasonably.

With JS223 you can add triggers by script, so you can add a rule-trigger for each member of a group.

The openhab2-jython scripts (https://github.com/steve-bate/openhab2-jython) from @steve1 add a decorator to do this, so writing a rule that is triggered by each member of a group is as simple as:

@item_group_triggered("TestGroup", result_item_name="TestString1")
def example(event):
    return event.itemName + " triggered me!"

As you can see, there is also an easy way to get the triggering item.

2 Likes

Hi

Group of strings does not trigger received update in rule.
Is this by meaning or bug?

/Mike

Hmmmm. This may be a side effect of the changes made to the way a Group’s state is calculated. Post your Group definition so I know exactly what we are dealing with.

Group gLC "Lowercase switches" (All)
String VolvoHeater_LC	"Car Heather LC"	 				(gLC)
String Light_GF_Living_CeilingTable_LC 	"Dinner Table LC"	(gLC)
String Light_GF_Living_Read_LC	"Reading light"  			(gLC)		
String Light_GF_Living_Ceiling_LC	"Livingroom Ceiling LC"	(gLC)
String gsKitchen_Lights_LC	"Kitchen Lights LC"				(gLC)
String Light_GF_Corridor_Ceiling_LC "Hallway LC"			(gLC)
String Light_GF_Corridor_Wardrobe_LC "Closet LC"			(gLC)
rule "IFTTT lowercase string"
when
	Item gLC received update 
then
 
    val changedswitch = gLC.members.sortBy[lastUpdate].last
    val switchname = changedswitch.name.substring(0, changedswitch.name.lastIndexOf('_')) 
	if (changedswitch.previousState !== null){ 
	sendCommand(switchname, changedswitch.state.toString.toUpperCase) 
	logInfo("IFTTT", switchname + " " + changedswitch.state.toString.toUpperCase )
	}
end

Yes, the problem is your Group doesn’t have a Type. There was a change early on in the 2.2 snapshot I think, maybe earlier. All Groups must have a Type to receive an update.

So just change

Group:String gLC "Lowercase switches" (All)

and it should work.

1 Like

Thanks

Now it is working.

/Mike

Hi Guys,
i want to use this for my online/offline detection of some network components in my house and adjusted the items, groups and rules a bit. But i get this error and have not found anything similar so far:
2018-01-11 12:33:26.278 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘A Door Sensor Changed’: ‘name’ is not a member of ‘java.util.ArrayList’; line 30, column 16, length 13

Any idea? Do i need to load any library in the rule?

BR
Andreas