Simplified Jython rule definition (similar to Rules DSL) using a universal decorator

Is enabling and disabling Jython rules (via Karaf or via RuleManager service in Jython) working for you?

I’ve tried to update rules_registry example with latest release and recent snapshot, but once the rule is disabled and re-enabled it is not being run properly. When called with runNow (without checking for predicates), it will appear to run (IDLE -> Running -> IDLE) but action is not executed (looks like missing action handler). In the same way, after re-enabling rule is not run on trigger condition.

And I can confirm, that rules can not be remove via rules global but only with ruleRegistry as said previously in this topic (on side note, why these two are different?)

Just a heads up to anyone using the Jython libraries that uses snapshots… don’t upgrade your OH! I’ve been working on updating the libraries due to the ESH migration, but have found an issue where some of the Java classes, which have not yet been fully migrated, are no longer accessible. So for now, it is best not to use the snapshots (or milestones, when they come) until this gets sorted.

5 Likes

does this support OR in @when conditions ? e.g.

@when("Item A changed" or "Item B changed")   (conceptually)

I can’t quite suss-out the proper syntax to use if it does…

I know that I could define a new group to do this – I have lots of Member Of rules working in Jython…this is more a “does this even work ?” question…

Yes, but not like in the DSL. Just add another one, like in the original example…

@when("Item A changed")
@when("Item B changed")

Hello, Im trying to port the MQTT2 eventbus rule to Jython but Im having a problem when I access the Channel Trigger. A rule like this:

@rule("MQTT Test2")
@when("Channel mqtt:broker:f79d2a84:TriggerIn triggered")
def MQTTPublishIn(event):
    input1 = event.event
    MQTTPublishIn.log.info(input1)

gives this error when beeing loaded:

2019-04-05 14:31:53.244 [ERROR] [tomation.jsr223.jython.core.triggers] - when: Exception [when: "Channel mqtt:broker:f79d2a84:TriggerIn triggered" could not be parsed because Channel "mqtt:broker:f79d2a84:TriggerIn" is not a trigger]: [Traceback (most recent call last):

  File "/etc/openhab2/automation/lib/python/core/triggers.py", line 313, in when

    raise ValueError("when: \"{}\" could not be parsed because Channel \"{}\" is not a trigger".format(target, trigger_target))

ValueError: when: "Channel mqtt:broker:f79d2a84:TriggerIn triggered" could not be parsed because Channel "mqtt:broker:f79d2a84:TriggerIn" is not a trigger

What am I doing wrong? It works without a problem when using it a s a trigger in a DSL Rule. When I use a another channel from a Rockerswitch the Jython rule works without a problem. It just doesnt work with the mqtt publish trigger channel.
Thanks for the great work on this.

After looking at the error in the log again I found the responsible line in the triggers.py:

 elif target_type == "Channel" and scope.things.getChannel(ChannelUID(trigger_target)).kind != ChannelKind.TRIGGER:
raise ValueError("when: \"{}\" could not be parsed because Channel \"{}\" is not a trigger".format(target, trigger_target))

Once I commented it out and restarted Openhab the rule works without an issue.
It looks like the MQTT2 binding doesnt declare the trigger channel right.

I agree. Could you please save this to a test script and post what is logged?

from core.log import logging, LOG_PREFIX
log = logging.getLogger(LOG_PREFIX + ".TEST")
from org.eclipse.smarthome.core.thing import ChannelUID
from org.eclipse.smarthome.core.thing.type import ChannelKind
log.warn("[{}]".format(things.getChannel(ChannelUID("mqtt:broker:f79d2a84:TriggerIn")).kind))

Never mind… I created one and it came through as STATE. I’ll take a look at the binding to see how this is happening.

1 Like

Thank you for looking into it. With the two lines commented out its working fine for now. Im really loving Jython combined with the simplified rule definition.

1 Like

Hello, just a short question, when I try to use the System started trigger I get this:

2019-04-15 16:30:53.582 [ERROR] [tomation.jsr223.jython.core.triggers] - when: Exception [when: "System started" could not be parsed because rule triggers do not currently support target_type "System"]: [Traceback (most recent call last):
  File "/etc/openhab2/automation/lib/python/core/triggers.py", line 325, in when
    raise ValueError("when: \"{}\" could not be parsed because rule triggers do not currently support target_type \"System\"".format(target))
ValueError: when: "System started" could not be parsed because rule triggers do not currently support target_type "System"
]
2019-04-15 16:30:53.596 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/etc/openhab2/automation/jsr223/personal/mqtt1.py': TypeError: 'NoneType' object is not callable in <script> at line number 32

Am I doing something wrong? Or is the Readme ahead of the implementation. This is the rule the trigger belongs too. It works fine without the system started trigger.

@rule("StatePublish")
@when("System started")
@when ("Time cron 0 0 0/1 * * ?")
def statepublish(event):   
    for member in ir.getItem("Stats").members:
        membername = ''.join(re.findall("^\S*", str(member)))
        memberstate = str(ir.getItem(membername).state)
        actions.get("mqtt","mqtt:broker:f79d2a84").publishMQTT("allItemsout/"+membername,memberstate)

Thanks for any insights, best regards Johannes

Short answer… you can’t use StartupTrigger yet.

StartupTrigger broke after an API change, but ScriptedCustomModuleHandlerFactory was recenty fixed. I’ll mention this restriction in the OP to match the readme, which still looks accurate… or are you referring to something else? I have not updated the libraries so that the StartupTrigger can be used, as there are several changes that are also needed in order to be compatible with the new openhab-core, and there was a critical issue for automation that needed to be resolved before the new OH core could be used.

I planned to put a release out for all of these changes, once the snapshots included the new core, but this is taking longer than expected. I’ll try to get something together before the end of the weekend, but this could be delayed as the OH-Jython-Scripters organization is about to be renamed, so that helper libraries for scripting langauges other than Jython can be added. :partying_face:

1 Like

Just had another thought… instead of using the StartupTrigger, you can get the result by just calling the function straight out of the script, so that it runs when the file is loaded or saved.

statepublish(None)
1 Like

Thank you for the answer, your second suggestion doesnt work for me. If I assign statepublish to None I get a syntax error.
But I can wait for the system started trigger. Thank you again, regards Johannes

Did you put it after the function definition?

@rule("StatePublish")
@when ("Time cron 0 0 0/1 * * ?")
def statepublish(event):   
    for member in ir.getItem("Stats").members:
        membername = ''.join(re.findall("^\S*", str(member)))
        memberstate = str(ir.getItem(membername).state)
        actions.get("mqtt","mqtt:broker:f79d2a84").publishMQTT("allItemsout/"

statepublish(None)
1 Like

:man_facepalming: that did it thanks :raised_hands:

1 Like

Would it be possible to extend the decoration to the class level.
something like:

@rule("test time rule", tags=["Tag 1", "Tag 2"])
@when("Channel homematic:HM-PB-6-WM55:3014F711A061A7D5698CE994:NEQ1001667:1#BUTTON triggered SHORT_PRESSED")
@when("Channel homematic:HM-PB-6-WM55:3014F711A061A7D5698CE994:NEQ1001667:1#BUTTON triggered DOUBLE_PRESSED")
class MyRule(MyOtherClass, YetAntherClass):  
    def execute(self, module, input):
        events.postUpdate("TestString2", "some data")

The rule decorator does work on classes. I believe this would be possible for the when decorator too. But TBH, I don’t think I’ll ever get to it, since I do not have or see the need. Other developers are welcome to contribute though!

What are you trying to do that can’t use a function for the action? Or by using the class in a function?

Here is a simple example of using the decorators with an instance of a class:

from org.slf4j import Logger, LoggerFactory

from openhab import osgi
from openhab.rules import rule
from openhab.triggers import when
import openhab.rules
reload(openhab.rules)
import openhab.triggers
reload(openhab.triggers)
from openhab.rules import rule
from openhab.triggers import when


ruleEngine = osgi.get_service("org.eclipse.smarthome.automation.RuleManager")

class GroupLister:
    def __init__(self, instName):
        self.__name__ = instName
        self.logger = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules.%s" %
                                              self.__class__.__name__)
        self.logger.info("Instantiating instance of class %s" %
                         self.__class__.__name__)

    def __call__(self, event):
        self.logger.info("'%s' action (Jython cron rule)" % self.__name__)

        self.logger.info("All Group items:")
        for gItem in sorted(item for item in ir.getItemsOfType("Group"),
                            key = lambda item: item.name):
            self.logger.info("==> %s" % gItem)
            map(lambda mbr: self.logger.info("  |-- %s" % mbr),
                sorted(gMbr for gMbr in gItem.members,
                       key = lambda mItem: mItem.name))

        ruleEngine.setEnabled(self.UID, False) # one-shot rule


logger = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.jsr223")

# requires rule and when imports
ruleInst1 = rule("Group Test Rule")(
            when("Time cron 10 0/1 * * * ?")
                (GroupLister("ruleInst1")))

logger.info("ruleInst1 attributes & methods: %s" % dir(ruleInst1))

Note that I coded that up some time ago, so the imports may no longer be valid.

2 Likes

After fixing the imports I get at line

2019-05-20 19:12:10.071 [ERROR] [jsr223.jython.Group Test Rule       ] - Traceback (most recent call last):
  File "/openhab/conf/automation/lib/python/core/log.py", line 43, in wrapper
    return fn(*args, **kwargs)
  File "<script>", line 35, in __call__
AttributeError: GroupLister instance has no attribute 'UID'

I have another question:
In the example, the

__call__

hook is used, which method is actually called. This would make impossible to define other methods.

I found this again while looking for @scottk’s previous discussion of the one shot rules :slightly_smiling_face:. If you define a scriptUnloaded function, it will be called when the script is unloaded. There is a scriptLoaded function too. ScriptEngineManager calls these when loading/unloading scripts. You might be able to utilize this for cancelling your timer.

You must be running a post-ESH migration snapshot. I haven’t run the example script on anything but an older OH2 snapshot.

I suspect the means of accessing the rule’s UID has changed.

The intent of this design is to allow multiple, unique instances of a single class to be used in multiple rules.