Use own java classes in rules

I think I’ve solved your problem by creating an OSGi bundle out of the code, see:

openhab> bundle:list |grep 'Problem Solved'
233 │ Active │  80 │ 1.0.0.202010221207      │ Problem Solved

openhab> bundle:capabilities de.no3x.openhab-jython-java-lib-problem 
de.no3x.openhab-jython-java-lib-problem_1.0.0.202010221207 [233] provides:
--------------------------------------------------------------------------
osgi.wiring.bundle; de.no3x.openhab-jython-java-lib-problem 1.0.0.202010221207 [UNUSED]
osgi.wiring.host; de.no3x.openhab-jython-java-lib-problem 1.0.0.202010221207 [UNUSED]
osgi.identity; de.no3x.openhab-jython-java-lib-problem 1.0.0.202010221207 [UNUSED]
osgi.wiring.package; de.no3x.core 1.0.0 [UNUSED]
osgi.wiring.package; de.no3x.core.history 1.0.0 [UNUSED]
osgi.wiring.package; de.no3x.core.input 1.0.0 [UNUSED]
osgi.wiring.package; de.no3x.core.output 1.0.0 [UNUSED]

openhab> bundle:requirements de.no3x.openhab-jython-java-lib-problem
de.no3x.openhab-jython-java-lib-problem_1.0.0.202010221207 [233] requires:
--------------------------------------------------------------------------
osgi.wiring.package; (&(osgi.wiring.package=com.google.common.collect)(&(version>=27.1.0)(!(version>=28.0.0)))) resolved by:
   osgi.wiring.package; com.google.common.collect 27.1.0 from com.google.guava_27.1.0.jre [28]
osgi.ee; (&(osgi.ee=JavaSE)(version=1.8)) resolved by:
   osgi.ee; JavaSE [1.0.0, 1.1.0, 1.2.0, 1.3.0, 1.4.0, 1.5.0, 1.6.0, 1.7.0, 1.8.0, 9.0.0, 10.0.0, 11.0.0] from org.eclipse.osgi_3.12.100.v20180210-1608 [0]

Thank’s for looking into it.

I installed the jar by putting it into the addons directory. I guess you put it there too?

I used the classes in my script and verified the the outputs:

 bundle:list |grep 'Solved'
371 x Active x  80 x 1.0.0.202010221430      x Problem Solved
openhab> bundle:capabilities de.no3x.openhab-jython-java-lib-problem^C
openhab> bundle:capabilities de.no3x.openhab-jython-java-lib-problem
de.no3x.openhab-jython-java-lib-problem_1.0.0.202010221430 [371] provides:
--------------------------------------------------------------------------
osgi.wiring.bundle; de.no3x.openhab-jython-java-lib-problem 1.0.0.202010221430 [UNUSED]
osgi.wiring.host; de.no3x.openhab-jython-java-lib-problem 1.0.0.202010221430 [UNUSED]
osgi.identity; de.no3x.openhab-jython-java-lib-problem 1.0.0.202010221430 [UNUSED]
osgi.wiring.package; de.no3x.core 1.0.0 required by:
   org.openhab.core.automation.module.script_2.5.0 [367]
osgi.wiring.package; de.no3x.core.history 1.0.0 required by:
   org.openhab.core.automation.module.script_2.5.0 [367]
osgi.wiring.package; de.no3x.core.input 1.0.0 [UNUSED]
osgi.wiring.package; de.no3x.core.output 1.0.0 [UNUSED]
openhab> bundle:requirements de.no3x.openhab-jython-java-lib-problem
de.no3x.openhab-jython-java-lib-problem_1.0.0.202010221430 [371] requires:
--------------------------------------------------------------------------
osgi.wiring.package; (&(osgi.wiring.package=com.google.common.collect)(&(version>=27.1.0)(!(version>=28.0.0)))) resolved by:
   osgi.wiring.package; com.google.common.collect 27.1.0 from com.google.guava_27.1.0.jre [22]
osgi.ee; (&(osgi.ee=JavaSE)(version=1.8)) resolved by:
   osgi.ee; JavaSE [1.0.0, 1.1.0, 1.2.0, 1.3.0, 1.4.0, 1.5.0, 1.6.0, 1.7.0, 1.8.0] from org.eclipse.osgi_3.12.100.v20180210-1608 [0]

My script looks like this:

"""
This script uses a decorated cron rule that will generate logs every 10s and can be used to test your initial setup.
"""

from de.no3x.core.history import StringHolder
from de.no3x.core.history import History
from de.no3x.core.output import BrightnessConsumer
from de.no3x.core import LEDBrightnessCalculator
from core.actions import LogAction

s = StringHolder("fasa")
print "s:", s.getString()
LogAction.logInfo("HelloWorld", s.getString())


history = History.create()

class LogOutputDevice(BrightnessConsumer):
    def output(self, brightness):
        LogAction.logInfo("HelloWorld", str(brightness))

logOutputDevice = LogOutputDevice()
brightnessCalculator = LEDBrightnessCalculator(history, lambda x: True, logOutputDevice)
brightnessCalculator.start()

The output is:

Error during evaluation of script 'file:/etc/openhab2/automation/jsr223/python/personal/hello_world.py': ImportError: No module named de in <script> at line number 5

Were you able to actually import the classes?

Edit:
I forgot that StringHolder was actually not included in this minified github Example.
The Code executes without error:

"""
This script uses a decorated cron rule that will generate logs every 10s and can be used to test your initial setup.
"""

from de.no3x.core.history import History
from de.no3x.core.output import BrightnessConsumer
from de.no3x.core import LEDBrightnessCalculator
from core.actions import LogAction

history = History.create()

class LogOutputDevice(BrightnessConsumer):
    def output(self, brightness):
        LogAction.logInfo("HelloWorld", str(brightness))

logOutputDevice = LogOutputDevice()
brightnessCalculator = LEDBrightnessCalculator(history, lambda x: True, logOutputDevice)
brightnessCalculator.start()

But there is no Output. If you compare the behaviour with the Main class then it sould log the calculated brightness every second. But there is no output and no error.

So basically I tried to convert the Main class to jython
Java:

	History history = History.create();
		LEDBrightnessCalculator brightnessCalculator = new LEDBrightnessCalculator(history, () -> true, System.out::println);
		brightnessCalculator.start();

Which prints the caclulated value every second. E.g. when I simply execute the Main:

0.01
0.02
0.03
0.04
0.05
0.06
0.07
0.08
0.09

Jython:

from de.no3x.core.history import History
from de.no3x.core.output import BrightnessConsumer
from de.no3x.core.output import DefaultBrightnessConsumer
from de.no3x.core import LEDBrightnessCalculator
from core.actions import LogAction

history = History.create()

class LogOutputDevice(BrightnessConsumer):
    def output(self, brightness):
        LogAction.logInfo("HelloWorld", str(brightness))

logOutputDevice = LogOutputDevice()
defaultBrightnessConsumer = DefaultBrightnessConsumer()
brightnessCalculator = LEDBrightnessCalculator(history, lambda x: True, defaultBrightnessConsumer)
brightnessCalculator.start()

No output. No error. Not sure what/if it’s doing something.

It might be that the script environment gets destroyed and there is no chance for the output logic to be called.

I’m not into Jython myself so I haven’t tested it, but based on the bundle:capabilities output it looks like your classes get picked up by the automation engine. :slight_smile:

If it starts its own thread that is very likely. You could do a quick test with a sleep to see if it generates output… or create a “Hello World!” example that just prints something using the calling thread.

I already tried this:

charger_timer = Timer(1, lambda: LogAction.logInfo("HelloWorld", "as"))
charger_timer.start()

sleep(10)
LogAction.logInfo("HelloWorld", "after 10")
sleep(10)
LogAction.logInfo("HelloWorld", "after 10")
LogAction.logInfo("HelloWorld", "exit")

But there is no output either. I guess there is a IPC problem or whosoever knowns how this language meshup works.

I’m not sure how to construct this.
But I guess I’m on the wrong track. I should create an own binding that runs in the background and provides an Thing\Item to be used in regular rules.

You might need to configure the loggers first for this to show:

log:set info HelloWorld

Output of these log statements works.

started
after 10
after 10
exit

But not the important onces from the LogAction.logInfo("HelloWorld", str(brightness)) which led me to the conclusion that this is not executed somehow. Sorry for for the unclear statement.

Well it sure works for me using Groovy and the library in my hello-world-lib repo. :slight_smile:

E.g. I created the following test.groovy file:

import com.acme.*;

HelloWorld.log();
HelloWorld.print();

PinguinSay.log("I just came by to say that using a Java library in your openHAB rules is very easy once the code is part of an OSGi bundle!");
PinguinSay.print("I just came by to say that using a Java library in your openHAB rules is very easy once the code is part of an OSGi bundle!");

When I save the file it prints/logs “Hello World!” using the library and there is even a penguin talking to me!

openhab> log:set info com.acme.PinguinSay
openhab> log:set info com.acme.HelloWorld
openhab> log:tail
20:12:37.779 [INFO ] [hab.core.service.AbstractWatchService] - Loading script '/home/wouter/software/openhab/instance/conf/automation/jsr223/test.groovy'
20:12:37.787 [INFO ] [com.acme.HelloWorld                  ] - Hello World!
Hello World!
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] -  _________________________________________
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] - / I just came by to say that using a Java \
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] - | library in your openHAB rules is very   |
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] - | easy once the code is part of an OSGi   |
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] - \ bundle!                                 /
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] -  -----------------------------------------
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] -    \
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] -     \
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] -         .--.
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] -        |o_o |
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] -        |:_/ |
20:12:37.788 [INFO ] [com.acme.PinguinSay                  ] -       //   \ \
20:12:37.789 [INFO ] [com.acme.PinguinSay                  ] -      (|     | )
20:12:37.789 [INFO ] [com.acme.PinguinSay                  ] -     /'\_   _/`\
20:12:37.789 [INFO ] [com.acme.PinguinSay                  ] -     \___)=(___/
 _________________________________________
/ I just came by to say that using a Java \
| library in your openHAB rules is very   |
| easy once the code is part of an OSGi   |
\ bundle!                                 /
 -----------------------------------------
   \
    \
        .--.
       |o_o |
       |:_/ |
      //   \ \
     (|     | )
    /'\_   _/`\
    \___)=(___/

But that’s all synchonous

The pinguin is also able to tell me the time every 30s:

import com.acme.*;
import java.time.ZonedDateTime;
import java.util.concurrent.TimeUnit;
import org.openhab.core.common.ThreadPoolManager;

ThreadPoolManager
    .getScheduledPool("pinguin")
    .scheduleWithFixedDelay(
        () -> PinguinSay.log("The time is: " + ZonedDateTime.now()
    ), 0, 30, TimeUnit.SECONDS);
21:02:33.510 [INFO ] [hab.core.service.AbstractWatchService] - Loading script '/home/wouter/software/openhab/instance/conf/automation/jsr223/test.groovy'
21:02:33.536 [INFO ] [com.acme.PinguinSay                  ] -  _________________________________________
21:02:33.536 [INFO ] [com.acme.PinguinSay                  ] - / The time is:                            \
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] - \ 2020-10-22T21:02:33.531784+02:00[Europe//
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -  -----------------------------------------
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -    \
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -     \
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -         .--.
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -        |o_o |
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -        |:_/ |
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -       //   \ \
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -      (|     | )
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -     /'\_   _/`\
21:02:33.537 [INFO ] [com.acme.PinguinSay                  ] -     \___)=(___/
21:03:03.538 [INFO ] [com.acme.PinguinSay                  ] -  _________________________________________
21:03:03.539 [INFO ] [com.acme.PinguinSay                  ] - / The time is:                            \
21:03:03.539 [INFO ] [com.acme.PinguinSay                  ] - \ 2020-10-22T21:03:03.538364+02:00[Europe//
21:03:03.540 [INFO ] [com.acme.PinguinSay                  ] -  -----------------------------------------
21:03:03.541 [INFO ] [com.acme.PinguinSay                  ] -    \
21:03:03.541 [INFO ] [com.acme.PinguinSay                  ] -     \
21:03:03.542 [INFO ] [com.acme.PinguinSay                  ] -         .--.
21:03:03.542 [INFO ] [com.acme.PinguinSay                  ] -        |o_o |
21:03:03.543 [INFO ] [com.acme.PinguinSay                  ] -        |:_/ |
21:03:03.543 [INFO ] [com.acme.PinguinSay                  ] -       //   \ \
21:03:03.543 [INFO ] [com.acme.PinguinSay                  ] -      (|     | )
21:03:03.543 [INFO ] [com.acme.PinguinSay                  ] -     /'\_   _/`\
21:03:03.543 [INFO ] [com.acme.PinguinSay                  ] -     \___)=(___/

But the threading is part of my Java-Classes in my case. Can your pinguin do this in java?

Something like this?

import com.acme.*;
import java.time.Duration;

PinguinSay.asyncLog([
    'I am simple Java based creature...',
    'So all the laws that govern the Java world also apply to me!' 
], Duration.ofSeconds(5));
23:30:59.445 [INFO ] [com.acme.PinguinSay                  ] -  _________________________________________
23:30:59.446 [INFO ] [com.acme.PinguinSay                  ] - < I am simple Java based creature...      >
23:30:59.447 [INFO ] [com.acme.PinguinSay                  ] -  -----------------------------------------
23:30:59.447 [INFO ] [com.acme.PinguinSay                  ] -    \
23:30:59.448 [INFO ] [com.acme.PinguinSay                  ] -     \
23:30:59.449 [INFO ] [com.acme.PinguinSay                  ] -         .--.
23:30:59.449 [INFO ] [com.acme.PinguinSay                  ] -        |o_o |
23:30:59.450 [INFO ] [com.acme.PinguinSay                  ] -        |:_/ |
23:30:59.450 [INFO ] [com.acme.PinguinSay                  ] -       //   \ \
23:30:59.450 [INFO ] [com.acme.PinguinSay                  ] -      (|     | )
23:30:59.450 [INFO ] [com.acme.PinguinSay                  ] -     /'\_   _/`\
23:30:59.450 [INFO ] [com.acme.PinguinSay                  ] -     \___)=(___/
23:31:04.451 [INFO ] [com.acme.PinguinSay                  ] -  _________________________________________
23:31:04.451 [INFO ] [com.acme.PinguinSay                  ] - / So all the laws that govern the Java    \
23:31:04.452 [INFO ] [com.acme.PinguinSay                  ] - \ world also apply to me!                 /
23:31:04.452 [INFO ] [com.acme.PinguinSay                  ] -  -----------------------------------------
23:31:04.452 [INFO ] [com.acme.PinguinSay                  ] -    \
23:31:04.453 [INFO ] [com.acme.PinguinSay                  ] -     \
23:31:04.453 [INFO ] [com.acme.PinguinSay                  ] -         .--.
23:31:04.453 [INFO ] [com.acme.PinguinSay                  ] -        |o_o |
23:31:04.453 [INFO ] [com.acme.PinguinSay                  ] -        |:_/ |
23:31:04.453 [INFO ] [com.acme.PinguinSay                  ] -       //   \ \
23:31:04.453 [INFO ] [com.acme.PinguinSay                  ] -      (|     | )
23:31:04.454 [INFO ] [com.acme.PinguinSay                  ] -     /'\_   _/`\
23:31:04.454 [INFO ] [com.acme.PinguinSay                  ] -     \___)=(___/

Could you spot the difference to my code?

Your executor is just a local variable so the JVM will likely garbage collect it as soon as the start() method exits:

Garbage collection does not seem to be a problem.
I fixed the code by implementing the ActivitySupplier

class MyActivitySupplier(ActivitySupplier):
    def supplyInput(self):
        return True

So that

brightnessCalculator = LEDBrightnessCalculator(history, myActivitySupplier, logOutputDevice)

The previous lambda at the second parameter did nit conform the ActivitySupplier of course. But there never was an error.

Does it work properly now just like my penguin?

Yes it prints the calculations done in java. I did not changed something about the ScheduledExecutor etc. Just the Jython code.
But I’m not really happy with this. Whenever I change something, place the new jar file there then the Logic/Outputs runs twice/multiple once (old and new). Sometimes I have to restart openhab to pick up changed classes. When I restart openhab then the rules engine is uninstalled again. I’m sure I will use this functionality in the future for some static calculations or mappings. Thanks for your help!
I’m currently looking into binding development to see if this is a better suit.

OK great! Yes I also noticed it did not pick up my changes sometimes. But it’s nice to have this as an alternative if you already have some Java code or want to write it instead of doing it all in scripts. :slight_smile:

With java i recommend you write junit testcases for your code. That way you don’t have to restart that often, if you can test it and sort the bugs before you deploy it. I’ve noticed the same behavior when developing bindings.

/s

Wrote a simple binding, that provides a thing and updates a channel. It allows items to link to it of course. Works perfectly and integrates well into my java project. Lifecycle methods also work this way and I simply delegate dispose to ExecutorService#shutdownNow(). This allows swapping the jar flawlessly.