Use of Timers within OH 3

Ah, so this is just a UI Rules issue?
I guess JS also does not need the „this“ then in rules JS files?

What are those?

I meant JavaScript rules in files

That seems to be covered by -

javascript transforms are something different,just wanted to be sure.

1 Like

Thanks again for pointing that out to me!

Either will work. I use this. for consistency and to be explicit, especially with answers and answering questions. It raises unnecessary questions when I show this.MyTimer = (... and later use just MyTimer. So I add this.MyTimer just to make it very clear that it’s the same variable.

You can do this in text files too.

I want to be careful here. this.Bath_Timer is not a global. Global implies it is accessible everywhere and that is not the case with this.Bath_Timer. It is still only accessible in the Script Action/Condition in which it is defined. You cannot use this.Bath_Timer from another rule or even from another Script Action/Condition in the same rule.

The trick here is that the context (I don’t have a better word right now) represented by this that gets reused every time that the Script Action/Condition is called. So we can check to see if a variable is defined and if it is, reuse the value it already has instead of just blindly redefining the variable which is what happens when you just have var Bath_Timer.

I do not want people to think that this is a blanket replacement for the “global” variables people are used to in text based rules. In that case too, the variable isn’t really global to openHAB, but it is global to that one file which means it can be shared between all the rules defined in that file.

That actually isn’t the case here, and it’s the reason why the this trick works. The local workspace gets reused every time the Script Action/Condition is called. But what does happen is if you have a line like:

var Bath_Timer = null;

it doesn’t matter what Bath_Timer was set to the last time the Script Action/Condition ran, the variable will be reset to null and we will lose the handle on the Timer.

this.Bath_Timer = (this.Bath_Timer === undefined) ? null : this.Bath_Timer;

works because it doesn’t just blindly initialize Bath_Timer to null. It checks first to see if it’s already defined and uses whatever it’s currently set to instead of null.

The trick here isn’t that we’ve promoted Bath_Timer to become preserved across runs of the rule. That’s already happening. The trick is to avoid reinitializing it to the default value every time it runs so we don’t wipe out the old values.

Note that this reuse of the context happens for all the languages. What I don’t know though is whether all the languages have a way to determine if a variable is undefined like JavaScript does. I assume Python and Groovy do but that’s an assumption. I also assume that Rules DSL does not and that is a bit more educated of an assumption, but still an assumption. There should be a work around for this to create truly global variables using ScriptExtensions but I’ve not yet had a chance to figure out how to use them and I don’t even know if ScriptExtensions are available in Rules DSL.

Yes, anything defined in that way will be global to that file. When writing UI rules, there is no “outside of the rule” available so we can’t use global variables in that way through the UI.

It’s also true in .py and .js files and I assume Groovy too.

4 Likes

Thanks. That’s a beyond awesome explanation
Looking forward tinplate with groovy, as I like it a lot.
Are there any examples for full groovy rule files? I’d rather work with files for rules.

Thanks again!

There are not many Groovy users who are active on the forum. You’ll be largely on your own. But from the little I’ve seen it looks like a lot like Rules DSL in a lot of ways. I’ve never looked into it but my intent is to have Python, JavaScript, and Groovy versions of my openhab-rules-tools libraries so I’ll be learning more about it at some point I’m sure.

1 Like

If I can be of help, let me know. I’ll try to read up on how groovy with OH works.

I used the input from this conversation to build a simple timer rule with a dimmer item and switch. The dimmer item defines the time my heating should stay on. Switching on works, but the minutes set are not applied. Where am I doing it wrong?

triggers:
  - id: "1"
    configuration:
      itemName: Infrarotheizung_Switch
      command: ON
    type: core.ItemCommandTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: Infrarotheizung_Switch
      state: OFF
      operator: =
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      itemName: Infrarotheizung_Switch
      command: ON
    type: core.ItemCommandAction
  - inputs: {}
    id: "4"
    configuration:
      type: application/javascript
      script: >
        var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
        var ZonedDateTime   = Java.type("java.time.ZonedDateTime");
        var TimerMinutes    = (Infrarotheizung_Timer.state as DecimalType).intValue

        if (this.timer != undefined) {
          this.timer.cancel();
          this.timer = undefined;
        }

        function turnOff() {
          events.sendCommand("Infrarotheizung_Switch", OFF);
          this.timer = undefined;
        }

        this.timer = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(TimerMinutes), turnOff);
    type: script.ScriptAction

Add some logging so you can see what your variables are set to at each stage of the rule.

It would be simpler to just reschedule the timer if it already exists instead of cancelling it. But after you cancel it you don’t need to set it to undefined since you will overwrite it again in a few milliseconds anyway.

But your biggest problem is your trigger and your condition are mutually exclusive. Your trigger is only when Infrarotheizung_Switch is commanded to ON but your condition only lets the rule run if Infrarotheizung_Switch is OFF. Depending on the timing that will almost never be the case that the Item will still be OFF after receiving an ON command by the time that the condition checks the state.

Not sure if it is because of this line:

Can you try?

var TimerMinutes = items["Infrarotheizung_Timer"];
this.timer = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(TimerMinutes), turnOff);

This works for me.

Great, I amended the code with the advises of @rlkoshak and @michaeljoos
works now - thanks!

triggers:
  - id: "1"
configuration:
  itemName: Infrarotheizung_Switch
  command: ON
type: core.ItemCommandTrigger
conditions: []
actions:
  - inputs: {}
id: "2"
configuration:
  type: application/javascript
  script: >
    var logger          = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID); 
    var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");  
    var ZonedDateTime   = Java.type("java.time.ZonedDateTime");  
    var TimerMinutes    = items["Infrarotheizung_Timer"];

    logger.info("TimerMinutes: "+TimerMinutes);

    if (this.timer != undefined) {
      this.timer.cancel();
    }

    function turnOff() {
      events.sendCommand("Infrarotheizung_Switch", OFF);
      logger.info("Infrarotheizung turned "+items["Infrarotheizung_Switch"]);
      this.timer = undefined;
    }

    this.timer = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(TimerMinutes), turnOff);
type: script.ScriptAction

Sorry for my stupid question but is it possible to use timer as a global variable (e.g. to cancel/reschedule a running timer that was recently activated by the same rule) directly in DSL Rules (so the GUI in OH3)?

I guess not but for me its not very clear.

In the UI, no, there are no way to define a variable that will survive the one run of the rule. If you need this you either need to define your rule in a .rules file or you need to use JavaScript.

1 Like

Is the JavaScript way the one called ECMAScript in the OH3 UI?

Yes. Search the forum for examples. Particularly OH 3 Examples: Writing and using JavaScript Libraries in MainUI created Rules

1 Like

Thank you very much for the fast replies!
In the meantime I was able to solve my needs.

I wrote a script to use a egg timer with Rhasspy (voice assistant). Do you think it could be interesting for someone. If yes, where is a correct place to post it?

i have tried to posted solution with a timer and tried to rescheduled it in the lamda, but in the mainui the rule doesnt work correct. I get the failure:

1. Cannot refer to the non-final variable timer_sonos inside a lambda expression; line 29, column 1292, length 11
   2. Cannot refer to the non-final variable timer_sonos inside a lambda expression; line 33, column 1346, length 11

i have also tried with var ScriptExecution = …, but it doesnt work also.
how can i write a correct rule through the mainui with a time that is rescheduled every 5 seconds?

var Timer timer_sonos = null

else if (scenes.state.toString() == "sonos") {
  TasmotaS20_E2B765_state.sendCommand("ON")
  if(timer_sonos === null){
    timer_sonos = createTimer(now, [ |
      // Check the status of the Thing inside the Timer
      var thingStatus = getThingStatusInfo('mqtt:homeassistant_E2B765:local_mqtt:E2B765').getStatus().toString()
      if(thingStatus == "ONLINE")
      {
        SonosPlay1_Lautstarke.sendCommand(7)
        Sonos_Favorit.sendCommand("Hit List")
        timer_sonos = null            // line 29
      }
      else
      {
        timer_sonos.reschedule(now.plusSeconds(5))           // line 33
      }
    ])
  }
}

See Rich’s post above