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

Are you defining the trigger like this? If so, I’ll do some testing.

@when("Item gMotion_Sensors changed")

That works totally fine, i was just confused on how this library behaves compared to the previous one…

That brings me to my next question, because this also behaves differently from what i know from the old lib:

I have an item which is a window contact state (Ct_Dc_State_Livingroom). This belongs to a group of all window contacs of the living room (gWindowcontacs_Livingroom) which belongs to a group with all window contacs in the house (gWindowcontacs). How can i use the top level group as trigger so that event.itemName gives me window contact item that changed?

@when(“Member of gWindowcontacs changed”) gives me event.itemName = “gWindowcontacs_Livingroom”

@when(“Descendent of gWindowcontacs changed”) gives me event.itemName = “gWindowcontacs”

but i would like to get the item “Ct_Dc_State_Livingroom” that actually triggered the whole rule.

Thanks in advance :slight_smile:

This is what should work for you… but I did a quick test and found a typo :roll_eyes:. There was a missing ‘s’ in descendent at line #176. Thank you for reporting this!

You can correct the file yourself, or download the latest triggers.py. Either way, you’ll need to restart OH, or use this trick in your rule to reload the module (add this, then save the rule, then clear it out and resave the rule). I did it like this…

import sys
from openhab.triggers import when
reload(sys.modules['openhab.triggers'])
from openhab.triggers import when

Something to note… the newly released M5 milestone build includes a fix for ItemStateTriggerHandler, which will correct some issues in rules with groups in their triggers. Now might be a good time to upgrade!

1 Like

Hi Michael, I’ve just submitted a PR for an enhanced implementation of the add() method found in lib/python/items.py. The enhancement provides a complete implementation of an item creator, which includes the ability to specify category, group membership, and label. For group items, the base item type and a group function may be specified.

Here is my modified version of items.py:

# NOTE: Requires JythonItemProvider component

from org.slf4j import Logger, LoggerFactory

from openhab import osgi, jsr223, JythonItemProvider
from openhab.jsr223 import scope

__all__ = ["add", "remove"]


logger = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules.jsr232.openhab")

def add(item, item_type=None, category=None, groups=None, label=None, gi_base_type=None, group_function=None):
    try:
        if isinstance(item, str):
            if item_type is None:
                raise Exception("Must provide item_type when creating an item by name")

            baseItem = None if item_type != "Group" or gi_base_type is None else scope.itemRegistry.newItemBuilder( gi_base_type       \
                                                                                                                  , item + "_baseItem")\
                                                                                                   .build()
            group_function = None if item_type != "Group" else group_function
            item = scope.itemRegistry.newItemBuilder(item_type, item)  \
                                     .withCategory(category)           \
                                     .withGroups(groups)               \
                                     .withLabel(label)                 \
                                     .withBaseItem(baseItem)           \
                                     .withGroupFunction(group_function)\
                                     .build()

        JythonItemProvider.add(item)
    except:
        import traceback
        logger.error(traceback.format_exc())

def remove(item):
    JythonItemProvider.remove(item)

… and a test that illustrates its usage:

"""
This example demonstrates item creation.
"""

import unittest
import time

from org.eclipse.smarthome.core.library.types.ArithmeticGroupFunction import And, Or
from org.slf4j import Logger, LoggerFactory

from openhab.testing import run_test
import openhab.items
reload(openhab.items)
import openhab.items


logger = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules")
    
class MyUnitTest(unittest.TestCase):
    def setUp(self):
        try:
            groupItems = {"gItem_0": Or(OnOffType.ON, OnOffType.OFF), "gItem_1": And(OnOffType.ON, OnOffType.OFF)}
            for iname, gfunc in groupItems.items():
                openhab.items.add(item = iname, item_type = "Group", gi_base_type = "Switch", group_function = gfunc)

            newItems = {"Item0": "Switch", "Item1": "Dimmer", "Item2": "Dimmer", "Item3": "Switch"}
            for iname, itype in newItems.items():
                openhab.items.add(iname, itype)

            gItem = ir.getItem("gItem_0")
            for member in ("Item0", "Item2"):
                gItem.addMember(ir.getItem(member))

            gItem = ir.getItem("gItem_1")
            for member in ("Item1", "Item3"):
                gItem.addMember(ir.getItem(member))
        except:
            import traceback
            logger.error(traceback.format_exc())

    def tearDown(self):
        itemNames = ("gItem_0", "gItem_1", "Item0", "Item1", "Item2", "Item3")
        for iname in itemNames:
            openhab.items.remove(iname)

    def test_item(self):
        itemNames = ("Item0", "Item1", "Item2", "Item3")
        for item in itemNames:
            events.postUpdate(item, "OFF")

        time.sleep(1)
        for item in itemNames:
            self.assertEqual(items[item].as(OnOffType), OnOffType.OFF)

        self.assertEqual(items["gItem_0"], OnOffType.OFF)
        self.assertEqual(items["gItem_1"], OnOffType.OFF)

        itemStates = {"Item0": "ON", "Item1": "33", "Item2": "67", "Item3": "OFF"}
        for item, cmd in itemStates.items():
            events.postUpdate(item, cmd)

        time.sleep(1)
        for item, state in itemStates.items():
            logger.info("item: {}".format(ir.getItem(item)))
            self.assertEqual(str(items[item]), state)

        self.assertEqual(items["gItem_0"], OnOffType.ON)
        self.assertEqual(items["gItem_1"], OnOffType.OFF)


run_test(MyUnitTest)

2 Likes

awesome, I will dig into this weekend.

I am trying to determine how to import modules from github. My thoughts are they should be installed in the OH config directory so they can be included in backups.

Not sure best way to do this - PIP? Pull the repository from github?

Michael, The file you need is the proposed updated version of items.py. The file items.py is already part of your Jython installation. You should be able to replace the current version, maybe copy the original somewhere safe first, then copy the contents of the first code panel in my post above to automation/lib/python/items.py. On my system, an RPi3, I run Openhabian and the Jython files are all located under /etc/openhab2/automation/, which is actually a symlink to /usr/share/openhab2/automation. Your installation may be different, but you will have an automation directory into which you copied the Jython files and directories. I assume you are familiar with the post How to Setup Jython where @5iver described the process of installing Jython.

Hi Scott,

I am running OpenHABian as well. 2.4M5.

I would like to use the python request library - not related to the .items stuff above.

Mike

@5iver

This line generates an error in triggers.py, my guess is there needs to be a check that the inputList is not empty? Rule works when you specify to X.

 
@when ("Item GarageDoubleDoorIndicatorPressed_Value changed from 0")

2018-10-31 14:25:21.132 [ERROR] [omation.core.internal.RuleEngineImpl] - when: Exception [index out of range: 0]: [Traceback (most recent call last):

  File "/etc/openhab2/automation/lib/python/openhab/triggers.py", line 297, in when

    if inputList[0] == "to":

IndexError: index out of range: 0
1 Like

Please open an issue for this in GH. I’m a bit out of pocket ATM, but will take a look tonight/tomorrow.

done.

done :slight_smile:

This should now be corrected. I refactored the trigger validation portion of the when decorator, and this corrected the issue and possible others! I’ve been building out a regression test for the decorators, and everything I have passed. There are a lot of combinations to test though, so I may have missed some.

1 Like

@5iver Scott, question about trigger behaviour…

When using Member of to detect a change in a item of a group, should that trigger catch a group that is a member. ie I have a group that is a member of another group and currently that does not get triggered. I can behavior in the code of triggers.py is doing what it should but I always thought the rules DSL would trigger on that … correct me if I am wrong :slight_smile:

There is "descendent of " immediately below at line 177 but AFAIK this isn’t part of the rules DSL.

I opened an issue on github.

Mike

My automation is very dependent on Member of triggering when child groups change values. I haven’t noticed any change in behavior, but I’ll specifically test it.

A group should trigger a Member of rule, if it is a child of the specified group.

I made up Descendent of, because it was functionality in the old item_triggered decorator. Groups are not considered with this trigger.

Actually groups do trigger with the descendent of and do not trigger with the member of…

This appears to be working properly for me:

Items/Groups

Group:Switch:OR(ON,OFF)         gTest_Parent                 "Test Parent Group [%s]"           <none>      (gTest)
    Group:Switch:OR(ON,OFF)         gChild_1                 "Test Child Group 1 [%s]"          <none>      (gTest_Parent)
        Switch                          Test_Switch_1        "Test Switch 1 [%s]"               <switch>    (gChild_1)
        Switch                          Test_Switch_2        "Test Switch 2 [%s]"               <switch>    (gChild_1)
    Group:Switch:AND(ON,OFF)         gChild_2                "Test Child Group 2 [%s]"          <none>      (gTest_Parent)
        Switch                          Test_Switch_3        "Test Switch 3 [%s]"               <switch>    (gChild_2)

Rule

from openhab.rules import rule
from openhab.triggers import when

from org.slf4j import Logger, LoggerFactory
log = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules")

@rule("Regression Test: Test decorator Member of changed")
@when("Member of gTest_Parent changed")
def testDecoratorGroupMemberChangedRegression(event):
    log.debug("JSR223: Regression Test: Jython decorator: Member of gTest_Parent changed [{}]: [{}]".format(event.itemName, event.itemState))

@rule("Regression Test: Test decorator Descendent of changed")
@when("Descendent of gTest_Parent changed")
def testDecoratorGroupMemberChangedRegression(event):
    log.debug("JSR223: Regression Test: Jython decorator: Descendent of gTest_Parent changed [{}]: [{}]".format(event.itemName, event.itemState))

Logs

2018-11-02 22:23:22.821 [DEBUG] [org.eclipse.smarthome.model.script.Rules] - JSR223: Regression Test: Jython decorator: Member of gTest_Parent changed [gChild_1]: [OFF]
2018-11-02 22:23:22.822 [DEBUG] [org.eclipse.smarthome.model.script.Rules] - JSR223: Regression Test: Jython decorator: Descendent of gTest_Parent changed [Test_Switch_1]: [OFF]

2018-11-02 22:23:37.540 [DEBUG] [org.eclipse.smarthome.model.script.Rules] - JSR223: Regression Test: Jython decorator: Member of gTest_Parent changed [gChild_1]: [ON]
2018-11-02 22:23:37.541 [DEBUG] [org.eclipse.smarthome.model.script.Rules] - JSR223: Regression Test: Jython decorator: Descendent of gTest_Parent changed [Test_Switch_1]: [ON]

2018-11-02 22:25:33.326 [DEBUG] [org.eclipse.smarthome.model.script.Rules] - JSR223: Regression Test: Jython decorator: Descendent of gTest_Parent changed [Test_Switch_3]: [ON]
2018-11-02 22:25:33.333 [DEBUG] [org.eclipse.smarthome.model.script.Rules] - JSR223: Regression Test: Jython decorator: Member of gTest_Parent changed [gChild_2]: [ON]

Scott, how did you address the problems with the components and JythonItemProvider not loading correctly at startup? I have looked at using the reload function but no success.

I am starting to use the item.add you posted above. Works great but with a restart JythonItemProvider fails and I have to “edit it” and save to be able to load it

Mike

Of course I found the answer in the post immediately above :slight_smile:

from openhab.items import add
import openhab.items
reload(openhab.items)
from openhab.items import add

Michael, glad you found the answer. I fought that issue myself until I recalled having read somewhere (I think it was the lucid README) about reload.

Scott, interesting problem that took me about an hour to sort out.

When doing string concatenation for item naming, item becomes type Unicode and not string. So the if statement False in items.py. Not sure this is desired or not…

if isinstance(item, str):

I’ve noted that behavior in some other areas with Jython. I’m not quite sure what to do about it. It does seem to cause problems with join() expressions when one object is a str object and the other is Unicode.