Simplified Jython rule definition (similar to Rules DSL) using a universal decorator

That fixed it for me!

Thanks a lot!

1 Like

@5iver, you have probably already received a PR notification for this PR, but thought I would bring it to your attention here.

2 Likes

I’m not that familiar with jython but can I use a jython script/rule that imports https://lmstools.readthedocs.io/en/latest/ (lmstools) ? I need some extra functionality (playlist control, add/remove track) the squeezebox binding doesn’t have…

You’ll need to try importing it to know for sure…

http://www.jython.org/archive/22/userfaq.html#id24

1 Like

mhm after updating to 2.4 all my jython rules stopped working they are also not showing in the ui anymore

Without much to go on, my best guess is that the Experimental Rule Engine add-on may not be installed?

More detail about your system would help:

My guess is that OH over wrote the JAVAEXTRAOPTs during the update

1 Like

@scottk: The Rule engine is installed. This was successfully running until the update.

@mjcumming: You were absolutley rigtht. Thanks!

Also i had to change my imoprts from openhab. something to core.something.

Hi,

I am currently testing out some functions for migrating all my DSL rules to Jython.

I have defined the following function, to test if I can use the createTimer action with functions defined within functions:

@rule("Test timer functions - Jython")
@when("Time cron 21 0/1 * * * ?")
def testFunctionsStates(event):
    def theInnerFunction():
        log.info("@@@@@@@@@@@@@@@@@@@@@  Inner Function was called")    
    
    log.info("################# Outer function was called ")

    createTimer(DateTime.now().plusSeconds(30), lambda: theInnerFunction())      

This worked well for some hours, but now I get the following exception:

2018-12-29 16:04:51.009 [INFO ] [e.smarthome.automation.test_rules.py] - @@@@@@@@@@@@@@@@@@@@@  Inner Function was called
2018-12-29 16:05:06.546 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.2018-12-29T16:05:06.545+01:00: <function <lambda> at 0xaf> threw an unhandled Exception: 
org.python.core.PyException: null
	at org.python.core.Py.TypeError(Py.java:259) ~[?:?]
	at org.python.core.PyObject._basic_add(PyObject.java:2141) ~[?:?]
	at org.python.core.PyObject._add(PyObject.java:2119) ~[?:?]
	at org.python.pycode._pyx98.checkSendCommand$2(<script>:34) ~[?:?]
	at org.python.pycode._pyx98.call_function(<script>) ~[?:?]
	at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
	at org.python.core.PyBaseCode.call(PyBaseCode.java:153) ~[?:?]
	at org.python.core.PyFunction.__call__(PyFunction.java:423) ~[?:?]
	at org.python.pycode._pyx98.f$14(<script>:156) ~[?:?]
	at org.python.pycode._pyx98.call_function(<script>) ~[?:?]
	at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
	at org.python.core.PyBaseCode.call(PyBaseCode.java:124) ~[?:?]
	at org.python.core.PyFunction.__call__(PyFunction.java:403) ~[?:?]
	at org.python.core.PyFunction.__call__(PyFunction.java:398) ~[?:?]
	at org.python.core.PyFunction.invoke(PyFunction.java:533) ~[?:?]
	at com.sun.proxy.$Proxy220.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
2018-12-29 16:05:06.549 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.2018-12-29T16:05:06.545+01:00: <function <lambda> at 0xaf> threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
Caused by: org.python.core.PyException
	at org.python.core.Py.TypeError(Py.java:259) ~[?:?]
	at org.python.core.PyObject._basic_add(PyObject.java:2141) ~[?:?]
	at org.python.core.PyObject._add(PyObject.java:2119) ~[?:?]
	at org.python.pycode._pyx98.checkSendCommand$2(<script>:34) ~[?:?]
	at org.python.pycode._pyx98.call_function(<script>) ~[?:?]
	at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
	at org.python.core.PyBaseCode.call(PyBaseCode.java:153) ~[?:?]
	at org.python.core.PyFunction.__call__(PyFunction.java:423) ~[?:?]
	at org.python.pycode._pyx98.f$14(<script>:156) ~[?:?]
	at org.python.pycode._pyx98.call_function(<script>) ~[?:?]
	at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
	at org.python.core.PyBaseCode.call(PyBaseCode.java:124) ~[?:?]
	at org.python.core.PyFunction.__call__(PyFunction.java:403) ~[?:?]
	at org.python.core.PyFunction.__call__(PyFunction.java:398) ~[?:?]
	at org.python.core.PyFunction.invoke(PyFunction.java:533) ~[?:?]
	at com.sun.proxy.$Proxy220.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
	... 1 more

As you can the from the line “@@@@@@@@@@@@@@@@@@@@@ Inner Function was called”, the timer triggered correctly.

I have no clue, why this exception is thrown. Maybe you have any idea?

Thanks for your help!

Juelicher

I have not used the java timer object in jython.

I have used the jython timer object with no problems.

from threading import Timer

timer = Timer(time_out_seconds, timeout_callback)

Well, I am a bit lazy and using the createTimer action I do not have to store the timer in a variable and start it explicitly :wink:

Today I did some testing with the Python timer, using the example linked in the documentation as a template.
I think, that there a two bugs in the example:

First:

if chargerTimer1 is None or str(chargerTimerAttached.getState()) == "TERMINATED":

Beside the typo in the timer name “chargerTimerAttached”, I get an error, that the timer object has no method “getState()”

Second:

chargerTimer1.stop()

A timer can be created by calling:

 createPythonTimer(15, 'TestTimerName', lambda: theInnerFunction())

There I also get an error, that this method does not exists, instead “cancel()” can be used.

What seems to work for me:

testTimer = {}

def createPythonTimer(elapseTime, item, callback):
    myTimer = testTimer.get(item) # returns none, when key does not exists

    if myTimer is None or not myTimer.isAlive():
        myTimer = Timer(elapseTime, callback)        
        testTimer[item] = myTimer
        myTimer.start()
        log.info("test_rules.py - Created python timer, item: " + item + ", time: " + str(elapseTime))


def stopPythonTimer(item):
    myTimer = testTimer.get(item) # returns none, when key does not exists

    if myTimer is not None and myTimer.isAlive():
        myTimer.cancel()
        testTimer[item]  = None
        log.info("test_rules.py - Stopped python timer, item: " + item)

In this small examples I store the timers in a dictionary, so that I do not have to create a variable for each timer.

According to what I found in the internet, “myTimer.finished” should be better than “myTimer.isAlive()”, as there is a small time between finishing the timer and exiting the background thread, internally used for the timer and therefor setting “isAlive()” to false. But checking “finished” does not work for me, I do not know why.

A better way to track timer state (running/stopped) would be to use the callback to set the state of the timer. Or you could remove the timer item from your dictionary in the callback. You do not need to check if the timer is alive to call cancel.

Thanks for the tipps, I will try that out. Have to think about it, as I use the same callback functions in multiple timers, so the timer would probably have to be passed to the callback.

I am still in the stage of figuring out how to convert my rules from DSL to Jython, but even now it makes some tasks much easier!

You could do

testTimer = {}

def createPythonTimer(elapseTime, item, callback):
    myTimer = testTimer.get(item) # returns none, when key does not exists

    if myTimer is None:

        def cb():
            testTimer[item] = None
            callback()

        myTimer = Timer(elapseTime, cb)        
        testTimer[item] = myTimer
        myTimer.start()
        log.info("test_rules.py - Created python timer, item: " + item + ", time: " + str(elapseTime))


def stopPythonTimer(item):
    myTimer = testTimer.get(item) # returns none, when key does not exists

    if myTimer is not None:
        myTimer.cancel()
        testTimer[item]  = None
        log.info("test_rules.py - Stopped python timer, item: " + item)

Thanks for your suggestion, good idea, I will try that!

Today I encountered an other problem, probably Jython related.

I created a mylib.py file in the lib/python/personal directory

In principal I can import that file in my rules files in jsr223/personal. But when I change the mylib.py the compiled bytecode file in mylib$py.class is not updated.

I can trigger an update by renaming the mylib.py and the import statement, but there surely is an other way…

Thanks for your help again and a happy new year to all of you!

Juelicher

Modules do not reload unless you restart OH. You can solve this by reloading any modules.

Example:

import area_occupancy_event_metadata
reload (area_occupancy_event_metadata)
from area_occupancy_event_metadata import Area_Occupancy_Event_Metadata

You can trigger the reload by changing and saving the script that call the module.

I don’t know if Windows provides a command equivalent to touch on linux, but on a linux system you can simply do the following to trigger the reload:

sudo touch /etc/openhab2/automation/jsr223/personal/mylib.py

Thanks, booth of you, for your quick help!

I am running openHAB on macOS, touch is available. But the mylib$py.class was not updated even when it was older than the mylib.py file. Maybe this is related to macOS and the HFS file system?!

Reloading mylib from of my rules files triggers recompiling the bytecode, this seems as a good enough workaround, as the library will (hopefully :wink: ) nor be changed very often.

Sorry if it gets annoying…

While moving my functions to the library file I stumble about an other issue, I would like to understand to get a better grasp of the internals:

In my rules I can use OnOffType, itemRegistry and other types without explicitly importing them. In the library file I had to add “from core.jsr223.scope import itemRegistry, OnOffType, UpDownType, OpenClosedType”

Why is that?

Thanks again!

Juelicher

I think it is because in your rules files, you have the following import:

from core.rules import rule

The file automation/lib/python/core/rules.py has some imports that take care of automagically making available those types and globals.