Design Pattern: Working with Groups in Rules

Expand your rule. Get your list of Items into a variable first. Then test to see if it has length zero before doing anything with it.

Hi I created a short test to understand the behaviour of Group but I receive always a “null” message

> Items
> Group PIPPO "test group"
> Switch PIPPO_1 "membro 1" (PIPPO)
> Switch PIPPO_2 "membro 2" (PIPPO)
> Switch PIPPO_3 "membro 3" (PIPPO)
> Switch MQTT_Remote  "membro 4"  (PIPPO)

Sitemap
Switch item= MQTT_Remote

Rule (I tried both the logInfo)

rule "testgroup"
when
  	Member of PIPPO received command
then
 	logInfo ("testgroup ", "MQTT_Remote") 	
//	logInfo ("testgroup ", MQTT_Remote)
	val prova=triggeringItem
	logInfo ("testgroup 1", prova)
end

Where am I wrong?
Thanks

logInfo(X,Y) wants to be given two strings. Your prova is an Item object, not a string.
Examples-
logInfo ("testgroup 1", "Testing " + prova.name)
logInfo ("testgroup 1", "State " + prova.state.toString)

There is a handy way to see objects using {} in the text and an extra parameter.
logInfo ("testgroup 1", "trigger Item was {}", prova)

Thanks, I was so sure that the problem was the use of Group that I did not see the most evident error!

Hello all,

I have posted here before because I would like to receive a Telegram message that says what windows are open.

This had also worked wonderfully. I have now switched to OH3 and yesterday I noticed that this query no longer works. Have I built in a bug after all? Or is this no longer possible under OH3?

This is my rule:

rule "Abfrage Fenster"
when
    Item Abfrage received command ON
then
val telegramAction1 = getActions("telegram","telegram:telegramBot:652b25b")
logInfo("Status", "Abfrage wurde gedrückt")
if (gWindow.members.filter[m|m.state==OPEN].size !==0) {
  telegramAction1.sendTelegram(gWindow.members.filter[i | i.state==OPEN].map[ label ].reduce[ result, label | result = result+", " + label ] + " ist offen" )
    }
end

And this is the Error-Log:

[ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'telegram-8' failed: Couldn't invoke 'assignValueTo' for feature param result in telegram

I hope for your help!

Lights in my home automation setup may be controlled multiple ways: by a physical switch hardwired to the light, by a button on a UI screen, or by a rule in response to some other events.
To keep it simple, I combine the design patterns for Proxy Items, for Groups and for Associated Items. I define rules for the desired behavior at the level of a group, and then assign the lights to that group.

With this setup, the proxy item will always correctly reflect the status of the light, independent of what caused that status (command from a rule, gesture on a physical control, gesture on a UI element).

A more detailed description can be found here.

Hi Guys,
I used Groups a lot in OH3 (DSL rules), but I am currently migrating to MainUI rules including Blockly.
For making this work, I am missing triggerinItem:
If a rule is triggered by a member of a group (e.g. one of 4 smartphones) and I would like to know which one triggered it.

Any suggestion how to solve this in blockly would be greatly appreciated.

Hi all,

I’m trying to work with a group rule and be able to create a timer. The issue I have is that I cannot figure out how to create the timer’s name dynamically based on the triggering items name.

Example: I have 6 items in this group. They are zwave sensors, which I grab their zwave_lastwake time form the API using the HTTP binding. If that doesn’t change in 3 hours I send alerts that the sensors battery is likely dead (because they always report 100% battery).

So In my normal (not group) rule I successfully do this like this for 1 item:

var Timer frontdoorSensor_Recent_Timer = null
rule "Front Door Sensor Last Wake check"
	when
		Item frontdoorSensor_LastUpdate changed							//triggered when a new ZWAVE_LAST_WAKEUP is detected
	then
		logInfo("Front Door Sensor","!!!!!!!!!!!!!!!!!!! New Wakeup Time Received")
		postUpdate(frontdoorSensor_Lowbat,"OK")							//set lowbat to indicate battery is OK
		if (frontdoorSensor_Recent_Timer !== null) {					//create timer to trigger battery low if not reset before it runs out.
			frontdoorSensor_Recent_Timer.reschedule(now.plusMinutes(180))
			logInfo("frontdoorSensor_Recent_Timer","!!!!!!!!!!!!!!!!!!! Reset 3 hour battery timer for front door sensor")
		} else {
			frontdoorSensor_Recent_Timer = createTimer(now.plusMinutes(180)) [|
				if (doorsensor_Lowbat_Notify.state == ON) {
					pushoverActions.sendMessage("The Front Door Sensor appears to be dead, please check the battery", "openHAB")
					ODTNotification.sendCommand("{\"title\": \"openHAB\", \"text\": \"The Front Door Sensor appears to be dead, please check the battery\", \"level\": \"critical\"}")	//Send to Desktop Computer (ODT)
				}
				logInfo("Front Door Sensor","!!!!!!!!!!!!!!!!!!! Wakeup Check timed out")
				postUpdate(frontdoorSensor_Lowbat,"Check")
			]
			logInfo("frontdoorSensor_Recent_Timer","!!!!!!!!!!!!!!!!!!! Created 3 hour battery timer for front door sensor")
		}
	end

This works great, however right now I repeat this same rule 6x for each of my 6 sensors. I am trying to move this to a group rule. My issue is that I cannot seem to figure out how to create a timer with a dynamic name.

import org.openhab.core.model.script.ScriptServiceUtil
var Timer frontdoorSensor_Recent_Timer = null
var Timer sidedoorSensor_Recent_Timer = null
var Timer backdoorSensor_Recent_Timer = null
var Timer upporchdoorSensor_Recent_Timer = null
var Timer sidedoorDoorbell_Recent_Timer = null
var Timer frontdoorDoorbell_Recent_Timer = null

rule "ZWave Sensor Last Wake check"
	when
		Member of gzwave_lastwake changed						//triggered when a new ZWAVE_LAST_WAKEUP is detected
	then
		val Itemname = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString.replace("_LastUpdate",""))
		val timerItem = ScriptServiceUtil.getItemRegistry.getItem(Itemname.name.toString + "_Recent_Timer")
		val lowbatItem = ScriptServiceUtil.getItemRegistry.getItem(Itemname.name.toString + "_Lowbat")

		logInfo("Zwave Sensor Check","!!!!!!!!!!!!!!!!!!! New Wakeup Time Received for " + triggeringItem.name.toString)
		postUpdate(lowbatItem,"OK")								//set lowbat to indicate battery is OK
		if (timerItem !== null) {								//create timer to trigger battery low if not reset before it runs out.
			timerItem.reschedule(now.plusMinutes(180))
			logInfo("Zwave Sensor Check","!!!!!!!!!!!!!!!!!!! Reset 3 hour battery timer for " + triggeringItem.name.toString)
		} else {
			timerItem = createTimer(now.plusMinutes(180)) [|
				if (doorsensor_Lowbat_Notify.state == ON) {
					pushoverActions.sendMessage("The Sensor \'" + Itemname + "\' appears to be dead, please check the battery", "openHAB")
					ODTNotification.sendCommand("{\"title\": \"openHAB\", \"text\": \"The sensor \'" + Itemname + "\' appears to be dead, please check the battery\", \"level\": \"critical\"}")	//Send to Desktop Computer (ODT)
				}
				logInfo("Zwave Sensor Check","!!!!!!!!!!!!!!!!!!! Wakeup Check timed out for " + triggeringItem.name.toString)
				postUpdate(lowbatItem,"Check")
			]
			logInfo("Zwave Sensor Check","!!!!!!!!!!!!!!!!!!! Created 3 hour battery timer for " + triggeringItem.name.toString)
		}
	end

When this rule gets triggered I get the following error:

2022-01-24 10:58:16.160 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'battdet-1' failed: Item 'backdoorSensor_Recent_Timer' could not be found in the item registry in battdet

I (now) know its because my code is trying to find the Timer item in the registry but its not there since it isn’t defined in a .items file (and cannot be).

So, Is there a way to do what I am trying to do here and create a timer with different names based on the triggering item of a group rule?

1 Like

Example above

The trick is to store many Timer handles in a Map “array” indexed by any-string-you-like, often conveniently an associated Item name.

1 Like

Thanks @rossko57! I was searching through this thread but since the syntax is so different than I had thought I missed it. Appreciate the help.

1 Like

@JJ_Reynolds

I’m a little late to the party, but what you posted here a little over 2 years ago is exactly what I’m looking to do. One question for you on this:

Typically in my rules dealing with timers, I check to see if the timer is NULL, if it is then I create a timer. If it is not NULL then I reschedule the timer. like this:


rule "Front Door Sensor Last Wake check"
	when
		Item frontdoorSensor_LastUpdate changed						//triggered when a new ZWAVE_LAST_WAKEUP is detected
	then
		logInfo("Front Door Sensor","!!!!!!!!!!!!!!!!!!! New Wakeup Time Received")
		postUpdate(frontdoorSensor_Lowbat,"OK")						//set lowbat to indicate battery is OK
		if (frontdoorSensor_Recent_Timer !== null) {					//create timer to trigger battery low if not reset before it runs out.
			frontdoorSensor_Recent_Timer.reschedule(now.plusMinutes(180))
			logInfo("frontdoorSensor_Recent_Timer","!!!!!!!!!!!!!!!!!!! Reset 3 hour battery timer for front door sensor")
		} else {
			frontdoorSensor_Recent_Timer = createTimer(now.plusMinutes(180)) [|
				if (doorsensor_Lowbat_Notify.state == ON) {
					pushoverActions.sendMessage("The Front Door Sensor appears to be dead, please check the battery", "openHAB")
					ODTNotification.sendCommand("{\"title\": \"openHAB\", \"text\": \"The Front Door Sensor appears to be dead, please check the battery\", \"level\": \"critical\"}")	//Send to Desktop Computer (ODT)
				}
				logInfo("Front Door Sensor","!!!!!!!!!!!!!!!!!!! Wakeup Check timed out")
				postUpdate(frontdoorSensor_Lowbat,"Check")
			]
			logInfo("frontdoorSensor_Recent_Timer","!!!!!!!!!!!!!!!!!!! Created 3 hour battery timer for front door sensor")
		}
	end

Am I reading your rule correctly in that you basically cancel the timer on entry to the rule then schedule the timer you want?

				todTimers.get(triggeringItem.name)?.cancel;

				todTimers.put(triggeringItem.name, createTimer(now.plusMinutes(30)) [|
				sendCommand(triggeringItem, OFF)
				todTimers.remove(triggeringItem.name)
				] )

Appreciate the help. I’m not familiar with these hash maps and the syntax they use.

That’s correct. Please be aware of the ?.

todTimers.get(triggeringItem.name)?.cancel;

is: get from hash map todTimers the object with name triggeringItem.name. If the object is not null (that’s the meaning of ?) call method .cancel.
So, any existing timer for the triggering Item will be cancelled. Then it’s safe to create a new timer and to put the object, referring to it to the hash map.

1 Like

Thank you @Udo_Hartmann - I very much appreciate the explanation!

I recently upgraded to OH3 and found my rules no longer worked – here is an updated version that works, and may be a bit easier to read.

import java.time.ZonedDateTime 
import java.util.Map

val Map<String, Timer> todTimers = newHashMap

rule "TOD Expire rule"
when
   Member of grp_TODExpire changed
then
   logInfo("RULE","Item "+triggeringItem.name+" turned "+triggeringItem.state+".  Current Hour="+ZonedDateTime.now().getHour()+".");
   todTimers.get(triggeringItem.name)?.cancel;
   var Number minutes;

   if (triggeringItem.state==ON || triggeringItem.state>0) {
      if (ZonedDateTime.now().getHour() >=23 || ZonedDateTime.now().getHour() <=6 ) minutes=30 else minutes=60;

          logInfo("RULE","Item "+triggeringItem.name+" turned on.  Will turn it off in "+minutes+" minutes");
          todTimers.put(triggeringItem.name, createTimer(now.plusMinutes(minutes)) [|
             logInfo("RULE","Item "+triggeringItem.name+" turned has been on "+minutes+" minutes; turning it off now.");
             sendCommand(triggeringItem, OFF)
             todTimers.remove(triggeringItem.name)
        ] )
   }
end

Hello,
i am trying to get all group members (the group is Rollershutter type and the group’s name is “gPanjurlar”) and send commands to all group members over the gatekeeper.
I am working with VS Code.
But I am getting the below error related to this code

import org.openhab.core.model.script.ScriptServiceUtil
var Number Log_Flag = 1
var Timer RSTimer = null
var Long duration = 100000000

rule "Panjurlar Up"
when
    Item gPanjurlar received command
then
    // get info for the current RS
  
  
    logInfo("currRollershutter =" + currRollershutter.name)
    var pID = 1
    // querying to Item name and item type; the item names can be I_x_x_S or I_x_x_L 
/***16***/     val curr_RS = gPanjurlar.members.filter[ var query_RS_temp = (ScriptServiceUtil.getItemRegistry.getItem ("I_"+ 1 + "_" + pID + "_" +  "S" )) | query_RS_temp.getType == "Rollershutter" ]
    logInfo("currRollershutter =" + curr_RS.name) 
    if (curr_RS == (null || 'UNDEF')){
        logInfo("currRollershutter  NULL or UNDEF" ) 
        pID = pID + 1
/***21***/         val curr_RS = gPanjurlar.members.filter[ var query_RS_temp = (ScriptServiceUtil.getItemRegistry.getItem ("I_"+ 1 + "_" + pID + "_" + "S" )) | query_RS_temp.getType == "Rollershutter" ]
    }
    logInfo("currRollershutter second try =" + curr_RS.name) 
   
end


2023-08-13 16:42:19.361 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'GK.rules' has errors, therefore ignoring it: [16,136]: no viable alternative at input '|'
[21,133]: no viable alternative at input '|'

I couldn’t understand what my mistake or problem was…
thank you for your time and advice/help.

maybe those can be useful,

additional information for my construction is below;

group item detail

group item members

items

Rollershutter item detail

I have one topic I can’t solve…

The group item MyGroup has members being group items themselves, say one of these is SubGroup_x.
SubGroup_x has one group member SubGroup_x_Online.

I want to apply a filter to the members of MyGroup based on the state of SubGroup_x_Online

I was unsuccessful in trying to append the member’s item name with _Online.

val list = MyGroup.members.filter[ i|(i.name + '_Online').state==OFF ]

… doesn’t work.

Hope someone can help.

Thanks in advance.

maxmaximax

That’s just a String. It’s not an Item. It has no .state. It’s just a sequence of characters.

You need to access the actual Group Item. If all you have is the name of the Item you need to pull that Item from the ItemRegistry. See Design Pattern: Associated Items.

With the details provided, it might be a better solution to use the semantic model potentially as you can get the Equipment that one Item belongs to and from there get the associated Online Item from the Equipment.

In the other rules languages it’s much easier to get at an Item when all you have is the Item name. But in none of them can you append two Strings and instantly have an Item.

val list = MyGroup.members.filter[ i| ir.getItem(i.name + '_Online').state==OFF ]

doesn’t seem to work in Rules DSL…

The name 'ir' cannot be resolved to an item or type

Is there another way to get this done in Rules DSL or can this only be done with JSR223 Python?

// put this line at the very top, outside the rule
import org.openhab.core.model.script.ScriptServiceUtil

// put this inside the rule
val list = MyGroup.members.filter[ i| ScriptServiceUtil.getItemRegistry.getItem(i.name + '_Online').state==OFF ]