Hi all,
I did a lot of fiddling around with this lately and finally found a solution. It’s quite hacky, but it works. If anybody’s interested here’s what I did:
- I added an additional System property
groovy.path
to EXTRA_JAVA_OPTS
which points to a lib directory outside the normal jsr223 directory, like they do for Jython here https://openhab-scripters.github.io/openhab-helper-libraries/Getting%20Started/Installation.html
E.g., EXTRA_JAVA_OPTS="-Dgroovy.path=${OPENHAB_CONF}/automation/lib/groovy"
- I put my utility classes to
${OPENHAB_CONF}/automation/lib/groovy
. Note that utility class must compile without errors when put to the normal jsr223 directory. (Not sure what happens when you leave the utility class in the default jsr223 directory…)
My Utility class currently looks like this:
RuleBase.groovy
import org.slf4j.LoggerFactory
import org.slf4j.Logger
import org.openhab.core.automation.*
import org.openhab.core.automation.util.*
import org.openhab.core.automation.module.script.rulesupport.shared.simple.*
import org.eclipse.smarthome.config.core.Configuration
import org.eclipse.smarthome.core.items.Item
import org.eclipse.smarthome.core.items.ItemRegistry
import org.eclipse.smarthome.core.items.events.ItemStateChangedEvent
import org.eclipse.smarthome.core.items.events.ItemStateEvent
import org.eclipse.smarthome.core.items.events.GroupItemStateChangedEvent
import org.eclipse.smarthome.core.types.State
import org.eclipse.smarthome.core.events.Event
scriptExtension.importPreset("RuleSupport")
scriptExtension.importPreset("RuleSimple")
// Ugly workaround for openhab issues, see https://community.openhab.org/t/groovy-rules-no-longer-work-after-update-to-2-5-1535/68767/12
Class.forName("org.eclipse.smarthome.core.items.events.ItemStateEvent");
Class.forName("org.eclipse.smarthome.core.items.events.ItemStateChangedEvent");
Class.forName("org.eclipse.smarthome.core.items.events.GroupItemStateChangedEvent");
Class.forName("org.eclipse.smarthome.core.events.Event");
public abstract class AbstractSimpleRule extends SimpleRule {
protected Logger logger = LoggerFactory.getLogger("jsr223.groovy.base."+this.getClass().getName());
protected ItemRegistry itemRegistry;
protected Item item;
public AbstractSimpleRule(ItemRegistry itemRegistry, String itemName)
{
setName(itemName);
this.itemRegistry = itemRegistry;
this.item = itemRegistry.getItem(itemName);
logger.debug("Initializing " + this.getClass().getSimpleName() + " for "
+ this.item.getName());
}
public void addOnChangeTrigger(Item item)
{
addTrigger(
TriggerBuilder.create()
.withId(this.uid + "-" + item.getName() + "-change")
.withTypeUID("core.ItemStateChangeTrigger")
.withConfiguration(new Configuration([itemName: item.getName()]))
.build()
);
}
public void addOnUpdateTrigger(Item item)
{
addTrigger(
TriggerBuilder.create()
.withId(this.uid + "-" + item.getName() + "-update")
.withTypeUID("core.ItemStateUpdateTrigger")
.withConfiguration(new Configuration([itemName: item.getName()]))
.build()
);
}
public void addTrigger(Trigger trigger)
{
List<Trigger> triggers = new ArrayList<Trigger>(getTriggers());
triggers.add(trigger);
setTriggers(triggers);
}
public Object execute(Action module, Map<String, ?> inputs) {
def event = inputs.get("event");
onEvent(event);
}
public void onEvent(Event event)
{
if (event instanceof ItemStateEvent)
{
onUpdate(event.itemName, event.itemState);
}
else if (event instanceof GroupItemStateChangedEvent)
{
onGroupItemChange(event.itemName, event.memberName, event.oldItemState, event.itemState);
}
else if (event instanceof ItemStateChangedEvent)
{
onChange(event.itemName, event.oldItemState, event.itemState);
}
else
{
logger.warn("Unsupported Event Type:" + event.getClass().getName());
}
}
public void onUpdate(String itemName, State itemState)
{
// Empty Implementation
// logger.warn("onUpdate("+itemName+", "+itemState+")");
}
public void onGroupItemChange(String groupItem, String memberItem, State oldState, State newState)
{
onChange(memberItem, oldState, newState);
}
public void onChange(String itemName, State oldState, State newState)
{
// Empty Implementation
// logger.warn("onChange("+itemName+", "+oldState+", "+newState+")");
}
}
- In your actual Script create a new GroovyShell using the ClassLoader and Binding of the current instance:
GroovyShell shell = new GroovyShell(this.getClass().getClassLoader(), this.binding);
This makes things like itemRegistry, automationManager etc. known to the new shell.
Determine the groovy scripts path (just to make sure we have the right path)
File groovyLibPath = new File(System.getProperty("groovy.path"));
Load all your “library”-Scripts using shell.evaluate()
. E.g.
shell.evaluate(new File(groovyLibPath, "RuleBase.groovy"));
Finally, run your actuall script using
shell.run(""" < YOUR SCRIPT > """, "MyScriptName", [])
The three quotes mark a multi-line string. Hence you need to be a bit carefull, that everything between these quotes is considered a string… Don’t use three quote multi-line strings inside this block!
Putting this all together it looks like this:
alarm.groovy
// Path is set using EXTRA_JAVA_OPTS="-Dgroovy.path=${OPENHAB_CONF}/automation/lib/groovy"
File groovyLibPath = new File(System.getProperty("groovy.path"));
// Create a new GroovyShell using the current ClassLoader and current Binding
GroovyShell shell = new GroovyShell(this.getClass().getClassLoader(), this.binding);
// Load any libraries from groovy lib path
shell.evaluate(new File(groovyLibPath, "RuleBase.groovy"));
// Run the actual script
shell.run("""
import org.slf4j.LoggerFactory
import org.slf4j.Logger
import org.eclipse.smarthome.core.items.ItemRegistry
import org.eclipse.smarthome.core.types.State
import AbstractSimpleRule;
Logger myLogger2 = LoggerFactory.getLogger("jsr223.groovy.base");
class AlarmRule extends AbstractSimpleRule {
public AlarmRule(ItemRegistry itemRegistry, String itemName)
{
super(itemRegistry, itemName);
addOnChangeTrigger(this.item);
addOnUpdateTrigger(this.item);
}
public void onUpdate(String itemName, State itemState)
{
logger.debug("onUpdate("+itemName+", "+itemState+")");
}
public void onChange(String itemName, State oldState, State newState)
{
logger.debug("onChange("+itemName+", "+oldState+", "+newState+")");
}
}
automationManager.addRule(new AlarmRule(itemRegistry, "gAlarm"));
""",
// Script name - we use the current script name. Not sure if this matters
this.getClass().getName(),
// Script parameters - none
[]);
There’s one known limitation to this approach: Changes to the utility class will not directly trigger a reload of the script and will not automatically be reflected. To have become active you need to trigger a reload of each script using them.
Best,
Andreas