Alternative to multiple setTimeout statements in JS rules

I was looking for a way to turn on ceiling spot lights in sequence. My first approach was to use setTimeout with a 500ms delay between each sendCommand statement. That resulted in a “Multithreaded Access Error.” After some searching in the forums it seems multiple setTimout statements aren’t allowed.

I googled around and found another solution that worked. A sleep-like function that uses javascript’s Promise object and setTimeout. You can find it here: https://www.sitepoint.com/delay-sleep-pause-wait/

Here is what my ECMA-11 script looks like:

var trigger = event.itemName.toString();
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
if(trigger == "LivingroomSensor_MotionAlarm"){
  items.getItem("HuespotHallway2_Color").sendCommand("5,100,30");
  sleep(600)
    .then(() => items.getItem("HuespotHallway1_Color").sendCommand("5,100,30"))
    .then(() => sleep(600))
    .then(() => items.getItem("HuespotDR4_Color").sendCommand("5,100,30"))
  setTimeout(()=>{if(items.getItem("HueRoomLR_Power").state == "OFF"){
    items.getItem("HuespotHallway2_Color").sendCommand("30,65,0");
    sleep(600)
      .then(() => items.getItem("HuespotHallway1_Color").sendCommand("5,100,30"))
      .then(() => sleep(600))
      .then(() => items.getItem("HuespotDR4_Color").sendCommand("5,100,30"));}},120000)
}
  else {
    items.getItem("HuespotDR4_Color").sendCommand("5,100,30");
    sleep(600)
      .then(() => items.getItem("HuespotHallway1_Color").sendCommand("5,100,30"))
      .then(() => sleep(600))
      .then(()=> items.getItem("HuespotHallway2_Color").sendCommand("5,100,30"))
    setTimeout(()=>{if(items.getItem("HueRoomLR_Power").state == "OFF"){
      items.getItem("HuespotDR4_Color").sendCommand("30,65,0");
      sleep(600)
        .then(() => items.getItem("HuespotHallway1_Color").sendCommand("30,65,0"))
        .then(() => sleep(600))
        .then(() => items.getItem("HuespotHallway2_Color").sendCommand("30,65,0"));}},120000)
  }

Please note: I am not a programmer. I was just luckily able to find a solution on the internet and cobble it into a rule. I can’t promise to answer any technical questions on this technique. Hopefully the link will be able to answer any questions you may have. I just wanted to share my solution that worked.

1 Like

This is a great example for how to use Promise. Thanks for posting!

Not quite. You can create as many as you want, but only one can run at a time. If you are careful to stagger the times so they don’t run at the same time they will work fine.

Another way you can sequence events is to use the Gatekeeper design pattern which I’ve provided as a part of openhab_rules_tools which you can install using npm.

So the above could be:

var {gatekeeper} = require('openhab_rules_tools');

var gk = cache.put(ruleUID+'_gatekeeper', () => new gatekeeper.Gatekeeper());

if(trigger = 'LivingroomSensor_MotionAlarm') {
  gk.addCommand(600, () => { items.getItem('HuespotHallway2_Color').sendCommand('5,100,30'); });
  gk.addCommand(600, () => { items.getItem('HuespotHallway1_Color').sendCommand('5,100,30'); });
  gk.addCommand('PT2m', () => { items.getItem('HuespotDR4_Color').sendCommand('5,100,30'); });
  gk.addCommand(0, () => {  if(items.getItem("HueRoomLR_Power").state == "OFF") {
      gk.addCommand(600, () => { items.getItem("HuespotHallway2_Color").sendCommand("30,65,0"); } );
      gk.addCommand(600, () => { items.getItem("HuespotHallway1_Color").sendCommand("5,100,30"); } );
      gk.addCommand(0, () => { items.getItem("HuespotDR4_Color").sendCommand("5,100,30"); } );
    }
  }; );
}
else { 
  ...

The first number is how long to wait after the passed in command is executed before running the next command. So the first line will command 'HuespotHallway2_Color, wait 600 msec and then run the command to HuespotHallway1_Color, wait 600 msec then run the command to HuespotDR4_Color. Then it'll wait two minutes (see the docs for time.toZDT()` in the JS Scripting addon for details on how that works) and run the if statement. If the if statement evaluates to true it adds those commands to the list with the indicated 600 msec spacing.

I’m not saying this is necessarily better, just pointing out alternatives. The reason why this works is that there is only the one timer in Gatekeeper. So you can never have two running at the same time.

1 Like