Examples for Groovy scripts

I have been PMed by someone to provide some samples and tips for writing groovy scripts, since there is so few documentation out there how to do it. I’ll post my answer here since it might also be interesting for others…

Hey there,
please have a look here, most of the things also apply, especially how to build the triggers etc. is a little bit cumbersome, but works:

Tips

  • always use an outermost try-catch-block to realize if there are errors.
  • Use logging, e. g. def logger = LoggerFactory.getLogger("name-of-the-rule") using import import org.slf4j.LoggerFactory
  • here is an explanation on how to reuse code blocks by extracting them to a separate script (and how to include it in other scripts: Groovy scripts: how to import another groovy file? - #8 by ndru )
  • there are two nasty things I stumbled upon:
    • there is a name clash of the automatically imported variable “action” which happens when you are inside a triggered rule… you can work around by creating a copy outside the rules (e. g. directly after the imports) like so: def actionsCopy = actions … and then using the actionCopy instead:
      def mailActions = actionsCopy.get("mail","mail:smtp:mailSender")
      mailActions.sendHtmlMail("...emailadress", "...subject...","...content...")
      
    • some of the core classes have a kind of class loading issue (inside the rules) and you have to call the constructor outside of the rules in the beginning of the script (e. g. after the imports) like so:
    // This is a workaround for ClassNotFoundExceptions of the core classes. The need to be instantiated outside the rule
    // scope to be visible inside the rules for some reason
    new HTTP()
    new Transformation()
    new GroupItem("")
    new DecimalType()
    
  • see this post for details on the RuleHelper class for easy trigger creation

Samples

Rotate a RGB light through different colors on keypress:

def ruleSchlaZiBettTaster2 = new SimpleRule() {
    @Override
    Object execute(Action module, Map<String, ?> inputs) {
        logger.info("Taster Bett2 betätigt")
        if(items.get("HueGo_Toggle") == ON) {
            logger.info("current state: " + items.get("HueGo_Color"))
            HSBType hsb = items.get("HueGo_Color")
            double hue = hsb.hue.doubleValue() + 50d
            if(hue >= 360) {
                hue = 0d
            }
            def color = new HSBType(new DecimalType(hue), hsb.saturation, hsb.brightness)
            logger.info("sending HueGo color command: " + color)
            events.sendCommand(itemRegistry.get("HueGo_Color"), color)
        } else {
            logger.info( 'Not switching Hue Go since its state is not ON: ' + items.get("HueGo_Toggle"))
        }
    }
}
ruleSchlaZiBettTaster2.name = "SchlaZiBettTaster2"
RuleHelper.itemAnyCommandTrigger(ruleSchlaZiBettTaster2, "SchlaZiBettTaster2", "Taster_SchlaZi_Bett2")
automationManager.addRule(ruleSchlaZiBettTaster2)

Automatically switch off a light after a given time (the kids always leave the light on :wink: ):

def ruleWCLicht1AutoAus = new SimpleRule() {
    @Override
    Object execute(Action module, Map<String, ?> inputs) {
        if (items.get("Licht_WC_1") == OnOffType.ON) {
            timer_wc_licht_1 = new Timer();
            timer_wc_licht_1.schedule(new TimerTask() {
                @Override
                void run() {
                    events.sendCommand(itemRegistry.get("Licht_WC_1"), OnOffType.OFF)
                }
            }, 20 * 60 * 1000)
        } else {
            if (timer_wc_licht_1 != null) {
                timer_wc_licht_1.cancel()
                timer_wc_licht_1 = null
            }
        }
    }
}
ruleWCLicht1AutoAus.name = "WCLicht1AutoAus"
RuleHelper.itemChangedTrigger(ruleWCLicht1AutoAus, "WCLicht1AutoAus", "Licht_WC_1")
automationManager.addRule(ruleWCLicht1AutoAus)

This is how you run stuff on OH start (when the script loads):

void scriptLoaded(Object notUsed) {
    try {
        if(items["BHKW_ServiceMailSent"] == NULL || items["BHKW_ServiceMailSent"] == UNDEF) {
            events.sendCommand("BHKW_ServiceMailSent", "OFF")
        }
    } catch(Throwable t) {
        logger.error("ERROR in BHKW startup rule: ${t.cause.toString()} - $t.message")
    }
}

Retrieving and using JSON from a remote webservice:

String json = HTTP.sendHttpGetRequest("http://....URL....")
logger.debug(json)
if (json != null) {
    events.postUpdate("BHKW_ThLeistung", Transformation.transform("JSONPATH", "\$.thLeistung", json))
...

EDIT: Bonus - generic rule to process ANY event for a given topic & source

    def ruleSpotifyEvent = new SimpleRule() {
        @Override
        Object execute(Action module, Map<String, ?> inputs) {
            try {
                logger.debug("Received: " + inputs.get("event").toString() + " - type: " + ((ItemEvent)inputs.get("event")).type)
                def itemName = ((ItemEvent)inputs.get("event")).getItemName()
                def ohEventType = ((ItemEvent)inputs.get("event")).type

                if(ohEventType == "ItemStateEvent") {
                    ItemStateEvent ohEvent = (ItemStateEvent)inputs.get("event")

                // ohEvent.itemState holds the new state .... 
                    ... do fancy stuff here ...

             }
        }

        ruleSpotifyEvent.name = "genericEvent"
        ruleSpotifyEvent.setTriggers([TriggerBuilder.create()
                                          .withLabel("genericEvent")
                                          .withId("genericEvent")
                                          .withTypeUID("core.GenericEventTrigger")
                                          .withConfiguration(
                                               new Configuration([
                                                   eventTopic: "openhab/items/*", 
                                                   eventSource: "spotify", 
                                                   eventTypes: "ItemStateEvent,ItemCommandEvent"]))
                                          .build()]
    )
    automationManager.addRule(ruleSpotifyEvent)

4 Likes

And one addition on the development environment. I don’t use the OH UI, since I only use file based configuration for everything so I can just copy everything over to a new Raspberry in case something breaks and also so I can use git for having the history of all my changes.

  • I use IntelliJ for development
  • I created a new Groovy + Gradle project
  • I imported/synced the /etc/openhab/ directory of my raspbian installation via the Intellij Remote Host feature into this project
  • I added some openhab core dependencies to the maven project, so I can have code completion for those classes

sample gradle file:

apply plugin: 'java'
apply plugin: 'maven'

group = 'groupId'
version = '1.0-SNAPSHOT'

description = """"""

sourceCompatibility = 8
targetCompatibility = 8


repositories {

    maven { url "https://openhab.jfrog.io/openhab/libs-release-local"}
    maven { url "https://repo.maven.apache.org/maven2" }

}
dependencies {
    compile group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.core', version:'3.18.0'
    compile group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version:'2.2.100'
    compile group: 'org.openhab.core.bundles', name: 'org.openhab.core', version:'3.1.0'
    compile group: 'org.openhab.core.bundles', name: 'org.openhab.core.config.core', version:'3.1.0'
    compile group: 'org.openhab.core.bundles', name: 'org.openhab.core.automation', version:'3.1.0'
    compile group: 'org.openhab.core.bundles', name: 'org.openhab.core.automation.module.script', version:'3.1.0'
    compile group: 'org.openhab.core.bundles', name: 'org.openhab.core.automation.module.script.rulesupport', version:'3.1.0'
    compile group: 'org.openhab.core.bundles', name: 'org.openhab.core.model.script', version:'3.1.0'
    compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.21'
}

… just a quick hack, the OH version can be adapted now to 3.2 and I think the source level is also higher now…

3 Likes

Just to be clear, everything you do in the UI is also saved as text and can be just copied over and checked into git. It’s just from a different folder.

I wasn’t sure about the copy-over part. And this “imported” stuff will also be editable via UI in the other OH instance?

Of course. Think about the Docker case. Every time you run a new container it’s like copying the config over to a new machine.

You’d want to skip a couple of the folders to ensure a clean startup in the new location, namely cache and tmp. If you don’t care about persistence skip that folder too (you probably don’t want to check that folder into git either).

The three most important folders are jsondb, etc, and config. The jsondb folder has all the Items, Things, Rules, Links, Item metadata, and custom widgets. Order is preserved so it won’t muck up your git history when you add or remove something. And on every change a backup is automatically created which is nice.

I’m not saying you should switch to the UI. I just feel compelled when someone says they don’t use the UI for reasons that the UI configs will support too.

For the most part, what you really want to use to move a config from one machine to another is openhab-cli backup and restore. That will grab everything that is needed from all the relevant folders.

1 Like

Hi,

i’ve made groovy-script to control the effects of the Lidl-Lichterkette via Zigbee2MQTT

It debounces Name and EffectSpeed-Changes and sends the JSON-message-payload for effect/speed/colors to the specific zigbee2mqtt-channel. (thing-definition is described here)

import com.fasterxml.jackson.databind.ObjectMapper
import org.openhab.core.automation.*
import org.openhab.core.automation.util.*
import org.openhab.core.automation.module.script.rulesupport.shared.simple.*
import org.openhab.core.config.core.Configuration
import org.openhab.core.items.Item
import org.openhab.core.library.items.ColorItem
import org.openhab.core.library.types.DecimalType
import org.openhab.core.library.types.HSBType
import org.slf4j.LoggerFactory

import static java.time.ZonedDateTime.now
import static org.openhab.core.model.script.actions.ScriptExecution.createTimer

def logger = LoggerFactory.getLogger("org.openhab.core.rule.lichterkette")
scriptExtension.importPreset("RuleSupport")
scriptExtension.importPreset("RuleSimple")

ObjectMapper objectMapper = new ObjectMapper();

def effectTimer = null
def effectSetTimer = null

def runEffectChange = {
    Item effectName = ir.get("Lichterkette_Effect_Name")
    Item effectSpeed = ir.get("Lichterkette_Effect_Speed")
    Item effectCmdItem = ir.get("Lichterkette_Effect_Cmd")
    def effectCmd = [:]
    effectCmd['effect'] = effectName.state.toString()
    effectCmd['speed']  = ((DecimalType) effectSpeed.state).intValue()
    def colors = []
    for( def itemName in ["Lichterkette_Color","Lichterkette_Color2","Lichterkette_Color3"] ) {
        ColorItem colorItem = ir.get(itemName)
        HSBType hsbType = colorItem.state;
        colors << [
                r : (hsbType.red.floatValue() * 2.55) as int,
                g : (hsbType.green.floatValue() * 2.55) as int,
                b : (hsbType.blue.floatValue() * 2.55) as int
        ]
    }
    if(!colors.empty) {
        effectCmd['colors'] = colors
    }
    def effectCmdJson = objectMapper.writeValueAsString(effectCmd)
    logger.info("cmd {}", effectCmdJson)
    events.sendCommand(effectCmdItem, effectCmdJson)
    effectTimer = createTimer(now().plusSeconds(5), {  effectTimer = null })
}

def sRule = new SimpleRule() {
    Object execute(Action module, Map<String, ?> inputs) {
        if ( effectTimer !== null ) {
            return
        }
        if(effectSetTimer!== null) {
            effectSetTimer.cancel()
        }
        effectSetTimer = createTimer(now().plusSeconds(1), runEffectChange)
    }
}
sRule.setTriggers([
    TriggerBuilder.create().withId('Lichterkette_Effect_1').withTypeUID("core.ItemStateChangeTrigger").withConfiguration(new Configuration(
            [itemName: 'Lichterkette_Effect_Name'])).build(),
    TriggerBuilder.create().withId('Lichterkette_Effect_2').withTypeUID("core.ItemStateChangeTrigger").withConfiguration(new Configuration(
            [itemName: 'Lichterkette_Effect_Speed'])).build()
                
    ])
sRule.name = "Lichterkette groovy"
automationManager.addRule(sRule)

BTW: is it possible to import helper-‘scripts’ in some way?
Is there a magic class loader-logic for certain groovy-paths?

e.g. automation/jsr223/lib/my/package/Util.groovy
which can be imported in my scripts via import my.package.Util

The GroovyClassLoader can do this magic.

1 Like

See Groovy scripts: how to import another groovy file? - #11 by boecko for a possible solution for that import ..-Problem

Hi pravussum,

Where does this come from. I have looked at the OpenHAB javadocs but can’t find that class. This sure looks easier than using the TriggerBuilder.

Thanks,
Joe

You’re right. I realized just today that I didn’t share any details on that one. It’s a small helper class to hide the boilerplate details of the trigger creation. I included it using the mechanism already mentioned & linked above.

import org.openhab.core.automation.Trigger
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule
import org.openhab.core.automation.util.TriggerBuilder
import org.openhab.core.config.core.Configuration
import org.slf4j.LoggerFactory

class RuleHelper {

    static LOGGER = LoggerFactory.getLogger("org.mortalsilence.net.openhab.rules.rulehelper")

    static void cronTrigger(SimpleRule rule, String id, String cronExpression) {
        rule.setTriggers([createCronTrigger(id, cronExpression)])
    }

    static Trigger createCronTrigger(String triggerId, String cronExpression) {
        TriggerBuilder.create()
                .withLabel(triggerId)
                .withId(triggerId)
                .withTypeUID("timer.GenericCronTrigger")
                .withConfiguration(new Configuration(["cronExpression": cronExpression]))
                .build()
    }


    static void itemChangedTrigger(SimpleRule rule, String id, String itemName) {
        rule.setTriggers([createItemStateChangeTrigger(id, itemName)])
    }

    static Trigger createItemStateChangeTrigger(String id, String itemName) {
        TriggerBuilder.create()
                .withLabel(id)
                .withId(id)
                .withTypeUID("core.ItemStateChangeTrigger")
                .withConfiguration(new Configuration([itemName: itemName]))
                .build()
    }

    static void itemAnyCommandTrigger(SimpleRule rule, String triggerId, String itemName) {
        rule.setTriggers([createAnyCommandTrigger(triggerId, itemName)])
    }

    static Trigger createAnyCommandTrigger(String triggerId, String itemName) {
        TriggerBuilder.create()
                .withLabel(triggerId)
                .withId(triggerId)
                .withTypeUID("core.ItemCommandTrigger")
                .withConfiguration(new Configuration([itemName: itemName]))
                .build()
    }

    static void channelTrigger(SimpleRule rule, String id, String channel, String value) {
        rule.setTriggers([
                TriggerBuilder.create()
                        .withLabel(id)
                        .withId(id)
                        .withTypeUID("core.ChannelEventTrigger")
                        .withConfiguration(new Configuration([channelUID: channel, "event": value]))
                        .build()
        ])
    }

    static void itemCommandTrigger(SimpleRule rule, String id, String itemName, String command) {
        rule.setTriggers([
                TriggerBuilder.create()
                        .withLabel(id)
                        .withId(id)
                        .withTypeUID("core.ItemCommandTrigger")
                        .withConfiguration(new Configuration([itemName: itemName, "command": command]))
                        .build()
        ])
    }

    static void itemUpdatedToTrigger(SimpleRule rule, String id, String itemName, String state) {
        rule.setTriggers([
                TriggerBuilder.create()
                        .withLabel(id)
                        .withId(id)
                        .withTypeUID("core.ItemStateUpdateTrigger")
                        .withConfiguration(new Configuration([itemName: itemName, "state": state]))
                        .build()
        ])
    }

    static void itemChangedToTrigger(SimpleRule rule, String triggerId, String itemName, String newState) {
        rule.setTriggers([createItemChangedToTrigger(triggerId, itemName, newState)])
    }

    static Trigger createItemChangedToTrigger(String triggerId, String itemName, String newState) {
        TriggerBuilder.create()
                .withLabel(triggerId)
                .withId(triggerId)
                .withTypeUID("core.ItemStateChangeTrigger")
                .withConfiguration(new Configuration([itemName: itemName, state: newState]))
                .build()
    }

    static void itemUpdatedTrigger(SimpleRule rule, String triggerId, String itemName) {
        rule.setTriggers([createItemUpdatedTrigger(triggerId, itemName)])
    }

    static Trigger createItemUpdatedTrigger(String triggerId, String itemName) {
        TriggerBuilder.create()
                .withLabel(triggerId)
                .withId(triggerId)
                .withTypeUID("core.ItemStateUpdateTrigger")
                .withConfiguration(new Configuration([itemName: itemName]))
                .build()
    }

    static Trigger createItemChangedFromToTrigger(String triggerId, String itemName, String previousState, String newState) {
        TriggerBuilder.create()
                .withLabel(triggerId)
                .withId(triggerId)
                .withTypeUID("core.ItemStateChangeTrigger")
                .withConfiguration(new Configuration([itemName: itemName, previousState: previousState, state: newState]))
                .build()
    }

    static void commandTrigger(SimpleRule rule, String triggerId, String itemName, String command) {
        rule.setTriggers([createCommandTrigger(triggerId, itemName, command)])
    }

    static Trigger createCommandTrigger(String triggerId, String itemName, String command) {
        TriggerBuilder.create()
                .withLabel(triggerId)
                .withId(triggerId)
                .withTypeUID("core.ItemCommandTrigger")
                .withConfiguration(new Configuration([itemName: itemName, "command": command]))
                .build()
    }

}

1 Like

Thank you very much!

I also added another interesting piece of code which I forgot until now (last sample in the first post now):
With this it’s possible to subscribe to any event on the OH event bus (you can specify a topic filter, source filter and event type filter) which makes it VERY flexible.