Calculate time on and energy use for an item/switch rule

Hi,
as your rule can trigger several times without changing the state, I would seperate the tracking into another rule. The variable declarations need to go to the top of the file below the import statements:

var DateTime RoofEnabled        // time when heating was switched on
var DateTime RoofDisabled       // time when heating was switched off
var long TimeEnabled               // cumulative time the heating was on in [milliseconds]
var float Energy                       // Energy consumed by heating
var float Power = 1800             // Enter total power consumption by both plugs here [in Watts]

rule "Track RoofCable on off"
when
    Item Roof changed    // does this work with a group?? Expert knowledge needed here ;-)
then
    if (Roof.state == ON) {
             RoofEnabled = now
    }
    else {
             RoofDisabled = now
             TimeEnabled = TimeEnabled + RoofDisabled.millis - RoofEnabled.millis
             Energy = TimeEnabled/1000 * Power      // in Joule
             Energy = Energy / 3600                         // in kWh
    }   
end

Some initialization to avoid summing up of undefined values

rule "Startup init"
when
     System started
then
     RoofEnabled = now
     RoofDisabled = now
     TimeEnabled = 0
     Energy = TimeEnabled/1000 * Power      // in Joule
     Energy = Energy / 3600                         // in kWh
end

If you want to keep the Energy consumed over system restarts you need to persist the TimeEnabled variable and change the Startup rule accordingly. How to do that I have no idea, yet :wink:

I don’t know if the state works on groups. If not, you can change to the individual plug states.

1 Like

was getting errors on data types with above (int vs number and similar).
got it to store a sample value in rrd4j but can’t get that to display in sitemap - assuming type mismatch.
current rule:
rule "track roof state" when Item Roof changed then var long RoofCableStart var long RoofCableStop var long RoofCableTimeOn // var float RoofCableEnergy // var float RoofCablePower=1800 // sendMail("my@email","OpenHAB Roof","Roof Heat changed state to:" + Roof.state) if (Roof.state == ON) { RoofCableStart=now.millis } else { RoofCableStop=now.millis RoofCableTimeOn=RoofCableStop-RoofCableStart postUpdate(RoofTime, RoofCableTimeOn) // RoofCableEnergy=RoofCableTimeEnabled/1000*RoofCablePower // in Joule // RoofCableEnergy=RoofCableEnergy/3600 // in kWh } end

In sitemap I have:
Text item=RoofCableTimeOn Text item=RoofTime
this is the log output:
12:41:30.981 [DEBUG] [m.r.internal.engine.RuleEngine:305 ] - Executing rule 'track roof state' 12:41:30.996 [DEBUG] [.p.rrd4j.internal.RRD4jService:132 ] - Stored 'RoofTime' with state '1448818890770' in rrd4j database 12:41:31.057 [DEBUG] [.p.rrd4j.internal.RRD4jService:113 ] - Stored 'Z_socket2' with state 'ON' in rrd4j database (again) 12:41:31.060 [DEBUG] [.p.rrd4j.internal.RRD4jService:132 ] - Stored 'Z_socket2' with state '0' in rrd4j database 12:41:31.100 [DEBUG] [.p.rrd4j.internal.RRD4jService:132 ] - Stored 'RoofTime' with state '1448818891014' in rrd4j database 12:41:31.221 [ERROR] [o.u.i.items.ItemUIRegistryImpl:438 ] - Cannot retrieve item RoofCableTimeOn for widget org.openhab.model.sitemap.Text 12:41:31.224 [ERROR] [o.u.i.items.ItemUIRegistryImpl:438 ] - Cannot retrieve item RoofCableTimeOn for widget org.openhab.model.sitemap.Text 12:41:31.226 [ERROR] [o.u.i.items.ItemUIRegistryImpl:438 ] - Cannot retrieve item RoofCableTimeOn for widget org.openhab.model.sitemap.Text 12:41:31.979 [DEBUG] [.p.rrd4j.internal.RRD4jService:132 ] - Stored 'Roof' with state '0' in rrd4j database

There is a long thread here where I helped someone implement just such a counter for a generator.

1 Like

very close but getting an error with data types in openHAB designer:
“type mismatch: cannot convert from org.openhab.core.types.State to java.lang.Number”
on line:
val long oldVal = if(RoofRuntimeMsec.state == Undefined || RoofRuntimeMsec.state == Uninitialized) 0 else (RoofRuntimeMsec.state as Number).longValue

below is log output looks like msec is ok but hours are not calculated

2015-11-30 21:21:34.024 [INFO ] [penhab.model.script.RoofUpTime] - Adding current time to total: timeSinceLastUpdate = 999 oldVal = 242992
2015-11-30 21:21:34.041 [INFO ] [penhab.model.script.RoofUpTime] - Current time hours = 0.0
2015-11-30 21:21:35.033 [INFO ] [penhab.model.script.RoofUpTime] - Adding current time to total: timeSinceLastUpdate = 1002 oldVal = 243991
2015-11-30 21:21:35.056 [INFO ] [penhab.model.script.RoofUpTime] - Current time hours = 0.0

full rule:
import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import java.util.*
import java.text.SimpleDateFormat
import java.lang.Math
import org.joda.time.*

var org.joda.time.DateTime whenStarted = null
rule "Roof started"
when

Item Roof received command ON
then
whenStarted = now
end

rule "Roof stopped"
when

Item Roof received command OFF
then
if(whenStarted != null) {
// do the same as the if(Roof.state == ON && whenStarted != null) in the increment rule
}
whenStarted = null
end

rule "Increment Roof Runtimes"
when

Time cron “* */1 * * * ?”
then
if(Roof.state == ON && whenStarted != null){
val long nowMsec = now.millis
val long wsMsec = whenStarted.millis
whenStarted = now
val long timeSinceLastUpdate = nowMsec - wsMsec
val long oldVal = if(RoofRuntimeMsec.state == Undefined || RoofRuntimeMsec.state == Uninitialized) 0 else (RoofRuntimeMsec.state as Number).longValue
logInfo(“RoofUpTime”, "Adding current time to total: timeSinceLastUpdate = " + timeSinceLastUpdate + " oldVal = " + oldVal)
val long totalMsec = oldVal + timeSinceLastUpdate // calculate total runtime
RoofRuntimeMsec.postUpdate(totalMsec) // post the full runtime
// val long hours = Math::round(totalMsec/1000/60/60)
val long temp = Math::round((totalMsec/100/60/60) * 10) // calculate the hours and move the decimal place before rounding to preserve the tenths place
val double hours = temp / 10.0 // move the decimal place back to get the tenth’s place, change the type to double so we don’t lose the decimal place
logInfo(“RoofUpTime”, "Current time hours = " + hours)
RoofRuntimeHours.postUpdate(hours)
}
else if(Roof.state == ON && whenStarted == null){
whenStarted = now
}
else {

}
end

just an update on this:
“val double hours = totalMsec/1000.0/60.0/60.0”
is now returning correct number of hours.
“Incompatible types. Expected double or java.lang.Double but was java.math.BigDecimal” and
“type mismatch: cannot convert from org.openhab.core.types.State to java.lang.Number”
are present in Designer but no errors in log and the rule is working.
thank you everyone for helping with this.

This should be:

(RoofRuntimeMsec.state as DecimalType).longValue

An Item carries a State which in turn carries the Number which you can get at using longValue, intValue, doubleValue, etc.

This is because by default the Rules engine casts all numerical values to BigDecimal. So if you do something like the following it should eliminate that warning:

val double hours = (totalMsec/1000.0/60.0/60.0).doubleValue

Rich thank you very much! This is now perfect and calculates time on and energy used in kWh.
This is the full working setup for anyone looking to do this in the future:
ITEMS:
Number RoofRuntimeHours “Roof Cable Runtime [%.2f h]”// used on sitemap
Number RoofRuntimeMsec // used to store intermediate values which get rounded
Number RoofEnergy “Roof Energy used [%.2f kWh]”

SITEMAP:
Text item=RoofRuntimeHours icon=“selfEnergy”
Text item=RoofEnergy icon=“selfEnergy”

RULE:
import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import java.util.*
import java.text.SimpleDateFormat
import java.lang.Math
import org.joda.time.*

var org.joda.time.DateTime whenStarted = null
var Number RoofPower = 1650

rule "Roof started"
when
Item Roof received command ON
   then
whenStarted = now
   end

rule "Roof stopped"
    when
Item Roof received command OFF
    then
if(whenStarted != null) {
}
whenStarted = null
    end

rule "Increment Roof Runtimes"
when
Time cron "* */1 * * * ?"
then

if(Roof.state == ON && whenStarted != null){
val long nowMsec = now.millis
val long wsMsec = whenStarted.millis
whenStarted = now
val long timeSinceLastUpdate = nowMsec - wsMsec
val long oldVal = if(RoofRuntimeMsec.state == Undefined || RoofRuntimeMsec.state == Uninitialized) 0 else (RoofRuntimeMsec.state as DecimalType).longValue
val long totalMsec = oldVal + timeSinceLastUpdate // calculate total runtime
RoofRuntimeMsec.postUpdate(totalMsec) // post the full runtime
val double hours = (totalMsec/1000.0/60.0/60.0).doubleValue
RoofRuntimeHours.postUpdate(hours)
RoofEnergy.postUpdate(hours*RoofPower/1000)
}
else if(Roof.state == ON && whenStarted == null){
whenStarted = now
}
else {
}
end

1 Like

i do something similar, but different. (might not have if i saw this first though)
i’m logging details about when my water well pump is on.
rules of a similar nature determine how many seconds it was ON, at the time it goes OFF. this value, the time it was ON, gets persisted (mysql) at the time it went off, so i could determine when it went ON if i wanted, and more.
now, to calculate energy/kwhr for the last 30 days i do this

ftmp = PumpFillOnTime.sumSince(now.minusDays(30)).floatValue

so i add up all the seconds it’s been on in the last month.
i know the pump is 220VAC and uses 7.0 amps, and using 20cents/kWhr as a guess

// Volts * Amps * kW * $/kW
ftmp = ((220.0 * 7.0) / 1000.0) * 0.2 * ftmp / 3600.0
ftmp = Math::round(ftmp.floatValue*100.0)/100.0

so this gives me total kWhr used in the last month.
(about $2/month to pump the water out of my well, unless i made some mistake here)
this way i can also retain all the information about when the pump went on and for how long (very interesting under drought conditions, how long it takes to refill my tanks)

likewise, to find out the total for one month, 2 months ago, say, i would calculate the total for the last two months and subtract out the previous month to leave the total for the month two months ago…
kWhr data is generated on the fly, not persisted. don’t know if i need to persist it or not, really.

i actually measure the pump current so could use .avgSince on the actual pump amperage to come up with an exact number for “7.0” but it never really changes very much.

This is a key point in your approach. Because of how rrd4j “complresses” data it might not be a suitable solution for this sort of data analysis.

Can’t think of a reason except if you wanted to chart it to perhaps see trends related to time of day and kWh usage. Or, if your rules might run at a time when your kWh Item is undefined it might be worthwhile to persist and restoreOnStartup.

Dear bakir, which z-wave devices you used? How to install and connect them to OH ? Can you show me some tutorials?

Hi,

probably many different options now but my setup is with
Aeotec by Aeon Labs ZW090 Z Stick Gen 5
GE Z-Wave Wireless Lighting Control Outdoor Module
Follow the z-wave setup tutorial for your version of OH and operating system (on a working system add zwave binding, modify config to assign correct serial port and I found HABmin to make life easier with setting up and monitoring zwave devices)

Unfortunately your rule @bakir is not working anymore in OH2.
It throws error:

[ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Policz czas CWU': The name 'Undefined' cannot be resolved to an item or type; line 78, column 46, length 9

Even i changed to ‘Undefined’ and ‘Uninitialized’ there are still errors:

Error during the execution of rule 'Policz czas CWU': Could not cast NULL to org.eclipse.smarthome.core.library.types.DecimalType; line 78, column 110, length 35

Anyone has an idea how to fix it to have it working under OH2?

I did copy’n’paste from your post changing only names of rule and items.

Use UNDEF instead of Undefined. This is one of the changes between 1.8 and 2.0.

Thanks, but there is still error
Error during the execution of rule 'Policz czas CWU': Could not cast NULL to org.eclipse.smarthome.core.library.types.DecimalType; line 79, column 104, length 35

my rule look like this:

var org.joda.time.DateTime whenStarted = null
var Number cwuPower = 2000

rule "CWU start"
	when
		Item cwu received command ON
	then
		whenStarted = now
end

rule "CWU stop"
	when 
		Item cwu received command OFF
	then
		if(whenStarted !== null){
		}
		whenStarted == null
end

rule "Policz czas CWU"
	when
		Time cron "* */1 * * * ?"
	then
		if(cwu.state == ON && whenStarted !== null){
			val long nowMsec = now.millis
			val long wsMsec = whenStarted.millis
			whenStarted = now
			val long timeSinceLastUpdate = nowMsec - wsMsec
			val long oldVal = if(cwuRuntimeMsec.state == UNDEF || cwuRuntimeMsec.state == 'Uninitialized') 0 else (cwuRuntimeMsec.state as DecimalType).longValue
			val long totalMsec = oldVal + timeSinceLastUpdate // calculate total runtime
			cwuRuntimeMsec.postUpdate(totalMsec) // post the full runtime
			val double hours = (totalMsec/1000.0/60.0/60.0).doubleValue
			cwuRuntimeHours.postUpdate(hours)
			cwuEnergy.postUpdate(hours*cwuPower/1000)
		}
		else if(cwu.state == ON && whenStarted == null){
			whenStarted = now
		}
		else {
		}
end

error is referencing to this line:
val long oldVal = if(cwuRuntimeMsec.state == UNDEF || cwuRuntimeMsec.state == 'Uninitialized') 0 else (cwuRuntimeMsec.state as DecimalType).longValue

Test for UNDEF and for NULL on that line. The test for “Uninitialized” does nothing and should be removed.

Great, thank you so much !!! works like a charm :slight_smile:

Hello.

I try to use your code also and I have this rule:

rule "Increment Roof Runtimes"
when
Time cron "* */1 * * * ?"
then
    if(Sonoff07.state == ON && whenStarted !== null){
        val long nowMsec = now.millis
        val long wsMsec = whenStarted.millis
        whenStarted = now
        val long timeSinceLastUpdate = nowMsec - wsMsec
        val long oldVal = if(RoofRuntimeMsec.state == UNDEF) 0 else (RoofRuntimeMsec.state as DecimalType).longValue
        val long totalMsec = oldVal + timeSinceLastUpdate // calculate total runtime
        RoofRuntimeMsec.postUpdate(totalMsec) // post the full runtime
        val double hours = (totalMsec/1000.0/60.0/60.0).doubleValue
        RoofRuntimeHours.postUpdate(hours)
        RoofEnergy.postUpdate(hours*RoofPower/1000)
    }
    else if(Sonoff07.state == ON && whenStarted === null){
        whenStarted = now
    }
    else {
    }
end

but I have an error on line

val long oldVal = if(RoofRuntimeMsec.state == UNDEF) 0 else (RoofRuntimeMsec.state as DecimalType).longValue

the error is:

2019-02-28 11:32:47.027 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Increment Roof Runtimes': Could not cast NULL to org.eclipse.smarthome.core.library.types.DecimalType; line 267, column 114, length 36

Do you have any ideea of what I do wrong?

Thank’s a lot!

Change UNDEF to NULL…

Thanks a lot, everything is ok now.

But how do I keep my values in time so I can make charts by day / month / year / etc? What values should I send to influxdb?

Hello

I manage to use this code for a TV, which is plugged in a strip.
Now I want to find a solution for all 3 sockets from the strip. Can this be done with members? Or should i create 2 times more the rule?

my rule:

import java.lang.Math

var org.joda.time.DateTime whenStarted = null

rule “LGTV started”
when
Item TellurPowerStripTellurPower1 received command ON
then
whenStarted = now
end

rule “LGTV stopped”
when
Item TellurPowerStripTellurPower1 received command OFF
then
if(whenStarted != null) {
// do the same as the if(TellurPowerStripTellurPower1.state == ON && whenStarted != null) in the increment rule
}
whenStarted = null
end

rule “Increment LGTV Runtimes”
when
Time cron “* */1 * * * ?”
then
if(TellurPowerStripTellurPower1.state == ON && whenStarted != null){
logInfo(“GenUptime”, “LGTV: Getting now”)
val long nowMsec = now.millis

    logInfo("GenUptime", "LGTV: Getting whenStarted")
    val long wsMsec = whenStarted.millis

    logInfo("GenUptime", "LGTV: Resetting whenStarted")
    whenStarted = now

    logInfo("GenUptime", "LGTV: Calculating time difference") 
    val long timeSinceLastUpdate = nowMsec - wsMsec

    logInfo("GenUptime", "LGTV: Getting total Uptime in msec")
    //val long oldVal = TellurPowerStripTellurPower1RuntimeMSec.state
    val long oldVal = if(TellurPowerStripTellurPower1RuntimeMSec.state === NULL) 0 else (TellurPowerStripTellurPower1RuntimeMSec.state as Number).longValue
    
    //logInfo("GenUptime", "Adding current time to total")
    logInfo("GenUpTime", "LGTV: Adding current time to total: timeSinceLastUpdate = " + timeSinceLastUpdate + " oldVal = " + oldVal)
    val long totalMsec = oldVal + timeSinceLastUpdate // calculate total runtime
    
    logInfo("GenUptime", "LGTV: Posting totalMsec to Item")
    TellurPowerStripTellurPower1RuntimeMSec.postUpdate(totalMsec) // post the full runtime

    logInfo("GenUptime", "LGTV: Calculating hours")
    //val long hours = Math::round(totalMsec/1000/60/60)
    val double hours = totalMsec/1000.0/60.0/60.0
    logInfo("GenUptime", "LGTV: Posting hours")
    TellurPowerStripTellurPower1RuntimeHours.postUpdate(hours)

    logInfo("GenUptime", "LGTV: Calculate energy consumption")
    var float LGTVPower = 130             // Enter total power consumption [in Watts]
    val double LGTVEnergyConsumption = hours * LGTVPower / 1000                  // Energy consumed [kWh] Energy = Power * Time
    LGTVEnergy.postUpdate(LGTVEnergyConsumption)
    }
else if(TellurPowerStripTellurPower1.state == ON && whenStarted == null){
    logInfo("GenUptime", "LGTV: TV is ON but 'LGTV started' rule never executed")
    whenStarted = now
}
else {
    logInfo("GenUptime", "LGTV is not running")
}

end

items:
Switch TellurPowerStripTellurPower1 “On/off switch” (gTellur) [“Outlet”] {channel=“mqtt:topic:46425acf:Tellur_Strip:Tellur_Power1”}
Switch TellurPowerStripTellurPower2 “On/off switch” (gTellur) [“Outlet”] {channel=“mqtt:topic:46425acf:Tellur_Strip:Tellur_Power2”}
Switch TellurPowerStripTellurPower3 “On/off switch” (gTellur) [“Outlet”] {channel=“mqtt:topic:46425acf:Tellur_Strip:Tellur_Power3”}
Number LGTVEnergy “TV Socket1 Energy Consumption [%.2f kWh]” (gEnergyTellur)
Number TellurPowerStripTellurPower1RuntimeHours “LGTV Runtime Hours [%.2f Hours]”
Number TellurPowerStripTellurPower1RuntimeMSec "LGTV Runtime "