A praise for JSR223 with Jython (highly recommended)

Hi all,

about two month ago I started creating rules with the JSR223-Engine and Jython.
I was instantly amazed by how simple I could do stuff and by the endless possibilities it offers.
No more need for lambdas, just straight function calls.
These Z-Wave remotes with multiple buttons - just create a base class with all the logic and just override the function for the button press.
Functions I want to use in multiple files - no problem!
And don’t get me started on performance. I was looking into an ODROID C2 for faster response times.
JSR223 runs at least 10 times faster than the stock rule ( at least for me - search for another thread from me there I do the comparison).
I am now still keeping my Raspberry PI2 because it is fast enough and noticeably running smoother.

For me my conclusion is easy:
With Jython Openhab finally became what I always wanted it to be!
Easy, well documented (just search for python code samples) and fast.

If I would had it in the beginning I would have way less problems learning Openhab.
For all the users who are thinking about giving it a try - I highly encourage you to do so!

I just wanted to say thanks for implementing all this! :heart_eyes:

2 Likes

Would you be interested in sharing your rules/config? I would be very keen to see move across if I could see some examples.

I have been pondering a switch to JSR223 myself for while, but haven’t gotten around to it yet.

Is it possible to mix JSR232 rules with “traditional” rules, as a migration step? Obviously I do not mean to mix JSR233 and “traditional” in the same rule file, but starting out with converting some of my rule to JSR233 to see how it works out…

@KjetilA: Yes - this is exactly what I have done. I started with one rule and left all the other ones as they are. Then I migrated more and more to jython. Now I only have few traditional rule files left.

@ben_jones12: Sure - I can post some examples.

1 Like

I’m not going to argue about your results. I’m more than willing to accept jython will be 10x to 20x faster. However, I do want to caveat that that in that other thread the test performed was not really an apples to apples comparison of the two’s performance.

@rlkoshak: I changed my top post. It sais now that for me it is over 10 times faster.

@ben_jones12:
Here is an example even if it is a little bit more sophisticated.

I have a couple of contacts (door/windows). Every time I open one, I want the amount of movements in the last half hour (for presence detection) and the last time stamps (for information). Additionally, I count all the movements just for fun! :wink:

First there is a function which immediately increases and after half an hour decreases the value:

def IncreaseDecrease( item, time):
        BusEvent.postUpdate( item, int(str(item.state)) + 1)
        oh.createTimer(DateTime.now().plusMinutes(time), lambda : BusEvent.postUpdate( item, max(int(str(item.state)) - 1,0) ))

Then there is my contact class, it basicly handles all the code. Note that it is very easy to get an item just by its name! My timestamps are all named like the item, but with a “ts_” prefix.

class cContact(Rule):
    def execute(self, event):

        logger.debug( "event: '" + str(event) + "'")

        item = event.item
        if not item:
            return None
        
        name = item.getName()
            
        #Zeitpunkte
        BusEvent.postUpdate( "ts3_" + name, str(ItemRegistry.getItem( "ts2_" + name).state) )
        BusEvent.postUpdate( "ts2_" + name, str(ItemRegistry.getItem( "ts1_" + name).state) )
        BusEvent.postUpdate( "ts1_" + name, str(ItemRegistry.getItem( "ts0_" + name).state) )
        BusEvent.postUpdate( "ts0_" + name, str(DateTimeType()))

        #30 min counter
        IncreaseDecrease(ItemRegistry.getItem( "ctr_" + name + "_30min"), 30)
        
        #normaler counter
        counter_item = ItemRegistry.getItem( "ctr_" + name)
        BusEvent.postUpdate( counter_item, int(str(counter_item.state)) + 1)

Now for all my contacts I just have to derive from the class:

class AlarmContact(cContact):
        def getEventTrigger(self):
            return [
                ChangedEventTrigger("Flur_Schlafzimmertuere_Tuer",      None, OpenClosedType.CLOSED),
                ChangedEventTrigger("Flur_Wohnungstuere_Tuer",          None, OpenClosedType.CLOSED),
                ChangedEventTrigger("Wohnzimmer_Balkontuere_Tuer",      None, OpenClosedType.CLOSED),
                ChangedEventTrigger("Schlafzimmer_Fenster_Fenster",     None, OpenClosedType.CLOSED)
            ]

Note, that it is also possible to know which item triggered the rule execution. This allows very elegant code!

1 Like

That is looking very powerful - how do you get an openHAB contact item to derive from your custom class tho?

Sorry, probably a stupid question but I obviously have a lot to learn about this Jython stuff!

Jython is basicly Python for Java. So you can search the web for python tutorials.
The real magic is the JSR223-Engine.

I’ll post a simpler example:

class UpdateSensorManuellKueche(Rule):
    def getEventTrigger(self):
        return [
            UpdatedEventTrigger("Kueche_Arbeitsplatte_Manuell")
        ] 

    def execute(self, event):
        
        item = event.item
        if not item:
            return None

        logger.debug("{:15s} | manuell: {}".format( "Arbeitsplatte", event.newState))            
        
        manuell = str( event.newState)
        
        if( manuell == "ON"):
            #sleep nur, wenn es an ist            
            if int(str(ItemRegistry.getItem("Kueche_Arbeitsplatte_Out_R").state)) > 0:
                BusEvent.sendCommand( "Kueche_Arbeitsplatte_Out_R",  "0")
                time.sleep( 1.5)
            
            BusEvent.sendCommand( "Kueche_Arbeitsplatte_Out_R",  "100")
        else:
            BusEvent.sendCommand( "Kueche_Arbeitsplatte_Out_R",  "0")

Each Rule ist a class.
With getEventTrigger you can return different types of triggers.
Based on these triggers the execute-function will be called.

Thanks Sebastian - I know a little python - I was more interested to see how rules got attached to items etc. From what you are saying you create a python class for each rule, and in the getEventTrigger method return one or more triggers for that rule - do you reference the item names by string?

Then you can access other items via the ItemRegistry and BusEvent.

Thanks for posting those, I think it is high time I started having a play and migrating some of my rules across.

I’m also a JSR223/Jython fan. I wrote a tutorial about using the JSR223 rules that might help.

In addition to rules, I’ve also written full OH1 bindings in JSR223 Jython.

1 Like

I would love to move to JSR223 but the documentation only shows a setup procedure for Linux. Applying the procedure on MacOS which has great similarity to LInux was not straight forward and a big barrier for me.
Unfortunately for some reasons which I cannot change I have to be on windows.

If someone could create a documentation on how to setup jython on windows that would be really appreciated.

A quick Google search reveals:

http://www.jython.org/archive/21/MacOS_Install.html

I think he was asking for directions about how to configure openHAB to use Jython. Installying Jython itself is relatively straightforward but to use it openHAB requires updating startup scripts with correct Java classpaths and system properties. It can be a bit challenging for some who isn’t a Java programmer.

@steve1. correct.
When I tried to get it running under MacOS, I indeed strugggled for sometime to change the start.sh because the Linux start.sh did not work.
Patching the start.bat in Windows exceeds my capabilities.

@martin_klimke: here is a working startup.bat. Just change the JYTHON_HOME accordingly.
It also references “configurations/scripts/lib” as a import/library folder where you can write library functions.

@echo off

:: set path to eclipse folder. If local folder, use '.'; otherwise, use c:\path\to\eclipse
set ECLIPSEHOME=server

:: set ports for HTTP(S) server
set HTTP_PORT=8080
set HTTPS_PORT=8443
set JYTHON_HOME=c:\Progs\jython2.7.0
 
:: get path to equinox jar inside ECLIPSEHOME folder
for /f "delims= tokens=1" %%c in ('dir /B /S /OD %ECLIPSEHOME%\plugins\org.eclipse.equinox.launcher_*.jar') do set EQUINOXJAR=%%c
 
:: start Eclipse w/ java
echo Launching the openHAB runtime...
set ARGS=-Dorg.osgi.framework.bundle.parent=ext -Dpython.home="%JYTHON_HOME%" -Dpython.path="configurations/scripts/lib" -Dosgi.clean=true -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Djetty.port=%HTTP_PORT% -Djetty.port.ssl=%HTTPS_PORT% -Djetty.home=. -Dlogback.configurationFile=configurations/logback.xml -Dfelix.fileinstall.dir=addons -Dfelix.fileinstall.filter=.*\\.jar -Djava.library.path=lib -Djava.security.auth.login.config=./etc/login.conf -Dorg.quartz.properties=./etc/quartz.properties -Dequinox.ds.block_timeout=240000 -Dequinox.scr.waitTimeOnBlock=60000 -Djava.awt.headless=true -Dfelix.fileinstall.active.level=4 -cp %JYTHON_HOME%\jython.jar;%EQUINOXJAR% org.eclipse.equinox.launcher.Main %* -console 
::echo %ARGS% 
java %ARGS%
1 Like

I have spent a frustrating hour trying to install this on a standard Ubuntu package installation. I have installed jython and added

#For Jython
JYTHON_HOME="/opt/jython"

OPENHAB_ARGS="-Dpython.home=${JYTHON_HOME}   -Dpython.path=${OPENHAB_CONFIGURATIONS_DIR}/scripts/lib -classpath ${JYTHON_HOME}/jython.jar"

to openhab.in.sh but it does not seem to be recognised

12:14:21.306 [DEBUG] [.r.internal.RuleModelActivator:42   ] - Registered 'rules' configuration parser
12:14:21.376 [DEBUG] [m.r.internal.engine.RuleEngine:77   ] - Started rule engine
12:14:24.775 [DEBUG] [j.internal.Jsr223CoreActivator:33   ] - Registered 'jsr223' scripting-engine
12:14:24.781 [DEBUG] [j.internal.engine.Jsr223Engine:96   ] - itemRegistry set
12:14:24.790 [DEBUG] [j.internal.engine.Jsr223Engine:68   ] - activate()
12:14:24.795 [INFO ] [o.o.c.j.i.e.s.ScriptManager   :58   ] - Available engines:
12:14:24.827 [INFO ] [o.o.c.j.i.e.s.ScriptManager   :60   ] - Oracle Nashorn
12:14:24.831 [INFO ] [o.c.j.i.e.scriptmanager.Script:84   ] - Loading Script test.py
12:14:24.839 [WARN ] [o.o.c.j.i.e.s.ScriptManager   :92   ] - No Engine found for File: test.py
12:14:24.849 [DEBUG] [j.internal.engine.Jsr223Engine:78   ] - Started jsr223 engine

What am I missing?

Edit:
I found the problem. The packages run a jar file and from man java

           When you use the -jar option, the specified JAR file is the source of all user classes, and
           other class path settings are ignored.

You learn something every day.

Edit2:
I learned something today as well. You can use the -Xbootclasspath in this situation. I changed /etc/default/openhab to include this line and now I can load python scripts

JAVA_ARGS="-Xbootclasspath/a:/opt/jython2.7.0/jython.jar -Dpython.home=/opt/jython2.7.0 -Dpython.path=configurations/scripts/lib"

@Spaceman_Spiff
worked like charm.
So easy to setup.

Thank you so much.

Just wondering if it is all that sensible to start migrating all my rules over to Jython, when it isn’t currently supported in OH v2.0. Do we know what the rules engine will be in v2.0?

Don’t want to have to do this migration twice!

A JSR223 extension is being developed for OH2…

1 Like

@steve1
Can you please comment the stability on the existing JSR233 solution based on your practical experience ?