Help with context of 'this' in javascript rules

I’ve setup a rule that notifies me when I left the bathroom window open cause I often forget to close it. The rule should remind me after half an hour and then every 15 minutes if the window still has not been closed. The following code is mostly working but apparently there is one flaw that must have something to do with the context of the keyword ‘this’ in the code.

What happens is:

  1. I open the window
  2. After 30 minutes I get a notification that the window is open for 30 minutes
  3. I close the window (the timer should be stopped since this is what always happens when the window handle is used)
  4. I receive a notification that the window has been closed (bottom of the code snippet) so the closing was detected
  5. 15 Minutes later I receive a notification that the window is open for 45 minutes
  6. This goes on every 15 minutes…

=> The outcome of 5 and 6 lets me know that in fact I do not cancel the 15 minute timer that was setup in the sendNotificationAndReschedule method. However I do not really understand why… Maybe someone with better javascript skills than me can explain what I do wrong here?

var NotificationAction = Java.type("org.openhab.io.openhabcloud.NotificationAction");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime = Java.type("java.time.ZonedDateTime");

// Reset timer when handle is used
if (this.timer != undefined) {
  this.timer.cancel();
  this.timer = undefined;
}

var sendNotificationAndReschedule = function(passedTime) {
  var msg = "Badezimmerfenster ist seit " + passedTime + " Minuten geöffnet";
  NotificationAction.sendBroadcastNotification(msg, "window", "Info");
  events.postUpdate('window_bath_notification_active', ON);
  
  var rescheduleTime = 15;
  var reschedule = ZonedDateTime.now().plusMinutes(rescheduleTime);
  this.timer = ScriptExecution.createTimerWithArgument(reschedule, passedTime + rescheduleTime, function(passedTime) {
    this.timer = undefined;
    sendNotificationAndReschedule(passedTime)
  });
};

// Window handle was opened
if(newState != "Geschlossen") {
  var in30Minutes = ZonedDateTime.now().plusMinutes(30);
  this.timer = ScriptExecution.createTimer(in30Minutes, function() {
    this.timer = undefined;
    sendNotificationAndReschedule(30);
  });
} else {
  // Send window closed notification when window was reported to be open before
  if(ir.getItem('window_bath_notification_active').state == ON) {
    var msg = "Badezimmerfenster wurde geschlossen";
    NotificationAction.sendBroadcastNotification(msg, "window", "Info");
    events.postUpdate('window_bath_notification_active', OFF);
  }
}

I guess I have to use call, apply or bind to explicitly set the context but this really a trial and error approach for me right now. What is generally the context of ‘this’ in a rule code?

This (sic) is something to do with creating a different Timer inside a Function which is called by a Timer. Because the Function is executed within the original Timer code, when the rule doesn’t exist, it might be using some “captured” version of it. Next time the rule runs, it creates a new version of the Function. Which one will the parallel Timer use, does that have the same ‘this’? It’s all rather headache inducing and I don’t know what is happening really.

Timers can reschedule themselves and I would recommend avoiding the issue by just having the original Timer do that. No Function.

Thank you @rossko57!

Rescheduling the timer was a great hint! This also makes the code a lot less complex:

var NotificationAction = Java.type("org.openhab.io.openhabcloud.NotificationAction");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime = Java.type("java.time.ZonedDateTime");
var ChronoUnit = Java.type("java.time.temporal.ChronoUnit");

// Reset timer when handle is used
if (this.timer != undefined) {
  this.timer.cancel();
  this.timer = undefined;
}

// Window handle was opened
if(newState != "Geschlossen") {
  var openingTime = ZonedDateTime.now();
  var in30Minutes = openingTime.plusMinutes(30);
  this.timer = ScriptExecution.createTimerWithArgument(in30Minutes, openingTime, function(openingTime) {
    var currentTime = ZonedDateTime.now();
    var minutesOpen = openingTime.until(currentTime, ChronoUnit.MINUTES);
    var msg = "Badezimmerfenster ist seit " + minutesOpen + " Minuten geöffnet";
    NotificationAction.sendBroadcastNotification(msg, "window", "Info");
    events.postUpdate('window_bath_notification_active', ON);
    this.timer.reschedule(currentTime.plusMinutes(15));
  });
} else {
  // Send window closed notification when window was reported to be open before
  if(ir.getItem('window_bath_notification_active').state == ON) {
    var msg = "Badezimmerfenster wurde geschlossen";
    NotificationAction.sendBroadcastNotification(msg, "window", "Info");
    events.postUpdate('window_bath_notification_active', OFF);
  }
}

Works, like a charm :slight_smile:

I can try to be the aspirin to your headache.

Let’s step back to first principles in Object Oriented Programming (OOP). We have two concepts, a Class and an Object. The Class is like the blueprint for a car and the Object is a specific car sitting in the parking lot. Put another way, the Class defines what data and methods are possible and the Object actually carries a specific set of data and has the methods.

There is a one-to-many relationship between a Class and Objects.

OK, I hope you are with me so far. In many programming languages there is a special keyword for an instance of an Object to refer to itself. In Python that keyword is self, in C++/Java/JavaScript and others that keyword is this. So think of it like a variable that is automatically a part of an Object that contains a pointer to itself.

Now let’s switch to talking about OH UI created rules. Each rule is an Object. And that Object gets reused each time the rule is triggered.

Finally, JavaScript there is an ability to test whether a variable exists in a given Object or not by comparing it to undefined.

So let’s put this all together.

  1. Each rule is a separate Object
  2. The same rule Object gets reused every time the rule is triggered instead of a new rule Object being created
  3. The keyword this is used inside an Object to refer to itself; in this case this is a pointer to the rule Object
  4. You can test to see if a variable already exists by comparing it to undefined

Therefore, you can test to see if the variable timer is already defined in the Object using if(this.timer === undefined). If a variable named timer were created the last time the rule ran, that condition will return false so we can avoid overwriting that variable this time the rule is running and reuse its old value.

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

The above line tests to see if this.timer already exists. If it doesn’t we create the variable and initialize it with null (which is very much not the same thing as undefined. Otherwise we just assign this.timer to itself which is a NoOp (i.e. doesn’t do anything, preserving the value of this.timer). The (condition) ? value1 : value2 is a ternary operator and short hand for:

if(condition){
    this.timer = value1;
}
else {
    this.timer = value2;
}

tl;dr: this is how an Object refers to itself. We can use this and the ability to test for whether a variable is undefined to avoid resetting the value of a variable every time the rule runs, therefore preserving the value from the last time it ran.

1 Like

That’s all very well but does not explain how the original rule malfunctions. :wink:

Something to do with the defined Function, I still suspect. Its created a Timer and put the handle into this, but when the rule runs again later and cancels.timer it appears not to be the same this? Is a Function’s this itself, or undefined? A tell-tale log in the cancelling code might offer a clue.

I was about to add some logging when you came up with your suggestion. Since your suggestion is the better solution anyway I didn’t bother to investigate any further but I will be cautious when using functions in combination with timers in the future :wink:

The first time the rule runs the following actions occur:

this.timer is undefined
newState != “Geschlossen”

  1. this.timer is set to a timer that goes off in 30 minutes
  2. 30 minutes pass and the timer’s function runs
    a. this.timer is restored to undefined
    b. sendNotificationAndReschedule is called with 30
    i. Notification is sent
    ii. this.timer is set to a timer that goes off in 15 minutes
  3. The window is closed. this.timer is not undefined (or is it? this is where logging is required)

Given the behavior what is probably happening is, as you surmise, the this inside the sendNotificationAndReschdule function is not the same this as outside the function. This is in fact a good thing because otherwise when the timer created in that function goes off subsequent executions of the rule may have changed the variables.

I can add a small side note here: In an attempt to break out of the never ending notification loop without rebooting OH I removed/commented out the code to reschedule a new notification in the next execution of sendNotificationAndReschedule. However notifications kept on coming so obviously the code modification had no impact on the already running rule execution. So obviously “this” in the already running rule must have referenced to a different object :wink:

Usually that will generate errors when the Timer goes off. It’s very unusual that it didn’t as you implemented it and it strongly implies that somehow the Timer did in fact get separated from the rule that created it.

JavaScript is a little weird when it comes to the scope of variables and I’m sure this is one of those cases where it’s something that gets learned with experience more than anything. I bet it’s because of the creation and setting of the this.timer inside a function like that. I’m no JS expert, but I see everywhere the use of functions to control the scope of variables. I bet it’s related to that.