Openhab 4.2.0 and timers in Javascript

I have tried to rescript for openhab 4.2.0 a javascript for simple timercreation/retrigger as long the device draws current und switch off if not.

var now = time.ZonedDateTime.now();

// Function to run when the timer goes off.  
function switch_off () {  
  console.info('WaschOFF timer run out Washingmachine switched off now');  
  items.getItem('Keller_WaschmaschineOnOff').sendCommandIfDifferent('OFF');
}

//create timer if not yet done
if (cache.shared.exists('WMTimer') === false || cache.shared.get('WMTimer').hasTerminated()) {
  cache.shared.put('WMTimer', actions.ScriptExecution.createTimer('WMTimer', now.plusMinutes(12), switch_off, ));
  console.info('WaschOFF timer created');
} 
//reschedule if timer active and washingmachine still running
else if (cache.shared.get('WMTimer').isActive()&& items.getItem("Strom_Waschmaschine").numericState<0.300){
  cache.shared.get('WMTimer').reschedule(now.plusMinutes(6));
  console.info('WaschOFF timer rescheduled');
}
else {
  console.info('WaschOFF timer NOT rescheduled');
  
}

Im seeing the logfile:

2024-08-01 18:13:40.434 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer created
2024-08-01 18:13:44.200 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 18:15:26.879 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer created
2024-08-01 18:15:35.480 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 18:15:43.607 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 18:18:01.537 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer created
2024-08-01 18:18:13.829 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 18:18:21.633 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 18:18:29.968 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 18:19:40.948 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer created


My question is why it is creating multiple timer and does not remember that there is one existing which it could reschedule or not reschedule if no current? I dont understand why it is creating new ones. Could someone give me a hint?

What’s the state of Strom_Waschmaschine at all of these times? How is this rule triggered, on changes to Strom_Waschmaschine? I suspect what’s happening is Strom_Waschmaschine is changing but remaining under 0.300. But you create a new timer no matter what Strom_Waschmaschine is if the previous timer has terminated.

if(items.Strom_Waschmaschine.quantityState.greaterThan(Quantity('300 W')) {
  //create timer if not yet done
  if (cache.private.exists('WMTimer') === false || cache.private.get('WMTimer').hasTerminated()) {
    cache.private.put('WMTimer', actions.ScriptExecution.createTimer('WMTimer', now.plusMinutes(12), switch_off, ));
    console.info('WaschOFF timer created');
  } 
  //reschedule if timer active and washingmachine still running
  else if (cache.private.get('WMTimer').isActive()){
    cache.private.get('WMTimer').reschedule(now.plusMinutes(6));
    console.info('WaschOFF timer rescheduled');
  }
}
else {
  console.info('WaschOFF timer NOT rescheduled');  
}

Other notes:

  • Why are you using the shared cache? Is there another rule that needs access to this Timer? If not, use the private cache.
  • Shouldn’t it be rescheduling when it’s using more that 0.300 ?

The code as written but it’s a little overly complicated.

First of all, if I were to write this I’d use openHAB Rules Tools Announcements LoopingTimer.

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

function switch_off () {
  if(items.getItem("Strom_Waschmaschine").quantityState.lessThan(Quantity('0.300 W'))) {
    console.info('WaschOFF timer run out Washingmachine switched off now');  
    items.Keller_WaschmaschineOnOff.sendCommandIfDifferent('OFF');
    return null;
  }
  else {
    return 'PT6M';
  }
}

if(items.getItem("Strom_Waschmaschine").quantityState.lessThan(Quantity('300 W'))) {
  var timer = cache.private.get('WMTimer', () => LoopingTimer());
  if(timer.hasTerminated()) timer.loop(switch_off, time.toZDT('PT12M'));
}

How it works:

  • Import LoopingTimer
  • We use the private cache except where we need to share a value between rules
  • The cache.x.get() method will take an optional second argument that is a function that gets called to populate the property in the cache. So we use this to create the LoopingTimer if it doesn’t already exist. Then we call loop() only if it hasn’t terminated.
  • The way LoopingTimer works is it reschedules itself based on the return value of the timer function. When null is returned the timer exits. When a time is returned (can be anything supported by time.toZDT() it reschedules itself at that time.
  • We use time.toZDT() and ISO8601 Duration strings to define the time for the Timer to run.

All the timer management stuff is handled by LoopingTimer.

But if you did it inline the code could be simpler. If you schedule the timer initially for the same amount as you reschedule it the code can reduce to:

function switch_off () {
  console.info('WaschOFF timer run out Washingmachine switched off now');  
  items.getItem('Keller_WaschmaschineOnOff').sendCommandIfDifferent('OFF');
}

var TIMER_NAME = 'WMTimer';

if((items.getItem("Strom_Waschmaschine").quantityState.lessThan(Quantity('300 W'))) {
  const timer = cache.private.get(TIMER_NAME);
  if(timer === null || timer.hasTerminated()) {
    cache.private.put(TIMER_NAME, actions.ScriptExecution.createTimer(TIMER_NAME, time.toZDT('PT12M'), switch_off);
    console.info('WaschOFF timer created');
  }
  else {
    timer.reschedule(time.toZDT('PT6'));
    console.info('WaschOFF timer rescheduled');
  }
}
else {
  console.info('WaschOFF timer NOT rescheduled');
}

While it’s not really less lines of code, it’s a bit shorter because you are not constantly needing to reference the cache over and over sometimes multiple times on the same line of code. Also, because we use it several times, it’s a good idea to set the key for the timer in the cache to a variable to avoid typos.

Hi Rich,

thanks for your reply. To your questions:

  • Why are you using the shared cache? Is there another rule that needs access to this Timer? If not, use the private cache.
    → No other rules accessing the timer. I had a lot of trouble with rewriting and so i tried blockly and was inspired by generated code with shared cache.
  • Shouldn’t it be rescheduling when it’s using more that 0.300 ?
    → The rule is triggered by 2 items. The current (“Strom_Waschmaschine”) if changed OR by ‘Keller_WaschmaschineOnOff’ changeto ON if the button on knx which is behind this item is pressed and goes to on. The current can than 0.001 A up to about 16A. The timer should only created if not exists and rescheduled all time the current is greater than 0.3 A. This is the current the machine has finished all jobs and only LED and controller is running. Then the timer waits and after the time run out the Power is disabled then via knx.

i used your code, changed some little things and it is now:

function switch_off () {
  console.info('WaschOFF timer run out Washingmachine switched off now');  
  items.getItem('Keller_WaschmaschineOnOff').sendCommandIfDifferent('OFF');
}

var TIMER_NAME = 'WMTimer';

  var timer = cache.private.get(TIMER_NAME);

  if(timer === null || timer.hasTerminated()) {
    cache.private.put(TIMER_NAME, actions.ScriptExecution.createTimer(TIMER_NAME, time.toZDT('PT12M'), switch_off, ));
    console.info('WaschOFF timer created');
  }
  else if(items.getItem("Strom_Waschmaschine").quantityState.lessThan(Quantity('0.300 A'))){
    timer.reschedule(time.toZDT('PT6M'));
    console.info('WaschOFF timer rescheduled');
  }
else {
  console.info('WaschOFF timer NOT rescheduled');
}

Log is now

2024-08-01 20:08:56.828 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer created
2024-08-01 20:08:59.602 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:09:02.816 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:09:07.677 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:09:18.647 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:09:42.786 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:15:42.782 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer run out Washingmachine switched off now
2024-08-01 20:18:20.939 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer created
2024-08-01 20:18:24.968 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:19:13.870 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer created
2024-08-01 20:19:17.946 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:19:26.671 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:19:48.415 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:19:55.311 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:20:05.986 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:21:19.640 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:21:22.247 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:23:36.942 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer rescheduled
2024-08-01 20:25:24.432 [INFO ] [nhab.automation.script.ui.c283d3976d] - WaschOFF timer created

Mostly it works, but it seems also to have the problem that sonmetimes new timers are created before the switch off appears.
Im not sure where my thinkings with the “ifs” are wrong…

//code with words maybe you understand better what i want
function of timer { switch off the power of washingmaching}

if (timer not exists){create a timer the current can be whatever it is}
else if (timer exists && current > 0.3A){reschedule the timer}
else {tell only and make nothing}

The same advice apples for Blockly. Only use the shared cache when you are sharing the variable across scripts. You can select the cache being used in the blocks.

But that means the timer is created even when the current is less than equal to 0.3 A. So once the timer expires in 12 minutes, it sends that OFF command. Next time a new reading comes in there’s no timer so it creates a new one. Youll see the created timer logs every 12 minutes as long as the usage is < 0.3 A.

You only need a timer if the current > 0.3A. Don’t create the timer if the current is < 0.3A.

Your current code it testing for lessThan, not greaterThan. This means the timer will only be reaschedule when the usage is 0.3A or less.

With the current code, you’ll never see this log statement.

What you need is

if(current > 0.3A && timer doesn't exist) create timer
else if(current > 0.3A && timer does exist) reschedule timer
else do nothing

When the current is <= 0.3A you neither want to create a timer nor reschedule it.

thats not correct here is a graph:

You can see that the current is a lot of times under 0.3A in the normal washing-periode. So it should make timer as long no one exists and reschedule this as long the current comes shortly above 0.3A.
After 17:45 it is not coming any more above 0.3A. Machine is finished and to avoid energy waste im shuting down the power (if the rule works).

On my former rule this was working, but it was still an ecmascript 5.1 and so i had to rewrite it.
im not sure why my code can make more that one time new timers without the info before “WaschOFF timer run out Washingmachine switched off now” that the existing timer was run out

OK, but then when the reading is under 0.3A and no timer exists you will always have a new timer created. If, for example, the usage remains under 0.3A for 6 hours, you’ll see “WaschOFF timer created” every 12 minutes a total of 30 times. Isn’t that what you are trying to eliminate?

And once the timer is created, if the usage is under 0.3A nothing happens. The existing Timer continues to run and is not cancelled and will go off at the sceduled time. The next time the value goes over 0.3A the timer is rescheduled.

The usage would have to be under 0.3A for six minutes before the timer goes off, even if it drops below 0.3A here and there during the whole cycle.

What through the events and the code manually if you are not convinced.

  1. reading is 0.2A
    a. timer === null || timer.hasTerminated() is true because timer == null
    b. create a timer to go off in 12 minutes
  2. another reading of 0.2A comes in
    a. timer === null || timer.hasTerminated() is false
    b. items.getItem("Strom_Waschmaschine").quantityState.lessThan(Quantity('0.300 A')) is true because 0.2A < 0.3A
    c. timer is rescheduled to go off in six minutes
  3. reading of 0.4A comes in
    a. timer === null || timer.hasTerminated() is false
    b. items.getItem("Strom_Waschmaschine").quantityState.lessThan(Quantity('0.300 A')) is false because 0.4A >= 0.3A
    c. nothing happens
  4. All the readings for the next six minutes are >= 0.3A
    a. timer wasn’t rescheduled so it finally goes off and commands the Item to OFF.

That scenario is the opposite of what you say you want to happen. You say you want to do set a timer when the usage is > 0.3A and wait six minutes after the reading is below 0.3A to command the Item OFF.

Now consider another scenario:

  1. reading is 0.4A
    a. timer doesn’t exist so a timer is created
  2. reading of 1A comes in
    a. timer === null || timer.hasTerminated() is false
    b. items.getItem("Strom_Waschmaschine").quantityState.lessThan(Quantity('0.300 A')) is false because 1A >= 0.3A
    c. nothing happens
  3. reading never falls below 0.3A over the next 12 minutes
    a. timer runs and command the Item to OFF, killing the machine in the middle of it’s cycle

Now consider what you say you want to have happen:

  1. reading is 0.2A
    a. there’s nothing to do
  2. reading 1A
    a. 1A > 0.3A && (timer === null || timer.hasTerminated()) is true
    b. timer is scheduled to go off in 12 minutes
  3. reading of 1A again
    a. 1A > 0.3A && (timer === null || timer.hasTerminated()) is false because timer exists
    b. 1A > 0.3A is true
    c. timer is rescheduled to go off in 6 minutes
  4. reading of 1A again
    a. 1A > 0.3A && (timer === null || timer.hasTerminated()) is false because timer exists
    b. 1A > 0.3A is true
    c. timer is rescheduled to go off in 6 minutes
  5. reading of 0.2A comes in
    a. 1A > 0.3A && (timer === null || timer.hasTerminated()) is false because timer exists
    b. 0.2A > 0.3A is false
    c. nothing happens
  6. six minutes pass without a reading over 0.3A
    a. timer runs and sends command OFF to the Item because the power level has been < 0.3A for six minutes

I’m not wholly convinced that’s what is happening. I don’t see how it would be happening given the code. You are definitely creating more timers than necessary. Logging out the state of Strom_Waschmaschine might make things more clear.

One thing to note is the cache will clean up Timers. For example, if you unload a rule it will cancel all the Timers that that rule put into the cache if there is nothing else referencing that Timer. I don’t know but suspect the same happens if you replace a Timer in the cache with another timer. So if you put a new timer over an old timer, it cancels the old timer.

ok, that would be something what could be the reason. My testings with changes and savings on the rule could have made these unloads possibly.

For today i set up the rule in this way which i will test next week when the washingmachine under real conditions will run again:

function switch_off () {
  console.info('WaschOFF timer run out Washingmachine switched off now');  
  items.getItem('Keller_WaschmaschineOnOff').sendCommandIfDifferent('OFF');
}

var TIMER_NAME = 'WMTimer';

var timer = cache.private.get(TIMER_NAME);

if((timer === null || timer.hasTerminated()) && items.getItem('Keller_WaschmaschineOnOff').state=='ON') {
  cache.private.put(TIMER_NAME, actions.ScriptExecution.createTimer(TIMER_NAME, time.toZDT('PT12M'), switch_off, ));
  console.info('WaschOFF timer created');
}
else if(items.getItem("Strom_Waschmaschine").quantityState.greaterThan(Quantity('0.300 A'))){
  timer.reschedule(time.toZDT('PT6M'));
  console.info('WaschOFF timer rescheduled');
}
else {
  console.info('WaschOFF timer NOT rescheduled');
}

I changed there 2 further things:

  • && items.getItem('Keller_WaschmaschineOnOff').state=='ON' the condition to avoid that it can create timers if power is off (normally it would also not trigger not because rule is not triggered on 0A)
  • else if(items.getItem("Strom_Waschmaschine").quantityState.greaterThan(Quantity('0.300 A'))){ you are correct that direction greaterthan is better then less. At my first code i changed the direction because washingmachine was finished to test the timercreation and reschedule…

I will let you know if the “doubletimers” are appearing under normal usage too or if this was coming from the testings.

Best thanks for today and for the learnings i can do every time with you!

You could set that as a rule condition so the rule only runs when that Item is ON.