[SOLVED] Iterate through nested groups

Hi all,

I’m currently trying to create a rule that checks if the sun is shining on windows and it should be shaded.

Therefore I created some groups

// Overall Group
Group:Switch			Fenster_Sonne
// One group for each angle of windows
Group:Switch			Fenster_Sonne_110		(Fenster_Sonne)
Group:Switch			Fenster_Sonne_200		(Fenster_Sonne)

My plan was to iterate over Fenster_Sonne and get the angle from the seperate subgroups-name (e.g. 110 and 200). This is working so far.

Unfortunately I’m not able to iterate over the sub group to update the switch items which are part of it because of the following error:

2019-10-09 14:15:11.268 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Error during the execution of startup rule 'Sonne scheint auf Fenster': 'members' is not a member of 'org.eclipse.smarthome.core.items.Item'; line 30, column 3, length 16

This is my rule:

rule "Sonne scheint auf Fenster"
when
	System started or
	Item Sonne_Azimuth changed
then
	val azimuth = (Sonne_Azimuth.state as QuantityType<Number>).doubleValue

	Fenster_Sonne.members.forEach[grpItem |

		val direction = Double.parseDouble(grpItem.name.split("_").get(2))
		var new_state = OFF

		logInfo("fenster.rules", grpItem.name)

		if (azimuth > direction)
			new_state = ON

		grpItem?.members.forEach[item | item.postUpdate(new_state)]
	]
end

The error is caused by

grpItem?.members.forEach[item | item.postUpdate(new_state)]

Am I missing something or is it not possible to iterate over nested groups?

It is not possible to get members of a non-Group Item. You’ve kept secret your log of the Item that it failed on - is it a Group type?

You might find the myGroup.allMembers method useful? This reaches into nested groups.

Ok, maybe I didn’t make it clear, sorry for that.

As you see in the items listing I have one group called Fenster_Sonne that only contains group items. I’m iterating over this group to get the subgroups what is working fine and I can see the name of this subgroups in my log.

When I try to iterate over the subgroups which contain switch items I get only the mentioned error.

I know the method allMembers but I think this won’t help me since I wanted to get the subgroups first because it contains the angle in the name.

Hope my problem is more clear.

Another option would be to use allMembers, but filter the list of items by the Method getGroupNames (as GroupNames is a list, I doubt that this would be sortable, even if each Item in only member of one group.)

By default grpItem is going to be given to you as a GenericItem type. GenericItem doesn’t have a members method. So you need to tell OH that it’s a GroupItem.

Fenster_Sonne.members.forEach[GroupItem grpItem |

The ? on grpItem is redundant. You can’t get to that line if grpItem is null.

Because these are Switches, will it cause any side effects if you sendCommand to the members of grpItem? If not, then just sendCommand to grpItem and the command will be forwarded to all of it’s members and you don’t need the forEach at all.

You can use the trinary operator to initialize new_state.

val new_state = if(azimuth > direction) ON else OFF

Hi Rich

Thanks a lot. That did the trick.

My original idea was that it might be related to a missing cast or type definition. Unfortunately I’m working as a software engineer since more than 10 years but Java and me will never be best friends.

Additionally thanks for the other hints on my code. It was a first test and hence not the best code.
However I will consider your remarks.

A sendCommand won’t cause any side effects and should also work. My only idea of using forEach and postUpdate was that it is more intuitive since it is a status change and not a command to be executed. The result remains the same.

Well, Rules DSL isn’t Java either. If you are a coder, you might be happier using JSR223 Rules in Python, JavaScript, or Groovy.

The Python version of your Rule using the Helper Libraries would look something like (NOTE I’m just typing this in, it almost certainly has errors):

from core.rules import rule
from core.triggers import when

@rule("Sonne scheint auf Fenster"
@when("System started")
@when("Item Sonne_Azimuth changed")
def sonne_fenster(event):
    azimuth = items["Sonne_Azimuth"]

    for grpItem in ir.getItem("Fenster_Sonne").members:
        direction = QuantityType(u"{} °".format(grpItem.name.split("_")[2])) # convert the string to a number:Angle
        new_state = ON if azimuth > direction else OFF
        sonne_fenster.log.info(grpItem.name)

        for it in grpItem.members:
            it.postUpdate(new_state

Sure. I know. But the general coding is at least similar.

Maybe JSR223 is worth a deeper look once I find some time. Unfortunately I’m also not very familiar with writing python. I can read and understand most languages but when it comes down to writing code I’m more into the old school ones like assembler and C/C++ :laughing:

I’m with you on this. People do get surprises when they don’t maintain the distinction between commands and postupdate.

There will be a small overhead on openHAB’s event bus with the command method, because that becomes two events - both the command, and autoupdate’s subsequent postUpdate. Should be negligible except maybe with large groups.