Hi.
Just upgraded from 3.4 stable to the latest 4.0 milestone and I’m surprised how much works out of the box. I’ve just found one problem and I guess it’s an easy fix for someone with higher conversion-fu. I get the following error in my log:
2023-07-07 13:00:00.789 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'ElforbrukningPerTimme' failed: Could not cast 31901.6 kWh to org.openhab.core.library.types.DecimalType; line 1, column 17, length 42
The rules code in question looks like this:
var nowValue = (ElectricMeterKWhTotal.state as DecimalType).floatValue
…and I might as well paste a screendump of the item in question:
It’s connected to a Qubino Z-Wave power meter. This all worked in 3.4 so I guess the problem lies in the new separation of value and unit, I’m just not sure how to fix it?
Pasting the entire rule for the sake of completeness. This is a rule saving my power consumption per hour to an item:
var nowValue = (ElectricMeterKWhTotal.state as DecimalType).floatValue
var thenValue = (ElectricMeterKWhTotal.historicState(now.minusHours(1)).state as DecimalType).floatValue
var diffValue = nowValue - thenValue
Elforbrukning_timme.sendCommand(diffValue)
The error is pretty clear. ElectricMeterKWhTotal is a Number:Energy Item which means it has units (i.e. it’s a QuantityType, not a DecimalType).
Based on the error it’s carrying kWh as the units.
In OH 4 implementation of units have been tightened up a lot. It’s no longer possible for a Number Item to ever carry a QuantityType. It’s no longer possible for a Number:X Item to not carry a QuantityType.
You’ve a few options.
Do the math keeping the units.
val nowValue = ElectricMeterKWhTotal.state as QuantityType<?>
val thenValue = new QuantityType(ElectricMeterKWhTotal.historicState(now.minusHours(1).state as Number, 'kWh')
val diffValue = nowValue.minus(thenValue)
Calls to persistence do not include units so it needs to be converted to a QuantityType
Throw out the units on the Item by changing it to be just a Number, not a Number:Energy
Throw out the units in the Rule
val nowValue = ElectricMeterKWhTotal.state as QuantityType<?>.floatValue
val thenValue = ElectricMeterKWhTotal.historicState(now.minusHours(1).state as Number
val diffValue = nowValue - thenValue
Note I’m not 100% on the syntax here. In JS Scripting the Item has a numericState and quantityState so the two versions of the rule would be
var nowValue = items.ElectricMeterKWhTotal.quantityState
var thenValue = Quantity(items.ElectricMeterKWhTotal.history.historicState(time.toZDT('PT1H')).state, 'kWh')
var diffValue = nowValue.subtract(thenValue)
or
var nowValue = items.ElectricMeterKWhTotal.numericState
var thenValue =
items.ElectrifMeterKWhTotal.history.historicState(time.toZDT('PT01H')).state
var diffValue = nowValue - thenValue
Why a command? Does this cause something to happen? A sensor reading like this should usually be an update, not a command. Only use commands when you mean “do this”.
Ok. I’ve always thought the types in OH are a bit confusing, I’m used to programming in languages where a variable just has a value, no unit. But I ended up with the following, which is quite close to your third alternative (and also very close to what I had before):
val nowValue = (ElectricMeterKWhTotal.state as QuantityType<?>).floatValue
val thenValue = (ElectricMeterKWhTotal.historicState(now.minusHours(1)).state as QuantityType<?>).floatValue
val diffValue = nowValue - thenValue
Elforbrukning_timme.postUpdate(diffValue)
The only thing I’m wondering about now are decimals. I guess it has with the different types to do, now if nowValue is say 31917.5 and thenValue is 31916.6 it calculates the diff to 0.9003906 which is close enough even though it’s not exactly right. But I only need it persisted with one decimal, how do I round it?
Good point. No, the only point is to get the value persisted, changed to a postUpdate().
It’s worth noting that now units are basically opt in. Even if the Channel says you need a Number:X you can link it to a Number and the units will be dropped.
My recommendation is if you don’t want to use units, don’t use units and define all your Items as Number. if you do want to use units, your rules code will be more flexible and self documenting if you work with units instead of dropping them.
That’s probably to do with how floating point is represented in primitive floats and doubles. Some values simply cannot be represented so the number is only really close with lots of decimal places.
One of the big dangers when doing calculations using IEEE floating point representations is if you round your calculations, you accumulate errors really fast to the point where the result is flat out wrong.
The call to floatValue gives you an IEEE floating point number.
The best way to deal with that is to not round until the last moment, which in this case would be for display. So you should do the rounding in the state description pattern and leave the actual number alone.
Note that if you keep the units and work with the QuantityTypes, or if you switch to Number and when with DecimalTypes cast to Number you will be working with a representation with a lot better fidelity in its ability to represent numbers. so instead of getting close, it gets it right more of the time.
It’s also possible that your .5 and .6 really isn’t that but it’s itself only close and is being rounded.
But in this case, if you are fine with incorrect calculations or are not using diff for calculations, round the value before updating the item.