Generator hours counter

Sorry; it must be a case where Xtend and the openHAB rule language differ. Rich’s approach should work.

It does!!! Odd sometimes how openhab does not throw errors.

Does this mean the rule overall works now?

Sure does! I am trying to get it to give me fractions of hours. I know that round() only rounds to whole numbers so I tried:

    val long hours = (Math::round(totalMSec/100/60/60)) /10

Thinking that /100 rather then /1000 would give me a larger number but when I / 10 I still get a whole number.

Dealing with decimal points on computers is surprisingly challenging. Most people don’t realize that computers are really horrible at doing decimal math.

This little trick will work most of the time:

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

Because computers are really bad at representing floating point numbers, there may be cases where you end up with something like “5.1000000000000005” instead of “5.1”. I don’t think it will occur frequently, but if it does the “fix” will be a little more tricky and involved.

However, it is better to not round at all and use the formatting on the labeltext for the Item to only show one decimal place.

Item GeneratorRuntimeHours "Generator Runtime Hours [%.f1]" ...

In the rule change the type of hours to a double and don’t round at all.

val double hours = totalMSec/100/60/60

If you want two decimal places just change the stuff in brackets to [%f.2].

I tried :

    val double hours = totalMSec/1000/60/60

Tho I still got whole numbers, I am using the correct display:

Number		GeneratorRuntimeHours		"Generator Runtime [%.1f Hours]"	   <generator>

Its an old trick that I didn’t think was still necessary. Try:

val double hours = totalMSec/1000.0/60.0/60.0

Perfect!

Awesome thread guys. Bookmarked for the day I have an automatic start/stop generator!

I have done remote site generator monitoring and something that does tend to come up (in many areas in fact) is drift between the generators hour meter and the calculated values from the controller. If you are storing/persisting the total run hours, and you have a means of updating that counter, you can periodically do a manual meter read and update the system with the “correct” value from the generator.

Another though, which is much simpler, but requires more storage, is just to store the current generator state every minute (or 5,10,15 minutes) as well as the run hours. This way it is really easy to just sum the run time between a start and end point in order to see how long the generator ran during that period.

There is always more than one way to solve a problem. I’m glad you posted this alternative method.

I think the big differences between the two approaches is when you do the math to come up with the total hours. Our approach does the math as values come up. Your approach does the math only when you look. Both have their place.

Rich

I just do that in the .rules:

val double hours = totalMSec/1000.0/60.0/60.0 + 100.3

Couldn’t agree more… I am tracking my energy use, and using both approaches. Must say that having the meter indexes at the begining of the month and throughout has bailed me out a number of times when there has been lost data… as you can still tell how much was used during the outage.

Hi @sipvoip, could you post the final code for the counter?

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 generator_status_timer = null
var Timer generator_load_timer = null
var Lock lock = new ReentrantLock()

rule "Generator Started"
when
	Item Generator_Auto received command ON or
	Item Generator_Override received command ON
then	
	logInfo("Testing", "Generator Started - Locking")
	lock.lock()
	try {
		sendCommand(Generator_Start, ON)
		Thread::sleep(45000) // Let Generator spool up
			if (Generator_Status.state == CLOSED) {
				sendCommand(Generator_Failed, OFF)
				sendCommand(Generator_Cooling, OFF)
				whenStarted = now // Start Clock
				generator_load_timer = createTimer(now.plusSeconds(15))
					[ 
						sendMail("8323303810@mms.att.net", "Generator", "Generator Started, load at " + Buy_Total_Watts.state + " watts")
					]
			} else {
				sendCommand(Generator_Failed, ON)
				sendCommand(Generator_Override, OFF)
				sendMail("8323303810@mms.att.net", "Generator", "Generator Failed to Start!!")
			}
	}
	finally {
		lock.unlock()
		logInfo("Testing", "Generator Started - Unlocked")
	}
end


rule "Generator Stopped"
when
	Item Generator_Auto received command OFF or
	Item Generator_Override received command OFF
then	
	lock.lock()
	try {
		sendCommand(Generator_Start, OFF)
		postUpdate(Generator_Auto, OFF)
		postUpdate(Generator_Override, OFF)
		postUpdate(Generator_Cooling, ON)
		Thread::sleep(90000) // Let Generator cool down
		postUpdate(Generator_Cooling, OFF)
    		whenStarted = null
	}
	finally {
		lock.unlock()
	}
end


rule "Check Generator and Increment Runtimes"
when
    	Time cron "0 0/1 * * * ?"
then	
	logInfo("Testing", "Check Generator and Increment Runtimes - Locking")
	lock.lock()
	try {
		// Check if Generator is running already
		if (Generator_Status.state == CLOSED && whenStarted == null) whenStarted = now 
		if (Generator_Status.state == CLOSED && Generator_Auto.state == OFF && Generator_Override.state == OFF && Generator_Cooling.state == OFF) {
			postUpdate(Generator_Override, ON)
			whenStarted = now
			sendMail("8323303810@mms.att.net", "Generator", "Generator Started Manually")
		}

		// Check if Generator failed
		if (Generator_Status.state == OPEN && Generator_Auto.state == ON) {
			sendCommand(Generator_Auto, OFF)
			sendMail("8323303810@mms.att.net", "Generator", "Generator Failed!!")
		}
	
		// Increment Runtimes
		if(Generator_Status.state == CLOSED) {
			val long nowMSec = now.millis
			val long wsMSec = whenStarted.millis
			whenStarted = now	
        		val long timeSinceLastUpdate = nowMSec - wsMSec
			val long oldVal = (Generator_Runtime_MSec.state as Number).longValue
			val long totalMSec = oldVal + timeSinceLastUpdate // calculate total runtime
        		Generator_Runtime_MSec.postUpdate(totalMSec) // post the full runtime
			val double hours = totalMSec/1000.0/60.0/60.0
        		Generator_Runtime_Hours.postUpdate(hours)

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


rule "Today Generator Runtime Reset"
when
	Time cron "50 59 23 * * ?"
then
	Today_Generator_Runtime_MSec.postUpdate(0)
end
4 Likes

Very nice thread ! What is your hardware? I mean what kind is your generator used? How to connect it to OpenHab? @sipvoip

Generac 20kw and set it up for 2 wire start and then control it via a relay off a pokeys 57 board.

how can you connect them to openhab? what was the setup and configuration?

I’m looking at porting your rule to control a 30HP industrial compressor
of all these variable I’d like help understanding what one are connected to physical switches and sensors and what are virtual switches and counters as well as all the items that need to be in the items file

generator_status_timer				//Declared but never used
generator_load_timer					Rule_Number				//Delay timer for load reading
Buy_Total_Watts.state					Item_Number_Physical	//Power reading from other device
Generator_Auto			"ON/OFF"
Generator_Override		"ON/OFF"
Generator_Start			"ON/OFF"
Generator_Status		"OPENED/CLOSED"	Item_Contact_Physical
Generator_Failed		"ON/OFF"		Item_Switch_Virtual		//genset Failed Indicator
Generator_Cooling		"ON/OFF"		Item_Switch_Virtual		//Delayed mode indicator to allow genset to cool before restart
whenStarted				"null/TIME"
Generator_Runtime_MSec					Item_Number_Virtual		// used to store intermediate values which get rounded
Generator_Runtime_Hours	"%.1f Hours"	Item_Number_Virtual		// used on sitemap
Today_Generator_Runtime_MSec
todayTotalMSec
todayOldVal
timeSinceLastUpdate
todayHours
Today_Generator_Runtime_Hours

Thanks,

This is very similar to what I’d like to achieve.

I need to track daily heating hours for three rooms.

It gets complicated because of difficulties using arrays in rules.

Is there any updated script as this one seems to use old openhab…?

This declaration doesn’t work for me:
var org.joda.time.DateTime whenStarted = null

Thanks!

There are no difficulties using arrays in Blockly, JS Scripting, Nashorn JS, Groovy, or jRuby. It’s only Rules DSL that makes it so you have to use a List instead of an array.

Given you’ve reopened a thread that is over seven years old, yes it is in fact using old openHAB.

I recommend opening a new thread but first give a try at implementing this using Blockly. Given all the new features added to OH in the past seven years, I believe the overall approach can be much simpler. 1. Trigger when the heater changes
2. If it changed to ON, capture the current time and put it into the cache.
3. If it changed to OFF, capture the difference between the time captured in 2 and now and add that to your total runtime Item

If you want to have a constant count up while the heater is running:

  1. Trigger the rule every second (or what ever time period you care about, once per second is as fast as it gets though)
  2. Set a condition to no run the actions if the heating is OFF
  3. Increment the HeatingOnTime Item by the cron period.

The second approach is like two lines of code in the UI.

1 Like