JSR223 Jython Openhab Imports Erroring?

I have checked out Steve’s repo as /etc/openhab2/lib/openhab2-jython and then linked it into the lib path with

cd /etc/openhab2/lib/python
ln -s ../openhab2-jython/lib/openhab openhab

That should work for you as well if your config lives at /etc/openhab2/

1 Like

Thanks, yeah, I’ve re-downloaded the lib and script files and put them in their respective places and it’s no longer complaining…I maybe tried to apply too many changes at once and got in a pickle…I thought I had covered everything the first time around but maybe not…I am not seeing errors at all with everything from Steve Bates repo in place :slight_smile:

Now to get my head around getting rules setup, I have existing rules done the old fashion way that I’m gonna try to “port” over making things a little more modular and efficient lol

thanks

I spoke too soon…I figured a simple example would set me straight…

Running this where I have a test switch in place in items and visible through a sitemap:

from openhab.triggers import time_triggered, EVERY_MINUTE

@time_triggered(EVERY_MINUTE)
def my_periodic_function():
events.postUpdate(“TestSwitch”, “On”)

I don’t see any changes to switch state and I got this in the console (but not log):

Traceback (most recent call last):
File “D:\openHAB2\conf\lib\python\openhab\triggers.py”, line 115, in execute
self.callback(module, inputs) if self.extended else self.callback()
File “”, line 5, in my_periodic_function
IllegalArgumentException: java.lang.IllegalArgumentException: The argument ‘state’ must not be null or empty.

Am I not setting state to “On”?

The switch item is simple:

Switch TestSwitch “Test Switch”

Any good examples anywhere of using Steve Bates scripts to good use?

EDIT: Okay so I can get things happening but still get the above errors, looks like I need to ignore them for now…

Be sure to not miss Heating Boilerplate - A Universal Temperature Control Solution with Modes

While the examples are Rules DSL, the concepts will still be useful.

I’d be interested to know what problems you faced.

I take this to mean that you are seeing “Hello world” or somesuch in openhab.log proving that your scripts are being successfully loaded and executed and your main problem is with libraries, correct?

So the openhab module is Steve’s library. You cannot access it without first installing his library.

How did you add them? I’ve not gone as far as downloading his libraries yet. I’ve only played around with the RAW ESH API and even that minimally. However, reading the instructions here indicates that the library needs to go in the lib directory and your scripts need to go in the scripts directory.

Thanks for the link to the example, I’ll take a look. I have something working with the standard script, I’m using json to define the schedule, and applying this to my “zones”, I’ve put the rules file contents at the end of the post (item names and what they relate to should be self explanatory)…oh and ignore all the ; on the end of lines, bad habit which doesn’t hurt the script running :slight_smile:

But I want to do something along these lines but with something more aligned to doing it…I found that I could get things done with the standard script but it wasn’t the most efficient (looping more than I’d like) and the work arounds for finding an item in a group event are not the best…lets not talk about val versus var and data types…coming from a java/c# dev background it drives me nuts :slight_smile:

I am fairly confident with python so I figured I would try and get into that instead and give myself more freedoms with approach and having the ability to import whatever from the python side that might be helpful etc.

As for my issues, I think I was a bit too soon to raise problems, I was getting a little frustrated with getting things sorted…after a days work in c#/mssql getting into this is obviously taking it’s toll on me :slight_smile: I’ve gotten over most of the issues now though I think and just need to get my head around this new approach…one thing I have learnt is it’s best to have a debug console running for testing and to kill it and clear log text before rerunning again after any significant changes made.

Hopefully once I’ve gotten my head into all this a bit more I can contribute too, I think once I have something working (using virtual switches rather than z-wave) I’ll post it up for review/dialogue/example/whatever in case it helps some.

import org.joda.time;
import java.io;

rule Startup
when
System started
then
println(“Loading HeatingControl Rules”)
end

rule "Zone Heat"
when
Item gTemperature received update or Item gThermostat received update
then

  val Number deadzone = 0.5;
  
  gRoom.members.forEach [room |
  	
  	println("*** Processing zone for '" + room.name + "'")

  	val zone = gZoneHeat.members.findFirst[name.equals("iZoneHeat_" + room.name)];
  	val temp = gTemperature.allMembers.findFirst[name.equals("iTemperature_" + room.name)];
  	val therm = gThermostat.allMembers.findFirst[name.equals("iThermostat_" + room.name)];

  	if (temp.state != NULL && therm.state != NULL) {

  		val Number actual = temp.state;
  		val Number target = therm.state;
  		
  		if ((actual + deadzone) < target)
  		{
  			println(" ** This zone requires heat, refreshing...")
  			zone.sendCommand(ON);
  		}
  		else
  		{
  			if ((actual - deadzone) > target)
  			{
  				println(" ** This zone does not require any heat, refreshing...")
  				zone.sendCommand(OFF);
  			}
  			
  		}
  	}

  ]

end

rule "Boiler Control"
when
Item gZoneHeat received update
then

  gZoneHeat.members.filter(zone|zone.state != NULL).forEach [zone |

  	if (zone.state == ON)
  	{
  		println(" ** Requesting boiler to provide heating")
  		iBoiler_Heating.sendCommand(ON);
  		return;
  	}
  ]

  println(" ** Requesting boiler to not provide heating")
  iBoiler_Heating.sendCommand(OFF);

end

rule "Update Thermostats"
when
Time cron "0 */15 * * * ?"
or Item TestSwitch received update
then

  var Number starthour;
  var Number startminute;
  var DateTime starttime;
  var DateTime endtime;
  var Number endhour;
  var Number endminute;
  var Number targettemp; 
  var Number defaulttemp;
  var Number entrycount;
  var Number counter;
  var process = true;
  var String thermjson;
  var Number currenttemp;
  var String schedule = '
  	{
  	"schedule": {
  		"name": "heating",
  		"thermostat": [
  		{
  			"name": "iThermostat_gLivingRoom",
  			"defaulttemp": 10,
  			"entry": [
  			{"starthour":6,"startminute":0,"endhour":7,"endminute":30,"targettemp":20},
  			{"starthour":16,"startminute":30,"endhour":21,"endminute":0,"targettemp":20}
  			]
  		},
  		{
  			"name": "iThermostat_gKitchen",
  			"defaulttemp": 10,
  			"entry": [
  			{"starthour":6,"startminute":0,"endhour":7,"endminute":30,"targettemp":20},
  			{"starthour":16,"startminute":30,"endhour":18,"endminute":0,"targettemp":20}
  			]
  		},
  		{
  			"name": "iThermostat_gOrangery",
  			"defaulttemp": 10,
  			"entry": [
  			{"starthour":16,"startminute":30,"endhour":19,"endminute":30,"targettemp":20}
  			]
  		},
  		{
  			"name": "iThermostat_gBedroomFrontLeft",
  			"defaulttemp": 10,
  			"entry": [
  			{"starthour":6,"startminute":0,"endhour":7,"endminute":0,"targettemp":20},
  			{"starthour":20,"startminute":00,"endhour":21,"endminute":30,"targettemp":20}
  			]
  		},
  		{
  			"name": "iThermostat_gBedroomFrontRight",
  			"defaulttemp": 10,
  			"entry": [
  			{"starthour":6,"startminute":0,"endhour":7,"endminute":0,"targettemp":20},
  			{"starthour":21,"startminute":00,"endhour":22,"endminute":30,"targettemp":20}
  			]
  		},
  		{
  			"name": "iThermostat_gBedroomBackRight",
  			"defaulttemp": 10,
  			"entry": [
  			{"starthour":6,"startminute":0,"endhour":7,"endminute":0,"targettemp":20},
  			{"starthour":19,"startminute":30,"endhour":20,"endminute":30,"targettemp":20}
  			]
  		}				
  		]
  	}
  	}'

  gThermostat.members.forEach[therm|
  	
  	process = true;
  	thermjson = transform("JSONPATH", "$..[?(@.name == '"+therm.name+"')]", schedule)

  	if (thermjson.length() > 2)
  	{

  		println("*** Processing matching schedule for '" + therm.name + "'")

  		defaulttemp = Integer::parseInt(transform("JSONPATH", "$..defaulttemp", thermjson).replace('[', '').replace(']', ''))
  		entrycount = Integer::parseInt(transform("JSONPATH", "$..entry.length()", thermjson).replace('[', '').replace(']', ''))
  		currenttemp = therm.state
  		if (currenttemp == NULL) { currenttemp = 0}

  		counter = 0
  		while (process == true) {		

  			if (entrycount == counter)
  			{
  				println(" ** No time matches found for '" + therm.name + "' at all, attempting to revert to the default temp")
  				if (currenttemp != defaulttemp)
  				{
  					println("  * Requesting thermostat to update to the default temp of " + defaulttemp + "°C");
  					therm.sendCommand(defaulttemp);
  				}
  				else
  				{
  					println("  * Thermostat already matches the default temp of " + defaulttemp + "°C");
  				}
  				process = false;
  			}

  			if (process == true)
  			{

  				starthour = Integer::parseInt(transform("JSONPATH", "$..entry[" + counter + "].starthour", thermjson).replace('[', '').replace(']', ''))
  				startminute = Integer::parseInt(transform("JSONPATH", "$..entry[" + counter + "].startminute", thermjson).replace('[', '').replace(']', ''))
  				endhour = Integer::parseInt(transform("JSONPATH", "$..entry[" + counter + "].endhour", thermjson).replace('[', '').replace(']', ''))
  				endminute = Integer::parseInt(transform("JSONPATH", "$..entry[" + counter + "].endminute", thermjson).replace('[', '').replace(']', ''))
  				targettemp = Integer::parseInt(transform("JSONPATH", "$..entry[" + counter + "].targettemp", thermjson).replace('[', '').replace(']', ''))

  				starttime = now.withTime(starthour, startminute, 0 ,0);
  				endtime = now.withTime(endhour, endminute, 0 ,0);

  				if (now > starttime && now < endtime)	
  				{
  					println(" ** Found time match for '" + therm.name + "' (" + starttime.toString("HH:mm") + " => " + endtime.toString("HH:mm") + ")");
  					if (currenttemp != targettemp)
  					{
  						println("  * Requesting thermostat to update to the target temp of " + targettemp + "°C");
  						therm.sendCommand(targettemp)
  					}
  					else
  					{
  						println("  * Thermostat already matches the target temp of " + targettemp + "°C");
  					}
  					process = false;
  				}
  				else
  				{
  					println(" ** No time match found for '" + therm.name + "' (" + starttime.toString("HH:mm") + " => " + endtime.toString("HH:mm") + ")")
  				}

  				counter=counter+1;
  			}					
  		}
  	}

  ]

end

I’d be interested to see the code. I’m not pushing the Rules DSL but I probably am one of the few experts on them running around. I usually find that looping like that is caused more by not taking advantage of events and/or Timers and have less to do with a limitation of the language.

Are you referring to identifying the Item that triggered a Rule using lastUpdate? Or just generally MyGroup.members.filter[i.name == "SomeName"].head? Because the latter isn’t some workaround, it is a core feature of the language. The former is indeed a work-around though. :frowning: I’ve never liked it but it works reasonably well.

val is identical to final in Java. I don’t understand why you would have a problem with that.

I actually do recommend developers go with JSR223 because they tend to try to force the Rules DSL to bend to the Java/C#/C/Python/Name your language approach than to try and learn and take advantage of the way the Rules DSL wants to do things. But be aware that the JSR223 binding is still very much beta, perhaps even alph at this stage. You will have to figure a lot out on your own. There simply isn’t that much documentation on it yet and I’m not sure it is feature complete with the OH 1.x JSR223 yet. Plus there is a looming issue where JSR223 goes away in Java 1.9 and is replaced by something else.

Multitail is fantastic for this. See this thread to get color coding for OH logs.

You can just hit <enter> and get a big read line across all the logs you are following. And it lets you connect to a remote machine and tail a log there over ssh.

That would be fantastic. We LOVE examples and I’m very happy to help make people’s rules more effecient.

To help you get your head around the Rules DSL way of wanting you to do things, look through the Design Pattern postings.

Here are a few comments on your code thus far:

  • Use code fences. Either highlight your code and press the </> button at the top of the post enter text area or use three ``` at the top and the bottom of your code and it will retain indentation and add some syntax highlighting.

  • You don’t need to import anything from joda in OH 2.

  • Your Zone Heat probably only needs to run when either gTemperature or gThermostat change. Assuming you set up your gTemperature and gThermostat groups such that they will change when any of their members change (e.g. AVG would probably do it) this is one of the cases where using changed on a Group would work as expected.

  • I always forget about findFirst. That is so much better than using a filter like I did above. Thanks for reminding me.

  • Use the log actions rather than println and your logs will go to openhab.log. You can even configure the logs to go to their own file if you want. See http://docs.openhab.org/administration/logging.html#create-log-entries-in-rules

    logInfo("logname", "message")

  • Your “Zone Heat” rule looks pretty tight. I would write it like the following though it is not significantly different and any mneaningful way.

    val deadzone = 0.5 // I would probably put this into an Item with restoreOnStartup so I could adjust it as desired on the sitemap

    gRoom.members.forEach[room |
        logInfo("heating", "Processing zone for '" + room.name + "'")

        val zone = gZoneHeat.members.findFirst[name.equals("iZoneHeat_" + room.name)]
        val actual = gTemperature.allMembers.findFirst[name.equals("iTemperature_" + room.name)].state
        val target = gThermostat.allMembers.findFirst[name.equals("iThermostat_" + room.name)].state
        
        // Fail fast and avoid unnecessary nesting
        if(actual == NULL || target == NULL) {
            logWarn("heating", "Heating temp and target are not initialized: temp = " + temp.state + " therm = " + therm.state)
            return false; // this is one place the ; actually means something
        }

        val diff = target as Number - actual as Number

        if(diff > deadzone) {
            logInfo("heating", "Current temp is more than deadzone lower than target, turning on the heat")
            zone.sendCommand(ON)
        }
        else if(diff < 0){
            logInfo("heating", "Current temp is more than deadzone higher than target, turning off the heat")
            zone.sendCommand(OFF)
        }
        // else do nothing
    ]

It has fewer nestings and therefore would have a lower Mccabe complexity rating. It uses the built-in logging. And it avoids some unnecessary variables by jumping straight to storing the state in a var instead of grabbing the Items first in one val and then its state in another.

Again, it is questionable whether this is an improvement or not. It might be a few lines of code shorter.

  • While not really a problem, it is usually good practice to avoid sendCommand to an Item if it is already in that state. Some devices it can cause problems for but over all it clutters your logs and can unnecessarily trigger some rules and such.

  • “Boiler Control” could be shortened with another filter to

    val numOn = gZoneHeat.members.filter[zone|sone.state == ON].size
    val newCommand = if(numOn > 0) ON else OFF

    if(iBoiler_Heating.state != newCommand) {
        logInfo("heating", "Requesting boiler to turn " + newCommand)
        iBoiler_Heating.sendCommand(newCommand)
    }

Alternatively, if you define gZoneHeat using Group:Switch:OR(ON,OFF) gZoneHeat than gZoneHeat’s state will be ON if any member is ON and the rule would become:

when
    Item gZoneHeat changed
then
    logInfo("Heating", "Requesting boiler to turn " + gZoneHeat.state)
    iBoiler_Heating.sendCommand(gZoneHeat.state)
end

We are trusting that gZoneHeat and iBoiler_Heating will always stay in sync so don’t need to test if iBoiler_Heating is already in the right state.

Note, this will also keep the rule from triggering a bunch of times if more than one Zone needs heat as gZoneHeat will only change when the first zone turns ON or ALL zones turn OFF.

I do think this is an improvement in both number of lines of code and in the code’s behavior.

  • I usually recommend the Design Pattern: Time Of Day for doing the sort of schedule modeling you do in “Update Thermostats” This separates the code so you can easily change things up and it lets you use the same time based states in other rule areas (e.g. lighting). Finally, it lets you take advantage of other events like presence and Astro events more easily. Since you are hard coding the schedule anyway in a JSON string, you can use cron triggers to drive it rather than a looping 15 minute poll.

It will take some modification of the example of the DP but should be pretty easy, especially since you are a coder and probably have seen and used design patterns before.

  • Having said the above, you can also save your JSON to a text file and load it into your rule with:

    val schedule = executeCommandLine("cat /path/to/schedule", 1000) // or equivalent if running on Windows

  • Personally, what I would do is store your default temp, start, and end times in Number and DateTime Items. You can populate them in a System started Rule to start and use restoreOnStartup to persist their values if desired and get rid of the System started Rule. Then if you name them using Design Pattern: Associated Items (which you’ve already done above in “Zone Heat”) the rule becomes


    // loop through each thermostat
    gThermostat.members.forEach[therm |

        // Get a list of all the start times for this thermostat
        val startTimes = gScheduleItems.members.filter[i|i.name.startsWith(therm.name+"_StartTime"]

        // Loop through each start time
        startTimes.forEach[start |

            // Get this start time's number
            val num = start.name.substring(therm.name+"_StartTime".length) // may be off by one, double check

            // Parse and get the actual startTime
            val startTimeStr = parseInt(start.state.toString).split(":")            
            val startHours = Integer::parseInt(startTimeStr.get(0))
            val startMins = Integer::parseInt(startTimeStr.get(1))
            val startTime = now.withTime(startHours, startMins, 0, 0)
        
            // Parse and get the actual endTime
            val endTimeStr = gEndTimes.members.findFirst(therm.name+"_EndTime"+num).state.toString.split(":")
            val endHours = Integer::parseInt(endTimeStr.get(0))
            val endMins = Integer::parseInt(endTimeStr.get(1))
            val endTime = now.withTime(endHours, endMins, 0, 0)

            // If now is between startTime and endTime
            if(now.isAfter(startTime) && now.isBefore(endTime)) { // spanning midnight is a special case that need special handling

                // Get the target temp
                var targetItem = gTargetTemps.members.findFirst(therm.name+"_Target"+num)

                // No target temp, fall back to Default
                if(targetItem == null) {
                    logWarn("heating", "Could not find " + therm.name+"_Target"+num+" attempting to use default")
                    targetItem = gTargetTemps.members.findFirst(therm.name+"_Default"+num)

                    // No Default, fall back to hard coded default
                    if(targetItem == null) {
                          logWarn("heating", "Could not find " + therm.name+"_Default"+num+" using 10")
                    }
                }

                var targetTemp = if(targetItem == null) 10 else targetItem.state as Number

                if(therm.state as Number != targetTemp) therm.sendCommand(targetTemp)
                else logInfo("heating", therm.name + " is already at the target temp")
            }

        ]
    ]

I do think this is an improvement on the original. It has several levels less nesting. I THINK it does the same thing your current one does. And it is far fewer lines of code. You can change the schedule without modifying the Rule and since everything is stored in Items you don’t have as much error checking that you have to do. It does come at the cost of a host of new Items to create and populate though.

However, I would probably not implement it myself like that above. Instead, I would probably create separate cron triggered rules for each start/end/zone and use a lambda to encapsulate the if(currTarget != new target) logic. Since your schedule is hard-coded anyway you can eliminate almost all of the looping and filtering and parsing of date time strings in exchange for a bunch of new, but relatively simple rules.

val Functions$Function2<SwitchItem, Number, Boolean> heating = [therm, tgt |
    if(therm.state as Number != targetTemp) therm.sendCommand(targetTemp)
    else logInfo("heating", therm.name + " is already at the target temp")
    true
]

rule "iThermostat_gLivingRoom start 1"
when
    Time cron "0 0 6 * * ?"
then
    heating.apply(iThermostat_gLivingRoom, 20)
end

rule "iThermostat_gLivingRoom end 1"
when
    Time cron "0 30 7 * * ?"
then
    heating.apply(iThermostat_gLivingRoom, 10)
end

rule "iThermostat_gLivingRoom start 1"
when
    Time cron "0 30 16 * * ?"
then
    heating.apply(iThermostat_gLivingRoom, 20)
end

rule "iThermostat_gLivingRoom end 1"
when
    Time cron "0 0 21 * * ?"
then
    heating.apply(iThermostat_gLivingRoom, 10)
end
...

In a case like this, I’m willing to give up a little bit in terms of length and adding lots of rules if it makes the overall logic simpler. And the above logic is really simple when compared to your original or my reworking. Simple rules are easir to reason about, debug, and maintain. I’m just not sure that it is worth writing the rules in the complicated monolith when compared to the simplicity of using separate rules. But that is my opinion.

And I notice that most of your start and end times overlap so you can combine rules where it makes sense (e.g. call heatng.apply for all the thermostats that start at 16:30 in the one rule). Also, you can use globals or Items to store the target temps if desired. But since you know which termostat is being commanded you don’t have to go through any Group filtering or parsing or the like to get the value.

1 Like

Wow, thanks for taking the time giving me such a detailed reply, very very much appreciated!

Interesting you mention the possibility of jsr223 not being supported in 1.9, that paints a different picture…

I’ll certainly look at a v2 of my dsl rules given all you’ve posted. Using json from a file I like, but I’ll toy with the cron approach too.

It would be great if the habmin scheduler was working, or better still there were item types to present and store schedule tables :slight_smile: I’ll also be testing the waters with the gcal integration, I have a heating calendar setup, it just needs some testing… But that would provide a nice ui to change start/end times per day of week etc.

Thanks again for all the very useful info, I’m already feeling like I need to give back :wink:

Can you point me in the direction of any discussions of dropping jsr223 support in Java 9? I can’t find anything through google-fu…all I can find is mention of dropping a browser plugin and no inclusion of a lightweight JSON API as the project has been shelved…

Understanding the jsr223 support of Java and where it is going will be key for me to decide where to spend my efforts…If jython will remain supported for the foreseeable I would sooner go that route, it will be an enabler in so many ways.

To be clear, JSR223 is being replaced with a new script feature in Java 1.9, so it isn’t like the capability is going away, it just means there will be some major growing pains when OH moves to Java 1.9.

There is a long-standing issue open. Since you know Java I’m sure the maintainers would be thrilled to accept a PR.

If someone were to implement a Calendar element for the sitemaps (a separate issue is open for that) it would go a long way by itself towards getting to this.

I usually recommend gCal or calDav for that sort of scheduling. But what I really do is try to make people think about how to automate something like this using events and state rather than a set clock-based schedule. Using events and state to drive your automation is much more flexible and reactive and fits the spirit of home automation a little better.

Jython isn’t going away. But how the libraries and languages that work now through JSR223 will almost certainly change which will necessitate changes in OH to support the new interfaces.

Gotcha, all makes sense, jython it will likely be, after I tidy up the dsl stuff of course :slight_smile:

I may have some time between contracts after the new year… I might, depending how far I get, have a crack at calendar based scheduling proper assuming i can get a working openhab build environment in place… Didn’t build smart home libraries on first attempt…

The OH2-specific documentation is at http://docs.openhab.org/configuration/jsr223.html. Between that and the Jython/Python documentation there may actually be more documentation than for the Rules DSL.

The features are somewhat different than for OH1, but I’m not aware of missing features. What did you have in mind?

The JSR223 API is simply being bundled with Java 1.9. From what I’ve seen the API is the same. Have you seen something to the contrary?

1 Like

Like I said, I’m not sure. I know in weeks past I’ve seen forum postings asking about crib triggers and runners and the like and the responses sounded like that want implanted yet or was being tested and the like.

Just past experience where it is just supposed to just be a port into the Java core and it didn’t work. I never meant to imply that it wouldn’t work, just that there will probably be some work on the oh side.

I never heard of “crib triggers”. Do you mean “cron triggers”? If so, those are well supported. You may be referring to questions about OH Timer functionality. In OH2, it’s recommended to use the Python standard timer support although the OH2 timer implementation is accessible from JSR223.

Maybe, but that could be true for other OH2 functionality when moving to Java 9. For the scripting API in Java 9, I’d bet there will not be breaking changes relative to JSR223. AFAICT, it’s not actually a port, but a bundling instead.

1 Like

That is a good point, jsr223 is a language scripting specification in the java core so you would expect it to do the same as usual with each release, any issues faced with openhab would need addressing by the jvm developers right?

In fact reading this https://en.m.wikipedia.org/wiki/Scripting_for_the_Java_Platform the below is good not bad and might explain some of the earlier posts:

Scripting for the Java Platform was developed under the Java Community Process as JSR 223. The final release of JSR 223 happened on December 11, 2006. The specification, however, was withdrawn later on December 13, 2016 after a Maintenance Review Ballot,[1] where it was decided that this functionality would be included as an integral part of Java 9 and onward.

It’s good to know that is become more integral to the jvm than ever :wink:

I think I’m being misunderstood. I’m not trying to imply anything negative about jsr223 not an I singling it out as the only thing that will have problems when oh moves to 1.9. I was just providing some cautions from my current knowledge.

If you prefer, I can ignore all future JSR223 postings.

I’d never suggest that you do that, but I do feel it’s important to address potential misunderstandings about OH2 JSR223 documentation and feature status (and the stability of the Java scripting APIs). I’m assuming “current knowledge” is subject to revision and expansion, yes?

Absolutely and I don’t want to mislead anyone and I’m constantly learning.

I usually try to step back from answering too much about JSR223 to give someone more knowledgeable to respond. JSR223 is one of my weaker areas.

I’ve set it up and have a “hello world” Jython script working and I’ve read a bit on how to get to the next steps. And I pretty much read every forum posting. But I don’t follow the github issues or discussions that take place elsewhere (there is only so much time in the day) so it is a little difficult to keep up with where JSR223 is as there isn’t much discussed on these forums. I’ve not yet tried to use your library either though it is on the list.

From my perspective it’s all good, I’m still getting my head around it all and like the discussion…voicing my thoughts (or typing in this case) and being challenged really helps me… I’ve been called a cardboard programmer before now :slight_smile:

I’m reactivating this thread as I’ve kind of a similar problem when importing a certain module with jython.

As soon as I try to import the BusEvent with from org.openhab.model.script.actions import BusEvent I get the following error: ImportError: No module named openhab in <script> at line number 3
I’ve seen this import is used in spacemanspiffs library and I couldn’t find any hint yet on why it’s not working for me. Maybe one of you has an idea on this?

Normal jython scirpts are working and I’m currently using steve1s library.

Basically I would like to use sendCommand in my own module and the way to go I could figure out by now is to import the BusEvent. If there are other solutions for that problem, I’m also more than willing to try these.

have a look at my post Delay Rule loading after Startup in OH2?. You can set the start-level of the rule engine to 90. Openhab will then start the rule engine after all other bundles are loaded.