Help converting group based xtend rule to JSR233 please

I’ve not looked really closely at this and I may have missed something, but wouldn’t a filter be appropriate here?

triggerinRoomGroup = filter(lambda roomGroup: roomGroup.type == "Group" and ir.getItem[event.itemName] in roomGroup.members, ir.getItem["gRoom"].members)

If I haven’t messed up the syntax this should replace the first set of nested loops, right?

I tried removing these lines:

for roomGroup in ir.getItem("gRoom").members:
    if roomGroup.type == "Group":
        for roomMember in ir.getItem(str(roomGroup.name)).members:
            if roomMember.name == event.itemName:
                triggeringRoomGroup = roomGroup

and replaced them with:

triggerinRoomGroup = filter(lambda roomGroup: roomGroup.type == "Group" and ir.getItem[event.itemName] in roomGroup.members, ir.getItem["gRoom"].members)

but got the following error in the log when I triggered the rule:

2019-08-12 21:06:53.701 [INFO ] [mple Channel event rule (decorators)] - gLightingPresetSetting received update
2019-08-12 21:06:53.702 [ERROR] [mple Channel event rule (decorators)] - Traceback (most recent call last):
  File "/etc/openhab2/automation/lib/python/core/log.py", line 43, in wrapper
    return fn(*args, **kwargs)
  File "<script>", line 17, in dev_lighting_preset_selection
TypeError: 'instancemethod' object is unsubscriptable

I can’t say that error message really gives me much of a clue as to what the problem is.

I’ve seen that error before and I can’t for the life of me remember what it meant. Maybe I’m wrong about roomGroup,members being a Python list of Items?

Oh, total bone headed typo on my part, use parens with ir.getItem, not brackets.

... ir.getItem(event.itemName) in roomGroup.members, ir.getItem("gRoom").members

Progress of sorts, the error message is now:

2019-08-12 21:19:12.621 [INFO ] [mple Channel event rule (decorators)] - gLightingPresetSetting received update
2019-08-12 21:19:12.622 [ERROR] [mple Channel event rule (decorators)] - Traceback (most recent call last):
  File "/etc/openhab2/automation/lib/python/core/log.py", line 43, in wrapper
    return fn(*args, **kwargs)
  File "<script>", line 18, in dev_lighting_preset_selection
AttributeError: 'NoneType' object has no attribute 'name'

2019-08-12 21:19:12.622 [ERROR] [omation.core.internal.RuleEngineImpl] - Failed to execute rule '55a142a8-29d4-4834-a4bf-48de19f3d519': Fail to execute action: 1

Hmmmmm. That error is occurring on the next line of code. I’m going to guess that the filter is returning None. That means it’s at least syntactically correct but it is not successfully finding the Item.

If you want to continue down this path debugging, the next step I’d take is to split it up a bit. Since you have a working solution it may not be worth your effort.

roomGroups = filter(lambda roomGroup: roomGroup.type == "Group", ir.getItem("gRoom").members
dev_lighting_preset_selection.log.info("Found groups: {}".format(roomGroups))

That will show what the first part of the conditional does. If that looks good, next try:

roomGroup = filter(lambda rmGrp: ir.getItem(event.itemName) in rmGrp.members, roomGroups)
dev_lighting_preset_selection.log.info("Found group: {}".format(roomGroup))

That will show if the second part returns the right Group or no Group at all.

But it occurs to me that we don’t have to go about it this way. Is there a way you can go about it the other direction by checking the names of the Groups that the triggering Item is a member of?

roomGroup = filter(lambda grp: grp == ???, ir.getItem(event.itemName).getGroupNames())

If the Item is only a member of one Group then it’s even easier, just get the first element from getGroupNames(). If there are more than one, maybe you can use an Associated Item type naming scheme along the lines of:

roomGroup = filter(lambda grp: startswith("Room"), ir.getItem(event.itemName).getGroupNames())

All of this is assuming that the List returned by the Item is a proper Python List and not a java.util.List in which case all bets are off.

Adding the first line you suggested:

roomGroups = filter(lambda roomGroup: roomGroup.type == "Group", ir.getItem("gRoom").members
    dev_lighting_preset_selection.log.info("Found groups: {}".format(roomGroups))

gives the following syntax error in the log:

2019-08-12 21:57:07.065 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/etc/openhab2/automation/jsr223/personal/lighting_preset.py': SyntaxError: no viable alternative at input 'dev_lig
hting_preset_selection' in <script> at line number 22 at column number 8

I’m not being much help other than reporting the errors in the logs, sorry. Since I’ve only just started working with Jython rules I don’t have much wisdom to add! I do find this very interesting though and if “we” get these filter statements working I’d be interested to see if there’s a way to evaluate the performance of the different approaches.

Now it’s complaining about the log statement. Did the name of your Rule function change since the earlier posts?

The @rule decorator automatically creates a logger for the Rule and you access the logger using the <name of the function>.log. For example, I have

@rule("Is Cloudy", description="Generates an event when it's cloudy or not", tags=["weather"])
@when("Item vCloudiness changed")
def is_cloudy(event):
    # some code

To log I would call is_cloudy.log.info("My log statement").

So either the Rule function changed names since the first posting that shows the whole code, or I have a typo in the name of the function because it is saying that dev_lighting_preset_selection doesn’t exist.

I don’t mind working through these errors with you as I think it’s a learning experience for both of us.

I suspect both versions will run so fast there will be no measurable difference.

Is my third approach idea above feasible (just search through the Groups that the triggering Item is a member of directly)? If so that would be the simplest and most efficient way to achieve this of them all.

Yes… that’s what I was hinting at :wink:.

I think this error may be due to a bad indent. The rules DSL doesn’t care about indents and dedents. This is not the case in Python… 2.1.7 Indentation. I suggest to us VS Code for editing your scripts and to only uses spaces instead of tabs (I think this is the default setting).

Try this…

    triggeringRoomGroup = [group for group in ir.getItem("gRoom").members if ir.getItem(itemName) in group.members][0]

This assumes there are only groups in gRoom. If there are more than GroupItems in gRoom, then…

    triggeringRoomGroup = [group for group in ir.getItem("gRoom").members if group.type == "Group" and ir.getItem(itemName) in group.members][0]

The equivalent using filter and a lambda is…

triggeringRoomGroup = filter(lambda group: group.type == "Group" and ir.getItem(itemName) in group.members, ir.getItem("gRoom").members)[0]

List and dict comprehension (search those terms) are very powerful features of Python. Lambdas can also be used, and I’d used them in the examples since DSL users would recognize them. The docs now have examples for both.

The complexity of your script would be reduced if you added your triggering Items to groups (i.e. gFamilyRoom_Trigger) which were all in a parent group (gArea_Trigger). Then your rule could trigger on changes to the members of gArea_Trigger. Your action Items, like lights, etc., would go into groups too and the triggering group would call it’s associated action group. Since your using other Items to store configuration data instead of metadata, these can stay in your regular gRoom groups and access them with associated Items.

This is a portion of my script to illustrate what I mean. You’d ask about comparing the times of certain changes, so I’ve included how I check how changes to the script effects performance. In my case, it takes ~5ms on average for the rule to run. At one point, I’d included a running averaging of the times, but took it out since it was affecting performance!

@rule("Light: Area trigger")
@when("Member of gArea_Trigger changed")
def area_trigger(event):
    start_time = DateTime.now().getMillis()
    area_trigger.log.debug("{}: {}: start".format(event.itemName, event.itemState))
    group_name = event.itemName.replace("_Trigger","")
    if "Speaker" in group_name:
        speaker_action(group_name, event)
    else:
        action_group = ir.getItem("{}_Action".format(group_name))
        for light_item in action_group.getMembers():
            ...
    area_trigger.log.critical("{}: {}: time=[{}]".format(event.itemName, event.itemState, DateTime.now().getMillis() - start_time))

That worked, well, almost, I spotted your deliberate mistake that you put there to test me. The itemName was missing the event. in front of it. The code to get the triggeringRoomGroup is now:

triggeringRoomGroup = [group for group in ir.getItem("gRoom").members if group.type == "Group" and ir.getItem(event.itemName) in group.members][0]

The nested for loop that follows it finds the lighting items in group gLighting that are also in the triggeringRoomGroup. It’s very similar to the filter expression that gets the triggering room group except we want to get all the members of gLighting that are also in the triggeringRoomGroup, not just the first member that matches (wihch is I guess the “[0]” does at the end of the above code).

I’ve put the code to set the item state to the value store in the preset item in a function:

def presetItemSendCommand(lightingItem, event):
    dev_lighting_preset_selection.log.info("test function ({})". format(event.itemName))

    lightingPresetName = "{}_gLightingPreset{}".format(lightingItem.name, event.itemState)

    dev_lighting_preset_selection.log.info("NG4: Lighting preset item name is ({}), with state ({}).".format(
        lightingPresetName, ir.getItem(str(lightingPresetName)).state))

    if ir.getItem(str(lightingPresetName)).state != NULL:
        events.sendCommand(lightingItem, items[lightingPresetName]) 
    else:
        events.sendCommand(lightingItem, OFF)

So the rule now looks like this:

@rule("Example Channel event rule (decorators)", description="This is an example rule that is triggered by the sun setting", tags=["Example", "Tag1"])
@when("Member of gLightingPresetSetting received update")
def dev_lighting_preset_selection(event):
    triggeringRoomGroup = None
    presetItem          = None
    dev_lighting_preset_selection.log.info("gLightingPresetSetting received update")

    triggeringRoomGroup = [group for group in ir.getItem("gRoom").members if group.type == "Group" and ir.getItem(event.itemName) in group.members][0]
    # presetItem          = [group for group in ir.getItem(str(triggeringRoomGroup.name)).members if ir.getItem(event.itemName) in group.members]
    dev_lighting_preset_selection.log.info("NG4: Triggering item was ({}), in room group ({}) with state ({}).".format(
        event.itemName, triggeringRoomGroup.name, event.itemState))
    for roomItem in ir.getItem(str(triggeringRoomGroup.name)).members:
        for lightingItem in ir.getItem(str("gLighting")).members:
            if roomItem.name == lightingItem.name:
                dev_lighting_preset_selection.log.info("NG4: Lighting item name is ({}), in room group ({}) with state ({}).".format(
                    lightingItem.name, triggeringRoomGroup.name, lightingItem.state))

                presetItemSendCommand(lightingItem, event)

Is it possible do a similar sort of filter expression to get the lightingItems but call presetItemSendCommand() from within that same line of code? I.e., combine the roomItem and lightingItem for loops together and call presetItemSendCommand() for every match? I’ve been reading python documentation and feel like I can almost do it but can’t quite get there…

Maybe by using map()?

I’ve been doing a bit more work on this and have the following code in my rule:

triggeringRoomGroup = [group for group in ir.getItem("gRoom").members if group.type == "Group" and ir.getItem(event.itemName) in group.members][0]
lightItem           = map( printItem, [lightItem for lightItem in ir.getItem(str("gLighting")).members if lightItem.type != "Group"])

with the function printItem being:

def printItem(item):
    dev_lighting_preset_selection.log.info("blah blah blah ({})". format(item))

This results in the following in the log:

2019-08-13 20:48:48.758 [INFO ] [mple Channel event rule (decorators)] - gLightingPresetSetting received update
2019-08-13 20:48:48.759 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (KitchenRGBWLight1_Dimmer (Type=DimmerItem, State=0, Label=Dimmer, Category=DimmableLight, Groups=[gKitchen, gLighting]))
2019-08-13 20:48:48.760 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (DimmerKitchenCeilingRing_Dimmer (Type=DimmerItem, State=0, Label=Dimmer Switch 1, Category=DimmableLight, Groups=[gKitchen, gLighting]))
2019-08-13 20:48:48.760 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (BulbDimmableWhiteTemp4_Brightness (Type=DimmerItem, State=0, Label=Brightness, Category=DimmableLight, Groups=[gTopLanding, gLighting]))
2019-08-13 20:48:48.760 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (BulbDimmableWhiteTemp2_Brightness (Type=DimmerItem, State=100, Label=Brightness, Category=DimmableLight, Groups=[gLauriesRoom, gLighting])
)
2019-08-13 20:48:48.761 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (SwitchSocket5_Switch (Type=SwitchItem, State=OFF, Label=Switch, Category=Switch, Groups=[gJulessRoom, gLighting]))
2019-08-13 20:48:48.761 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (DimmerKitchenCupboards_Dimmer (Type=DimmerItem, State=0, Label=Dimmer Switch 1, Category=DimmableLight, Groups=[gKitchen, gLighting]))
2019-08-13 20:48:48.761 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (SwitchSocket4_Switch (Type=SwitchItem, State=OFF, Label=Switch, Category=Switch, Groups=[gFrontroom, gLighting]))
2019-08-13 20:48:48.762 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (LightCeilingBlueroomDimmer_Dimmer (Type=DimmerItem, State=100, Label=Dimmer, Category=DimmableLight, Groups=[gBlueRoom, gLighting]))
2019-08-13 20:48:48.762 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (DimmerBathroomCeilingLights_Dimmer (Type=DimmerItem, State=0, Label=Dimmer, Category=DimmableLight, Groups=[gBathroom, gLighting]))
2019-08-13 20:48:48.762 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (SwitchSocket3_Switch (Type=SwitchItem, State=OFF, Label=Switch, Category=Switch, Groups=[gKitchen, gLighting]))
2019-08-13 20:48:48.763 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (DimmerMiddleLandingCeiling_Dimmer (Type=DimmerItem, State=0, Label=Dimmer 1, Category=DimmableLight, Groups=[gMiddleLanding, gLighting]))
2019-08-13 20:48:48.763 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (SwitchSocket6_Switch (Type=SwitchItem, State=OFF, Label=Switch, Category=Switch, Groups=[gBlueRoom, gLighting]))
2019-08-13 20:48:48.764 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (LaurieDoorLight_Brightness (Type=DimmerItem, State=100, Label=Brightness, Category=DimmableLight, Groups=[gLauriesRoom, gLighting]))

One of the attributes of lightItem is the groups it’s in. Awesome! I then tried to incorporate a check on the lightItem.Groups list:

lightItem = map( printItem, [lightItem for lightItem in ir.getItem(str("gLighting")).members if lightItem.type != "Group" and triggeringRoomGroup.name in lightItem.Groups])

But that results in the following error in the log:

2019-08-13 20:51:59.445 [ERROR] [mple Channel event rule (decorators)] - Traceback (most recent call last):
  File "/etc/openhab2/automation/lib/python/core/log.py", line 43, in wrapper
    return fn(*args, **kwargs)
  File "<script>", line 13, in dev_lighting_preset_selection
AttributeError: 'org.eclipse.smarthome.core.library.items.DimmerIte' object has no attribute 'Groups'

2019-08-13 20:51:59.445 [ERROR] [omation.core.internal.RuleEngineImpl] - Failed to execute rule '59b4c1ab-373b-4863-a5fa-bbda6b491ace': Fail to execute action: 1

There’s no attribute called Groups? But I just saw it in the log earlier. Any ideas what I’m missing?

I think you need to call getGroupNames()

...triggeringRoomGroup.name in lightItem.getGroupNames()])

A-ha! Thank you! That fixed it.

Code:

lightItem = map( printItem, [lightItem for lightItem in ir.getItem(str("gLighting")).members if lightItem.type != "Group" and triggeringRoomGroup.name in lightItem.getGroupNames()])

Log output:

2019-08-13 22:04:17.047 [INFO ] [mple Channel event rule (decorators)] - gLightingPresetSetting received update
2019-08-13 22:04:17.050 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (KitchenRGBWLight1_Dimmer (Type=DimmerItem, State=0, Label=Dimmer, Category=DimmableLight, Groups=[gKitchen, gLighting]))
2019-08-13 22:04:17.056 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (DimmerKitchenCeilingRing_Dimmer (Type=DimmerItem, State=0, Label=Dimmer Switch 1, Category=DimmableLight, Groups=[gKitchen, gLighting]))
2019-08-13 22:04:17.057 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (DimmerKitchenCupboards_Dimmer (Type=DimmerItem, State=0, Label=Dimmer Switch 1, Category=DimmableLight, Groups=[gKitchen, gLighting]))
2019-08-13 22:04:17.057 [INFO ] [mple Channel event rule (decorators)] - blah blah blah (SwitchSocket3_Switch (Type=SwitchItem, State=OFF, Label=Switch, Category=Switch, Groups=[gKitchen, gLighting]))

However, if I try and call the function that actually sets the lights to the preset item values, I get an error. Function that sets the lights states:

def presetItemSendCommand(lightingItem, event):
    # dev_lighting_preset_selection.log.info("test function ({})". format(event.itemName))

    lightingPresetName = "{}_gLightingPreset{}".format(lightingItem.name, event.itemState)

    dev_lighting_preset_selection.log.info("NG4: Lighting preset item name is ({}), with state ({}).".format(
        lightingPresetName, ir.getItem(str(lightingPresetName)).state))

    if ir.getItem(str(lightingPresetName)).state != NULL:
        events.sendCommand(lightingItem, items[lightingPresetName]) 
    else:
        events.sendCommand(lightingItem, OFF)

This accepts two arguments, the lighting item name and the lighting preset number. It does some concentation to derive the lighting item preset item name, e.g. SwitchSocket3_Switch and preset 2 gives the preset item name SwitchSocket3_Switch_gLightingPreset2. If I call this function from the revised map code:

lightItem = map(presetItemSendCommand, [lightItem for lightItem in ir.getItem(str("gLighting")).members if lightItem.type != "Group" and triggeringRoomGroup.name in lightItem.getGroupNames()], event)

I get the following error:

2019-08-13 22:10:17.111 [ERROR] [mple Channel event rule (decorators)] - Traceback (most recent call last):
  File "/etc/openhab2/automation/lib/python/core/log.py", line 43, in wrapper
    return fn(*args, **kwargs)
  File "<script>", line 13, in dev_lighting_preset_selection
TypeError: argument 2 to map() must support iteration

2019-08-13 22:10:17.111 [ERROR] [omation.core.internal.RuleEngineImpl] - Failed to execute rule 'bd293ba8-c259-4720-b3cb-ce3f58a8b2e6': Fail to execute action: 1
2019-08-13 22:10:17.148 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Dev Motion Detected Trigger Virtual Switch': cannot invoke method public java.lang.String org.eclipse.smarthome.core.items.GenericItem.getName() on
null

This looks like it’s because map() requires all arguments to be lists and I’m passing it a list (the lighting items) and a variable (the event value, the value of the preset item that was selected).

For clarification, the preset item in the sitemap looks like this:

Selection item=lightingPresetKitchen label="Lighting Preset" mappings=["1"="All on full", "2"="Cupboards full, ceiling dim", "3"="Cupboards full, centre ceiling full", "4"="Cupboards full, centre ceiling dim"]

I guess it makes sense that map() works that way. If there’s no sneaky syntactical workaround then I suppose I could put together a hack to store the preset value (called event in the code above) in a global variable before calling presetItemSendCommand but that wouldn’t work if two presets were changed at the same time to different numbers. Unlikely but still, it would be a bit messy.

Functions like map and filter and reduce and the like require a dict or a list as the second argument. You are passing it event which is neither a dict nor a list. And it looks like you are passing three arguments to the map when map only accepts two. What are you trying to do by passing event to the map?

It kind of looks like you are trying to call presetItemSendCommand with two arguments, in which case you need to use a lambda explicitly I believe. Something like

lightItem = map(lambda light: presetItemSendCommand(light, event), [lightItem for ...

As with Rules DSL and the stuff before the |, the stuff between the lambda and the : defines the arguments to the lambda.

lightItem = map[ light | presetItemSendCommand.apply(light, receivedCommand) ]

or something like that would be roughly equivalent. And I do be roughly, there are lots of differences here and I’m not even positive you can call map like that in Rules DSL. But it illustrates my point.

Personal preference… I use the attributes instead of methods, when available. In most (all?) cases, the method is just returning the attribute… and the attribute looks cleaner.

...triggeringRoomGroup.name in lightItem.groupNames])

You can do it without the lambda, but you need to pass all arguments to the function as iterables…

lightItem = map(function, [argA1, argA2, argA3], [argB1, argB2, argB3])

I don’t see a need for the map though. How does this work for you?

@rule("Example Channel event rule (decorators)", description="This is an example rule that is triggered by the sun setting", tags=["Example", "Tag1"])
@when("Member of gLightingPresetSetting received update")
def dev_lighting_preset_selection(event):
    dev_lighting_preset_selection.log.info("gLightingPresetSetting received update")
    triggeringRoomGroup = [group for group in ir.getItem("gRoom").members if group.type == "Group" and ir.getItem(event.itemName) in group.members][0]
    dev_lighting_preset_selection.log.info("NG4: Triggering item was ({}), in room group ({}) with state ({}).".format(event.itemName, triggeringRoomGroup.name, event.itemState))
    for lightingItem in [lightingItem for lightingItem in triggeringRoomGroup.members if "gLighting" in lightingItem.groupNames]:
        dev_lighting_preset_selection.log.info("NG4: Lighting item name is ({}), in room group ({}) with state ({}).".format(lightingItem.name, triggeringRoomGroup.name, lightingItem.state))
        presetItemSendCommand(lightingItem, event)

If you’re not using the presetItemSendCommand function anywhere but in this rule, I’d just just leave it in there.

Excellent! That works perfectly! Thank you!

The rule started as this:

rule "Dev Lighting Preset Selection"
when
	Member of gLightingPresetSetting received update
then
	var	GroupItem 	triggeringRoomGroup

	logInfo("dev", "NG3: Lighting preset triggered.")
	logInfo("dev", "NG3: Lighting preset triggeringItem = (" + triggeringItem.name + ") " + "has state (" + triggeringItem.state + ")." )

    // gRoomGroup = "gRoom" as GroupItem
	gRoom.members.forEach[roomGroup|
	 	if (roomGroup instanceof GroupItem)
	 	{
	 		roomGroup.members.forEach[roomMember|
	 			if (roomMember.name == triggeringItem.name)
	 			{
					triggeringRoomGroup = roomGroup
	 				logInfo("dev", "NG3: Triggering item was (" + triggeringItem.name + "), in room group (" + triggeringRoomGroup.name + ")")
	 			}
			]
	 	}]

	// Loop through all items in the room group to get items in room with required values, e.g. motion lighting timeout, lighting luminance threshold, etc
	triggeringRoomGroup.members.forEach[roomItem|

		gLighting.members.forEach[lightingMember|
			if (roomItem.name == lightingMember.name)
			{
				val lightingPresetName = lightingMember.name + "_gLightingPreset" + triggeringItem.state
				val lightingPresetItemName = gLightingPreset.members.findFirst[ i | i.name == lightingPresetName ]
				logInfo("dev", "NG3: Lighting item name is (" + lightingMember.name + ") in room group (" + triggeringRoomGroup.name + ") with state (" + lightingMember.state + ").")
				logInfo("dev", "NG3: Lighting preset item name is (" + lightingPresetItemName.name + ") with state (" + lightingPresetItemName.state + ").")
				if (lightingPresetItemName.state != NULL) {
					// logInfo("dev", "NG3: setting lighting item to preset value...")
					lightingMember.sendCommand(lightingPresetItemName.state)
					// logInfo("dev", "NG3: set lighting item to preset value.")
				}
				else {
					lightingMember.sendCommand(OFF)
				}
 				// Thread::sleep(100)
			}
		]
    ]

end

Was then converted, using the same logic, to Jython:

@rule("Example Channel event rule (decorators)", description="This is an example rule that is triggered by the sun setting", tags=["Example", "Tag1"])
@when("Member of gLightingPresetSetting received update")
def dev_lighting_preset_selection(event):
    triggeringRoomGroup = None
    dev_lighting_preset_selection.log.info("gLightingPresetSetting received update")

    for roomGroup in ir.getItem("gRoom").members:
        # if isinstance(roomGroup, GroupItem):
        if roomGroup.type == "Group":
            for roomMember in ir.getItem(str(roomGroup.name)).members:
                if roomMember.name == event.itemName:
                    triggeringRoomGroup = roomGroup
                    dev_lighting_preset_selection.log.info("NG4: Triggering item was ({}), in room group ({}) with state ({}).".format(
                        event.itemName, triggeringRoomGroup.name, event.itemState))
                    
                    for roomItem in ir.getItem(str(triggeringRoomGroup.name)).members:
                        for lightingItem in ir.getItem(str("gLighting")).members:
                            if roomItem.name == lightingItem.name:
                                dev_lighting_preset_selection.log.info("NG4: Lighting item name is ({}), in room group ({}) with state ({}).".format(
                                    lightingItem.name, triggeringRoomGroup.name, lightingItem.state))

                                lightingPresetName = "{}_gLightingPreset{}".format(lightingItem.name, event.itemState)

                                dev_lighting_preset_selection.log.info("NG4: Lighting preset item name is ({}), with state ({}).".format(
                                    lightingPresetName, ir.getItem(str(lightingPresetName)).state))

                                if ir.getItem(str(lightingPresetName)).state != NULL:
                                    events.sendCommand(lightingItem, items[lightingPresetName])
                                else:
                                    events.sendCommand(lightingItem, OFF)

and then optimised for the mighty power of Jython to this:

@rule("Example Channel event rule (decorators)", description="This is an example rule that is triggered by the sun setting", tags=["Example", "Tag1"])
@when("Member of gLightingPresetSetting received update")
def dev_lighting_preset_selection(event):
    triggeringRoomGroup = None
    presetItem          = None
    dev_lighting_preset_selection.log.info("gLightingPresetSetting received update")

    triggeringRoomGroup = [group for group in ir.getItem("gRoom").members if group.type == "Group" and ir.getItem(event.itemName) in group.members][0]
    dev_lighting_preset_selection.log.info("NG4: Triggering item was ({}), in room group ({}) with state ({}).".format( 
        event.itemName, triggeringRoomGroup.name, event.itemState))
    for lightingItem in [lightingItem for lightingItem in triggeringRoomGroup.members if "gLighting" in lightingItem.groupNames]:
        dev_lighting_preset_selection.log.info("NG4: Lighting item name is ({}), in room group ({}) with state ({}).".format(
            lightingItem.name, triggeringRoomGroup.name, lightingItem.state))
        presetItemSendCommand(lightingItem, event)
                                 

def presetItemSendCommand(lightingItem, event):
    lightingPresetName = "{}_gLightingPreset{}".format(lightingItem.name, event.itemState)

    dev_lighting_preset_selection.log.info("NG4: Lighting preset item name is ({}), with state ({}).".format(
        lightingPresetName, ir.getItem(str(lightingPresetName)).state))

    if ir.getItem(str(lightingPresetName)).state != NULL:
        events.sendCommand(lightingItem, items[lightingPresetName]) 
    else:
        events.sendCommand(lightingItem, OFF)

Much much nicer to my eyes than the Xtend code.

Many thanks to all who helped, my next challenge will be the motion based timers that trigger the presets.

In the Java GenericItems class, which is what I assumed is what we get when we pull an Item out of the ir, that attribute is protected. In the Java world that would mean that only classes in the same package would be able to access it directly without going through the getter. I assumed the same would apply in the JSR223 context.

There are a few more things left to clean up!

These can be removed.

There are two places where this should be replaced with just…

items[lightingPresetName]

You need to be careful here, since sendCommand is for sending commands. You are sending a state, but not all states are commands. If you prefer, this could also be simplified to a one-liner…

events.sendCommand(lightingItem, items[lightingPresetName] if items[lightingPresetName] != NULL else OFF)

… and the helper libraries! :wink:

If not sendCommand, then what syntax should be used?

I think the warning is that not all States can be sent as a Command so if you happen to have a lightingItem that has a State that is not also a Command you will get an error. Probably the easiest solution would be to send the Command as a String.

events.sendCommand(lightingItem, str(items[lightingPresetName]))

While not all States are a Command, I known of no Item that won’t accept a representation of the State as a command. It all is concerning types. Don’t take these examples as real, I’m going from memory.

Let’s say you have a Number Item .Number Item has a State of type DecimalType. This means DecimalType is of type State. Now let’s assume that DecimalType is not also of type Command. That means if you called:

events.sendCommand(ir.getItem("MyNumberItem"), items["MyNumberItem"])

you would get an error because MyNumberItem’s state is of type DecimalType and DecimalType isn’t of type Command.

The work around of converting the state to a string works because instead of just a Command, there is a version of sendCommand that accepts a String and then the code tries to parse that String to an acceptable command.