Reusable Functions: A simple lambda example with copious notes

Sure you can.

But you cannot call a function in one xxx.rules file from rules in some other zzz.rules file.

Put all the rules that require some function in the same xxx.rules file as the function.

Wow… two years later! Nowadays, you just use Jython and put the function in a module that can be called from any rule in any file or even from a UI rule.

1 Like

I mean right this. From another rule files.
I would like to make a logging function, that sends logs to syslog, email and telegramm. Of course logs comes from different rules files.

Jyton. I have not heard about it. I asked google and it looks like another language and there is not much examples how to use it.
Maybe u know a good tutorial or thread here with explanation?

Thanks!

You can use the new rule engine and do scripted automation using Jython, which is Python for the JVM. The OH docs are a bit meager. It has some important information, but don’t follow those installation instructions. The raw automation API is extremely rough to use without living in the source code. Fortunately, there are helper libraries that make it very easy to use and are a game changer for OH automation. There is a lot of documentation to help and most design patterns now also have Jython examples. The installation isn’t too difficult, but I’m working on an addon to simplify things. Even @rlkoshak has migrated all of his rules from the rules DSL to Jython and @vzorglub is in process.

https://openhab-scripters.github.io/openhab-helper-libraries/index.html

Let me know if you have an questions or issues… but probably should be in a new topic :slightly_smiling_face:.

If you post your logging messages to Item states, these are common to all rules, whatever their source - xxx.rules files, Jython, etc.

This is demonstrated in Design Pattern: Separation of Behaviors

Do lambas go in .rules files?

I ask because the example is giving me validation warnings with OH2.5M5:

2019-11-26 08:15:26.434 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'light.rules' has errors, therefore ignoring it: [97,1]: missing EOF at 'val'

TIA

Yes, but they must go at the top of the file before all your Rules. This error is coming from line 97 which means your lambda is likely after the definition of a Rule or the error is coming from a Rule.

I should note that global lambdas like this are a code smell in Rules DSL. They can be handy in a very small set of circumstances, but they have sever limitations that make them dangerous to use. For example, they are not thread safe. If it gets called twice the variables from the first call will become overwritten by the second call. Also, you cannot call the lambda outside of the .rules file it is defined in. There is almost always a better way. For example: Design Pattern: Separation of Behaviors

@rlkoshak

I’m looking into lamdas and found your post. Is it worth rewriting to include notes for JRS223 Python/Jython?

Maybe there’s a good design pattern article. I’m still searching

Regards,
Burzin

Functions are available as a basic part of the Scripted Automation languages. If you take a week long course on Python, functions will be introduced somewhere on the morning of the first day. I didn’t include Python or JavaScript in the above DP because, for all purposes, the above DP doesn’t apply to Scripted Automation. Use what ever is available for the language chosen.

Using lambdas in this way is a work around for the fact that Rules DSL doesn’t support functions. There is no such limitation in any of the other languages.

2 Likes

@rlkoshak is it possible that lambdas are no longer supported in Openhab 3? I upgraded yesterday and since have been trying to get a rule to work again. When changing reloading the model it says:

2021-01-04 17:15:47.814 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'alert.rules', using it anyway:

The field Tmp_alertRules.checkAndSendAlert refers to the missing type Object

When triggering the rule it just says:

2021-01-04 17:15:47.770 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'alert-1' failed: null in alert

Check the section concerning Rules here

Sadly, I did not find anything regarding lambdas there.

No, lambdas have to be supported or else you couldn’t create a Timer nor run a forEach or the like. Those require a lambda as an argument.

I’ve seen no reports of lambdas not working on the forum except this one.

No. But you might find a reason why your rule no longer works. Please post the rule.

Here is the rule in question:

val String fileName = "alert.rules"

val telegramBot = getActions("telegram", "telegram:bot:myOpenhabianBot")

val checkAndSendAlert = [ NumberItem temperature, NumberItem minimum, NumberItem maximum, SwitchItem normal |

    logInfo(fileName, "Checking temperature")

    var sensor = transform("MAP", "alert.map", temperature.name)

    logInfo(fileName, sensor)

    if ((temperature.state as Number) < (minimum.state as Number))

    {

        logInfo(fileName, "Teperature is below minimum")

        if (normal.state == ON)

        {

            sendCommand(normal, OFF)

            telegramBot.sendTelegram("⚡ %s temperature at %.1f°C\nSensor is *below* minimum of %.1f°C",

                sensor,

                (temperature.state as DecimalType).floatValue(),

                (minimum.state as DecimalType).floatValue())

        }

    }

    else if ((temperature.state as Number) > (maximum.state as Number))

    {

        logInfo(fileName, "Teperature is above maximum")

        if (normal.state == ON)

        {

            sendCommand(normal, OFF)

            telegramBot.sendTelegram("⚡ %s temperature at %.1f°C\nSensor is *above* maximum of %.1f°C",

                sensor,

                (temperature.state as DecimalType).floatValue(),

                (maximum.state as DecimalType).floatValue())

        }

    }

    else if (normal.state == OFF)

    {

        logInfo(fileName, "Teperature is normal")

        sendCommand(normal, ON)

        telegramBot.sendTelegram("⚡ %s temperature at %.1f°C\nSensor is back to *normal*",

            sensor,

            (temperature.state as DecimalType).floatValue())

    }

]

rule "Send alert when VrRoom temperature goes out of limits"

when

    Item VrRoom_Temperature changed or

    Item VrRoom_Temperature_Min changed or

    Item VrRoom_Temperature_Max changed

then

    logInfo(fileName, "Checking")

    checkAndSendAlert.apply(VrRoom_Temperature, VrRoom_Temperature_Min, VrRoom_Temperature_Max, VrRoom_Temperature_Normal)

end

A couple things I notice.

  1. There is no reason to put this into a lambda unless you have multiple rules that call it that you’ve not posted.

  2. This lambda should have never worked in OH 2.5 either. checkAndSendAlert is a global lambda. Global lambdas have no context. Consequently it cannot see other global variables unless you pass them to it as an argument. You are attempting to use fileName and telegramBot inside the lambda without passing them as arguments.

  3. I’ve seen many many reports where trying to get the action as store it in a global variable does not work. You should move the call to getActions into where ever the action is called.

  1. You are correct in that I did not post all rules that use the function. There are many and I would love to parameterize it even further to send alerts for humidity as well as temperature.
  2. This is not 100% the version that was running on OH 2.5 since I’ve been trying to get it to work for some time now. Good to know though that this is not allowed. Is there a page with detailed documentation about scripting rules?
  3. That’s something I will try.

See Design Pattern: Associated Items

Rules | openHAB. Be sure to follow the links as well.

In general, a global lambda like this is a real code smell. There are a few cases where it can make sense but this isn’t one of them I think. Lambdas are not thread safe so if you do have more than one rule call it at the same time, the second rule will overwrite the variables from the first one while the first one is still running. And most of the time there is a better way.