Help converting group based xtend rule to JSR233 please

Boy I should have waited to wake up some before posting that haha.

You could import GroupItem but it would be easier to do this:

        if roomGroup.type == "Group":

Excellent! Thanks again for more help. I’ve made more progress and my rule now looks like this:

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

@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")
# @when("Item PIRMotionSensor2_MotionAlarm 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 ({})".format(
                        event.itemName, triggeringRoomGroup.name))
                    
                    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 = lightingItem.name + "_gLightingPreset" + ir.getItem(event.itemName).state
                                # break # exits the roomMember loop, no need to continue if we found the triggering item

I can’t get the syntax right for the “ir.getItem(event.itemName).state” on the second to last line. I need to get the state of the triggering item. I’ve tried all sorts of incantations but can’t find the right one.

Ignore my last message. I’ve solved it! :slight_smile:

What was the issue?
And why all of the casting to string str()?

The issue was me being dumb I think. I’ve got the rule working, it currently looks like this:

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

@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 ({})".format(
                        event.itemName, triggeringRoomGroup.name))
                    
                    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 = '%s%s%s' % (str(lightingItem.name), str("_gLightingPreset"), str(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, ir.getItem(str(lightingPresetName)).state)
                                else:
                                    lightingItem.sendCommand(OFF)
                                  
        # break # exits the roomMember loop, no need to continue if we found the triggering item

It seemed that I needed to cast to string to avoid Unicode errors but they might not be needed and I might be going overboard. I need to spend some time trying to strip some of those casts out. I’d also like to see if I can avoid some of those nested loops, maybe with an early break and possibly using some metadata to identify items as lighting items.

Thanks for all your help and patience :slight_smile:

Glad you got it working, I think I see what your issue was. You should use string format instead:

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

It attempts to cast all arguments given to a string automatically, and frankly it looks nicer. I’ve given a second example of the same thing using named substitutions, though not necessary in this case it can be useful to keep things clear or when you need the same thing repeated in the string.

In terms of metadata and what you are doing with your lights, I might have just the thing. I am nearing completion on I have written an extension for the Helper Libraries that will basically do what I think you are doing with your lights. It puts all settings in metadata and uses “scenes” like you seem to use “presets”. It is usable right now, but lets say it is in “beta”. I will be pushing updates and possibly adding a few things before it gets merged. The catch is that I haven’t written any documentation yet, but I am working on that now. If you’re feeling adventurous you can look into it now, or wait for the docs.
Eos Lighting

This looks like a good place to use format…

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

Should there be another underscore after the gLightingPreset? I should have ready further… Michael already suggested this.

No need to go to the Item registry for this…

items[lightingPresetName]

Metadata is awesome, but for this purpose you might want to try adding your lighting Items to a group (like gLight) and then filter on that group.

Thanks for the tips about the string formatting and accessing items without using the registry. I did think using the item registry like that looked a bit long winded.

I do use a gLighting group in the rule. I’ll go through the code, in case it’s of benefit to anyone else trying to do the same sort of thing…

This section of the rule determines which room group the triggering item is in:

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

This next section loops through the room items and then the lighting items and determines if each of the lighting items is in the triggering item room group:

            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))

At this point we’re looping around the items that are both in the triggering item room group and in the lighting group. If the items were represented in a Venn diagram we’d be looping through the items that are in the intersection of the room group that the triggering item is in and the lighting items in the same room group. Then we can build the item name of the preset (or scene as I probably should have called it) and either set the item to the preset state or turn it off:

                        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)

There’s a lot of looping involved in this rule but I guess in practical terms, given the processing power of the server it’s running on it doesn’t really matter. I like this approach because I can move lights around the house and move them into different presets just by changing the room group that they’re in. This one rule can be used for any of the rooms in the house due to the trigger being “Member of gLightingPresetSetting received update”.

There’s another rule, still in xtend, that deals with motion detectors, lux thresholds and timers. My next challenge! :slight_smile:

I have something you may really like and I’ll get it submitted as a community contribution in to the helper library repo this week. I’ve been testing and tweaking it for over a year now and I’m way past due in getting it to the community. I last mentioned it here with some more detail. It’s very simple, very similar to what you are doing, and very quick. And it uses the same concept, where adding/changing/removing a device to the automation is as simple as adding it to a group. If it’s lighting depends on lux, then add lux triggers and values to metadata. If the action needs a timer, add it to metadata.

Basically, take all of the logic out of your rule and put it into groups, with Area_Triggers and Area_Actions as groups using the associated Items DP. Very similar to what you’ve done! I think we’ve actually communicated about this before.

1 Like

Sounds very interesting! I’d love to see it!

I’ve held off from using metadata because there’s no super simple way of maintaining it via the Paper UI. I base my logic around groups: room groups, a lighting group, a motion detector group, a Lux sensor group, a Lux threshold group, etc. All coordinated around the intersection with the room groups. The “member of group” trigger was a HUGE thing for reducing the amount of code and rules in my system.

Eos has an editor for its metadata. It’s a good place to start until I have the docs up.

@ysc, is metadata editing functional in the UI you are working on? If not, I’ve thought about adapting the PersistenceViewer for reading/editing metadata. It really shouldn’t take that much effort, but it’s been easy enough to use scripts for it…

from core.metadata import set_metadata, remove_metadata

set_metadata("US_GarageAttached_Dimmer", "Area_Action", {"Timer": {"OFF": {"Time": 180}}}, overwrite=True)
set_metadata("US_MasterBathroom_Speaker_Player", "Area_Action", {"Timer": {"OFF": {"Time": 30}}}, overwrite=True)
set_metadata("DS_MasterBathroom_Speaker_Player", "Area_Action", {"Timer": {"OFF": {"Time": 30}}}, overwrite=True)

set_metadata("DS_FamilyRoom_TV_LED_Color", "Area_Action", {"Mode": {"Morning": {"Low_Lux_Trigger":5, "Brightness":10, "Hue":100, "Saturation":100}, "Day": {"Low_Lux_Trigger":55, "Brightness":10, "Hue":255, "Saturation":100}, "Evening": {"Low_Lux_Trigger":90, "Brightness":10, "Hue":255, "Saturation":100}, "Night": {"Low_Lux_Trigger":90, "Brightness":10, "Hue":240, "Saturation":100}, "Late": {"Low_Lux_Trigger":5, "Brightness":10, "Hue":0, "Saturation":100}}}, overwrite=True)

set_metadata("DS_Kitchen_Sink_Switch", "Area_Action", {"Mode": {"Morning": {"Low_Lux_Trigger":5, "Brightness":98}, "Day": {"Low_Lux_Trigger":90, "Brightness":98}, "Evening": {"Low_Lux_Trigger":90, "Brightness":98}, "Night": {"Low_Lux_Trigger":90, "Brightness":98}, "Late": {"Low_Lux_Trigger":90, "Brightness":0}}}, overwrite=True)
set_metadata("DS_Kitchen_Spots_Dimmer", "Area_Action", {"Mode": {"Morning": {"Low_Lux_Trigger":5, "Brightness":98}, "Day": {"Low_Lux_Trigger":90, "Brightness":98}, "Evening": {"Low_Lux_Trigger":90, "Brightness":98}, "Night": {"Low_Lux_Trigger":90, "Brightness":98}, "Late": {"Low_Lux_Trigger":90, "Brightness":0}}}, overwrite=True)

Yep, it’s kind of functional but the goal is to ship it with OH3, not before :wink:

Thank you for the reply… completely understood! I just don’t recall seeing it last time I took a look… glad to hear it’s evolving. I’ll take another peak before considering building something on my own for manipulating metadata.

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.