JS Rule, setTimeout with access to variable not working

I’ve tried to implement a generic retriggerable timer with the setTimeout() method in a JS Rule with this function (OH 4.1.1):

function switchTimer(switchItem, seconds) {
  this.myTimersMap = (this.myTimersMap === undefined) ? null : this.myTimersMap;
  if(this.myTimersMap === null) {
    this.myTimersMap = new Map();
  }
  var timer = this.myTimersMap.get(switchItem.name);
  if(timer != null) {
    // delete already running timer for this item
    clearTimeout(timer);
  }
  timer = setTimeout((si) => "${si}".sendCommand("OFF"), seconds*1000, switchItem);
  this.myTimersMap.set(switchItem.name, timer)
  switchItem.sendCommand("ON");
}

I store the timerId in a global map to find it again after a retrigger event to clear it before creating a new timer for this item. The timer function is implemented as a lambda to switch off the item (usually a light) after the timer expires. Because of that, I need to access the item from inside the lambda function which was defined when the timer was created.

Regarding the JS Rule documentation, it’s possible to access variable with the ${…} syntax inside the lambda. But I can’t bring this to work. I’ve tried different versions (w/o mutable):

timer = setTimeout((si) => "${si}".sendCommand("OFF"), seconds*1000, switchItem);

or

var si = switchItem;
timer = setTimeout((sia) => "${sia}".sendCommand("OFF"), seconds*1000, si);

or

timer = setTimeout((switchitem) => ${switchItem}.sendCommand("OFF"), seconds*1000);

or

timer = setTimeout((si) => this.switchItem.sendCommand("OFF"), seconds*1000);

The log shows something like this:

TypeError: “${si}”.sendCommand is not a function

What am I doing wrong here?

As a rule I rarely if ever use setTimeout because it’s so limited. So I don’t have a lot of experience with it. But I think you have a misreading of what that section is talking about.

I really have no idea what that "${varname}" stuff is all about in the examples. If you are passing the variable as a parameter to the function, you use it inside the function the same as you’d use any variable passed to a function.

timer = setTimeout((si) => si.sendCommand("OFF"), seconds*1000, switchItem);

I wonder if the “${}” stuff is a mistake in the documentation markdown or something. @florian-h05 , do you have any ideas? I tried a bunch of stuff and can’t get anything reasonable to work using ${}, whether I pass the variable as a parameter or not.

Beyond that, this approach seems awfully low level and you are reimplementing a bunch of stuff that is already provided to you or available elsewhere.

For example, the cache would be a better choice than setting up your own map. For one thing when the rule gets unloaded all the timers get cancelled automatically. I know this works for OH Timers and think it works for setTimeout timers too. You don’t need to create a map to store the timers.

In fact, you don’t even need to create the timers at all. openHAB Rules Tools Announcements includes a TimerMgr that implements all of this for you. All of the code above would become:

var { TimerMgr } = require('openhab_rules_tools');
var tm = cache.private.get('timers', () => TimerMgr()); // create the timer manager if it doesn't already exist

function switchTimer(switchItem, seconds) {
  tm.check(switchItem.name, seconds*1000, () => switchItem.sendCommand("OFF"));
}

If you want to share these timers between different rules (which isn’t always possible when using setTimeout) change cache.shared.get() to use the shared cache with the timer manager.

The simple lambda usage works! I was a bit irritated because of the documentation with this “${}” mechanism…

Thanks a lot for this hint with the cache and the “openhab_rules_tools” - this is so much easier! I’m still in the migration phase from DSL to JS based Rules and still learning about the additional possibilities…

The example you are referring to tries to show that when accessing global vars, they can be mutated after timer creation.
The ${} stuff works in template literals, those only work when using backticks (the docs are correct). See also Template literals (Template strings) - JavaScript | MDN.

Maybe those do not work with UI rules? I used pretty much exactly the example in the docs and what got printed was the string literal “${somevar}”, not the value of the variable.

I have just copied the example from the docs into the scratchpad inside the UI, it works there.

I’m not sure what I’m doing wrong but there is definitely something wrong.

var foo = 'foo';

setTimeout((foo) => {
  console.log('This is "${foo}"');
  console.log('This is also ' + foo);  
}, 1000, foo);


var myVar = 'Hello world!';

// Schedule a timer that expires in ten seconds
setTimeout((myVariable) => {
  console.info(`Timer expired with variable value = "${myVariable}"`);
}, 1000, myVar); // Pass one or more variables as parameters here. They are passed through to the callback function.

myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello world!"

produces

2024-01-26 16:56:08.987 [INFO ] [nhab.automation.script.ui.scratchpad] - This is "${foo}"
2024-01-26 16:56:08.988 [INFO ] [nhab.automation.script.ui.scratchpad] - This is also foo
2024-01-26 16:56:08.989 [INFO ] [nhab.automation.script.ui.scratchpad] - Timer expired with variable value = "Hello world!"

I have something wrong and it’s not at all clear what it could be. But it definitely works when what ever I’m doing wrong is corrected since the example copied and pasted instead of transcribed seems to work. Maybe the quotes?

There are no backticks!

Correct solution:

var foo = 'foo';

setTimeout((foo) => {
  console.log(`This is "${foo}"`);
  console.log('This is also ' + foo);  
}, 1000, foo);


var myVar = 'Hello world!';

// Schedule a timer that expires in ten seconds
setTimeout((myVariable) => {
  console.info(`Timer expired with variable value = "${myVariable}"`);
}, 1000, myVar); // Pass one or more variables as parameters here. They are passed through to the callback function.

myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello world!"
1 Like