Rounding a Number in a rule?

Rick,

I think so, here is a grab from the log,

2016-02-23 05:12:42.309 [INFO ] [runtime.busevents             ] - mysDistance state updated to 20
2016-02-23 05:12:42.313 [INFO ] [runtime.busevents             ] - mysDistanceupdate state updated to 2016-02-23T05:12:42
2016-02-23 05:12:42.314 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Water tank percent': Could not invoke method: java.lang.Math.round(float) on instance: null

The rule tries to run on an update to mysDistance.

Regards,
George

1 Like

Maybe try distance.floatValue in the calculation. Unfortunately the Rules DSL tends to treat pretty much every error as a null pointer error.

Rick,

Thanks for that.

I am getting this error when I changed it,

2016-02-24 17:39:57.377 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Water tank percent': Could not invoke method: java.lang.Math.round(float) on instance: null

Do I need to import anything particular to make this work?

Regards
George

Not sure what the problem here is. Try breaking the calculation up into multiple steps and add logging statements between each step so we can see which part of the calculation it doesn’t like.

Hey there,

I am experiencing the same problems while trying to get the average:

items:

if( Keller_Waschmaschine_Energie.historicState(now.minusMinutes(5)) != null) {
    //Mittelwerte
    var rmin1 = Keller_Waschmaschine_Energie.averageSince(now.minusMinutes(1))
    var rmin5 = Keller_Waschmaschine_Energie.averageSince(now.minusMinutes(5))
    logDebug(    "Keller", "RMin1: " + rmin1 + " RMin5: " + rmin5)
    
    //Mittelwerte
    var min1 = Keller_Waschmaschine_Energie.averageSince(now.minusMinutes(1)) as DecimalType
    var min5 = Keller_Waschmaschine_Energie.averageSince(now.minusMinutes(5)) as DecimalType
    logDebug(    "Keller", "Min1: " + min1 + " Min5: " + min5)
    
    logDebug(    "Keller", "Update1")
    Keller_Waschmaschine_Energie_1min.postUpdate( rmin1)
    Keller_Waschmaschine_Energie_5min.postUpdate( rmin5)
    
    logDebug(    "Keller", "Update2")
    Keller_Waschmaschine_Energie_1min.postUpdate( String::format( "%.1f", min1.floatValue()))
    Keller_Waschmaschine_Energie_5min.postUpdate( String::format( "%.1f", min5.floatValue()))
}

Output:

2016-02-25 19:04:08.443 [DEBUG] [rg.openhab.model.script.Keller] - RMin1: 56.02333333333333342807236476801335811614990234375 RMin5: 58.578571428571422075037844479084014892578125
2016-02-25 19:04:08.464 [DEBUG] [rg.openhab.model.script.Keller] - Min1: 56.02333333333333342807236476801335811614990234375 Min5: 58.578571428571422075037844479084014892578125
2016-02-25 19:04:08.467 [DEBUG] [rg.openhab.model.script.Keller] - Update1
2016-02-25 19:04:08.475 [DEBUG] [rg.openhab.model.script.Keller] - Update2
2016-02-25 19:04:08.482 [WARN ] [.c.i.events.EventPublisherImpl] - given new state is NULL, couldn't post update for 'Keller_Waschmaschine_Energie_1min'
2016-02-25 19:04:08.488 [WARN ] [.c.i.events.EventPublisherImpl] - given new state is NULL, couldn't post update for 'Keller_Waschmaschine_Energie_5min'

It seems that posting a string-update to a Number doesn’t work? :open_mouth:

If you have a String you can try the postUpdate action. Otherwise I think you need to parse the string yourself (e.g. Float::parseFloat(min1)).

How does the postUpdate differ from item.postUpdate?
I can successfully cast it to a float, but not post an item update! :frowning:

This is going to get down into the weeds a little bit, so bear with me. I put the key info in bold.

As you see, there are two ways to update an Item, four if you include sendCommand. Lets just focus on postUpdate for now. This all also applies for the difference between sendCommand and item.sendCommand.

The code in openHAB is Object Oriented (OO). In an OO language you have these things called Classes. A Class is a definition for collection of data and methods that operate on that data. When your program is running, an instance of a Class (i.e. a bundle with actual values for the data and functions to operate on that data) it is called an Object.

So the definition of the Class Item lists all the data and methods you can call on an Item Object. In your code, MyItem is an Object, an instance of the Item Class.

There is one powerful feature of OO languages and that is inheritance. With Inheritance you can create a parent Class (e.g. Item) which defines a bunch of generic things. Then you can create a sub-Class (e.g. Switch) which gets all the stuff from the Item class plus the opportunity to create some new data, members, or the ability to change the behavior of some of the methods the Item Class defined.

With Inheritance you can then in your code treat a Switch as if it were an Item which then lets you write code that doesn’t have to care about the difference between a Switch, a Contact, a Dimmer, etc. This is what the Rules Domain Specific Language (DSL) does, it treats all your Items as if they are Item objects.

All of that is background. The key piece of information is the line I bolded above. When the coders created the Switch, Contact, Dimmer, etc Classes, they wrote their own version of the postUpdate method customized for that particular Class. So when you call MySwitchItem.postUpdate(newState) on it runs different code than when you call MyDimmerItem.postUpdate(newState). And this different code can be smart and flexible in converting newState from various different Classes (e.g Strings, ints, OnOffType, etc.) into the specific type of Class the Item needs (e.g. Switch needs an OnOffType).

The postUpdate Action on the other hand is completely generic and has to work for all Item Classes. Consequently it is not able to be as smart about handling converting between random types that you might pass to it and the State Class the Item needs.** So the developers created only one postUpdate action that accepts two Strings, one that is the Item name and the second which is a String version of the new State.** The Rules DSL will sometimes hide this fact from you by automatically converting things to a String for you, but it isn’t always able to do that (e.g. when you try to pass a primitive int or float as the new State).

Therefore, because the Item.postUpdate method can be smarter and more flexible about converting stuff passed to it to the needed State I always recommend using Item.postUpdate wherever possible.

3 Likes

Thank you for your detailed explanation!
But as you can see in my code-snippet I am using the item.postUpdate already.
And it does not seem to be able to convert a value from a string, since it returns null.

This is most likely a bug. See the following Thread:

There are various threads about rounding.
But I still didn’t found a working universal solution.

I have numbervalues in rules. From calculations or from Item-states.

Now I want to display them in the same format - in this case with 1 decimal-place.

What is the shortest and easiest way to create a string of a number with exactly 1 decimal-digit?

In what context? Are you creating the string in a rule or are you just putting it on your sitemap?

If putting it on your sitemap just use the [%.1f] formatting option. E.g.

Text item=Temperature label="Temperature is [%.1f]"

In a rule you can use String::format method:

val myStr = String::format("Temperature is %.1f", Temperature.state as DecimalType)
1 Like

If you use JSR223 with jython :wink: here is something that works every time:

BusEvent.sendCommand( "Keller_Waschmaschine_Energie_1min",  "{:.1f}".format( float( str(avg_1))))

If you just want to display a rounded value in the sitemap then it’s what rlkoshak said. :slight_smile:

JSR223 with jython does not work with OH2

val KessTempStr = String::format("%.1f°C", (H_WP_Kesseltemperatur.state as DecimalType).floatValue())

did the trick for me…

I’m reviving this old thread just so this Rounding issue’s solutions might be in one place.

I am doing energy monitoring and the math is creating numbers like 249.363648099999973338954077917151153087615966796875. I want to send myself an email each morning with the use for yesterday, but obviously it is sending this super long number and I would like to round it. I have tried almost all the options I could find online and they all generate an error, usually about type mismatch. I have tried your string option now as well, but get this error:

2016-08-15 08:37:00.009 [ERROR] [.o.m.r.i.engine.ExecuteRuleJob] - Error during the execution of rule Send usage email
java.util.IllegalFormatConversionException: f != org.openhab.core.library.types.DecimalType

Here is my rule with my email address hidden of course. You’ll see I commented out some of the previous tries:

rule "Send usage email"

when
	Time cron "0 * * * * ?"
then
	val energyMail = String::format("Energy use is %.1f", Energy_UseDaily.state as DecimalType)
//	var energyMail = Energy_UseDaily.float as DecimalType
//	var tmp = (Math::round(energyMail*10.0)/10.0)
    sendMail("myemail", "Daily energy use", "Yesterday we used " + energyMail)
end

My Energy_UseDaily item looks like this:

Number      Energy_UseDaily            	"Today's use [%.3f kW]"           	<calculator>    (Energy)

Thanks.

Try:

(Energy_UseDaily.state as DecimalType).getFloat

It might be “asFloat” or “floatValue”.

The format method may not be able to handle Number objects and need a primitive.

Thanks, I got it. My working rule is:

rule "Send usage email"

when
	Time cron "0 * * * * ?"
then
	val energyMail = String::format("Energy use was %.1f kWh. ", (Energy_UseDaily.state as DecimalType).floatValue)
	val energyUnits = String::format("There are %.1f units left.", (Energy_Meter.state as DecimalType).floatValue)
    sendMail("myemail", "Daily energy use", "" + energyMail + energyUnits)
end

FWIW, I just struggled through a rounding-based issue so I thought I’d post it here.

I was trying to do a linear interpolation for setting my thermostat temperature based on a few different factors. However, I ultimately wanted to set it to a whole number, not a decimal, hence the rounding. Here’s what I ended up with:

var Number defaultSetPoint = SetPoint_F.state
var Number setPoint = AwaySetPoint_F.state
var Number outdoorTemp = OutdoorTemperature_F.state
var Number heatPumpCutoffHigh = 40
var Number heatPumpCutoffLow = 28

var multiplier = (heatPumpCutoffHigh - outdoorTemp) / (heatPumpCutoffHigh - heatPumpCutoffLow)

// this ends up being type BigDecimal
var setPointDec = sleepSetPoint + multiplier * (defaultSetPoint - sleepSetPoint)
setPoint = Math::round(setPointDec.floatValue())

sendCommand(Nest_temp_setpoint_f, setPoint)
1 Like
Group:Number:AVG gTemperatureAverage "Average Temperature [%.1f °C]" <temperature> (gTemperature)

say("Temperature is " + Math::round(Float::parseFloat(gTemperatureAverage.state.toString)))

Works for me

setPoint = setPointDec.intValue

I expect they’ve sorted it out by now, though.