openhab-scripters / openhab-helper-libraries

Scripts and modules for use with openHAB
Eclipse Public License 1.0
88 stars 68 forks source link

Add syntactic sugar in rules to check if item maintains state for specified duration #342

Closed boc-tothefuture closed 4 years ago

boc-tothefuture commented 4 years ago

Is your feature request related to a problem? Please describe. It is verbose and potentially error prone for users to implement a fairly common automation of "do this after XXX is in state YYY for duration DDD". For example, send notification if front door left open for 5 minutes.

It would be nice for syntactic sugar to exist to abstract this from the user. It can be even more complex when working with groups.

Describe the solution you'd like Syntactic sugar added to the helper language to facilitate these fairly common rules. Example would look like:

@when("Item foo change to ON for 5m")

or

@when("Member of foos changed to ON for 30s")

The syntactic sugar would be desugared to create a timer (of appropriate duration) linked to the item. When the timer expires the defined code block would execute. If the item state changed the timer would be cancelled.

Describe alternatives you've considered There are multiple alternatives, all with drawbacks.

  1. Creating and managing timers is error prone and verbose.
  2. Using dummy items with expire bindings is also possible, but this creates more to manage (for no real benefit).

Additional context

5iver commented 4 years ago

Thank for for submitting this feature request, but the core helper libraries are only a temporary crutch until the rule engine has been further built out with features. It would be possible to implement something like this in Jython, but by implementing it in the rule engine, it could be used in rules built through the UI rule editor and any scripting language. There have been discussions about implementing Actions or Profiles as a replacement for the Expire binding functionality in OH 3.0.

That said, Area Triggers and Actions implements similar functionality by just adding some Item metadata.

mjcumming commented 4 years ago

@boc-tothefuture what is the use case for this scenario?

boc-tothefuture commented 4 years ago

@mjcumming - In this particular case I want to send a notification if any of my ESPHome devices go offline for more than 30 minutes. I have done this in the past with virtual items w/ the expire binding and creating and maintaining my own timers. However, I wanted an easier solution with little to no boilerplate.

5iver commented 4 years ago

I use some ESP12Es and check that they are still online with the network binding. Here is the metadata I use to get a notification when either are offline for more than an hour...

set_metadata("Status_Network_ESP12E_01", "area_triggers_and_actions", {
    "actions": {"notification_action": {"OFF": {"delay": 3600, "types": {"audio": True, "mobile": True}, "Priority": 0, "recurring": True}}}}, overwrite=True)
set_metadata("Status_Network_ESP12E_02", "area_triggers_and_actions", {
    "actions": {"notification_action": {"OFF": {"delay": 3600, "types": {"audio": True, "mobile": True}, "Priority": 0, "recurring": True}}}}, overwrite=True)
rkoshak commented 4 years ago

https://github.com/openhab-scripters/openhab-helper-libraries/pull/233 provides a library that does all the Timer management book keeping for you. With one function call (check()) you can create a new timer or reschedule it if it already exits. You pass it up to two functions, one that optionally gets called if the Timer already existed when check was called and the other that gets called when the Timer expires.

So for the simple case where you want a Timer to check when foo is ON for five minutes

from core.rules import rule
from core.triggers import when
from community.timer_mgr import TimerMgr # probably should go in personal.timer_mgr until/if it gets merged

timers = TimerMgr()

def on_too_long(itemName):
    # what you do when the timer expires

@rule("timers for being on too long")
@when("Item foo changed to ON")
def changed_on(event):
    timers.check(event.itemName, 300000, lambda: on_too_long(event.itemName))

With the above code, if no Timer exists when check is called with the name event.itemName, one is created. If check is called before the timer triggers it gets cancelled. If check isn't called before the five minutes is up on_too_long gets called.

The library also has the option to reschedule the timer instead of cancelling it and you can pass an optional function to it to that gets called when check is called and the Timer already exists, useful for sending alerts that an Item is flapping for example.

boc-tothefuture commented 4 years ago

@rkoshak - This does not cancel the timer if the item get changed to "OFF", Correct? That logic would still need to be implemented (potentially in the on_too_long method).

5iver commented 4 years ago

This issue is resolved and the requested feature will not be implemented. If you wish to discuss alternatives, please do so in the forum.