Timer Value

Hi all,
I created:

timer = createTimer(now.plusSeconds(180))

is there a way to Display the value of a timer in my Sitemap in iOS App?
I tried to Format the string somehow but nothing works :frowning:

Any help is much appreciated.

Regards
Joerg

No, a timer is a rule thing, not something that is exposed to the sitemap. The purpose of a timer is for it to execute some code that you pass to it at a certain time in the future. So what you have is essentially a no-op (i.e. doesnā€™t do anything). A proper timer in a rule would be:

timer = createTimer(now.plusSeconds(180), [|
    // code that should execute 180 seconds from now
])

To have a countdown timer like you are looking for you would need to create a rule with a loop and a sleep that updates an item every second.

rule "Countdown"
when
    Item StartCountdown received command ON
then
    var count = 180
    while(count >= 0) {
        Countdown.postUpdate(count)
        count = count - 1
        Thread::sleep(1000)
    }
end
3 Likes

Hello Rich,
thank you for the your explanation.
I tried to implement you ā€œcountdownā€ code but I received the following message from OpenHab Designer. "Couldnā€™t resolve reference to JvmIdentifiableElement ā€˜Countdownā€™.
No idea what i did wrong.

As you can see my programming knowledge is not brilliant.

So any help is much appreciated

Have you defined Countdown in a .items file anywhere? Did you save that .items file?

You need to define all Items that you use in a .items file.

Number Countdown "My Countdown [%d]"

Thank you for the fast replay!
Yes I defined the Countdown in the .items file. (like all the outer Items).
I restarted OpenHAB Designer and then the error messages dissipated. It is now working for me :smile: Tknaks for your help!

Hi,

i got it working for me too but i want a number (set from habpanel) as the timer value so i just replaced the 180 with number.state and it does not work.
It updates my current time once but then is stuck.
i cant see why because the while-loop should be still active.

Please use

(number.state as DecimalType)

instead

Hi!

Thanks for your reply!

still no luck - same issue ā€¦ here my rules-file:

rule "Countdown"
when
    Item WoZi_Schalter_Countdown received command ON
then
    var count = (WoZi_Nummer_Countdown_Wunsch.state as DecimalType)
    while(count >= 0) {
        WoZi_Nummer_Countdown_Ist.postUpdate(count)
        count = count - 1
        Thread::sleep(1000)
    }
    WoZi_Licht_Couchtisch_Alarm.sendCommand("LSELECT")
end

and the items:

/* Timer */
Number WoZi_Nummer_Countdown_Wunsch "Wohnzimmer-Countdown [%d]"
Number WoZi_Nummer_Countdown_Ist "Istzustand Countdown [%d]"
Switch WoZi_Schalter_Countdown "Starte Countdown Wohnzimmer"

Try

var count = (WoZi_Nummer_Countdown_Wunsch.state as Number).intValue
1 Like

working like charm! thank you! can you link me to an article where i can see some commands? or is this java script? then i know where to read :wink:

thank you

http://docs.openhab.org/configuration/rules-dsl.html

Tanks a lot!
Now i have another issue. I kind of understand why but i donā€™t know how to solve it:

Everytime i push the Switch the timer function (rule) is called again and this messes up the current timer (it posts updates from each instance of functions that is called).
to solve this i wanted to reset the counter to 0. When the rule is called (i assume in a new instance) the var counter is of course not the same as before so i cant just write:

var counter = 0

after the when.
So i decided to create a global variable in the items as a number. (just added:

Number counter ā€œZaehlerā€

to items. I donā€™t quite understand why this does not solve my problem and how to write to number variables.
i used sendCommand in the past but this wonā€™t help if i use math-functions as in this case.

Why can i not just assign a value to the Number-Variable like:
counter = 0 (counter defined in items files as described above).

Thank you!

There is nothing ā€œNumber-Variable likeā€ about a Number Item. Items are complex Objects with lots of moving parts and the Number Item just happens to carry a State that is a Number.

To change the state of an Item you must use either sendCommand or postUpdate. This is the only way to change the state of an Item.

I donā€™t understand this statement. I and others do math with and update the results from calculations using postUpdate and sendCommand all the time. Please review:

and if that doesnā€™t solve your problem post your code.

Hello, my very first post after 6 months learning OH.

A small improvement on this script:
==> purpose is to make the timer restartable when is it still running.

I uploaded a v2 version, using switches instead of global variables, and applying some formatting.
Please note, this script is just a snippet, it contains (at least I think) reusable code - but it needs tweaking; Mind for instance the dummy if-then-else to test the display of days, when needed. Of course, this is no real life case, but my code to test the display of days (easily).

Lastly, I reused code I found here on the forum to:

  • create the countdown timer
  • display it properly with leading zeroā€™s, when needed.
    So my contribution is the assembly of those parts to a ā€˜restartableā€™ countdown timer.
var count = 180


rule "Countdown"
when
    Item broodKlaar changed to ON
then
    //logInfo("countdown", "countdownRunning.state :"+countdownRunning.state.toString)
    
//initialise when needed
    if (countdownRunning.state == NULL) sendCommand(countdownRunning,OFF)
    if (countdownStop.state == NULL) sendCommand(countdownStop,OFF)
    
    if (countdownRunning.state==ON) //is timer already running --> start the logic to restart it
    {
        sendCommand(countdownStop,ON) // see below, this breaks the while loop
        Thread::sleep(2500) // needed to be sure the while loop condition evaluated at least once
        count = 180 //reinitialise to value of choice
        sendCommand(countdownRunning,OFF) 
        sendCommand(countdownStop,OFF)
        sendCommand(broodKlaar,OFF) // to act as a push button iso a two-state switch
        Thread::sleep(2500) // setting switches here above takes some time, so stalling a bit.
    }
    
    if (countdownRunning.state==OFF) // no timer is running (yet)
    {
        sendCommand(countdownRunning,ON)  
        sendCommand(broodKlaar,OFF) // to act as a push button iso a two-state switch
        while(count >= 0 && countdownStop.state == OFF) { 
            var sec = count % 60
            var min = (count / 60) % 60
            var hrs = (count / (60*60)) % 24
            var day = count / (60*60*24)
            var String stringToSend 
            
            if ( count  < 150 && count > 100)  //dummy code to test the display of days, when needed
                {
                    day=5
                //stringToSend = day.toString("02d") + " days, " + hrs.toString("02d") + ":" + min.toString("02d") + ":" + sec.toString("02d") 
                stringToSend = String::format("%1$02d days, %2$02d:%3$02d:%4$02d", day, hrs, min, sec)
                }
                else 
                //stringToSend = hrs.toString("02d") + ":" + min.toString("02d") + ":" + sec.toString("02d")
                stringToSend = String::format("%1$02d:%2$02d:%3$02d", hrs, min, sec)
            
            countdown.postUpdate(stringToSend)
            
            count = count - 1
            Thread::sleep(1000)    
        }
        
    }
    
end

Hoping I use the correct formatting.

Cheers,

Raf

All of those long Thread::sleeps give me concern. Especially there long Thread::sleep in the while loop. See Why have my Rules stopped running? Why Thread::sleep is a bad idea for why and alternatives.

Hello, thanks for the heads-up;

maybe this looks better in the sight of ā€˜no sleepā€™ :wink:

Remark: donā€™t pay attention to the (if 1==1), just a part of the development track of this organic code;

var Integer startvalue = 30

rule "Countdown running"
when
    Item countdownStart changed to ON
    or
    Item countdownKeeprunning changed to ON
    or
    Item countdownReset changed to ON
then
    
    if (countdownRunning.state == NULL) sendCommand(countdownRunning,OFF)
    if (countdownReset.state == NULL) sendCommand(countdownReset,OFF)
    if (countdownKeeprunning.state == NULL) sendCommand (countdownKeeprunning,OFF)
    if (countdownStart.state == NULL) sendCommand (countdownStart,OFF)
    if (count.state == NULL || (count.state as Number).intValue==0) count.postUpdate(startvalue)
    
    
    if (countdownStart.state == ON)
    {
        if(1==1) { //showing the result
            logInfo("Countdown","showing the result")
            var sec = (count.state as Number).intValue  % 60
            var min = (((count.state as Number).intValue) / 60) % 60
            var hrs = (((count.state as Number).intValue) / (60*60)) % 24
            var day = ((count.state as Number).intValue) / (60*60*24)
            var String stringToSend 
            
            if ( count.state  < 25 && count.state > 20) 
                {
                    day=5
                    stringToSend = String::format("%1$02d days, %2$02d:%3$02d:%4$02d", day, hrs, min, sec)
                }
                else 
                {
                    stringToSend = String::format("%1$02d:%2$02d:%3$02d", hrs, min, sec)
                }   
            
            
            
            countdown.postUpdate(stringToSend)
        }

        if (count.state >0 && countdownReset.state==OFF)
        {
            createTimer(now.plusSeconds(1),  [ |
               logInfo ("Countdown","if count.state > 0 - start == ON")
               count.postUpdate((count.state as Number).intValue -1)
               sendCommand(countdownKeeprunning, OFF)
               sendCommand(countdownKeeprunning, ON)
            ])
        }
        if ((count.state as Number).intValue == 0) sendCommand(countdownStart,OFF)

         
    }

    if (countdownReset.state==ON)
    {
        count.postUpdate(startvalue)
        countdown.postUpdate("resetting")
        sendCommand(countdownKeeprunning,OFF)
        sendCommand(countdownReset,OFF)
        sendCommand(countdownStart,OFF)
        createTimer(now.plusMillis(1200),  [ |
               count.postUpdate (startvalue)
               sendCommand(countdownStart,ON) 
        ])   
    }
    
    
end

rule "Countdown cleaning up"
when
    Item countdownStart changed to OFF
then
    
    if (countdownRunning.state == NULL) sendCommand(countdownRunning,OFF)
    if (countdownReset.state == NULL) sendCommand(countdownReset,OFF)
    if (countdownKeeprunning.state == NULL) sendCommand (countdownKeeprunning,OFF)
    if (countdownStart.state == NULL) sendCommand (countdownStart,OFF)
    if (count.state == NULL) count.postUpdate(startvalue)
    
    createTimer(now.plusMillis(1200),  [ |
               sendCommand(countdownKeeprunning,OFF)
               sendCommand(countdownReset,OFF)   
               countdown.postUpdate ("timer stopped")
               count.postUpdate (startvalue) 
    ])
    
end    

I quickly perused your code:
In your last rule, the trigger is countdownStart changed to OFF so there is no need to test if NULL
Also, when then items are know, use the sendCommand method.
If these items have no binding then use the postUpdate method instead (Itā€™s quicker because the sendCommand will send a command and then post an update but you donā€™t need the command because there is no physical device involved with a binding)

So a quick clean up:

rule "Countdown cleaning up"
when
    Item countdownStart changed to OFF
then
    if (countdownRunning.state == NULL) countdownRunning.postUpdate(OFF)
    if (countdownReset.state == NULL) countdownReset.postUpdateOFF)
    if (countdownKeeprunning.state == NULL) countdownKeeprunning.postUpdate(OFF)
    if (count.state == NULL) count.postUpdate(startvalue)
    
    createTimer(now.plusMillis(1200),  [ |
               countdownKeeprunning.postUpdate(OFF)
               countdownReset.postUpdate(OFF)   
               countdown.postUpdate ("timer stopped")
               count.postUpdate (startvalue) 
    ])
    
end    

You can do the same on your other rules

1 Like

Iā€™ll second all of Vincentā€™s recommendations. Iā€™m much happier with this version that lacks the Thread::sleeps.

update with your hints:

What Iā€™ve learned:

  • difference between sleep and createtimer
  • difference between sendcommand and postupdate
var Integer startvalue = 10

rule "Countdown initialise"  
when 
    System started
then
    if (countdownRunning.state == NULL) countdownRunning.postUpdate(OFF)
    if (countdownReset.state == NULL) countdownReset.postUpdate(OFF)
    if (countdownKeeprunning.state == NULL) countdownKeeprunning.postUpdate(OFF)
    if (countdownStart.state == NULL) countdownStart.postUpdate(OFF)
    if (count.state == NULL) count.postUpdate(startvalue)
   
end

rule "Countdown running"
when
    Item countdownStart changed to ON
    or
    Item countdownKeeprunning changed to ON
    or
    Item countdownReset changed to ON
then

    if(1==1) //formatting and outputting count to stringToSend 
    { 
        logInfo("Countdown","formatting and outputting count to stringToSend --> Countdown ")
        var sec = (count.state as Number).intValue  % 60
        var min = (((count.state as Number).intValue) / 60) % 60
        var hrs = (((count.state as Number).intValue) / (60*60)) % 24
        var day = ((count.state as Number).intValue) / (60*60*24)
        var String stringToSend 
            
        if ( count.state  < 25 && count.state > 20) 
            {
                day=5
                stringToSend = String::format("%1$02d days, %2$02d:%3$02d:%4$02d", day, hrs, min, sec)
            }
            else 
            {
                stringToSend = String::format("%1$02d:%2$02d:%3$02d", hrs, min, sec)
            }   
            
            
        countdown.postUpdate (stringToSend)        
    }

    
     
     
    if (countdownStart.state == ON)  //Start is ON
    {
        logInfo("Countdown","Start is on")
        if ((count.state as Number).intValue > 0 && countdownReset.state==OFF)
        {
            logInfo("Countdown","countdown value in seconds: " + count.state.toString)
            createTimer(now.plusSeconds(1),  [ |
               if (countdownStart.state == ON) //if start is STILL on this second later
               {
                    logInfo ("Countdown","((count.state as Number).intValue > 0 && countdownReset.state==OFF)")
                    count.postUpdate((count.state as Number).intValue -1)
                    countdownKeeprunning.postUpdate(OFF)
                    countdownKeeprunning.postUpdate(ON)
               }
            ])
        }
         
         
    }

    if (countdownReset.state==ON)
    {
        logInfo("Countdown","if (countdownReset.state==ON)")
        countdown.postUpdate("resetting")
        countdownKeeprunning.postUpdate(OFF)
        countdownReset.postUpdate(OFF)
        countdownStart.postUpdate(OFF)
        createTimer(now.plusMillis(1500),  [ |
                count.postUpdate (startvalue)
                countdownStart.postUpdate(ON) 
        ])   
    }
    
    if ((count.state as Number).intValue == 0) 
    {
        logInfo("Countdown"," if ((count.state as Number).intValue == 0)")
        countdownStart.postUpdate(OFF)
    }
end

rule "Countdown cleaning up"
when
    Item countdownStart changed to OFF
then
    logInfo("Countdown"," Item countdownStart changed to OFF")
        
    createTimer(now.plusMillis(1200),  [ |
               countdownKeeprunning.postUpdate(OFF)
               countdownReset.postUpdate(OFF)   
               countdown.postUpdate ("timer stopped")
               count.postUpdate (startvalue) 
    ])
    
end        
2 Likes

@Raf_Daems Could you please post the complete example including items/sitemap? It looks very interesting, so Iā€™d try it out :slight_smile: