Timer countdown vsible in human reading format

somehow yes. If the counter approach 00:01sec I can react and extend it. But the main purpose is just to visualise defined timer. I have it fixed now for 20min but the user will be able to change it.
So the timer shall really go down from the value given by the user. Assume 2h max.

ok, have a Number:Time Timeout

var Timer motionDelayTimer = null
var RuleFlag = null
val int initialTimeoutMinutes = 20

// Number:Time Timeout "Timer countdown blah blah [%1$tM:%1$tS]"

rule "Office Motion Sensor ON" 

when 
    Item Office_Multisensor_6_Motion_Alarm changed from OFF to ON 
then
    if (RuleOfficeLight.state == ON){
        RuleFlag = true
        if(motionDelayTimer === null || motionDelayTimer.hasTerminated) {
            motionDelayTimer = null
            Office_FIBARO_Double_Switch.sendCommand(ON)
            Office_FIBARO_Dimmer.sendCommand(2)
            logInfo("Motion Office", "Movement recognised -> Light ON")
        }
        else {
            logInfo("Motion Office", "Movement recognised -> Timer stop.")
            motionDelayTimer.cancel
            motionDelayTimer = null
        }
    }
    else{
        logInfo("Motion Office", "Rule deactivated")
    }

end

rule "Office Motion Sensor OFF"

when
    Item Office_Multisensor_6_Motion_Alarm changed from ON to OFF
then
    if(RuleFlag == true) { 
        Timeout.postUpdate(initialTimeoutMinutes * 60) // reset the timeout
        logInfo("Motion Office", "No more movement -> Timer start.")
        RuleFlag = null
        motionDelayTimer = createTimer(now.plusSeconds(1), [ |
          val timeout = (Timeout.state as QuantityType).intValue 
          if (timeout == 0) {
            motionDelayTimer.cancel
            motionDelayTimer = null
            Office_FIBARO_Double_Switch.sendCommand(OFF)
            logInfo("Motion Office", "Timer elapsed -> Light OFF")
          } else {
            Timeout.postUpdate(timeout - 1)
            motionDelayTimer.reschedule(now.plusSeconds(1))
          }
        ])
    }
    else{
        logInfo("Motion Office", "Rule deactivated already")
    }
end

The initial timer is set to 20 minutes. You can easily make this an item value too if you want to be able to customise it.

Thank you very much… Let me try that…

What does it do? Why you multiply by 60? The value will be 20min*60=120min. Just for example?

didn’t know that. Does it change the value of the timer to the intiger value?

Do you define an Item with the UI or in the seperate file?

Number:Time wants it in seconds

Timeout is an ITEM. This gives you the current timeout (maybe should’ve called it Countdown?) in seconds.

You either define that in UI or in an .items file. NOT in the rules file.

I made some slight modifications and it works. Appriciate your support very much @JimT
How can we make it in the format of 01:20:59 ?

var Timer motionDelayTimer = null
var RuleFlag = null
val int timeoutMinutes = 20

rule "Office Motion Sensor ON" 

when 
    Item Office_Multisensor_6_Motion_Alarm changed from OFF to ON 
then
    if (RuleOfficeLight.state == ON){
        RuleFlag = true
        if(motionDelayTimer === null || motionDelayTimer.hasTerminated) {
            motionDelayTimer = null
            Office_FIBARO_Double_Switch.sendCommand(ON)
            Office_FIBARO_Dimmer.sendCommand(2)
            logInfo("Motion Office", "Movement recognised -> Light ON")
        }
        else {
            logInfo("Motion Office", "Movement recognised -> Timer stop.")
            motionDelayTimer.cancel
            motionDelayTimer = null
            CountdownItem.postUpdate(0)
        }
    }
    else{
        logInfo("Motion Office", "Rule deactivated")
    }

end

rule "Office Motion Sensor OFF"

when
    Item Office_Multisensor_6_Motion_Alarm changed from ON to OFF
then
    if(RuleFlag == true) { 
        CountdownItem.postUpdate(timeoutMinutes * 60) // reset the timeout
        logInfo("Motion Office", "No more movement -> Timer start.")
        RuleFlag = null
        motionDelayTimer = createTimer(now.plusSeconds(1), [ |
        
        val timeout = (CountdownItem.state as QuantityType).intValue 
        if (timeout == 0) {
            motionDelayTimer.cancel
            motionDelayTimer = null
            Office_FIBARO_Double_Switch.sendCommand(OFF)
            logInfo("Motion Office", "Timer elapsed -> Light OFF")
        } 
        else {
            CountdownItem.postUpdate(timeout - 1)
            motionDelayTimer.reschedule(now.plusSeconds(1))
          }
        ])
    }
    else{
        logInfo("Motion Office", "Rule deactivated already")
    }
end

Instead of giving you the answer, I’ll tell you how I got the answer:

If you go to openhab docs Configuration Guide → Items and scroll down to “State”:

you’ll notice a link to Java formatter syntax

Click it, and you’ll go to it, then you read through it… or just do an in-page search for “time format”.

thank you. Appriciate that. I will let you know my results

Ok got it.
The issue is:
If I define the state description to %tS seconds are counted properly from 60 down to 0

As soon as I define the state description with %tM:%tS displayed values are in common seconds.
Not min:sec

UPDATE!:
The proper formatting will be %tT

I have two more problems:

  1. Sometimes I receive this warning. Is it because the Item is updated every second?
2023-03-28 15:45:01.925 [WARN ] [d4j.internal.RRD4jPersistenceService] - Failed to open rrd4j database 'timerOfficeLight' to store data (java.lang.IllegalStateException: request interrupted for file:///var/lib/openhab/persistence/rrd4j/timerOfficeLight.rrd)
  1. If I want to display the countdown in HABPanel I can not format it to the wished format
<div class="countdown">{{itemValue('timerOfficeLight') | date:'HH:mm:SS'}}</div>

seconds are being displayed. What might be wrong?

I can not format Number:Time item in HABPanel widget! I am getting crazy on that.
I tried few suggestions from here but nothing worked.

I wouldn’t think so.

I have no idea what timerOfficeLight is and how it relates to anything else. This is the first time I’ve seen it - you haven’t mentioned it above I think.

Unfortunately I have zero knowledge about HABPanel. I have never used it. Have you tried reading the documentation? That’s what I would do before scouring the forums.

timerOfficeLight is an Number:Time Item
I changed your Timeout item and rename it to timerOfficeLight
In the item language it would be like that:

Number:Time timerOfficeLight "Timer countdown"

In the item configuration the pattern for the State Description is %tT this gives me a nice format.
Unfortunately I do not know how to format it into javascript recognised by the HABPanel widgets. Neither %tT nor HH:mm:SS work :frowning:

does the rrd error happen every second?

No it appears after a bunch of messages.

In addition I see this failure as well:

2023-03-29 08:49:48.461 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job '<unknown>' failed and stopped
java.lang.NullPointerException: cannot invoke method public boolean org.openhab.core.model.script.actions.Timer.cancel() on null
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1177) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1167) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1153) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:1098) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:151) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:878) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:243) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:475) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:251) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:488) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:267) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:475) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:251) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:213) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:47) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:30) ~[?:?]
	at com.sun.proxy.$Proxy424.apply(Unknown Source) ~[?:?]
	at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$12(SchedulerImpl.java:191) ~[?:?]
	at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$1(SchedulerImpl.java:88) ~[?:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
	at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
	at java.lang.Thread.run(Thread.java:829) [?:?]

for the NPE:

var Timer motionDelayTimer = null
var RuleFlag = null
val int timeoutMinutes = 20

rule "Office Motion Sensor ON" 

when 
    Item Office_Multisensor_6_Motion_Alarm changed from OFF to ON 
then
    if (RuleOfficeLight.state == ON){
        RuleFlag = true
        if(motionDelayTimer === null) {
            Office_FIBARO_Double_Switch.sendCommand(ON)
            Office_FIBARO_Dimmer.sendCommand(2)
            logInfo("Motion Office", "Movement recognised -> Light ON")
        } else {
            logInfo("Motion Office", "Movement recognised -> Timer stop.")
            motionDelayTimer.cancel
            motionDelayTimer = null
            CountdownItem.postUpdate(0)
        }
    } else{
        logInfo("Motion Office", "Rule deactivated")
    }

end

rule "Office Motion Sensor OFF"

when
    Item Office_Multisensor_6_Motion_Alarm changed from ON to OFF
then
    if(RuleFlag == true) { 
        CountdownItem.postUpdate(timeoutMinutes * 60) // reset the timeout
        logInfo("Motion Office", "No more movement -> Timer start.")
        RuleFlag = null
        motionDelayTimer = createTimer(now.plusSeconds(1), [ |
        
        val timeout = (CountdownItem.state as QuantityType).intValue 
        if (timeout == 0) {
            motionDelayTimer = null
            Office_FIBARO_Double_Switch.sendCommand(OFF)
            logInfo("Motion Office", "Timer elapsed -> Light OFF")
        } else {
            CountdownItem.postUpdate(timeout - 1)
            motionDelayTimer.reschedule(now.plusSeconds(1))
          }
        ])
    }
    else{
        logInfo("Motion Office", "Rule deactivated already")
    }
end

For the rrd errors, I don’t know.

1 Like

Do I see properly that the only condition as a part of code has been changed/removed?

.... || motionDelayTimer.hasTerminated)

not just that.

this appears usually after 290 sec. from timer kick off. Sometimes it comes twice in the timer period.
Timer is set to 1200sec.

I know this isn’t addressing the issue, but you should probably NOT persist that item anyway. What’s the point of persisting it?

What item do you exactly mean? What persistence?

Some more explanation…
I have an office room without the wall switches (they are physically but have been placed behind the furniture) The only way to lighten the office is movement recognition and time controlled lighting.
I know it is not the best way but only this one i was concentrating on.

By the way I very appreciate your support. You were able to help me in resolving quite big part of the problem. I am not a developer so I would not be able to resolve that issues without your support. Thanks for that again!