Cannot identify null value in rule

In one of the code above I saw Duration.between(now, previousState.timestmap) (sic!)
It should be Duration.between(previousState.timestamp, now)

Basically, with a “pseudocode” Duration.between(now, 1-hour-ago) you’ll get a duration of NEGATIVE 1 hour, and with Duration.between(1-hour-ago, now) you’ll get a POSITIVE 1 hour. Just something to be aware of, as it has tripped me up too

Apparently ?: is an elvis operator but I’m not sure if it works in the xtend version that openhab is using. I haven’t used rulesdsl in years.

got it, thanks! i added that to that code but it still throws an error:

cannot invoke method public abstract java.time.ZonedDateTime org.openhab.core.persistence.HistoricItem.getTimestamp() on null

latest code:

rule 'Heater runtime'
when
    Item main_heater_power changed to ON
then
	val previousState = main_heater_power.previousState(true) 
	logInfo("heater runtime", "Previous state result is " + previousState);
	logInfo("heater runtime", "Previous state timestamp is " + previousState.timestamp)
	val newDuration = Duration.between(previousState.timestamp, now)
	logInfo("heater runtime", "Duration is " + newDuration);
	
end

What do you use now @rlkoshak ?

and very Groovy… sorry…

1 Like

What was the log output of these lines?

He uses JS Scripting
I prefer JRuby scripting

Both are actively maintained at the moment.

This page gives you a good syntax comparison / overview of all the options, including the no-longer-maintained Jython scripting

oh my, I have been away for awhile so I guess things have changed. Guess I gotta find a new method.

Rulesdsl is still supported for the foreseeable future I believe. So nothing wrong with sticking to it.

Jruby is just soooo much nicer to use.

Sorry, didnt answer this. The script actually chokes.

cannot invoke method public abstract java.time.ZonedDateTime org.openhab.core.persistence.HistoricItem.getTimestamp() on null

OK I’ve tested that exact rule and I can see the same error message.

  • Firstly, you’ll probably see this in your log too: Previous state result is null
  • This means the call main_heater_power.previousState(true) returned null, which explains why you cannot call previousState.timestamp

Please check:

  • What is your DEFAULT persistence service? Is it really influxdb?
  • Is the item actually persisted to influxdb, on every change? If not, then the data might indeed be null / non existent from the persistence service

Try this version which specified “influxdb” specifically on the persistence call, and also converts the ZonedDateTime to string otherwise rulesdsl will complain

rule 'Heater runtime'
when
    Item main_heater_power changed to ON
then
	val previousState = main_heater_power.previousState(true, "influxdb") 
	logInfo("heater runtime", "Previous state result is {}", previousState);
	logInfo("heater runtime", "Previous state timestamp is {}", previousState.timestamp.toString)
	val newDuration = Duration.between(previousState.timestamp, now)
	logInfo("heater runtime", "Duration is {}",  newDuration.toString);
end

If you can’t rely on persistence, then you could keep track of the durations manually.

I find the code a bit strange though. Why trigger when it turned ON, and then find the time it turned OFF (from persistence)? Wouldn’t that calculate the OFF duration? I thought you wanted to know the ON duration?

Yeah, this is what I get when type “influx” in command line:

Connected to http://localhost:8086 version 1.8.10

i’ve used influx for years with openhab.
In the openhab console I have influxDB persistence layer checked.

I do not have previousState as an item or persisted. thought that was a val and did not need an item. May be confusing with var? If so, dang, I will add it as a number? Maybe thats the whole problem. Then there is newDuration as well…

And yes, I want the ON time so may have that backwards.

You need to persist the item main_heater_power on every change in your influxdb.persist configuration file.

previousState is the name of the persistence extensions’ method/function, not an item name.

Can you query the values for main_heater_power in influxdb directly? I am even more unfamiliar with influxdb although I do use it, I just stumble in the dark when it comes to influxdb management.

oh my gosh, I was not persisting that. I have it fixed now and tried to run it again. still same error though.

Is the data available yet? Try updating main_heater_power’s state to ON then OFF then ON then it should work.

ok, good, making progress. that error is gone, now this…

Cannot format given Object as a Date

Use my version above. You need to call toString on the zoneddatetime objects

so it looks like it ran ok…

it didnt throw an error but it did have this info:

Previous state result is [FAILED toString()]

it gave this as duration:

Duration is PT6M1.229392S

thats a little weird but making progress!

ok, so i changed it from ON to OFF to see what difference that made. now it shows this:

Duration is PT4.808149S

so its counting. so it displayed PT1, PT2, PT3, etc.

So I have to strip out the PT and then be able to do the math to make millis to min/hours, i’m guessing. That number i get is millis.

The Duration is an awesome class, please don’t do Duration → String → Parse the string to get the value… that’s horrible, not to mention error prone. The string representation of the Duration can include H, M, etc.

See Duration (Java SE 17 & JDK 17)

At the very least use .toMillis but even so, why work with millis when you can store it in Number:Time quantity.

However, I understand working in rulesDSL is cumbersome.

OK, that one is easy. Number is an object. ofMillis() requires a primitive long.

Duration.ofMillis(totalRuntime.longValue)

Yes, that’s correct. That was a typo on my part. Duration also has an abs() method that will return the absolute value of the Duration. That 's handy when you don’t know ahead of time which timestamp is earlier, or don’t care.

OH usually keeps up with Xtend so it’s probably supported. It wouldn’t work in this context though because we are dealing with Item states and NULL is not the same as null.

If you know programming I recommend either JS Scripting or jRuby (I use JS Scripting). If you are not a programmer I recommend Blockly. In all of these you get a more feature complete language to operate in and more access to OH than is available in Rules DSL (e.g. functions, libraries, access to Item metadata, rules that can call other rules, etc.). All three are also supported through the UI though Blockly, being graphical, is only supported through the UI.

Duration’s toString() outputs an ISO8601 duration formatted string. That one means 6 minutes, 1 second and 229392 nanoseconds (I think). 3 days, 4 hours, 5 minutes, 6 seconds would be P6DT4H5M6S. It’s really quite flexible and once you understand the “PT” part super easy to read.

JS Scripting uses the joda-js library which works almost the same as the Java ones only a bunch has been added to the helper library to auto convert stuff to ZonedDateTimes.

No that’s seconds and the decimal part is nanos. But like @JimT points out, you don’t need to do any math at all. That’s why I recommend it’s use. I cringe every time I see someone converting times to epoch and tracking milliseconds.

To elaborate on this a little bit. A Number:Time’s default units are a seconds and it supports the same state description patterns as a DateTime. So if you postUpdate

HeaterTotalRuntime.postUpdate(newDuration.toSeconds);

Or if you only wanted minutes you could

HeaterTotalRuntime.postUpdate(newDuration.toMinutes + ' min')

and then added a pattern like the following to the Item’s state description

%1$tH:%1$tM:%1$tS

On the UIs the value will be shown as HH:MM:SS.

This is one of those rare cases where you can have your cake and eat it too. Now you can get the nice pretty human readable view but you also can chart it because it’s just a number.

1 Like