Use own java classes in rules

I have developed some classes, integrated them in a simulation and visualisation as preparation to use them in openhab. Is it possible to use own classes from a jar?
I try the following:

import my.pkg.LEDBrightnessCalculator;

rule "Test PIR LEDBrightnessCalculator"
when
    Time cron "/5 * * ? * * *"
then
    logInfo("LEDBrightnessCalculator", "1")
    var calc = new LEDBrightnessCalculator(new History(), null, null)
    logInfo("LEDBrightnessCalculator", "2")
    calc.start()
end

An the log is:

[INFO ] [model.script.LEDBrightnessCalculator] 1
[ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Test PIR LEDBrightnessCalculator': An error occurred during the script execution: null

It’s the same output for

//import my.pkg.LEDBrightnessCalculator;

rule "Test PIR LEDBrightnessCalculator"
when
    Time cron "/5 * * ? * * *"
then
    logInfo("LEDBrightnessCalculator", "1")
    var calc = new LEDBrightnessCalculator(null, null, null)
    logInfo("LEDBrightnessCalculator", "2")
    calc.start()
end
import my.pkg.LEDBrightnessCalculator;

rule "Test PIR LEDBrightnessCalculator"
when
    Time cron "/5 * * ? * * *"
then
    logInfo("LEDBrightnessCalculator", "1")
    var calc = new NotEvenMyClassButDoesNotEvenChangeAThing(null, null, null)
    logInfo("LEDBrightnessCalculator", "2")
    calc.start()
end

I packed my library as .kar, put it into the addons directory and it’s picked up by the runtime:

Found a .kar file to deploy.

2020-10-05 12:18:18.272 [INFO ] [af.deployer.kar.KarArtifactInstaller] - Installing KAR file /usr/share/openhab2/addons/led-brightness-calculation-core-1.0.0-SNAPSHOT.kar
2020-10-05 12:18:18.550 [DEBUG] [ps4j.pax.url.mvn.internal.Connection] - Resolving [mvn:de.no3x/led-brightness-calculation-core/1.0.0-SNAPSHOT/xml/features]
2020-10-05 12:18:18.599 [INFO ] [internal.service.FeaturesServiceImpl] - Adding features: led-brightness-calculation-core/[1.0.0.SNAPSHOT,1.0.0.SNAPSHOT]
2020-10-05 12:18:18.607 [DEBUG] [org.apache.felix.configadmin        ] - getConfiguration(pid=org.ops4j.pax.url.mvn, location=null)

I don’t think this is possible. You can if those classes were written in Python 2, JavaScript, or Groovy when using the NGRE (OH 2.5, will just become the rules engine in OH 3). I believe it’s possible to even write rules using Java (I know of one developer who has done that using the NGRE and JSONDB rules). But I don’t know if there is a way to add your own Java classes to the context where Rules run.

You would only be able to do this using the new rule engine, as a feature/side-effect of using DynamicImport-Package, which was not used by the old rule engine (removed in OH 3)…

Thank you. If I understand right then installing NGRG should solve the issue. I tried but it does not change a thing.
I see that you are a contributor to this .bnd stuff I have never heard before. Maybe you know how I need to configure my .kar/OSGi bundle to make it work? I just use the following:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>de.no3x</groupId>
        <artifactId>led-brightness-calculation</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>led-brightness-calculation-core</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>kar</packaging>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>27.1-jre</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.6.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.16.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.karaf.tooling</groupId>
                    <artifactId>karaf-maven-plugin</artifactId>
                    <version>4.2.9</version>
                    <extensions>true</extensions>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>karaf-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Or should it work with jar out of the box?

Can I get more information than:
Error during the execution of rule 'Test PIR LEDBrightnessCalculator': An error occurred during the script execution: null? I used log:set trace in karaf but it’s not more verbose about this error.

In OH2, the rules DSL uses the old rule engine. You will need to install the new rule engine and a supported scripting language to access your class. I recommend Jython…

Yes, Jython can use Java classes. :slightly_smiling_face:

If you post a link to a repo with your code and provide an explanation of what you are attempting to accomplish, I will take a look.

I tried to install jython and NGRG following the guide from https://openhab-scripters.github.io/openhab-helper-libraries/Getting%20Started/Installation.html

[21:52:50] openhabian@openhabianpi:/etc/openhab2/automation/jsr223/python/personal$ tree ${OPENHAB_CONF}/automation
/etc/openhab2/automation
├── jsr223
│   ├── groovy
│   │   ├── community
│   │   │   └── README.md
│   │   └── personal
│   │       └── README.md
│   ├── javascript
│   │   ├── community
│   │   │   └── README.md
│   │   └── personal
│   │       └── README.md
│   └── python
│       ├── community
│       │   └── README.md
│       ├── core
│       │   ├── 000_startup.py
│       │   └── components
│       │       ├── 100_DirectoryTrigger.py
│       │       ├── 100_OsgiEventTrigger.py
│       │       ├── 100_StartupTrigger.py
│       │       ├── 200_JythonBindingInfoProvider.py
│       │       ├── 200_JythonExtensionProvider.py
│       │       ├── 200_JythonItemChannelLinkProvider.py
│       │       ├── 200_JythonItemProvider.py
│       │       ├── 200_JythonThingProvider.py
│       │       ├── 200_JythonThingTypeProvider.py
│       │       └── 200_JythonTransform.py
│       └── personal
│           ├── hello_world.py
│           └── README.md
├── jython
│   └── jython-standalone-2.7.2.jar
└── lib
    ├── groovy
    │   ├── community
    │   │   └── README.md
    │   ├── configuration.groovy.example
    │   ├── core
    │   │   └── README.md
    │   └── personal
    │       └── README.md
    ├── javascript
    │   ├── community
    │   │   └── README.md
    │   ├── configuration.js.example
    │   ├── core
    │   │   ├── actions.js
    │   │   ├── conditions.js
    │   │   ├── metadata.js
    │   │   ├── osgi.js
    │   │   ├── PersistenceExtensions.js
    │   │   ├── rules.js
    │   │   ├── triggers.js
    │   │   └── utils.js
    │   └── personal
    │       └── README.md
    └── python
        ├── community
        │   └── __init__.py
        ├── configuration.py.example
        ├── core
        │   ├── actions.py
        │   ├── date.py
        │   ├── __init__.py
        │   ├── items.py
        │   ├── jsr223.py
        │   ├── links.py
        │   ├── log.py
        │   ├── metadata.py
        │   ├── osgi
        │   │   ├── events.py
        │   │   └── __init__.py
        │   ├── rules.py
        │   ├── testing.py
        │   ├── triggers.py
        │   └── utils.py
        └── personal
            └── __init__.py.example

27 directories, 51 files

But there is no output of the hello world example but an error:
ImportError: No module named configuration in <script> at line number 5

Sounds like you skipped step 8… https://openhab-scripters.github.io/openhab-helper-libraries/Getting%20Started/Installation.html#core. I have an update that will prevent this in the future.

Gosh you are right. It works now. Hello World ist printed periodically. And now I can use my classes from the jar theoretically? But it’s python code. :thinking:

It’s Jython code…

… and you can use Java in Jython, and Jython in Java.

Correct. Try importing your class into a test script. You may need to put your jar in the python.path though (copy it to $OPENHAB_CONF/automation/lib/personal/).

I have put my jar there.
Could you help me with the jython code? I tried this:

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

from core.rules import rule
from core.triggers import when
from de.no3x.core.history import StringHolder

@rule("Jython Hello World (cron decorators)", description="This is an example cron triggered rule using decorators", tags=["Test tag", "Hello World"])# description and tags are optional
@when("Time cron 0/10 * * * * ?")
def hello_world_cron_decorators(event):
    hello_world_cron_decorators.log.info("Hello World!")
    new StringHolder()

But get

 Error during evaluation of script 'file:/etc/openhab2/automation/jsr223/python/personal/hello_world.py': SyntaxError: no viable alternative at input 'StringHolder' in <script> at line number 13 at column number 8

I need to learn this jython syntax if working with my java classes works this way.
The source of the StringHolder is just a stupid class for testing:

package de.no3x.core.history;

public class StringHolder {
	private final String string;

	public StringHolder() {
		System.out.println("StringHolder()");
		this.string = "";
	}

	public StringHolder(String string) {
		System.out.println("StringHolder("+string+")");
		this.string = string;
	}

	public String getString() {
		System.out.println("StringHolder.getString()");
		return string;
	}

}

Instead of…

… just use…

StringHolder()

Also, there’s no need to be adding this to a rule. You can simplify your testing by just running a script…

from de.no3x.core.history import StringHolder

StringHolder()

I suggest putting personal scripts into $OPENHAB_CONF/automation/jsr223/personal/… https://openhab-scripters.github.io/openhab-helper-libraries/Getting%20Started/File%20Locations.html.

There wouldn’t be a need for the Java classes… you can make your classes using Jython.

But the point was to use my previously developed classes.

I get
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

from de.no3x.core.history import StringHolder

s = StringHolder("f")

print "s:", s.getString();

Try restarting OH or reloading the package. Did you try putting the jar in /runtime/lib/ext/?

Loading the JAR from /runtime/lib/ext/ works.

from de.no3x.core.history import StringHolder
from core.actions import LogAction

s = StringHolder("f")
LogAction.logInfo("HelloWorld", s.getString())

Logs f. :partying_face:

But in my real code (e.g. History) I use a bunch of dependencies like guava. This does not seem to work.
hello_world.py': java.lang.NoClassDefFoundError: java.lang.NoClassDefFoundError: com/google/common/collect/EvictingQueue in <script> at line number 14

I build a fat jar and the dependencies are included but this does not help.

 <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

I put my used libraries also in /runtime/lib/ext/ and now the code executes. But I don’t feel like this is a good solution to put libraries manually there.

Then I tried to actually use my classes:

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()
public LEDBrightnessCalculator(History history, ActivitySupplier activitySupplier, BrightnessConsumer brightnessConsumer) {
public interface BrightnessConsumer {
	void output(double brightness);
}

But It does not output anything. No error… no warning. This does not make fun.

Why not? This is what that directory exists for.

I mistyped this by leaving out a directory! It should have been $OPENHAB_CONF/automation/lib/python/personal/. You would then import your libraries with…

from personal.de.no3x.core.history import History

Here is an old example where jars are being used.

Is this part of a Jython script? If so, this will not work and needs to be migrated to Jython. You can use Java objects in Jython, but you still need to write the code in Jython, It should throw errors from under org.eclipse.smarthome.automation. Look through the setup steps again, since they include setup of logging here. If you are not catching exceptions in your libraries, then they will be eaten. Also, setup VS Code and use pylint.

Because that’s an awful 2007 workflow if you manually need to keep track of your jars and move them around manually.

If I copy them there then I get the message
hello_world.py’: ImportError: No module named de in at line number 5

I extracted my jar there and with imports

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

I get:

hello_world.py': ImportError: No module named de in <script> at line number 5

Moved the de directory to $OPENHAB_CONF/automation/lib/python/ and now it’s found with the imports:

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

LEDBrightnessCalculator and BrightnessConsumer are part of my library (Java)
and the jython code is my attempt to use it:

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()

I executed
log:set DEBUG org.openhab.core.automation log:set DEBUG org.eclipse.smarthome.automation log:set DEBUG jsr223 already yesterday.

Did you do step 9 of the installation instructions?

That directory is setup as a Python package, but I recommend not putting anything in there. It will be messy… especially when updating the helper libraries.

What is returned in the console with log:get?

I really won’t be able to help much further without seeing the contents of your libraries.

yes

yes… but the import did not work in personal.

DEBUG of course

I will create an minimal example to reproduce.:
You can find it at https://github.com/No3x/openhab-jython-java-lib-problem

@5iver did you get a chance to look at the code?
Thanks for your help