I’m checking on an item with the following JS-Scripting rule:
...
var interval15min = time.ZonedDateTime.now().minusMinutes(15);
lastUpdate = items.getItem("KOS_TotalDCPower").persistence.lastUpdate('jdbc');
if (lastUpdate.isBeforeDateTime(interval15min)) {
// do some stuff
}
...
occasionally I get the following ERROR:
2024-09-10 09:45:00.853 [ERROR] [n.script.javascript.checkItemChanges] - Failed to execute script: TypeError: null has no such function "isBeforeDateTime"
at <js>.letztesUpdateIstPassiert(<eval>:23)
at <js>.:program(<eval>:38)
at org.graalvm.polyglot.Context.eval(Context.java:399)
at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458)
at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:426)
... 22 more
2024-09-10 09:45:00.855 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'checkItemChanges' failed: org.graalvm.polyglot.PolyglotException: TypeError: null has no such function "isBeforeDateTime"
whereas the events.log shows:
...
2024-09-10 09:44:59.112 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'KOS_TotalDCPower' changed from 756.3720703125 W to 769.3482666015625 W
2024-09-10 09:45:00.849 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'KOS_TotalDCPower' changed from 769.3482666015625 W to 762.6401977539062 W
2024-09-10 09:45:02.926 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'KOS_TotalDCPower' changed from 762.6401977539062 W to 761.4241333007812 W
...
According to the timestamps the item changes on 2024-09-10 09:45:00.849 whereas the script runs literally in parallel to that on 2024-09-10 09:45:00.853.
My bet is: the persistence query is fired at the same exact moment, the persistence is written and gets some NULL response? The rule runs regularly and doesn’t throw errors except on those coincidences.
e.g. yesterday
2024-09-09 15:40:00.847 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'KOS_TotalDCPower' changed from 757.0888671875 W to 748.7073364257812 W
2024-09-09 15:40:00.850 [ERROR] [n.script.javascript.checkItemChanges] - Failed to execute script: TypeError: null has no such function "isBeforeDateTime"
at <js>.letztesUpdateIstPassiert(<eval>:23)
at <js>.:program(<eval>:38)
at org.graalvm.polyglot.Context.eval(Context.java:399)
at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458)
at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:426)
... 22 more
I could of course test on a NULL value, but I’d rather have a solution on this without that workaround…
And honestly i don’t understand the “null if current Item state different from last persisted state”? I do ask the “lastUpdate()” and want to get exactly that information…?
For that specific use case I could wait for the release of the the “lastChanged”-argument on the Item itself which is in the works without the need of asking persistence. But I do have other cases, where I do rely on the lastChanged-argument explicitely from the persistence. So…
The arguments for implementing it the way it was implemented is if the Item’s current state is different from the most recent value saved to persistence, we can’t know when the lastUpdate occurred. OH doesn’t have “exactly that information”. All it knows is the Item changed sometime between now and the timestamp of the most recent value in the database. Depending on the persistence strategy that could be hours or days.
One proposal was to just return now when this happens but that proposal was shot down as invalid for several use cases. The compromise was to return null in this case to indicate that OH does not and cannot know when the last update occurred given the information at hand. No information was deemed better than wrong information in this case.
This whole situation is the reason why that PR was created in the first palce. There will always be times when we simply cannot get lastUpdate from persistence so it’s also being added to the Item.
Pay attention to @Udo_Hartmann’s post. The persistence extension is returning null, not the special Item state NULL.
Your only option is the work around. You can test for null returns and decide what to do then, whether it’s wait half a second and trying again (if your persistence strategy is such that it’s most likely that the reason for the null is the current state is in the process of being written to persistence) or use now or do something else.
You can also time any cron triggered rules so they are less likely to coincide with a write to persistence. For example, rrd4j writes on the minute so avoid running a cron triggered rule that depends on persistence a second 0 too; wait a second or so.
A few shorter ways to do this line could be one of:
var interval15min = time.toZDT('PT-15M');
var interval15min = time.toZDT(-1000*60*60);
var interval15min = time.toZDT().minusMinutes(15);
time.toZDT() returns now so that’s shorter than time.ZonedDateTime.now(). If it’s given duration string or something that can be interpreted as a number it adds that amount of time to now and returns that. If it’s given something that can be interpreted as a time or date time it converts that to a ZDT (e.g. time.toZDT('8:00 AM')).
This is an interesting information. I’m having the issue that the persistence of new value can take some time. In my case it takes usually up to 10s as I’m using a MySQL server in the cloud.
So I have checked if the
is not 0. Once it is not 0 any more I know that the value is persisted.
How can this be handled if lastUpdate is sometimes null? Maybe we need some kind of busy flag for the persistence service. Can anybody please send the link to the PR.
This is my code which is working pretty good until now.
var deltaTime = 0;
var persistenceNotAvailable = false;
// Calculate the difference between two timestamps of old and new reading
var i = 1;
var flag = true;
while(flag) {
java.lang.Thread.sleep(500);
deltaTime = time.Duration.between(itemEnergyReading.persistence.previousState(true, "jdbc").timestamp, itemEnergyReading.persistence.lastUpdate("jdbc")).toMillis();
if (deltaTime == 0) {
console.info("Persisting not finished, delta time: " + deltaTime + " ms");
} else {
console.info("Delta time between old and new meter reading: " + deltaTime + " ms");
flag=false;
}
if (i >= 120) {
console.info("No update. Exit rule.");
persistenceNotAvailable=true;
flag=false;
}
i = i + 1;
}
if (persistenceNotAvailable == true) { console.warn("Do nothing. Value of item not persisted after 60s:", itemEnergyReading.name); return; }