Port JSR223 bundle to openHAB 2

Hello i hope you guys can help me. I am trying to define and import my own modules.
Therfore i created the file $ohdir/lib/python/openhab/timer.py with the followng content:

import threading
import time
         
class MyTimer(threading._Timer):
    started_at = None
    def start(self):
        self.started_at = time.time()
        threading._Timer.start(self)
    def elapsed(self):
        return time.time() - self.started_at
    def remaining(self):
        return self.interval - self.elapsed()

In my script i import this class with

from openhab.timer import MyTimer

and i am able to create an object where the type() returns the correct type. But the problem ist that my own methods (e.g. remaining()) do not work. If i however define the class MyTimer in the script itself it works like a charm. What is wrong with importing my class from a library?

My seconds issue presumably has the same root cause. I would like to define a global dictionary, which i can access from every script

devicesdictionary= {"Lb":"Gluehbirne", "Ls":"Lichtschalter", "Ht":"Heizungsthermostat", "Us":"Universalsensor", "Wc":"Fensterkontakt", "Dc":"Tuerkontakt", "Sd":"Rauchmelder", "Ps":"Steckdose"}

where and how do i have to define it properly? Tried several ways (e.g. loading as “component”) which didn’t work.

Thanks :slight_smile:

In Python, “global” variables are scoped to a module. If you create a module and expose your dictionary though a “global” variable in the module, you can then import that module into multiple scripts.

For your timer example, what is threading._Timer (with the underscore) vs. threading.Timer?

Thanks the import of a dictionary works like you said :slight_smile:

threading.Timer is a function and threading._Timer is a class which MyTimer inherits from. My function basically does the same as threading.Timer but has a .remaining() and .elapsed() method which returns a time in seconds

Interesting. I didn’t realize Timer was a function (never tried to subclass it). Importing classes from modules is not a problem in general. However, I don’t understand what you’re doing with the _Timer subclass. It appears you are not invoking the _Timer constructor which has required arguments. How does that work?

Hi steve thanks again for your help. It actually works now (with the class like I posted above) but i have to

import threading, time

in my script which is strange imo, because it is imported in the module.

It would have been easy to identify if i would be able to see the error messages the script is throwing.
Is there a way to display them all, like a was in a python shell? Now I am debugging with a lot of logging statements, which is very annoying :frowning:

Use a try/except around the code (possibly the entire script body) and log the traceback.

1 Like

Currently I am observing the same error „NameError: name ‘SimpleRule’ is not defined in at line number xy“ as described above when openHAB is started. When touching the file at a later time it is loaded without an issue.

Has there been any solution for that problem?

Thanks for your help,
Juelicher

Hello i got two questions i can not answer by searching the forum or docs:

  1. Is it possible to have a trigger similar to Xtend “System started”? Like:
self.triggers = [
  Trigger("SystemStartedTrigger", "???", Configuration({ ??? })),
  ]
  1. What are the other options for
automationManager.addRule(MyRule())

In particular i would like to be able to remove single rules under certain condition. The obvious

automationManager.removeRule(MyRule())
automationManager.deleteRule(MyRule())

does not work.

Thanks

It’s not built-in. Even the new SmartHome rule engine doesn’t support it, AFAIK. I’m guessing the reason it wasn’t officially supported with the new engine is because there is no well-defined “system started” event and so the trigger has always been a bit dubious. It’s possible to write your own custom trigger to do something similar. I created one in my openhab2-jython GitHub repo. In this case, “System Started” was really “Rule Defined”.

I’m not currently in a position to try it myself, but I’d recommend trying ruleRegistry.remove(ruleInstance.uid). You can also look at the source code (especially the documentation) for the RuleRegistryImpl class.

1 Like

Sorry I am not a very skilled programmer, I hope you can guide me on how to use your custom trigger. How would my class look like? If i do it like this it complains about a missing handler:

scriptExtension.importPreset("RuleSupport")
scriptExtension.importPreset("RuleSimple")
from openhab.triggers import StartupTrigger

class MyRule(SimpleRule):
    def __init__(self):
        self.triggers = [
             Trigger("SystemStartedTrigger", "openhab.triggers.StartupTrigger", 
                    Configuration())
        ]
        
    def execute(self, module, input):
        # some code

automationManager.addRule(MyRule())

From your source code i assume i need an “openhab.STARTUP_MODULE_ID”, but where do i get it from?
Everytime a rule gets loaded it gets a new random id. Is it possible to define an own rule id beforehand? I assume that would also help me to delete a specific rule as asked in my 2nd question.

Thanks in advance :slight_smile:

Hey @steve1:

You were right. I tried the following:

rules.setEnabled(u'TestCaseRunner', False)
rules.remove(u'TestCaseRunner')

setEnabled seems to work flawlessly:

2018-02-13 19:31:50.000 [.event.RuleStatusInfoEvent] - TestCaseRunner updated: DISABLED

remove however does nothing. Any Idea?

Edit:
It seems that I can only remove rules that were added during the script scope?

a = MyRule()
s.rules.add(a)
id = rules.getAll()[-1].getUID()
s.rules.remove(id)

produces:

2018-02-13 19:53:27.875 [thome.event.RuleAddedEvent] - Rule 'rule_1' has been added.
2018-02-13 19:53:27.875 [.event.RuleStatusInfoEvent] - rule_1 updated: INITIALIZING
2018-02-13 19:53:27.875 [.event.RuleStatusInfoEvent] - rule_1 updated: IDLE
2018-02-13 19:53:27.891 [ome.event.RuleRemovedEvent] - Rule 'rule_1' has been removed.

Is ‘TestCaseRunner’ the rule name or the UID? About the scope, you may be right. There are a few scope objects that are created for each script. I think that the automation manager and rule registry are two of those.

Yes, ‘TestCaseRunner’ is the UID of a rule created in another script.
You can see that enable/disbable on the rule works but not the remove.
How could I remove a rule declared in another script?

2018-02-13 19:53:28.936 [thome.event.RuleAddedEvent] - Rule 'TestCaseRunner' has been added.

Based on Spaceman_Spiff’s post i tried the following:

scriptExtension.importPreset("RuleSupport")
scriptExtension.importPreset("RuleSimple")
from org.slf4j import LoggerFactory

class MyRule(SimpleRule):
    def __init__(self):
        self.triggers = [
             Trigger("MyTrigger", "core.ItemStateUpdateTrigger", Configuration({ "itemName": "TestString1"}))
        ]
        
    def execute(self, module, input):
        LoggerFactory.getLogger("Test").warn("Test")

idMyRule = ruleRegistry.add(MyRule()).getUID()
ruleRegistry.setEnabled(idMyRule, False)
ruleRegistry.setEnabled(idMyRule, True)
ruleRegistry.runNow(idMyRule)

The setEnabled solves my question 2. To get run the rule once its activated (question 1) i tried the runNow method, where the log tells me that the rule is started, but the execute block does not seem to be executed since the log is missing my logging line.

2018-02-14 19:31:21.924 [.event.RuleStatusInfoEvent] - 3625ec18-b40a-452a-a690-a753f045a0a8 updated: INITIALIZING

2018-02-14 19:31:21.941 [.event.RuleStatusInfoEvent] - 3625ec18-b40a-452a-a690-a753f045a0a8 updated: IDLE

2018-02-14 19:31:21.956 [.event.RuleStatusInfoEvent] - 3625ec18-b40a-452a-a690-a753f045a0a8 updated: DISABLED

2018-02-14 19:31:21.961 [.event.RuleStatusInfoEvent] - 3625ec18-b40a-452a-a690-a753f045a0a8 updated: INITIALIZING

2018-02-14 19:31:21.973 [.event.RuleStatusInfoEvent] - 3625ec18-b40a-452a-a690-a753f045a0a8 updated: IDLE

2018-02-14 19:31:21.978 [.event.RuleStatusInfoEvent] - 3625ec18-b40a-452a-a690-a753f045a0a8 updated: RUNNING

2018-02-14 19:31:21.981 [.event.RuleStatusInfoEvent] - 3625ec18-b40a-452a-a690-a753f045a0a8 updated: IDLE

Any idea?

Can you make sure your logging is working? Please add an additional log line before your code.

LoggerFactory was indeed not working somehow but:

scriptExtension.importPreset("RuleSupport")
scriptExtension.importPreset("RuleSimple")
from openhab.log import logging

logging.getLogger("Test1").warn("Test1")

class MyRule(SimpleRule):
    def __init__(self):
        self.triggers = [
             Trigger("MyTrigger", "core.ItemStateUpdateTrigger", Configuration({ "itemName": "TestString1"}))
        ]
        
    def execute(self, module, input):
        logging.getLogger("Test").warn("Test")

idMyRule = ruleRegistry.add(MyRule()).getUID()
ruleRegistry.setEnabled(idMyRule, False)
ruleRegistry.setEnabled(idMyRule, True)
ruleRegistry.runNow(idMyRule)

returns still no log entry

2018-02-15 07:48:20.701 [WARN ] [Test1                               ] - Test1

==> /var/log/openhab2/events.log <==

2018-02-15 07:48:20.728 [thome.event.RuleAddedEvent] - Rule '73697671-6181-493e-ba17-fe6e8406ca75' has been added.
2018-02-15 07:48:20.731 [.event.RuleStatusInfoEvent] - 73697671-6181-493e-ba17-fe6e8406ca75 updated: INITIALIZING
2018-02-15 07:48:20.738 [.event.RuleStatusInfoEvent] - 73697671-6181-493e-ba17-fe6e8406ca75 updated: IDLE
2018-02-15 07:48:20.745 [.event.RuleStatusInfoEvent] - 73697671-6181-493e-ba17-fe6e8406ca75 updated: DISABLED
2018-02-15 07:48:20.748 [.event.RuleStatusInfoEvent] - 73697671-6181-493e-ba17-fe6e8406ca75 updated: INITIALIZING
2018-02-15 07:48:20.756 [.event.RuleStatusInfoEvent] - 73697671-6181-493e-ba17-fe6e8406ca75 updated: IDLE
2018-02-15 07:48:20.760 [.event.RuleStatusInfoEvent] - 73697671-6181-493e-ba17-fe6e8406ca75 updated: RUNNING
2018-02-15 07:48:20.762 [.event.RuleStatusInfoEvent] - 73697671-6181-493e-ba17-fe6e8406ca75 updated: IDLE

Edit: Ok i figured it out.

ruleRegistry.add(MyRule())

leaves the rule inoperable. This however works:

automationManager.addRule(MyRule())
idMyRule = rules.getAll()[-1].getUID()
ruleRegistry.runNow(idMyRule )

Did you try just changing


idMyRule = ruleRegistry.add(MyRule()).getUID()

to

idMyRule = automationManager.addRule(MyRule()).getUID()

From the source code, it appears that the automationManager does some adaptation to the registered rule for scripting purposes. Adding the rule directly to the ruleRegistry may have been the original issue. Grabbing the last registered rule will work most of the time but it also may fail nondeterministically since the registry could be updated from other threads and the most recent rule may not be the one you want.

Thanks, that works :slight_smile:

What would be a safe way to get the id from the functions like “item_triggered” where the adding to the registry is wrapped in a decorator?

You’d need to modify the decorator to store the rule as an attribute on the function. You’d use it like


@time_triggered(EVERY_SECOND)
def my_periodic_function():
  pass

ruleRegistry.setEnabled(my_periodic_function.rule.getUID(), False)

Of course, it would be easier to just call the function directly to get runNow behavior.

1 Like

@smerschjo:
I am running in the same problem again. Could you help me out on how to delete a rule just by the UID?

Edit:
As always - once you post you figure it out yourself :smiley:
I used the “ruleRegistry” instead of “rules” instance.

1 Like