How to use `itemRegistry` within a jython-**module**?

Based on the really nice decorators from @steve1 (openhab2-jython), I tried to create my own decorator to add a trigger for each item of a given group.

Therefore I tried to change the triggers.py module, which is installed in the jython-path and not run as script.
These modules don’t have the scope of the scripts, so you need to import anything you need on your own.

You can see the change I did at GitHub.

To get the members of the group, I need access to the itemRegistry - where can I import this?

It seems to belong to org.eclipse.smarthome.core.items, but from org.eclipse.smarthome.core.items import ItemRegistry results in an error:

21:55:31.727 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/etc/openhab2/automation/jsr223/homie_monitoring.py': NameError: global name 'itemRegistry' is not defined in <script> at line number 37

BTW: If I add the decorator to my script, everything works fine - however, this is not a long-term solution.

Working script:

se.importPreset("RuleSimple")
se.importPreset("RuleSupport")

from openhab.log import logging
from openhab import triggers
import openhab.items
from openhab.triggers import ItemStateUpdateTrigger, ItemEventTrigger, _FunctionRule
  
ITEM_CHANGE = "ItemStateChangedEvent"
ITEM_UPDATE = "ItemStateEvent"
ITEM_COMMAND = "ItemCommandEvent"
 
def item_group_triggered(group_name, event_types=None, result_item_name=None):
    event_types = event_types or [ITEM_CHANGE]
    if hasattr(event_types, '__iter__'):
        event_types = ",".join(event_types)
    def decorator(fn):
        dlog = logging.getLogger("RULES.oh2-jython.triggers.item_group_triggered")
 
        def callback(module, inputs):
            result_value = fn(inputs)
            if result_item_name:
                scope.events.postUpdate(result_item_name, str(result_value))
        group_triggers = []
        groupitems = itemRegistry.getItem(group_name)
        for i in groupitems.getAllMembers():
            group_triggers.append(ItemEventTrigger(unicode(i.name), event_types))
            dlog.debug("   added ItemStateUpdateTrigger for " + unicode(i.name))
        rule = _FunctionRule(callback, group_triggers, extended=True)
        #get_automation_manager().addRule(rule)
        automationManager.addRule(rule)
        return fn
    return decorator

@item_group_triggered("homie_uptime")
def debug_uptime(input):
    logging.getLogger("RULES.Debug.Uptime_Test").debug("Event: {} - [{}]".format(unicode(input), unicode(input.__class__)))

from openhab.jsr223 import scope
item = scope.itemRegistry.getItem("TEST")

Some context… the JSR223 script scope variables are mostly script-independent. There are types (e.g., SimpleRule) and global-ish objects like itemRegistry (an instance of the ‘ItemRegistry’ type). These objects are safe to import normally into Jython modules. However, the automationManager scope instance is per-script. You can’t import this into a module because the module instance will be the one associated with the first script that imports the module and will be invalid for other script imports. The get_automation_manager function avoids this problem because the module queries the scope for the script-specific automation manager each time it is used.

1 Like

Many thanks.
I created a PR for your openhab2-jython, so if you want you can merge back my @item_group_triggered - for me its very useful - I’ve a lot of rules based on groups, where I need to know which group member has triggered the rule. That was quite messy with Xtend.

Do you know, if there is already an “API documentation” for writing Jython (or any other JSR223) rules? For me it seems, that useful documentation is quite cluttered over several projects.
For example, the complete scope package or an API documentation for the SimpleRule and its callbacks - especially the execute(module, input) callback. What is module and what is input?

Thanks. I’ll take a look.

Yes, the official documentation is here.

http://docs.openhab.org/configuration/jsr223.html

I know the openhab-jsr223 doc, but this is on a too abstract level. (Not not even the “execute()” function is mentioned).

I already found that the full name of SimpleRule is org.eclipse.smarthome.automation.module.script.rulesupport.shared.simple.SimpleRule via Eclipse Smarthome API - however, that was just a guess, there was no link form the openhab2-documentation to it.

So there I find that execute is Object execute(Action module, Map<String,?> inputs).

What is inputs? There is no more documentation for it.

So I had a look at Action and it seems that this is somehow the representation of the Rule itself? It also contains some inputs, but there is no clear documentation, how they are related to the Map<String,?> inputs parameter of execute() ?

Are they somehow the same? If so, why the inputs are transported as own parameter for execute()?

What can you do with the Action object? Is there a simple documentation that explains the useful parameters?
When you try to answer these question with the current state of documentation, you soon get lost in the jungle of very internal API descriptions.

It is mentioned on the Jython-specific JSR223 child page. There is an example of a simple rule and some brief documentation related to the execute function.

That said, there’s no question the documentation can be improved. The JSR223 support in OH2 is very new. As you learn more about how to use it, I hope you’ll help fill some of the documentation gaps.

@Euphi, thank you for this! I’ve been working on making an adjustment for a few days now, and I could use some help. I’m converting my DSL rules to jsr223 Jython, and the most complex one relies heavily on nested groups which trigger the rule, and then using triggeringItem, another group is called by deriving it’s name from the name of the triggering group to take action on the lights, speakers, etc. in that area. Some day soon I’ll do a full writeup because this is working great… but I’m hoping for better performance with jsr223. Here is a simple example to illustrate what I’m trying to do…

Group                      gArea_Trigger
    Group:Switch:OR(ON,OFF)    gDS_FamilyRoom_Trigger    (gArea_Trigger)
    Group:Switch:OR(ON,OFF)    gUS_LivingRoom_Trigger    (gArea_Trigger)
    Group:Switch:OR(ON,OFF)    gUS_EntranceGarage_Trigger (gArea_Trigger)
        Group:Switch:NAND(ON,OFF)    gUS_EntranceGarage_Bathroom_Trigger    (gUS_EntranceGarage_Trigger)
        Group:Switch:NAND(ON,OFF)    gUS_EntranceGarage_Lock_Trigger    (gUS_EntranceGarage_Trigger)

gArea_Triggers
    |_gFamilyRoom_Trigger
        |_DS_FamilyRoom_Motion
        |_DS_FamilyRoom_Contact
    |_gLivingRoom_Trigger
        |_US_LivingRooom_Motion
        |_US_LivingRoom_Contact
    |_gEntranceGarage_Trigger
        |_US_EntranceGarage_Motion
        |_US_Laundry_Contact
        |_gUS_EntranceGarage_Bathroom_Trigger
            |_US_GuestBathroom_Contact
        |_gUS_EntranceGarage_Lock_Trigger
            |_Lock_GarageAttached_Inner_State

gArea_Action
    |_gFamilyRoom_Action
        |_gDS_FamilyRoom_Bulbs
    |_gLivingRoom_Action
        |_US_LivingRoom_Dimmer
    |_gEntranceGarage_Action
        |_US_EntranceGarage_Dimmer

@item_group_triggered("gArea_Trigger", event_types=["ItemStateChangedEvent"])
def areaTrigger(event):
    groupName = event.itemName.replace("_Trigger","")        
    for item in scope.itemRegistry.getItem(groupName + "_Action").getMembers():        
        #do stuff

Using .getAllMembers in your decorator returns the lowest leaf in the hierarchy, but I am looking for only direct descendants of the group. Changing to .getMembers creates the rules, but they never fire, and I have not been able to determine why. It’s entirely possible this is a bug somewhere. Is there any chance you could help me to modify your decorator, or create another? Or maybe @steve1 could point me in the right direction?

I started down the path of using your current decorator and getting the groups for the item that was triggered, filter on those that contain “_Action”, and then process those groups. Unfortunately, this destroys the simplicity of using the groups in the first place, since just because a device triggers does not mean the group has triggered.

Edit: I found something else. When I try…

@item_triggered("gDS_FamilyRoom_Trigger", event_types=["ItemStateChangedEvent"])
def test(event):
    log.debug("JSR223: Area_Trigger: {0}: {1}: test [{2}]".format(event.itemName,event.itemState,event))

… the rule is created as with item_group_triggered, still with no errors, but the rule is never triggered. Switching to ItemStateEvent works, as well for item_group_triggered with .getMembers. This is looking like a bug using groups as triggers with ItemStateChangedEvent. I’m using OH snapshot 1302.

Edit 2: Correction. ItemStateEvent only partially corrects this. I was using Karaf to send updates to the group item, and this worked. But when the group is updated through item state changes, the rule is still not firing. :roll_eyes: As this is looking more and more like a bug, maybe @smerschjo could please take a look?

To summarize, when using item_triggered or item_grop_triggered (with decorator using .getMembers… not tested with .getAllMembers) with a group (without an itemtype) as the item, with members that are groups with itemtype and aggregation function defined:

  • event_type=[‘ItemStateChangedEvent’] never triggers
  • event_type=[‘ItemStateEvent’] only triggers when group state is directly updated, not when updated by member items

I can now use a modified item_group_triggered in a similar manner to using a ‘Member of’ trigger in the Rules DSL. This was not a bug but simple inexperience with using JSR223-Jython for rules. I looked into ItemEventFactory and found the cause of my issue. Changing the item_group_triggered module to use .getMembers instead of .getAllMembers was not firing because I was using ItemStateChangedEvent for the event_type but was attempting to fire on group state changes. The solution was to instead use GroupItemStateChangedEvent. I will submit a PR to include either a new module or to add a parameter to specify .getMembers or .getAllMembers.

1 Like