Timer Value

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:

Very sorry for my late reply.

I’m afraid I did nor do have a lot more than this.

I use habpanel.

Let me know if that answers more or less your question?

Best regards

Raf

ok, I’ll give it one more try :slight_smile:

I realize this is an old post, but found @Raf_Daems starter code helpful and was able to setup the necessary working code to accomplish a simple timer for my concrete heater. You will notice, that in most ways the code is unchanged from that provided in Jul '18, but included the items definitions in case that helps other noobs like myself.

Item Definition in a file called mqtt.items:

Switch Concrete_Heater              "Concrete Heater"                           <snow>          (Actuation, gGar)      {channel="mqtt:topic:MQTTBroker:T71:switch"}
String conc_heater_countdown        "Concrete Heater Countdown Timer [%s]"      <time>          (Actuation, gGar)
Number conc_heater_count            "Concrete Heater Countdown Timer (seconds)"
Switch conc_heater_countdownRunning
Switch conc_heater_countdownReset
Switch conc_heater_countdownKeeprunning
Switch conc_heater_countdownStart

Rule Definition within a file called conc_heater.rules

var Integer conc_heater_startvalue = 7200 //starting timer value in seconds

rule "Initialize concrete heater countdown timer"  
when 
    System started
then
    if (conc_heater_countdownRunning.state == NULL) conc_heater_countdownRunning.postUpdate(OFF)
    if (conc_heater_countdownReset.state == NULL) conc_heater_countdownReset.postUpdate(OFF)
    if (conc_heater_countdownKeeprunning.state == NULL) conc_heater_countdownKeeprunning.postUpdate(OFF)
    if (conc_heater_countdownStart.state == NULL) conc_heater_countdownStart.postUpdate(OFF)
    if (conc_heater_count.state == NULL) conc_heater_count.postUpdate(conc_heater_startvalue)
end

rule "Start countdown when concrete heater is turned on"  
when 
    Item Concrete_Heater changed to ON
then
    if (conc_heater_countdownStart.state == ON) {
        logInfo("Concrete heater countdown timer","Restart timer")
        conc_heater_countdownReset.postUpdate(ON)
    }
    if (conc_heater_countdownStart.state == NULL) {
        logInfo("Concrete heater countdown timer","Start timer")
        conc_heater_countdownStart.postUpdate(ON)
    }
    if (conc_heater_countdownStart.state == OFF) {
        logInfo("Concrete heater countdown timer","Start timer")
        conc_heater_countdownStart.postUpdate(ON)
    }
end

rule "Stop countdown when concrete heater is turned off"  
when 
    Item Concrete_Heater changed to OFF
then
    if (conc_heater_countdownStart.state == ON) conc_heater_countdownStart.postUpdate(OFF)
    if (conc_heater_countdownStart.state == NULL) conc_heater_countdownStart.postUpdate(OFF)
end

rule "Concrete heater countdown timer running"
when
    Item conc_heater_countdownStart changed to ON
    or
    Item conc_heater_countdownKeeprunning changed to ON
    or
    Item conc_heater_countdownReset changed to ON
then

    //if(1==1) //formatting and outputting count to stringToSend 
    //{ 
        logInfo("Concrete heater countdown timer","formatting and outputting count to stringToSend --> concrete heater countdown timer")
        var sec = (conc_heater_count.state as Number).intValue  % 60
        var min = (((conc_heater_count.state as Number).intValue) / 60) % 60
        var hrs = (((conc_heater_count.state as Number).intValue) / (60*60)) % 24
        var day = ((conc_heater_count.state as Number).intValue) / (60*60*24)
        var String stringToSend 
            
        if ( conc_heater_count.state > 86400) 
            {
                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)
            }   
            
            
        conc_heater_countdown.postUpdate (stringToSend)        
    //}

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

    if (conc_heater_countdownReset.state==ON)
    {
        logInfo("Concrete heater countdown timer","if (countdownReset.state==ON)")
        conc_heater_countdown.postUpdate("resetting")
        conc_heater_countdownKeeprunning.postUpdate(OFF)
        conc_heater_countdownReset.postUpdate(OFF)
        conc_heater_countdownStart.postUpdate(OFF)
        createTimer(now.plusSeconds(1),  [ |
                conc_heater_count.postUpdate (conc_heater_startvalue)
                conc_heater_countdownStart.postUpdate(ON) 
        ])   
    }
    
    if ((conc_heater_count.state as Number).intValue == 0) 
    {
        logInfo("Concrete heater countdown timer"," if ((count.state as Number).intValue == 0)")
        conc_heater_countdownStart.postUpdate(OFF)
    }
end

rule "Cleaning up concrete heater countdown timer"
when
    Item conc_heater_countdownStart changed to OFF
then
    logInfo("Concrete heater countdown timer"," Item countdownStart changed to OFF")
        
    createTimer(now.plusSeconds(1),  [ |
               conc_heater_countdownKeeprunning.postUpdate(OFF)
               conc_heater_countdownReset.postUpdate(OFF)   
               conc_heater_countdown.postUpdate ("timer stopped")
               conc_heater_count.postUpdate (conc_heater_startvalue) 
    ])
    logInfo("Concrete heater countdown timer","Stop timer and turn off concrete heater")
    Concrete_Heater.sendCommand(OFF)
end      

Very simple, when the concrete heater is turned on, it will start a 7200sec timer (2hr) and display a string of the countdown just below the concrete heater switch in my UI. The heater is turned off when the timer reaches 0.

1 Like