I try to use the new best price calculation from Tibbe binding in OH5. I dont’ understand how to latestEnd/latestStop are supposed to work.
First in the docs latestStop is explained, the examples use lastestEnd.
From my expermients latestStop is just ignored. So I try lastestEnd.
What ever I tried in JavaScript, I ended up with kind of following exception:
2025-07-31 21:27:28.217 [ERROR] [.handler.AbstractScriptModuleHandler] - Script execution of rule with UID ‘003928037c’ failed: java.lang.ClassCastException: class com.oracle.truffle.polyglot.PolyglotMap cannot be cast to class java.time.Instant (com.oracle.truffle.polyglot.PolyglotMap is in unnamed module of loader org.eclipse.osgi.internal.loader.EquinoxClassLoader @387b7670; java.time.Instant is in module java.base of loader ‘bootstrap’)
This is my code
// Init Start Time for correct day
var runTimeSeconds = 2 \* 60 \* 60;
if (!(runTimeSeconds > 0)) {
console.info(“No valid runtime given. Cannot calulate start time.”);
} else {
let now = time.ZonedDateTime.now();
var latestEndTime = now.truncatedTo(time.ChronoUnit.HOURS).withHour(items.getItem(“Keba_Mode_Hour”).numericState);
if (now.isAfter(latestEndTime)) {
latestEndTime = latestEndTime.plusDays(1);
}
console.info(`Calculating cheap charging time ending latest at ${latestEndTime}. Charging for ${runTimeSeconds / 60} minutes`);
var tibberActions = actions.thingActions(“tibber”,“tibber:tibberapi:7d9b92db28”)
//create parameters for calculation
console.info(tibberActions.priceInfoEnd());
var parameters = {duration : ‘2 h 0 m’, latestEnd : latestEndTime, power : 11000};
console.info(parameters);
var result = JSON.parse(tibberActions.bestPricePeriod(parameters));
console.info(result);
var bestStartTime = result.cheapestStart;
console.info(`Calculated start time ${bestStartTime}.`);
/*
if (bestStartTime == null || bestStartTime.isBefore(now)) {
bestStartTime = now.plusSeconds(3);
}*/
console.info(`Preparing charging at ${bestStartTime}`);
items.getItem(“Keba_Next_Cheap_Time”).postUpdate(bestStartTime);
// items.getItem(“Keba_Next_Cheap_Price”).postUpdate(minCost \* 100);
}
OK, based on the error I don’t think this is a Tibber binding problem. If it is I cannot help.
The error appears to occur on the JSON.parse. The error is saying it cannot convert a PolyglotMap to an Instant, which doesn’t make a whole lot of sense.
Just log out what tibberActions.bestPricePeriod() is returning without trying to parse it first.
Because a java.lang.String which is what the action returns (based on the docs) and a JS String are not the same, it’s possible JSON.parse is running into problems parsing the Java String. But calling toString() can get to a JS String (it’s part of the magic that lets JS run on Java). So you can try that.
var Instant = Java.type("java.time.Instant");
var Duration = Java.type("java.time.Duration");
var HashMap = Java.type("java.util.HashMap");
var latestEnd = Instant.now().plusSeconds(20000);
// Get Tibber actions
var tibberActions = actions.get("tibber", "tibber:tibberapi:7d9b92db28");
var parameters = { "duration" : "2h 0 m", "latestEnd" : latestEnd};
// Call bestPricePeriod
var result = JSON.parse(tibberActions.bestPricePeriod(parameters));
console.info("Tibber result: ", result);
// Extract cheapestStart
var startsAt = Instant.parse(result.cheapestStart);
// Calculate seconds until start
var secondsTillStart = Duration.between(Instant.now(), startsAt).getSeconds();
if (startsAt.equals(Instant.MAX)) {
console.info("No valid startTime found, setting secondsTillStart to 0.");
secondsTillStart = 0;
}
console.info("Seconds till start: " + secondsTillStart);
I think it is a class loading issue. Using time.* implementations provided by OH and mixing them with the native Java types in the binding. If define the JS types for the required Java types like in the example it is working.
var Instant = Java.type("java.time.Instant");
var latestEnd = Instant.now().plusSeconds(20000);
// Get Tibber actions
var tibberActions = actions.get("tibber", "tibber:tibberapi:7d9b92db28");
var parameters = { "duration" : "2h 0 m", "latestEnd" : latestEnd};
// Call bestPricePeriod
var result = JSON.parse(tibberActions.bestPricePeriod(parameters));
console.info("Tibber result: ", result);
// Extract cheapestStart
var startsAt = time.toZDT(result.cheapestStart);
// Calculate seconds until start
var secondsTillStart = time.Duration.between(time.toZDT(), startsAt).seconds();
// I'm not sure how this part works, I suspect there are other tests that would be shorter
if (startsAt.toInstant().equals(time.Instant.MAX)) {
console.info("No valid startTime found, setting secondsTillStart to 0.");
secondsTillStart = 0;
}
console.info("Seconds till start: " + secondsTillStart);
Unfortunately I can’t think of a way to avoid importing Instant entirely to create the property passed to the Action.
For legibility I might use JS to initially create the instant and then convert that to the Java Instant at the last moment.
var Instant = Java.type("java.time.Instant");
var latestEnd = Instant.ofEpochSecond(time.toZDT("PT5H33M20S").toEpochSecond()); // 20000 seconds in ISO8601 format broken into hours, minutes and seconds to make it easier for humans to understand
It wouldn’t be the worst thing in the world to file an issue on the binding to see if the devs would be willing to let you optionally pass a String or epoch seconds there instead of requiring a Java class. That should make the action easier to use from Blockly and JS, maybe others.
Note, I constantly recommend against messing with epoch. This is one case among many where it makes sense.