Extract hours, mins from Duration (?)

I have a rule that stores the duration of the overall heating process of my central heater…from Start to Stop. Normally it takes a few hours. What i’m already able to see is the following output stored in Delta:


PT4H6M59.673275S

which I guess is 4hours and 6minutes.

IS there a way to extract it into two sepearte variables mins and hours as numbers?

here is the rule simplified to the core, i’m on latest v3 Milestone 4 version of Openhab.

rule "ETA SH20_Start/Stop Logger"
when
    Item EtaKesselState received update
then
    if (EtaKesselState.state=="Heizen" && EtaState_Switch.state==OFF) {
       EtaStart = now
    else
    if (EtaKesselState.state=="Bereit" && EtaState_Switch.state==ON){
       var EtaDelta = Duration.between(EtaStart, now)
   }
end

Thanks a lot for your review,
Norbert

Might help

var myDurationInHours = EtaDelta.toHours   // e.g. 136.78
var myDurationJustTheMinutes = EtaDelta.toMinutesPart  // e.g. 45
var myDurationString = EtaDelta.format(DateTimeFormatter.ofPattern("HH:mm")
1 Like

Thanks a lot! Norbert

Sorry to bother again, did adapt the rule accordingly…first received an error. After short search i found out quick that i missed a required libraray: import java.time.format.DateTimeFormatter

I thought this was it, but after todays heatup i again receive error messages:
Script execution of rule with UID 'eta-1' failed: 'format' is not a member of 'java.time.Duration'; line 24, column 32, length 53 in eta

The rule simplfied looks like:

import java.time.Duration
import java.time.format.DateTimeFormatter
var ZonedDateTime EtaStart = null

// ***************************************************************************************************
// ***************************************************************************************************
rule "ETA SH20_Start/Stop Logger"
when
    Item EtaKesselState received update
then
    if (EtaKesselState.state=="Heizen" && EtaState_Switch.state==OFF) {
       EtaStart = now }
    else
    if (EtaKesselState.state=="Bereit" && EtaState_Switch.state==ON){
       var EtaDelta = Duration.between(EtaStart, now)
       var EtaDurationString = EtaDelta.format(DateTimeFormatter.ofPattern("HH:mm"))
       val mailActions = getActions("mail","mail:smtp:gmailsmtp")
       mailActions.sendMail("xxxxxxxxxxxxxxx", "ETA SH20 | Finished " + PufferState.state + "%", "Run-Time: " + EtaDurationString)
    }

end

Yeh, that looks like duff info I found. Good for times but not duration.
Don’t use that, then.

// crude
var EtaDurationString = EtaDelta.toHours.toString + ":" + EtaDelta.toMinutesPart.toString

Fancier methods here

Beware that many suggestions mess up with duration > 24 hours

1 Like

hm, I now used the more elegant way you linked above…but still there is a strange error message that does not help me a lot. Any idea what still is not the way it should be…

2021-12-08 00:22:47.674 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'eta.rules', using it anyway:
This expression is not allowed in this context, since it doesn't cause any side effects.

2021-12-08 00:23:04.463 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'eta-1' failed: null in eta
rule "ETA SH20_Start/Stop Logger"
when
    Item EtaKesselState received update
then
    if (EtaKesselState.state=="Heizen" && EtaState_Switch.state==OFF) {
       EtaStart = now }
    else
    if (EtaKesselState.state=="Bereit" && EtaState_Switch.state==ON){
       var EtaDelta = Duration.between(EtaStart, now)
       String hms = String.format("%d:%02d:%02d",
                                EtaDelta.toHours(),
                                EtaDelta.toMinutesPart(),
                                EtaDelta.toSecondsPart());
       val mailActions = getActions("mail","mail:smtp:gmailsmtp")
       mailActions.sendMail("xxxxxxxxxxxxxxx", "ETA SH20 | Finished " + PufferState.state + "%", "Run-Time: " + hms)
    }
end

That’s usually written String::format( in DSL, I don’t know if that is your problem.

You’ll need to declare hms with var or val, too

1 Like

Thanks. did play arround and struggling with the following error message.

2021-12-08 22:38:03.644 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'eta-1' failed: null in eta

even if I remove all new code based on your help its still there…and that is strange

import java.time.Duration
//import java.time.format.DateTimeFormatter
var ZonedDateTime EtaStart = null

// ***************************************************************************************************
// ***************************************************************************************************
rule "ETA SH20_Start/Stop Logger"
when
    Item EtaKesselState received update
then
    if (EtaKesselState.state=="Heizen" && EtaState_Switch.state==OFF) {
       EtaStart = now
    }
    else
    if (EtaKesselState.state=="Bereit" && EtaState_Switch.state==ON){
       var EtaDelta = Duration.between(EtaStart, now)
//       var String hms = String.format("%d:%02d:%02d",
//                                EtaDelta.toHours(),
//                                EtaDelta.toMinutesPart(),
//                                EtaDelta.toSecondsPart());
       val actions = getActions("pushover", "pushover:pushover-account:account")
       actions.sendMessage("Finished in " + EtaDelta +" | " + PufferState.state + "% | " + "["+PUtopTemp.state+"/"+PUobenTemp.state+"/"+PUuntenTemp.state+"]°C", "ETA SH20 | Heating")
       val mailActions = getActions("mail","mail:smtp:gmailsmtp")
       mailActions.sendMail("xxxxxxx", "ETA SH20 | Finished " + PufferState.state + "%", "Run-Time: " + EtaDelta)
    }

end

Use logInfo() to follow progress through your rule, important values, and help identify where it breaks. Finding out beats staring at code.

This looks interesting though -

var ZonedDateTime EtaStart = null

rule "ETA SH20_Start/Stop Logger"
...
var EtaDelta = Duration.between(EtaStart, now)

I can’t see anything stopping you arriving there with EtaStart still null.

thanks, will do a better debugging/testing approach.

Only short question, what do you mean by “arriving still null”.
The rule overall works in a way that first (when heating starts) it will take for sure the IF part and remember a timestamp in EtaStart. Only once as the State_Switch would lock up so its not going another time in there. As soon as heating is finished it would start the “else if” part and do the duration calculations…

Do you already see an obvious issue i’m having?

Kind REgards Norbert

Well, let’s find out.
Nothing in the rule as it is written stops it triggering from an update (not a change) of EtaKesselState to “Bereit”, while EtaStart still = null.
You might have other reasons to think that cannot happen.

OK, sure the rule will be triggered every time EtaKesselState is updated…even if the value does not change…but still the if will not allow any change if conditions are not met.

what i missed and this for sure makes it not easy to follow for you is the fact that inside the if and the else if there will be changed this LOCK_variable "EtaState_Switch vice-versa…
I tried to remove all parts not important but did remove this locking mechanism. Not sure if there is a much better way to handle such behaviour.

import java.time.Duration
var ZonedDateTime EtaStart = null


rule "ETA SH20_Start/Stop Logger"
when
    Item EtaKesselState received update
then
    if (EtaKesselState.state=="Heizen" && EtaState_Switch.state==OFF) {
       EtaStart = now
       EtaState_Switch.sendCommand(ON) }
    else
    if (EtaKesselState.state=="Bereit" && EtaState_Switch.state==ON){
       var EtaDelta = Duration.between(EtaStart, now)
       EtaState_Switch.sendCommand(OFF)
       val mailActions = getActions("mail","mail:smtp:gmailsmtp")
       mailActions.sendMail("asdfasdfn@live.com", "ETA SH20 | Finished " + PufferState.state + "%", "Run-Time: " + EtaDelta)
    }
end

@rossko57 I now did analyze the rule and found out that most of the time it works as it should. nicely.
Only few times it does not start correctly in the IF part so it never switches ON and so it never stops the ELSE IF part. But as soon as this faulty scenario is done, it again works.

Script execution of rule with UID 'eta-1' failed: An error occurred during the script execution: Cannot assign a value in null context. in eta

Without good knowledge about variables being reset (e.g. when rules are changed and their files stored) my assumltion is that this error log messages comes into play…so for whatever reason a NULL issue shows up.Only i do not see any problem, as the IF part does not use EtaStart reading…so its NULL state is irrelevant and set anyway to “NOW”.

Or does this error mean that if the “else if” part would be considerred with NULL there would happen a problem. But in reality this is not going to happen.

Any idea - if this is the issue - how i could overcome this error message in the logs.

Kind Regards
Norbert

These messages are horrid; you don’t know what’s behind it and must find out. Use the available tools to find out.

If you edit the rules file, EtaStart will be null. You told it make it null at the top of your rule file.
If you edit the rules file, that won’t change any pre-existing Item states.
Let’s find out.

Careful here. A valid Item with a NULL state has little to do with null variables. You can of course trip over a null if you do things like trying to get the decimal value of NULL, but that doesn’t look likely here. Let’s find out.

then
    logInfo("test", "My rule is triggered")
    logInfo("test", "These look important -")
    logInfo("test", "EtaKesselState state -" + EtaKesselState.state.toString)
    logInfo("test", "EtaState_Switch state -" + EtaState_Switch.state.toString)
    logInfo("test", "PufferState state -" + PufferState.state.toString)
    if (EtaStart === null {
        logInfo("test", "EtaStart is null")
    } else {
        logInfo("test", "EtaStart is " + EtaStart.toString)
    }

    if (EtaKesselState.state=="Heizen" && EtaState_Switch.state==OFF) {
       logInfo("test", "Satisfied the first if()")
       EtaStart = now
       EtaState_Switch.sendCommand(ON) }
    else
    if (EtaKesselState.state=="Bereit" && EtaState_Switch.state==ON){
       logInfo("test", "Satisfied the second if()")
       var EtaDelta = Duration.between(EtaStart, now)
       EtaState_Switch.sendCommand(OFF)
       val mailActions = getActions("mail","mail:smtp:gmailsmtp")
       if (mailActions === null {
            logInfo("test", "Failed to get mailActions")
       }
       mailActions.sendMail("asdfasdfn@live.com", "ETA SH20 | Finished " + PufferState.state + "%", "Run-Time: " + EtaDelta)
    } else (
       logInfo("test", "Neither if() satisfied")
    )
end