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!
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 .
If you post your logging messages to Item states, these are common to all rules, whatever their source - xxx.rules files, Jython, etc.
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
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.
@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
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.
There is no reason to put this into a lambda unless you have multiple rules that call it that you’ve not posted.
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.
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.
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.
Comment only - I think that’s mostly about startup timing, as the xxx.rules file is loaded an attempt is made to get the Action before the binding has prepared it. A circumvention is to get the Action later, when you actually run a rule. I don’t think there’s any problem with the principle of getting a “global” Action, just the startup timing.
Yes, that’s my understanding too. I just didn’t want to go down that rabbit hole in my reply.
I managed to get it to work. By using group members as triggers I was able to cut down the amount of rules. But I still have two separate rules that use the lambda and I don’t think I can resolve that.
Generally I am a little stumped that there is no simple way to use functions in these rules. I code a lot in Java where I do a lot of modularization through functions. This doesn’t seem possible in the rules DSL. For that reason I shortly tried to get the new Python scripting to work but I did not find a way to do it file based and have it show up as a rule instead of a script.
Anyway. Here is the finished rule. Maybe you see something else I could do to enhance it:
import org.openhab.core.model.script.ScriptServiceUtil
val checkAndSendAlert = [ NumberItem value, NumberItem minimum, NumberItem maximum, SwitchItem normal |
if (normal.state == NULL)
{
sendCommand(normal, ON)
}
val sensor = transform("MAP", "alert.map", value.name)
if ((value.state as Number) < (minimum.state as Number))
{
logInfo("alerts", "Value ({}) is below minimum ({})", value.state, minimum.state)
if (normal.state == ON)
{
sendCommand(normal, OFF)
val message = if (value.category.equals("temperature")) "⚡ %s %s at %.1f°C\nSensor is *below* minimum of %.1f°C" else "⚡ %s %s at %.0f%%%%\nSensor is *below* minimum of %.0f%%%%"
val telegram = String::format(message, sensor, value.category, (value.state as DecimalType).floatValue, (minimum.state as DecimalType).floatValue)
getActions("telegram", "telegram:telegramBot:myopenhabianbot").sendTelegram(telegram)
}
}
else if ((value.state as Number) > (maximum.state as Number))
{
logInfo("alerts", "Value ({}) is above maximum ({})", value.state, maximum.state)
if (normal.state == ON)
{
sendCommand(normal, OFF)
val message = if (value.category.equals("temperature")) "⚡ %s %s at %.1f°C\nSensor is *above* maximum of %.1f°C" else "⚡ %s %s at %.0f%%%%\nSensor is *above* maximum of %.0f%%%%"
val telegram = String::format(message, sensor, value.category, (value.state as DecimalType).floatValue, (maximum.state as DecimalType).floatValue)
getActions("telegram", "telegram:telegramBot:myopenhabianbot").sendTelegram(telegram)
}
}
else if (normal.state == OFF)
{
logInfo("alerts", "Value ({}) is back to normal ({}-{})", value.state, minimum.state, maximum.state)
sendCommand(normal, ON)
val message = if (value.category.equals("temperature")) "⚡ %s %s at %.1f°C\nSensor is back to *normal*" else "⚡ %s %s at %.0f%%%%\nSensor is back to *normal*"
val telegram = String::format(message, sensor, value.category, (value.state as DecimalType).floatValue)
getActions("telegram", "telegram:telegramBot:myopenhabianbot").sendTelegram(telegram)
}
]
rule "A temperature or humidity sensor changed"
when
Member of Temperature changed or
Member of Humidity changed
then
if (previousState != NULL) {
val minimum = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name + "_Min") as NumberItem
val maximum = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name + "_Max") as NumberItem
val normal = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name + "_Normal") as SwitchItem
checkAndSendAlert.apply(triggeringItem, minimum, maximum, normal)
}
end
rule "A threshold changed"
when
Member of Threshold changed
then
val monitoredItemName = triggeringItem.name.substring(0, triggeringItem.name.lastIndexOf("_"))
val monitoredItem = ScriptServiceUtil.getItemRegistry.getItem(monitoredItemName) as GenericItem
val minimum = ScriptServiceUtil.getItemRegistry.getItem(monitoredItemName + "_Min") as NumberItem
val maximum = ScriptServiceUtil.getItemRegistry.getItem(monitoredItemName + "_Max") as NumberItem
val normal = ScriptServiceUtil.getItemRegistry.getItem(monitoredItemName + "_Normal") as SwitchItem
checkAndSendAlert.apply(monitoredItem, minimum, maximum, normal)
end