Using a previousState for timer rule

Hi there,

i would like to change my rules with timers for open windows from createTimer to a method with calculation and dummy items.
I´m having problems with the createTimer rules when the window get closed and opened again inside a working timer instance.
Even when checking the state it´s not possible to get these rules working properly…

Now i tried to work with dummy items, cron rules and the previousState.

  1. When a window state changes from CLOSED to OPEN a rule checks the temperature outside and then writes a numeric value (e.g. 60) in the dummy item for this window
  2. A cron rule starts every minute and …
    2.1 checks if the current state is 0 and the previous state was 1, so i know that the item changed from 1 to 0. In this case i´m able to send a message.
    2.2 checks if the current state is >= 1 and the window is still open, so it substract 1 from the dummy. If the dummy is already at the 0 it doesn´t get reduced anymore.

The problem is that tWindowKitchen.previousState doesn´t return the pure value.
It returns a timestamp, the item name and the previous value…

How do i only get the previous value instead of the whole persistence entry?

And the other question is how do i change the persistence for my three dummy items so they save every update and not just every change?
Currently the previousState would stay at 1 when the dummy was reduced, because i´m only saving changes.
I would like to save the state every minute and not just when it changed.
That´s the only way to be sure that a message only gets send once.

Thanks
Michael

  • Platform information:
    • Hardware: Raspberry Pi 3
    • OS: raspbian 8 jessie
    • Java Runtime Environment: Oracle 1.8.0_161
    • openHAB version: 1.8.3

previousState is not working correct. I open a Ticket, but it is not solved at the moment.

What I do is a little bit different.

I start the timer if window is open and cancel the timer if window gets closed.

I wrote a procedure for this named escalationtimer. You can send a maximum time the window should be open before a message will send.

import java.util.HashMap
var HashMap<String, Timer>         tEscalationTimer = newHashMap()

// relatedItem = Item welches überwacht wird
// tEscalationTimer = Hashmap Timer of the escalation
// iEscalationTimer = Hashmap min of the timer to run before escalate
// stEscalationTimer = Hashmap Message with put into stEskalationMessage Item
// iEscalationTimerStartInterval = min of the timer to run before escalate
// fuEscalationFunction = Function which should be executed if escalationinterval is reached
val Procedures$Procedure6<GenericItem, HashMap<String, Timer>, HashMap<String, Integer>, HashMap<String, String>, Integer, Functions$Function1<GenericItem, Boolean>> startEscalationTimer=[
    relatedItem,
    tEscalationTimer,
    iEscalationTimer,
    stEscalationTimer,
    iEscalationTimerStartInterval,
    fuEscalationFunction |

    // little cosmetic if there is some error messages somewhere
    if(stEscalationTimer.size == 0)
        stEskalationMessage.postUpdate("")

    // EscalationTimer should start
    if(iEscalationTimerStartInterval != 0)
    {
        // set escalationsstartinterval in sec. to Hashmap
        iEscalationTimer.put(relatedItem.name, iEscalationTimerStartInterval)

        // if still an escalation timer for this item runs, restart it will full value
        if(tEscalationTimer.get(relatedItem.name) !== null)
        {
            logInfo("Loesslein", "EscalationTimer for Item " + relatedItem.label + " restarted with value " + iEscalationTimer.get(relatedItem.name) + " sec.")
            tEscalationTimer.get(relatedItem.name).reschedule(now.plusSeconds(iEscalationTimer.get(relatedItem.name)))
        }
        else
        {
            logInfo("Loesslein", "EscalationTimer for Item " + relatedItem.label + " started with value " + iEscalationTimer.get(relatedItem.name) + " sec.")
            tEscalationTimer.put(relatedItem.name, createTimer(now.plusSeconds(iEscalationTimer.get(relatedItem.name)))
            [ |
                stEscalationTimer.put(relatedItem.name,"Problems with "+relatedItem.label)
                stEskalationMessage.postUpdate("Problems with "+relatedItem.label)

                // if escalationinterval is more then 5min restart timer
                iEscalationTimer.put(relatedItem.name,iEscalationTimer.get(relatedItem.name)/2)
                if(iEscalationTimer.get(relatedItem.name) >= 300)
                {
                    logInfo("Loesslein", "EscalationTimer for Item " + relatedItem.label + " decreased with value " + iEscalationTimer.get(relatedItem.name) + " sec.")
                    tEscalationTimer.get(relatedItem.name).reschedule(now.plusSeconds(iEscalationTimer.get(relatedItem.name)))
                    stEscalationTimer.put(relatedItem.name,"Problems with "+relatedItem.label)
                    stEskalationMessage.postUpdate("Problems with "+relatedItem.label)

                    // do whatever you need by every escalation interval change
                    logInfo("Loesslein", "Escalation for Item  " + relatedItem.label + " initialized")
                    fuEscalationFunction.apply(relatedItem)
                }

                // If interinterval is shorter then 5min give up and end timer
                else
                {
                    // If after first escalation the time to no more escalation is reached put message to string
                    if(stEscalationTimer.get(relatedItem.name) !== null)
                    {
                        stEscalationTimer.put(relatedItem.name,"Problems with "+relatedItem.label)
                        stEskalationMessage.postUpdate("Problems with "+relatedItem.label)
                    }

                    tEscalationTimer.get(relatedItem.name).cancel()
                    tEscalationTimer.put(relatedItem.name, null)
                    tEscalationTimer.remove(relatedItem.name)

                    // do whatever you need by escalation ended because interval is to short
                    logInfo("Loesslein", "EscalationTimer for Item " + relatedItem.label + " ended but not solved.")                    
                    fuEscalationFunction.apply(relatedItem)
                }
            ])
        }
    }
    // escalationTimer stopped manually because incident is no longer available
    else
    {
        // timer still running, but no excalation needed any more
        if(tEscalationTimer.get(relatedItem.name) !== null)
        {
            tEscalationTimer.get(relatedItem.name).cancel()
            tEscalationTimer.put(relatedItem.name, null)
            tEscalationTimer.remove(relatedItem.name)

            // if an escalation message exist delete it from hashmap
            if(stEscalationTimer.get(relatedItem.name) !== null)
                stEscalationTimer.remove(relatedItem.name)
            
            // if there are any other escalation messaged available post one of them
            if(stEscalationTimer.size == 0)
                stEskalationMessage.postUpdate("")
            else
                stEskalationMessage.postUpdate(stEscalationTimer.values.get(0))
            logInfo("Loesslein", "EscalationTimer for Item "  + relatedItem.label + " no longer needed - canceled ")
        }
        
        // timer was ended before, now post that the incident is ended too
        else
        {
            if(stEscalationTimer.get(relatedItem.name) !== null)
            {
                stEscalationTimer.remove(relatedItem.name)
                if(stEscalationTimer.size == 0)
                    stEskalationMessage.postUpdate("")
                else
                    stEskalationMessage.postUpdate(stEscalationTimer.values.get(0))
                logInfo("Loesslein", "EscalationTimer for Item "  + relatedItem.label + " solved after escalationinterval ended")
            }
        }
    }
]
rule "Check bathroom window open too long"
    when
        Item DoorSensor01 changed
    then
        Thread::sleep(100)
        if(DoorSensor01.state == OPEN)
        {
            startEscalationTimer.apply(DoorSensor01, tEscalationTimer, iEscalationTimer, stEscalationTimer, (nuDoorSensor01_Delay.state  as DecimalType).intValue * 60, doEscalationForItem)
        }
        else if(DoorSensor01.state == CLOSED)
        {
            startEscalationTimer.apply(DoorSensor01, tEscalationTimer, iEscalationTimer, stEscalationTimer, 0, doEscalationForItem)
        }
end

sitemap:

Text item=stEskalationMessage visibility=[stEskalationMessage!=""]

tWindowKitchen.previousState.state

But there are other approaches to cancelling a Timer already in operation when an event happens. They key is to keep a handle to your timer, then a rule triggered from the new event can look to see if the is already running and the cancel orreschedule as you wish.
Example

Thanks for the hint!
This syntax is working.

But it´s not giving me the previous value…
It´s the same value as the current value.
Even when using tWindowKitchen.historicState(now.minusMinutes(1)).state i´m not getting the previous value…

I already tried to just give me a couple of values with logInfo to check them.
It doesn´t matter how far i go back in time, it´s always the same value.
And when i´m not using .state at the end, all timestamps are the same!

I´m now using a second dummy that start with the same value +1.
This way i´m able to check if timer is 0 and timer2 is 1 to send a message and abort when both are 0.

It´s not the best way but the easiest for me and my skills :slight_smile:

Actually it’s tWindowsKitchen.previousState().state to be on the safest possible side.
Besides that, my rules work with that syntax and 2.2 stable and mysql-persistence.

What are you using?

Doesn´t matter it´s not working for me.

I´m using the RRD4J:

Strategies {
							everyMinute : "0 * * * * ?"
							everyHour : "0 0 * * * ?"
							everyDay  : "0 0 0 * * ?"

        			// if no strategy is specified for an item entry below, the default list will be used
							default = everyChange
}

Items {
							* : strategy = everyChange, everyDay, restoreOnStartup
							Temperatur* : strategy = everyHour
}

RRD4j isn’t working for this purpose. It’s how rrd is saving the values - it won’t work for this purpose.
read the docs, please:
https://docs.openhab.org/addons/persistence/rrd4j/readme.html

This service cannot be directly queried, because of the data compression. You could not provide precise answers for all questions.

besides that: rrd can’t store non-numerical items - in your case Contact items. That’s two things, that make it impossible for your rule to get historic states.
also in the docs:

NOTE: rrd4j is for storing numerical data only. Attempting to use rrd4j to store complex datatypes (eg. for restore-on-startup) will not work.

For historic items, you have to use a persistence, which saves the values sequentially in a database. Presently only mysql, influxdb (I hope!) and relational databases connected via JDBC work that way. And mapdb and JPA connected databases should also give you at least the previousState().

I agree with @binderth here.
Try using mapdb as a minimum for persistence.
It will allow you to use previousState and also the restoreOnStartup options
Mapdb comes bundled with openHAB and all you have to do is install the persistence , set-up the persistence strategies and you are good to go.

Good luck

Good to know that the readme says This service cannot be directly queried, while the persistence page in the wiki says it is queryable…
Then i will move to a mysql database cause i´m already have a server running for kodi.

The tWindowSchlafzimmer is an numeric value.

Thanks for your help!

With previousState i could atleast save one dummy item :slight_smile:
Still need one dummy for the working timer and one dummy to know what value was set.
That way i´m able to send a message after the working timer is done and get the information how long the window was open from the second dummy.

Alternative approach, no previousState required

2.1 checks if the current state is 1 and send a message.

Count down from 61 to zero

which one do you mean?

  1. don’t use the github-wiki anymore - it’s deprecated. anything was moved to docs.openhab.org
  2. this one is/was the generic persistence description - that’s why there’s caveats in each persistence technology

Thanks for all your help!

I´m now running with the following rules:

wBadezimmer.rules (w = window)
This rule triggers the timers.

import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import org.openhab.core.library.types.DateTimeType
import org.joda.time.*

rule "Timer Fenster Badezimmer"

when

    Item itmFenster_Badezimmer changed from CLOSED to OPEN

then

	var t30 = (wTemp.state < 5)
	var t60 = (wTemp.state < 12)
	var t90 = (wTemp.state < 20)
	var t120 = (wTemp.state > 20)
	
	if(t30)
	{
		tWindowBadezimmer.postUpdate(31)
		t2WindowBadezimmer.postUpdate(30)
		logDebug("SetTimerBadezimmer","Der Timer für das Badezimmer Fenster wurde auf 30min gesetzt.")
		return(false)
	}
	if(t60)
	{
		tWindowBadezimmer.postUpdate(61)
		t2WindowBadezimmer.postUpdate(60)
		logDebug("SetTimerBadezimmer","Der Timer für das Badezimmer Fenster wurde auf 60min gesetzt.")
		return(false)
	}
	if(t90)
	{
		tWindowBadezimmer.postUpdate(91)
		t2WindowBadezimmer.postUpdate(90)
		logDebug("SetTimerBadezimmer","Der Timer für das Badezimmer Fenster wurde auf 90min gesetzt.")
		return(false)
	}
	if(t120)
	{
		tWindowBadezimmer.postUpdate(121)
		t2WindowBadezimmer.postUpdate(120)
		logDebug("SetTimerBadezimmer","Der Timer für das Badezimmer Fenster wurde auf 120min gesetzt.")
		return(false)
	}

end

tBadezimmer.rules (t = timer)
This rule reduces the timer, set a new timer and sends a message once the timer reaches 1

import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import org.openhab.core.library.types.DateTimeType
import org.joda.time.*
import java.lang.Math.*

rule "Erinnerung Fenster Badezimmer"

when

	Time cron "0 0/1 * * * ?" //jede Minute

then

	//Anleger der Variablen
	var oBadezimmer = (itmFenster_Badezimmer.state == OPEN)
	var cBadezimmer = (itmFenster_Badezimmer.state == CLOSED)

	//Abbrechen wenn das Fenster geschlossen wird
	if(cBadezimmer)
	{
		logDebug("WindowTimerBadezimmer", "Timer abgebrochen, Fenster wurde geschlossen")
		tWindowBadezimmer.postUpdate(0)
		t2WindowBadezimmer.postUpdate(0)
		return(false)
	}

	//Abbrechen wenn der Timer 0 erreicht hat oder ist
	if(tWindowBadezimmer.state == 0)
	{
		logDebug("WindowTimerBadezimmer", "Timer beendet oder nicht aktiv.")
		return(false)
	}

	//Prüfen ob eine Nachricht versendet werden darf und das Fenster noch offen ist
	if((tWindowBadezimmer.state == 1) && oBadezimmer)
	{
		sendTelegram("bot1", "Achtung, das Fenster im Badezimmer ist seit " + t3WindowBadezimmer.state + " Minuten offen!")
		sendTelegram("bot2", "Achtung, das Fenster im Badezimmer ist seit " + t3WindowBadezimmer.state + " Minuten offen!")
		tWindowBadezimmer.postUpdate(30)
		t2WindowBadezimmer.postUpdate((t2WindowBadezimmer.state as DecimalType) + 30)
		logDebug("WindowTimerBadezimmer", "Erinnerung wurde versendet!")
	}

	//Prüfen ob die Timer noch laufen müssen und um 1min reduzieren
	if((tWindowBadezimmer.state >= 1) && oBadezimmer)
	{
		if(tWindowBadezimmer.state >= 1)
		{
			logDebug("WindowTimerBadezimmer", "Restzeit: " + tWindowBadezimmer.state)
			tWindowBadezimmer.postUpdate((tWindowBadezimmer.state as DecimalType) - 1)
		}
	}

end