Jython openHAB Helper Libraries - Rules not executing

OH 2.4 stable release on debian jessie

Hello community,

i’m just moving from lucid to the new Helper libraries (Language: Python) - in terms of syntax, that’s not really challenging (everything seems to be pretty much the same), but still, when i migrate a rule based on a class, it seems to load fine, but doesn’t get executed like it’s told.

@rule("Stromzaehler Rule")
class StromzaehlerRule(object):
...
  def getEventTriggers(self):
    return [
      CronTrigger('58 * * * * ?', triggerName='StromzaehlerRuleTrigger')
    ]
...
  def execute(self, module, inputs):
...

I have also tried using the @when decorator, it does register a trigger in the logs, but still doesn’t get executed - the above does not even show a trigger in the logs.

I am pretty sure i’m missing something fundamental here, maybe someone can point me in the right direction?

Fun fact: the hello_world rule from the zip file gets executed fine with the @when trigger, but i’m not just executing a single function (see above).

Best regards,
Alex

That’s a good sign… Jython is setup properly and it can find the core package.

You’re using an extension here. The API has changed since before 2.4, so take a look here… Rules — openHAB Helper Libraries documentation (hint… .trigger).

This is concerning. Would you please post the complete rule?

First thing: after following your .trigger hint, i can see in the logs that the CronTrigger gets scheduled correctly AND is executed, so the rule works correctly again. Thank you!

For the sake of completion:

from core.rules import rule
from core.triggers import CronTrigger

import org.joda.time.DateTime as DateTime
import org.joda.time.Minutes as Minutes

from org.eclipse.smarthome.model.persistence.extensions import PersistenceExtensions as pe

# Regel zum Auswerten der Stromzaehler

GESAMT = 'Stromzaehler_Gesamt'
HEIZUNG = 'Stromzaehler_Heizung'

GESAMT_WATT = 'Stromzaehler_Gesamt_Watt'
HEIZUNG_WATT = 'Stromzaehler_Heizung_Watt'

GESAMT_AVG10 = 'Stromzaehler_Gesamt_AVG10'
HEIZUNG_AVG10 = 'Stromzaehler_Heizung_AVG10'

@rule("Stromzaehler Rule")
class StromzaehlerRule(object):

    def __init__(self):
        self._heizung_itm = ir.getItem(HEIZUNG)
        self._gesamt_itm = ir.getItem(GESAMT)
        self._heizung_watt_itm = ir.getItem(HEIZUNG_WATT)
        self._gesamt_watt_itm = ir.getItem(GESAMT_WATT)
        self._heizung_avg10_itm = ir.getItem(HEIZUNG_AVG10)
        self._gesamt_avg10_itm = ir.getItem(GESAMT_AVG10)

    def getEventTriggers(self):
        return [
            CronTrigger('58 * * * * ?', triggerName='StromzaehlerRuleTrigger').trigger
        ]

    def execute(self, module, inputs):
        pow_ges = self.calcPower(self._gesamt_itm)
        pow_hz = self.calcPower(self._heizung_itm)
        events.sendCommand(self._gesamt_watt_itm, str(pow_ges))
        events.sendCommand(self._heizung_watt_itm, str(pow_hz))
        avg_hz = self.calcPowerAVG10(self._heizung_watt_itm)
        avg_ges = self.calcPowerAVG10(self._gesamt_watt_itm)
        events.sendCommand(self._heizung_avg10_itm, str(avg_hz))
        events.sendCommand(self._gesamt_avg10_itm, str(avg_ges))

    def calcPower(self, item):
        curVal = item.state.intValue() if hasattr(item.state, 'intValue') else 0
        curTime = DateTime.now()

        hist = pe.historicState(item, DateTime.now())
        histVal = hist.state
        histTime = DateTime(hist.timestamp)
        self.log.debug("Current state of item {}: {}, Historic state: {} at {}".format(item.name, curVal, histVal, histTime))
        timeDiff = Minutes.minutesBetween(curTime, histTime).getMinutes()
        if (timeDiff < 1): timeDiff = 1
        power = 0.0
        diff = curVal - histVal.intValue()
        if (diff > 0):
            power = diff * (60.0 / timeDiff)

        self.log.debug("Difference ticks: {}, min: {}, Power: {}".format(diff, timeDiff, power))
        return power

    def calcPowerAVG10(self, item):
        return pe.averageSince(item, DateTime.now().minusMinutes(10))

That’s the rule right now - working :slight_smile:

To test, i commented the whole getEventTriggers(self) function and added the following decorator right after @rule before the classname:

@when("Time cron 58 * * * * ?")

which yields the following log entry:

2019-08-04 09:46:31.043 [INFO ] [rt.internal.loader.ScriptFileWatcher] - Loading script 'python/personal/090_stromzaehler.py'
2019-08-04 09:46:31.268 [DEBUG] [jsr223.jython.core.triggers         ] - when: target=[Time cron 58 * * * * ?], target_type=Time, trigger_target=cron, trigger_type=58 * * * * ?, old_state=None, new_state=None
2019-08-04 09:46:31.277 [DEBUG] [jsr223.jython.core.triggers         ] - when: Created cron_trigger: [Time_cron_58_]
2019-08-04 09:46:31.308 [DEBUG] [jsr223.jython.core.rules            ] - Added rule [Stromzaehler Rule]
2019-08-04 09:46:31.328 [DEBUG] [rt.internal.loader.ScriptFileWatcher] - Script loaded: python/personal/090_stromzaehler.py

But it does NOT get executed - nothing in the logs afterwards (not even an error).

How does the decorator figure out which function to execute in the class? Should it follow the same methodology as the @rule decorator by using the execute(self, module, inputs) method?

Best regards,
Alex

P.S.: Any comment on improving my code is more than welcome :wink:

OK… that won’t work since it is a function decorator. Among other things, the when decorator adds a triggers attribute to the function that it decorates. The rule decorator then build the rule using the triggers in the function’s trigger attributes. There are ways to use the when decorator with a class and they’ve been discussed in the forum, but you could also just get rid of the class. IMO, this is cleaner and easier to read… see if this works…

from core.rules import rule
from core.triggers import when
from core.actions import PersistenceExtensions

from org.joda.time import DateTime, Minutes

# Regel zum Auswerten der Stromzaehler

@rule("Stromzaehler Rule")
@when("Time cron 58 * * * * ?")
def stromzaehler_rule(event):
    calcPower(ir.getItem("Stromzaehler_Gesamt"))
    calcPower(ir.getItem("Stromzaehler_Heizung"))
    calcPowerAVG10(ir.getItem("Stromzaehler_Heizung_Watt"))
    calcPowerAVG10(ir.getItem("Stromzaehler_Gesamt_Watt"))

def calcPower(item):
    curVal = item.state.intValue() if hasattr(item.state, 'intValue') else 0
    curTime = DateTime.now()

    hist = PersistenceExtensions.historicState(item, DateTime.now())
    histVal = hist.state
    histTime = DateTime(hist.timestamp)
    stromzaehler_rule.log.debug("Current state of item {}: {}, Historic state: {} at {}".format(item.name, curVal, histVal, histTime))
    timeDiff = Minutes.minutesBetween(curTime, histTime).getMinutes()
    if (timeDiff < 1): timeDiff = 1
    power = 0.0
    diff = curVal - histVal.intValue()
    if (diff > 0):
        power = diff * (60.0 / timeDiff)

    stromzaehler_rule.log.debug("Difference ticks: {}, min: {}, Power: {}".format(diff, timeDiff, power))
    events.sendCommand(item, power)

def calcPowerAVG10(item):
    events.sendCommand(item, PersistenceExtensions.averageSince(item, DateTime.now().minusMinutes(10)))
1 Like

It does, but frankly i’m more of an encapsulation type and i’m heavily using inheritance in other rules so i’ll stick with the class scheme for now, but thanks for hinting and for the following, which i’m picking up on:

I didn’t realize that they’re now available through the helper classes - have to dig through the API more thoroughly i guess :blush: