Jython problems in OH3.1 - event no longer parsed as object

I recently upgraded from openHAB3.0 to openHAB 3.1 and suddenly several Jython scripts stopped working.

After debugging a rule, I noticed that the event is no longer parsed into an object but stays a string (as reported in events.log:

Here’s the rule:

@rule(
    rulePrefix + "Update machine state when automation parameters have been updated",
    description="""When the roller shutter automation settings have been modified, the machine state may have to ba recomputed."""
    + ruleTimeStamp,
    tags=["roller shutter automation", ruleTimeStamp],
)
@when(
    "Item {item} changed".format(
        item=my_ir_items.AUTOMATION_TEMPERATURE_HYSTERESIS
    )
)
@when(
    "Item {item} changed".format(
        item=my_ir_items.COLD_DAY_TEMPERATURE_THRESHOLD_ITEM
    )
)
@when(
    "Item {item} changed".format(
        item=my_ir_items.WARM_DAY_TEMPERATURE_THRESHOLD_ITEM
    )
)
@when(
    "Item {item} changed".format(
        item=my_ir_items.HOT_DAY_TEMPERATURE_THRESHOLD_ITEM
    )
)
def Rule_UpdateMachineStateAfterAutomationSettingsChanged(event):
    global logTitle
    logPrefix = "Rule_UpdateMachineStateAfterAutomationSettingsChanged(): "


    if event is None or isinstance(event, UnDefType):
        # Event not defined or instance of UnDefType - nothing to do
        return

    LogAction.logInfo(
        logTitle,
        logPrefix
        + "[ XXX ] event := " + pp.pformat(event)
    )

    '''
    # This no longer works when upgrading from openHAB3.0 to openHAB3.1
    if event['itemCommand'] and event.itemCommand == "REFRESH":
        # Skip processing of Z-Wave REFRESH commands (by cron rule or otherwise)
        return

    LogAction.logInfo(
        logTitle,
        logPrefix
        + "Item '{name}' received command '{cmd}'".format(
            name=event.itemName, cmd=event.itemCommand
        ),
    )
'''

Here's the resulting `openhab-cli showlogs` output:
==> /var/log/openhab/events.log <==
2021-07-30 11:35:24.816 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'ShutterAutomation_Roof_TemperatureThreshold_Warm' received command 22.5
2021-07-30 11:35:24.838 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'ShutterAutomation_Roof_TemperatureThreshold_Warm' changed from 22 °C to 22.5 °C

==> /var/log/openhab/openhab.log <==
2021-07-30 11:35:24.842 [INFO ] [tters.py@2021-07-30T09:34:57.709427Z] - Rule_UpdateMachineStateAfterAutomationSettingsChanged(): [ XXX ] **event := Item 'ShutterAutomation_Roof_TemperatureThreshold_Warm' changed from 22 °C to 22.5 °C**

If I remove the block comment, I get the following error:

2021-07-30 11:32:40.457 [WARN ] [jsr223.jython ] - Traceback (most recent call last):
File “/etc/openhab/automation/lib/python/core/log.py”, line 96, in wrapper
return function(*args, **kwargs)
File “/etc/openhab/automation/lib/python/core/rules.py”, line 108, in execute
self.callback(inputs.get(‘event’))
File “/etc/openhab/automation/jsr223/python/personal/roller_shutters.py”, line 2372, in Rule_UpdateMachineStateAfterAutomationSettingsChanged
if event[‘itemCommand’] and event.itemCommand == “REFRESH”:
TypeError: ‘org.openhab.core.items.events.ItemStateChangedEvent’ object is unsubscriptable

Is it possible that the upgrade to OH3.1 wiped the Jython helper library?

See

I don’t think that applies here. That PR just applied to Rules DSL, not the other languages.

@shutterfreak, event should still be an Object. At least it still is in JavaScript in the UI. But event is defined by the Rule Engine itself so it shouldn’t be any different for Python. event only exists in the rule when the rule is actually triggered by an Item or Thing event. Time/cron/System/manual triggers will not have an event Object at all. All the triggers are Item changed triggers so that shouldn’t be a problem here.

Note: event will never be UnDefType. event isn’t a State. That test should be

if event is None or isinstance(event.itemState, UnDefType):

I’ve never used the event['itemCommand'] syntax even back when I used Python so I can’t comment if there might be something wrong with that.

The error is telling us that event is of type ItemStateChangedEvent, not String so perhaps that’s a clue. The JavaDoc for that Class is located at ItemStateChangedEvent (openHAB Core 3.2.0-SNAPSHOT API).

OK, now that I’m looking more closely that if statement that is failing is redundant anyway. There are no received command triggers on this rule. event.itemCommand will never exist in this rule because the rule is never triggered by a command, only state changes. So you can safely sidestep that whole block of code without changing the behavior of the rule.

The actual problem though is either event['itemCommand'] or it’s event.itemCommand == "REFRESH". So if you want to figure out which one you will need to break that into two separate lines so you can see what happens with each test independently. As I’ve said, I’ve never used the ['name of variable'] syntax so I don’t know whether/how that works. But the error hints that that might be the problem. In order for that to work, Jython would have to be able to convert the Java Object to a Python dict on the fly and there might need to be extra stuff done to allow that to happen.

It’s also possible that @CrazyIvan359 has made some changes in the Helper Library on how event is processed before the rule runme function is called. I know he’s doing a lot of good things with the Helper Library. Maybe event was converted to a dict all along and I didn’t know it but that’s changed.

In any case you are not working with a Python dict right now but are working with a Java Object with event.

1 Like

Hmmm… I took a look at event by exporting dir(event) to openhab.log:

        LogAction.logInfo(
            logTitle,
            logPrefix
            + "[ XXX ] dir(event) := " + pp.pformat(dir(event))
        )

2021-07-30 21:12:02.577 [INFO ] [tters.py@2021-07-30T19:11:22.010977Z] - Rule_UpdateMachineStateAfterAutomationSettingsChanged(): [ XXX ] dir(event) :=

[   'TYPE',
    '__class__',
    '__copy__',
    '__deepcopy__',
    '__delattr__',
    '__doc__',
    '__ensure_finalizer__',
    '__eq__',
    '__format__',
    '__getattribute__',
    '__hash__',
    '__init__',
    '__ne__',
    '__new__',
    '__reduce__',
    '__reduce_ex__',
    '__repr__',
    '__setattr__',
    '__str__',
    '__subclasshook__',
    '__unicode__',
    'class',
    'equals',
    'getClass',
    'getItemName',
    'getItemState',
    'getOldItemState',
    'getPayload',
    'getSource',
    'getTopic',
    'getType',
    'hashCode',
    'itemName',
    'itemState',
    'notify',
    'notifyAll',
    'oldItemState',
    'payload',
    'source',
    'toString',
    'topic',
    'type',
    'wait']

So I added another log line in my rule:

    LogAction.logInfo(
        logTitle,
        logPrefix
        + "[ XXX ] item [{itemName}] changed from [{oldState}] to [{newState}]".format( 
            itemName=event.itemName,
            oldState = event.oldItemState,
            newState = event.itemState,)
    )

and that one results in:

2021-07-30 21:22:50.146 [INFO ] [tters.py@2021-07-30T19:22:14.122165Z] - Rule_UpdateMachineStateAfterAutomationSettingsChanged(): [ XXX ] item [ShutterAutomation_Roof_TemperatureThreshold_Warm] changed from [23 °C] to [22.5 °C]

The weird thing is that only now I realise that this rule does nothing :smiley:

I was curious to know if there is a way to determine if a variable exists in an object and I think the following will work in Python.

if hasattr(event, 'itemCommand'):

So, assuming that works even with the Java Objects, you should be able to replace event['itemCommand'] and the like with a call to hasattr and it should work out OK.

I need to test it but this could be the lead I need to figure out how to preserve variables across a rules runs when defined in the UI with Python. Though in that case if 'myVar' in locals(): will probably be more appropriate. Or it might be if 'myVar' in globals():. I’m not sure how variables are stored in a Script Action in the UI.

2 Likes

I changed nothing in the handling of event, but @rlkoshak you are on the right track about the syntax.

The event parameter is a Java object, as everything in Java is. I don’t remember what kind, subclassed from Event I think, but the important thing is that it is not a subscriptable object. That is, it is neither a collection(array) nor a map(dictionary). Which means we can’t use the event['element'] syntax, because there are no elements in the object to look up.

Java objects have properties though, which is what you are trying to access. Once again, Rich, you found a solution to Olivier’s problem with hasattr. It will work on Java objects just as you expect it would.

Another solution would be to branch the code based on the event object type like below, with a branch for each event type that needs special handling and possibly an else for the rest that would be handled the same.

    if isinstance(event, ItemStateChangedEvent):
        pass

Also sorry for the late reply, I don’t have as much time for these forums lately.

1 Like

I think it’s ItemEvent (openHAB Core 3.2.0-SNAPSHOT API) with a subclass for each different Rule trigger type that generates an event.

For those who want to branch, the full list of subclasses are at the top of that link.

For Things I think it’s ChannelTriggeredEvent (openHAB Core 3.2.0-SNAPSHOT API) and ThingStatusInfoChangedEvent (openHAB Core 3.2.0-SNAPSHOT API). I think event is null/None for all other triggers (e.g. time based, cron based, manual).

1 Like

That would be what you’ll see most often.

More information on common events in my Python stubs docs and I list the full Java class names there as well.

Thank you all for your input.

I am actually processing a Java object, not a Python dictionary.