Skip to content

weberjn/org.openhab.automation.javascripting

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

openHAB Java Scripting

This openHAB add-on provides support for JSR 223 scripts written in Java that can be used as rules or transformations.

It makes heavy use of Eric ObermĂĽhlner's Java JSR 223 ScriptEngine java-scriptengine.

Advantages of Programming Scripts in Java

  • high level language Java

  • use the power of the Java runtime library

  • develop in your favorite IDE

  • remote-debug scripts

  • scripts run in Java's speed, after JVM warm-up in native code speed

Programming Hints

  • all Java classes used as JSR 223 script have to inherit from org.openhab.automation.javascripting.scriptsupport.Script

  • When the openHAB ScriptFileWatcher detects a new .java File in conf/automation/jsr223 it is loaded, compiled into memory and its onLoad() method is executed. Then it is parsed for @Rule annotations and the rules are activated.

  • Java script classes do not see other script classes. Each one has its own ClassLoader. This is an consequence of the way openHAB JSR223 and the Java ScriptEngine work, each script is loaded separately and so has its own memory classloader.

  • you can use libraries if you package them as OSGI bundles.

  • you can use openHAB classes from the packages listed in bnd.bnd.

  • openHAB Java Scripting requires openHAB 3.3.0 or later

Remote Debugging

start openHAB with start_debug.sh and remote debug from Eclipse, stop at breakpoints.

screenshot

Test

  • Copy org.openhab.automation.javascripting-VERSION.jar into the addons folder (download via the Releases link).

  • Copy from the sample Java classes into conf/automation/jsr223/

(they are all in src/script/java)

A Java class is loaded, compiled into memory and its onLoad() method executed. A Python or JS Script is evalated during load, this is simulated with the onLoad() method. So, rules can be defined programmatically in onLoad().

Or, you can annotate public instance variables of type SimpleRule. See the FileWriteRule sample.

You can also directly annotate methods of the Script. See the CronRule sample.

Project for Scripts

To have a script compile without errors in Eclipse, it should be in a Java project with openHAB dependencies and a dependency to javascripting.

  • create a folder with a Maven project:
  • use this src/doc/pom.xml as template
  • adapt parent relativePath
  • change groupId and artifactId
  • import the folder as maven project into Eclipse
  • create the Java scripts in src/main/java in the default package
  • if the source compiles without errors, copy it to conf/automation/jsr223
mvn  -DskipChecks clean install

Library Code

Java Rules has DynamicImport-Package: * so it can access code in other bundles.

Bundle your code as OSGI bundle as in this sample: https://github.com/weberjn/org.openhab.automation.javascripting.ext

Building the Addon

Clone java-scriptengine and mvn install (symlink ch.obermuhlner.scriptengine.java/src to make the Maven build work).

Clone Java Scripting under openhab-addons/bundles and run mvn install

Sample Scripts

The samples are all in src/script/java.

Item change rules, annotation based

import java.util.Map;

import org.openhab.automation.javascripting.annotations.ItemStateUpdateTrigger;
import org.openhab.automation.javascripting.annotations.Rule;
import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
import org.openhab.core.items.Item;
import org.openhab.core.library.types.OnOffType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MPDSilencer extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.mpdrules");

    private OnOffType mpd_previous_stop_state;

    @Rule(name = "PhoneRingingRule")
    @ItemStateUpdateTrigger(id = "PhoneRingingTrigger", item = "PhoneRinging")
    public SimpleRule phoneRingingRule = new SimpleRule() {

        @Override
        public Object execute(Action module, Map<String, ?> inputs) {

            logger.info("phone", "phone ringing {}", inputs.get("state"));

            Item mpd_stop = itemRegistry.get("mpd_music_player_pi_stop");

            OnOffType mpd_stop_state = (OnOffType) mpd_stop.getState();

            mpd_previous_stop_state = mpd_stop_state;

            if (mpd_stop_state == OnOffType.OFF) {
                events.sendCommand("mpd_music_player_pi_stop", "ON");
            }

            return "";
        }
    };

    @Rule(name = "PhoneIdleRule")
    @ItemStateUpdateTrigger(id = "PhoneIdleTrigger", item = "PhoneIdle")
    public SimpleRule phoneIdleRule = new SimpleRule() {

        @Override
        public Object execute(Action module, Map<String, ?> inputs) {

            logger.info("phone", "phone idle {}", inputs.get("state"));

            if (mpd_previous_stop_state == OnOffType.OFF) {
                events.sendCommand("mpd_music_player_pi_stop", "OFF");
            }

            return "";
        }
    };

    @Override
    protected Object onLoad() {
        logger.info("phone", "rules loaded");
        return null;
    };
}

Changing Items

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.types.DecimalType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EventBusExamples extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.eventbus");

    @Override
    protected Object onLoad() {

        logger.info("Java onLoad()");

        events.sendCommand("Livingroom_Light", "OFF");

        Item item = itemRegistry.get("Morning_Temperature");

        ((NumberItem) item).setState(new DecimalType(0.0f));

        events.postUpdate(item, 37.2f);

        Number state = (Number) item.getState();
        logger.info("new State: {}", state.floatValue());

        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));

        state = (Number) item.getState();
        logger.info("new State again: {}", state.floatValue());

        logger.info("eventbus done");
        
        return null;
    }
}

Cron Rule, method annotation based

import java.util.Map;

import org.openhab.automation.javascripting.annotations.CronTrigger;
import org.openhab.automation.javascripting.annotations.Rule;
import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CronRule extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.cronrule");

    private int counter = 1;

    @Rule(name = "CronRule")
    @CronTrigger(id = "CronTrigger", cronExpression = "0 * * * * ?")
    public Object execute(Map<String, ?> inputs) {

        logger.info("Java cronrule execute {}", counter++);

        return "";
    }

    @Override
    protected Object onLoad() {
        logger.info("Java onLoad()");
        return null;
    };
}

ItemChanged Rule

import java.util.Map;

import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ItemChangedRule extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.itemrule");

    private int counter = 1;

    @Override
    protected Object onLoad() {

        logger.info("Java onLoad()");

        SimpleRule sr = new SimpleRule() {

            @Override
            public Object execute(Action module, Map<String, ?> inputs) {

                logger.info("Java execute {}", counter++);

                return "";
            }
        };

        Trigger trigger = createItemStateChangeTrigger("BatteryLevelChangedTrigger", "BatteryLevel");

        ruleBuilder(sr).withName("BatteryLevelChanged").withTrigger(trigger).activate();

        logger.info("BatteryLevelChanged rule activated");
        
        return null;
    };
}

Transformation Script in Java

a sitemap referencing a transformation in Java

sitemap demo label="My home automation" {
    Frame label="Uptime" {

		Text icon="time" label="uptime [JAVA(SecHHMMSSTransformation.java):%s]" item=uptimeSeconds
    }
}

and the transformation used (it must be in conf/transform)

import java.time.Duration;

import org.openhab.automation.javascripting.scriptsupport.Script;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SecHHMMSSTransformation extends Script {

	private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.TR");
	
	@Override
	protected Object onLoad() {
		
		logger.info("Java onLoad()");
		
		String s = (String)input;
		
		Duration d = null;
		
		try {
			d = Duration.ofSeconds(Long.parseLong(s));
		} catch (NumberFormatException e) {
			return null;
		}

		String timeHHMMSS = String.format("%02d:%02d:%02d", d.toHours(), d.toMinutesPart(), d.toSecondsPart());

		logger.info( "duration: {}", timeHHMMSS);
		
		return timeHHMMSS;

	}

}

Addon Actions

import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.thing.binding.ThingActions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SendMail extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.mail");

    @Override
    protected Object onLoad() {

        ThingActions thingActions = actions.get("mail", "mail:smtp:local");
        
        actions.invoke(thingActions, "sendMail", "weberjn", "java sendmail", "mailcontent Java script onload()");

        logger.info("mail sent");
        
        return null;
    }
}

Static Actions

import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.model.script.actions.Exec;
import org.openhab.core.model.script.actions.HTTP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StaticActions extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javarules.actions");

    @Override
    protected Object onLoad() {

        String res = HTTP.sendHttpGetRequest("http://localhost/");

        String cmd = "termux-media-player play camera-shutter.mp3";
        Exec.executeCommandLine("ssh", "nexus9", cmd);

        logger.info("static actions done");
        
        return null;
    }
}

Transformations

import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.transform.actions.Transformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Transformations extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javarules.transform");

    @Override
    protected Object onLoad() {

        String s = Transformation.transform("REGEX", ".*(hello).*", "hello, world");

        logger.info("transform done, got: " + s);
        
        return null;
    }
}

Persistence

import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.items.Item;
import org.openhab.core.persistence.extensions.PersistenceExtensions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PersistItems extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.persist");

    @Override
    protected Object onLoad() {

        Item item = itemRegistry.get("Morning_Temperature");

        PersistenceExtensions.persist(item);

        logger.info("persist done");
        
        return null;
    }
}

Write to a File

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;

import org.openhab.automation.javascripting.annotations.ItemStateUpdateTrigger;
import org.openhab.automation.javascripting.annotations.Rule;
import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
import org.openhab.core.items.Item;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FileWriteRule extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.writefile");

    @Rule(name = "TemperatureToFileRule")
    @ItemStateUpdateTrigger(id = "TemperatureTrigger", item = "Morning_Temperature")
    public SimpleRule temperatureToFileRule = new SimpleRule() {

        @Override
        public Object execute(Action module, Map<String, ?> inputs) {

            logger.info("@ItemStateUpdateTrigger Morning_Temperature");

            Item item = itemRegistry.get("Morning_Temperature");

            Number state = (Number) item.getState();

            Path path = Paths.get("/tmp/Morning_Temperature.txt");

            try {
                Files.writeString(path, String.format("%f%n", state.floatValue()), StandardOpenOption.CREATE,
                        StandardOpenOption.APPEND);
            } catch (IOException e) {
                logger.error("", e);
                throw new RuntimeException(e);
            }

            return "";
        }
    };

    @Override
    protected Object onLoad() {
        logger.info("Java onLoad()");
        return null;
    }
}

Set a new temperature

openhab> openhab:update Morning_Temperature 37.7
$ cat /tmp/Morning_Temperature.txt
37.700001

Json Rule

This rule is triggered bei either of two items, creates a Json String from their states and sends it to a third item (which should be linked to an MQTT command topic, on which a Python script could listen and feed an e-paper display).

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.openhab.automation.javascripting.annotations.ItemStateUpdateTrigger;
import org.openhab.automation.javascripting.annotations.Rule;
import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
import org.openhab.core.items.Item;
import org.openhab.core.library.types.DecimalType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

public class JsonRule extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.jsonrule");

    final String outsideTemperatureItem = "OutsideTemperature";
    final String salonTemperatureItem = "SalonTemperature";

    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Rule(name = "JsonRule")
    @ItemStateUpdateTrigger(id = "OutsideTemperatureTrigger", item = outsideTemperatureItem)
    @ItemStateUpdateTrigger(id = "SalonTemperatureTrigger", item = salonTemperatureItem)
    public SimpleRule jsonrule = new SimpleRule() {

        @Override
        public Object execute(Action module, Map<String, ?> inputs) {

            logger.info("Java jsonrule execute: {}", inputs.toString());

            try {
                String json = createJson();

                logger.info("json: {}", json);

                Item item = itemRegistry.get("EPaper_Screen_Json");

                events.sendCommand(item, json);
            } catch (Exception e) {
                logger.error(inputs.toString(), e);
                throw e;
            }

            return "";
        }
    };

    @Override
    protected Object onLoad() {
        logger.info("Java onLoad()");
        return null;
    };
    

    private String createJson() {
        Map<String, List<Map<String, Object>>> screen = new HashMap<>();

        List<Map<String, Object>> ops = new ArrayList<>();

        int x = 10;
        int y = 0;

        LocalDateTime ldt = LocalDateTime.now();
        String s = ldt.format(dateTimeFormatter);

        Map<String, Object> text0 = new HashMap<>();
        text0.put("type", "text");
        text0.put("x", x);
        text0.put("y", y);
        text0.put("text", String.format("@ %s", s));

        ops.add(text0);

        y += 30;

        DecimalType dt = itemRegistry.get(outsideTemperatureItem).getStateAs(DecimalType.class);

        if (dt != null) {
            float f = dt.floatValue();

            Map<String, Object> text1 = new HashMap<>();
            text1.put("type", "text");
            text1.put("x", x);
            text1.put("y", y);
            text1.put("text", String.format("Temp Outside: %.1f", f));

            ops.add(text1);

            y += 30;
        }

        DecimalType dts = itemRegistry.get(salonTemperatureItem).getStateAs(DecimalType.class);

        if (dts != null) {
            float f = dts.floatValue();

            Map<String, Object> text1 = new HashMap<>();
            text1.put("type", "text");
            text1.put("x", x);
            text1.put("y", y);
            text1.put("text", String.format("Temp Salon: %.1f", f));

            ops.add(text1);

            y += 30;
        }

        screen.put("screenobjects", ops);

        Gson gson = new Gson();

        String output = gson.toJson(screen);

        return output;
    }
}
openhab> openhab:update  OutsideTemperature 27
Update has been sent successfully.
openhab> openhab:status EPaper_Screen_Json
{"screenobjects":[{"x":10,"y":0,"text":"@ 2023-06-04 20:07:42","type":"text"},{"x":10,"y":30,"text":"Temp Outside: 27.0","type":"text"}]}

Groovy Port

This class is ported from the openHAB JSR 223 Groovy Sample. It does not use syntactic sugar of the Script base class, only pure openHAB JSR 223.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.openhab.automation.javascripting.scriptsupport.Script;
import org.openhab.core.automation.Action;
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.Logger;
import org.slf4j.LoggerFactory;

/*
 * @author weberjn
 */
public class GroovyPort extends Script {

    private Logger logger = LoggerFactory.getLogger("org.openhab.automation.javascripting.script");

    public int counter = 1;

    protected Object onLoad() {

        SimpleRule sr = new SimpleRule() {

            @Override
            public Object execute(Action module, Map<String, ?> inputs) {

                logger.info("Java execute {},  inputs: {} ", counter++, inputs);

                return "";
            }
        };

        sr.setName("Java-One");

        List<Trigger> triggers = new ArrayList<Trigger>(1);

        Map<String, Object> triggerConf = new HashMap<String, Object>();
        triggerConf.put("cronExpression", "0 * * * * ?");

        Trigger trigger = TriggerBuilder.create().withId("aTimerTrigger").withTypeUID("timer.GenericCronTrigger")
                .withConfiguration(new Configuration(triggerConf)).build();

        triggers.add(trigger);

        sr.setTriggers(triggers);

        automationManager.addRule(sr);

        logger.info("onLoad() done");
        
        return null;
    }
}

About

openHAB add-on for JSR 223 scripts in Java

Resources

License

EPL-2.0, MIT licenses found

Licenses found

EPL-2.0
LICENSE
MIT
LICENSE-java-scriptengine.txt

Stars

Watchers

Forks

Packages

No packages published

Languages