Generator hours counter

That doesn’t make much sense…

Lets see what the values are.

Change the logging statement that states “Adding current time to total” to

logInfo("GenUpTime", Adding current time to total: timeSinceLastUpdate = " + timeSinceLastUpdate + " oldVal = " + oldVal)

Also, lets see if we can give it some help with getting the long from the state.

val long oldVal = if(GeneratorRutimeMSec.state == UNDEFINED) 0 else (GeneratorRuntimeMSec.state as Number).longValue

Hmm, that blew things all up. First I got an error on UNDEFINED, so I changed it to Undefined, but still get:

2015-10-20 15:02:32.225 [ERROR] [.o.m.r.i.engine.ExecuteRuleJob] - Error during the execution of rule Increment Generator Runtimes
org.eclipse.emf.common.util.WrappedException: org.eclipse.xtext.util.PolymorphicDispatcher$NoSuchMethodException: Couldn't find method ''_assignValue'' for objects [JvmVoid:  (eProxyURI: generator.rules#xtextLink_::0.2.2.2.0.1::0::/1), <null> timeSinceLastUpdate <XStringLiteralImpl>,  + timeSinceLastUpdate + , org.eclipse.xtext.xbase.interpreter.impl.DefaultEvaluationContext@6eb05488, org.eclipse.xtext.util.CancelIndicator$1@623f551d]

Unfortunately I’m at work and outbound ssh is verboten.

Yes, only the first letter should have been capitalized.

If I could change one thing in openHAB it would be to have errors that are more meaningful and point you at exactly the line that failed.

Gah! I’m missing the opening quote on the logInfo. Oh wherefore art thou Designer. If I had ssh I could get to my machine and at least catch these stupid typos.

logInfo("GenUpTime", "Adding current time to total: timeSinceLastUpdate = " + timeSinceLastUpdate + " oldVal = " + oldVal)
2015-10-20 15:37:44.001 [INFO ] [openhab.model.script.GenUptime] - Generator is not running
2015-10-20 15:37:44.344 [INFO ] [runtime.busevents             ] - Generator received command ON
2015-10-20 15:37:45.006 [INFO ] [openhab.model.script.GenUptime] - Getting now
2015-10-20 15:37:45.019 [INFO ] [openhab.model.script.GenUptime] - Getting whenStarted
2015-10-20 15:37:45.031 [INFO ] [openhab.model.script.GenUptime] - Resetting whenStarted
2015-10-20 15:37:45.051 [INFO ] [openhab.model.script.GenUptime] - Calculating time difference
2015-10-20 15:37:45.070 [INFO ] [openhab.model.script.GenUptime] - Getting total Uptime in msec
2015-10-20 15:37:45.089 [ERROR] [.o.m.r.i.engine.ExecuteRuleJob] - Error during the execution of rule Increment Generator Runtimes
java.lang.ClassCastException: Cannot cast org.openhab.core.types.UnDefType to java.lang.Number

Two steps forward, one step back. Sigh… I knew this was going to be a pain but didn’t expect it to be this hard.

The if clause doesn’t seem to be working right. Googling…

Ah, there are two things to check when it may not be initialized. Try this.

val long oldVal = if(GeneratorRutimeMSec.state == Undefined || GeneratorRuntimeMSec.state == Uninitialized) 0 else (GeneratorRuntimeMSec.state as Number).longValue
2015-10-20 17:45:44.204 [ERROR] [.o.m.r.i.engine.ExecuteRuleJob] - Error during the execution of rule Increment Generator Runtimes
java.lang.RuntimeException: The name 'java' cannot be resolved to an item or type.

And this is the full script:

var org.joda.time.DateTime whenStarted = null

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

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

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

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

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

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

        logInfo("GenUptime", "Getting total Uptime in msec")
        val long oldVal = if(GeneratorRuntimeMSec.state == Undefined || GeneratorRuntimeMSec.state == Uninitialized) 0 else (GeneratorRuntimeMSec.state as Number).longValue

        logInfo("GenUpTime", "Adding current time to total: timeSinceLastUpdate = " + timeSinceLastUpdate + " oldVal = " + oldVal)
        val long totalMsec = oldVal + timeSinceLastUpdate // calculate total runtime<F2>

        logInfo("GenUptime", "Posting totalMsec to Item")
        GeneratorRuntimeMSec.postUpdate(totalMsec) // post the full runtime

        logInfo("GenUptime", "Calculating hours")
        val long hours = java.lang.Math.round(totalMsec/1000/60/60)

        logInfo("GenUptime", "Posting hours")
        GeneratorRuntimeHours.postUpdate(hours)
    }
    else if(Generator.state == ON && whenStarted == null){
        logInfo("GenUptime", "Generator is ON but Generator started rule never executed")
        whenStarted = now
    }
    else {
        logInfo("GenUptime", "Generator is not running")
    }
end

Does this work?

import static java.lang.Math.round
...
round(totalMsec/1000/60/60)

Crap, right, static methods need to be referenced using ::

So it should probably be

import java.lang.Math

...

val long hours = Math::round(totalMsec/1000/60/60)

I simply add that at the top of the .rules right? As soon as I do that it looks like the rule stops running. I no longer see the:

2015-10-21 10:09:04.339 [INFO ] [openhab.model.script.GenUptime] - Generator is not running
2015-10-21 10:09:05.000 [INFO ] [openhab.model.script.GenUptime] - Generator is not running

If I edit the rule and take out the import line I start seeing the not running lines in logs.

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