Lambdas, groups and other Lambdas

Hi there and thanks for being such a great, supportive & friendly community! My openHAB setup was greatly inspired by the many posts & tutorials on this site - yet I am still struggling a bit with setting up “reusable rules” with the help of Lambdas.

What I am trying to accomplish is to set the brightness level and toggle of some of my lights. With single lights this code works really well:

val itemToggle = [ GenericItem item, String logMessage |
    if (item.state == OFF || item.state == NULL) {
        logInfo(item.name, "Switched on. (" + logMessage + ")")
        item.sendCommand(ON)
    } else if (item.state == ON) {
        logInfo(item.name, "Switched off. (" + logMessage + ")")
        item.sendCommand(OFF)
    }
]

val itemBrightnessLevelUp = [ GenericItem item, int brightnessStep |
    var brightness = if (item.state == NULL) 0 else item.state as Number + brightnessStep
    if (brightness <= 100) item.sendCommand(brightness)
]

val itemBrightnessLevelDown = [ GenericItem item, int brightnessStep |
    var brightness = if (item.state == NULL) 0 else item.state as Number - brightnessStep
    if (brightness >= 0) item.sendCommand(brightness)
]

and

// Global brightness step when changing the dimmer value
var brightnessStep = 20

rule "Remote (001): Remote for playing around"
when
    Item ZigbeeTradfriRemote001Action received update
then
    switch (ZigbeeTradfriRemote001Action.state) {
        case "toggle": itemToggle.apply(zwave_livingroom_tv, "Outlet at TV - manually switched.")
        case "brightness_up_click": itemBrightnessLevelUp.apply(ZigbeeTradfriLED001FloorUpperLevelBrightness, brightnessStep)
        case "brightness_down_click": itemBrightnessLevelDown.apply(ZigbeeTradfriLED001FloorUpperLevelBrightness, brightnessStep)
        case "arrow_right_click", 
        case "arrow_left_click": itemToggle.apply(ZigbeeTradfriLED001FloorUpperLevelSwitch, "Floor light - manually switched")
    }
end

What I want to do now is to switch groups. But there is no “GenericGroup” object type avaliable - at least not that I know of. What I found was a post by @rlkoshak where he describes the use of groups with filters (Rule which iterates through group(s) of similar items). I tried to modify the code a bit and see if that could work, but it doesn’t (LEDBedroom being my group of all light (brightness & switch) items in the bedroom):

rule "Remote (002): Bedroom lighting"
when
    Item ZigbeeTradfriRemote002Action received update
then
    switch (ZigbeeTradfriRemote002Action.state) {
        case "toggle": LEDBedroom.getAllMembers.filter[ i | i instanceof SwitchItem ].forEach[ filteredItem | itemToggle.apply(filteredItem, "Bedroom lighting- manually switched") ]
        case "brightness_up_click": LEDBedroom.getAllMembers.filter[ i | i instanceof DimmerItem ].forEach[ filteredItem | itemBrightnessLevelUp.apply(filteredItem, brightnessStep) ]
        case "brightness_down_click": LEDBedroom.getAllMembers.filter[ i | i instanceof DimmerItem ].forEach[ filteredItem | itemBrightnessLevelDown.apply(filteredItem, brightnessStep) ]
    }
end

The logs are talking about this:

There is no context to infer the closure’s argument types from. Consider typing the arguments or put the closures into a typed context.

I read somewhere that openHAB tries to “guess” which type of object I am using, but casting didn’t help or just threw another round of error messages. Do you know what I could do here?

I would love to use reusable code.

A Group is a type of Item.

Thanks. But is there a way to determine, if I currently “handle” a group of items or an item itself?

I think this thread covers the right areas

Great. Thank you!

item.getType

Yet, I would still like to know, if there is something wrong with the way I am using the “filter”. :slight_smile:

I just edited my code and tried to do it like this:

val itemToggle = [ GenericItem item, String logMessage |
    if (item.getType == "Group") {
        logInfo(item.name, "Trying to toggle a group.")
        item.members.forEach[ i | itemToggle.apply(i, logMessage) ]
    } else
    if (item instanceof SwitchItem) {
        if (item.state == OFF || item.state == NULL) {
            logInfo(item.name, "Switched on. (" + logMessage + ")")
            item.sendCommand(ON)
        } else if (item.state == ON) {
            logInfo(item.name, "Switched off. (" + logMessage + ")")
            item.sendCommand(OFF)
        }
    } else {
        logInfo(item.name, "The item is not a \"switchable\" item. Ignoring.")
    }
]

rule "Remote (002): Bedroom"
when
    Channel "mqtt:topic:broker:zigbee_tradfri_remote_02:action" triggered
then
    switch (receivedEvent.getEvent) {
        case "toggle": itemToggle.apply(LEDBedroom, "Trying to switch a group.")
    }
end

When saving the rule file I get:

There is no context to infer the closure’s argument types from. Consider typing the arguments or put the closures into a typed context.
The field Tmp_mqtt_zigbee_remoteRules.itemToggle refers to the missing type Object

Yet, openHAB let’s me save the file and accepts it (I think). When hitting the remote I get this:

‘apply’ is not a member of ‘Object’

I am out of ideas.

GroupItem is the type for a Group. GroupItem is of type GenericItem.

When using Rules DSL, sadly lambdas global lambdas are way more trouble than they are worth. They obfuscate errors, are not thread safe and usually end up causing more trouble than they are worth. There are several better techniques discussed in Design Pattern: DRY, How Not to Repeat Yourself in Rules DSL.

You need to add logging to figure out exactly what line is generating the error. If could be the filter, the forEach, or coming from inside the lambdas.

I think this defines a lambda that must be supplied with one parameter (an item) and returns a String.
It all gets a bit weird about how rules interpreter guesses what kind of lambda you are trying to define based on the “last line”, which in your case I think is }

You seem to want a lambda that accepts two params?

No, it’s two arguments. When using the shorter syntax like this the engine will guess at the return type based on what the last line of the lambda returns. In this case it returns void so it will use a Procedures$Procedure2. the difference between Procedures and Functions is Procedures don’t have a return.

It’s all very confusing and another reason I’ve grown to hate global lambdas.

… and another reason to love scripted automation :wink:!

Thank you for all your answers. @rlkoshak reading through a lot of your posts, I certainly understand now that you don’t like (global) Lambdas for a lot of reasons. I fully understand that - and why. Yet, this is a very simplistic approach to an otherwise more complicated solution (like using real scripting, which comes not as an integral part of openHAB (AFAIK), but with another hurdle (like installing & setting up additional bindings, jars and such). One might argue that openHAB itself is not the ideal system (yet) for “plug’n play lovers”, but it would be nice to get the additional scripting support right upon installing the system as is. Or maybe it is easier than I think, but since I am not that well informat (yet), using Lambdas sounds very nice & cozy at first glance.

Anyway: I will try to extend my Lamdas a bit. If that really doesn’t go anywhere, because of the issues you mentioned, I will try to look deeper into JSR223 - and maybe I should :slight_smile:

You mean something like:

which will become the default way to write Rules for OH 3?

This I think. It doesn’t take much at all to get it installed and to start using it. If you really want to use lambdas, I cannot recommend strongly enough that you would be better off dropping Rules DSL and using Python instead compared to using lambdas.

And for the Rules you have shown above, you don’t even need to use global lambdas to achieve in Rules DSL anyway. Review the DRY DP I posted above. Associated Items DP and Separation of Behaviors DP are both better approaches in Rules DSL for something like this.

There is PR in review that greatly simplifies the installation of Jython and the helper libraries. You can try it out here…

1 Like

Thank you for your work on this - this will help me surpass the hurdles of setting up Python / Jython! I will start testing ASAP. :slight_smile:

1 Like