[SOLVED] Need to know the correct way of implementing a recurring timer or looping thread in JSR223/Jython

  • Platform information:
    • Hardware: Raspberry Pi 3 B+, 120gb mSATA SSD in a USB enclosure
    • OS: Raspbian Stretch
    • Java Runtime Environment: 1.8.0_212
    • openHAB version: 2.4.0 release build. Jython 2.7.0

Hi there!

Short version: The script execution context of a JSR223 Jython script is not terminated when a script is updated and reloaded, so you end up with old recurring timers or threads running simultaneously, fighting each other.

I’m a new to openhab (and linux) but I have 20 years of C/C++ experience as a Windows developer. I’m trying to migrate from fibaro (lua) before my system grows any bigger as it’s already showing serious cracks. Experiencing some serious learning curve issues with openHAB but working through them – openHAB is incredibly powerful if you know how to use it, so it really looks like the best answer. I’ve spent over a week watching tutorials, reading documentation, following guide, and experimenting, but here’s where I’m stuck and could use a few pointers if possible. :wink:

I have exterior lights all around my property on 3 separate circuits in a round-robin fashion (1-2-3-1-2-3), controlled by a web relay. Controlling them is as simple as sending an HTTP request or an UDP packet.

I’m trying to make lights either flash or chase. Chase mode is strictly for showing off, so long-term performance is not an issue (I never leave them in chase mode for very long) but I do need to loop at 3 Hz or so (interval ~300ms).
Flashing is for the door bell. If I’m out in the garden far from the house, it would be great if the lights flash a couple of times if someone happens to ring the door bell when I’m outside… and if it’s currently in chase mode it should flash in a way that is noticeable and then go back to chase. It’s those kind of features that make it necessary to use a recurring timer of some kind.

At first, I was going to try to do it in the Rules DSL (after all I managed to do it in LUA in a fibaro HC2 scene so how hard could it be!). But, then I saw an answer from a highly skilled volunteer in another thread about looping timers, that anything below 750ms should not be implemented in a rule DSL for performance reasons. He did say he didn’t think it should be a JSR223 rule either but for reasons not applicable to my particular case (not performance related). Here is that thread: Design Pattern: Looping Timers

So, I’m trying to implement this recurring loop in Jython. It really doesn’t feel like this should be difficult, but this is where I’m stuck. In other platforms I know, using a recurring timer is the easiest thing in the world. Hopefully I’m just doing it the wrong way.

So, the problem:

I can create a thread with a while loop…

from threading import Thread import time def thread_function(name): for i in xrange(10) : LoggerFactory.getLogger("org.eclipse.smarthome.automation.examples").info("looping in thread") time.sleep(2) LoggerFactory.getLogger("org.eclipse.smarthome.automation.examples").info("thread done")

x=Thread(target=thread_function,args=(1,))
x.start()

…or I can create a timer which reinitializes itself on the callback.

from threading import Timer
def timerhandler():
LoggerFactory.getLogger(“org.eclipse.smarthome.automation.examples”).info(“tick tock”)
t=Timer(3.0,timerhandler)
t.start()
timerhandler()

The problem is:
When the script reloads (because i made a change), the thread or recurring timer from the previous version never stops running. So then there’s two of them running, fighting. Another time and there’s three. sudo service openhab2 restart is the only way out.

Surely there must be a better way. CRON won’t do it because I need higher granularity than 1 second for this particular purpose.

So, what is the best way of having something recurring (at an interval of let’s say 3 times per second) running as a script inside OpenHAB?

It doesn’t have to be Jython. I’m still just starting out with OpenHAB and I have no previous python or java experience so I can totally use another form of scripting if there’s a better way.

Thanks very much in advance!

///Leif

Add a function called scriptUnloaded to your script. This will get called by the ScriptEngineManager when the script is unloaded. Inside the function, stop your timer.

1 Like

That did it! Thanks, Scott.
In hindsight it seems so obvious. So I started thinking, how did I miss this? so I searched for scriptUnloaded in the forum, and only found two previous mentions, and one of them was from you, Scott! So, I don’t feel so bad anymore, but still more appreciative. Thank you again – no way in hell I would have found this if you hadn’t responded.

If anyone could clue me in as well, is it really good practice to restart the timer every time it fires, or is there a way to make an actual recurring timer?

There is a documentation update in flight for the helper libraries and another for OH scripted automation. This information is included.

You aren’t really restarting the timer, but creating a new one. After a timer starts it’s action, it’s terminated. There is no builtin module that provides recurring timer functionality and I don’t see any issue with what you are doing.

BTW, when posting code or logs, please put them in code fences. And welcome to the forum!

Understood. Thanks, Scott! And, I thought I did put the code in code fences (< code >) but i see now that the backticks provide a much better result.

It seems like a thread is the most efficient option then. I’m having a python syntax problem I can’t get past – I’ve been reading documentation and trying things for hours. Hopefully a simple issue.

When I define a class derived from Thread, if I add a member function in my derived class (with a new name, one not defined in the parent class which is Thread) then why can’t I call call this member function on my object instance? I’ve stripped it down to the smallest section I can.

Canonical example:

from org.slf4j import LoggerFactory
from threading import Thread

def logprint(text):
	LoggerFactory.getLogger("org.eclipse.smarthome.automation.examples").info(text)

class MyClass(Thread):
	def __init__(self, name):
		Thread.__init__(self)
		self.name = name
	
	def mygetName(self):
		return "HELLO my name is "+self.name

	def run(self):
		pass

	

instance=MyClass("bobby")

logprint(instance.mygetName () )

If I change the class definition to class MyClass(object): and comment out Thread.init(self) then I can call instance.mygetName() just fine. But, if derived from Thread, I get the following error:

2019-06-10 21:53:12.158 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script ‘file:/etc/openhab2/automation/jsr223/python/personal/classtest.py’: AttributeError: ‘org.python.core.FunctionThread’ object has no attribute ‘mygetName’ in at line number 22

…as if I’m trying to call an instance of Thread… but I’m not. I’m calling an instance of MyClass, which does have a mygetName function. Any idea what I’m missing?

Thanks again :slightly_smiling_face:

///Leif

Okay, I’ve figured it out, here’s the solution in case anyone else sees this in the future:

Although you can inherit a class from from threading.Thread class in Python, you cannot inherit from Thread in Jython without running into trouble such as what I did (unable to access member functions externally), because you get an object reference to the Thread template, not your own class object. This seems to be one of the places Python and Jython differ. I never would have figured this one out without a friend who has years of Python experience. What finally yielded the clue was:

logprint(repr(type(instance)))

which prints the underlying type of any python object.

Instead, do something like this:

main_object = MyClass()
thread_object=Thread(target=main_object.run)
#notice how it's run and not run() -- we want a reference to the function instead of running the function
thread_object.start()

and then:

def scriptUnloaded():
    global main_object
    main_object.stop()
    thread_object.stop()

Now it works properly for me, or at least well enough to do what i wanted. I’ll post the whole script file later when I’ve deployed it.

2 Likes