Timer block + countdown item/variable

Blockly on OH 4.3.2 release

@stefan.hoehn
In some cases it would be very useful to have a countdown item in the standard blockly timer block.

There is a countdown item implementation in the openHAB Rules Tools [4.1.0.0;4.9.9.9] library developed by @rlkoshak, however that block doesn’t support “context” and “rescheduling if retriggered”.

Would it be possible to enhance the standard timer block with a countdown item or countdown variable as follows:

My scripting skills are not advanced enough to get this done… unfortunately…

Any chance to get this implemented in e.g. OH5?

What context do you need to pass into the timer for your use case? The private cache is usually a good option. But often you don’t even need it.

Adding support for passing a context is on the todo but there’s no timeline for when that will get added to OHRT.

To reschedule:

In almost all cases I use:
timer context

Thanks for the hint for how to reschedule…

Current implementation with timer context:

This is the rule in which I use timer context…

var Item_timer_auto_off, Dedicated_timer;


console.info('Rule fired ... (Light Timer)');
Item_timer_auto_off = String(event.itemName) + '_timer_auto_off';
Dedicated_timer = String(event.itemName) + '_timer';
if (cache.private.exists(Dedicated_timer) === false || cache.private.get(Dedicated_timer).hasTerminated()) {
  cache.private.put(Dedicated_timer, actions.ScriptExecution.createTimer(Dedicated_timer, time.ZonedDateTime.now().plusMinutes(items.getItem(Item_timer_auto_off).numericState), function (timer_context) {
    items.getItem((timer_context)).sendCommand('OFF');
    }, event.itemName));
} else {
  cache.private.get(Dedicated_timer).reschedule(time.ZonedDateTime.now().plusMinutes(items.getItem(Item_timer_auto_off).numericState));
};
console.info('Rule finished ... (Light Timer)');

I have different switch items triggering the same rule by pressing wallswitches - in fact the rule could potentially be triggerd by one wallswitch while another timer is still running from another wallswitch that was pressed before.

That’s why I append the triggering item name with:

  • “_timer_auto_off” to get a Number_Time item corresponding to the triggering item.
  • “_timer” to get a timer name corresponding to the triggering item.

By that I avoid duplicating the rule for every wallswitch = avoid that timers conflict each other, if running in parallel.

This rule works perfectly well when using timer context.

I took this as a challenge and tried an alternative implementation without timer context:

var Item_switch, Item_timer_auto_off, Dedicated_timer;


console.info('Rule fired ... (Light Timer)');
Item_switch = event.itemName;
Item_timer_auto_off = String(event.itemName) + '_timer_auto_off';
Dedicated_timer = String(event.itemName) + '_timer';
if (cache.private.exists(Dedicated_timer) === false || cache.private.get(Dedicated_timer).hasTerminated()) {
  cache.private.put(Dedicated_timer, actions.ScriptExecution.createTimer(Dedicated_timer, time.ZonedDateTime.now().plusMinutes(items.getItem(Item_timer_auto_off).numericState), function (timer_context) {
    items.getItem(Item_switch).sendCommand('OFF');
    }, undefined));
} else {
  cache.private.get(Dedicated_timer).reschedule(time.ZonedDateTime.now().plusMinutes(items.getItem(Item_timer_auto_off).numericState));
};
console.info('Rule finished ... (Light Timer)');

I thought this could be done by using a variable “Item_switch” holding the triggering item name and using that variable inside the timer instead of using the timer context. It seemed to me so logical that this should work, but it doesn’t. Parallel timers conflict each other.

What’s wrong with that approach?
Is there a way to avoid timer context in this use case?

Creating a new timer with a different name is pretty much the same for countdown timer. Just use Dedicated_timer in place a “MyTimer” in the block. That’s what stores the timer under a unique key in the cache so you can have one timer per Item that triggers the rule.

After you create the one Timer the second one comes around and changes the state of the Item so that when the first Timer runs, its values were replaced.

I’m not certain it would be required in the countdown timer case. Can you show what you tried there and more importantly what JS code was generated?

When the CountdownTimer is created, it creates a brand new function that it passes as the body to the timer. That new function should “fix” the states of the variables at the time it’s created which is when the timer is created.

Do the new triggers of the rule overwrite the variables then?

For your specific case using the built in timer instead of an OHRT timer, no you’d have to use the context in that case I think.

I’m not using the countdown timer from your library at the moment. I would want to find a solution first to go without timer context. I thought to take a step at the time and then move on and use your countdown timer block.

Maybe I should give it a try during the weekend and just ignore the timer context issue for the moment.

I added the JS code for both options to my previous post.

This seems to be the case with the alternative approach using a variable instead of timer context when the second timer comes around.

I am not sure if it is generic enough to be useful to be added to the standard blocks? @rlkoshak WDYT

This is how you can achieve the same but granted, this is far from being as elegant as the block that Rich has provided.

By the way, as a sidenote I added this if-branch to show a pattern that I highly recommend when testing: it allows the timer to be stopped in case you have a bug in you loop which never ends (this approach also works for a private timer) - alternatively you can make it shared (like I did) and stop it from a different rule…

@rlkoshak , @stefan.hoehn ,

I’ve got good and bad news in a way.

Good news that I got it working with Stefan’s approach. Thanks a lot!

Bad news that I couldn’t get it working with Rich’s less complex CountdownTimer block - the timers were conflicting each other - no chance I could get it done without timer context.

var Item_timer_auto_off, Item_countdown_auto_off, Dedicated_timer;


console.info('Rule fired ... (Light Timer)');
Item_timer_auto_off = String(event.itemName) + '_timer_auto_off';
Item_countdown_auto_off = String(event.itemName) + '_countdown_auto_off';
Dedicated_timer = String(event.itemName) + '_timer';
items.getItem(Item_countdown_auto_off).postUpdate((items.getItem(Item_timer_auto_off).numericState * 60));
if (cache.private.exists(Dedicated_timer) === false || cache.private.get(Dedicated_timer).hasTerminated()) {
  cache.private.put(Dedicated_timer, actions.ScriptExecution.createTimer(Dedicated_timer, time.ZonedDateTime.now().plusSeconds(1), function (timer_context) {
    if (items.getItem((String(timer_context) + '_countdown_auto_off')).numericState > 0) {
      items.getItem((String(timer_context) + '_countdown_auto_off')).postUpdate((items.getItem((String(timer_context) + '_countdown_auto_off')).numericState - 1));
      if (cache.private.exists((String(timer_context) + '_timer'))) { cache.private.get((String(timer_context) + '_timer')).reschedule(time.ZonedDateTime.now().plusSeconds(1)); };
    } else {
      items.getItem((timer_context)).sendCommand('OFF');
    }
    }, event.itemName));
};
console.info('Rule finished ... (Light Timer)');

Just a note: My timer item holds the time in minutes - that’s why I convert to seconds for the countdown item. Using seconds for the countdown item is perfectly fine.

I still believe that adding the countdown function to the generic timer block (with rescheduling) would be great!

I do have plans to add a context to all of my timer blocks in the OHRT library but don’t know when I’ll get to it. If you think it’s worth adding to the core Blockly blocks I’ll be happy to provide the code I have. It’s pretty close in logic to what you show only it guarantees that the main body of the timer runs at the exact (to the msec) of the original scheduled time so if it counts down by the minute (for example) it will still run at 3 minutes, 34 seconds if that’s the scheduled time.

1 Like