Effective use of Timers in Rules

Hi,

for a while I used openHAB rarly, now as OH2 should be stable, I started over again. Now as I start clean, I would like to learn how to write more effective and clever rules.

I have a window and would like to indicate with my hue, if it stays open for a longer period. At the moment I have three rules and it is working:

var Timer GaesteWC_timer = null	

rule "Fensteralarm Gäste-WC Offen"
when
    Item EG_Fenster_Gaeste_WC changed to OPEN
then
			logInfo("GaesteWC_timer","Fenster ist OFFEN")
			GaesteWC_timer = createTimer(now.plusMinutes(5), [|
					EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED)
		])
end

rule "Fensteralarm Gäste-WC TILTED"
when
    Item EG_Fenster_Gaeste_WC changed to TILTED
then
			logInfo("GaesteWC_timer","Fenster ist TILTED")
			GaesteWC_timer = createTimer(now.plusMinutes(7), [|
					EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::GREEN)
		])
end

rule "Fensteralarm Gäste-WC Closed"
when
	Item EG_Fenster_Gaeste_WC changed to CLOSED
then
        if(GaesteWC_timer != null) {
        	EG_Hue_bloom_Wohnzimmer.sendCommand(OFF)
            logInfo("GaesteWC_timer","Canceling the Timer")
            GaesteWC_timer.cancel()
            GaesteWC_timer = null
   	    }
end

I believe this can be done more easily with less of code. I cannot figure it out. This is not working:

var Timer GaesteWC_timer = null	

rule "Fensteralarm Gäste-WC"

when
    Item EG_Fenster_Gaeste_WC changed
then
	if(EG_Fenster_Gaeste_WC == "OPEN") {
			logInfo("GaesteWC_timer","Fenster ist OFFEN")
			GaesteWC_timer = createTimer(now.plusMinutes(5), [|
					EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED)
		])
	}
	if(EG_Fenster_Gaeste_WC == "TILTED") {
			logInfo("GaesteWC_timer","Fenster ist TILTED")
			GaesteWC_timer = createTimer(now.plusMinutes(7), [|
					EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::GREEN)
		])
	}
	else {
        if(GaesteWC_timer != null) {
            logInfo("GaesteWC_timer","Canceling the Timer")
            GaesteWC_timer.cancel()
            GaesteWC_timer = null
   	    }
	}
end

Is somebody able to give me a hint?

Thank you,
Timo

See the Design Pattern postings. That is where I and others document all our cleverness. :wink:

First, let’s look for problems. You do not say in what way this Rule is not working so it will be a challenge.

The first problem I see is you need to access the state of your Item using .state. I.e. EG_Fenster_Gaeste_WC.state == "OPEN"

That is probably the biggest source of your error.

Next, before creating a new Timer you should check to see if there already is a Timer. I’m not sure if the states go CLOSED -> TILTED -> OPEN or CLOSED -> OPEN -> TILTED but in either case, if it transitions from OPEN -> TILTED in less than five minutes you will end up with two Timers running. For example, if you go from OPEN to TILTED then the Hue will go RED in five minutes then GREEN in seven minutes. I assume in this case you would only want it to go to GREEN in seven minutes, skipping the RED.

Thus it will always immediately cancel the timer when the window is OPEN.

So I would add code to always cancel the timer and create a new one based on the current state of the EG_Fenster_Gaeste_WC Item.

then
        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null

	if(EG_Fenster_Gaeste_WC.state == "OPEN") {
			logInfo("GaesteWC_timer","Fenster ist OFFEN")
			GaesteWC_timer = createTimer(now.plusMinutes(5), [|
					EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED)
		])
	}
	if(EG_Fenster_Gaeste_WC.state == "TILTED") {
			logInfo("GaesteWC_timer","Fenster ist TILTED")
			GaesteWC_timer = createTimer(now.plusMinutes(7), [|
					EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::GREEN)
		])
	}
end

The next error I see is you should have an else if for the TILTED if statement. Otherwise, the code will execute the else clause which cancels the timer every time the window is OPEN. This is how the code executes when the window is OPEN.

The window is open so set the timer.
Check to see if the window is Tilted. If not cancel the timer.

Now for reducing lines of code. My suggestion to always cancel and recreate the Timer is a good first start. You can further reduce the lines of code using a switch statement and combine your log statements which would make your rule look like this:

then
        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null
        
        logInfo("GaesteWC_timer", "Fenster ist " + EG_Fenster_Gaeste_WC.state)

        switch EG_Fenster_Gaeste_WC.state {
            case "OPEN": {
                GaesteWC_timer = createTimer(now.plusMinutes(5), [| 
                    EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED) 
                ])
            }
            case "CLOSED": {
                GaesteWC_timer = createTimer(now.plusMinutes(7), [| 
                    EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::GREEN)
                ])
            }
        }
end

Looking better. Now if you really really want as few lines of code as possible, you can further reduce this if you don’t mind long lines. The case statements are essentially one-liners so we can eliminate the curly brackets and spacing of the lambdas which results in:

then
        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null
        
        logInfo("GaesteWC_timer", "Fenster ist " + EG_Fenster_Gaeste_WC.state)

        switch EG_Fenster_Gaeste_WC.state {
            case "OPEN": GaesteWC_timer = createTimer(now.plusMinutes(5), [| EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED) ])
            case "CLOSED": GaesteWC_timer = createTimer(now.plusMinutes(7), [|EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::GREEN)])
        }
end
5 Likes

Dear Rich,

thank you so much for this detailed answer. It helped me alot and I appreciate it very much.

My working :slight_smile: rule looks like this in the moment:

rule "Fensteralarm Gäste-WC Offen"
when
    Item EG_Fenster_Gaeste_WC changed
then
        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null
        Thread::sleep(3000)
        
        logInfo("GaesteWC_timer", "Fenster ist " + EG_Fenster_Gaeste_WC.state)

        switch EG_Fenster_Gaeste_WC.state {
            case "OPEN": {
                GaesteWC_timer = createTimer(now.plusMinutes(5), [| 
                    EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED) 
                ])
            }
            case "TILTED": {
                GaesteWC_timer = createTimer(now.plusMinutes(7), [| 
                    EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::GREEN)
                ])
            }
            case "CLOSED": {
                EG_Hue_bloom_Wohnzimmer.sendCommand(OFF)
            }
        }
end

I also tried your alarm clock example: Alarmclock whit blinking Light

I found some errors eg. lightsBlinkingTimer (in the rule “Alam Lights”) vs. lightBlinkingTimer (in the rule “Stop Alarm”)
the extra S caused me some headache, but I found it! :slight_smile: I’m slowly getting better in it.

This rule worked for me:

rule "Alam Lights"
when
    Item Alarm_MasterEvent received command ON
then
    if(lightsBlinkingTimer != null) {
        lightsBlinkingTimer.cancel
        lightsBlinkingTimer = null
    }

    lightsBlinkingTimer = createTimer(now.plusSeconds(1), [|
        EG_Hue_bloom_Wohn_Switch.sendCommand(if(EG_Hue_bloom_Wohn_Switch.state == ON) OFF else ON)
        lightsBlinkingTimer.reschedule(now.plusSeconds(1))
    ])
end

rule "Stop Alarm"
when
    Item Alarm_MasterEvent received command OFF
then
    if(lightsBlinkingTimer != null) {
        lightsBlinkingTimer.cancel
        lightsBlinkingTimer = null
    }
end

I set up a EG_Hue_bloom_Wohn_Switch as a switch item because the EG_Hue_bloom_Wohnzimmer as a Color item didn’t seems to respond on ON/OFF. I am still wondering why. Without the IF-statement, ON/OFF is working on the Color item, see above in my first rule.

This toggle alarm will of course in the next step be integrated in my "Fensteralarm Gäste-WC Offen" rule.

The only thing I still have to figure out is, how to save the color item status before the “red alarm” to rewrite it when the window will be closed again.

Thanks again so much,
Timo

You have three options:

  1. Save the color to another Item before changing it and restore it from that Item when the Window closes.

  2. Set up persistence and use EG_Hue_bloom_Wohn_Switch.previousState(true) to get at the previous color

  3. Save the HSBType to a global var and restore from that.

Rich, can you give me more details to 1 & 3?

Color LastLightColor
case "OPEN": {
    GaesteWC_timer = createTimer(now.plusMinutes(5), [| 
        LastLightColor.postUpdate(EG_HUE_bloom_Wohnzimmer.state)
        EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED) 
    ])
}

...
case "CLOSED": {
    EG_Hue_bloom_Wohnzimmer.sendCommand(LastLightColor.state)
}
var HSBType lastColor = null

...
case "OPEN": {
    GaesteWC_timer = createTimer(now.plusMinutes(5), [| 
        lastColor = EG_HUE_bloom_Wohnzimmer.state
        EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED) 
    ])
}

...
case "CLOSED": {
    EG_Hue_bloom_Wohnzimmer.sendCommand(lastcolor)
}

It’s a shame you are not interested in 2. That would be my preferred solution. I find you get more capabilty for less code and complexity when you take advantage of the features of OH to solve your problems.

1 Like

I thought that previousState would not be a good idea, as I wanted the lamp to blink acc. to your “Alam Lights” example. Because of toggling every second ON/OFF the previousState is very uncertain.

If I am wrong, please also present solution 2! :slight_smile:

The second reason for avoiding option 2 was: I have not worked with persistence so far and the Add-ons in OH2 are still 1.9.0 so I did not want the risk to go insane.

To re-use code, do you think it would be a good idea to use your option 1 to call the Alarm_MasterEvent like this:

Color LastLightColor
case "OPEN": {
    GaesteWC_timer = createTimer(now.plusMinutes(5), [| 
        LastLightColor.postUpdate(EG_HUE_bloom_Wohnzimmer.state)
        Alarm_MasterEvent.sendCommand(ON)
    ])
}

...
case "CLOSED": {
    EG_Hue_bloom_Wohnzimmer.sendCommand(LastLightColor.state)
    Alarm_MasterEvent.sendCommand(OFF)
}

I got the following error in the log:

java.lang.RuntimeException: The name 'EG_HUE_bloom_Wohnzimmer' cannot be resolved to an item or type.

This was my code:

var HSBType lastColor = null

rule "Fensteralarm Gäste-WC Offen"
when
    Item EG_Fenster_Gaeste_WC changed
then
        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null
        Thread::sleep(3000)
        
        logInfo("GaesteWC_timer", "Fenster ist " + EG_Fenster_Gaeste_WC.state)

        switch EG_Fenster_Gaeste_WC.state {
            case "OPEN": {
                GaesteWC_timer = createTimer(now.plusSeconds(10), [| 
                    lastColor = EG_HUE_bloom_Wohnzimmer.state
                    Alarm_MasterEvent.sendCommand(ON) 
                ])
            }
            case "TILTED": {
                GaesteWC_timer = createTimer(now.plusSeconds(10), [| 
                    lastColor = EG_HUE_bloom_Wohnzimmer.state
                    Alarm_MasterEvent.sendCommand(ON)
                ])
            }
            case "CLOSED": {
                Alarm_MasterEvent.sendCommand(OFF)
                EG_Hue_bloom_Wohnzimmer.sendCommand(lastcolor)
            }
        }
end


rule "Alam Lights"
when
    Item Alarm_MasterEvent received command ON
then
    if(lightsBlinkingTimer != null) {
        lightsBlinkingTimer.cancel
        lightsBlinkingTimer = null
    }

    lightsBlinkingTimer = createTimer(now.plusSeconds(1), [|
        EG_Hue_bloom_Wohn_Switch.sendCommand(if(EG_Hue_bloom_Wohn_Switch.state == ON) OFF else ON)
        lightsBlinkingTimer.reschedule(now.plusSeconds(1))
    ])
end

rule "Stop Alarm"
when
    Item Alarm_MasterEvent received command OFF
then
    if(lightsBlinkingTimer != null) {
        lightsBlinkingTimer.cancel
        lightsBlinkingTimer = null
    }
end

Can you see the problem?

The same happens for option 1:

java.lang.RuntimeException: The name 'EG_HUE_bloom_Wohnzimmer' cannot be resolved to an item or type.

This was the code:

In items:

Color LastLightColor

In rules:

rule "Fensteralarm Gäste-WC Offen"
when
    Item EG_Fenster_Gaeste_WC changed
then
        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null
        Thread::sleep(3000)
        
        logInfo("GaesteWC_timer", "Fenster ist " + EG_Fenster_Gaeste_WC.state)

        switch EG_Fenster_Gaeste_WC.state {
            case "OPEN": {
                GaesteWC_timer = createTimer(now.plusSeconds(10), [| 
                    LastLightColor.postUpdate(EG_HUE_bloom_Wohnzimmer.state)
                    Alarm_MasterEvent.sendCommand(ON) 
                ])
            }
            case "TILTED": {
                GaesteWC_timer = createTimer(now.plusSeconds(10), [| 
                    LastLightColor.postUpdate(EG_HUE_bloom_Wohnzimmer.state)
                    Alarm_MasterEvent.sendCommand(ON)
                ])
            }
            case "CLOSED": {
                Alarm_MasterEvent.sendCommand(OFF)
                EG_Hue_bloom_Wohnzimmer.sendCommand(LastLightColor.state)
            }
        }
end

…and because of this error I believe the rule is not executed any further. The Alarm_MasterEvent is not changing state.

Any idea?

You might have a guess at this. It means it couldn’t find an Item of that name.
In your original rule you have
EG_Hue_bloom_Wohnzimmer
Note case of Hue not HUE

1 Like

Brilliant! It works! Thank you Rossko57!

In items:

Color LastLightColor

In rules:

rule "Fensteralarm Gäste-WC Offen"
when
    Item EG_Fenster_Gaeste_WC changed
then
        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null
        Thread::sleep(3000)
        
        logInfo("GaesteWC_timer", "Fenster ist " + EG_Fenster_Gaeste_WC.state)

        switch EG_Fenster_Gaeste_WC.state {
            case "OPEN": {
                GaesteWC_timer = createTimer(now.plusSeconds(10), [| 
                    LastLightColor.postUpdate(EG_Hue_bloom_Wohnzimmer.state)
                    Alarm_MasterEvent.sendCommand(ON) 
                ])
            }
            case "TILTED": {
                GaesteWC_timer = createTimer(now.plusSeconds(10), [| 
                    LastLightColor.postUpdate(EG_Hue_bloom_Wohnzimmer.state)
                    Alarm_MasterEvent.sendCommand(ON)
                ])
            }
            case "CLOSED": {
                Alarm_MasterEvent.sendCommand(OFF)
                EG_Hue_bloom_Wohnzimmer.sendCommand(LastLightColor.state)
            }
        }
end


rule "Alam Lights"
when
    Item Alarm_MasterEvent received command ON
then
    if(lightsBlinkingTimer != null) {
        lightsBlinkingTimer.cancel
        lightsBlinkingTimer = null
    }
    EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED)
        lightsBlinkingTimer = createTimer(now.plusSeconds(1), [|
                EG_Hue_bloom_Wohn_Switch.sendCommand(if(EG_Hue_bloom_Wohn_Switch.state == ON) OFF else ON)
        lightsBlinkingTimer.reschedule(now.plusSeconds(1))
    ])
end

rule "Stop Alarm"
when
    Item Alarm_MasterEvent received command OFF
then
    if(lightsBlinkingTimer != null) {
        lightsBlinkingTimer.cancel
        lightsBlinkingTimer = null
    }
end

I learned a lot!

Still some ideas to option 2?

That does make it more complicated. You could use historicState(now.minusMinutes(X)) where X is how long ago the lights started blinking. But once you do that you really are not gaining much by using Persistence.

If I understand correctly, I actually wrote that up as a Design Pattern (Separation of Behaviors).

Dear Rich,
dear rossko57,
dear Forum,

thank you very much. I learned a lot and would like to share some of my learnings:

As my window has three states (TILTED, OPEN, CLOSED) I had the issue, if I moved from OPEN to TILTED, it lost the origin status of the light. First I had the following solution. I added

if(Alarm_MasterEvent.state == OFF) Alarm_MasterEvent.sendCommand(ON)

in the Timer. Then I realized to put

    Item Alarm_MasterEvent changed to ON

instead of

    Item Alarm_MasterEvent received command ON

in the when section of the ’rule "Alam Lights". This seems to have the same function.

Furthermore I realized that

var HSBType lastColor = null

does not work. It gave me a mismatch error message in the Eclipse SmartHome Designer. I tried it, it practically did not work. By not defining the type of variable, it worked. I have no explanation for that.

My code looks like that in the moment and my next step will be to add more windows. :slight_smile:

var lastColor = null

rule "Fensteralarm Gäste-WC Offen"
when
    Item EG_Fenster_Gaeste_WC changed
then
        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null
        Thread::sleep(3000)
        
        logInfo("GaesteWC_timer", "Fenster ist " + EG_Fenster_Gaeste_WC.state)

        switch EG_Fenster_Gaeste_WC.state {
            case "OPEN": {
                GaesteWC_timer = createTimer(now.plusSeconds(5), [|Alarm_MasterEvent.sendCommand(ON)])
            }
            case "TILTED": {
                GaesteWC_timer = createTimer(now.plusSeconds(7), [|Alarm_MasterEvent.sendCommand(ON)])
            }
            case "CLOSED": {
        		Alarm_MasterEvent.sendCommand(OFF)               
            }
        }
end


rule "Alam Lights"
when
    Item Alarm_MasterEvent changed to ON
then
    if(lightsBlinkingTimer != null) {
        lightsBlinkingTimer.cancel
        lightsBlinkingTimer = null
    }
    lastColor = EG_Hue_bloom_Wohnzimmer.state
    Thread::sleep(500)
    EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED)
        lightsBlinkingTimer = createTimer(now.plusSeconds(1), [|
                EG_Hue_bloom_Wohn_Switch.sendCommand(if(EG_Hue_bloom_Wohn_Switch.state == ON) OFF else ON)
        lightsBlinkingTimer.reschedule(now.plusSeconds(1))
    ])
end

rule "Stop Alarm"
when
    Item Alarm_MasterEvent received command OFF
then
	EG_Hue_bloom_Wohnzimmer.sendCommand(lastColor)
    if(lightsBlinkingTimer != null) {
        lightsBlinkingTimer.cancel
        lightsBlinkingTimer = null
    }
end

If you should have anything to add, I am always happy to learn from you!

Greetings,
Timo

You may shorten your code

as Rich has already suggested by

lightsBlinkingTimer?.cancel
1 Like

@rlkoshak your timerName?.cancel is a great tip! Saved me so much hassle! Thank you!

Does anybody have a clue how to update the rule?

It seems the HUE-Binding has changed a lot. I’m running on openHAB 2.3.0 now.

Hi, when I use the

timerName?.cancel command in a rule I get the following error message in openhab.log and the rule doesn*t work at all.

2019-08-09 22:35:53.739 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'WZ_Alle_Rollos': 'cancel' is not a member of 'Object'; line 31, column 2, length 18

What is the reason for that and how can it be solved?

You probably haven’t declared the timerName as a Timer variable and it is interpreted as an instance of Object instead.

1 Like

Does anybody have an idea how to transfer the rules into Openhab 3.0?

The main rule checks for the change of the window item and starts a timer based on the three cases. After the timer has expired, it sends the command ON to the virtual item Alarm_MasterEvent:

rule "Fensteralarm Gäste-WC Offen"

when

    Item EG_Fenster_Gaeste_WC changed

then

        GaesteWC_timer?.cancel // the ? will skip this line if GaesteWC_timer is null

        Thread::sleep(3000)

        

        logInfo("GaesteWC_timer", "Fenster ist " + EG_Fenster_Gaeste_WC.state)

        switch EG_Fenster_Gaeste_WC.state {

            case "OPEN": {

                GaesteWC_timer = createTimer(now.plusMinutes(5), [|Alarm_MasterEvent.sendCommand(ON)])

            }

            case "TILTED": {

                GaesteWC_timer = createTimer(now.plusMinutes(7), [|Alarm_MasterEvent.sendCommand(ON)])

            }

            case "CLOSED": {

                Alarm_MasterEvent.sendCommand(OFF)               

            }

        }

end

Changes of the item Alarm_MasterEvent will be handled in these two rules:

rule "Alam Lights"

when

    Item Alarm_MasterEvent changed to ON

then

    if(lightsBlinkingTimer != null) {

        lightsBlinkingTimer.cancel

        lightsBlinkingTimer = null

    }

    EG_Hue_bloom_Wohn_LastColor.postUpdate(EG_Hue_bloom_Wohnzimmer.state)       //tracking state by item

    EG_Hue_bloom_Wein_LastColor.postUpdate(EG_Hue_bloom_Weinschrank.state)

    Thread::sleep(500)

    EG_Hue_bloom_Wohnzimmer.sendCommand(HSBType::RED)

    EG_Hue_bloom_Weinschrank.sendCommand(HSBType::RED)

        lightsBlinkingTimer = createTimer(now.plusSeconds(1), [|

                EG_Hue_bloom_Wohn_Switch.sendCommand(if(EG_Hue_bloom_Wohn_Switch.state == ON) OFF else ON)

                EG_Hue_bloom_Wein_Switch.sendCommand(if(EG_Hue_bloom_Wein_Switch.state == ON) OFF else ON)

        lightsBlinkingTimer.reschedule(now.plusSeconds(1))

    ])

end

and

rule "Stop Alarm"

when

    Item Alarm_MasterEvent received command OFF

then

    EG_Hue_bloom_Wohnzimmer.sendCommand(EG_Hue_bloom_Wohn_LastColor.state)  // posting state of lastColor item

    EG_Hue_bloom_Weinschrank.sendCommand(EG_Hue_bloom_Wein_LastColor.state)

    if(lightsBlinkingTimer != null) {

        lightsBlinkingTimer.cancel

        lightsBlinkingTimer = null

    }

end

I have no clue how to set this up in OH3. The start is easy:

Then I tried this code:

And checked for the status change of item Alarm_MasterEvent:

Analyse tracks no status change, the event log shows no changes and the rule delivers no errors.

Can anybody help? I would love to do it with Blockly but it seems not to have timers at all.

Thanks,
Timo

There are two problems with the code you tried.

  1. GaesteWC_timer is a global variable. I know of no way to define a global variable like that in Rules DSL in the UI. If you have rules that use a global variable like this, you are better off keeping it in .rules files. This is even more the case if that global variable is used by more than one Rule.

  2. The “then” on the first line renders this script syntactically invalid. Script Actions should not include “rule”, “when”, the triggers, “then” and “end”.

And if you are creating Timers anyway, why the sleep for three seconds?