Calculating Heater Runtime

Hi all, I am trying to calculate the runtime of my greenhouse heater with the final goal of calculating the amount of propane we use. I have looked through a good many posts and the one that is closest is the generator runtime.

I set the rule up as I best I could figure out. I am close I think but throwing an error. I am also confused on what can be posted out the sitemap and what can persist as I will want to graph/calculate/monitor…

Here are my items. I have two of them commented out as I dont think I need them here?

Switch MainHeater "Main Heater" <switch> (persist,gAllHeater)  [ "Switchable"]
    { mqtt=">[broker:cmnd/sonoff_7F5C1F/POWER:command:*:default],
            <[broker:stat/sonoff_7F5C1F/POWER:state:default]" } 
Number MainHeaterRuntimeHours		"MainHeater Runtime [%.1f Hours]"	   <heater>
//Number MainHeater_On_Duration   "Heater Duration Total"
//Number MainHeater_On_Asc   "Heater Duration Ascending" 

Here is my rule:

import java.lang.Math
import org.openhab.model.script.actions.*
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

var org.joda.time.DateTime whenStarted = null
var Timer MainHeater_status_timer = null
var Timer MainHeater_load_timer = null
var Lock lock = new ReentrantLock()

rule "Main Heater Started"
when
	Item MainHeater received command ON
	
then	
	logInfo("Testing", "Main Heater Started - Locking")
	lock.lock()
	try {
		sendCommand(MainHeater, ON)
		Thread::sleep(45000) // Let MainHeater spool up
			if (MainHeater_Status.state == CLOSED) {
				sendCommand(MainHeater_Failed, OFF)
				sendCommand(MainHeater_Cooling, OFF)
				whenStarted = now // Start Clock
				MainHeater_load_timer = createTimer(now.plusSeconds(15))
					[ 
						sendMail("myemail@gmail.com", "MainHeater", "Main Heater Started, load at " + Buy_Total_Watts.state + " watts")
					]
			} else {
				sendCommand(MainHeater_Failed, ON)
				sendCommand(MainHeater_Override, OFF)
				sendMail("myemail@gmail.com", "Main Heater", "Main Heater Failed to Start!!")
			}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Main Heater Started - Unlocked")
	}
end


rule "Main Heater Stopped"
when
	Item MainHeater received command OFF
	
then	
	lock.lock()
	try {at
		sendCommand(MainHeater, OFF)
		postUpdate(MainHeater, OFF)
		postUpdate(MainHeater_Cooling, ON)
		Thread::sleep(90000) // Let Main Heater cool down
		postUpdate(MainHeater, OFF)
    		whenStarted = null
	}
	finally {
		lock.unlock()
	}
end


rule "Check Main Heater and Increment Runtimes"
when
    	Time cron "0 0/1 * * * ?"
then	
	logInfo("Testing", "Check Main Heater and Increment Runtimes - Locking")
	lock.lock()
	try {
		// Check if MainHeater is running already
		if (MainHeater_Status.state == CLOSED && whenStarted == null) whenStarted = now 
		if (MainHeater_Status.state == CLOSED && MainHeater_Auto.state == OFF && MainHeater_Override.state == OFF && MainHeater_Cooling.state == OFF) {
			postUpdate(MainHeater_Override, ON)
			whenStarted = now
			sendMail("mymail@gmail.com", "Main Heater", "Main Heater Started Manually")
		}

		// Check if MainHeater failed
		if (MainHeater_Status.state == OPEN && MainHeater_Auto.state == ON) {
			sendCommand(MainHeater_Auto, OFF)
			sendMail("mymail@gmail.com", "Main Heater", "Main Heater Failed!!")
		}
	
		// Increment Runtimes
		if(MainHeater_Status.state == CLOSED) {
			val long nowMSec = now.millis
			val long wsMSec = whenStarted.millis
			whenStarted = now	
        		val long timeSinceLastUpdate = nowMSec - wsMSec
			val long oldVal = (MainHeater_Runtime_MSec.state as Number).longValue
			val long totalMSec = oldVal + timeSinceLastUpdate // calculate total runtime
        		MainHeater_Runtime_MSec.postUpdate(totalMSec) // post the full runtime
			val double hours = totalMSec/1000.0/60.0/60.0
        		MainHeater_Runtime_Hours.postUpdate(hours)

			// Caculate runtime for today		
			val long todayOldVal = if(Today_MainHeater_Runtime_MSec.state == Uninitialized) 0 else (Today_MainHeater_Runtime_MSec.state as Number).longValue
			val long todayTotalMSec = todayOldVal + timeSinceLastUpdate // calculate total runtime
			Today_MainHeater_Runtime_MSec.postUpdate(todayTotalMSec) // post the full runtime
			val double todayHours = todayTotalMSec/1000.0/60.0/60.0
        		Today_MainHeater_Runtime_Hours.postUpdate(todayHours)
		}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Check Main Heater and Increment Runtimes - Unlocked")
	}
end


rule "Today Main Heater Runtime Reset"
when
	Time cron "50 59 23 * * ?"
then
	Today_MainHeater_Runtime_MSec.postUpdate(0)
end

Here is what I put in the sitemap:

Text item=MainHeaterRuntimeHours

Here is my error:

[ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Check Main Heater and Increment Runtimes': The name 'MainHeater_Status' cannot be resolved to an item or type; line 70, column 7, length 17

Hope thats enough info.

thanks!

First make sure there is an contact item called MainHeater_Status
or could it be that

if (MainHeater_Status.state == CLOSED && whenStarted == null) whenStarted = now 

does not have {}

if (MainHeater_Status.state == CLOSED && whenStarted == null) {whenStarted = now}

In the first rule MainHeater_Status is not valid as seen in the error log. The items file has MainHeater as a switch and OH will see this as ON or OFF unless transformed using something like MAP.

The quick fix is change the rule to match the item, use MainHeater.state == OFF if off is equal to closed for your application, else use ON.

thanks! i was wondering about that when I went through the code. to your point I don’t use “closed”. I made the changes throughout the rule to change it from “closed” to “OFF” I also added the brackets. updated code below and still getting the same error.

import java.lang.Math
import org.openhab.model.script.actions.*
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

var org.joda.time.DateTime whenStarted = null
var Timer MainHeater_status_timer = null
var Timer MainHeater_load_timer = null
var Lock lock = new ReentrantLock()

rule "Main Heater Started"
when
	Item MainHeater received command ON
	
then	
	logInfo("Testing", "Main Heater Started - Locking")
	lock.lock()
	try {
		sendCommand(MainHeater, ON)
		Thread::sleep(45000) // Let MainHeater spool up
			if (MainHeater_Status.state == OFF) {
				sendCommand(MainHeater_Failed, OFF)
				sendCommand(MainHeater_Cooling, OFF)
				whenStarted = now // Start Clock
				MainHeater_load_timer = createTimer(now.plusSeconds(15))
					[ 
						sendMail("mymail@gmail.com", "MainHeater", "Main Heater Started, load at " + Buy_Total_Watts.state + " watts")
					]
			} else {
				sendCommand(MainHeater_Failed, ON)
				sendCommand(MainHeater_Override, OFF)
				sendMail("mymail@gmail.com", "Main Heater", "Main Heater Failed to Start!!")
			}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Main Heater Started - Unlocked")
	}
end


rule "Main Heater Stopped"
when
	Item MainHeater received command OFF
	
then	
	lock.lock()
	try {at
		sendCommand(MainHeater, OFF)
		postUpdate(MainHeater, OFF)
		postUpdate(MainHeater_Cooling, ON)
		Thread::sleep(90000) // Let Main Heater cool down
		postUpdate(MainHeater, OFF)
    		whenStarted = null
	}
	finally {
		lock.unlock()
	}
end


rule "Check Main Heater and Increment Runtimes"
when
    	Time cron "0 0/1 * * * ?"
then	
	logInfo("Testing", "Check Main Heater and Increment Runtimes - Locking")
	lock.lock()
	try {
		// Check if MainHeater is running already
		if (MainHeater_Status.state == OFF && whenStarted == null) {whenStarted = now} 
		if (MainHeater_Status.state == OFF && MainHeater_Auto.state == OFF && MainHeater_Override.state == OFF && MainHeater_Cooling.state == OFF) {
			postUpdate(MainHeater_Override, ON)
			whenStarted = now
			sendMail("mymail@gmail.com", "Main Heater", "Main Heater Started Manually")
		}

		// Check if MainHeater failed
		if (MainHeater_Status.state == ON && MainHeater_Auto.state == ON) {
			sendCommand(MainHeater_Auto, OFF)
			sendMail("mymail@gmail.com", "Main Heater", "Main Heater Failed!!")
		}
	
		// Increment Runtimes
		if(MainHeater_Status.state == OFF) {
			val long nowMSec = now.millis
			val long wsMSec = whenStarted.millis
			whenStarted = now	
        		val long timeSinceLastUpdate = nowMSec - wsMSec
			val long oldVal = (MainHeater_Runtime_MSec.state as Number).longValue
			val long totalMSec = oldVal + timeSinceLastUpdate // calculate total runtime
        		MainHeater_Runtime_MSec.postUpdate(totalMSec) // post the full runtime
			val double hours = totalMSec/1000.0/60.0/60.0
        		MainHeater_Runtime_Hours.postUpdate(hours)

			// Caculate runtime for today		
			val long todayOldVal = if(Today_MainHeater_Runtime_MSec.state == Uninitialized) 0 else (Today_MainHeater_Runtime_MSec.state as Number).longValue
			val long todayTotalMSec = todayOldVal + timeSinceLastUpdate // calculate total runtime
			Today_MainHeater_Runtime_MSec.postUpdate(todayTotalMSec) // post the full runtime
			val double todayHours = todayTotalMSec/1000.0/60.0/60.0
        		Today_MainHeater_Runtime_Hours.postUpdate(todayHours)
		}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Check Main Heater and Increment Runtimes - Unlocked")
	}
end


rule "Today Main Heater Runtime Reset"
when
	Time cron "50 59 23 * * ?"
then
	Today_MainHeater_Runtime_MSec.postUpdate(0)
end

error:

[ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Check Main Heater and Increment Runtimes': The name 'MainHeater_Status' cannot be resolved to an item or type; line 70, column 7, length 17

Looking at you items file this is not an item thus the error. If you change the statement to remove the _Status and use MainHeater.state does the error go away?

was wondering about that as well. there are several places in there where I dont have the same associated item but never could figure out what was a virtual item vs a real item or however that should be said. in any case i made a good many changes in there to take out thoe MainHeater_auto and MainHeater_Failed and MainHeater_Cooling. Now i get all kinds of things going on.

code:

import java.lang.Math
import org.openhab.model.script.actions.*
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

var org.joda.time.DateTime whenStarted = null
var Timer MainHeater_timer = null
var Timer MainHeater_load_timer = null
var Lock lock = new ReentrantLock()

rule "Main Heater Started"
when
	Item MainHeater received command ON
	
then	
	logInfo("Testing", "Main Heater Started - Locking")
	lock.lock()
	try {
		sendCommand(MainHeater, ON)
		Thread::sleep(45000) // Let MainHeater spool up
			if (MainHeater.state == OFF) {
				sendCommand(MainHeater_Failed, OFF)
				sendCommand(MainHeater_Cooling, OFF)
				whenStarted = now // Start Clock
				MainHeater_load_timer = createTimer(now.plusSeconds(15))
					[ 
						sendMail("myemail@gmail.com", "MainHeater", "Main Heater Started, load at " + Buy_Total_Watts.state + " watts")
					]
			} else {
				sendCommand(MainHeater_Failed, ON)
				sendCommand(MainHeater_Override, OFF)
				sendMail("myemail@gmail.com", "Main Heater", "Main Heater Failed to Start!!")
			}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Main Heater Started - Unlocked")
	}
end


rule "Main Heater Stopped"
when
	Item MainHeater received command OFF
	
then	
	lock.lock()
	try {at
		sendCommand(MainHeater, OFF)
		postUpdate(MainHeater, OFF)
		Thread::sleep(90000) // Let Main Heater cool down
		postUpdate(MainHeater, OFF)
    		whenStarted = null
	}
	finally {
		lock.unlock()
	}
end


rule "Check Main Heater and Increment Runtimes"
when
    	Time cron "0 0/1 * * * ?"
then	
	logInfo("Testing", "Check Main Heater and Increment Runtimes - Locking")
	lock.lock()
	try {
		// Check if MainHeater is running already
		if (MainHeater.state == OFF && whenStarted == null) {whenStarted = now} 
		if (MainHeater.state == OFF) {
			postUpdate(MainHeater, ON)
			whenStarted = now
			sendMail("myemail@gmail.com", "Main Heater", "Main Heater Started Manually")
		}

		// Check if MainHeater failed
		if (MainHeater.state == ON) {
			sendCommand(MainHeater, OFF)
			sendMail("myemail@gmail.com", "Main Heater", "Main Heater Failed!!")
		}
	
		// Increment Runtimes
		if(MainHeater.state == OFF) {
			val long nowMSec = now.millis
			val long wsMSec = whenStarted.millis
			whenStarted = now	
        		val long timeSinceLastUpdate = nowMSec - wsMSec
			val long oldVal = (MainHeater_Runtime_MSec.state as Number).longValue
			val long totalMSec = oldVal + timeSinceLastUpdate // calculate total runtime
        		MainHeater_Runtime_MSec.postUpdate(totalMSec) // post the full runtime
			val double hours = totalMSec/1000.0/60.0/60.0
        		MainHeater_Runtime_Hours.postUpdate(hours)

			// Caculate runtime for today		
			val long todayOldVal = if(Today_MainHeater_Runtime_MSec.state == Uninitialized) 0 else (Today_MainHeater_Runtime_MSec.state as Number).longValue
			val long todayTotalMSec = todayOldVal + timeSinceLastUpdate // calculate total runtime
			Today_MainHeater_Runtime_MSec.postUpdate(todayTotalMSec) // post the full runtime
			val double todayHours = todayTotalMSec/1000.0/60.0/60.0
        		Today_MainHeater_Runtime_Hours.postUpdate(todayHours)
		}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Check Main Heater and Increment Runtimes - Unlocked")
	}
end


rule "Today Main Heater Runtime Reset"
when
	Time cron "50 59 23 * * ?"
then
	Today_MainHeater_Runtime_MSec.postUpdate(0)
end

errors:

018-11-13 13:05:00.024 [INFO ] [lipse.smarthome.model.script.Testing] - Check Main Heater and Increment Runtimes - Locking

==> /var/log/openhab2/events.log <==

2018-11-13 13:05:00.069 [vent.ItemStateChangedEvent] - MainHeater changed from OFF to ON

2018-11-13 13:05:01.617 [ome.event.ItemCommandEvent] - Item 'MainHeater' received command OFF

2018-11-13 13:05:01.637 [vent.ItemStateChangedEvent] - MainHeater changed from ON to OFF

==> /var/log/openhab2/openhab.log <==

2018-11-13 13:05:03.029 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Check Main Heater and Increment Runtimes': The name 'MainHeater_Runtime_MSec' cannot be resolved to an item or type; line 88, column 23, length 23

getting constant emails so going to comment that out.

You can remove them or create a proxy item (a.k.a virtual item) but the rule will error if the item doesn’t exist. A virtual item is something you create to hold a value so it can be used in a rule. For your rule you may need these items so add them as needed.

This item, like the others has not been created, if removing the error should go away.

I feel that I need MainHeater_Runtime_MSec as an Item but not sure how to structure it.

You can try a Number or DateTime item like:

Number MainHeater_Runtime_MSec "Heater run time [%d]"
or
DateTime MainHeater_Runtime_MSec "Heater run time [%1$tm/%1$td %1$tH:%1$tM]"

I tried adding this to my items. not sure its valid but now this is what i get in the logs:

Number MainHeater_Runtime_MSec      "MainHeater Runtime [%.1f MSec]" 	   <heater>

log:

2018-11-13 13:29:00.018 [INFO ] [lipse.smarthome.model.script.Testing] - Check Main Heater and Increment Runtimes - Locking

2018-11-13 13:29:00.048 [INFO ] [lipse.smarthome.model.script.Testing] - Check Main Heater and Increment Runtimes - Unlocked

no errors. maybe its working?

No errors is good,:grinning: are you getting the expected output?

Dont know yet. The heater has not run yet. i will stick around and make sure that my current rules work correctly. dont want it to mess up over night. hope so!

how do i get the runtime hours to persist correctly? i want to be able to store them in my influxdb.

thanks!

I don’t use influxdb so check the doc’s for how to setup the persist file. If it’s similar to rrd4j then the persist file will look like this:

Strategies {
	// for rrd chart cron strategy every minute
	everyMinute : "0 * * * * ?"
	// get data reduced for older values to keep database small
	everyHour : "0 0 * * * ?"
	everyDay : "0 0 0 * * ?"

	default = everyChange
}

Items {
            Your_Items : strategy = everyMinute, restoreOnStartup
}

Be sure to check the doc’s b/c I think there are a few differences in the persist file.

That jogged my memory. I added this in. Once I get some values in there (hopefully i get some values) then l will see how it persists. I think I want to do it every change as I think it will add up over the day and just posts to that item once a day. but we will see…

MainHeaterRuntimeHours: strategy = everyChange, restoreOnStartup

Drats, now I am getting this error:

[ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Check Main Heater and Increment Runtimes': Could not cast NULL to java.lang.Number; line 88, column 23, length 39

latest code:

import java.lang.Math
import org.openhab.model.script.actions.*
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

var org.joda.time.DateTime whenStarted = null
var Timer MainHeater_timer = null
var Timer MainHeater_load_timer = null
var Lock lock = new ReentrantLock()

rule "Main Heater Started"
when
	Item MainHeater received command ON
	
then	
	logInfo("Testing", "Main Heater Started - Locking")
	lock.lock()
	try {
		sendCommand(MainHeater, ON)
		Thread::sleep(45000) // Let MainHeater spool up
			if (MainHeater.state == OFF) {
				sendCommand(MainHeater_Failed, OFF)
				sendCommand(MainHeater_Cooling, OFF)
				whenStarted = now // Start Clock
				MainHeater_load_timer = createTimer(now.plusSeconds(15))
					[ 
						//sendMail("myemail@gmail.com", "MainHeater", "Main Heater Started, load at " + Buy_Total_Watts.state + " watts")
					]
			} else {
				sendCommand(MainHeater_Failed, ON)
				sendCommand(MainHeater_Override, OFF)
				//sendMail("myemail@gmail.com", "Main Heater", "Main Heater Failed to Start!!")
			}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Main Heater Started - Unlocked")
	}
end


rule "Main Heater Stopped"
when
	Item MainHeater received command OFF
	
then	
	lock.lock()
	try {at
		sendCommand(MainHeater, OFF)
		postUpdate(MainHeater, OFF)
		Thread::sleep(90000) // Let Main Heater cool down
		postUpdate(MainHeater, OFF)
    		whenStarted = null
	}
	finally {
		lock.unlock()
	}
end


rule "Check Main Heater and Increment Runtimes"
when
    	Time cron "0 0/1 * * * ?"
then	
	logInfo("Testing", "Check Main Heater and Increment Runtimes - Locking")
	lock.lock()
	try {
		// Check if MainHeater is running already
		if (MainHeater.state == OFF && whenStarted == null) {whenStarted = now} 
		if (MainHeater.state == OFF) {
			postUpdate(MainHeater, ON)
			whenStarted = now
			//sendMail("myemail@gmail.com", "Main Heater", "Main Heater Started Manually")
		}

		// Check if MainHeater failed
		if (MainHeater.state == ON) {
			sendCommand(MainHeater, OFF)
			//sendMail("myemail@gmail.com", "Main Heater", "Main Heater Failed!!")
		}
	
		// Increment Runtimes
		if(MainHeater.state == OFF) {
			val long nowMSec = now.millis
			val long wsMSec = whenStarted.millis
			whenStarted = now	
        		val long timeSinceLastUpdate = nowMSec - wsMSec
			val long oldVal = (MainHeater_Runtime_MSec.state as Number).longValue
			val long totalMSec = oldVal + timeSinceLastUpdate // calculate total runtime
        		MainHeater_Runtime_MSec.postUpdate(totalMSec) // post the full runtime
			val double hours = totalMSec/1000.0/60.0/60.0
        		MainHeater_Runtime_Hours.postUpdate(hours)

			// Caculate runtime for today		
			val long todayOldVal = if(Today_MainHeater_Runtime_MSec.state == Uninitialized) 0 else (Today_MainHeater_Runtime_MSec.state as Number).longValue
			val long todayTotalMSec = todayOldVal + timeSinceLastUpdate // calculate total runtime
			Today_MainHeater_Runtime_MSec.postUpdate(todayTotalMSec) // post the full runtime
			val double todayHours = todayTotalMSec/1000.0/60.0/60.0
        		Today_MainHeater_Runtime_Hours.postUpdate(todayHours)
		}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Check Main Heater and Increment Runtimes - Unlocked")
	}
end


rule "Today Main Heater Runtime Reset"
when
	Time cron "50 59 23 * * ?"
then
	Today_MainHeater_Runtime_MSec.postUpdate(0)
end
val long oldVal = (MainHeater_Runtime_MSec.state as Number).longValue

I’ve never used longValue in a rule and not sure if this is line 88 or not. Double check the doc’s for proper use and syntax. If everything is correct, perhaps @rlkoshak or @vzorglub could provide some assistance, their some of the best with rules.

I think I have more than that going on the rule. I had to head down there last night and take it out. It was preventing the heater from turning on. That would kinda not be good. The rule seems to be checking the heater and turning it off. I will have to walk back through it. Agreed on @rlkoshak and @vzorglub!

I will test out that line and see what it does. Will have to check on other issues in there too. Once I get this figured out I will document it so others can follow. The generator post was great but was missing some key items like how to actually structure the items that are listed in the rules.

@boilermanc
I have had a quick look at your rule and I am working on it.
I have to say that i t is a bit of a mess with locks and 45s thread sleep… Begging for failure

A bit busy right now but I should come up with something soon

1 Like

thanks for much vincent. huge help!

That’s what the little heart icon is for.:wink: