I want to make it easy to write rules in Groovy by creating a small set of base classes that add functionality found in the standard rules engine.
The base class contains methods for logging (logError, logWarn, logInfo, and logDebug), for interacting with bus (sendCommand and postUpdate), for interacting with the item registry (getItem, getItems), getting actions (getAction). There are some other convenience functions for things like creating triggers for different events.
In addition to the base classes, I have a small set of classes covering some of the common use cases for rules. For example, “do something when a contact opens”, “do something if a reading is outside a certain range”, “do something if a door has been open too long”, “do something when a door opens”, “do something when an item changes state”, etc. Generally, these classes have one or two methods with default implementations (logging only) that can be overridden to implement what the user wants to do. The event handler also traps and logs any exceptions thrown by those methods.
I implemented this for my own use in OpenHab1 and found it to work well.
I’m new to OSGi and the SmartHome framework, so this has been a lot of trial-and-error to get things working. I tried a lot of different things to get my external Groovy classes loaded and usable in JSR223 groovy scripts, and repeatedly failed. So for now I’ve added them directly to the RuleSupport bundle in the smarthome codebase. I’m not sure what the best or optimal approach should be, but at least this has allowed me to make progress on implementing what I want. I had to make some adjustments to my Java code such as passing and returning Object instead of types to allow the scripts to be a little more Groovy-like.
Anyway, please share your thoughts on this approach and how to proceed. Assuming the response is positive, my next step is to open an Issue in the appropriate repository and begin working on getting my code to a state that can be merged.
Thanks,
Doug
Some simple demonstration examples:
“do something when a switch is thrown”
“do something when a contact/door opens”
“do something if a door has been open too long”
This example uses a switch to simulate a door opening and closing. A text field is updated when the door opens and closes, and also if the door is left open longer then 5 minutes.
import org.eclipse.smarthome.automation.module.script.rulesupport.shared.simple.helper.*
scriptExtension.importPreset('RuleSupport')
def r1 = new OnOffRule('TestSwitch2') {
def on(event) { postUpdate('TestContact1', 'OPEN'); }
def off(event) { postUpdate('TestContact1', 'CLOSED'); }
}
def r2 = new OpenClosedRule('TestContact1') {
def open(event) {
logWarn('Door open');
postUpdate('TestString2', 'Door open')
}
def closed(event) {
logInfo('Door closed');
postUpdate('TestString2', 'Door close')
}
}
def r3 = new OpenTooLongRule('TestContact1', 'Back door', 5) {
def alert(itemName) {
logWarn('Door open too long!!!');
postUpdate('TestString2', getFriendlyName(itemName) + ' open longer than ' + minutes + ' minutes!')
}
def closedAfterAlert(itemName) {
infoMsg(getFriendlyName(itemName) + ' has been closed.')
}
}
automationManager.addRule(r1)
automationManager.addRule(r2)
automationManager.addRule(r3)
“do something if a reading is outside a certain range”
This example updates a text field based on a value in a range. It uses a more Java-like approach by defining a new class instead of using an anonymous one. This is more lines of code, but has the advantage that log messages with have a category of “UserRules.RangeDemo” instead of just “UserRules”.
import org.eclipse.smarthome.automation.module.script.rulesupport.shared.simple.helper.*
scriptExtension.importPreset('RuleSupport')
class RangeDemo extends RangeRule {
public RangeDemo() {
super('TestSlider', 'Temp', 30, 80);
}
def low(event) {
def msg = getFriendlyName(event) + ' low: ' + event.getItemState() + ' degrees'
logWarn(msg)
postUpdate('TestString3', msg)
}
def high(event) {
def msg = getFriendlyName(event) + ' high: ' + event.getItemState() + ' degrees'
logWarn(msg)
postUpdate('TestString3', msg)
}
def backInRange(event) {
def msg = getFriendlyName(event) + ' has returned to normal.'
postUpdate('TestString3', msg)
logInfo(msg)
}
}
def r = new RangeDemo()
automationManager.addRule(r)
“do something when a switch is thrown”
“get and use an openhab action”
This uses the pushover to send a message when a switch turns from OFF to ON
import org.eclipse.smarthome.automation.module.script.rulesupport.shared.simple.helper.*
scriptExtension.importPreset('RuleSupport')
def r = new OnOffRule('TestSwitch5') {
def on(event) {
getAction('pushover').pushover('TestSwitch5 ON')
}
}
automationManager.addRule(r)
“turn on the outside lights at sunset and off at sunrise”
import org.eclipse.smarthome.automation.module.script.rulesupport.shared.simple.helper.*
scriptExtension.importPreset('RuleSupport')
def r1 = new ChannelEventRule('astro:sun:local:set#event', 'START') {
def triggered(event) {
sendCommand('OutsideLight', 'ON')
}
}
def r2 = new ChannelEventRule('astro:sun:local:rise#event', 'START') {
def triggered(event) {
sendCommand('OutsideLight', 'OFF')
}
}
automationManager.addRule(r1)
automationManager.addRule(r2)
“turn the hall light on at 7:30 PM and off at 11:00 PM”
import org.eclipse.smarthome.automation.module.script.rulesupport.shared.simple.helper.*
scriptExtension.importPreset('RuleSupport')
def r1 = new TimerRule('0 30 19 * * ?') {
def timeFor() { sendCommand('HallLight', 'ON') }
}
def r2 = new TimerRule('0 0 23 * * ?') {
def timeFor() { sendCommand('HallLight', 'OFF') }
}
automationManager.addRule(r1)
automationManager.addRule(r2)
“turn a switch on when the daylight event starts and off when it ends”
import org.eclipse.smarthome.automation.module.script.rulesupport.shared.simple.helper.*
scriptExtension.importPreset("RuleSupport")
def r1 = new RangeEventSwitchRule("astro:sun:local:daylight#event", "Day_Event");
automationManager.addRule(r1)