JSR223 Jython: EnvironmentError: No JSR223 scope is available

I’m attempting to write a module that I can use across multiple Rules to manage antiflapping Timers. If you saw my other thread on this topic, I’ve decided to go down the Group membership path. I’ve encountered two problems, one of which I’ve spend some time trying to debug, the other of which I’ve not.

Problem 1: I have a test Rule that triggers on System started. It calls my module and tries to set a 1 second antiflapping Timer using my module. So far so good.

My module is a class that creates an AntiflappingTimers Group if one doesn’t exist. This detail is important because it means that the scope is successfully imported and usable at this point.

When a Rule calls the set method on my class, a new Timer gets created and I store it along with a bunch of other information in a dict. Again, so far so good.

The Timer calls a function that is also part of this module class and I do successfully have the function successfully called.

But when I try to use scope.ir.getItem[itemName] I get

2019-07-28 18:41:11.656 [ERROR] [sr223.jython.rules.Antiflapping test] - Traceback (most recent call last):                                                                 
    File "/openhab/conf/automation/lib/python/core/log.py", line 51, in wrapper                                                                                               
      return fn(*args, **kwargs)                                                                                                                                              
    File "/openhab/conf/automation/lib/python/personal/antiflapping_timer.py", line 29, in not_flapping                                                                       
      scope.ir.getItem("AntiflappingTimers").removeMember(scope.ir.getItem(itemName))                                                                                         
    File "/openhab/conf/automation/lib/python/core/jsr223.py", line 42, in __getattr__                                                                                        
      scope = get_scope()                                                                                                                                                     
    File "/openhab/conf/automation/lib/python/core/jsr223.py", line 28, in get_scope                                                                                          
      raise EnvironmentError("No JSR223 scope is available")                                                                                                                  
EnvironmentError: No JSR223 scope is available      

I’ve tried importing the scope and ir directly in the function. I’ve tried passing the scope to the function. I’ve tried importing the scope at the top of the file. I continue to get this error. The code is as follows.

"""
A class to centralize antiflapping timer logic for multiple Items
"""
from core.jsr223 import scope
from core.items import add_item
from core.rules import rule
from core.triggers import when
from core.actions import ScriptExecution
from org.joda.time import DateTime
from core.log import log_traceback

class Antiflapping_Timers(object):
    """Creates an openHAB Timer and calls the passed in method. It manages
    keeping track of the timers and cleaning up after they expire
    """

    def __init__(self, log):
        self.timers = {}
        self.log = log

        if scope.ir.getItems("AntiflappingTimers") == []:
            log.info("Creating AntiflappingTimers Group")
            add_item("AntiflappingTimers", item_type="Group", label="AntiflappingTimers")

    @log_traceback
    def not_flapping(self, scope, itemName):
        # from core.jsr223.scope import ir
        self.log.info("{} is not flapping!".format(itemName))
        scope.ir.getItem("AntiflappingTimers").removeMember(scope.ir.getItem(itemName))
        # self.log.info("Removed {} from group".format(itemName))
        # self.timers[itemName][not_flapping]()
        # self.log.info("Called function")

    @log_traceback
    def set(self, itemName, interval, not_function, flapping_function):
        """
        If a Timer alreday exists, cancel it and delete it and create a new one:

        Args
            itemName: The name of the Item the Timer is watching for flapping
            interval: how long the item need to remain in the same state to be considered not flapping in milliseconds
            not_function: function to call when the Item is determined to not be flapping
            flapping_function: function to call when the Item is determined to be flapping
        """
        if itemName in self.timers:
            self.log.warning("There is already an antiflapping timer for {}".format(itemName))
            self.timers[itemName].cancel()
            del self.timers[itemName]

        self.log.info("Creating the antiflapping timers for {}".format(itemName))
        item = scope.ir.getItem(itemName)
        scope.ir.getItem("AntiflappingTimers").addMember(item)
        self.timers[itemName] = {"orig_state":   item.state,
                                 "flapping":     flapping_function,
                                 "not_flapping": not_function,
                                 "timer":        ScriptExecution.createTimer(DateTime.now().plusMillis(interval), lambda: self.not_flapping(scope, itemName))}

    # @rule("Listen for flapping", description="A Rule to listen for flapping of Items")
    # @when("Member of AntiflappingTimers changed")
    # def flapping(self, event):
    #     self.log("{} is flapping!".format(event.itemName))
    #     self.timers[itemName][flapping]()
    #     scope.getItem("AntiflappingTimers").removeMember(event.itemName)
 

Problem 2 is that creation of the Rule inside the class. Is that not allowed? As I’ve said, I’ve not really looked into this one yet and I suspect the OpenWeatherMap module in the community modules will be helpful.

Thanks

I ran into this same problem Rich. I have figured out the cause, but not how to fix it. In my case I am building rules at runtime, and the way around it was to put the rule function in a script file.

The issue is that modules don’t get a ScriptEngine context from openhab, only script files do. Without that I think the modules just end up executing in a normal Python context, which has no ScriptEngine context, even if the module code is being triggered by a rule.

1 Like

I don’t see the need for a class or the use of timers. You could just put DateTimes in the dict and measure the time intervals. But to get this working, you can change your import to…

from core.jsr223.scope import ir

… and change references to scope.ir to just ir.

There’s also a bug in your code where you cancel the timer. You’ll need to change it to…

self.timers[itemName]['timer'].cancel()

After these, your module worked when used from this script…

from core.log import logging, LOG_PREFIX
log = logging.getLogger("{}.TEST".format(LOG_PREFIX))

import personal.timer_test

anti_flap = personal.timer_test.Antiflapping_Timers(log)

def not_function():
    log.warn("not_function")

def flapping_function():
    log.warn("flapping_function")

from time import sleep
anti_flap.set("Virtual_Switch_1", 5000, not_function, flapping_function)
sleep(0.5)
anti_flap.set("Virtual_Switch_1", 5000, not_function, flapping_function)
sleep(0.5)
anti_flap.set("Virtual_Switch_1", 5000, not_function, flapping_function)
sleep(0.5)
anti_flap.set("Virtual_Switch_1", 5000, not_function, flapping_function)
sleep(0.5)
anti_flap.set("Virtual_Switch_1", 5000, not_function, flapping_function)

You may not but it would be something useful for me. And this isn’t a generic class for timers. This is a very specific use case that I repeat at least three times in my Rules:

  1. Have a dict to keep track of the timers
  2. When a timer goes off do one thing (this is why I can’t just use a DateTime, I need something to happen when the wait time ends, i.e. do something when the Item remains in the same state for a certain amount of time)
  3. If the Item changes state before the timer goes off do something else.

If I didn’t have 2, I could get away with just using DateTimes. But even if I use just DateTimes, the problems I’m having would still be the case since they had to do with messing with the Group.

I tried that too and got the same error.

I saw that after I posted. I never ran into that bug because I haven’t had the code actually run that far yet.

I wonder what else I’ve changed because I did try that based on the OpenWeatherMap forecast Rules, and I got the same error. I’ve gone back to that and now it works.

On to problem #2. Thanks!

Problem #2 could be solved using a lambda in the timer that calls a function with an argument (I’m thinking the sensor item name) ans that function could take a specific action based on the item.

Maybe I’m not understanding a finer detail of what you’re wanting to do though

The part of the whole approach I’m trying to solve with problem #2 is to add the listening for changes to the Item’s state while the Timer is running. This would indicate that the Item is flapping and I would cancel the Timer and perform some other action instead.

In order to accomplish that I’d need to create a Rule to listen for changes to that Item, but only when the Timer exists. I can control whether the Item will trigger that Rule by dynamically adding/removing the Item from a Group. But I want to create both the Group and the Rule using my centralized code so it’s all self contained.

I have a bunch of different ways I can make this work but, as a learning exercise, I’m going down this path. It’s not as much about whether the approach is the best approach or that it makes sense as it is learning by doing.

I’m already doing that actually. When I call my class’s set function, I pass it two lambdas in fact, one that gets run when we determine the Item is flapping and another that gets called when the Item remains in the same state for the given number of milliseconds.

But the part that I need the Rule for is to detect that the Item changed state while the Timer was running, indicating that it was flapping.

Now I follow.

The lighting system I built does something similar. I have an init function that scans for items that are members of a group and adds an update trigger for each one to a rule, I do this dynamically because there is some validation and there can be nested groups and the root group can be changed at runtime.
In your case I don’t think anything so complicated is needed. In a single script file you have a dict of timers, a rule that triggers on state change of descendant of group, and a function that the timer calls with an item name.

I understand the eagerness to learn new concepts, but for this use case it doesn’t make sense to make or modify the rules at runtime.

You didn’t mention this here before, why would this happen? Wouldn’t the rule that spawns the timer check that it exists first?

It is defensive programming as much as anything. If you can think of it someone will try to do it, whether or not it make sense to. I can easily see a case where someone has two sets of Rules that could be triggered by the same Item, and in both sets of rules they would want to have antiflapping logic (or perhaps someone would use this class for some other purpose).

Take for example my door reminder code I just recently posted. That’s a case where each Item has two Timers, one for the flapping and another for the reminder. I don’t plan to try and reuse this library for both, but I could. And if I see that someone would certainly try (not that I expect to ever release this or submit it to the helper library, it’s utility seems obvious to me but I feel like I’m spending a lot of time justifying why I’m even thinking about it so maybe it’s not something broadly useful, but it’s useful to me).

One of my goals is to make it as self contained as possible. I’d like to just instantiate the class, call the function and everything is done. The Group is created, the Rule listens to the events is created, and all the rest just happens. I just need to call set with my two lambdas and all the rest is handled.

I may have to be a little less dynamic in the end. I may have to give up on multiple Timers per Item use case and create the Group and Rule at load time instead of run time and I can live with that. But it would be cool if I don’t have to live with that.

OK… I did not understand that requirement. So, the timers are needed, but if you have a single instance of the class, then what is the benefit of adding the complexity of using a class? I don’t see why your class methods couldn’t just be functions.

This won’t be possible. When you use ‘Member of’ in the when decorator, it is creating individual triggers for each Item in the group. Changes to that group are not reflected in those triggers. Even if the triggers were adjusted based on new/changed group membership, due to the speed with which the devices could be flapping, the rule engine may take more time to process the rule change than it takes for the device to flap. What are the potential minimum and maximum times between flaps?

I suggest to use a single rule triggered on change and check for the existence of that Item’s flapping timer to determine if the device is flapping. So, have your ‘set’ function return a boolean to report the state of flapping, and act on that in your rule actions.

Because it’s not managing a timer. It’s managing a bunch of timers. All the dict logic, checking whether the Timer exits or not, removing the Timer from the dict when done, all of that is part of the class. I used a class because it is both data (the dict) and functions and therefore it seemed reasonable to make it a class.

Since we are looking at openHAB, I’m thinking along the lines of half a second at the absolute smallest. I’m the first person to say that OH is not a real time system. I’m not trying to handle button debounce or electrical flapping or anything like that (which is why I’m not using the word debounce). I’m thinking of cases like “wait five minutes before marking everyone away in case someone just went to get the mail.”

I’m not actually too worried about the speed at all. If it’s that fast, it’s not something one should rely upon OH to handle in the first place.

That’s a pretty big difference between Rules DSL behavior and JSR223. That’s unfortunate and it’s going to take some docs to explain thoroughly. I expect a bunch of posts to the forums from some of the more advanced users.

How does code like @CarzyIvan359 and other users who have posted about using dynamic Group membership work?

Being tenacious by nature, could I use the lower level Rules API to add/remove triggers then since Group membership won’t work? That’s all I’m trying to accomplish with the Group membership in the first place.

Maybe a lesson learned then? If you don’t need a class, don’t use one. For OO programmers, I understand the urge to use classes, but this is scripting. You will never have more than one instance of your class. Your dict and functions are already organized into a module. So, there’s no need to build a new structure to organize them into an object. KISS :kissing:!

There are a number of module types yet to create and ‘Member of’ is on the list, but I don’t see an easy way to dynamically update the triggers on member change. I haven’t dug too deeply into this, but I believe the rules DSL behaves this way because the rules are reloaded after an Item is created or removed, which I consider a bug. When reloading, the rules pick up the changes in the group membership and include triggers for the new group members. The NGRE might be able to be made to behave the same, but I’m doubtful and have come to expect that my scripts are NOT reloaded after an Item is added or removed :slight_smile:.

Yes, you can programmatically manipulate a rule’s triggers, but I don’t see a need for it in this scenario. In each rule triggering on an Item that could flap, use a return from the ‘set’ function to determine if it is flapping and whether to continue the action. Where do you see a second rule being needed, or are you talking theory?

But I am and will. As I’m using it right now I have three instances, two of which have to be separate because both have a separate timer for a single item (one flapping timer and one reminder timer). I can’t do that with just a script. I have to keep the dicts separately or I need to add a bunch of ugly logic to allow the same item to have multiple timers.

While I have coded OO a lot in the past, I do know the difference. In this case I think it’s warranted give the requirements. I could rework my rules a little too adjust the timing so the same item doesn’t need two timers, but that saves me like two lines of code. Doesn’t seem like a fair trade to maintain scripting purity. I could also pass the dicts to the functions but then that breaks the encapsulation I’m after.

At this point I’m mainly taking theory. I have a class already that is almost as self contained as I’d like (I’m testing it and will post it and how I’m using it in my alerting and door reminder rules when I’m done).

As I coded it and run into problems and limitations I’ve changed the scope and purpose somewhat. Instead of being focused on strictly flapping timers now it’s mainly focused on the book keeping involved with managing timers for multiple items. I’ve already found it cuts 10-20 lines of code from each of those two rules and makes them a bit simpler. And this way I won’t run into trouble if I forget to del the timer from the dict when it’s done or something like that