You are absolutely right. With the indentation I got turned around. But that is absolutely correct, you have to wait a bit after the call to postUpdate before the Item will reflect that state.
Instead of a sleep though we already know what state we sent to the two Items so just keep those values locally instead of relying on calling the .state
.
val startTime = Heizung_Start_Time.state
val endTime = Heizung_End_Time.state
var nowMillis = now.toInstant.toEpochMilli
var runtime = 0
var totalRuntime = Heizung_Runtime_Minutes_Total.state
if(totalRuntime instanceof UnDefType) {
totalRuntime = 0
}
if(newState == OFF) {
endTime = nowMillis
Heizung_Stop_Time.postUpdate(nowMillis)
}
else if(newState == ON) {
startTime = nowMillis
endTime = 0
Heizung_Start_Time.postUpdate(nowMillis)
Heizung_Stop_Time.postUpdate(UNDEF) // undef is a more appropriate than NULL
}
else {
// Solar changed to NULL or UNDEF
// You can check for this and prevent the rule from running with a but only if coindition
return;
}
if(startTime < endTtime) {
runtime = (endTime - startTime) / 60000 // in minutes
totalRuntime = totalRuntime + runtime
logInfo("Heizung", "Heizung Laufzeit in Minuten - " + runtime)
Heizung_Runtime_Minutes_Total.postUpdate(totalRuntime)
}
- Never rely on the state of an Item immediately after you updated it. You know what you updated it to already so just save that as a local variable and use that.
- I don’t see the point of Heizung_Runtime_Minutes so I don’t use it.
-
NULL
means never initialized. UNDEF
means the Item has been initialized but we don’t know what state it is in now. UNDEF
is more appropriate to use here. But in this case I just ignored updating the Item because we handle that by only calculating a new total when the start time occurs before the end time.
- I added some error checking because as it was originally written if Solar or any of the other Items were
NULL
or UNDEF
the rule would explode with errors.
OK, now that we’ve fixed the original rule, what about improvements? I have come to love Duration
. So how would we use Duration
here?
Change your Heizung_Start_Time and Heizung_End_Time Items to DateTime Items instead of Number Items. Change Heizung_Runtime_Minutes_Total to be a Number:time (you might want to change the name to remove the “Minutes” part, that will be clear in a moment why).
var startTime = Heizung_Start_Time.state
var endTime = Heizung_End_Time.state
var totalRuntime = 0
if(Heizung_Runtime_Total instanceof QuantityType){
totalRuntime = (Heizung_Runtime_Total.state as QuantityType).toUnit("s").intValue // get the time in number of seconds
}
if(newState == OFF){
endTime = now
Heizung_End_Time.postUpdate(endTime)
}
else if(newState == ON){
startTime = now
Heizung_Start_Time.postUpdate(startTime)
}
else {
return;
}
if(startTime.isBefore(endTime)){
var runtime = java.time.Duration.between(startTime, endTime)
logInfo("Heizung", "Heizung Laufzeit in Minuten - " + runtime.toMinutes)
totalRuntime = totalRuntime + runtime.toSeconds
Heizung_Runtime_Total.postUpdate(totalRuntime.toString+" s")
}
So why use Number:time here? Because we can apply standard DateTime label formatting to it to have it split into days:hours:minutes:seconds for us. For example, here is the Number:time Item I have for the runtime of my UPS.
I’ve set the Pattern in the State Display Item metadata to %1$tH:%1$tM:%1$tS
and it looks like this:
If you prefer to ignore the seconds, replace the s
units above with min
and replace toSeconds
with toMinutes
at the calculation of the total duration.
In my opinion this version is a little more flexible and a lot more self documenting. It doesn’t require one to translate mathematical expressions to their meanings.
Having gone through all that though, the best approach, using persistence would be…
val minsInDay = 24*60
var totalRuntimeDay = Solar.averageSince(now.minusDays(1)) * minsInDay
var totalRuntimeWeek = Solar.averageSince(now.minusDays(7)) * (minsInDay * 7)
var totalRuntimeMonthSoFar = Solar.averageSince(now.minusDays(now.getDayInMonth) * (minsInDay * now.getDayInMonth)
You can see how this is so much better. We don’t care what state Solar actually is in. As long as we have the data we can calculate how long it’s been on for any arbitrary amount of time. I’ve calculated time in minutes but you should be able to see how to use any units you want to. You’d want to add some checking in case averageSince doesn’t return a value for some reason (DB is unavailable, no data for the time period, etc).