Get the member of a Group that triggered the rule

Many run into a situation where they want to have a rule triggered by updates to a Group or have multiple Item triggers but want to know which Item triggered the rule. OH’s Rules DSL does not have this capability built in (I’m told the JSR233 Rules Engine does if you are on OH 1.x and good with JavaScript or Jython).

There is a simple way to do this.

  1. Set up persistence on all the Items that can trigger the rule.

  2. If your rule is triggered by multiple Items instead of a Group, make sure the Items are members of a Group anyway.

  3. Inside your Rule you can get the Item that was most recently updated (i.e. the the triggering Item) with the following line:

    val trigger = gMyGroup.members.sortBy[lastUpdate].last

Limitations:

This assumes that updates to the members of the Group do not occur so close together (hundreds of milliseconds or less) that an update occurs between the time that the last update occurred and line above finishes.

Also, realize that for a Rule that uses a Group Item received update as the trigger, the Rule will be triggered multiple times for one update to an Item in that Group.

In addition, you cannot use a periodic cron strategy (e.g. everyMinute) because you need the timestamp of the most recent value to be when the Item was actually updated, not when the cron time period ended.

Finally, ALL of the Items in the Group must have been persisted to the DB at least once before the above rule will work. When an Item has not been persisted, lastUpdate returns null.

4 Likes

I run into a problem with these rule on startup of OH.

It looks like the result is null. Can I catch this in anyway to prevent this error message?

rule "group ueberwachen"
    when
        Item gDoorSensorCO_LogOpenClose received update
    then
        Thread::sleep(100)
        val alarm = gDoorSensorCO_LogOpenClose.sortBy[lastUpdate("mapdb")].last
        if(alarm !== null)
            logInfo("Loesslein", "gDoorSensorCO_LogOpenClose: " + alarm.name + ": " + alarm.state.toString+ ": " + alarm.label)
end
2017-12-31 12:20:15.392 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘group ueberwachen’: null

Filter out the null lastUpdates before sorting:

val alarm = gDoorSensorCo_LogOpenClose.filter[d | d.lastUpdate !== null].sortBy[lastUpdate("mapdb")].last

Hi Rick,

this is working if all is running, but at startup, all items are null. This is the only time I got this error. But I think, this cannot be fixed, is be design. And I just will ignore it.

Or does thsi fix it? (questionmark in rule)
val alarm = gDoorSensorCo_LogOpenClose?.filter[d | d.lastUpdate !== null].sortBy[lastUpdate(“mapdb”)].last

the ? skips the line if the thing before it is null. but in this case it will result in errors later in the rule.

This might not fit a 100% here, but I hesitated to create a new post …

I would have expected that the new implicit var “triggeringItem” would contain the Item that triggered the rule, however when I have a group in the triggering section, then triggeringItem is the Group itself, not the Item within the group that fired the rule.

.items

Group:Switch gTimerAllFire	"alle Timer Fire"	// alle Timer_xx_Fire; need persistance for triggeringItem

Switch Timer_01_Fire	"ausführen []"		(gTimer01, gTimerAllFire, gTimerPersist)
Switch Timer_02_Fire	"ausführen []"		(gTimer02, gTimerAllFire, gTimerPersist)
:

.rules

rule "gTimerAllFire"
when 
	Item gTimerAllFire received update
then
	logError( "(timer)", "(timer) gTimerAllFire => {}", triggeringItem )
end	


Result => “(timer) gTimerAllFire => gTimerAllFire (Type=GroupItem, …)”

Can somebody confirm that this is the to be expected behaviour?

Unfortunately the dcoumentation is fairly vague on this:

  • will be implicitly available in every rule that has at least one command, status update, or status change event trigger

This is the current behavior. There is work in progress to let you trigger a Rule using the Group and triggeringItem contain the Item in the Group that triggered the update to the Group that triggered the Rule, but it is not merged in the baseline.

It is behaving as expected. Your Rule is triggered by updates to a Group so triggeringItem will be the Group.

AFAIK, this is a different trigger condition. Whenever the change @rlkoshak wrote about is merged, the above rule will behave exactly like it does now.

A new trigger type “MemberOf [Group]” (instead of “Item [Group]”) will be introduced, if you use that trigger, the triggeringItem will be a member of that group.

Until then, you need to list all Items of the group seperately, to use the functionality of the triggeringItem for a group of items (not that beautiful, but no showstopper).

Ok thanks Rich for the prompt reply!

This prevents me from further testing and searching :slight_smile:

Maybe there should also be a triggeringGroup implicit var and a triggeringThing, just to be able to mix/bundle Groups, Items and Things in the rule.

Knowing which Group fired the rule would save me a lot of code. I currently have one use case (not implemented yet), where several Groups could trigger only one Rule. If triggeringItem contains - as expected - the Item in future (and no longer the Group), then this would not be possible, so I would end up having multiple, nearly identical lines of code… :frowning:

I also currently have several (i.e. 7) indentical rules for Things (Sonos Player), to check if they changed online state. The same is true for Heating Devices. If there was a triggeringThing variable, this would drastically reduce my lines of code. I could put all the similar rules into one, query triggeringThing for it’s UUID / Thing and would know which Player came online and go from there…

This all would lead to a lot flexibility in the code I believe.

Just my 2 cents…

There will be a new MemberTrigger trigger type that lets you choose between triggeringItem being the Group or the member of the Group that triggered the Rule.

You won’t be able to get both the Group AND the member of that Group that triggered the Rule. You can choose either or but not both.

So put your Groups into a parent Group and trigger the rule on the parent Group. The triggeringItem will be the Group that triggered the Rule. Or use the received update trigger instead of the new trigger to get the Group that triggered the rule. And then you can use one of the existing approaches, like the persistence hack, to get the Item that triggered.

Feel free to add your comments to the open Issues on ESH project where this is being implemented.

Hi rich,These method is availbe also for nested groups?

I mean for example an alarm system which have some PIRs (putted into a group called G_PIR), some REEDs (putted into a group called G_REED) and finally some TAMPERs, also into his grop.

G_REED, G_PIR, G_TAMPER is inside a group call G_ALARM

example.sitemap

Group G_ALARM   "label"   <icon>


Group G_REED   "label bla bla bla"    <icon lalala>    (G_ALARM)
Group G_PIR ........      ................(G_ALARM)
Group G_TAMPER . ....................  (G_ALARM)

with these configuration can I call

val trigger = G_ALARM.members.sortBy[lastUpdate].last ???

You can use allMembers to get all descendants of a GroupItem.

1 Like

Hello,
I cannot face off a problem I Have with these rule.

Like in the previuos post I write, I really have 3 groups for my alarm system:
G_Pir group, G_Reed group and G_Tamper group.

These groups seems equivalent one eachother BUT If the rule is with G_Reed or G_Tamper All works well; same rule with G_Pir not work !!!


Because i made many test to try make it work I can tell you that IF i change the gropup of pir items in the myhome.items with G_Reed or G_Tamper all works, but pir is not in the correct group anymore…

Those are the groups declaration in .items file

Group Sensori_Mov		"Sensori di Movimento" <motion>
Group:Contact:OR(OPEN, CLOSED) Infissi   		"Infissi Aperti [(%d)/9]"       <contact>
Group:Contact:OR(OPEN, CLOSED) Antitaglio 		"Antitaglio[(%d)/9]"			<lock>

These is the rule:

	rule "Ultimo Sensore"
	
	    when 
	    	// Group to monitor
	        Item Infissi received update 
	    
	    then
	    	
	    	//Get item from group
			val lastItem = Infissi.allMembers.sortBy[lastUpdate("mapdb")].last
			
			// Grab name of item
			val nameOfItem = lastItem.label
			postUpdate(Sensor_Allarme,nameOfItem)
	end

As said before;the prev rule write with Infissi group (Reed) and Antitaglio (Tamper) works, BUT with Sensori_Mov does not. But they seems equivalent groups

Any ideas???


EDIT 2h later

I solved making these change in group declaration:

From these:
Group Sensori_Mov "Sensori di Movimento" <motion>

to these
Group:Switch:OR(ON, OFF) Sensori_Mov "Sensori di Movimento" <motion>

now it works as expected but dont know why ^_-

Group:Switch:OR(ON, OFF) tells openHAB that the group has to have a state like a Switch does. Furthermore, openHAB is told to work out the state of the group form an OR of the group member states.

Group on it’s own makes a group with no particular state, and no reason to ever change the state.
So a rule triggering from group state change will never fire.

1 Like

Oh thank you rosko. now becames to be clear the view behind the code indeed I was reading the official docs Groups tutorial

For other people in future: I hope will help

Can you group different groups like mine:

Group:Contact:OR(OPEN, CLOSED) Infissi   		"Infissi Aperti [(%d)/9]"       <contact>
Group:Contact:OR(OPEN, CLOSED) Antitaglio 		"Antitaglio[(%d)/9]"			<lock>
Group:Switch:OR(ON, OFF) Sensori_Mov			"Sensori di Movimento" 			<motion>	

in a super-group that is possible to use with the topic rule simply adding these:

Group:Switch Super_Group         "label" 

 Group:Contact:OR(OPEN, CLOSED) Infissi   		"Infissi Aperti [(%d)/9]"       <contact> (Super_Group)
  Group:Contact:OR(OPEN, CLOSED) Antitaglio 		"Antitaglio[(%d)/9]"			<lock> (Super_Group)
  Group:Switch:OR(ON, OFF) Sensori_Mov			"Sensori di Movimento" 			<motion>	 (Super_Group)

and writing these rule:

	rule "Last Sensor Triggered"
	
	    when 
	    	// Group to monitor
	        Item Super_Group  received update 
	    
	    then
	    	
	    	//Get item from group
			val lastItem = Super_Group.allMembers.sortBy[lastUpdate("mapdb")].last
			
			// Grab name of item
			val nameOfItem = lastItem.label
			postUpdate(Sensor_Allarme,nameOfItem)
	end

That’s right. You can make a group of groups. But what state would you expect the group-of-groups to have, when different types are involved? If you cannot get the group state to work, it’s difficult to trigger a rule from group state.

Don’t forget you can have a rule triggered by this group or that group or other group, which works around part of the problem.

Don’t forget that such a rule has to deal with members that are of different types, and so have different states.
It looks like you only want to get the name of the last member to change, and don’t use the state in the rule, so it should work out ok.

You are adding to a quite old post from 2017, and new openHAB features are available since then.
I think you’d be interested in Member of rule triggers.

Note what Rick says in post one -

the Rule will be triggered multiple times for one update to an Item in that Group.

That may or may not be important to you.