TypeError: timer.reschedule is not a function

Since a recent upgrade my timer has stopped working. I went from 3.0.4.M? to 3.4.0.M4 to 3.4.0.M5 (I was getting a different error with M4)

Now I get the following error in a rule using a script using ECMAScript-2021

TypeError: waketimer.reschedule is not a function

Looking at the docuemntation (JavaScript Scripting - Automation | openHAB) it looks like timer.reschedule is still a function (and it was working). Any pointers to what I’ve missed? Code below.

Thanks,
Matthew

// Script to increase light level slowly over time

var Wakeup_StartLevel = 0;      //Start at this level - Useful if too dim at min level
var Wakeup_EndLevel = 100;      //End at this level - Useful if too bright at max level
var Wake_Over = 180;            //How long for the transition (seconds 1800s = 30 mins)
var Delay = Wake_Over / ( Wakeup_EndLevel - Wakeup_StartLevel );

var Level = Wakeup_StartLevel;

console.log("WakeUpLight - Rule Starting");
console.log("Wake Light",items.getItem("Wake_Up_Lights_Master_Bed").state);
var waketimer = setTimeout(() => {  
  if ( (Level < Wakeup_EndLevel) && (items.getItem("Wake_Up_Lights_Master_Bed").state=="ON") )  {
    console.log("WakupLight - Setting level to: "+Level); 
    items.getItem("MasterBedMainLightsSamotechHKDIMA_DIMMABLE_LIGHTLevelControl").sendCommand(Level);
    Level++;
    waketimer.reschedule(time.ZonedDateTime.now().plusSeconds(Delay));
  } else {
    clearTimeout()
    console.log("WakupLight - Woke");
    var offtimer = setTimeout(() => { items.getItem("MasterBedMainLightsSamotechHKDIMA_DIMMABLE_LIGHTLevelControl").sendCommand("OFF"); }, 900000); // Turn off after 15 mins (900k milliseconds)
  }
}, 1000);

It looks like you are using setTimeout. I think reschedule is a method of openhab timers. You use createTimer for that.

But the documentation (linked above) uses setTimeout and .reschedule as an example?

var timer = setTimeout(() => { console.log('Timer expired.'); }, 10000); // Would log 'Timer expired.' in 10s.
if (timer.isActive()) console.log('Timer is waiting to execute.');
timer.cancel();
if (timer.isCancelled()) console.log('Timer has been cancelled.');
timer.reschedule(time.ZonedDateTime.now().plusSeconds(2)); // Logs 'Timer expired.' in 2s.

Have a look to thread [Js Scripting] Why are we deprecated createTimer? as far as I understand parts have been discussed there.

To provide a tl;dr of the link @Wolfgang_S posted, there is a limitation in GraalVM that doesn’t allow multithreading. If a Timer is scheduled to run at the same time as another Timer from the same rule an error occurs.

There was a first attempt to fix this by simply dropping support for openHAB Timers altogether and only use JavaScript native timeouts instead. Almost as soon as that was implemented, a change was made to the core that allows createTimer to be supportable once again.

But I don’t know if setTimeout() was restored so it returns an openHAB Timer or if it still returns a JavaScript timeout (i.e. an integer ID for the timeout that was created).

Log out what is returned by setTimeout(). If it’s just a number, then you are getting a JavaScript timeout which are, indeed not reschedulable. You’ll have to cancel and recreate those. Otherwise, you’ll need to use createTimer() to create an openHAB Timer.

2 Likes

Yes, looks like an integer returned, so a JavaScript timeout. Looks like I’ll have to try and figure out how to adjust my code :frowning:

Thanks!

So my solution was to use setInterval. I was trying to mangle the code to use setTimer and timer.reschedule to do what was alread implemented in setInterval.

Thanks again Rich

I find the docu lacks A LOT for JS. For example no one tells you to require('openhab') for using some NECESSARY functions …

In OH 3.4 you can use ThreadsafeTimers.createTimer. Here’s a example from my Hue motion sensor:

const brightnessThreshold = 150;
const timerThreshold = "PT5M";

rules.JSRule({
  name: "onFittingMotion",
  triggers: [triggers.ItemStateUpdateTrigger("Bewegung_Ankleidezimmer", "ON")],
  execute: (event) => {
    if (this.myFittingTimer !== undefined) this.myFittingTimer.cancel();

    let fittingBrightness = items.getItem(
      "Bewegung_Ankleidezimmer_Helligkeit"
    ).state;

    if (fittingBrightness < brightnessThreshold) {
      items.getItem("AnkleidezimmerLampe_Power").sendCommand("ON");

      let bis = time.toZDT(timerThreshold);
      this.myFittingTimer = ThreadsafeTimers.createTimer(
        bis,
        resetFittingTimer
      );
    }
  },
});

In resetFittingTimer I test for existing timer:

const resetFittingTimer = () => {
  if (this.myFittingTimer !== undefined) {
    this.myFittingTimer.cancel();
    this.myFittingTimer = undefined;

    items.getItem("AnkleidezimmerLampe_Power").sendCommand("OFF");
  }
};

The only thing I still couldn’t work out is to send ON or OFF command to a group ¯_(ツ)_/¯

If you haven’t changed the default setting in MainUI, you don’t need to. That gets injected by default. Only if you turn it off do you have to require the openhab library.

If you require openhab, you get everything.

In OH 3.4 setTimeout, setInterval, and actions.ScriptExecution.createTimer are all now thread safe now. There is no need to use ThreadSafeTimers isn’t exported as part of the public part of the openhab-js library because it’s expected to be used internally. It’s what gets used any time you create any Timer through the openhab-js library. Only if you pull the Java ScriptExecution class and work with it do you need to worry about multi-threaded problems now.

1 Like

Hey rlkoshak,

first of all happy new year and thanks for clarifying the require thing :slight_smile: ! I removed the require and it’s still working. Unfortunately now I’m missing the great intellisense in VS Code!

Can you tell me why the PR states ThreadsafeTimers when createTimer should be threadsafe by default? The example uses ThreadsafeTimers btw.
You can find the PR here:
https://github.com/openhab/openhab-addons/pull/13695

That’s something what I meant with Lack of documentation. I can’t find a single word about why createTimer is threadsafe by default. This wasn’t the case with OH 3.3 - I failed most of the time writing timers in JS using setTimeout, setInterval and createTimer in OH.

Furthermore:
In the documentation it says actions.ScriptExecution.createTimer (you can find it here https://www.openhab.org/addons/automation/jsscripting/#scriptexecution-actions) which confuses me more! A lot of examples stating createTimer (I guess that’s in Java?) without any namespace / class. There’s no word about threadsafe implementation.

I hope you see why it is often a little bit frustrating when searching the docs or reading the questions in the community area when there are plenty of different solutions and different implementations.

Hello, first of all, I wish a happy new year to all of you!

I‘ve been notified by GitHub that one of my PRs was mentioned here, so I‘ve seen that thread and I will answer to most of the posts. (Although this is with some delay, I have to clarify a few things.)

When you upgraded with apt, this shouldn‘t be a surprise for you:
When upgrading to 3.4.0.M5 or higher, apt displays a warning:

JavaScript Scripting Automation: 'setTimeout' and 'setInterval' return a timerId (a positive integer value) as in standard JS instead of an openHAB Timer.

Correct :+1:

When you use a 3.4.0.Mx Milestone Build, you should also use the 3.4.0 documentation and not the 3.3.0 documentation.
The same would apply now when using 4.0.0 Builds, use the latest documentation, there is a option for that in the docs:

setTimeout and setInterval return timeout/interval ids as NodeJS and the browser do. The docs state that clearly (that doesn‘t go to you Rich, I guess you read the (correct version) docs).

As Rich already pointed out, you don‘t need to (as long as you haven‘t disabled the automatic injection).

You are not supposed to to so! Use actions.ScriptExecution.createTimer.

That‘s because IntelliSense needs to know where actions etc. come from and it also needs the openhab package installed to have access to the type definitions. Please do not forget to upgrade the openhab npm package when upgrading openHAB: otherwise documentation is out of sync for your installation and you don‘t have the npm package version that is tested to work with your openHAB version.

As a side note: The code examples in PRs are not meant to be used or taken as a examples for/by end-users, they are meant for other devs!
Please do not get inspired by code from JS Scripting PRs at the openhab-addons repo.

As Rich already stated, the library takes care of exporting the thread-safe version of createTimer to actions.ScriptExecution.createTimer.

Some background:
You have the raw Java ScriptExecution, createTimeout from this is not thread-safe!
Then you have ThreadsafeTimers that provides a thread-safe version of createTimer.
The openHAB JavaScript library exports the one from ThreadsafeTimers, therefore actions.ScriptExecution.createTimer is thread-safe by default.

You are should neither use the raw Java ScriptExecution nor ThreadsafeTimers!

Because you don‘t need to know why. If we added the technological explanation to the docs, that would only confuse those users who just read the documentation. Just follow the documentation and enjoy a thread-safe createTimer.

These are examples for Rules DSL (the syntax is a bit like Java), which is another rule language and behaves differently. You cannot mix up examples of different languages, that‘s like you compare apples to pears.
And there is no word about thread-safe implementation because Rules DSL is different to JS Scripting when we talk about multi-threading.

When reading the JS Scripting docs, there is only one implementation of createTimer shown.

2 Likes

Also check the date on that reply. It was written before the docs got updated.

Might be true, but I’ve just checked and I updated the docs just one day after I changed setTimeout and setInterval.

I’m not sure all the PRs that were flying around at that time had been merged yet when I wrote that reply. I know it was before the thread safe timers PR was completed.

Regardless, it’s in the docs now and that’s what matters. It’s a great addition to JS Scripting and OH overall.

1 Like

Thanks for your hint, I updated my rule to match the examples shown in the documentation. This works like a charm, so thank you for pinpoint me and sorry for being a bit slow :wink: Here’s the code just to provide a working example. If you have any improvement, please let me know.

const brightnessThreshold = 150;

rules.JSRule({
  name: "onFittingMotion",
  triggers: [triggers.ItemStateUpdateTrigger("Bewegung_Ankleidezimmer", "ON")],
  execute: (event) => {
    if (this.myFittingTimer !== undefined) {
      this.myFittingTimer.reschedule(time.ZonedDateTime.now().plusMinutes(5));
    } else {
      let fittingBrightness = items.getItem(
        "Bewegung_Ankleidezimmer_Helligkeit"
      ).state;
  
      if (fittingBrightness < brightnessThreshold) {
        items.getItem("AnkleidezimmerLampe_Power").sendCommand("ON");
  
        let bis = time.toZDT(timerThreshold);
  
        this.myFittingTimer = actions.ScriptExecution.createTimer(
          bis,
          () => {
            this.myFittingTimer.cancel();
            items.getItem("AnkleidezimmerLampe_Power").sendCommand("OFF");
          }
        );
      }
    }
  },
});

I would use the cache to store the timer and not the local context (this), I am not sure if it is actually possible to store something in the context of a file based rule since the context is not re-used.

Using the cache would also have the benefit, that when you edit the script file, a running timer would be cancelled so that you don‘t have a unmanageable timer.

How does that sound to you? If it sounds good, I can modify your code.

I think I saw some example slightly above that topic:
https://www.openhab.org/docs/tutorial/rules_advanced.html#but-only-if-conditions

I had to adjust my rule once again… I have 3 motion sensors switching the same lamps on / off. Unfortunately there are multiple timers created / rescheduled for some reason I don’t know. When replacing reschedule with cancel it works. Is there anything I’m doing wrong with reschedule? Right now my lights are going on / off like in a disco when passing the sensors a few times after a couple of minutes.

Trying the cache thing tomorrow (due to much work this week and less patience from my better half :slight_smile:) and will report if I’m facing any problems.

That is because you cannot store something inside this. The context of the rule is not reused and therefore all variables locally defined in a rule‘s execute are not persisted between rule runs.
When one of your motion sensors triggers, a timer is created each time.

This is the reason why you need the cache: it persists between subsequent runs of a rule.

I have modified the code you posted, please report if it works:
This should save you some time for your better half :wink:

const brightnessThreshold = 150;

rules.JSRule({
  name: "onFittingMotion",
  triggers: [triggers.ItemStateUpdateTrigger("Bewegung_Ankleidezimmer", "ON")],
  execute: (event) => {
    let timer = cache.private.get("onFittingMotionTimer");

    if (timer !== null) {
      timer.reschedule(time.ZonedDateTime.now().plusMinutes(5));
    } else {
      let fittingBrightness = items.getItem(
        "Bewegung_Ankleidezimmer_Helligkeit"
      ).state;
  
      if (fittingBrightness < brightnessThreshold) {
        items.getItem("AnkleidezimmerLampe_Power").sendCommand("ON");
  
        let bis = time.toZDT(timerThreshold);
  
        timer = actions.ScriptExecution.createTimer(
          bis,
          () => {
            this.myFittingTimer.cancel();
            items.getItem("AnkleidezimmerLampe_Power").sendCommand("OFF");
          }
        );
        cache.private.put("onFittingMotionTimer", timer);
      }
    }
  },
});

I guess you understand how to use the cache from that code.

1 Like

Thank you very much @florian-h05 for your appreciated help!

Here’s my final - at least for me working - code:

const brightnessThreshold = 50;
const timerThreshold = 5;

rules.JSRule({
  name: "onMotion",
  triggers: [
    triggers.ItemStateUpdateTrigger("Bewegung_Eingang", "ON"),
    triggers.ItemStateUpdateTrigger("Bewegung_Flur", "ON"),
    triggers.ItemStateUpdateTrigger("Bewegung_Ankleidezimmer", "ON"),
  ],
  execute: (event) => {
    let timer = cache.private.get("onMotionTimer");

    if (!timer) {
      let floorBrightness = items.getItem("Bewegung_Flur_Helligkeit").state;

      if (floorBrightness < brightnessThreshold) {
        const flurLichter = items.getItemsByTag("Flur_Licht");
        flurLichter.forEach((item) => {
          item.sendCommand("ON");
        });
        let bis = time.toZDT(`PT${timerThreshold}M`);

        timer = actions.ScriptExecution.createTimer(bis, () => {
          flurLichter.forEach((item) => {
            item.sendCommand("OFF");
          });

          cache.private.put("onMotionTimer", null);
        });

        cache.private.put("onMotionTimer", timer);
      }
    } else {
      timer.reschedule(time.ZonedDateTime.now().plusMinutes(timerThreshold));
    }
  },
});
1 Like