A praise for JSR223 with Jython (highly recommended)

Tags: #<Tag:0x00007f616e81e2b0> #<Tag:0x00007f616e81e058>

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 ?

It’s been very stable for me. When I started using openHAB I looked at the Xtext language and knew right away that I wanted to use JSR223. I know many programming languages already (like Python) and it didn’t make sense to invest time in learning an openHAB-specific language unless there were clear and significant benefits, which I didn’t see, From the postings I’ve seen in the forum it seems to me that JSR223 is less buggy and performs better than the Xtext rule framework. My personal opinion is that the JSR223-based rules should be the primary rule framework with the Xtext-based framework remaining to support legacy rule implementations.

3 Likes

@steve1.
This is great news. I also was never convinced about the Xtest DSL and always thought that this aspect was one of the weak aspects in openhab.
You encourged me to give it a try.

@steve1 + @martin_klimke :
I completely agree with you. With python being so starter-friendly it would make lots of sense using this as the primary (or at least default) rule language.
For me it is better in every aspect. Even the Error messages.

I am trying my first jython scripts and want stop the thread for 1.5 seconds. When using

time.sleep( 1.5)

I am receiving the following error messages.

2016-04-29 19:49:37.570 [ERROR] [.c.j.i.e.RuleExecutionRunnable] - Error while executing rule: org.python.proxies.builtin$Martin_kommt_nach_hause$107@1bdf00
org.python.core.PyException: null
at org.python.core.Py.NameError(Py.java:284) ~[jython.jar:na]
at org.python.core.PyFrame.getglobal(PyFrame.java:265) ~[jython.jar:na]
at org.python.pycode.pyx37.execute$11(:65) ~[na:na]
at org.python.pycode.pyx37.call_function() ~[na:na]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[jython.jar:na]
at org.python.core.PyBaseCode.call(PyBaseCode.java:307) ~[jython.jar:na]
at org.python.core.PyBaseCode.call(PyBaseCode.java:198) ~[jython.jar:na]
at org.python.core.PyFunction.call(PyFunction.java:482) ~[jython.jar:na]
at org.python.core.PyMethod.instancemethod___call
(PyMethod.java:237) ~[jython.jar:na]
at org.python.core.PyMethod.call(PyMethod.java:228) ~[jython.jar:na]
at org.python.core.PyMethod.call(PyMethod.java:218) ~[jython.jar:na]
at org.python.core.PyMethod.call(PyMethod.java:213) ~[jython.jar:na]
at org.python.core.PyObject._jcallexc(PyObject.java:3626) ~[jython.jar:na]
at org.python.core.PyObject._jcall(PyObject.java:3658) ~[jython.jar:na]
at org.python.proxies.builtin$Martin_kommt_nach_hause$107.execute(Unknown Source) ~[na:na]
at org.openhab.core.jsr223.internal.engine.RuleExecutionRunnable.run(RuleExecutionRunnable.java:36) ~[na:na]
at java.lang.Thread.run(Unknown Source) [na:1.8.0_73]

That is the code:
class Martin_kommt_nach_hause(Rule):

def getEventTrigger(self):
    return [
        ChangedEventTrigger("Presence_Martin")
    ]

def execute(self,event):
    oh.logInfo("Martin_kommt_nach_hause","hallo")
    item = event.item
    oh.logInfo("Event :", str(ItemRegistry.getItem("Presence_Martin").state))
    oh.logInfo("Event :", str(item.state))    
    if str( event.newState) == "OFF":
        oh.sendCommand("Command_2_speech", "Martin hat das Haus verlassen")
        oh.sendCommand("Home_big_vorkurzem", "OFF")

    if str( event.newState) == "ON":
        #oh.sendCommand("Martin_home_time", new DateTimeType())
        oh.sendCommand("Command_2_speech", "Martin kommt gleich")

    time.sleep( 1.5)