I’m afraid we have to wait until OH 2’s new rules engine to support reusable generic rules and rule fragments. You can do it now for all the rules in a single .rules file but you can’t share them across .rules files. This is one reason why I organize my rules by function instead of other organization schemed (e.g. organized by room).
Agreed and so far, for my setup at least, I’ve managed to apply DRY quite successfully. I can’t think of a single case where I’ve had to repeat more than two lines of code. So it can be done.
val Functions$Function3 setBrightnessSmooth = [DimmerItem dimmer, Number desiredBrightness, Number speed |
...do logic to gradually change dimmer brightness
]
// To call from within your rule:
setBrightnessSmooth.apply(dimmer, desiredBrightness, desiredSpeed)
There are caveats. Only rules in the file where this is defined can see and call it. Also, the lambda does not have any context so you have pass to it anything defined in your rule or globally as a command line argument. Finally it only goes up to Functions$Function7 I think, but if you find you need more arguments than that it is a code smell and there is probably a better way.
If you organize your rules such that all your Dimmers are in the same file everything that needs to see that lambda can.
This one is harder as lambdas don’t return a value. But for things like that I create a Night switch which gets switched from ON to OFF based on Astro or Time events. See this post for details.
This is partially what I mean by “Where ever and as much as possible put state and data structures into Items, not global hashMaps or arrayLists etc”. In this case I created a time of day state and some simple rules to set the state. In my other rules that care about time of day I simple check to see if I’m in a time period I care about by checking the Item.
So rather than creating a little function that returns true or false when it is night, I just
rule "Dispatch Notification"
when
Item Notification_Proxy received update
then
logWarn("Notification", Notification_Proxy.state.toString)
if(Night.state == OFF) sendNotification("rlkoshak@email.com", Notification_Proxy.state.toString)
else sendPushToDefaultDevice(Notification_Proxy.state.toString)
end
One of the maintainers on this forum (watou) prefers to use a single String Item that is set to “Day”, “Night”, etc instead of using separate switches, but the concept is the same.
In other cases where I need to create a boolean out of whether a bunch of Items have a certain state (e.g. if I want to see that all my Presence switches are OFF) I’ll use a Group and filter command.
val isPresent = gPresent.members.filter[i|i.state == ON].count > 0 // isPresent is true if one or members of gPresent are ON
It’s a one liner so I don’t consider it to be a violation of DRY if I reuse it elsewhere. Though the need to reuse it elsewhere rarely happens because I usually create rules to maintain these sorts of flags as Switch Items instead of recalculating the state all over my rules. Seems like a good application of DRY to me.
Search this forum for “lambda” and “Functions$” and you will find all the examples that have been posted to this forum by me and others. Search the wiki for “Functions$” for some more examples. However, if you find yourself writing lots of little functions that also is a bit of a code smell. You shouldn’t write little one or two liners in a lambda. You should think bigger. Write the entire logic for how you handle all the logic around your dimmers into a lambda and have one rule that figures out which dimmer you need to control and calls that lambda.
Here are the lambdas I’m using, though to be honest they are pretty unique to my rule set because they basically centralize the logic for all Items of a given type (e.g. both of my garage doors use the same lambda for opening/closing) and are basically the rule body of multiple rules. And frankly a couple of them should probably be eliminated and the multiple rules that call them be merged into one rule.
// I have two garage doors so I merged the logic that triggers them into one lambda
// This lambda can probably be simplified, I don't think I really need it anymore and instead of having two rules that
// call the same lambda I can merge everything into one rule
val Functions$Function1 openGarage = [ String garageNum |
logInfo("Garage Controller", "The garage door " + garageNum + " has been triggered")
var url = "http://192.168.1.201:8000/GPIO/"
switch garageNum {
case "1" : url = url + "17"
case "2" : url = url + "22"
default : url = null
}
if (url == null) {
Notification_Proxy.postUpdate("Received cmd to trigger unknown garage door: " + garageNum)
}
else {
url = url + "/sequence/500,01"
logDebug("Garage Controller", "The URL is " + url)
logInfo("Garage Controller", "HTTP Post result " + sendHttpPostRequest(url).toString)
if (Present.state != ON) {
Notification_Proxy.postUpdate("Garage door " + garageNum + " has been triggered.")
}
if (gGarageNet.state != ON) {
Notification_Proxy.postUpdate("The garage controller may be offline, trigger may have failed!")
}
}
true
]
// If you look at other postings on that link I provided above you will see my lighting rules which includes a few
// lambdas. The logic is a bit subtle if you don't have your head wrapped around the Rules DSL yet so ask questions
// Here is a lambda that I use to detect and reset one of my Raspberry Pis when it falls offline
// Note: This lambda can be simplified I think, there is surely a better way for me to track which ones I need to
// send a notification on
val Functions$Function5 procChange = [ GroupItem gr, String name, Map<String, Timer> timers, SwitchItem reset, Map<String, Boolean> notified|
logInfo("Network", "Detected the " + name + " controller state changed to " + if(gr.state==ON) "ON" else "OFF")
// New state is OFF
if(gr.state == OFF) {
if(timers.get(gr.name) == null || timers.get(gr.name).hasTerminated){
logDebug("Network", "Creating controller offline timer for " + name)
timers.put(gr.name, createTimer(now.plusMinutes(1), [|
// Still off
if(gr.state == OFF) {
logInfo("Network", name + " is still offline after one minute")
Notification_Proxy.postUpdate("The " + name + " controller is still offline after one minute, resetting")
notified.put(gr.name, true)
// reset the power
try {
reset.sendCommand(OFF)
Thread::sleep(5000)
} catch(InterruptedException e) {
logWarn("Network", "Interrupted while sleeping to give switch time to power off")
} finally {
reset.sendCommand(ON)
}
// if it is the garage, wait five minutes then reset mqttReporter
if(name == "Garage") {
logWarn("Network", "Resetting mqttReporter in five minutes")
createTimer(now.plusMinutes(5), [|
logWarn("Network", "Resetting mqttReporter on Garage")
S_N_GarageMqttReporter.sendCommand(ON)
])
}
}
// came back while timer was asleep
else {
logInfo("Network", name + " came back online before timer expired")
}
timers.put(gr.name, null)
]))
} else {
logInfo("Network", name + " was detected offline again but a timer is already set")
}
}
// New state is ON
else {
if(timers.get(gr.name) != null) {
logInfo("Network", name + " is now ON, canceling timer")
timers.get(gr.name).cancel
timers.put(gr.name, null)
}
// If we sent a notification that it went offline, send one so we know it is back
if(notified.get(gr.name)) {
Notification_Proxy.postUpdate(name + " is back online")
notified.put(gr.name, false)
}
else {
logInfo("Network", name + " is back online and there was no timer and no notification was sent: " + notified.get(gr.name))
}
}
]
// As you saw above in the link to my time of Day posting, I also use a lambda to determine what time of day it is and switch
// the right switches ON and OFF accordingly. It mainly gets called
That is pretty much it. I used to have soooo many more lambdas and lambdas that called lambdas and my rules ran to the thousands of lines. But by applying some Rules DSL best practices which I’ve already mentioned my entire home automation runs on 667 lines spread across seven files, none of which exceeds 200 lines. And as my comments above imply, I could probably trim a good number of those lines out if I just applied my own rules of thumb more rigorously and cut out some of the extra comments and white space I don’t really need anymore.
If you have a specific set of Items and Rules you want to do something and want to see how I would implement it in Xtend feel free to post them. I’ve helped others before in this way, and working on problems like this keep my Rules DSL coding skills sharp.