[SOLVED] How to check if a value stays over X for Y seconds

Hi there,

i´m currently trying to write a DSL Rule to check if the current consumption of my air conditioning stays over 250mA for atleast 30 seconds.
The current consumption comes from my Homematic HM-ES-PMSw1-Pl-DN-R1 and the AC peaks to ~330mA when switching on the HM socket and stays under 250mA when the AC is off.
But the AC can also sink to ~260mA when the defined temperature is reached and the AC shuts off for some minutes.

What does my rule do or should do?
I want to know when the AC is switched on and starts working but not when the socket is switched on and the AC only peaks without switching it on.
This should add one annotation to my Grafana chart and another when the AC is switched off.

My current rule also fires when the current consumption peaks over 250mA and i don´t know why.

This is what the event.logs looks like when switching on the socket:

2020-06-22 09:40:57.073 [vent.ItemStateChangedEvent] - itmSteckdoseC changed from 0.00 to 339.00
2020-06-22 09:41:05.067 [vent.ItemStateChangedEvent] - itmSteckdoseC changed from 339.00 to 163.00
2020-06-22 09:43:54.966 [vent.ItemStateChangedEvent] - itmSteckdoseC changed from 163.00 to 162.00
2020-06-22 09:56:19.226 [vent.ItemStateChangedEvent] - itmSteckdoseC changed from 162.00 to 163.00
2020-06-22 09:58:30.462 [vent.ItemStateChangedEvent] - itmSteckdoseC changed from 163.00 to 162.00

The current Consumption only stays over 250mA for 8 seconds…

My current rule:

val String ruleId = "ClimateGrafanaAnnotation"
val String GrafanaAPIKey = "<MyAPIKey>"
val String GrafanaAPIUser = "api_key"

var long last_On  = now.millis
var long last_Off = now.millis
var long last_AnnotationId_On = 0
var Timer timer = null

rule "KlimaanlageGrafanaAnnotation"

when

    Item itmSteckdoseC changed

then

    var String json
    var String url = "http://" + GrafanaAPIUser + ":" + GrafanaAPIKey + "@<MyGrafanaIP>:3000/api/annotations"

    val StringBuilder outputText = new StringBuilder
    outputText.append("Status:\n")

    val currentConsumption = itmSteckdoseC.state
    val lastConsumption = itmSteckdoseC.previousState(true).state
    val minConsumption = 250

    val climateON = ((currentConsumption >= minConsumption) && (lastConsumption < minConsumption))
    val climateOFF = ((currentConsumption < minConsumption) && (lastConsumption >= minConsumption))

    if(climateON && timer === null)
    {
        logInfo(ruleId, "Timer gestartet.")
        timer = createTimer(now.plusSeconds(30), [ |
            if(currentConsumption >= minConsumption)
            {
                outputText.append("Klimaanlage eingeschaltet\n")
                last_On = now.millis
                outputText.append("Zeitpunkt: " + last_On + "\n")
                json =  '{
                    "time":' + Long::toString(last_On) + ',
                    "tags":["climate"],
                    "text":"Klimaanlage eingeschaltet"
                        }'

                var output = sendHttpPostRequest(url, "application/json", json)

                outputText.append("Output: " + output + "\n")

                // Save Annotation-ID to use it later
                last_AnnotationId_On = Long.parseLong(transform("JSONPATH","$.id", output))
                outputText.append("Annotation ID: " + last_AnnotationId_On + "\n")
                logInfo(ruleId, outputText.toString)
            }
            timer = null
        ])        
    }
    else if(climateOFF && timer === null)
    {
        outputText.append("Klimaanlage ausgeschaltet\n")
        last_Off = now.millis
        outputText.append("Zeitpunkt: " + last_Off + "\n")
        json =  '{
            "time":' + Long::toString(last_On) + ',
            "isRegion":true,
            "timeEnd":' + Long::toString(last_Off) + ',
            "tags":["climate"],
            "text":"Klimaanlage"
                }'

        var output = sendHttpPostRequest(url, "application/json", json)

        outputText.append("Output: " + output + "\n")

        // Delete old Annotation
        var String delete_url = url + "/" + last_AnnotationId_On
        var delete_output = sendHttpDeleteRequest(delete_url)
        outputText.append("Delete Output: " + delete_output + "\n")
        logInfo(ruleId, outputText.toString)
    }

end

I´m not sure if the whole timer thing is correct at all.
I used some timers before but not to check if a value stays or atleast has the same value after a certain amount of time.

The annotation part is from this HowTo

kind regards
Michael

The generalised way to do this is indeed with a timer, but slightly different logic.

If value changes over threshold, start a timer. Unless timer already exists, in which case leave it alone.
If value changes below threshold, cancel timer.
If timer code ever executes, you know it has stayed over threshold for the duration.

You can decide in the timer code whether to null itself after execution - so that it may run again immediately - or to just hang around, so it won’t run again until after a below threshold change.

1 Like

Thanks for your answer, i think i now have a better understanding of how timers work in oH.
I tried playing around with a small test rule to see how the timer behavior is.
I think i´m able to get only one “switched on” event but i´m still struggling to get only one “switched off” event.

kind regards
Michael

I think this should solve my problem.

val String ruleId = "ClimateGrafanaAnnotation"
val String GrafanaAPIKey = "<MyAPIKey>"
val String GrafanaAPIUser = "api_key"

var long last_On  = now.millis
var long last_Off = now.millis
var long last_AnnotationId_On = 0
var Timer timer = null

rule "KlimaanlageGrafanaAnnotation"

when

    Item itmSteckdoseC changed

then

    var String json
    var String url = "http://" + GrafanaAPIUser + ":" + GrafanaAPIKey + "@<MyGrafanaIP>:3000/api/annotations"

    val StringBuilder outputText = new StringBuilder
    outputText.append("Status:\n")

    val currentConsumption = itmSteckdoseC.state
    val lastConsumption = itmSteckdoseC.previousState(true).state
    val minConsumption = 250

    val climateON = ((currentConsumption >= minConsumption) && (lastConsumption < minConsumption))
    val climateOFF = ((currentConsumption < minConsumption) && (lastConsumption >= minConsumption))

    if(climateON && timer !== null)
    {
        logInfo(ruleId, "Timer läuft bereits.")
    }
    if(climateON && timer === null)
    {
        logInfo(ruleId, "Timer gestartet.")
        timer = createTimer(now.plusSeconds(30), [ |
                //Do something only when the AC was on for atleast 30seconds
        ])        
    }
    if(climateOFF && timer !== null && timer.hasTerminated)
    {
        //Do something when the timer was running and the AC is now off
        timer = null
    }
    if(climateActive && timer !== null)
    {
        logInfo(ruleId, "Timer abbrechen")
        timer.cancel
        timer = null
    }

end
2 Likes