Countdown timer widget

Does anybody already created his own widget for a countdown timer?

I have not seen anything like a dedicated countdown timer yet. There are a few small roadblocks to developing such a thing.

You could take a look at the timeout portion of the confirm button, that might give you some ideas:

But that will be very difficult to use with text.

Otherwise, you’re going to need to use some feature outside of the widget system (e.g., a rule to do the actual timing and change an item accordingly).

1 Like

OHRT has a countdown timer which populates a String Item or a Number:Time Item with how much is left on the scheduled timer. The value is updated once per second.

For Blockly users see openHAB Rules Tools [4.1.0.0;4.9.9.9] (countdown timer is the first one in the list).

From there I think it’s just a matter of showing the Item which the remaining time on the Timer in a widget and you are good.

3 Likes

How would that work for non-Blockly users?

Install the openhab-rules-tools library using npm as documented same as you do for Blockly GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the development of new Rules.

Then you can require and use the CountdownTime class.

1 Like

I finally got to toying with this a bit. Copying from the Blockly rule (with which I have no experience, nor much desire to get any :)), I created this rule:

const { rules, triggers, items } = require('openhab');
var OHRT = (() => {
    const ohrt = require('openhab_rules_tools');
    ohrt.helpers.validateLibraries('4.1.0', '2.0.3');
    return ohrt;
})();

rules.JSRule({
    name: "countdowntimer-javascript",
    description: 'Coutdowntimertest',
    triggers: [],
    execute: (event) => {

        var afteltijdinseconden = 70
        
        if (!cache.shared.exists('MyTimer') || cache.shared.get('MyTimer').hasTerminated()) {
            cache.shared.put('MyTimer',
                OHRT.CountdownTimer(
                    'PT' + afteltijdinseconden + 'S',
                    () => {
                        cache.shared.put('MyTimer', null);
                    },
                    'countdowntestitem',
                    'MyTimer'));
        }
    }
});

This works. And (for future reference) I also found a way to interrupt and reset countdowntestitem (although the latter is maybe redundant…):

const { rules, triggers, items } = require('openhab');
var OHRT = (() => {
    const ohrt = require('openhab_rules_tools');
    ohrt.helpers.validateLibraries('4.1.0', '2.0.3');
    return ohrt;
})();

rules.JSRule({
    name: "countdowntimer-stoppen-javascript",
    description: 'Coutdowntimertest-stoppen',
    triggers: [],
    execute: (event) => {

        var afteltijdinseconden = 70
        
        var countdowntestiteminregel = items.getItem("countdowntestitem")
        
        function timerResetten() {
            countdowntestiteminregel.sendCommand(afteltijdinseconden)
        }
        
        if (cache.shared.exists('MyTimer')) {
            
            console.log("Timer loopt...")
            cache.shared.get('MyTimer').cancel()
            setTimeout(timerResetten, 1000)
        }
    }
});

I assume I can put both in the same rule, and then work with a private cache…

One thing I don’t get, is why countdowntestitem displays the time in 0.0 (seconds):

I would expect the HH:MM:SS format, based on this: openHAB Rules Tools [4.1.0.0;4.9.9.9]?

I got it to MM:SS format through the stateDescription metadata (%1$tM:%1$tS):

I copied this code, and altered a few strings, but now I can’t get the timer to stop anymore…

const { rules, triggers, items } = require('openhab');
var OHRT = (() => {
    const ohrt = require('openhab_rules_tools');
    ohrt.helpers.validateLibraries('4.1.0', '2.0.3');
    return ohrt;
})();

rules.JSRule({
    name: "waakvlamaftelling",
    description: 'Als de waakvlam (misschien) gedoofd is, mag de haard 5 minuten lang niet ontstoken worden. Dat staat zo in de handleiding van de haard, en heeft allicht met brandveiligheid te maken.',
    triggers: [
        triggers.ItemStateChangeTrigger(items.getItem("haard_eetkamer_waakvlam_mss_uit"))
    ],
    execute: (event) => {

        const afteltijdinseconden = 15
                
        const waakvlamcachenaam = "waakvlamtimer_haard_eetkamer"

        const waakvlamaftelitem = "haard_eetkamer_waakvlamaftelling"
        
        function timerResetten(testoppenaftelitem) {
            console.log("testoppenaftelitem = "+testoppenaftelitem)
            items.getItem(testoppenaftelitem).sendCommand(afteltijdinseconden)
        }
        
        switch (event.newState) {
            case "ON":
                try {
                    console.log("ON - cache.shared.exists(waakvlamcachenaam) = " + cache.shared.exists(waakvlamcachenaam))
                }
                catch { }


                if (!cache.shared.exists(waakvlamcachenaam) || cache.shared.get(waakvlamcachenaam).hasTerminated()) {
                    try {
                        console.log("!cache.shared.exists(waakvlamcachenaam) = " + !cache.shared.exists(waakvlamcachenaam))
                    }
                    catch { }
                    try {
                        console.log("cache.shared.get(waakvlamcachenaam).hasTerminated() = " + cache.shared.get(waakvlamcachenaam).hasTerminated())
                    }
                    catch { }
                    
                    cache.shared.put(waakvlamcachenaam,
                        OHRT.CountdownTimer(
                            'PT' + afteltijdinseconden + 'S',
                            () => {
                                cache.shared.put(waakvlamcachenaam, null);
                            },
                            waakvlamaftelitem,
                            waakvlamcachenaam
                        )
                    );
                   
                } else {
                    // Nog in te vullen
                }
                break
            case "OFF":
                try {
                    console.log("OFF- cache.shared.exists(waakvlamcachenaam) = " + cache.shared.exists(waakvlamcachenaam))
                }
                catch {
                    console.log("niet gelukt")
                }
                if (cache.shared.exists(waakvlamcachenaam)) {
                    
                    //console.log("Timer voor waakvlam van haard-" + kamernaam + " loopt...")
                    console.log("Timer voor waakvlam van haar eetkamer loopt...")
                    console.log(JSON.stringify(cache.shared.get(waakvlamcachenaam), null, 4));
                    console.log("cache.shared.get(waakvlamcachenaam)['countItem'] = " + cache.shared.get(waakvlamcachenaam)['countItem'])
                    //cache.shared.get(waakvlamcachenaam).cancel()
                    cache.shared.remove(waakvlamcachenaam)
                    try {
                        console.log("OFF na cache.shared.remove(waakvlamcachenaam) - cache.shared.exists(waakvlamcachenaam) = " + cache.shared.exists(waakvlamcachenaam))
                    }
                    catch {
                        console.log("niet gelukt")
                    }
                    setTimeout(timerResetten, 1000, waakvlamaftelitem)
                }
        }
    }
});

If I turn haard_eetkamer_waakvlam_mss_uit ON, the timer starts correctly. But if I turn it OFF, the cache is removed, but item haard_eetkamer_waakvlamaftelling keeps on counting down… @rlkoshak, do you have any insight? I don’t understand why stopping the timer does works with the code of my previous post, but not whit this code…

OH logs:

18:14:31.109 [INFO ] [openhab.event.ItemCommandEvent       ] - Item 'haard_eetkamer_waakvlam_mss_uit' received command ON
18:14:31.110 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlam_mss_uit' changed from OFF to ON
18:14:31.111 [INFO ] [ipt.file.haarden-waakvlamaftelling.js] - ON - cache.shared.exists(waakvlamcachenaam) = false
18:14:31.113 [INFO ] [ipt.file.haarden-waakvlamaftelling.js] - !cache.shared.exists(waakvlamcachenaam) = true
18:14:31.116 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 0 s to 15 s
18:14:32.117 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 15 s to 14 s
18:14:33.118 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 14 s to 13 s
18:14:34.119 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 13 s to 12 s
18:14:35.119 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 12 s to 11 s
18:14:35.693 [INFO ] [openhab.event.ItemCommandEvent       ] - Item 'haard_eetkamer_waakvlam_mss_uit' received command OFF
18:14:35.694 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlam_mss_uit' changed from ON to OFF
18:14:35.695 [INFO ] [ipt.file.haarden-waakvlamaftelling.js] - OFF- cache.shared.exists(waakvlamcachenaam) = true
18:14:35.696 [INFO ] [ipt.file.haarden-waakvlamaftelling.js] - Timer voor waakvlam van haar eetkamer loopt...
18:14:35.697 [INFO ] [ipt.file.haarden-waakvlamaftelling.js] - {
    "start": "2024-10-10T18:14:31.113+02:00[SYSTEM]",
    "end": "2024-10-10T18:14:46.114+02:00[SYSTEM]",
    "ONE_SEC": "PT1S",
    "timer": {},
    "timeLeft": "PT10.001S",
    "countItem": "haard_eetkamer_waakvlamaftelling",
    "countdownTimer": {
        "name": "waakvlamtimer_haard_eetkamer",
        "timer": {}
    }
}
18:14:35.698 [INFO ] [ipt.file.haarden-waakvlamaftelling.js] - cache.shared.get(waakvlamcachenaam)['countItem'] = haard_eetkamer_waakvlamaftelling
18:14:35.698 [INFO ] [ipt.file.haarden-waakvlamaftelling.js] - OFF na cache.shared.remove(waakvlamcachenaam) - cache.shared.exists(waakvlamcachenaam) = false
18:14:36.121 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 11 s to 10 s
18:14:36.700 [INFO ] [ipt.file.haarden-waakvlamaftelling.js] - testoppenaftelitem = haard_eetkamer_waakvlamaftelling
18:14:36.702 [INFO ] [openhab.event.ItemCommandEvent       ] - Item 'haard_eetkamer_waakvlamaftelling' received command 15.0
18:14:36.703 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 10 s to 15 s
18:14:37.121 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 15 s to 9 s
18:14:38.122 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 9 s to 8 s
18:14:39.123 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 8 s to 7 s
18:14:40.124 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 7 s to 6 s
18:14:41.125 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 6 s to 5 s
18:14:42.126 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 5 s to 4 s
18:14:43.127 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 4 s to 3 s
18:14:44.129 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 3 s to 2 s
18:14:45.130 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 2 s to 1 s
18:14:46.132 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'haard_eetkamer_waakvlamaftelling' changed from 1 s to 0 s

You never cancel the timer. You just remove a reference to it from the cache, but that doesn’t stop the timer. You have to do that by calling cancel timeout.

And you don’t actually store the timeout reference anywhere at all so you don’t have an buying to call cancel on.

If you are going to use to setTimeout instead of LoopingTimer, you are responsible for managing the time’s full life cycle. You need to save the value returned by setTimeout in the cache and when it’s time to cancel it, you need to use that saved reference to cancel it.

Right. Although I tried using cache.shared.get(waakvlamcachenaam).cancel() instead of cache.shared.remove(waakvlamcachenaam), and it didn’t work. But now it does. I assume I did something wrong before.

Thanks for responding!