Reschedule timer

Hey I try to create a rule that let me use my shelly I4 buttons with different click events and for dimming while pressed.

This is the error when pressing my thing’s button:

21:02:44.072 [WARN ] [core.internal.scheduler.SchedulerImpl] - Scheduled job 'org.openhab.automation.script.ui.ShellyTestMultiPressHoldPatternRule.timeout.2' failed and stopped
org.graalvm.polyglot.PolyglotException: ReferenceError: "event" is not defined
        at <js>.timerEnd(<eval>:100) ~[?:?]
        at <js>.:=>(<eval>:135) ~[?:?]
        at com.oracle.truffle.polyglot.PolyglotFunctionProxyHandler.invoke(PolyglotFunctionProxyHandler.java:154) ~[bundleFile:?]
        at com.sun.proxy.$Proxy214.run(Unknown Source) ~[?:?]
        at org.openhab.automation.jsscripting.internal.threading.ThreadsafeTimers.lambda$1(ThreadsafeTimers.java:121) ~[bundleFile:?]
        at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$12(SchedulerImpl.java:191) ~[?:?]
        at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$1(SchedulerImpl.java:88) ~[?:?]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
        at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
        at java.lang.Thread.run(Thread.java:829) [?:?]
21:02:48.137 [WARN ] [se.californium.scandium.DTLSConnector] - Shutdown DTLS connector on [0.0.0.0/0.0.0.0:54795] executor not terminated in time!

This is my rule:

configuration: {}
triggers:
  - id: "2"
    configuration:
      itemName: shellyplusi4a8032ab1101c__19216817863_EingangTaste_1
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >-
        console.log("script called");


        // INIT

        var dimItem = items.getItem('WohnzimmerTischlampe_Helligkeit'); // set your dim Item here.

        var now = time.ZonedDateTime.now();

        var cacheId = "TestBtnMultiPressPattern5742"

        var storage = cache.private.get(cacheId, () => ({
          'timerId' : null,
          'count' : 0,
          'dimUp' : false,
          'dimTimerId' : 'TestBtnMultiPressPattern8956DimTimerId',
          'dimTimer' : null,
        }));


        var delay = 500;


        function cancelTimer (logMsg) {
          console.log(logMsg);
          if (storage.timerId === undefined) {
            console.log("timer === undefined");
          } else {
            clearTimeout(storage.timerId);
            cache.private.remove(cacheId);
            console.log("timer canceled");
          }
        }


        /*

        Attention: https://github.com/rkoshak/openhab-rules-tools#function-generators

        */


        function clickOne() {
          cancelTimer("Called function number " + storage.count);
        }


        function clickTwo() {
          cancelTimer("Called function number " + storage.count);
        }


        function clickThree() {
          cancelTimer("Called function number " + storage.count);
        }


        function clickFour() {
          cancelTimer("Called function number " + storage.count);
        }


        function clickFour() {
          cancelTimer("Called function number " + storage.count);
        }


        function dimFunction() {

          // Set dim direction
          if (dimItem.state == 100) {
            storage.dimUp = false;
            cache.private.put(cacheId, storage);
          } else if (dimItem.state == 0) {
            storage.dimUp = true;
            cache.private.put(cacheId, storage);
          }
          
          // Dimming step
          if (storage.dimUp == true){
            dimItem.sendCommand(INCREASE);
          } else {
            dimItem.sendCommand(DECREASE);
          }
          
          // reschedule dimmer to loop
          storage.dimTimer.reschedule(now.plusSeconds(1));

        }


        function timerEnd(n) {
          console.log("timerEnd triggered, state=" + event.oldItemState /*+ ", oldState=" + event.ItemState.toString()*/)
          if (event.oldItemState.toString() == "OFF") { // Short press cases
            switch (n){
              case 1:
                clickOne();
                break;
              case 2:
                clickTwo();
                break;
              case 3:
                clickThree();
                break;
              case 4:
                clickFour();
                break;
              default:
                cancelTimer("Clicked " + storage.count + " times.");
            }
          } else { // hold cases
            cancelTimer("Btn Timer stopped, hold action.");
            
            // set dim timer
            if (storage.dimTimer === null) {
              storage.dimTimer = actions.ScriptExecution.createTimer(storage.dimTimerId, now, dimFunction);
              cache.private.put(cacheId, storage);
            }
          }
        }


        if (event.itemState.toString() =="ON") { // only check button down state
          if (storage.timerId !== null)
            clearTimeout(storage.timerId);
          
          storage.count ++
          console.log("Click number " + storage.count + " detected.")
          storage.timerId = setTimeout( () => timerEnd(storage.count), delay);
          cache.private.put(cacheId, storage);
        }
    type: script.ScriptAction

Platform information:

  • Hardware: CPUArchitecture/RAM/storage RPI4 x64 8GB
  • OS: Ubuntu 20.04.5 LTS
  • Java Runtime Environment: openjdk 11.0.17 2022-10-18 OpenJDK Runtime Environment (build 11.0.17+8-post-Ubuntu-1ubuntu220.04)
  • openHAB version: 3.4.2

You are using what I call a function generator here. () => timerEnd(storage.count). That line creates a function that calls a function with the given arguments. This is a great way to make sure that all the variables in that generated function are fixed and available and won’t be replaced later when the rule runs again. However, anything that is not passed into that generated function does not get fixed.

event is kind of a special variable in that the add-on actually cleans that up after the rule is done running so that if the rule is triggered again later in a way that doesn’t populate the event Object (e.g. manually run) that the old value for event doesn’t stick around.

So when the timeout finally calls the generated function, event has been reset and no longer exists so it cannot be used in timerEnd.

Therefore, as a general rule of thumb, when you use a function generator like this, you need to pass everything that the function needs to run (in this case, pass event.oldItemSate.toString() as an additional argument to timerEnd.

Now before you think to not use a function generator, without the generator you’d still have the problem that event goes away when the rule exits. And if the add-on didn’t clean up event, than if the rule was run again before the timer ran the function, event would have the information for the new run of the rule, not the value when the timer was created. So function generators are a good thing. There’s even a section in the docs for timers that talks about them: JavaScript Scripting - Automation | openHAB.

3 Likes

Thank you! Now my code works as expected.