Discussion : rule implicit variable triggeringGroup

I’m delighted by the member of group trigger feature, coupled with triggeringItem, as introduced in OH 2.3 and discussed here. (Thankyou devs!!)

It’s a very powerful feature set.

I think there is a missing feature though, which might be called triggeringGroup. An implicit variable giving the Group that triggeringItem is a member of, when the Member of trigger pattern is used.

To give a use-case ; suppose we have a number of sensors divided into several groups representing areas. Each area group associated with a light.

Group gBothyards
Group gFrontyard  .. (gBothyards)
Item Contact SensorA  .. (gFrontyard)
Item Contact SensorB  .. (gFrontyard)
Item Switch FrontyardLight
Group gBackyard .. (gBothyards)
Item Contact SensorX  .. (gBackyard)
Item Contact SensorY  .. (gBackyard)
Item Switch BackyardLight
Item Contact SensorQ .. (gFrontyard, gBackyard)    // member of both

We might want to write a single rule to handle any sensor event and control the approprate light

rule "handle sensors"
when
   Member of gFrontyard received update or
   Member of gBackyard received update
then
   // we know what sensor triggered with triggeringItem
   // but we do not know which group it is in
   // if we knew which group, we could work out the light to operate
end

I do not think there is a direct way to discover which group(s) an Item is a member of?
It would be possible to test if our triggering Item is a member of each possible group in turn, but that feels a bit hacky.
So my thought would be to have a triggeringGroup implicit variable in a similar way to triggeringItem.
This would get populated when Member of … is used.

This may or may not get populated when a group is used as a direct trigger like so?

rule "normal trigger example"
when
   Item gFrontyard received update or
   Item gBackyard received update
then
   // the group will of course be in triggeringItem
   // our imagined triggeringGroup may or may not contain it as well?
   // It should NOT contain gBothyards as that was not involved in triggering.

My gut feel is to only populate it when ‘Member of’ is involved.

I do not know how much effort would be involved in creating such an implicit variable, I suspect it might be fairly easy?

But is it worthwhile, useful?
Please discuss!

EDIT - added the case where an Item is a member of both possible triggers. Expected action - rule triggers twice, once for each Member of, while triggeringGroup is different each time ?
EDITED AGAIN - yes, SensorQ will trigger the example rule twice - once for each membership condition satisfied.

In principle that should be defined in triggeringItem.groupNames if I am not mistaken. (No chance to test right now)
Of course, any item could be part of multiple groups, depening on your definitions, but you could at least check if the triggeringItem is part of one specific group or the other.
For that you will need to do some further processing of triggeringItem.groupNames as it is a list of 1 or multiple group names, but there is multiple ways to go about that.

1 Like

Ooh, that’s new to me. Will play later - it ought to fit the use-case

Yep, that is true in either case. I imagined triggeringGroup as a single group, not a list, as the rule trigger is a single event related to that single group mentioned in the Member of trigger.

It does beg a question now, whether a rule gets triggered multiple times when an Item falls into multiple possible Member of triggers. i will have to play with that too.
I’ve updated my first post to add the possibility.

There is.

 MyItems.getGroupNames

This returns a List<String> of all the names of the Groups MyItems belongs to.

You have to have an if statement to test which Group it is individually anyway. I don’t see much difference between

if(triggeringGroup.name == "gFrontYard") {
    // blah blah blah
}
else if(triggeringGroup.name == "gBackYard") {
    // blah blah blah
}

and

if(triggeringItems.getGroupNames.contains("gFrontYard") {
    // blah blah blah
}
else if(triggeringItems.getGroupNames.contains("gBackYard") {
    // blah blah blah
}

in terms of hackiness. triggeringGroup is a little bit cleaner for sure but we are looking at the same number of lines.

But my question is if you need to do something different for the front yard or back yard groups, why not use two different rules?

I’m a little cautious about this idea. It should be pretty easy to implement. But there are a whole lot of edge cases and complexities that need to be ironed out and dealing with each of them in OH code versus Rules code exclude some use cases.

For example, what if an Item is a member of both gFrontYard and gBackYard? What does triggeringGroup get set to? What if I want to do something different when it is a member of both? Items can belong to multiple Groups so this will happen.

That is a good question. I look forward to hearing reports of your experiments.

To confirm behaviour is as anticipated (OH2.3) -

Group gTestX
Group gTestY
Switch TestXY  (gTestX, gTestY)
rule "grouptest"
when
	Member of gTestX received command or
	Member of gTestY received command
then
	logInfo("grpTest", "triggered " + triggeringItem.name)
end

This rule will trigger for each ‘Member of’ condition that is satisfied; it will run twice when the switch receives one command.

In the case of my trivial sensors + lights example, that’s actually desirable behaviour. And entirely logical.

1 Like

The advantage of the imaginary triggeringGroup variable would be that it tells you directly which group was involved in the trigger.

It’s easy to foresee Items with multiple group memberships e.g. (gFrontyard, gRestore) where not all groups will ever be involved in rule triggers e.g. they’re for persistence or other management.
The game with using a membership list in a rule instead would be deciding which group you are interested in. (Often that will be easy though!)

I’m not convinced yet about the utility of this feature - there is always more than one way to skin a cat !!
If it’s of any value, it is likely to emerge in a non-trivial dozen groups scenario.

I’ve no killer app for it, hence discussion. Just feels like it ought to have utility in manage-by-group instead of manage-by-code. I like rules that automatically just work with later additions to configuration - which is already do-able with Rik’s ‘associated Items’ naming convention technique.

I’ve been thinking about the use of ‘mixed’ groups of dissimilar Items - in the original example, sensors (Contacts) , lights (Switches), a Number used as countdown timer, could all be bundled in one Group and automatically associated with each other as a ‘set’. The working involved in making use of that doesn’t seem to offer any advantage over alternative methods.

That’s an interesting approach. Then you could do something like the following:

val associated = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name + "_Associated")
val timer = associated.members.findFirst[ i | i instance of SwitchItem ]
...

Though with the Item Registry (third example in the rewritten Associated Items DP) you don’t really even need the Groups anymore. You can get them by name independent of the Group membership so everything would be associated by name alone.

Perhaps there is a use case for those users who dynamically change Group membership on the fly. I’ve never really explored those approaches so I can’t really say.

I suppose a classic example would be called “group intersection”.
gLivingroom = thermostat, heater, light, PIR. Mixed group.
gThermostats = all thermostats. Grouped by function.
gHeaters = all heaters
etc.

The task would be a single thermostat group-triggered rule that finds it’s partner heater via the common gLivingroom group. Comprehensive version would deal with multiples (e.g. stairway lights + wallswitches).
triggeringGroup seems to offer no shortcuts there.

Tinkering with some real rules (currently using associated-items-by-name technique), I’ve come to realise that what I was really trying to avoid was hardcoding group name strings into rules.
Just this kind of thing -

if (triggeringGroup.name == "gFrontYard") {
    // blah blah blah
} else if (triggeringGroup.name == "gBackYard") {

If we add a new group, say - then the rule needs rewriting…

Avoidance involving the already well known use of associated-by-name Items is straightforward.

Group gFrontyard
Contact SensorA (gFrontyard, gTimestamping)
Contact SensorB (gFrontyard, gTimestamping)
Switch light_Frontyard
Group gBackyard
Contact SensorX (gBackyard, gTimestamping)
Switch light_Backyard
Contact SensorQ (gFrontyard, gBackyard, gTimestamping)

We choose to name with suffixes & prefixes to make the associations.

Using triggeringItem.groupNames makes it just a string manipulation job to derive the ‘target’ Item name…
So long as you can pick out the group you want from the list !
That’s quite difficult without hardcoding, or we resort to adding something else detectable to the group - naming gFrontyard_controls or somesuch, so that we can pick out group names including “_controls”
It is do-able, but getting clumsy.

Our both-front-and-back SensorQ member Item doesn’t work well here either.

It’s not very convincing justification - but triggeringGroup.name would be helpful here to avoid rummaging through the list.

Mind you - you’d still have to edit the rule triggers if you added a new group. The way to avoid that would be to put all the sensors into an overall ‘gSensors’ for rule triggering. And then use the membership list to work out the group/target(s). Ah, well.

I think I have a solution looking for a problem here :slight_smile:

Perhaps but that isn’t a really a bad thing. If we can find a compelling use case, particularly one that isn’t viable without triggeringGroup perhaps we will figure out another powerful way to make generic Rules akin to the Associated Items DP.

So far I don’t think either of us are convinced yet we that have one. But it is an interesting idea.

Yeh, I’m not raising an official enhancement request yet.
It does look like the logical completion of the Member-of feature set, but doesn’t seem to add real value to justify effort.